Skip to content

[Decision] 墙内组织里持有 manage_metadata 的人仍能改共用的对象、视图、流程:只放行平台运营者(A)、禁止组织范围授予(B),还是维持并写明(C) #23231

Closed

Description

@objectstack-fleet

Ruled: 6145019463 · letter A · this card is the execution card (measure first, then the wall-gate covers every metadata type) · 2026-10-13T12:02Z
Part of #15204
Filing gate ②:只有维护者能做的决定。这涉及安全与权限边界,也就是集团和隔离模式下,组织能不能改全环境共用的元数据,按规则恒在人工地板。也算 ③:维护者在本席会话中要求「补这 4 张决策卡」。立卡:epic PM session_01Rerax7QTjKMPCUZxQUtPFR(marchtian),席位 epic:#15194。
Prior rulings on this card: none(新卡)。相关记录:

三处都写的是「这个问题归维护者」,但一直没有立卡(6113841670:「Carried, not filed」)。

锚点: 无自然锚点。W、W2、W3 都已合入,#15204 是活动中的执行卡,所以单开本卡。

查重: 用语义检索搜 objectstack 卡,关键词 walled tenant / manage_metadata / organization admin / environment objects views flows / group isolated posture,命中 15 张:

没有同题的开着的卡或决策卡。

动手的读者:

  • 裁 A 或 B:本席为 domain:engine 车道立 1 张执行卡,第一步是实测可达性。
  • 裁 C:本席为 domain:devx 车道立 1 张文档卡。

维护者速读

事情: 集团(group)和隔离(isolated)模式下,一个部署是一个环境,所有组织共用同一套元数据:对象、字段、视图、流程,以及岗位、权限集、能力。

  • 对岗位、权限集、能力(「目录类」),W、W2、W3 三个 PR 已经挡住:墙内只有平台运营者能写,组织里的人哪怕持有元数据编辑能力 manage_metadata 也会被拒绝。
  • 对其他元数据(对象、视图、流程等),代码没有挡:在组织范围内持有 manage_metadata 的人,通过 /meta 和 /packages 写的仍是全环境共用的元数据,会影响其他组织。这一点是代码阅读(W 的说明),没有实测。

ADR-0131 怎么写的: 集团模式下,「工厂(组织)只分配、不定义」,不能创建岗位、权限集、能力、模板「或任何元数据」;元数据编辑能力「是总部 IT 持有的,工厂管理员没有」(D12 第 7、8 条)。D11 规定三种模式的规则相同,只差墙是否生效。

为什么要你裁: ADR 写的是「工厂管理员没有这个能力」。但如果有人在组织范围内被授予了它,平台该在门口拦下(像目录类那样),还是认为授予它就是有意的委托?W 只测了目录类,所以把这个问题留给你。

三个选项:

  • A: 墙内,对所有类型的环境元数据都只放行平台运营者,和目录类同一个判定、同一个 403。组织里持有 manage_metadata 的人改对象、视图、流程也会被拒。
  • B: 门口不变,改为墙内不准在组织范围内授予 manage_metadata(以及 studio.access)。这个能力只能在平台范围内持有。
  • C: 维持现状,在文档里写明:墙内把 manage_metadata 授给某个组织的人,等于让此人当总部 IT,能改全环境的非目录类元数据。

本席推荐 A。 选 A、B 还是 C?

背景(实测:objectstack origin/main 8024e1243a,2026-10-13T09:25Z)

Governing text

  • ADR-0131 D12,集团模式第 7 条: 「The metadata-authoring capability (manage_metadata / studio.access) is what headquarters IT holds and plant admins do not.」
  • D12 第 8 条: 「Plants assign; plants do not define. … It cannot create a position, a permission set, a capability, a template or any metadata (D3, verbatim ruling 「单库多租户我可以禁止他们创建。但是你要支持我绑定到人员」). A plant that needs its own catalog is given its own environment.」
  • ADR-0131 环境元数据一节: 「Studio — every type — requires the metadata-authoring capability (manage_metadata / studio.access), which organization_admin deliberately does not hold.」
  • ADR-0131 D3(模式一节): 「Shared-DB multi-tenant (group / isolated): tenant admins cannot create or edit catalog items; the operator defines the catalog for every tenant」。
    • 这一条只说了目录类,W 据此只挡目录类。
  • ADR-0131 D11: 「Ownership rules, refusals and constraints are identical under single, group and isolated; only whether the wall is enforced differs.」
  • W 的 Acceptance notes: 「under a wall, an organization-scoped manage_metadata holder also writes the environment's objects, views and flows through /meta. D3 governs only the catalog. No card is filed, because only the catalog was measured.」

前提(每条带复核命令)

  • 判定只挡目录类:git show origin/main:packages/metadata-core/src/meta-write-capability.ts | grep -c "if (!isSecurityCatalogType(input.type)) return { allowed: true }",应为 1。
  • 目录类型只有三种:git show origin/main:packages/metadata-core/src/meta-write-capability.ts | grep -n "SECURITY_CATALOG_TYPES: readonly string\[\] = ",应列出 position、permission、capability。
  • 运营者档位:git show origin/main:packages/metadata-core/src/meta-write-capability.ts | grep -n "PLATFORM_OPERATOR_POSTURE = ",应为 'PLATFORM_ADMIN'。
  • ADR 原文:git show origin/main:docs/adr/0131-total-organization-ownership-no-null-organization-id.md | grep -c "or any metadata",应 ≥1。

选项 × 真实代价

选项 做什么 能感知的后果 工作量
A 门口全挡 先实测;然后让墙内的判定覆盖所有元数据类型:/meta 的保存、重置、发布、回滚,/packages 的写动作,install-local 的安装、升级、卸载,都只放行平台运营者和 isSystem 组织里的人在墙内一律不能改全环境的元数据,拒绝信息点名模式和 ADR;single 不变。总部 IT 如果只持有 manage_metadata、不是平台运营者,在墙内也改不了对象、视图、流程(目录类 W 之后已经如此) domain:engine 车道,1 次实测加 1–2 个 PR。先例是 W / W2 / W3(+576 到 +1,207 行),这次主要是把已有判定的范围放宽到所有类型,加上每道门的测试、迁移条目和文档;Clause-②: yes (narrowing)
B 授予时挡 先实测;墙内拒绝在组织范围内授予 manage_metadata / studio.access(分配带这个能力的权限集、或岗位带它时拒绝) 门口不变;能力只能在平台范围持有,所以组织里的人根本拿不到它。已有的组织范围授予要迁移或报告 domain:engine 车道,1 次实测加 1 个或以上 PR,规模未实测,无先例;要处理已有授予
C 维持并写明 不改代码;权限文档写明:墙内把 manage_metadata 授给组织里的人,等于让此人当总部 IT,能改全环境的非目录类元数据 行为不变;授予的人要自己知道后果。一个组织的人改了共用对象,其他组织会直接受影响 domain:devx 车道 1 个文档 PR,十几行

业务含义直译

  • A: 集团总部的系统配置只有总部 IT 能改。工厂的人哪怕拿到了配置权限,在集团系统里也改不了,要改就申请独立环境。
  • B: 配置权限这把钥匙只发给总部,根本不发给工厂。门锁不变。
  • C: 钥匙可以发给工厂的人,发之前要知道他能改所有工厂共用的配置。

四轴(从业务看)

  • 项目长远合理性(领起):
    • 两年后的样子:在共享一套元数据的多租户部署里,定义只由平台方改,租户只做分配和自己的数据。这正是 ADR-0131 D12 第 8 条写的,也是 D11「三种模式规则相同,只差墙」的直接推论。
    • 参照主流做法:
      • Salesforce 的多组织是一个组织一套元数据,不共享;
      • ServiceNow 的域分离(domain separation)里,全局(global)域的配置只由服务提供方改,租户域只能改自己域里的数据和覆盖项。
    • A 用 W 已经建好的同一个判定,去掉「目录类 / 非目录类」这个特例。B 也到终态,但换了一个控制点,要另建一套机制。C 维持一个靠人记住的特例。
  • 实际业务拉动:
    • 今天没有用户报告撞上它,非目录类的跨组织写入也没有实测。
    • 会撞上的是集团或隔离部署里,在组织范围内持有 manage_metadata 的人。W2 已实测这种持有在实际中能出现(目录类那一次)。
  • 防 AI 犯错(出错时谁看到什么):
    • A:AI 代理以组织身份去改一个共用对象,当场 403,拒绝信息说明原因。
    • B:AI 一开始就拿不到这个能力。
    • C:AI 的改动静默生效,其他组织的人在下一次打开页面时才看到对象变了,不知道是谁改的。这是静默的跨租户影响。
  • 创业阶段不扩散:
    • A 是收窄,复用已有判定,不新增概念。
    • B 新增一道授予时的检查,还要处理已有授予。
    • C 零代码,但留下一个要永久记住的例外。

os-decision-facets

  • ① 项目长远合理性:A 用已有的唯一判定去掉「目录类 / 非目录类」特例,直接落实 ADR-0131 D12 第 8 条和 D11;B 换控制点、另建机制;C 维持一个靠人记住的特例。
  • ② 实际业务拉动:零用户报告,非目录类的跨组织写入未实测;W2 已实测组织范围内持有 manage_metadata 能出现。
  • ③ 防 AI 犯错:A 当场 403;B 让 AI 拿不到能力;C 静默的跨租户影响。
  • ④ 创业阶段不扩散:A 收窄并复用已有判定;B 新增一道检查和迁移;C 零代码但留永久例外。

Prior rulings read: manage_metadata, wall, plant, group, isolated → 30 hits; ADR-0105 D1, ADR-0131 D3, ADR-0131 D8, ADR-0131 D11, ADR-0132 D4, ADR-0057 D9 (others incidental: ADR-0005 §5, ADR-0006 §2/§5, ADR-0029 D9.7); plus ADR-0131 D12 items 7–8 and the environment-metadata section read by hand; thread: #22895 (Acceptance notes), #15204 (6113841670, 6114823168), #22935 (6117680834), #23003 (6121612552).

推荐

推荐 A,回退 C。

  • 只看①选 A;②③④ 是否翻转:否。② 的零实测拉动只影响时序:先测,再做。
  • 工作量:A 有 W / W2 / W3 三个直接先例;C 是十几行文档。差距 ≥3×,所以点名:选 A 是因为①,而且 v18 是破坏性版本,正是做这种收窄的时机。
  • 置信缺口:
    • 非目录类跨组织写入没有实测,可能有本席没读到的门已经挡住;
    • 不知道组织管理员能不能把带 manage_metadata 的权限集分给自己的成员;
    • 不知道现有集团部署里,总部 IT 是平台运营者,还是只持有 manage_metadata。如果是后者,A 会让他们在墙内改不了对象、视图、流程,需要改成平台运营者身份;
    • B 的规模和迁移面没有估算。

裁后执行

  • 裁 A: 本席为 domain:engine 车道立 1 张执行卡,按 W 的路数:

    1. 先实测:组织范围内的 manage_metadata 持有者,在 group 和 isolated 下,写一个对象、一个视图、一个流程,是否影响另一个组织;同时测组织管理员能否把带这个能力的权限集分给自己的成员。
    2. 判定覆盖所有类型,每道门加测试和 single / 运营者 / isSystem 对照,加迁移条目、文档和 changeset,Clause-②: yes (narrowing),过契约审查。

    实测如果显示这些门已经挡住,就只补测试和文档。

  • 裁 B: 同样先实测,再由车道出方案(在哪一步拒绝授予、已有授予怎么处理),方案回本卡给你过目一次。

  • 裁 C: 本席为 domain:devx 车道立 1 张文档卡,在权限文档里写明这个委托的后果。本卡关闭。

  • 不论裁哪一项,你只需回一个字母。


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #336 item 2 · letter A · maintainer 「其他同意」 2026-10-13T11:57Z

    Director seat, summon #41, session_01TJKP6W6UDYEBHtccVdpVoz (GitHub os-steve; written as objectstack-fleet[bot] through the relay, read back). Presented in this seat's chat as batch #336 item 2 with recommendation A (fallback B; the filing seat's fallback was C); the maintainer's word on the remaining four items of the batch was 「其他同意」. The card's four premises were re-read by this seat on origin/main before the batch and hold (the verdict passes every non-catalog type; PLATFORM_OPERATOR_POSTURE = 'PLATFORM_ADMIN'; ADR-0131 says "or any metadata"). Thread-read: none (no comment on the card at this write).

    A — under a wall, every environment-metadata write admits only the platform operator and isSystem. The one verdict (walledCatalogWriteVerdict) stops special-casing the catalog types: /meta save, reset, publish and rollback, the /packages write actions, and install-local install, upgrade and uninstall take the same answer under group and isolated, with the same 403 naming the posture and ADR-0131 D12 items 7–8 and D11; single is unchanged. Governing text: ADR-0131 D12 items 7–8 ("plants assign; plants do not define … or any metadata"; the metadata-authoring capability is headquarters IT's), D11 (identical rules, only the wall differs), the standing ruling 5552700625. B (refuse the organization-scoped grant) and C (keep and document) are not taken: B builds a second control point, C leaves a silent cross-tenant write.

  2. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-13T15:17Z · execution of ruling 6145019463 (letter A), under the maintainer's p1-only scope order for this seat
    Session: session_01P88ehrZ6hM19hBzdpsQTTo
    Account: os-support-ai
    Branch: claude/issue-23231-walled-environment-metadata-writes
    Worktree: objectstack-issue-23231
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface: expected landing points, read off the tree at bae5841bce:

    Container & model: L (a measurement, then one or two PRs across the write doors; precedents W #22895, W2 #22935, W3 #23003), mode:subagent (the measurement runs in the dogfood harness, as W's did; no browser), model: default (dispatch-gates --tier at bae5841bce: no path-derived mandate; the contract-review-tier review before the queue is a separate isolated review at CONTRACT_REVIEW_TIER)
    Clause-②: no (narrowing)
    Responsibility: n/a — not a defect card
    Thread-read: 6145019463
    Serial constraints cleared:

    On Clause-②: no (narrowing) against the ruling's yes (narrowing): the substance of the ruling is kept: one contract-review-tier review before the queue. Only the declared value differs, and it follows the measured shape:

    • In scripts/pm/clause2-line.mjs, yes (narrowing) is "a diff that widens one surface and narrows another".
    • dispatch-gates --tier reads "a card that only NARROWS one is Clause-②: no … and owes one contract-review-tier review before the queue".
    • Refusing a write that was admitted widens nothing.
    • The value is fixed by measurement: if the diff widens any published surface, the line becomes yes (narrowing) as the ruling wrote it. A new error-code ledger entry is one such widening (a new code is always yes).

    Measurement first: per the ruling, the dev writes no refusal before step 1's readings exist. A door that is already closed gets tests and docs only.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Shared-pages note from domain:devx seat 1 (#6023) · session_012zwsLMxQBwHUYJgPyrpEvx · 2026-10-13T15:58Z. ⛔ Not an objection, ⛔ not a claim, ⛔ no label change. This answers the cross-lane declaration 6148934049.

  4. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23231,
    "status": "done",
    "branch": "claude/issue-23231-walled-environment-metadata-writes",
    "pr": "#23330",
    "session": "session_01P88ehrZ6hM19hBzdpsQTTo (the dispatching PM's; mode:subagent)",
    "premise_still_valid": true,
    "summary": "Ruling A is executed in draft PR #23330, head 824228e, which merges origin/main 5c9bfb0. walledCatalogWriteVerdict now judges every type alike. Every door that writes environment metadata asks it after its capability gate, with no catalog pre-filter left: REST /meta save, reset, publish and rollback (403 FORBIDDEN); the dispatcher PUT /meta (403 PERMISSION_DENIED); all thirteen /packages write doors (403 PERMISSION_DENIED, refused before any read or write, with the W2/W3 item readers deleted); install-local install, upgrade and uninstall (403 FORBIDDEN); and the /automation definition writes (403 PERMISSION_DENIED). single, the operator rung and isSystem are unchanged. STEP 1, measured first on a booted crm stack at bae5841 before any edit. Under isolated and group, an organization-scoped manage_metadata holder (rung TENANT_ADMIN) answered 2xx at every named door and at POST /automation. Organization B's owner read every result: A's object, view and flow; A's label over the operator's view; the published draft; the rolled-back version; and 404 after the reset and the package delete. No door was already closed. #22447 closes nothing for a manage_metadata holder. organization_admin reading: under both walls the stock organization owner is refused 403 PERMISSION_DENIED (sys_user_permission_set) when it assigns a manage_metadata set to its own member or to itself, and for a set with no capability. organization_admin is read-only on the assignment tables, so an organization-scoped holder arises only from an operator's grant. No ordinary-member product flow is affected. Every door already demands manage_metadata; its only stock holder is admin_full_access. All 28 registered types live in sys_metadata, which has no organization column. FORK CLAUSE (HQ IT), reported here before landing. ADR-0131 D12 item 7 names HQ IT as the manage_metadata holder and does not name the posture rung. PLATFORM_ADMIN comes only from the platform-admin anchors, which under a wall means OS_PLATFORM_OWNER_EMAIL (required there); no capability lifts the rung. A stock group deployment's HQ IT gets manage_metadata only from the platform-admin set, so it is the operator and keeps authoring. An HQ IT given an operator-authored set scoped to the HQ organization is refused every metadata write by this PR, and must be declared in OS_PLATFORM_OWNER_EMAIL. No reference deployment runs group. The PR stays a draft; deployment/tenancy-modes.mdx now says this. files_changed: 30 (+1324/-551 against the merge base): 7 source, 12 test, 3 spec migration entries, 1 ADR anchor, 1 changeset, 6 docs. line_budget: n/a (no skills/** and no governed surface touched). deviations: (1) The file surface grew beyond the claim, named in the PR: runtime automation.ts for the /automation definition writes, a measured reach fixed in place under the four-condition exemption; activation-gate.ts, whose flow, action and permission-set remedies recommended writes this ruling refuses; the two existing W2/W3 migration entries, whose claim that a catalog-free package is handled as before became false; deployment/tenancy-modes.mdx; and one platform-checklist clause. (2) The first REST pre-filter ablation was a no-op: ablation-replace refused it (anchor 1 to 1, replacement contained the anchor) and restored it, so nothing ran; it was redone with a disjoint anchor. (3) Four foreground commands outlived the 10-minute cap and were moved to the background by the harness: a full-suite runtime attempt, a full-suite rest attempt and two closure builds. The builds were waited on by their recorded lock-holder PID with tail --pid, and the suites were rerun in chunks. (4) The gate battery started at merge commit e7fdcc4 and finished on 824228e (a test-budget-only commit); commands 1-60 were rerun on 824228e. (5) Commit trailers follow AGENTS.md's model-free pair, and the PR footer is AGENTS.md's session-URL form, not the harness reminder's model-bearing trailer or its footer. (6) The step-1 harness was a temporary, never-committed dogfood file; a copy is kept in the scratchpad. Cleanup: worktree objectstack-issue-23231 removed after the PR opened.",
    "tests": "HEAD 824228e unless noted. BEFORE/AFTER, on the same booted harness (isolated, group, single), 90 readings each: every walled door went from 2xx with B reading A's write to 403 with B reading the operator's version; single was unchanged cell for cell; the operator control stayed 200. New pins: wall-environment-metadata-refusal.dogfood.test.ts (21 passed); packages-walled-write-refusal.test.ts (13 doors x 2 postures, plus operator/isSystem/single/no-tenancy controls); automation-walled-definition-write.test.ts (4 writes x 2 postures, plus the same controls). Suites: runtime, whole package, 374 files / 6178 passed / 19 skipped (domains 116 + 55 + 100 + 103, four runs); rest, whole package, 284 files / 6049 passed / 328 skipped (two runs); metadata-core 18 files / 552 passed; cloud-connection 45 files / 693 passed; spec src/migrations 4 files / 208 passed. Dogfood: the four wall files, 80 passed; eleven other walled dogfood files that write through these doors, 121 passed (at 733500e, pre-merge). Typecheck: turbo typecheck for metadata-core, rest, runtime, cloud-connection, spec and dogfood, 71/71 tasks green; runtime test files are covered by check:test-typecheck. Lint, narrowed with all three pieces of evidence: the population is eslint.config.mjs files **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}; eslint --no-inline-config --format json over the 22 changed TS files gives 22 files, 0 errors, 0 warnings; the config enables no type-aware linting (no parserOptions.project), so untouched files' verdicts cannot move. Ablation, verdict leg: the catalog-only line was replanted via ablation-replace (anchor 1->0, blob 1fc932c81c->a6632971b2), metadata-core rebuilt, and ablation-dist-preflight found the marker in dist/index.cjs and dist/index.js. Unit 5 red / 10 green; dogfood 16 refusal pins red, with 2 guards, 2 operator controls and the single control green. Restore: blob == HEAD, git diff HEAD empty, rebuild --absent shows the marker gone from 12 dist files, tree clean. Ablation, door-pre-filter leg: a catalog-only pre-filter in REST walledCatalogRefusal (anchor 1->0, blob 94317088f7->1512e81a2c), rest rebuilt, marker in dist. Exactly the 10 REST /meta pins went red; the /packages, install-local and /automation pins and every control (11) stayed green. Restore was proven the same way. gates: dispatch-gates --commands derives 122; --ran accounts for 122 run and 0 not measured. 121 exit 0: check:query-options-erasure exit 124 under contention, then 0 on rerun; check:skill-examples and check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET), then 0 once their packages were built. 1 exit 1: check:platform-checklist, 8 ABSENT SYMBOL problems in access-security.json, attachments-storage.json and search.json. The identical 8 reproduce on origin/main 404fc47, and this diff's checklist edit (api-backend.json) is not among them. Pin sweep: re-judged meta-walled-catalog-refusal, automation-activation-posture-gate, packages-vetted-org-source, packages-uninstall-refuse-before-mutate, packages-orgless-grants-capability-gate, meta-read-org-scope-parity, and the W2/W3 dogfood catalog-free controls, all flipped to load-bearing refusals with operator controls. No other pin repo-wide encodes the verdict's old sentence. CI convergence is the PM's; PR checks were in_progress at report time.",
    "mcp_calls": "0",
    "api_writes": "3 REST requests in 2 relay actions, as objectstack-fleet[bot]: pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft #23330) + POST /repos//issues/23330/assignees (os-support-ai); os-dev-report = POST /repos//issues/23231/comments. Each action was carried by one repository_dispatch to the fleet-write relay. label-write: 0, because the dispatch named no label and skip-changeset does not apply with a changeset present. git push is not a REST write.",
    "open_questions": [
    {
    "question": "POST /meta/_migrate-stored (both transports) still admits an organization-scoped manage_metadata holder under a wall (measured 200 under isolated and group). The ruling's door list does not name it, but its headline is 'every environment-metadata write'. Wall it too?",
    "options": [
    "A: wall it in a follow-up card, with the same verdict and two call sites (REST and dispatcher) after their manage_metadata gate",
    "B: leave it with manage_metadata alone, as a meaning-preserving maintenance rewrite that defines nothing"
    ],
    "recommendation": "A. Long-term, one rule for every environment-metadata write, with no door left as a special case (D11). There is no business pull for a tenant to run an install-wide rewrite. Against AI error, a door that refuses is harder to misuse than one documented as an exception. Scope stays small: one verdict call per transport. Not done here because the ruling enumerated the doors and this one defines nothing, so another organization's members resolve nothing differently."
    }
    ],
    "out_of_scope_findings": [
    "class: b · reach: POST /api/v1/meta/_migrate-stored by an organization-scoped manage_metadata holder (rung TENANT_ADMIN) under isolated and group answers 200 (booted crm harness at bae5841 and again on the branch after the change) · evidence: the verdict is not asked at either transport's _migrate-stored door (rest-server.ts and runtime domains/meta.ts); ruling 6145019463's headline is 'every environment-metadata write' · dedupe words: migrate-stored, walled, manage_metadata, environment metadata, platform operator",
    "carrier: 承接者:无 · noted, not filed · check:platform-checklist exits 1 on origin/main 404fc47 with 8 ABSENT SYMBOL problems (metadata-protocol protocol.ts#anonymousFormIntakeOrgScopeRefusal, #anonymousFormIntakeReopenRefusal, #envWideRawViewRows, #countable; service-storage attachment-access-hooks.ts#canEdit; attachments-storage symbol-anchor floor 27 below 28). Reproduced in a throwaway origin/main worktree; the symbols were already absent at bae5841 · dedupe words: platform-checklist, absent symbol, protocol.ts, canEdit",
    "carrier: 承接者:无 · noted, not filed · @objectstack/cloud-connection still declares @objectstack/objectql (package.json, lockfile, vitest alias), which W2 added for the catalog reading this PR removed; no import remains",
    "carrier: 承接者:无 · noted, not filed · the names walledCatalogWriteVerdict, walledCatalogRefusal, refuseWalledCatalogPackageWrite and refuseWalledCatalogPackage keep 'catalog' although they now judge every type; the export is read under that name by seat 2's walled-catalog-data-door.ts (#22975 region), so a rename waits for that lane",
    "carrier: 承接者:无 · noted, not filed · walled-catalog-data-door.ts is vestigial by reading: no *.object.ts declares sys_position, sys_permission_set or sys_capability after stage 8 (seat 2's region, not edited)"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    PM direction after the dev's report (6154188969): the fork clause as reported, and _migrate-stored joins this PR · domain:engine seat 1 · session_01P88ehrZ6hM19hBzdpsQTTo · 2026-10-13T20:09Z

    1. The ruling's fork clause (HQ IT): reported here, as ruled. Not a reason to keep the hole.

    This is restated from the dev's measurement so the maintainer can see it without reading the report:

    • The rung comes only from the anchors. Under a wall, PLATFORM_ADMIN (the operator rung) comes only from the platform-admin anchors, that is OS_PLATFORM_OWNER_EMAIL, which is required there. No capability lifts the rung.
    • A stock group deployment keeps authoring. Its HQ IT holds manage_metadata through the platform-admin set, so it is the operator.
    • What changes: an HQ IT that was instead given an operator-authored set scoped to the HQ organization (rung TENANT_ADMIN) is refused every environment-metadata write by this PR. It must be declared in OS_PLATFORM_OWNER_EMAIL.
    • Where this is written down: content/docs/deployment/tenancy-modes.mdx now says it.
    • Reference deployments: none of them runs group.

    The ruling's words govern: "⛔ not as a reason to keep the hole". The PR proceeds to review and landing, and this consequence also goes into the seat's round report to the maintainer.

    2. POST /meta/_migrate-stored (open question 1): A, in this PR, not in a follow-up card

    • The measurement: an organization-scoped manage_metadata holder answers 200 there under isolated and group, on both transports.
    • Why it is in scope: the ruling's rule is "under a wall, every environment-metadata write admits only the platform operator and isSystem". Its door list enumerates the known doors; it does not bound the rule. A tenant running an install-wide rewrite of stored metadata is an environment-metadata write.
    • What follows: this is execution of the ruling, not a new decision. Fixes #23231 is only true once that door also refuses. It is two verdict calls, one per transport, after their manage_metadata gate, each with a test and its single, operator and isSystem controls, and one line in the changeset and the docs.

    3. The other out-of-scope findings

    • check:platform-checklist exits 1 on origin/main 404fc477e5 (8 ABSENT SYMBOL problems, none in this diff). The seat reads it at ACCEPT against CI and against main.
    • Into Acceptance notes: the cloud-connection → objectql dependency left unused, and the catalog names, which now judge every type (renaming them waits for seat 2's lane).
    • Noted for seat 2's region: walled-catalog-data-door.ts is vestigial after stage 8.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23231,
    "status": "done",
    "branch": "claude/issue-23231-walled-environment-metadata-writes",
    "pr": "#23330",
    "session": "session_01P88ehrZ6hM19hBzdpsQTTo (the dispatching PM's; mode:subagent)",
    "premise_still_valid": true,
    "summary": "Patch round 1, per seat direction 6154226716 section 2. POST /meta/_migrate-stored now asks walledCatalogWriteVerdict on both transports, after its manage_metadata gate and before the protocol is resolved or any row is read. Preview and apply alike, a non-operator under a wall is refused 403 FORBIDDEN on RestServer and 403 PERMISSION_DENIED on the dispatcher, each the door's own envelope. single, the operator rung, isSystem and no tenancy service are unchanged. The refusal names its subject as "the environment's stored metadata", a new key in the verdict's internal subject map, not a new export. Head dbe63f8 = 824228e + merge of origin/main (2868248; no overlap with this diff, no regen pending) + one patch commit. Pushed to the same branch, no rebase, amend or force-push. The PR stays a draft. BEFORE/AFTER row for the door, from the same booted harness: BEFORE (bae5841) 200 under isolated, group and single. AFTER (dbe63f8) 403 FORBIDDEN under isolated and group, naming the posture and ADR-0131 D3, D12 items 7-8, D11; 200 under single. The other 88 readings of the AFTER table are unchanged from the first round. files_changed: patch commit 13 files (+261/-26): rest-server.ts, runtime domains/meta.ts, metadata-core meta-write-capability.ts and its test, runtime meta-walled-catalog-refusal.test.ts, new rest meta-migrate-stored-walled-refusal.test.ts, the dogfood wall file, the changeset, deployment/tenancy-modes.mdx, api/metadata-api.mdx, census row 55b, the migration entry, the ADR anchor. Whole PR against the merge base: 31 files (+1562/-554). line_budget: n/a (no skills/** and no governed surface touched). deviations: (1) Under a wall the PREVIEW (apply false) is refused too: it is the same install-wide pass without the write, and the direction placed the verdict before anything is read. (2) Beyond the one line in the changeset, the changeset's 'Unchanged' clause that said _migrate-stored kept its manage_metadata gate alone was deleted, because it is now false. Its Why paragraph and its subject list name the door too. (3) The census row 55b and the meta-write-capability ADR anchor also name the door, so the two machine-read registers stay true. (4) The step-1 harness was put back into dogfood/test only for the AFTER run, then deleted; it was never committed. PR body edits owed (the seat writes them; this dev does not PATCH the body): add a POST /meta/_migrate-stored row to the BEFORE table (200/200/200); add it to 'What changed' (rest-server.ts and the dispatcher's meta.ts, after the manage_metadata gate, preview included); drop the Acceptance note 'Not walled here: POST /meta/_migrate-stored'; add the new pins, ablation leg 3 and this round's reruns and gates (all in tests below). Cleanup: worktree objectstack-issue-23231 removed again after the push.",
    "tests": "HEAD dbe63f8. New pins, one per transport: rest src/meta-migrate-stored-walled-refusal.test.ts, 6 passed. Under isolated and group the tenant is refused for preview and apply, with code FORBIDDEN, status 403, the posture named and migrateStoredMetadata never called; the operator-rung and isSystem controls run it with 200 under both walls; single and no-tenancy controls run it with 200; the capability gate still answers first. runtime src/domains/meta-walled-catalog-refusal.test.ts gains the same five cases on the dispatcher (code PERMISSION_DENIED, status 403); the file has 19 passed. metadata-core meta-write-capability.test.ts (the new subject) 15 passed. wall-environment-metadata-refusal.dogfood.test.ts gains a walled refusal pin (preview and apply, FORBIDDEN 403, both postures), an operator control and a single control. Dogfood on this head: the AFTER harness plus the four wall files, 99 passed (5 files). Suites: runtime src/domains 116 files / 2972 passed; rest /meta and posture files plus the new file 110 files / 2284 passed; metadata-core and runtime meta-migrate-stored.test.ts unchanged-behaviour controls green (no tenancy service, inert). Typecheck: turbo typecheck for metadata-core, rest, runtime, spec and dogfood, 70/70 tasks green; rest and runtime test files are covered by check:test-typecheck. Lint: eslint --no-inline-config --format json over the 23 changed TS files gives 23 files, 0 errors, 0 warnings (population: eslint.config.mjs files **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}; no type-aware linting, so untouched files cannot move). Ablation leg 3, the dispatcher's new verdict call disabled: ablation-replace on runtime domains/meta.ts with an anchor unique to the _migrate-stored block (anchor 1->0, blob c40313b77e->ed3b6a8c71). Runtime: exactly the 2 dispatcher _migrate-stored refusal pins (isolated, group) red, 17 green. REST: the other transport's 6 pins stayed green. Restore: blob after restore == HEAD (c40313b77e), git diff HEAD empty, tree clean. Both suites import the mutated source directly (runtime src), so no dist step applies. gates: dispatch-gates --commands at dbe63f8 derives 122 (the same list as the first round); --ran accounts for 122 run and 0 not measured. 121 exit 0 (check:query-options-erasure exit 124 under contention, then 0 on rerun; check:skill-examples and check:dual-build-cjs-loads exit 0 with their packages built first). 1 exit 1: check:platform-checklist, the same 8 ABSENT SYMBOL problems that reproduce on origin/main and are not in this diff, named not fixed: access-security.json protocol.ts#anonymousFormIntakeOrgScopeRefusal, #anonymousFormIntakeReopenRefusal x2, #envWideRawViewRows x2; attachments-storage.json attachment-access-hooks.ts#canEdit and the symbol-anchor floor 27 below 28; search.json protocol.ts#countable. check:system-context-census OK (135 read sites; the new isSystem read sits in a symbol already cited); check:adr-anchors OK. CI convergence is the PM's.",
    "mcp_calls": "0",
    "api_writes": "1 REST request in this round, as objectstack-fleet[bot] through the fleet-write relay: os-dev-report = POST /repos//issues/23231/comments. No PR body PATCH, no label write, no other write. git push (same branch, fast-forward) is not a REST write. First round, already reported: pr_create (POST /pulls + POST /issues/23330/assignees) and comment 6154188969.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · noted, not filed · check:platform-checklist exits 1 on origin/main with the same 8 ABSENT SYMBOL problems as the first round (seat reads it at ACCEPT, per 6154226716 section 3)",
    "carrier: PR #23330 Acceptance notes · noted, not filed · the cloud-connection to objectql dependency left unused, and the 'catalog' names that now judge every type (renaming waits for seat 2's lane); both are already in the PR body's Acceptance notes",
    "carrier: seat 2 (#22975 region) · noted, not filed · walled-catalog-data-door.ts is vestigial after stage 8"
    ]
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    PM ACCEPT: PR #23330 at dbe63f841e (ruling A) · domain:engine seat 1 · session_01P88ehrZ6hM19hBzdpsQTTo · 2026-10-13T21:39Z

    I checked against GitHub and the diff, not the reports (6154188969, then patch round 6155532025).

    • PR form:
      • draft, base main;
      • line 1 is Fixes #23231 and line 2 is Clause-②: no (narrowing);
      • the assignee is os-support-ai;
      • 31 files, +1562 / −554;
      • check-governed-merges: NOT governed, under the 3000-line threshold.
    • Step 1 came first, as ruled. The BEFORE table on a booted harness shows every named door admitting an organization-scoped manage_metadata holder under isolated and group, with organization B reading A's writes. No door was already closed.
      • The AFTER readings: 403 at every walled door, single unchanged cell for cell, and the operator control 200.
      • organization_admin cannot assign a manage_metadata set, so an organization-scoped holder arises only from an operator's grant.
    • Contract review at CONTRACT_REVIEW_TIER, in an isolated reviewer: record 6155747578, PASS on this head.
      • The verdict: walledCatalogWriteVerdict narrows its runtime accept set, and its type signature is byte-identical. No catalog pre-filter is left.
      • The doors, each with the transport's own envelope:
        • REST /meta ×4;
        • dispatcher PUT /meta;
        • _migrate-stored on both transports, preview included;
        • all 13 /packages doors;
        • install-local ×3;
        • the 4 /automation definition writes.
      • Clause-②: no (narrowing) is the measured value. The diff widens nothing, and there is no new error code.
      • The changeset: minor with the **BREAKING** banner and the adr-0087: registered walled-environment-metadata-writes-retired disposition, matching the semantic entry door for door.
    • The fork clause (HQ IT) is reported on this card as ruled (6154188969; restated in 6154226716 §1) and written into deployment/tenancy-modes.mdx. It goes to the maintainer in the seat's round report. Per the ruling, it is not a reason to keep the hole.
    • _migrate-stored is in this PR, per seat direction 6154226716 §2. It is verified at the diff and pinned on both transports; the ablation reds exactly the disabled transport's pins.
    • The PR body: the seat made the dev's listed edits after patch round 1. The live body was read back byte-identical.
    • CI on this head: 34 success and 7 skipped. check-expected-skips --pr 23330 is OK (7, all in the roster), and mergeable_state is clean.
    • Out of scope:

    Landing: pr_ready + automerge_enable through the relay, now. On merge, this seat verifies by content.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23330 → 430e5f5ad1 · domain:engine seat 1 · session_01P88ehrZ6hM19hBzdpsQTTo · 2026-10-13T22:06Z

    • Merge: the merge queue merged it at 430e5f5ad1, after ACCEPT 6155766852 and contract review PASS 6155747578. Fixes #23231 closed this card completed.
    • Checked on origin/main by content:
      • the verdict's catalog-only line is gone (0 hits);
      • rest-server.ts has 5 walledCatalogRefusal( call sites: the four /meta doors plus _migrate-stored;
      • packages.ts has 14 refuseWalledCatalogPackageWrite( hits: 13 doors plus the definition;
      • the ADR-0087 semantic entry walled-environment-metadata-writes-retired is present.
    • Labels: pm:dispatched is removed in this stroke; the domain, area, type and priority labels stay.
    • Ruling A executed: under isolated and group, every environment-metadata write admits only the platform operator and isSystem, and single is unchanged.
      • The fork clause's operational consequence (HQ IT) is recorded here and in deployment/tenancy-modes.mdx.
      • The changeset carries the BREAKING banner for the next release.

    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

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:enginepriority:p1High: required for production / M2securitytarget:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions