Skip to content

fix(spec): 两条 area 退役处方在 #4722 之后改口径 —— 项级闸门在两棵树都由服务端剥离 (#4749) - #5336

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-4749-area-prescription-stale
Aug 5, 2026
Merged

fix(spec): 两条 area 退役处方在 #4722 之后改口径 —— 项级闸门在两棵树都由服务端剥离 (#4749)#5336
os-zhuang merged 2 commits into
mainfrom
claude/issue-4749-area-prescription-stale

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #4749

前提复核(改之前先证实)

issue 是线索不是规格,两半都在 origin/main(c7406b0)上核过 —— 引用的行号确实因今日
churn 移位了:

issue 的说法 origin/main 实际 结论
处方正文 app.zod.ts:662 仍写 "the server does not walk areas" 实际在 app.zod.ts:690 成立(行号移位)
pin 断言 app.test.ts:1374 实际在 app.test.ts:1439 成立(行号移位)
#4722 已让服务端走 areas[].navigation rest-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 点名的那条

原本说:

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.

改后陈述当前事实:项级 requiredPermissions / requiresService两棵树(顶层
navigation 与每一棵 areas[].navigation)都由服务端经同一个 filter 剥离(#4722),被闸住
的条目不会进入 /meta 响应体。

2. AREA_VISIBLE_RETIRED —— PM 复核时扩入本 PR 的姊妹条

issue 正文本就把「核对 AREA_VISIBLE_RETIRED」列进本单,预判是「大概率不用改」;核对结果
需要改。它收尾句把服务端强制的落点枚举为:

use requiredPermissions: on the app itself, or on items of the app's top-level
navigation tree.

其中没有假话(列出的两层确实被强制),但 #4722 之后这是一份漏了第三项的枚举,危害与上面
那句同构:一个已经站在 area 内部、本可以就地把闸门写在该 area 项上的作者,被劝去重构导航树。
现在两处落点都列全。

PM 的裁定理由(记录在案):它与第 1 条同文件、同常量块、同一类缺陷;另开 PR 按同文件串行
规则要等本 PR 合入,为六个词付一整轮;而且留着它 = 本 PR 把一条处方改对、把紧邻的另一条留错
—— 正是下面 JSDoc 那步所依据的同一个自相矛盾。

三件刻意保住的事

  1. filterAppForUser 只走 app 顶层 navigation —— areas[] 里的 nav 项权限过滤仅客户端生效(#4651 移除假闸门后剩下的真缺口) #4722 没有改变的那一半非对称性:visible(CEL)与 requiresObject 在任何层级依然
    只在客户端求值(服务端跑 CEL 需要读层没有的 user 绑定上下文)。这在 area 被服务端闸住
    之后反而更容易被误读成「现在写 visible 也安全了」;在第 2 条里风险更高 —— 作者是攥着
    一个 CEL 表达式走到那条报错前的,「就近改写成项级 visible」是此刻最顺手也最危险的落点。
    故两条正文都把分工写死:visible 隐藏的是已经发出去的条目,requiredPermissions
    让条目根本不被发出。两条都单独钉了 pin。
  2. 退役裁决不动:被强制的是 area 内部的项,area 键(app.areas[] 的 visible / requiredPermissions 是 fail-open 的访问闸门 —— 服务端从不走 areas(ADR-0049,v17 限时) #4651)保持退役、没有复活
    —— 第 1 条正文里明写 "the area-level key is not revived"。
  3. pin 是换钉不是删钉(两条同样处理)。断言钉在改正后的事实上,并各加一条对旧措辞的
    负向断言。只删旧断言会让一份「对 areas[] 闭口不谈」的处方照样通过,而作者读到闭口不谈时
    的默认结论正是旧边界。

措辞蓝本取自 #4722 已改写的 packages/spec/liveness/app.jsonareas.navigation note。

一处越出「仅处方字符串」的改动(PM 复核已批准)

app.zod.ts 里这两个常量正上方的 JSDoc,用现在时陈述了同一个已过期的事实
("reads … walks ONLY item.navigation … never touches item.areas at 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 两个改动文件 → clean
  • check:generated9/9 green(build 之后一轮全过)。check:api-surface 在全新
    worktree 上首轮报红是 AGENTS.md §9 记的 stale-dist 幻影,build 后即
    "public API surface + factory signatures unchanged"。没有生成物因本改动变 stale ——
    这两段文字只活在 Zod 的 error 字符串里,不投影进任何生成物,所以本 PR 不含重生成的产物。
  • 反向验证,两条各做一次,方向都是事前预测的「红」:
    • 还原第 1 条旧文本 → AssertionError: expected 'Unrecognized key(s) on this navigatio…' to match /BOTH trees/s,Tests 1 failed | 97 skipped
    • 只还原第 2 条旧文本 → AssertionError: expected 'Unrecognized key(s) on this navigatio…' to match /either navigation tree/s,Tests 1 failed | 97 skipped
    • 两次还原后复跑均全绿。
  • 消费半径扫过:packages/cli / packages/lint / packages/rest 及各 fixture 都没有钉这两段
    措辞的断言。
  • node scripts/check-nul-bytes.mjs OK,并对三个改动文件做了越出该 gate 的控制字符自扫。

仍留在 #5337 的越界发现(在本 PR 修)

packages/spec/src/migrations/registry.ts 的 protocol-17 rationale 仍写着 "since the server
does 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

… 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
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 4, 2026 11:52pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests protocol:ui tooling size/s labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…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
@github-actions github-actions Bot added size/m and removed size/s labels Aug 4, 2026
@os-zhuang os-zhuang changed the title fix(spec): areas[].requiredPermissions 处方改口径 —— 项级闸门在两棵树都由服务端剥离 (#4749) fix(spec): 两条 area 退役处方在 #4722 之后改口径 —— 项级闸门在两棵树都由服务端剥离 (#4749) Aug 4, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 5, 2026 00:23
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 553a47f Aug 5, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4749-area-prescription-stale branch August 5, 2026 00:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AREA_REQUIRED_PERMISSIONS_RETIRED 的处方在 #4722 之后过时:仍写着「the server does not walk areas

2 participants