跟踪 ADR-0106 (#3678 已合并)的实现。决策背景与完整论证见 ADR 正文及 #3661 ;客户端侧 ①② 已由 objectstack-ai/objectui#2866 承担。
实现清单(按 ADR 决策点)
D1/D2 — 分发层投影 :object schema 读按 security.getReadableFields(object, ctx) 投影 fields,掩码字段整体删除。协议层 getMetaItem/getMetaItems 签名不动。落点:
packages/rest/src/rest-server.ts — GET /meta/object/:name 的 cached 与非 cached 两条路径
packages/rest/src/rest-server.ts — GET /meta/object 列表读(逐 item 投影)
packages/runtime/src/domains/meta.ts — /metadata catch-all 的 object 分支(protocol 与 registry 两条查找路径)
D3 — mask-after-cache + 指纹 ETag :共享缓存继续每对象存一份全量;取出后掩码;调用者对该对象的 denied-set 稳定哈希(无限制调用者为空)折入 ETag。顺序约束:fetch → mask(或 error)→ send,缓存全量体不得有任何绕过掩码上线的路径。实现受阻时可先落 doc/book 式 bypass(记录为过渡,非终态)。
D4 — 豁免 :isSystem(getReadableFields 已有)+ 平台 admin(复用 app 过滤的 systemPermissions 判定)。豁免是 caller 属性,不是路由属性。
D5(4) — 出口审计 :排查其余 schema-bearing 端点(OData $metadata/describe 类、client SDK describe);未覆盖的按 Prime Directive chore: version packages #10 开 issue。
D6 — 三档失败姿态 :无 security 服务 → 放行;undefined → 放行 + 结构化 warn + 指标 + Cache-Control: private, no-store(不发共享 ETag);评估抛异常 → 5xx (绝不放出未掩码体,绝不返回空 fields 的 200)。
D7 — 零权限集调用者 :接 /auth/me/permissions 同款 fallback set 解析(security.fallbackPermissionSet,默认 member_default),落 plugin-security。
D8 — 配置逃生舱 :metadata.maskObjectFields(命名实现时定),默认开,随当前 major 发布。
测试 :掩码正确性(含 formula/options/requiredPermissions 随字段整体消失)、同 cohort 304 语义与权限变更后指纹失效、admin/isSystem 豁免、guest fallback、三档失败姿态(尤其「抛异常路径不泄漏缓存体」)。
文档 :部署文档补 CDN/代理缓存指引(指纹 ETag 与共享缓存的交互)。
收尾 :实现合入后将 ADR-0106 Status 行 Proposed → Accepted。
验收标准
对一个持有受限权限集的已认证调用者:任一 /meta//metadata 出口返回的 object schema 中,其不可读字段(含 readable:false 与缺失 requiredPermissions 两种成因)完全不存在 ;无限制调用者的响应与实现前字节一致;权限变更后旧 ETag 不再命中 304。
关联:#3661 (③)、#3678 (ADR)、objectstack-ai/objectui#2866 (①②)、#3547 (getReadableFields)、ADR-0049 / ADR-0066 D3 / ADR-0090。
跟踪 ADR-0106(#3678 已合并)的实现。决策背景与完整论证见 ADR 正文及 #3661;客户端侧 ①② 已由 objectstack-ai/objectui#2866 承担。
实现清单(按 ADR 决策点)
security.getReadableFields(object, ctx)投影fields,掩码字段整体删除。协议层getMetaItem/getMetaItems签名不动。落点:packages/rest/src/rest-server.ts—GET /meta/object/:name的 cached 与非 cached 两条路径packages/rest/src/rest-server.ts—GET /meta/object列表读(逐 item 投影)packages/runtime/src/domains/meta.ts—/metadatacatch-all 的 object 分支(protocol 与 registry 两条查找路径)isSystem(getReadableFields 已有)+ 平台 admin(复用 app 过滤的systemPermissions判定)。豁免是 caller 属性,不是路由属性。$metadata/describe 类、client SDK describe);未覆盖的按 Prime Directive chore: version packages #10 开 issue。undefined→ 放行 + 结构化 warn + 指标 +Cache-Control: private, no-store(不发共享 ETag);评估抛异常 → 5xx(绝不放出未掩码体,绝不返回空 fields 的 200)。/auth/me/permissions同款 fallback set 解析(security.fallbackPermissionSet,默认member_default),落plugin-security。metadata.maskObjectFields(命名实现时定),默认开,随当前 major 发布。Proposed→Accepted。验收标准
对一个持有受限权限集的已认证调用者:任一
/meta//metadata出口返回的 object schema 中,其不可读字段(含readable:false与缺失requiredPermissions两种成因)完全不存在;无限制调用者的响应与实现前字节一致;权限变更后旧 ETag 不再命中 304。关联:#3661(③)、#3678(ADR)、objectstack-ai/objectui#2866(①②)、#3547(
getReadableFields)、ADR-0049 / ADR-0066 D3 / ADR-0090。