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. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Aug 2, 2026
  2. self-assigned this
    on Aug 3, 2026
  3. 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

  4. 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

Labels

bugSomething isn't workingpm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch looppriority:p2Medium: important, M3security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions