feat(spec)!: action 收紧,ADR-0010 信封欠债归零(#4001 批 6d) - #4533
Merged
Conversation
…es zero (#4001) `ActionParamSchema` has been strict since #3746 — the template this campaign was generalized from, and the source of its sharpest lesson: `visibleWhen` → `visible` showed the most valuable alias entry is rarely a typo but a key that reads as a control and silently is not one. The action AROUND the param stayed open for three more releases. The AI exposure block is why this one mattered. `ActionAiSchema` is the governance gate — its own doc says a half-finished or unreviewed action must never be silently armed — yet `requireConfirmation`, one letter off, was dropped in silence, so an author who asked for a human-in-the-loop gate on an AI-invoked action did not get one and was not told. The root tombstones the key this campaign cares most about. `permissions` on an action is NOT a permission gate: authorization comes from the object's permission sets and, on the AI surface, from the agent's access/permissions (#1884). The rejection says so and names the trap beside it — `visible` and `disabled` are UI predicates, they hide or grey a button, they do not stop a request. Third time in this campaign after skill.permissions and agent.visibility, always a different surface, always reading like a control. The undeclared-envelope debt list is now EMPTY. The structural walk opened it with eight names after replacing a probe that had been hiding seven; `action` was the last. The empty set is kept, not deleted: with no exemptions the DECLARES case runs over every registered type, so a new type shipping without the spread fails immediately instead of being quietly added to a list. Registered types closed: 24 of 25. Only `view` remains. Two lint-layer facts recorded rather than papered over: the array-index test has run out of subject (no strip-mode array-of-objects remains anywhere), and `view` is the last open root — when it closes, change the floor to 0 and assert the empty set deliberately rather than deleting the test, because an empty result nobody chose is indistinguishable from a derivation that broke. Verified: 284 files / 7240 tests, tsc clean, 8 generated artifacts current, all spec gates green, and CRM/Todo/showcase/platform-objects build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-zhuang
marked this pull request as ready for review
August 1, 2026 19:41
os-zhuang
enabled auto-merge
August 1, 2026 19:42
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…s AT it (#4001) The first draft of this batch's guidance claimed `permissions` on an action is not a permission gate and sent authors to the object's permission sets. That is wrong: `requiredPermissions` (ADR-0066 D4) is a declared key enforced with a 403 on the platform action route, and `ui/actions.mdx` teaches it by name. Anyone following the prescription would have been invited to delete a working gate. - `permissions` / `capabilities` / `requiresPermissions` / `acl` now RENAME onto `requiredPermissions` instead of pointing away from it. - The dead `requiredPermissions` guidance entry is removed — the key is declared, so strict never reaches it; the entry was wrong AND unreachable. - `location` → `locations` was backwards: `locations` is the declared key, so the alias would have suggested renaming a correct key to a nonexistent one. - The `visible`/`disabled` warning survives on `hidden`, where it belongs: hiding is not gating, and a headless action is `locations: []`. Ledger finding 7 for the fourth time — this campaign's own prescriptions are a surface that can be confidently wrong. Caught by checking the docs the drift report flagged against the schema rather than against memory of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
…e key is `objectName` (#4001) `ActionSchema` has never declared `object` — the key is `objectName`. Two stored-row fixtures wrote it anyway, `.strip` ate it, and the tests passed. With the shape closed the row now fails spec validation after conversion and the protocol reports `invalid: 1`. Worth naming because of where it was: these fixtures stand in for pre-17 rows in a database, so a typo in one reads as a claim about real legacy data — "stored actions carry `object`" — when no such key ever existed. There is no missing conversion here; `git log -S` finds no commit that ever declared it. Sixth strip-era fiction this campaign has found in a test, and the first dressed as a stored ROW rather than an authored literal. Also the reason this was caught by CI rather than locally: the previous pass ran only `packages/spec`. The blast radius of closing a registered type is every package that parses that type. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
This was referenced Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#4001 批 6d(清单)。批 1–6c = #4514 → #4532,均已合并。
模板自己一直没被应用到
ActionParamSchema从 #3746 起就是 strict——这场战役就是从它推广出来的,而且它贡献了最锋利的那条教训:而 param 外面的那个 action,又开了三个 release。
为什么这一个特别要紧:AI 暴露块
ActionAiSchema是治理闸门。它自己的文档写着:平台的价值在于「一个人类能精确治理 agent 队伍被允许调用哪些能力」,以及「一个半成品或未经审阅的 action 绝不能被静默武装」。然而 ——
requireConfirmation(比requiresConfirmation少一个字母)会被静默丢弃。一个作者要求在 AI 调用这个 action 时插入人类审批环节,他没有得到,而且没有人告诉他。
现在这个块和 action 根都会拒绝,并且对作者最容易写错层级的两个键给出处方(
exposed和requiresConfirmation属于ai之下)。两个形状相同:对着「我记得的 schema」判断,而不是对着 schema。都已修正(commit 2、commit 3)。
错误一:一条会让人删掉权限门的处方
第一版 guidance 写的是「
permissions写在 action 上不是权限门」,并把作者引向对象的权限集。这是错的,而且错在最要命的方向。
requiredPermissions(ADR-0066 D4)是一个已声明的键,在平台 action 路由上以 403 强制执行,而content/docs/ui/actions.mdx就在教它:任何照着我那条处方做的人,都会被引导去删掉一个正在工作的权限门。
permissions/capabilities/aclrequiredPermissionsrequiredPermissions的 guidance 条目location→locationslocations才是声明的键,原别名会建议把正确的键改成不存在的键visible/disabled那句警告hidden上——隐藏不等于门控,无 UI 的 action 是locations: [],依然有门账本第 7 条的第四次:这场战役自己的处方,本身就是一个可能自信地写错的表面。
抓到它的是那份 docs-drift 报告——那份我一直当例行公事的「107 个文件、仅供参考」清单,逐条对着 schema 核的时候,
ui/actions.mdx正好教了那个键。错误二:本地只跑了
packages/specCI 的
Test Core挂了 ——@objectstack/metadata-protocol两个 fixture 在 action 上写了object: 'crm_invoice',而ActionSchema从未声明过object,键是objectName。git log -S确认没有任何 commit 曾声明过它,所以不是缺转换链,就是打错的键。值得记的是它在哪儿:这两个 fixture 扮演「数据库里的 pre-17 行」,所以一个打错的键读起来像一个关于真实历史数据的断言——「存量 action 带
object」——而那个键从未存在过。第六个 strip 时代的假象,第一个伪装成「存量行」而不是「作者写的字面量」的。
教训直白:关闭一个注册类型的影响半径,是每一个解析那个类型的包,不是
packages/spec一个。🎯 ADR-0010 信封欠债归零
结构化遍历当初打开这张表时有八个名字(
actionbookfieldjobmappingpagetranslationvalidation)——而它替换掉的那个探针,藏了其中七个。action是最后一个。空集合被保留,而不是删掉。 没有豁免项之后,
DECLARES the protection envelope这条会跑遍每一个注册类型——新类型漏了那个 spread 会立刻报红,而不是被悄悄加进一张豁免表。进度
注册类型顶层已关闭:24 / 25。只剩
view。两件记下来而不是抹掉的事
1. 数组下标那个测试已经没有测试对象了。
action关闭之后,整个注册面上再没有任何仍是 strip 的「对象数组」。遍历器的数组处理没变、依然正确——消失的是能触发它的元数据类型。测试改为断言交接 + 已关闭根下的逐节点下降(#4522),并在原地写明:将来出现新的 strip 表面就把带下标的断言加回来。2.
view是最后一个开着的根。 它关闭后这一层在根层面就没有可警告的了。测试里写明:把下限改成 0 并「刻意」断言空集,不要删掉这个测试——因为一个没人选择的空结果,和一个坏掉的推导,长得一模一样。验证
@objectstack/spec:284 文件 / 7240 用例,tsc --noEmit干净check:*全绿requiredPermissions+locations: []被接受;permissions/capabilities改名到requiredPermissions;location改名到locations授权影响:
ActionSchema没声明的键从「静默丢弃」变成「拒绝」——本来就已经被忽略。参考
visibleWhen先例)、[P0][security] Agent access control is a no-op (permissions/visibility/access) #1884(agent 层权限门)、feat(spec)!: 六个注册类型收紧,skill 的 permissions 不再假装是权限门(#4001 批 4) #4528 / feat(spec)!: mapping/agent/page 收紧,strictObject 不再把作者指向已删除的键(#4001 批 6a) #4530(同一类的前两次)、ADR-0010、ADR-0064、ADR-0066 D4(action 的requiredPermissions,403 强制)🤖 Generated with Claude Code
https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY