Skip to content

feat(spec)!: 移除 app.areas[] 的两个 fail-open 访问闸门 —— visible / requiredPermissions (#4651) - #4718

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-4651-app-areas-fail-open
Aug 2, 2026
Merged

feat(spec)!: 移除 app.areas[] 的两个 fail-open 访问闸门 —— visible / requiredPermissions (#4651)#4718
os-zhuang merged 2 commits into
mainfrom
claude/issue-4651-app-areas-fail-open

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #4651

按维护者裁决走 路线 B(移除):把 app.areas[].visibleapp.areas[].requiredPermissions 从作者契约里真正退役。这两个键不是普通死键,是fail-open 的访问闸门——作者写下它们会得到干净的 parse、被存下来的值,以及一个对所有人可见的 area。


一、两键零消费者:证据链

服务端权威闸门 filterAppForUserpackages/rest/src/rest-server.ts):

做什么
:1814 app 级 requiredPermissions —— 不满足直接 return null,整个 app 从 /meta 消失
:1823 const nav = Array.isArray(item.navigation) ? item.navigation : null; if (!nav) return item;
:1830 filterNav 递归检 nav 项级 requiredPermissions / requiresService

item.areas 从头到尾一个字都没读——:1823navigation 缺席时直接早返回。客户端(objectui packages/layout/src/NavigationRenderer.tsx:894)同样只对 nav item 做 checkPerm,area 切换器渲染每一个 area。

它为什么读起来像活的:同名的兄弟键上下两层都是真的。app 级 requiredPermissions 服务端强制,nav 项级 requiredPermissions / requiresService 服务端剥离 + 客户端复检,nav 项级 visible 是真的 CEL 闸门。三层里中间那层是演的——ADR-0078 false compliance,与 #4583 capabilities.readOnly 同一个模子。

全仓复核过其余可能的消费者,没有一个读这两个键:packages/lint/src/validate-capability-references.ts 会走进 rec.areas,但那是为了够到 area 里面的 nav 项(它只是泛型地 flag 每个节点的 requiredPermissions,area 节点从此不再带这个键就不会命中);validate-nav-access / validate-nav-target-refs / validate-object-references / validate-action-name-refs / validate-translation-references / cli doctor / stack.zod.ts 一律只读 area.navigation。该文件的注释已按实情更正(仍保留遍历,删掉遍历会让 area 内的每一项失去能力校验)。

未采纳 A(enforce),理由不是它错,是它先要定语义:area 被过滤后其下 nav 项整体消失还是仍按自身权限参与其它 area?服务端 CEL 求值上下文有没有 user 绑定?那本身是一轮决策,v17 窗口塞不下;而 B 是破坏性的,错过 changeset pre exit 就要让这个 fail-open 闸门带着整个 17.x 跑。一个从不 gate 的闸门,删掉严格安全于留着。

二、迁移处方(strict 删除路线)

NavigationAreaSchema.strict(),所以走「删键 + guidance 处方」,不是 tombstone。处方不说「已移除」了事,而是点名真正生效的两层:

FROM 处方
areas[].requiredPermissions 删键。把闸门下沉到 area 内每个 navigation 项的 requiredPermissions / requiresService,或上浮到 AppSchema 的 requiredPermissions
areas[].visible 删键。同一段 CEL 搬到 area 内的 nav 项上——nav 项的 visible 是逐项求值的

处方里带了一条诚实的限定,而不是藏起来:area 里面的 per-item 闸门只由客户端强制(服务端不走 areas),所以「绝不能到达浏览器」的东西要放进 app 的顶层 navigation 树、或者独立成一个 app。把一个错误的安全感换成一个更弱的安全感,就是重犯本单要终结的那个错误。

三个别名拼写 visibleWhen / visibleOn / permissions 原本 rename 到这两个键上。键都没了,别名就成了「用一个未知键回答另一个未知键」,因此全部改成 guidance 处方(#4667sort 先例)。

三、ADR-0087 登记

新增独立 conversion app-area-fail-open-gates-removedtoMajor: 17retiredFromLoadPath: true,surface app.areas.visible / app.areas.requiredPermissions),并接进 protocol-17 的 D3 chain step(conversionIds + rationale 各加一段)。

为什么不并进隔壁的 app-dead-authoring-keys-removed:那条的 summary 是一串「无人读的作者键」,而这两个是安全发现;summary 字符串正是 spec-changes.json、生成的升级指南和 spec_changes MCP 工具端给升级者看的东西。并进去,「一个从不 gate 的闸门被移除了」会被埋在讲 versionmobileNavigation 的句子里——而它恰恰是这个 release 里带 gated area 的作者唯一必须读到的一行。

fixture 刻意做了两个 area:一个声明了两个假闸门(被剥离,2 条 notice),一个把同名的真闸门写在里面的 nav 项上(必须原样保留)。按键名盲扫会把能用的闸门跟假的一起删掉——见下面 sabotage ⑥b。

⛔ 没有把这两个键写进 RENAMED_DEFS:那张表的不变式是「没有 key 离开契约」,本单是真退役,authorable-surface.json 净减 2 个 key

四、两本账:实跑结论,不是假设

活性账本(packages/spec/liveness/app.json)——必须改,两行删掉。 实证(先把两行放回去再跑):

✗ 2 ORPHAN ledger row(s) — the property is gone from the schema:
    app/areas.visible
    app/areas.requiredPermissions

_noteareas 的 note 同步收口,nav 项 visible 的 note 从「对照 areas[].visible,它不是」改成指向本次处方的落点。liveness/README.md 的 app 行按 check-liveness.mts --json 重新生成:dead 11 → 9(原文 Notes 里写的 "Dead 12" 本身也是旧的,一并改对)。

严格性台账(docs/audits/2026-07-unknown-key-strictness-ledger.md)——不用改,且已实跑确认。 check:strictness-ledger 数的是每个文件的 z.object( 站点数(脚本自己的说明:site counts are z.object( occurrences per file),本单是在一个既有站点里删键,站点数零变化;ui/app.zod.ts 的 18 与「strict as of #4001 PR B」的断言都仍然成立(schema 依然 .strict())。C3 的教训针对的是删掉 .strict() 形状,本单不是那种。gate 实跑 PASS

五、生成物

authorable-surface.json 没有手编:直接 rm 掉整个文件再跑 gen:schema 重新生成,diff 正好是那两行(8262 → 8260 keys)。

顺带留个记录给 #4650:strict 删除路线下,gate (a) 会先红:

❌ 2 authorable key(s) disappeared from the contract:
     - ui/NavigationArea:requiredPermissions
     - ui/NavigationArea:visible

它无法区分「合法的 strict 退役」和「删基线行洗掉证据」——正是 #4650 要堵的洞。本单的删除是合法的那种(有 conversion、有 guidance、有 changeset),全部证据在这个 PR 里。

check:generated --fix 只重生了它证明过期的三个:spec-changes.jsondocs/protocol-upgrade-guide.mdcontent/docs/references/**。⛔ 没碰 content/docs/releases/

六、每条 pin 都实际破坏过一次

新增 pin 全部做了 sabotage 实证(下面都是真实输出摘录,不是「加完是绿的」)。

① 把两个键加回 schema → parse pin + 类型 pin 一起红:

FAIL src/ui/app.test.ts > … > rejects `areas[].requiredPermissions` and names the layers that DO enforce
FAIL src/ui/app.test.ts > … > rejects `areas[].visible` and points at the item-level CEL gate that is evaluated
FAIL src/ui/app.test.ts > … > the retired gates are gone from the TYPE, not only from the parse (compiler API)
AssertionError: expected [ 'description', 'icon', 'id', …(4) ] to deeply equal [ 'description', 'icon', 'id', …(2) ]
+   "requiredPermissions",
+   "visible",
      Tests  3 failed | 3 passed | 89 skipped (95)

② 走错路线:改成 retiredKey() tombstone 而不是 strict 删除(parse 仍然拒绝,但键回到 TYPE 里)→ 类型 pin 照样红:

FAIL src/ui/app.test.ts > … > the retired gates are gone from the TYPE, not only from the parse (compiler API)
AssertionError: expected [ 'description', 'icon', 'id', …(4) ] to deeply equal [ 'description', 'icon', 'id', …(2) ]
+   "requiredPermissions",
+   "visible",

这条是本单类型 pin 存在的理由。#4642 已证本包编译期 pin 空转tsconfig.json 排除 **/*.test.ts、vitest 不开 typecheck),所以类型断言写成 Assert< Equal< … > > 根本不会跑。这里用 TypeScript compiler API 解析符号身份、读 NavigationArea 的成员列表,并带两道防空转守卫:模块符号必须解析成功,且成员列表必须全等 ['description','icon','id','label','navigation']——后者防的是 z.infer 退化成 any 时成员数为 0、两条 not.toContain 空过。

③ 阉掉 app 级闸门rest-server.ts:1814)→ 真闸门 pin 红:

FAIL src/rest.test.ts > filterAppForUser — the enforced permission layers (#4651) > APP level: an app whose requiredPermissions the caller lacks is dropped entirely
AssertionError: expected { name: 'crm', …(2) } to be null
FAIL … > APP level: every declared permission is required, not any of them
      Tests  2 failed | 3 passed | 185 skipped (190)

④ 阉掉 nav 项级闸门rest-server.ts:1830)→ 同一组 pin 红:

FAIL src/rest.test.ts > … > ITEM level: nav entries the caller cannot satisfy are stripped from the served tree
AssertionError: expected [ 'nav_leads', 'nav_forecast', …(1) ] to deeply equal [ 'nav_leads', 'grp_admin' ]
FAIL … > a group left empty by the item gate is dropped, not served as a bare label
AssertionError: expected [ 'grp_admin' ] to deeply equal []

③④ 是本单的第二道要求:删掉假闸门之后,必须有人盯着真闸门还在不在,否则这次移除只是把「有闸门的错觉」换成「没人检查闸门」。

⑤ 把 conversion 从 D3 chain step 里摘掉 → chain-replay 红(这就是「没接线」的表现形式):

FAIL src/migrations/migrations.test.ts > … > app-area-fail-open-gates-removed: fixture.before → fixture.after via the chain
      Tests  1 failed | 169 passed (170)

⑥a expectedNotices 2 → 4 → fixture 红:

FAIL src/conversions/conversions.test.ts > … > app-area-fail-open-gates-removed: before → after, emits 4 notice(s)
AssertionError: expected [ { …(9) }, { …(9) } ] to have a length of 4 but got 2

⑥b 让 conversion 按键名盲扫、连 area 内 nav 项上的真闸门一起剥 → fixture + chain 一起红:

FAIL src/conversions/conversions.test.ts > … > app-area-fail-open-gates-removed: before → after, emits 2 notice(s)
FAIL src/migrations/migrations.test.ts > … > app-area-fail-open-gates-removed: fixture.before → fixture.after via the chain
      Tests  2 failed | 168 passed (170)

⑦ 把两条账本行放回去(带 authorWarn → CLI 顾问 lint 的静默 pin 红:

FAIL src/utils/lint-liveness-properties.test.ts > … > is silent on the fail-open area gates — retired in 17.0.0 (#4651)
FAIL src/utils/lint-liveness-properties.test.ts > … > the area-gate silence is a real verdict, not a lint that stopped loading ledgers
      Tests  2 failed | 19 passed (21)

CLI 那条从「warn pin」翻成「silence pin」,并加了一条防空转守卫lintLivenessProperties 找不到账本目录时返回 [],「没有 finding」和「lint 坏了」长得一模一样,所以同一次调用里同时写入 object.externalSharingModel(全仓最后一条 authorWarn),要求它仍然报警。

七、changeset 定级理由

@objectstack/spec major,而且据实写了迁移,没有照抄 C9(#4695)/C12(#4710) 的「零元数据迁移」——那个结论只对改名成立。本单是真退役:这两个是作者可写的元数据键,schema 是 .strict(),既有 app 元数据带着它们会解析失败authorable-surface.json 净减 2 个 key。changeset 正文给出 FROM → TO 表、一行修法、os migrate meta --from 16,以及那条「area 内 per-item 闸门只有客户端强制」的限定。

八、门禁

门禁 结果
@objectstack/spec build PASS
check:generated(8 项) PASS
check:spec-changes / check:upgrade-guide / check:authorable-surface / check:api-surface / check:docs PASS
check:liveness / check:empty-state / check:strictness-ledger PASS
check:dual-source-exports PASS(基线零改动,本单不碰双源)
check:i18n / check:doc-authoring / check:release-notes / check:adr-anchors PASS
spec test 293 files / 7369 tests passed
rest / cli / lint test 37 + 70 + 46 files,559 + 712 + 778 tests passed
全仓 turbo run typecheck PASS —— Tasks: 122 successful, 122 total
全仓 turbo run test PASS —— Tasks: 133 successful, 133 total

九、顺带


🤖 Generated with Claude Code

https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9


Generated by Claude Code

os-zhuang and others added 2 commits August 2, 2026 20:45
`app.areas[].visible` and `app.areas[].requiredPermissions` leave the authorable
contract. They were not ordinary dead keys: they were capability gates that
FAILED OPEN. `filterAppForUser` (packages/rest/src/rest-server.ts) checks the
app's `requiredPermissions`, then walks ONLY `item.navigation` — it returns
early when that tree is absent and never reads `item.areas` at all — while the
client renders every area in the switcher. An author writing
`requiredPermissions: ['sales.admin']` on an area got a clean parse, a stored
value, and an area visible to everyone.

What made them read alive is that the SAME names are enforced one level up and
one level down: app-level `requiredPermissions` drops the whole app server-side,
and a navigation ITEM's `requiredPermissions` / `requiresService` are stripped
server-side and re-checked in the shell, whose item-level `visible` is a real
CEL gate. Three layers, of which the middle one was theatre — ADR-0078 false
compliance, the `capabilities.readOnly` shape (#4583).

Route B (remove) per the maintainer's ruling. Route A (enforce) is not wrong,
it is unscoped: area filtering needs semantics decided first (does filtering an
area remove its items everywhere? does the server bind `user` for area CEL?),
which the 17.0.0 window cannot hold — and a retirement PR must not invent an
authorization mechanism. Shipping the major with the gate still declared would
have kept authors writing it for all of 17.x.

`NavigationAreaSchema` is `.strict()`, so this is the strict-deletion route:
the keys leave the shape and two `guidance` prescriptions carry the upgrade.
The prescriptions name the layers that DO enforce rather than saying "removed",
and carry the caveat that per-item gating INSIDE an area is shell-side only —
trading one false belief for a weaker one would repeat the defect. The three
alias spellings that used to RENAME onto these keys (`visibleWhen`, `visibleOn`,
`permissions`) become prescriptions too: an alias pointing at a key that is
itself gone answers an unknown key with a second unknown key (#4667 `sort`
precedent).

ADR-0087: a dedicated `app-area-fail-open-gates-removed` conversion rather than
two more keys on `app-dead-authoring-keys-removed`. That entry's summary is a
list of inert authoring keys; this is a security finding, and the summary string
is what spec-changes.json, the upgrade guide and the `spec_changes` MCP tool
serve — folded in, "a gate that never gated has been removed" would arrive
buried in a sentence about `version` and `mobileNavigation`. Wired into the
protocol-17 D3 chain step with its rationale.

Ledgers, per route: the liveness rows are DELETED (strict removal — the keys
left the walked shape, and retained rows report ORPHAN), and the CLI advisory
lint falls silent by itself, its test flipped from warn-pin to silence-pin with
an anti-vacuity guard. The strictness ledger needs no change and was not
touched: `check:strictness-ledger` counts `z.object(` SITES per file, and this
removes keys from an existing site — the C3 lesson applies to deleting a
`.strict()` shape, which this is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
…4651)

The `app` row's counts come from `check-liveness.mts --json byStatus`, the
number CI enforces — regenerated rather than hand-edited: dead 11 → 9 after the
two area-gate rows were deleted. (Its Notes cell said "Dead 12" against a column
reading 11, so that was already stale; corrected in the same pass.)

The changeset is deliberately NOT the "zero metadata migration" shape the
same-window renames (#4684 C9, #4535 C12) could claim. Those kept every key.
These two are authorable keys on a `.strict()` schema, so existing `app`
metadata declaring either one now fails to parse and `authorable-surface.json`
is net −2. It carries the FROM → TO table, the one-line fix, the
`os migrate meta --from 16` command, and the caveat that per-item gating inside
an area is shell-side only — a breaking changeset ships as CHANGELOG.md in the
npm package and is what an upgrading agent greps after the rejection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
@vercel

vercel Bot commented Aug 2, 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 2, 2026 9:11pm

Request Review

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @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 @objectstack/lint, 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 packages/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/lint, @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/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/kernel/runtime-capabilities.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.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests protocol:ui tooling size/l labels Aug 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 2, 2026 21:12
@os-zhuang
os-zhuang enabled auto-merge August 2, 2026 21:12
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 2, 2026
Merged via the queue into main with commit ad047d2 Aug 2, 2026
22 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4651-app-areas-fail-open branch August 2, 2026 21:33

Copy link
Copy Markdown
Contributor Author

落地后复核:main 上生成物全绿

PR 已合入 mainad047d2)。因为本单落地前 main 又动了三次(#4710 C12 / #4712 / #4713),其中 C12 也改了 packages/spec/authorable-surface.jsondata/FieldMapping:*data/ImportFieldMapping:*,11 个 key),所以专门在合并后的 main 上复核了这个生成物——两个 PR 同时改同一个 8k 行排序数组,正是 #4675 那套 merge=os-regen 驱动存在的场景。

结论:合入结果正确,无需补跟进main 上该文件同时含有

  • C12 的 4 个 data/ImportFieldMapping:*,以及
  • 本单退役后剩下的 5 个 ui/NavigationArea:*visible / requiredPermissions 已不在),

即相对合并前净减 2 个 key,与 PR 正文的结论一致。

main 的树上实跑:

✓ check:spec-changes         spec-changes.json
✓ check:upgrade-guide        docs/protocol-upgrade-guide.md
✓ check:skill-docs           skill docs (from SKILL.md frontmatter)
✓ check:skill-refs           skill references
✓ check:react-blocks         react-blocks contract
✓ check:authorable-surface   authorable-surface.json + JSON schemas
✓ check:api-surface          api-surface.json
✓ check:docs                 content/docs/references/**

✓ All 8 generated artifacts are up to date.

顺带记一条给后来者(也是 #4675 驱动注释里写着、我这次亲眼撞到的):在本地main merge 进特性分支时,authorable-surface.jsonmerge=os-regen——驱动不做文本合并,直接留下 OURS 当占位符并把路径写进 $GIT_DIR/os-regen-pending,因为 git 调用 merge driver 时源码还没合完。所以本地 merge 之后那个文件里 C12 的改名是缺的,必须从合并完的树重跑 gen:schema 才正确(pre-commitcheck-regen-pending 会拦住没重跑的提交)。GitHub 这边是把 PR 的 diff(对该文件恰好只有 −2 行)应用到 main 上,所以线上结果直接就是对的——两边不是一回事,别拿本地那个半成品去判断线上。


🤖 Generated with Claude Code


Generated by Claude Code

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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

app.areas[] 的 visible / requiredPermissions 是 fail-open 的访问闸门 —— 服务端从不走 areas(ADR-0049,v17 限时)

1 participant