Skip to content

filterAppForUser 只走 app 顶层 navigation —— areas[] 里的 nav 项权限过滤仅客户端生效(#4651 移除假闸门后剩下的真缺口) #4722

Closed

Description

@os-zhuang

从 #4651 / #4718 分出的独立记录。未指派 —— 只是记录。

现象

服务端权威可见性闸门 filterAppForUser(packages/rest/src/rest-server.ts)只过滤 app 的顶层 navigation 树:

:1814  检 app 级 requiredPermissions —— 不满足 return null
:1823  const nav = Array.isArray(item.navigation) ? item.navigation : null;
       if (!nav) return item;                       ← 没有 navigation 就直接原样返回
:1826  filterNav 递归检 nav 项级 requiredPermissions / requiresService

item.areas 从头到尾没有被读到。所以一个写在 area 内部的 nav 项上的 requiredPermissions / requiresService:

  • 服务端不剥离 —— 该条目连同它的 objectName / pageName / componentRef 等指向,照常出现在 /meta 响应里;
  • 只有客户端 NavigationRenderer.tsx:894 的 checkPerm 会把它藏起来。

也就是说,对 areas 型 app 而言,导航项级权限目前是渲染层的礼貌,不是服务端强制。改一次前端状态、或者直接读 /meta 的 JSON,就能看到本该被 gate 掉的条目。

与 #4651 的关系(这是剩下的那一半)

#4651 处理的是 area 级的两个键 areas[].visible / areas[].requiredPermissions——它们任何一层都没有消费者,是纯粹 fail-open 的假闸门,已按维护者裁决在 17.0.0 走路线 B 移除(#4718)。

本单是另一件事:area 里面的 nav 项闸门是真的被消费的,只是只被客户端消费。#4651 的处方对此没有回避,明写了限定并落进了三处:

  • 退役处方本身(ui/app.zod.ts 的 AREA_REQUIRED_PERMISSIONS_RETIRED:「Items nested under areas[] are gated in the shell only — the server does not walk areas」);
  • 活性账本 packages/spec/liveness/app.json 的 areas.navigation 条目 note;
  • 一条 characterisation pin(packages/rest/src/rest.test.ts,filterAppForUser — the enforced permission layers (#4651) 里的最后一条),它故意把当前行为钉住,并在注释里写明:谁让它开始走 areas,就应该看到这条断言红掉、然后连同账本 note 一起有意识地改写。

所以现状是「已知、已记录、已钉住」,但没有 open issue 跟踪——#4651 已随 #4718 关闭。本单补上这个跟踪位。

需要决定(与 #4651 的路线 A 是同一组语义问题)

要让服务端也过滤 areas,先得回答:

  1. 一个 area 里的项被过滤光之后,这个 area 本身还返不返回?(返回一个空 area,客户端切过去是一片空白)
  2. 顶层 navigation 与 areas[].navigation 同时存在时(schema 允许,AppSchema with areas 的 backward-compat 用例就是这个形状),两棵树的过滤要不要一致?
  3. 是否顺带补服务端的 visible CEL 求值?目前 visible 任何层级都只在客户端求值(AppSidebar.tsx:236 经 ExpressionProvider),服务端从不绑定 user 上下文——所以「服务端强制」在 requiredPermissions / requiresService 上成立,在 visible 上不成立,这个不对称本身也值得写进文档而不是让人自己发现。

影响面

关联

#4651(area 级假闸门,已移除)、#4718(实现 PR,含上述三处记录与 pin)、ADR-0045(hidden app 可见性)、ADR-0057 D10(requiresService 能力闸门)、ADR-0049(enforce-or-remove)、ADR-0078(false compliance)

Activity

  1. self-assigned this
    on Aug 3, 2026
  2. os-zhuang commented on Aug 3, 2026

    @os-zhuang
    ContributorAuthor

    🔒 认领 —— 第 2 轮(发布窗口 bug 批)

    • 会话:session_015Br2xsJsczFsTR9bvbh2Ny(PM 循环)
    • 分支:claude/issue-4722-filter-app-areas-nav
    • Worktree:objectstack-issue-4722

    ⚠️ 共用身份,session ID 不符即本单已被占,请另选。

    正文三问的 PM 裁定(维护者可否决):

    1. 空 area 是否返回 → 照顶层 navigation 树对「项被滤光的分组」的既有处理同形(先读代码确认既有行为,照抄;若顶层无此形状,则滤光的 area 剥离 —— 返回空壳没有任何消费价值)。在 PR 里写明你实测到的既有行为。
    2. 两棵树一致性 → 必须一致:同一个键在两处语义相同,filterNav 逻辑复用到 areas[].navigation,不复制第二份实现。
    3. visible CEL 服务端求值 → 明确不做,保持客户端求值,但把这个不对称写进文档注释与 characterisation pin(正文的建议采纳)。服务端 CEL 绑定 user 上下文是另一个体量的事,需要时另立。

    已预期的既有钉:rest.test.ts 里 filterAppForUser — the enforced permission layers (#4651) 的最后一条 characterisation pin 会红,红了是对的 —— 按它注释里的预案改写断言,并同步改写 packages/spec/liveness/app.json 里 areas.navigation 的 note(它现在写着 server does not walk areas,修完必须跟着改,否则账本说谎)。

    ⚠️ spec 零改动约束的单文件例外:本单允许改 packages/spec/liveness/app.json 仅此一个文件、仅该 note 行 —— 这是账本记录,不是契约 schema。除此之外 packages/spec/** 仍零改动。此例外已在分片登记与轮报中记录。

    其余约束:退役键 AREA_REQUIRED_PERMISSIONS_RETIRED(area 级)不复活 —— 本单只管 area 里面的 nav 项级键。⛔ content/docs/releases/ 不碰;资源纪律照旧。

    验收:areas 型 app 下,无权限用户的 /meta 响应里被 gate 的 nav 项(含其 objectName/pageName 指向)不再出现;requiresService 同理;顶层树行为回归不变;pin 与账本 note 已按预案改写。


    Generated by Claude Code

  3. os-zhuang commented on Aug 3, 2026

    @os-zhuang
    ContributorAuthor

    ✅ PM 验收通过 —— PR #4751,CI 全绿(20 success + 2 skipped,0 失败),已转 ready 并入合并队列

    复核对着 GitHub:

    • 三问全部按裁定落实,Q1 比裁定更精确:实测顶层既有行为后区分「被闸门滤空的 area 剥离」/「作者写空的 area 保留」,过滤不越权整理元数据,各有 pin
    • resolveRegisteredServices 探测面同步下钻 areas —— 这是 dev 自己发现的正确性前提(未探测的服务名 = 「服务不存在」会误剥离合法条目),不是顺手改,论证成立且有专门 pin
    • characterisation pin 按预案改写,liveness/app.json 账本 note 同步(spec 单文件例外,实测其余 packages/spec/** 零改动;Spec property liveness 门禁绿)
    • 测试先证明能红:stash 修复后 6 + 3 条红;路由级测试断言响应体字节(被 gate 条目的指向在 JSON 里搜不到)
    • visible CEL 不对称按裁定只记录不实现,三处落档

    场外发现 #4749(spec 退役处方正文陈旧)归 spec 车道,留待对侧;本会话按暂停指示不派。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions