从 #4749(PR #5336)的实现中记录的越界发现(未认领)。
2026-08-04 更新(PM 复核后):本单原先记了两条。第 1 条(AREA_VISIBLE_RETIRED 的
收尾枚举)经 PM 复核已扩入 PR #5336 并在那里修掉,不再属于本单 —— 原文保留在下面的
「已在别处修掉」一节仅作存档,不要重复实现。本单现在只剩第 2 条。
现象(本单唯一剩下的一条)
packages/spec/src/migrations/registry.ts(protocol-17 step 的 rationale)仍写着:
One honest caveat the prescription carries rather than hides — per-item gating INSIDE an
area is enforced by the shell only, since 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 之后这句已不成立:filterAppForUser 现在对每一棵 areas[].navigation 跑同一个
filterNav(packages/rest/src/rest-server.ts:1811 的 [#4722] 注释 + 1890 行的
filterAreas),项级 requiredPermissions / requiresService 在两棵树被同等强制。
这是三处里读者最多的一处:docs/protocol-upgrade-guide.md 是 ADR-0087 D4 的纯投影
(gen:upgrade-guide),正是正在从 16 升到 17 的作者读的那份文档,而该句在整段里以现在时
陈述。同一句也留在尚未发布的 .changeset/app-area-fail-open-gates-removed.md。
⚠️ 读数陷阱(PM 首次核验踩过,记下以免重复):git grep "does not walk" -- packages/spec/src/migrations/registry.ts 零命中,会让人误判本单不实 —— 该句是跨行字符串拼接,横跨 672–674 行,单行 grep 看不见。
修法要点(避免踩坑)
未发布 changeset 的口径 —— 已由 PM 裁定,不再上交维护者
.changeset/app-area-fail-open-gates-removed.md 里的同一句一并改(裁定见本单
2026-08-04 分诊评论,留否决窗口)。理由:该句是以现在时写给读者的操作建议(「anything
that must never reach the browser belongs in the top-level tree」),不是对 #4651 当时状态的
历史陈述;而 #4722 与 #4651 落在同一个大版本里,读 v17 发布说明的人所处的世界已经是
「服务端走两棵树」,原句会劝他做一次不必要的导航树重构。属恢复性修复,非产品语义决策。
⛔ 边界:改的是 .changeset/*.md(PR 的法定输入),不是 content/docs/releases/。
正确措辞的蓝本:packages/spec/liveness/app.json 的 areas.navigation note(#4722 已改写),
以及 PR #5336 改后的两条处方正文。
已在别处修掉(存档,勿重复实现)
原第 1 条:AREA_VISIBLE_RETIRED 的收尾句把服务端强制的落点枚举成「on the app itself, or on
items of the app's top-level navigation tree」,#4722 之后漏了 areas[].navigation 的项。
经 PM 复核判定它与 #4749 同文件、同常量块、同一类缺陷,已扩入 PR #5336 一并修掉(连同
app.test.ts 的换钉与反向验证)。
参考:#4749 / PR #5336、#4722(实现)、#4651(退役裁决)。
从 #4749(PR #5336)的实现中记录的越界发现(未认领)。
现象(本单唯一剩下的一条)
packages/spec/src/migrations/registry.ts(protocol-17 step 的rationale)仍写着:#4722 之后这句已不成立:
filterAppForUser现在对每一棵areas[].navigation跑同一个filterNav(packages/rest/src/rest-server.ts:1811的[#4722]注释 + 1890 行的filterAreas),项级requiredPermissions/requiresService在两棵树被同等强制。这是三处里读者最多的一处:
docs/protocol-upgrade-guide.md是 ADR-0087 D4 的纯投影(
gen:upgrade-guide),正是正在从 16 升到 17 的作者读的那份文档,而该句在整段里以现在时陈述。同一句也留在尚未发布的
.changeset/app-area-fail-open-gates-removed.md。git grep "does not walk" -- packages/spec/src/migrations/registry.ts零命中,会让人误判本单不实 —— 该句是跨行字符串拼接,横跨 672–674 行,单行 grep 看不见。修法要点(避免踩坑)
registry.ts的 rationale,不要手改docs/protocol-upgrade-guide.md—— 它是生成物,改完跑
pnpm --filter @objectstack/spec gen:upgrade-guide,check:generated会盯着。(该文档走 os-regen 驱动,这也是它没有混进 PR fix(spec): 两条 area 退役处方在 #4722 之后改口径 —— 项级闸门在两棵树都由服务端剥离 (#4749) #5336 的原因。)
git merge origin/main→git checkout origin/main -- <生成物>→ 整体重新生成(⛔ 绝不文本合并)→ 断言兄弟单条目都还在。该驱动会 exit 0、零冲突标记地静默吞掉一侧改动。保留;要改的只有把 caveat 那句从「仍然如此」改成「filterAppForUser 只走 app 顶层 navigation —— areas[] 里的 nav 项权限过滤仅客户端生效(#4651 移除假闸门后剩下的真缺口) #4722 已关闭」。
visible自身的口径带歪:项级visible(CEL)依然只在客户端求值,filterAppForUser 只走 app 顶层 navigation —— areas[] 里的 nav 项权限过滤仅客户端生效(#4651 移除假闸门后剩下的真缺口) #4722 没有碰这一半。
未发布 changeset 的口径 —— 已由 PM 裁定,不再上交维护者
.changeset/app-area-fail-open-gates-removed.md里的同一句一并改(裁定见本单2026-08-04 分诊评论,留否决窗口)。理由:该句是以现在时写给读者的操作建议(「anything
that must never reach the browser belongs in the top-level tree」),不是对 #4651 当时状态的
历史陈述;而 #4722 与 #4651 落在同一个大版本里,读 v17 发布说明的人所处的世界已经是
「服务端走两棵树」,原句会劝他做一次不必要的导航树重构。属恢复性修复,非产品语义决策。
⛔ 边界:改的是
.changeset/*.md(PR 的法定输入),不是content/docs/releases/。正确措辞的蓝本:
packages/spec/liveness/app.json的areas.navigationnote(#4722 已改写),以及 PR #5336 改后的两条处方正文。
已在别处修掉(存档,勿重复实现)
原第 1 条:
AREA_VISIBLE_RETIRED的收尾句把服务端强制的落点枚举成「on the app itself, or onitems of the app's top-level
navigationtree」,#4722 之后漏了areas[].navigation的项。经 PM 复核判定它与 #4749 同文件、同常量块、同一类缺陷,已扩入 PR #5336 一并修掉(连同
app.test.ts的换钉与反向验证)。参考:#4749 / PR #5336、#4722(实现)、#4651(退役裁决)。