Skip to content

feat(rest): PUT /meta/:type/:name 要求 manage_metadata 能力 (#6603) - #7027

Merged
os-project-manager merged 5 commits into
mainfrom
claude/issue-6603-put-meta-manage-metadata-gate
Aug 9, 2026
Merged

feat(rest): PUT /meta/:type/:name 要求 manage_metadata 能力 (#6603)#7027
os-project-manager merged 5 commits into
mainfrom
claude/issue-6603-put-meta-manage-metadata-gate

Conversation

@os-project-manager

@os-project-manager os-project-manager commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #6603

按 2026-08-08T09:33Z 维护者裁决(Option B —— 拒绝写入,评论 5225531464)与 2026-08-09T05:39Z 复核(5229988826)实施。四条路线不再论证。

1. 调用方普查(裁决采纳的实施第一步)

⚠️ 结论:没有 in-repo 调用方会因这道门新吃 403。 下面是证据(搜了什么、在哪搜、找到什么,包括看了但没找到的地方),不是结论复述。

搜索面:objectstackobjectui 两仓,saveMetaItem / saveItem / saveMetadataItem / method: 'PUT' 交叉 meta,覆盖 .ts/.tsx/.js/.vue,排除 node_modulesdist

调用方 位置 落在门内吗
SDK meta.saveItem packages/client/src/index.ts:698:4963 唯一的 in-repo HTTP 客户端;能力随登录会话
CLI os meta register packages/cli/src/commands/meta/register.ts:76 走 SDK;实际以 dev admin 运行
console 对象/字段设计器 objectui apps/console/src/services/MetadataService.ts:139/149/164/178/210components/schema/objectDetailWidgets.tsx:204/341/360 ✅ Studio 内
console 公开表单页 objectui apps/console/src/pages/developer/PublicFormsPage.tsx:190/253(view) ✅ Studio 内
应用设计器 objectui packages/plugin-designer/src/pages/{CreateAppPage,EditAppPage}.tsx(app) ✅ Studio 内
app-shell 元数据服务 / 导航同步 objectui packages/app-shell/src/services/MetadataService.ts:139/149hooks/useNavigationSync.ts:216 ✅ 仅由页面/仪表板 CRUD 触发,即 Studio 内
in-repo QA packages/qa/dogfood/test/dashboard-designer-roundtrip.dogfood.test.ts:54 stack.signIn() = dev admin

看了但没有找到调用方的地方(同样是证据):apps/examples/core/ 全部无 /meta PUT 调用;Setup 应用的导航贡献(packages/platform-objects/src/apps/setup-nav.contributions.ts)只有数据面 object 项与只读 developer:packages,没有任何 /meta 写入面;packages/metadata/src/loaders/remote-loader.ts:125RemoteLoader.save() 虽从 packages/metadata/src/index.ts:19 导出,但仓内无消费者(库面,非调用方);packages/runtime/src/domains/packages.ts:246 是进程内直调 protocol.saveMetaItem,不经这条路由。

为什么这些都在门内 —— 关键测量:所有元数据编写面都在 Studio 应用里(packages/platform-objects/src/apps/studio.app.ts:54,requiredPermissions: ['studio.access']),而仓内唯一studio.access 的权限集是 admin_full_access,它同时带 manage_metadata(packages/plugins/plugin-security/src/objects/default-permission-sets.ts:120-129)。

另注:objectui 的导航同步已经把这条路由的 403 当作正常可处理情况(isNavigationSyncableApp 的注释:写保护应用「would 403 every PUT — skip them instead of spraying failure toasts」),所以即便有边角调用方吃到 403,也不是崩溃路径。

2. ⚠️ 必须写明:本 PR 建立的不是裁决理由里那句话

裁决的理由句是「能写 schema 的人就是能看见完整 schema 的人」。本 PR 建立的是「能写者需持 manage_metadata」—— 这两句不是同一个集合,不变量本身没有被建立。

  • ADR-0106 D4 豁免集是 OBJECT_SCHEMA_MASK_EXEMPT_CAPABILITIES = ['studio.access','setup.access'](packages/metadata-core/src/object-schema-fls.ts:72),不含 manage_metadata;
  • 于是 organization_admin(['manage_org_users','setup.access','setup.write'])是 D4 豁免、从来没有往返丢失风险,却被这道门 403;
  • 反过来,只持 manage_metadata 的调用方能写,但读仍被掩码,它自己的往返仍会丢字段;
  • 两者只在 admin_full_access 上重合 —— 让不变量在默认部署里成立的是权限集的巧合,不是这道门。

照裁决落 manage_metadata,不动(它在第二条理由上独立成立:关掉「任何已认证会话都能覆写任意 schema」这个与掩码无关的旧洞)。这条差已按实测钉进测试矩阵,并另立 #7020 记录,不在本 PR 替维护者选对齐方向。

3. 真红:先写下预测,再跑

预测写在实施前(方向:RED,三条断言应同时翻)。实测(去掉门,同一往返,真实路由 + 真实内存库):

GET served fields   : id, name
STORE before PUT    : bonus_formula, id, name, salary_grade
PUT status          : 200
saveMetaItem calls  : 1
STORE after PUT     : id, name          (两个被掩码字段被真删)

加门后:403 + { error: { code: 'FORBIDDEN' } },saveMetaItem 零调用,库中四个字段完好、label 未变。整份新测试在删门后 6/12 转红,方向与预测一致,无反转、无「诊断增多」情形。

钉的是丢失本身,不只是状态码 —— 断言直接读库(storedFields()),因为「先写了再 403」正是最值得防的失败形态,只断言状态码看不见它。拒绝类断言一律同时断 codestatus(ADR-0112 信封);这条路由是发送而非抛出,toThrow 形状的断言在这里分不清「拒绝但信封错」与「根本没拒绝」。

豁免调用方:admin_full_access 形状的调用方往返无损(读不被掩码、写 200、库中四字段完好)。⚠️ 该用例加门前后都绿,所以它是护栏,不是证据 —— 按要求明说。

4. ⚠️ 本 PR 关的是一扇门,缺陷没有被关闭

已实测:同一次 GET → 改 → PUT 的数据丢失,在本门落下后仍可经复合名路由 PUT /meta/:type/:section/:name 原样复现(compound GET fields : id, namecompound PUT status : 200STORE after PUT : id, name)。此外 dispatcher 自己的 /meta PUT(packages/runtime/src/domains/meta.ts:264)与无门的 DELETE /meta/:type/:name 同形。

三处同族出口按 PD #10 归档为 #7019,不在本 PR 修 —— 区域纪律:本卡与 #6599 / #6920 并发占用同一文件的不同区域。#7019 正文已写明「#6603 落地后缺陷仍可复现,只是换一扇门」,复合名那条是实测,dispatcher 与 DELETE 两条明标未实测、须实测,没有用静态观察冒充实测。

5. 改了什么

  • packages/rest/src/rest-server.ts —— PUT /meta/:type/:name 处理器开头加能力门,复用 POST /meta/_migrate-stored 的同一机制(!ctx?.isSystem && !held.has('manage_metadata') → 403 FORBIDDEN),不另造第二种写法。门在 resolveProtocol 之前,所以未授权调用方无法用 501-vs-200 探测内核能力,且拒绝时什么都没写
  • packages/rest/src/meta-item-save-capability-gate.test.ts —— 新建文件(不碰任何共享测试文件),12 例。
  • packages/rest/src/rest-route-ledger.ts —— 该行补 note,与 _migrate-stored 的记法一致。
  • .changeset/meta-save-manage-metadata-gate.md —— minor,写明谁开始吃 403、需要什么(给权限集加 manage_metadata),以及尚未关闭的部分。

5.1 另有 5 个既有测试文件各改 1–4 行 —— 为什么必须

rest.test.ts(4 处)、rest-meta-save-receipt-envelope.test.tsrest-unknown-object-heuristic.test.tsrest-5xx-message-sanitization.test.tsrest-4xx-message-truncation.test.ts

这不是「顺手修了几个测试」,是同一处行为变更的必然连带。 这些用例测的是本路由的机制(If-Match 转发、保存收据信封、4xx 截断与 5xx 脱敏),但它们的 boot 桩把 resolveExecCtx 打成只带一个 userId 的调用方 —— 也就是把「只要有 session 就能写元数据」当作既成前提。而这个前提正是本 PR 要消灭的东西。门落下后,这些请求在到达被测机制之前就先被 403 拦住,断言全部失效:不改它们,PR 一落地 @objectstack/rest 即红(实测:14 例失败)。

因此改动给 boot 桩补上 systemPermissions: ['manage_metadata'],让调用方合法地到达被测代码;被测机制、断言、期望值一字未动。每处都留了一行说明:// [#6603] this route now demands manage_metadata — an authoring capability, not just a session.

关于范围:这几处一度被本 PR 记为「超出区域」,已由协调人裁定在范围内 —— 它们是行为变更的必然连带,不是越界。rest.test.ts 是并发共用文件,若与 #6599 / #6920 冲突,按本车道惯例由后推者重并。

6. 本地验证

  • pnpm --filter @objectstack/rest test74 文件 / 1154 例全绿(先建了 @objectstack/types 等依赖)
  • pnpm --filter @objectstack/rest typecheck → 通过
  • check:route-envelope / check:error-code-casing / check:empty-changeset / check:meta-type-normalized / check:spec-parsed-alias / check:nul-bytes → 全部 PASS

⚠️ CI 的 ESLint(含更多族门)与 TypeScript Type Check(含 check:api-surface)本地不覆盖,交 PM。

claude added 4 commits August 9, 2026 09:01
既有的 header 转发 / 收据信封 / 错误信封等路由机制单测都以「只有 session」的
调用方驱动 PUT /meta/:type/:name,新门落下后它们先吃 403。给这些 boot 桩加上
manage_metadata,测的仍是原来的机制。

同时在 rest-route-ledger 的该行记下这道门,与 _migrate-stored 的记法一致。
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 9, 2026 10:09am

Request Review

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 9, 2026
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest.

12 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/connect-mcp.mdx (via @objectstack/rest)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest)
  • content/docs/api/index.mdx (via @objectstack/rest)
  • content/docs/permissions/authentication.mdx (via @objectstack/rest)
  • content/docs/permissions/system-context.mdx (via packages/rest)
  • content/docs/plugins/index.mdx (via @objectstack/rest)
  • content/docs/plugins/packages.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest)
  • content/docs/releases/implementation-status.mdx (via @objectstack/rest)
  • content/docs/releases/v12.mdx (via @objectstack/rest)
  • content/docs/releases/v17.mdx (via @objectstack/rest)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-project-manager
os-project-manager marked this pull request as ready for review August 9, 2026 10:32
@os-project-manager
os-project-manager added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit 623d008 Aug 9, 2026
26 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-6603-put-meta-manage-metadata-gate branch August 9, 2026 10:48
os-zhuang pushed a commit that referenced this pull request Aug 9, 2026
…scope fixture (#7018)

`main` moved under this branch: #6603 / PR #7027 put a `manage_metadata` gate in
front of `PUT /meta/:type/:name`, ahead of everything this file measures. The
fixture's session carried `systemPermissions: []`, so on the PR's merge ref the
door answered 403 and the scoping decision was never reached.

The capability is now granted explicitly rather than inherited from whatever
another suite registered, so the file is order- and shard-independent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiRUoQkTSBBmpyXBY3cVn2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding: after ADR-0106, a restricted caller's GET → edit → PUT of an object schema DELETES the fields that were masked out of their read

2 participants