You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
C9 remainder: under a wall, Setup's catalog editors (position, permission set, capability) are read-only for tenants and say why — the server already refuses (objectstack#22895) #12183
Handed out of the objectstack#15194 epic subtree to the domain:ui lane. The epic PM (session_01Rerax7QTjKMPCUZxQUtPFR, marchtian) did this at the maintainer's request in its session on 2026-10-12: clear next-step work that can be built ahead goes to lane PMs, and the maintainer's tasks are high priority (priority:p1). This card is the lane's claim surface. #7611 stays open with the epic seat for its other parts.
The gap (measured by code read; the lane measures it in a browser first)
on objectui main, the metadata admin's writability gate does not read the posture (useTenancyPosture, packages/app-shell/src/hooks/useTenancyPosture.ts:55, is not consulted there);
a walled tenant withoutmanage_metadata is already read-only through useCanAuthorMetadata;
a walled tenant with it is offered Save, Clone, New and Activate.
objectstack#22895 (stage W) merged at 2026-10-12T03:43Z as 06cc45dc36. Under isolated / group, every door now refuses an organization-scoped principal's write to an environment-level position / permission / capability item with 403 naming the posture. The platform operator keeps the writes. So today the console offers actions the server refuses.
Scope
Measure first, on objectui main against objectstack main (06cc45dc36 or later), under isolated or group with a tenant admin holding manage_metadata:
which catalog surfaces offer a write: the editors, Clone, New, Activate/Deactivate, and the holder-assignment halves;
what the server answers for each.
Also measure an operator (platform admin) as the control. Its writes must stay offered.
Build:
The catalog types become read-only for a walled tenant, with one sentence saying why (a server rule, not a missing permission).
The operator keeps write.
Assignments (who holds a position or set) stay writable for tenants: "tenants are refused creation and may only assign" (objectstack triage pointer 6070695107).
Read the posture and the caller's operator standing from what the server already publishes. ⛔ Do not re-implement the server's verdict. ⛔ Invent no spec field (objectui AGENTS.md #0/#0.1).
Pins: a walled tenant is read-only on each catalog type; the operator is writable; single is unchanged (the control); assignment stays writable.
Boundaries
W2 (objectstack, in flight) adds the same refusal for installing a catalog-carrying package. Marketplace install under a wall is NOT in this card; the epic seat routes it after W2 lands.
⛔ No stacked PRs; base main.
Coordination: post on this card; the epic seat reads it.
Re-verify that no branch or PR already does this (the maintainer's 「不要重复开发」).
Claim: PM loop round 4, queue jump on the seat principal's word in this session, verbatim: 「objectui#12183 插队。」 (the C9 remainder, as filed: measure first, then the walled tenant's catalog types read-only with one sentence why; the operator keeps write; assignment stays writable)
Session: session_016djJF12Qt14ejKR5Vjt4sK
Account: os-steve
Branch: claude/issue-12183-walled-catalog-readonly
Worktree: objectui-issue-12183
Domain: domain:ui
Seat: domain:ui#1
File surface, on objectui main5b20aed:
The catalog write gates at environment scope (?scope=environment), under packages/app-shell/src/views/metadata-admin/:
ResourceEditPage.tsx: canWrite / itemWritable. Rider, same gate: the designer auto-on effect reads canWrite, per pointer 6117177535 on objectui#7611.
PermissionMatrixEditor.tsx: Save and Clone.
ResourceListPage.tsx: New, Clone and Activate/Deactivate. It already reads useTenancyPosture.
catalog-scope.ts and catalog-activation.ts.
The position and capability editors, whichever files render them.
One new sentence saying why, en and zh, in views/metadata-admin/i18n.ts, the designer table.
The posture hook (packages/app-shell/src/hooks/useTenancyPosture.ts), and the operator-standing read where the console already carries it (isPlatformAdmin), only to read what the server publishes.
Assignment (AssignedUsersSection.tsx and the holder halves): read only, to pin that it stays writable.
Co-located tests, the new pins, and .changeset/12183-walled-catalog-readonly.md.
⛔ No spec field. ⛔ No @object-ui/i18n pack key. ⛔ No published type or prop change: if one is needed, stop and report.
(Stop on breach; explain in the report.)
Container & model: M, mode:subagent, model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
Clause-②: no
Responsibility: objectui's Setup catalog write gates: the metadata admin's writability does not read the tenancy posture, so under isolated or group a tenant admin holding manage_metadata is offered Save, Clone, New and Activate on position, permission-set and capability items | the server refuses every one since objectstack#22895 (06cc45dc36, ADR-0131 D3), which publishes the posture through the auth config's features.tenancyPosture | every walled tenant admin with manage_metadata, offered writes that 403
Thread-read: none
Serial constraints cleared: read at 2026-10-12T04:02Z on objectui main5b20aed:
Duplicates: no remote branch or open PR touches this card's subject. Branches matching *12183*, *wall* and *posture*: 0. Open PRs whose file lists touch metadata-admin, useTenancyPosture, useCanAuthorMetadata or the catalog: 0.
This seat's objectui#12169 (PR objectui#12181) edited ResourceEditPage.tsx and PermissionMatrixEditor.tsx. It landed as 5b20aed, so the dev starts from it.
This seat's PR objectui#12180 (objectui#6349 batch 11, in the merge queue) touches packages/app-shell/src/types.ts, fields and sdui-parser. These are disjoint.
{
"issue": 12183,
"status": "done",
"branch": "claude/issue-12183-walled-catalog-readonly",
"pr": "https://github.com/objectstack-ai/objectui/pull/12185",
"session": "session_016djJF12Qt14ejKR5Vjt4sK — the seat session the relay stamped on the PR (mode:subagent, so the parent session).",
"premise_still_valid": true,
"summary": "Under `isolated` or `group`, a tenant holding manage_metadata was offered every catalog write the server refuses since objectstack#22895: New, Save, create, Reset/Delete, Clone, the active switch and the History rollback. One predicate, `walledCatalogReadOnly` / `useWalledCatalogReadOnly` in metadata-admin `catalog-scope.ts`, now folds into the existing caller tier (`canAuthor` → `canAuthor && !walled`) on ResourceListPage, ResourceEditPage and PermissionMatrixEditor. It reads only what the server publishes: `features.tenancyPosture` and the session's `user.isPlatformAdmin`. That flag is objectstack's `grants.posture === 'PLATFORM_ADMIN'`, from the same `resolveUserAuthzGrants` resolution whose rung the doors' `walledCatalogWriteVerdict` admits, so H2 resolved to 'served' and nothing is approximated client-side. Each page names the server rule where the wall decides, the operator keeps every write, `single` and an unknown posture are unchanged, and assignment stays writable on both halves. Rider landed: the designer auto-on effect reads `canWrite`.",
"tests": "HEAD 3bdffbd (code unchanged since eed4f3e; 3bdffbd adds README prose only). Measurement on untouched main sources: the new pin file gave 17 failed / 10 passed, with every walled case offered the write and every control green once a harness slip was fixed (a writable form-only editor draws Edit twice). New pin file walledCatalogReadOnly-12183.test.tsx: 28/28. Union at eed4f3e of every test file naming a touched module, plus the 12141 and 12169 pins, the holder sections and tenancyPostureWall.parity: 104 files / 946 tests pass, in two batches: VERDICT command-exit 0, '52 passed (52) · 369 passed' and '52 passed (52) · 577 passed'. The 3 tests that read the app-shell README pass at 3bdffbd (58). Ablations ran through ablation-replace.mjs from committed state; each proved its mutation on disk and restored blob == HEAD with `git diff HEAD` empty. The subject resolves from src via a relative import and the vitest alias, so no dist was involved. F (predicate false): 14 red, 13 green. A (list): 6 red. B (editor): 4 red. C (matrix): 3 red. D (auto-on reads itemWritable): 1 red. E1/E2 (over-gate the holder sections): 1 red each. G (History rollback ungated): 1 red, and the failure is the expected assertion, a Rollback button rendered for the walled tenant. The first F attempt was refused by the tool before anything ran (the replacement re-contained the anchor); it was re-anchored and rerun.",
"mcp_calls": "0 — no MCP tool used.",
"api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create, executed as POST /repos/objectstack-ai/objectui/pulls (draft) plus the relay assignee request for os-steve on objectui#12185; (2) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/12183/comments. Also 4 git pushes (not REST). Zero label writes, zero writes to objectstack-ai/objectstack content.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted in PR Acceptance notes, not filed (no public-door reach measured; code-read plus the editor-sheet twin pinned here). The standalone history route ResourceHistoryPage passes onRollback to every caller; it is reached from the permission-set editor's History button and by URL, and the rollback door refuses a walled tenant and any caller without manage_metadata. Family: the metadata editor caller tier (objectui#7611, pointer 6117177535). Fix shape: read the item and caller gates there. Dedupe words: 'ResourceHistoryPage rollback canWrite', 'HistoryPanel onRollback ungated', 'history rollback read-only caller'.",
"carrier: 承接者:无 · noted, not filed (pre-existing misleading text, no reach measured). PermissionMatrixEditor's identity badge for a non-walled caller without manage_metadata falls into the type branch and reads 'Read-only (this metadata type has no runtime write channel)', which is false for permission. Dedupe words: 'perm.readOnly badge canAuthor', 'matrix badge names type tier'.",
"carrier: 承接者:无 · noted, not filed (objectstack seam, inference only). plugin-auth effectiveTenancyPosture() publishes the requested posture when no tenancy service answers, while the doors' effectiveTenancyPosture(service) reads that absence as inert. On such a deployment the console shows read-only where the server admits: the conservative direction. Dedupe words: 'features.tenancyPosture fallback requested posture', 'getTenancy absent inert doors'."
],
"gates": {
"build_deps": "turbo run build --filter='@object-ui/app-shell^...' --concurrency=2 via os-verify-lock: VERDICT command-exit 0, 28 successful / 28",
"type_check": "pnpm --filter @object-ui/app-shell type-check (echoed 'tsc --noEmit && tsc -p tsconfig.test.json'): exit 0 at eed4f3e; tsc -p tsconfig.test.json --listFilesOnly lists the new pin (1 hit in 5345 files; 12169 pin as positive control)",
"vitest": "104 files / 946 tests pass at eed4f3e; new pins 28/28; README readers 58/58 at 3bdffbd",
"check:i18n-designer-parity": "exit 0 ('Every en row has a zh row…')",
"check:i18n-drift": "exit 0 ('No en value changed in this range'; 1 designer key added)",
"check:i18n-keys": "exit 0",
"check-changeset-presence": "exit 0 ('6 source file(s) of 1 released package(s) changed … declares 1 changeset')",
"check-changeset-no-major": "exit 0",
"check:changeset-claims": "exit 0",
"check:pending-changeset-literals": "exit 0",
"check:control-bytes": "exit 0; plus grep -naP control-byte self-scan of the 8 files: no hit",
"check:new-line-citations": "exit 0 ('VERDICT new-cross-file-line-citations: 0 new citation(s)')",
"check-vi-mock-override-shape": "exit 0",
"check-vi-mock-inherit": "exit 0",
"check-vi-mock-specifiers": "exit 0",
"check-test-path-roots": "exit 0",
"doc_gates": "check-doc-links, check-doc-fence-languages, check-doc-json-fences, check-doc-expression-carriage: exit 0 at 3bdffbd",
"eslint_targeted": "eslint, run as CI's per-package 'eslint .' runs it (inline config honoured), on the 6 touched .ts/.tsx files from --format json: 0 errors; per-file warning counts equal to main; new test file 0. Population: CI 'pnpm lint' = turbo run lint = each package's 'eslint .'. Invariance: eslint.config.js has no parserOptions.project/projectService (not type-aware), and no repo rule in eslint-rules/ reads other files from disk, so the diff moves no untouched file's verdict.",
"check-readme-exports": "NOT MEASURED: exit 1 is PREREQUISITE (no package dist/index.d.ts built here; CI builds all packages first). The README diff adds no fenced block or import, so the gate input is unchanged.",
"ci": "in_progress — not awaited, per contract"
},
"line_budget": "n/a — no skills/** or governed ledger touched. Diff +704/-43 over 8 files, 478 of the added lines in the new pin file.",
"deviations": [
"Zone 3: the gate does NOT read the environment scope. The server verdict has no scope input; REST PUT /meta asks walledCatalogRefusal before ?package=/mode=draft, so Studio's package-door drafts of a catalog item are refused too, and catalog-scope.ts's header already says the scope is 'never an authority'. Consequence: in Studio, for a walled tenant, these components are read-only for the 3 types too (list New, position editor, matrix grants). A pin covers the list and the matrix.",
"Added within the claimed files: the editor's History sheet rollback (ResourceEditPage HistoryPanel onRollback) now reads canWrite, because the REST rollback door asks the same capability and walled verdicts. This also withholds rollback from a non-walled caller who cannot save (refused by the door too). Pinned, ablation G.",
"File-surface addition: packages/app-shell/README.md (one paragraph in the Setup catalog gates bullet), required by objectui AGENTS.md #2. Outside the claim's list; declared here.",
"H5: the walled sentence already existed (engine.catalog.readOnly.walled, objectui#12089, en+zh) and is reused on all three pages. The one new designer-table key is the matrix badge label perm.readOnly.walled (en+zh), not a sentence key.",
"H4 is code-read of the doors plus the merged dogfood pins (wall-catalog-refusal.dogfood.test.ts and its controls); no live boot of objectstack by this dev.",
"Commit trailers use objectui AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude) over the harness reminder's model-named Co-Authored-By, per the reminder's own precedence clause."
],
"files_changed": [
".changeset/12183-walled-catalog-readonly.md",
"packages/app-shell/README.md",
"packages/app-shell/src/views/metadata-admin/catalog-scope.ts",
"packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx",
"packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx",
"packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx",
"packages/app-shell/src/views/metadata-admin/i18n.ts",
"packages/app-shell/src/views/metadata-admin/walledCatalogReadOnly-12183.test.tsx"
]
}
Reviewed against GitHub and the diff, not the report (os-dev-report6118051883).
Verdict
Shape:
Draft on main. The first line is Fixes #12183, and Clause-②: no sits at a line start.
No closing keyword sits next to objectui#7611.
Assignee os-steve.
Scope: 8 files, +704/−43. 478 of the added lines are the new pin. check-governed-merges --pr objectstack-ai/objectui#12185: NOT governed, 747 changed lines.
H2, the operator standing: SERVED. The seat re-read it on objectstack origin/main.
The one refusal predicate, walledCatalogWriteVerdict (packages/metadata-core/src/meta-write-capability.ts), admits isSystem and callerPosture === PLATFORM_OPERATOR_POSTURE ('PLATFORM_ADMIN').
That rung is derived from the isPlatformAdmin evidence (packages/core/src/security/posture-ladder.ts), and the session's isPlatformAdmin is grants.posture === 'PLATFORM_ADMIN' (resolve-authz-context.ts).
The client reads user.isPlatformAdmin from the session. That reads the served fact; the server's verdict is not re-implemented.
The predicate, read in the diff:walledCatalogReadOnly in catalog-scope.ts is a security-catalog type (position, permission, capability) ∧ postureHasOrgWall(posture) ∧ ¬operator.
It folds into the existing caller tier as canAuthor && !walled on ResourceListPage (New, the active switch), ResourceEditPage (canWrite: Save, create, Reset/Delete, History rollback) and PermissionMatrixEditor (Save, Clone, the artifact caption).
An absent posture is inert, as the server's is: postureHasOrgWall(undefined) is false. single is unchanged.
Assignment is not gated: the holder sections are pinned writable, with ablations E1 and E2.
The served sentence engine.catalog.readOnly.walled (objectui#12089) is reused on all three pages. One new designer-table key, the matrix badge label perm.readOnly.walled, has en and zh.
The rider, landed: the designer auto-on effect reads canWrite (pointer 6117177535 on objectui#7611). Ablation D covers it.
Deviations, the seat's judgement on each:
The gate does not read environment scope. Accepted. The server's verdict has no scope input, and per its doc comment every catalog door asks it, "the /meta item write doors on both transports (save, reset, publish, rollback)". Studio's package-door drafts of these three types are refused for a walled tenant too, so drawing them writable there would offer a 403. The list and the matrix are pinned.
The editor's History-sheet rollback now reads canWrite. Accepted. It is in a claimed file, and the rollback door asks the same capability and walled verdicts. Ablation G covers it.
Clause-②: no: the predicate and its hook are module-internal. The package entry exports only ENVIRONMENT_SCOPE_QUERY from catalog-scope.js, and no prop or type member moves. Changeset: @object-ui/app-shellpatch.
Evidence:
The new pin on untouched main: 17 failed / 10 passed. Every walled case was offered the write, and every control was green.
After the change: 28/28, and the union of 104 files and 946 tests passes.
Ablations F, A, B, C, D, E1, E2 and G each went red as predicted. Each restore was blob-equal to HEAD.
H4: a code read of the doors, plus objectstack's merged wall-catalog-refusal.dogfood.test.ts and its controls. There was no live boot. Declared.
Gates on this head (read at this record): 25 success, 3 skipped by design, 14 in progress, 0 failure.
Out-of-scope findings
Two caller-tier gaps in the same family as objectui#7611, carried there by a pointer in this act:
the standalone ResourceHistoryPage offers rollback to every caller;
PermissionMatrixEditor's badge for a non-walled caller without manage_metadata reads "no runtime write channel", which is false for permission.
objectstack seam, inferred and not measured. With no tenancy service, plugin-auth publishes the requested posture while the doors treat the absence as inert. The console then shows read-only where the server would admit: the conservative direction. Acceptance notes.
Landing: ready and auto-merge through the merge queue once every check on 3bdffbd is success or skipped by design.
The merge: through the merge queue, on top of 7e30d0b. The card closed as completed through Fixes #12183, and no other card closed with it. objectui#7611 stays open.
Content check: all 8 files the PR changes are blob-identical at d928c83 to the accepted head 3bdffbd6e2, which ACCEPT 6118074273 covers.
On main now:
Under isolated or group, a caller who is not the platform operator is read-only on position, permission and capability: New, Save, create, Reset/Delete, Clone, the active switch and the editor's History rollback.
Each page names the server rule (ADR-0131 D3) as the reason.
The rule is one predicate, walledCatalogReadOnly in metadata-admin/catalog-scope.ts. It reads the served features.tenancyPosture and the session's isPlatformAdmin, the same rung objectstack's walledCatalogWriteVerdict admits.
The operator keeps every write. single and an unknown posture are unchanged. Assignment stays writable on both halves.
This closes the C9 remainder the card was handed for.
Two caller-tier gaps in the same family are on objectui#7611 (pointer 6118079494): the standalone history route's rollback, and the matrix badge naming the wrong tier.
Marketplace install under a wall (W2) stays the epic seat's, as the card said.
State:pm:dispatched is removed in this act. domain:ui, priority:p1 and target:v18 stay.
Activity
objectstack-fleet commentedon Oct 12, 2026
Claim: PM loop round 4, queue jump on the seat principal's word in this session, verbatim: 「objectui#12183 插队。」 (the C9 remainder, as filed: measure first, then the walled tenant's catalog types read-only with one sentence why; the operator keeps write; assignment stays writable)
Session:
session_016djJF12Qt14ejKR5Vjt4sKAccount:
os-steveBranch:
claude/issue-12183-walled-catalog-readonlyWorktree:
objectui-issue-12183Domain:
domain:uiSeat:
domain:ui#1File surface, on objectui
main5b20aed:?scope=environment), underpackages/app-shell/src/views/metadata-admin/:ResourceEditPage.tsx:canWrite/itemWritable. Rider, same gate: the designer auto-on effect readscanWrite, per pointer6117177535on objectui#7611.PermissionMatrixEditor.tsx: Save and Clone.ResourceListPage.tsx: New, Clone and Activate/Deactivate. It already readsuseTenancyPosture.catalog-scope.tsandcatalog-activation.ts.views/metadata-admin/i18n.ts, the designer table.packages/app-shell/src/hooks/useTenancyPosture.ts), and the operator-standing read where the console already carries it (isPlatformAdmin), only to read what the server publishes.AssignedUsersSection.tsxand the holder halves): read only, to pin that it stays writable..changeset/12183-walled-catalog-readonly.md.@object-ui/i18npack key. ⛔ No published type or prop change: if one is needed, stop and report.Container & model:
M,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"Clause-②: no
Responsibility:
objectui's Setup catalog write gates: the metadata admin's writability does not read the tenancy posture, so under isolated or group a tenant admin holding manage_metadata is offered Save, Clone, New and Activate on position, permission-set and capability items | the server refuses every one since objectstack#22895 (06cc45dc36, ADR-0131 D3), which publishes the posture through the auth config's features.tenancyPosture | every walled tenant admin with manage_metadata, offered writes that 403Thread-read: none
Serial constraints cleared: read at 2026-10-12T04:02Z on objectui
main5b20aed:*12183*,*wall*and*posture*: 0. Open PRs whose file lists touchmetadata-admin,useTenancyPosture,useCanAuthorMetadataor the catalog: 0.ResourceEditPage.tsxandPermissionMatrixEditor.tsx. It landed as5b20aed, so the dev starts from it.packages/app-shell/src/types.ts, fields and sdui-parser. These are disjoint.marchtian, held by [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) objectstack#15194) handed this card to the lane. Coordination posts go on this card, per its body.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{ "issue": 12183, "status": "done", "branch": "claude/issue-12183-walled-catalog-readonly", "pr": "https://github.com/objectstack-ai/objectui/pull/12185", "session": "session_016djJF12Qt14ejKR5Vjt4sK — the seat session the relay stamped on the PR (mode:subagent, so the parent session).", "premise_still_valid": true, "summary": "Under `isolated` or `group`, a tenant holding manage_metadata was offered every catalog write the server refuses since objectstack#22895: New, Save, create, Reset/Delete, Clone, the active switch and the History rollback. One predicate, `walledCatalogReadOnly` / `useWalledCatalogReadOnly` in metadata-admin `catalog-scope.ts`, now folds into the existing caller tier (`canAuthor` → `canAuthor && !walled`) on ResourceListPage, ResourceEditPage and PermissionMatrixEditor. It reads only what the server publishes: `features.tenancyPosture` and the session's `user.isPlatformAdmin`. That flag is objectstack's `grants.posture === 'PLATFORM_ADMIN'`, from the same `resolveUserAuthzGrants` resolution whose rung the doors' `walledCatalogWriteVerdict` admits, so H2 resolved to 'served' and nothing is approximated client-side. Each page names the server rule where the wall decides, the operator keeps every write, `single` and an unknown posture are unchanged, and assignment stays writable on both halves. Rider landed: the designer auto-on effect reads `canWrite`.", "tests": "HEAD 3bdffbd (code unchanged since eed4f3e; 3bdffbd adds README prose only). Measurement on untouched main sources: the new pin file gave 17 failed / 10 passed, with every walled case offered the write and every control green once a harness slip was fixed (a writable form-only editor draws Edit twice). New pin file walledCatalogReadOnly-12183.test.tsx: 28/28. Union at eed4f3e of every test file naming a touched module, plus the 12141 and 12169 pins, the holder sections and tenancyPostureWall.parity: 104 files / 946 tests pass, in two batches: VERDICT command-exit 0, '52 passed (52) · 369 passed' and '52 passed (52) · 577 passed'. The 3 tests that read the app-shell README pass at 3bdffbd (58). Ablations ran through ablation-replace.mjs from committed state; each proved its mutation on disk and restored blob == HEAD with `git diff HEAD` empty. The subject resolves from src via a relative import and the vitest alias, so no dist was involved. F (predicate false): 14 red, 13 green. A (list): 6 red. B (editor): 4 red. C (matrix): 3 red. D (auto-on reads itemWritable): 1 red. E1/E2 (over-gate the holder sections): 1 red each. G (History rollback ungated): 1 red, and the failure is the expected assertion, a Rollback button rendered for the walled tenant. The first F attempt was refused by the tool before anything ran (the replacement re-contained the anchor); it was re-anchored and rerun.", "mcp_calls": "0 — no MCP tool used.", "api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create, executed as POST /repos/objectstack-ai/objectui/pulls (draft) plus the relay assignee request for os-steve on objectui#12185; (2) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/12183/comments. Also 4 git pushes (not REST). Zero label writes, zero writes to objectstack-ai/objectstack content.", "open_questions": [], "out_of_scope_findings": [ "carrier: 承接者:无 · noted in PR Acceptance notes, not filed (no public-door reach measured; code-read plus the editor-sheet twin pinned here). The standalone history route ResourceHistoryPage passes onRollback to every caller; it is reached from the permission-set editor's History button and by URL, and the rollback door refuses a walled tenant and any caller without manage_metadata. Family: the metadata editor caller tier (objectui#7611, pointer 6117177535). Fix shape: read the item and caller gates there. Dedupe words: 'ResourceHistoryPage rollback canWrite', 'HistoryPanel onRollback ungated', 'history rollback read-only caller'.", "carrier: 承接者:无 · noted, not filed (pre-existing misleading text, no reach measured). PermissionMatrixEditor's identity badge for a non-walled caller without manage_metadata falls into the type branch and reads 'Read-only (this metadata type has no runtime write channel)', which is false for permission. Dedupe words: 'perm.readOnly badge canAuthor', 'matrix badge names type tier'.", "carrier: 承接者:无 · noted, not filed (objectstack seam, inference only). plugin-auth effectiveTenancyPosture() publishes the requested posture when no tenancy service answers, while the doors' effectiveTenancyPosture(service) reads that absence as inert. On such a deployment the console shows read-only where the server admits: the conservative direction. Dedupe words: 'features.tenancyPosture fallback requested posture', 'getTenancy absent inert doors'." ], "gates": { "build_deps": "turbo run build --filter='@object-ui/app-shell^...' --concurrency=2 via os-verify-lock: VERDICT command-exit 0, 28 successful / 28", "type_check": "pnpm --filter @object-ui/app-shell type-check (echoed 'tsc --noEmit && tsc -p tsconfig.test.json'): exit 0 at eed4f3e; tsc -p tsconfig.test.json --listFilesOnly lists the new pin (1 hit in 5345 files; 12169 pin as positive control)", "vitest": "104 files / 946 tests pass at eed4f3e; new pins 28/28; README readers 58/58 at 3bdffbd", "check:i18n-designer-parity": "exit 0 ('Every en row has a zh row…')", "check:i18n-drift": "exit 0 ('No en value changed in this range'; 1 designer key added)", "check:i18n-keys": "exit 0", "check-changeset-presence": "exit 0 ('6 source file(s) of 1 released package(s) changed … declares 1 changeset')", "check-changeset-no-major": "exit 0", "check:changeset-claims": "exit 0", "check:pending-changeset-literals": "exit 0", "check:control-bytes": "exit 0; plus grep -naP control-byte self-scan of the 8 files: no hit", "check:new-line-citations": "exit 0 ('VERDICT new-cross-file-line-citations: 0 new citation(s)')", "check-vi-mock-override-shape": "exit 0", "check-vi-mock-inherit": "exit 0", "check-vi-mock-specifiers": "exit 0", "check-test-path-roots": "exit 0", "doc_gates": "check-doc-links, check-doc-fence-languages, check-doc-json-fences, check-doc-expression-carriage: exit 0 at 3bdffbd", "eslint_targeted": "eslint, run as CI's per-package 'eslint .' runs it (inline config honoured), on the 6 touched .ts/.tsx files from --format json: 0 errors; per-file warning counts equal to main; new test file 0. Population: CI 'pnpm lint' = turbo run lint = each package's 'eslint .'. Invariance: eslint.config.js has no parserOptions.project/projectService (not type-aware), and no repo rule in eslint-rules/ reads other files from disk, so the diff moves no untouched file's verdict.", "check-readme-exports": "NOT MEASURED: exit 1 is PREREQUISITE (no package dist/index.d.ts built here; CI builds all packages first). The README diff adds no fenced block or import, so the gate input is unchanged.", "ci": "in_progress — not awaited, per contract" }, "line_budget": "n/a — no skills/** or governed ledger touched. Diff +704/-43 over 8 files, 478 of the added lines in the new pin file.", "deviations": [ "Zone 3: the gate does NOT read the environment scope. The server verdict has no scope input; REST PUT /meta asks walledCatalogRefusal before ?package=/mode=draft, so Studio's package-door drafts of a catalog item are refused too, and catalog-scope.ts's header already says the scope is 'never an authority'. Consequence: in Studio, for a walled tenant, these components are read-only for the 3 types too (list New, position editor, matrix grants). A pin covers the list and the matrix.", "Added within the claimed files: the editor's History sheet rollback (ResourceEditPage HistoryPanel onRollback) now reads canWrite, because the REST rollback door asks the same capability and walled verdicts. This also withholds rollback from a non-walled caller who cannot save (refused by the door too). Pinned, ablation G.", "File-surface addition: packages/app-shell/README.md (one paragraph in the Setup catalog gates bullet), required by objectui AGENTS.md #2. Outside the claim's list; declared here.", "H5: the walled sentence already existed (engine.catalog.readOnly.walled, objectui#12089, en+zh) and is reused on all three pages. The one new designer-table key is the matrix badge label perm.readOnly.walled (en+zh), not a sentence key.", "H4 is code-read of the doors plus the merged dogfood pins (wall-catalog-refusal.dogfood.test.ts and its controls); no live boot of objectstack by this dev.", "Commit trailers use objectui AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude) over the harness reminder's model-named Co-Authored-By, per the reminder's own precedence clause." ], "files_changed": [ ".changeset/12183-walled-catalog-readonly.md", "packages/app-shell/README.md", "packages/app-shell/src/views/metadata-admin/catalog-scope.ts", "packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx", "packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx", "packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx", "packages/app-shell/src/views/metadata-admin/i18n.ts", "packages/app-shell/src/views/metadata-admin/walledCatalogReadOnly-12183.test.tsx" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
ACCEPT: PR objectui#12185 (head
3bdffbd6e2) ·domain:uiseat 1 ·session_016djJF12Qt14ejKR5Vjt4sK· 2026-10-12T05:02ZReviewed against GitHub and the diff, not the report (
os-dev-report6118051883).Verdict
main. The first line isFixes #12183, andClause-②: nosits at a line start.os-steve.check-governed-merges --pr objectstack-ai/objectui#12185: NOT governed, 747 changed lines.origin/main.walledCatalogWriteVerdict(packages/metadata-core/src/meta-write-capability.ts), admitsisSystemandcallerPosture === PLATFORM_OPERATOR_POSTURE('PLATFORM_ADMIN').isPlatformAdminevidence (packages/core/src/security/posture-ladder.ts), and the session'sisPlatformAdminisgrants.posture === 'PLATFORM_ADMIN'(resolve-authz-context.ts).user.isPlatformAdminfrom the session. That reads the served fact; the server's verdict is not re-implemented.walledCatalogReadOnlyincatalog-scope.tsis a security-catalog type (position,permission,capability) ∧postureHasOrgWall(posture)∧ ¬operator.canAuthor && !walledonResourceListPage(New, the active switch),ResourceEditPage(canWrite: Save, create, Reset/Delete, History rollback) andPermissionMatrixEditor(Save, Clone, the artifact caption).postureHasOrgWall(undefined)isfalse.singleis unchanged.engine.catalog.readOnly.walled(objectui#12089) is reused on all three pages. One new designer-table key, the matrix badge labelperm.readOnly.walled, has en and zh.canWrite(pointer6117177535on objectui#7611). Ablation D covers it./metaitem write doors on both transports (save, reset, publish, rollback)". Studio's package-door drafts of these three types are refused for a walled tenant too, so drawing them writable there would offer a 403. The list and the matrix are pinned.canWrite. Accepted. It is in a claimed file, and the rollback door asks the same capability and walled verdicts. Ablation G covers it.packages/app-shell/README.md: one paragraph. Accepted as forced by objectuiAGENTS.mdAdd automated testing infrastructure and CI/CD workflows #2.Clause-②: no: the predicate and its hook are module-internal. The package entry exports onlyENVIRONMENT_SCOPE_QUERYfromcatalog-scope.js, and no prop or type member moves. Changeset:@object-ui/app-shellpatch.main: 17 failed / 10 passed. Every walled case was offered the write, and every control was green.wall-catalog-refusal.dogfood.test.tsand its controls. There was no live boot. Declared.Out-of-scope findings
ResourceHistoryPageoffers rollback to every caller;PermissionMatrixEditor's badge for a non-walled caller withoutmanage_metadatareads "no runtime write channel", which is false forpermission.tenancyservice, plugin-auth publishes the requested posture while the doors treat the absence as inert. The console then shows read-only where the server would admit: the conservative direction. Acceptance notes.Landing: ready and auto-merge through the merge queue once every check on
3bdffbdissuccessor skipped by design.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Landed: PR objectui#12185 →
maind928c83·domain:uiseat 1 ·session_016djJF12Qt14ejKR5Vjt4sK· 2026-10-12T05:37Z7e30d0b. The card closed ascompletedthroughFixes #12183, and no other card closed with it. objectui#7611 stays open.d928c83to the accepted head3bdffbd6e2, which ACCEPT6118074273covers.mainnow:isolatedorgroup, a caller who is not the platform operator is read-only onposition,permissionandcapability: New, Save, create, Reset/Delete, Clone, the active switch and the editor's History rollback.walledCatalogReadOnlyinmetadata-admin/catalog-scope.ts. It reads the servedfeatures.tenancyPostureand the session'sisPlatformAdmin, the same rung objectstack'swalledCatalogWriteVerdictadmits.singleand an unknown posture are unchanged. Assignment stays writable on both halves.canWrite.6118079494): the standalone history route's rollback, and the matrix badge naming the wrong tier.pm:dispatchedis removed in this act.domain:ui,priority:p1andtarget:v18stay.Generated by Claude Code
fix(app-shell): under a wall the security catalog's pages are read-on…