fix(spec): 两条 area 退役处方在 #4722 之后改口径 —— 项级闸门在两棵树都由服务端剥离 (#4749) - #5336
Merged
Conversation
… stripped in BOTH trees (#4749) `AREA_REQUIRED_PERMISSIONS_RETIRED` is the only text an author who wrote an area-level gating key ever reads (it is the strict schema's unknown-key error body). It still claimed: Items nested under `areas[]` are gated in the shell only — the server does not walk `areas` — so anything that must never reach the browser belongs in the top-level tree, or in its own app. #4722 closed that gap: `filterAppForUser` now runs the same `filterNav` over every `areas[].navigation`, so a navigation ITEM's `requiredPermissions` / `requiresService` is enforced server-side in both trees and a gated entry never ships in the `/meta` body. The stale wording erred toward over-caution rather than danger — it sent authors to the top-level tree, which still works — but it was wrong in the one copy written FOR the author. The rewritten prescription states the current fact and keeps the half #4722 did NOT change: `visible` (CEL) and `requiresObject` are still evaluated client-side only at every level, so a must-never-ship gate goes in `requiredPermissions`, never in `visible`. That asymmetry gets newly tempting to misread once areas are server-gated, so it is spelled out and pinned. The retirement itself is untouched: what is enforced is the items INSIDE an area, not a revived area-LEVEL key (#4651 stands). - app.test.ts: the pin moves to the corrected wording rather than being deleted — a prescription that merely goes quiet about `areas[]` leaves the author believing the old boundary. Adds a negative assertion on the retired claim. - app.zod.ts: the JSDoc above these two constants asserted the same expired fact in the present tense; re-tensed as history plus one paragraph recording #4722, so the file does not contradict its own prescription. Generated artifacts: `check:generated` reports all 9 green — this prose lives only in a Zod error string and is projected into no generated artifact. Fixes #4749 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
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 4, 2026
…enumeration also missed areas[] (#4749) PM review widened this issue's file surface, correctly: #4749's body already listed "confirm AREA_VISIBLE_RETIRED" as in-scope work, and the confirmation came back needing a change rather than clearing it. `AREA_VISIBLE_RETIRED` closed by naming where a SERVER-enforced gate may live: use `requiredPermissions`: on the app itself, or on items of the app's top-level `navigation` tree. Nothing there is false — both named layers are enforced — but after #4722 it is an ENUMERATION missing its third entry, and the omission does the same damage as the sentence fixed in the previous commit: an author already standing inside an area is sent off to restructure their navigation tree for a gate they could now write in place. Both destinations are now named. What deliberately did NOT change is `visible`'s own verdict. #4722 touched `requiredPermissions` / `requiresService` and nothing else, so item-level `visible` is still CEL evaluated in the browser at every level. The prescription now states the division of labour outright — `visible` hides an entry that has already been sent, `requiredPermissions` stops it being served — because the author reaching this message is holding a CEL expression, which makes "just move it to the item's `visible`" the nearest and worst destination available. The retirement itself still stands: the area-LEVEL keys stay retired, and no wording here may be read as reviving them. - app.test.ts: same discipline as the first pin — re-nailed onto the corrected enumeration (`either navigation tree`, `areas[].navigation`, `#4722`), plus a positive pin on the surviving CEL semantics and a negative pin on the enumeration this replaced. - Changeset: extended the existing `.changeset/area-prescription-both-trees.md` rather than adding a second file. Out of scope by review decision and left on #5337: the protocol-17 migration rationale and its generated upgrade-guide projection, which need `gen:upgrade-guide` and an os-regen-driven artifact. Fixes #4749 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL
areas[].requiredPermissions 处方改口径 —— 项级闸门在两棵树都由服务端剥离 (#4749)
os-zhuang
marked this pull request as ready for review
August 5, 2026 00:23
This was referenced Aug 5, 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.
Fixes #4749
前提复核(改之前先证实)
issue 是线索不是规格,两半都在
origin/main(c7406b0)上核过 —— 引用的行号确实因今日churn 移位了:
app.zod.ts:662仍写 "the server does not walkareas"app.zod.ts:690app.test.ts:1374app.test.ts:1439areas[].navigationrest-server.ts:1811的[#4722] Applies the SAME item gate to every areas[].navigation tree,实现在 1890 行的filterAreas(复用同一个filterNav)所以
premise_still_valid: true,处方正文把一个已经关闭的缺口描述成仍然存在。改了什么(两条处方,一次改完)
1.
AREA_REQUIRED_PERMISSIONS_RETIRED—— issue 点名的那条原本说:
改后陈述当前事实:项级
requiredPermissions/requiresService在两棵树(顶层navigation与每一棵areas[].navigation)都由服务端经同一个 filter 剥离(#4722),被闸住的条目不会进入
/meta响应体。2.
AREA_VISIBLE_RETIRED—— PM 复核时扩入本 PR 的姊妹条issue 正文本就把「核对
AREA_VISIBLE_RETIRED」列进本单,预判是「大概率不用改」;核对结果是需要改。它收尾句把服务端强制的落点枚举为:
其中没有假话(列出的两层确实被强制),但 #4722 之后这是一份漏了第三项的枚举,危害与上面
那句同构:一个已经站在 area 内部、本可以就地把闸门写在该 area 项上的作者,被劝去重构导航树。
现在两处落点都列全。
PM 的裁定理由(记录在案):它与第 1 条同文件、同常量块、同一类缺陷;另开 PR 按同文件串行
规则要等本 PR 合入,为六个词付一整轮;而且留着它 = 本 PR 把一条处方改对、把紧邻的另一条留错
—— 正是下面 JSDoc 那步所依据的同一个自相矛盾。
三件刻意保住的事
visible(CEL)与requiresObject在任何层级依然只在客户端求值(服务端跑 CEL 需要读层没有的
user绑定上下文)。这在 area 被服务端闸住之后反而更容易被误读成「现在写
visible也安全了」;在第 2 条里风险更高 —— 作者是攥着一个 CEL 表达式走到那条报错前的,「就近改写成项级
visible」是此刻最顺手也最危险的落点。故两条正文都把分工写死:
visible隐藏的是已经发出去的条目,requiredPermissions才让条目根本不被发出。两条都单独钉了 pin。
—— 第 1 条正文里明写 "the area-level key is not revived"。
负向断言。只删旧断言会让一份「对
areas[]闭口不谈」的处方照样通过,而作者读到闭口不谈时的默认结论正是旧边界。
措辞蓝本取自 #4722 已改写的
packages/spec/liveness/app.json的areas.navigationnote。一处越出「仅处方字符串」的改动(PM 复核已批准)
app.zod.ts里这两个常量正上方的 JSDoc,用现在时陈述了同一个已过期的事实("reads … walks ONLY
item.navigation… never touchesitem.areasat all")。只改字符串会让同一段代码自相矛盾。故把该段改为过去时的退役当时状态,并补一段记录 #4722。没有动
schema 形状、没有动任何别的键。
验证
vitest run src/ui/app.test.ts→ 98 passed;全量pnpm --filter @objectstack/spec test→ 312 files / 8022 tests passed(两轮改动后各跑一次,均全绿)
pnpm --filter @objectstack/spec typecheck→ clean;eslint两个改动文件 → cleancheck:generated→ 9/9 green(build 之后一轮全过)。check:api-surface在全新worktree 上首轮报红是 AGENTS.md §9 记的 stale-dist 幻影,
build后即"public API surface + factory signatures unchanged"。没有生成物因本改动变 stale ——
这两段文字只活在 Zod 的 error 字符串里,不投影进任何生成物,所以本 PR 不含重生成的产物。
AssertionError: expected 'Unrecognized key(s) on this navigatio…' to match /BOTH trees/s,Tests 1 failed | 97 skippedAssertionError: expected 'Unrecognized key(s) on this navigatio…' to match /either navigation tree/s,Tests 1 failed | 97 skippedpackages/cli/packages/lint/packages/rest及各 fixture 都没有钉这两段措辞的断言。
node scripts/check-nul-bytes.mjsOK,并对三个改动文件做了越出该 gate 的控制字符自扫。仍留在 #5337 的越界发现(不在本 PR 修)
packages/spec/src/migrations/registry.ts的 protocol-17 rationale 仍写着 "since the serverdoes not walk
areas",并投影进生成的docs/protocol-upgrade-guide.md(升级中的作者正在读的那份);同一句也留在待发布的
.changeset/app-area-fail-open-gates-removed.md。按 PM 复核裁定留给后续单:它要跑
gen:upgrade-guide,且该文档走 os-regen 驱动,不该混进本 PR。🤖 Generated with Claude Code
https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL