发现于 #5563 实施(信封收敛)期间的旁证测量,与该单的改动无关 —— 改前改后同样成立。基线 origin/main @ 55c74deeb。
事实
packages/rest/src/rest-server.ts 的单条元数据读取 handler 里,ADR-0057 D10 的 dashboard 组件门禁(filterDashboardForUser,按 widget.requiresService 剔除指向未注册可选服务的磁贴)只写在非缓存分支里:
} else {
// 非缓存分支
const envelope = await p.getMetaItem({...});
...
if (RestServer.metaTypeSingular(req.params.type) === 'dashboard' && visible) {
... visible = this.filterDashboardForUser(visible, serviceGate);
}
而进入缓存分支的条件是:
if (metadata.enableCache && p.getMetaItemCached && !isAppType && !isDraftRead
&& !previewDrafts && !packageScoped && req.params.type !== 'doc' && req.params.type !== 'book')
app 被 !isAppType 显式排除在缓存之外(注释写明:正是为了让 per-user RBAC 过滤能跑),doc / book 也被排除(§6.7 audience 是 per-caller)。dashboard 没有被排除。而 enableCache 默认为 true(packages/spec/src/api/rest-server.zod.ts 的 enableCache: z.boolean().default(true))。
于是:默认配置下,一次 GET /api/v1/meta/dashboard/:name 走缓存分支,filterDashboardForUser 一次都不会执行。
实测
在 worktree 里对着真 RestServer 跑一次探针(protocol 同时提供 getMetaItem 与 getMetaItemCached,serviceExistsProvider 报告 org-scoping 未注册,dashboard 有两个组件,其中 w_orgs 声明 requiresService: 'org-scoping'),捕获 res.json 的实参:
cachedCalls: 1
widgetsServed: [ "w_users", "w_orgs" ]
w_orgs 被原样送出。把同一请求切到非缓存分支(metadata.enableCache: false),门禁生效、w_orgs 被剔除 —— 这条断言现在钉在 packages/rest/src/rest.test.ts 的 filterDashboardForUser 一组里(#5563 PR 内),该用例显式关掉缓存才跑得出绿,注释里也记了这条为什么。
(探针脚本是一次性的,未提交。)
影响
ADR-0057 D10 的立论是「服务端是权威的可见性门禁」—— 客户端的隐藏只是礼貌。这里默认配置下门禁根本不运行,所以:
- 在没有该可选服务的部署里,console 会渲染一块绑定到缺失服务的死磁贴(D10 原文点名的场景:单租户运行时里的 Organizations KPI);
- 与
app 的处置不对称:app 为了让 per-user 过滤能跑而特意绕开缓存,dashboard 的门禁却写在只有绕开缓存才会到达的分支里 —— 两处是同一天同一个 ADR 的两半;
- 属于「声明了却没有强制」:门禁代码在、测试在(测试直接调私有方法或关掉缓存,因此一直绿),没有任何 gate 会发现默认路径上它不执行。
可能的处置(未定,交分诊)
- A:把
dashboard 加进缓存排除条件(与 app 同款),代价是 dashboard 读失去 ETag 快路径;
- B:门禁提到分支之外,缓存与非缓存两条路径出口处统一跑一次(需要注意 ETag 是 per-published-checksum 而门禁是 per-deployment 的,不是 per-caller,所以共享 ETag 未必不安全);
- B 看起来更贴合「一次读取一个出口」,但 ETag/缓存键与门禁结果的关系需要确认,不代拍。
关联:#5563(在其实施中测得,两者互不阻塞)、ADR-0057 D10。
发现于 #5563 实施(信封收敛)期间的旁证测量,与该单的改动无关 —— 改前改后同样成立。基线
origin/main@55c74deeb。事实
packages/rest/src/rest-server.ts的单条元数据读取 handler 里,ADR-0057 D10 的 dashboard 组件门禁(filterDashboardForUser,按widget.requiresService剔除指向未注册可选服务的磁贴)只写在非缓存分支里:而进入缓存分支的条件是:
app被!isAppType显式排除在缓存之外(注释写明:正是为了让 per-user RBAC 过滤能跑),doc/book也被排除(§6.7 audience 是 per-caller)。dashboard没有被排除。而enableCache默认为true(packages/spec/src/api/rest-server.zod.ts的enableCache: z.boolean().default(true))。于是:默认配置下,一次
GET /api/v1/meta/dashboard/:name走缓存分支,filterDashboardForUser一次都不会执行。实测
在 worktree 里对着真
RestServer跑一次探针(protocol 同时提供getMetaItem与getMetaItemCached,serviceExistsProvider报告org-scoping未注册,dashboard 有两个组件,其中w_orgs声明requiresService: 'org-scoping'),捕获res.json的实参:w_orgs被原样送出。把同一请求切到非缓存分支(metadata.enableCache: false),门禁生效、w_orgs被剔除 —— 这条断言现在钉在packages/rest/src/rest.test.ts的filterDashboardForUser一组里(#5563 PR 内),该用例显式关掉缓存才跑得出绿,注释里也记了这条为什么。(探针脚本是一次性的,未提交。)
影响
ADR-0057 D10 的立论是「服务端是权威的可见性门禁」—— 客户端的隐藏只是礼貌。这里默认配置下门禁根本不运行,所以:
app的处置不对称:app为了让 per-user 过滤能跑而特意绕开缓存,dashboard的门禁却写在只有绕开缓存才会到达的分支里 —— 两处是同一天同一个 ADR 的两半;可能的处置(未定,交分诊)
dashboard加进缓存排除条件(与app同款),代价是 dashboard 读失去 ETag 快路径;关联:#5563(在其实施中测得,两者互不阻塞)、ADR-0057 D10。