发现于 #5881 的实施测量(旁证,与该单改动无关 —— 改前改后同样成立)。基线 origin/main @ 8e2bbba24。
事实
packages/rest/src/rest-server.ts:4249 的缓存分支进入条件,对 doc / book 的排除写的是字面量比较:
if (metadata.enableCache && p.getMetaItemCached && !isAppType && !isDraftRead && !previewDrafts
&& !packageScoped && req.params.type !== 'doc' && req.params.type !== 'book') {
而同一 handler 里 app 用的是归一化后的 RestServer.metaTypeSingular(req.params.type) === 'app'。PLURAL_TO_SINGULAR(packages/spec/src/shared/metadata-collection.zod.ts:144-145)含 docs: 'doc' / books: 'book',元数据路由两种拼写都受理,且 Prime Directive #3 规定复数才是 REST 的规范拼写(/api/v1/meta/books)。
于是 GET /api/v1/meta/books/:name 进入缓存分支,而 §6.7 audience 门禁只写在 else(非缓存)分支里(rest-server.ts:4390-4434)—— 门禁一次都不跑。enableCache 默认为 true。
这正是 metaTypeSingular 自己的 docstring 记下的 #3984 那一类缺陷("the plural spelling cannot fall outside it"),在同一个文件里换了个位置复发。
实测
真 RestServer,默认配置(不写 metadata 块 ⇒ enableCache 取默认 true),protocol 同时提供 getMetaItem 与 getMetaItemCached;book 声明 audience: { permissionSet: 'ps_admin' };调用方已登录但不持有任何 permission set:
singular "book" :: cachedCalls=0 status=[403] body={"error":"This documentation is limited to holders of a permission set you do not have","code":"PERMISSION_DENIED"}
plural "books":: cachedCalls=1 status=[] body={"type":"book","name":"admin_guide","item":{"name":"admin_guide","audience":{"permissionSet":"ps_admin"},"title":"Admin Guide"}}
同一个 book,同一个调用方:单数拼写 403,复数拼写 200 并原样送出正文。
(探针脚本一次性,未提交。)
影响
- ADR-0046 §6.7 的 audience 授权在规范拼写上失效:
{ permissionSet } 门禁的 book(如 Admin Guide)会送给不持有该 set 的已登录调用方;org book 在对匿名开放的部署上同理(匿名分支由 umbrella gate 决定可达性,授权本应留给 handler 的 §6.7 门禁 —— 而它没跑)。
doc 同款:GET /meta/docs/:name 走同一条路。
- 与
app 的处置不对称:app 用归一化比较,doc/book 用字面量比较,两者在同一个 if 里。
可能的处置(未定,交分诊)
把两处字面量比较换成 metaTypeSingular(...) 归一化后的比较(与 isAppType 同款)。#5881 的修复已把 dashboard 的排除写成归一化形式,可直接照抄该形状。
需要注意这不是纯粹的一行改动:换成归一化后,docs/books 复数读取会从缓存分支移到非缓存分支,ETag 快路径随之消失(与 #5881 对 dashboard 的取舍同源),现有测试若有钉复数缓存行为的需要重判。
关联:#5881(在其实施中测得,互不阻塞)、#3984(同类缺陷的前身)、ADR-0046 §6.7。
发现于 #5881 的实施测量(旁证,与该单改动无关 —— 改前改后同样成立)。基线
origin/main@8e2bbba24。事实
packages/rest/src/rest-server.ts:4249的缓存分支进入条件,对doc/book的排除写的是字面量比较:而同一 handler 里
app用的是归一化后的RestServer.metaTypeSingular(req.params.type) === 'app'。PLURAL_TO_SINGULAR(packages/spec/src/shared/metadata-collection.zod.ts:144-145)含docs: 'doc'/books: 'book',元数据路由两种拼写都受理,且 Prime Directive #3 规定复数才是 REST 的规范拼写(/api/v1/meta/books)。于是
GET /api/v1/meta/books/:name进入缓存分支,而 §6.7 audience 门禁只写在else(非缓存)分支里(rest-server.ts:4390-4434)—— 门禁一次都不跑。enableCache默认为true。这正是
metaTypeSingular自己的 docstring 记下的 #3984 那一类缺陷("the plural spelling cannot fall outside it"),在同一个文件里换了个位置复发。实测
真
RestServer,默认配置(不写metadata块 ⇒enableCache取默认 true),protocol 同时提供getMetaItem与getMetaItemCached;book 声明audience: { permissionSet: 'ps_admin' };调用方已登录但不持有任何 permission set:同一个 book,同一个调用方:单数拼写 403,复数拼写 200 并原样送出正文。
(探针脚本一次性,未提交。)
影响
{ permissionSet }门禁的 book(如 Admin Guide)会送给不持有该 set 的已登录调用方;orgbook 在对匿名开放的部署上同理(匿名分支由 umbrella gate 决定可达性,授权本应留给 handler 的 §6.7 门禁 —— 而它没跑)。doc同款:GET /meta/docs/:name走同一条路。app的处置不对称:app用归一化比较,doc/book用字面量比较,两者在同一个if里。可能的处置(未定,交分诊)
把两处字面量比较换成
metaTypeSingular(...)归一化后的比较(与isAppType同款)。#5881 的修复已把dashboard的排除写成归一化形式,可直接照抄该形状。需要注意这不是纯粹的一行改动:换成归一化后,
docs/books复数读取会从缓存分支移到非缓存分支,ETag 快路径随之消失(与 #5881 对dashboard的取舍同源),现有测试若有钉复数缓存行为的需要重判。关联:#5881(在其实施中测得,互不阻塞)、#3984(同类缺陷的前身)、ADR-0046 §6.7。