feat(spec)!: mapping/agent/page 收紧,strictObject 不再把作者指向已删除的键(#4001 批 6a) - #4530
Merged
Merged
Conversation
…ing removed keys (#4001) The helper bug first, because this campaign put it there. `skill` closed last batch while still carrying `retiredKey` tombstones, and `strictObject` built its "did you mean" candidates from the whole shape — tombstones included. So a `triggerPhrase` typo was answered with "Did you mean `triggerPhrases`?", a key that had been REMOVED, and the author who complied got a second rejection telling them to delete what they had just been told to write. Third occurrence of a shape the ledger already records twice, and the first one in a SHARED helper, where it would have reached every conversion after it. Both helpers were correct alone; only the combination was wrong. Fixed structurally rather than by special-casing tombstones: never suggest a key the schema cannot accept. They stay complementary — `retiredKey` is stronger than `guidance`, since typing the key as `never` also fails `tsc` through a variable, where excess-property checking would not fire. `agent` had two security-shaped removals with no tombstone. `visibility` and `tenantId` were deleted as unenforced security properties — correctly — but without a prescription, because the shape was `.strip` and there was no rejection to hang one on. An author who wrote `visibility: 'private'` believed the agent was hidden; it was listed to everyone, and always had been. The `skill.permissions` class again. Both now name what actually gates an agent. `route` on a page was a fiction the platform's own suite carried. `stack.test.ts` authored `route: '/landing'` for years; `PageSchema` never declared it, and a page is routed by its `name` — which in the map format under test IS the map key, which the test asserted six lines below the key contradicting it. Fifth test found codifying a strip-era fiction, and the likeliest to be reinvented. `mapping` and `page` gain their ADR-0010 envelope. The undeclared-envelope debt list is down to two, from eight. The headline number is now derived instead of tallied — it had already drifted by one, in a campaign whose recurring finding is that hand-maintained coverage measurements go stale. `STILL_STRIP` carries a reverse pin like the envelope list; verified it goes red in both directions before being trusted. Registered types closed: 21 of 25. Still open: action, dashboard, field, view. The warning layer's covered roots drop 6 → 3, verified as a hand-off rather than a hole: `agent.zzz`, `page.zzz` and nested `page.regions[0].zzz` are each now rejected by the parse. A broken walk and a graduation shrink that count identically, so the check is pinned next to the number. Verified: 284 files / 7239 tests, tsc clean, 8 generated artifacts current, all spec gates green, and the CRM/Todo/showcase/platform-objects builds pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
os-zhuang
marked this pull request as ready for review
August 1, 2026 18:17
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-zhuang
enabled auto-merge
August 1, 2026 18:17
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:
|
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 批 6a(清单)。批 1–5 = #4514 / #4519 / #4522 / #4527 / #4528 / #4529,均已合并。
先修这场战役自己捅的娄子
上一批
skill关闭时,它还带着retiredKey墓碑。而strictObject的「你是不是想写」候选表取自整个 shape——墓碑也在里面。于是:triggerPhrases是被删掉的键。 照做的作者会撞上墓碑,拿到第二条拒绝,告诉他把刚被指示写下的东西删掉。账本里已经记了两次的形状,这是第三次——而且第一次出现在共享 helper 里,也就是说它会流向之后每一次转换。两个 helper 各自都是对的,只有组合起来是错的——这种缺陷没有任何单 schema 的测试会去找。
修法不是给墓碑开特例,而是一条更窄也更稳的规则:永远不要建议一个 schema 根本不接受的键。 候选表剔除接受
never的键(不管包了几层)。两者是互补的,不是重复:
retiredKey比guidance更强——它把键的类型变成never,所以即使配置是从变量传进来的也会tsc报错,而这正是 excess-property 检查够不到的地方。agent有两个安全形状的删除,没有墓碑visibility和tenantId当初作为未被强制执行的安全属性被删除——删得对,它们确实什么都没做——但删的时候没留处方,因为那时 shape 是.strip,根本没有一条拒绝可以挂。一个写下
visibility: 'private'的作者,以为这个 agent 被隐藏了。它对所有人可见,而且一直如此。又是
skill.permissions那一类:读起来像安全控制、实际不是、写下去还一声不吭。现在两条都指向真正门控 agent 的东西(access/permissions,自 #1884 起在 chat 路由上真正生效)。route—— 平台自己的测试套件里养了多年的虚构键stack.test.ts里写着route: '/landing'。PageSchema从来没有声明过route——页面由name路由,而在这个测试用的 map 格式里,name就是 map 的键,这正是同一个测试在六行之后断言的东西。第五个被发现「把 strip 时代的假象固化成预期」的测试,而且是最容易被重新发明的一个——
route是任何人在页面上第一个会去写的键。已连同path、url一起立碑。position.parent被接受object.namespace被剥离是对的compactLayout被剥离是对的skill.permissions被剥离是对的page.route被剥离是对的顺带立碑(因为现在终于有地方放了)
agent.memory.shortTerm—— 声明了一个运行时无人消费的工作记忆窗口(ADR-0013 D3)page.recordReview/page.blankLayout—— 没有渲染器的页面类型(framework#2265)interfaceConfig.visualization—— 显示方式是 visualization,不是 page type,从appearance.allowedVisualizations里运行时选guardrails.allowedTopics—— 没有白名单,只有blockedTopics黑名单闸门
mapping和page补上 ADR-0010 保护信封。信封欠债从 8 降到 2(只剩action、field)。头条数字改成推导,不再手数。 它已经飘了 1 ——在一场反复发现「手工维护的覆盖率度量会腐烂」的战役里。
STILL_STRIP带反向钉,和信封欠债表一样;并且我先验证了它能双向报红才敢信它:把已关闭的agent塞进去 → 红;把仍 strip 的view拿出来 → 红;恢复 → 绿。注册类型顶层已关闭:21 / 25。仍剥离:
action·dashboard·field·view告警层覆盖的根从 6 降到 3 —— 验证过是交接不是窟窿:
agent.zzz、page.zzz、嵌套的page.regions[0].zzz现在都被解析拒绝。一个坏掉的遍历和一次成功的毕业,会让这个数字以完全相同的方式变小,所以这条检查就钉在数字旁边。验证
@objectstack/spec:284 文件 / 7239 用例通过,tsc --noEmit干净check:*绿授权影响:这些形状没声明的键从「静默丢弃」变成「拒绝」——本来就已经被忽略,没有在工作的元数据会变。
参考
visibleWhen先例)、[P0][security] Agent access control is a no-op (permissions/visibility/access) #1884(agent 层权限门)、[P2][security] Enforce agentvisibility(organization/private) — context now has tenant (ADR-0095); needs owner/org anchor on the agent #1901 / [11.0][A2] Remove dead author-facing metadata properties (ADR-0049 enforce-or-remove) #2377(两个安全形状的删除)、framework#2265(无渲染器的页面类型)🤖 Generated with Claude Code
https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
Generated by Claude Code