Repository navigation
finding(app-shell): Studio offers Edit on a packaged item of a type the OS_METADATA_WRITABLE hatch elevates, and the server refuses the save (ADR-0131 D6: the hatch never opens a managed item) #12169
Description
Filing gate ①: a user-visible product defect with a named producer. Filed by the domain:ui seat 1 (seat post #5560, session_016djJF12Qt14ejKR5Vjt4sK). The source is the out-of-scope finding in the objectui#12141 dev report (PR #12168), class (b). ⛔ Not a claim.
objectui#12141 says "Whether this gate should read more than the flag is this lane's call". This seat ruled it out of #12141's scope (a pure rename plus the banner). This card is that call.
What is wrong
- Seam:
spec:allowEnvironmentOverlay(the type-registry flag, as spec: renameallowOrgOverrideto an environment-overlay key with an ADR-0087 load-time conversion (ADR-0131 C5's spec half, split from #15206 per #22007 ruling C) objectstack#22340 S2's describe writes it: the elevation "opens the type's writes of items no managed package ships and never a managed item (ADR-0131 D6)") →renderer:ResourceEditPagecanWriteByTypeartifact arm, plus thePermissionMatrixEditorartifact tier and the "writable" badge inDirectoryPage/PageShell. - Those gates read only the flag. When the hatch elevates a type (
allowEnvironmentOverlay: truewithoverrideSource: 'env'), every item of that type looks editable, including an item a package ships. reach:named real producer, measured by a probe render. The dev rendered the realMetadataResourceEditPagefor anagententry elevated by the hatch, with no runtime create and a packaged item: it draws an Edit button (Tests 1 passed (1)). The probe was not committed.- The server's refusal of that save was read from objectstack
main, not measured live:SysMetadataRepository.assertAllowed(the hatch applies only to the runtime-only intent),codeOnlyOverrideErrorandmanagedItemSealedSentence. It is a loud 403, not a silent loss. - The user-visible effect: an admin is offered an edit that fails on save.
Done when
- On a hatch-elevated type, a packaged (managed) item opens read-only in the editor, and the permission matrix's artifact tier agrees. An item no package ships stays editable.
- The "writable" badge does not claim more than that.
- Pins:
- a hatch-elevated type with a packaged item → read-only;
- the same type with a runtime-only item → editable;
- CONTROL: a registry-opt-in type (
overrideSource: 'registry') → as today.
- No new client-side copy of the server's seal rule if a served field already carries it. The claim measures what
/meta/typesand the item read serve first.
Who acts, and in what order
- The
domain:uiexecution seat, after triage grades this card. - Serial: after PR feat(app-shell): metadata-admin reads
/meta/typesallowEnvironmentOverlay; the read-only banners nameOS_METADATA_WRITABLE(objectui#12141) #12168 (objectui#12141) lands, because it renames the field these gates read. spec: renameallowOrgOverrideto an environment-overlay key with an ADR-0087 load-time conversion (ADR-0131 C5's spec half, split from #15206 per #22007 ruling C) objectstack#22340 S2 carries the describe sentence.
Dedupe
Two repo-scoped semantic issue searches on objectui, open and closed:
- "Studio edit button offered for packaged managed item when OS_METADATA_WRITABLE hatch elevates type, save refused 403 managed content seal": 14 hits.
- "artifact tier write gate canWriteByType overrideSource env hatch packaged item read-only": 7 hits.
No open hit except objectui#12141, which carries the rename and not this gate. The nearest closed one is objectui#4518 (the permission matrix not modelling the artifact tier). It predates ADR-0131 D6, which changed what the hatch opens.
Activity
objectstack-fleet commentedon Oct 12, 2026
Pointer: name both halves of the seam ·
domain:uiseat 1 ·session_016djJF12Qt14ejKR5Vjt4sK· 2026-10-12T00:42ZPR objectui#12168's contract review (
6115701744, ③ item 1) asks this card to name both halves of the cross-layer seam. It does not pick between them.overrideSourcebesideallowEnvironmentOverlay. The body already names this half./meta/typesstops reportingallowEnvironmentOverlay: truefor a type whose managed items the hatch never opens. That moves the answer to the server (spec: renameallowOrgOverrideto an environment-overlay key with an ADR-0087 load-time conversion (ADR-0131 C5's spec half, split from #15206 per #22007 ruling C) objectstack#22340's layer).Which half moves is the claim's first measurement. The body's line "No new client-side copy of the server's seal rule if a served field already carries it" still holds. If neither half is served today, the choice goes to the maintainer as a cross-layer question; the seat does not decide it.
Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Triage: first grade,
bug·priority:p3·domain:ui·area:studio, intopm:queue. The editable answer is per item; the gate reads a served per-item answer, and a gap there is an objectstack card, not a maintainer questionTriage seat (seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-12T01:52Z. ⛔ Not a claim, ⛔ not a dispatch. Thread-read:6115735683.ResourceEditPage,PermissionMatrixEditor's artifact tier and the "writable" badge arepackages/app-shell, sodomain:ui.OS_METADATA_WRITABLE) elevates. Nothing is lost or stored wrong, and the hatch is an operator's setting./meta/types(a per-type flag reportingfalse) would wrongly lock the runtime-only items too. ⛔ It is not the fix.Blocked-by:it.overrideSource.Clause-②: noon the client. A server half, if needed, declares its own.objectstack-fleet commentedon Oct 12, 2026
Claim: PM loop round 4 (as triage
6116342111routes it: the claim first measures whether the item read serves the per-item editable answer, and the client gates read it if served. If not served, one objectstack card is filed for it, and this card goesBlocked-by:that card)Session:
session_016djJF12Qt14ejKR5Vjt4sKAccount:
os-steveBranch:
claude/issue-12169-packaged-item-edit-gateWorktree:
objectui-issue-12169Domain:
domain:uiSeat:
domain:ui#1File surface, on objectui
main2498a07:packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx: thecanWriteByTypeartifact arm.PermissionMatrixEditor.tsx: its artifact tier only.DirectoryPage.tsxandPageShell.tsx: the "writable" badge.useMetadata.ts), only to carry a served per-item field.overrideSourceto derive editability.Container & model:
M,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"Clause-②: no
Responsibility:
objectui's metadata-admin write gates: the artifact arm reads the type flag alone, so a hatch-elevated type's packaged item offers Edit | the server seals a managed item whatever the hatch (ADR-0131 D6; SysMetadataRepository, managedItemSealedSentence), and the per-item answer is the server's | every admin on a runtime with OS_METADATA_WRITABLE set, on any packaged item of an elevated type: an Edit the server refuses with 403Thread-read: 6116342111
Serial constraints cleared: read at 2026-10-12T02:46Z on objectui
main2498a07:e531116.console.md, which is disjoint from these files.PermissionMatrixEditor.tsxis on epic objectui#7611's C9 reader list (itssys_capabilityread). This card edits only its artifact-tier gate. A declaration goes on [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) objectstack#15194 in this same act.pm:blocked, with no PR.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{ "issue": 12169, "status": "done", "branch": "claude/issue-12169-packaged-item-edit-gate", "pr": "https://github.com/objectstack-ai/objectui/pull/12181", "session": "session_016djJF12Qt14ejKR5Vjt4sK (mode:subagent; the parent PM session's id, as the container's CLAUDE_CODE_REMOTE_SESSION_ID gives it). Identity: the newest Claim: on the card is 6116840829 and it names branch claude/issue-12169-packaged-item-edit-gate; no second claim was posted.", "premise_still_valid": true, "summary": "H1 = SERVED. The item read GET /meta/:type/:name/layers serves `editable`, the write door's own per-item verdict. On objectstack main 018b077fb, getMetaItemLayered returns editable from servedLockState, which asks packagedBaseRefusal, and its seal predicate isSealedManagedItem reads the registry overlay opt-in alone and never the hatch. objectstack pins this in protocol.read-lock-flags-write-door.test.ts: with OS_METADATA_WRITABLE='flow,action', a packaged item reads lock full, editable false, deletable false. MetadataClient.layered() carries editable through. The premise holds in part. Measured on objectui 2498a07 with the served sealed envelope (hatch-elevated agent): the editor's Save gate already read editable and showed no Edit and no Save. The card's Edit button needs a double that serves editable: true for the packaged item. But the designer auto-on effect copied the type half without the item half and opened the sealed item in design mode, and the matrix artifact tier ignored editable and offered Save, which the server refuses with 403. The change: ResourceEditPage computes itemWritable (type tier plus served editable) once, and both the auto-on effect and the Save gate read it. PermissionMatrixEditor keeps servedEditable from the same layered envelope, its artifact arm is allowEnvironmentOverlay && servedEditable !== false, and lockedByArtifactTier now covers that case, so the sealed set shows the artifact caption and Clone. Neither gate reads overrideSource. Badge (H2): no code change. It describes a type. Its tooltip has said 'opens the items no package ships, never an item a package ships' since e531116. The remaining label copy lives in views/metadata-admin/i18n.ts, objectui#12171's file: reported, not made. A per-item badge would need a new prop on PageShell, which the package entry exports as MetadataPageShell, a published type change: not done.", "tests": "Before any implementation, on base 2498a07, the new pin packagedItemWriteGate-12169.test.tsx gave 'Tests 3 failed | 8 passed (11)': editor sealed case expected 'false' received 'true' on the canvas editing prop; matrix sealed case found a Save button where null was expected; the crossed registry+editable:false case got editing 'true'. Base probe reading for the sealed editor: editing true, designTab false, editButtons 0, save false, lockBanner true. After, at 6b91355, the same unchanged file gave 'Tests 11 passed (11)'. At final HEAD 92b0df6, all through os-verify-lock with VERDICT command-exit read: (1) turbo run build --filter='@object-ui/app-shell^...' --concurrency=2 -> '28 cached, 28 total', exit 0. (2) pnpm --filter @object-ui/app-shell type-check, echoed 'tsc --noEmit && tsc -p tsconfig.test.json'; tsconfig.test.json includes src/**/*.test.tsx; exit 0. (3) Targeted vitest -> 'Test Files 77 passed (77)', 'Tests 479 passed (479)', exit 0. Scope: every ResourceEditPage.*, PermissionMatrixEditor.* and DirectoryPage.* test, both environmentOverlay-12141 pins, the new pin, and the other app-shell tests that render either page (ConditionWidget.conditionSubjects, SchemaForm.instanceIdScope, SchemaForm.topLevelUnion-11251, diagnostics-gate, FlowObjectListField.commit, PackageOwdOverviewPanel, two StudioDesignSurface pins, console/__tests__/AppContent*). PageShell has no test file of its own; its badge is asserted inside the new pin and DirectoryPage.environmentOverlay-12141. Lint, narrowed and declared: eslint --no-inline-config --format json on the 3 touched source and test files. Root eslint.config.js declares no parserOptions.project or projectService, so it is not type-aware and untouched files' verdicts cannot move. JSON count: 3 files, 3 errors, 76 warnings. The same command on the 2498a07 versions of the two source files: the same 3 react-hooks/static-components errors (inline-disabled under the package's own eslint .) and 76 warnings in total. Repo-wide pnpm lint is CI's. Ablation: the before/after pair above is the reverse verification: the implementation was absent in the before run and present at the committed 6b91355. The tests import ./ResourceEditPage and ./PermissionMatrixEditor from source, so there is no dist path. No permanent ablation file was left. Two scratch probe files were removed, and git status was clean before the worktree was removed.", "mcp_calls": "0", "api_writes": "2 relay strokes (scripts/pm/fleet-write/dispatch.mjs, transport auto -> dispatch, landing as objectstack-fleet[bot]). Each stroke is one POST /repos/objectstack-ai/objectstack/dispatches; the board's fleet-write run then executes it against objectui. (1) pr_create: POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos/objectstack-ai/objectui/issues/12181/assignees [os-steve]; read-back: 11163 bytes sent, 11163 stored, identical; assignees os-steve. (2) comment: POST /repos/objectstack-ai/objectui/issues/12169/comments (this os-dev-report). Plus git push of the branch (not REST). Label writes: 0. REST reads only otherwise: card 12169 and its comments, objectstack#22340 and its comments, PR 12181.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: named real producer: the designer auto-on effect in ResourceEditPage (metadata editor). Measured by a probe render on this branch: useCanAuthorMetadata false, a writable runtime-only item of a designer-capable type gives editing true, designTab false, save false, capabilityBanner true. A caller without manage_metadata lands in design mode (the canvas takes edits and reports unsaved changes) with nothing to save it with. Same symptom as this card, on the caller tier, not the item tier. That tier is objectui#7611's, so fold it into that family's close-out rather than a single-point card. Remedy shape: the effect reads canWrite (itemWritable && canAuthor), which means hoisting useCanAuthorMetadata above the effect. Dedupe words: 'designer auto-on design mode canAuthor', 'ResourceEditPage editing without manage_metadata', 'read-only caller canvas editing unsaved'", "carrier: objectui#12171's dev / the domain:ui seat (owner of views/metadata-admin/i18n.ts this round) · noted, not filed: on a hatch-elevated type, the item-page header badge still reads 'writable' above a sealed item's lock banner. The tooltip scopes it. A label for the env source (for example 'writable (runtime items)') is copy in i18n.ts.", "carrier: 承接者:无 · noted, not filed: outside the artifact tier, PermissionMatrixEditor reads no ADR-0010 editable, so a runtime-created set carrying an item _lock would still offer Save. No producer of such a set is known: dormant. In the PR's Acceptance notes." ], "gates": [ "turbo run build --filter='@object-ui/app-shell^...' --concurrency=2 (locked): exit 0, '28 cached, 28 total'", "pnpm --filter @object-ui/app-shell type-check (locked): exit 0", "targeted vitest, 77 files (locked): exit 0, 'Test Files 77 passed (77)' / 'Tests 479 passed (479)' at 92b0df6", "new pin before/after: 2498a07 'Tests 3 failed | 8 passed (11)' -> 6b91355 'Tests 11 passed (11)'", "node scripts/check-changeset-presence.mjs: exit 0, '3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'", "pnpm check:changeset-claims: exit 0, 'No pending changeset names a file this change touches.'", "pnpm check:pending-changeset-literals: exit 0, 'No test source names a pending changeset.'", "pnpm check:control-bytes: exit 0, 'check-control-bytes: OK'", "pnpm check:new-line-citations: exit 0, 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only'", "node scripts/check-vi-mock-override-shape.mjs: exit 0 (added; lint.yml runs it, and the new pin carries vi.mock overrides)", "node scripts/check-test-path-roots.mjs: exit 0 (added; lint.yml)", "node scripts/check-lint-coverage.mjs: exit 0 (added; lint.yml)", "eslint narrowed to the 3 touched files: 3 errors / 76 warnings, equal to base; repo-wide pnpm lint declared to CI", "CI on PR 12181: not waited for (in_progress at report time), per contract" ], "line_budget": "n/a: no skills/** or governed ledger touched. Diff 2498a07..92b0df6: 4 files, +432 / -36 (ResourceEditPage.tsx +49/-26, PermissionMatrixEditor.tsx +37/-10, new pin +332, changeset +14).", "deviations": [ "Citations are by file and symbol, not file:line. The dispatch asked for file:line on both sides; objectui AGENTS.md #11 (objectui#10048) bans line addresses in issue and PR bodies. The trees are pinned by sha instead (objectstack 018b077fb, objectui 2498a07).", "Zone 3's single shared predicate module was not created: the claimed file surface holds no new module. Both pages read the same served field with the same tri-state convention, and the pin asserts that they agree per fixture.", "Opportunistic repair per AGENTS.md #11: one PermissionMatrixEditor comment in the type-gate block, next to the artifact tier, cited three sibling sites by stale cross-file line address. It now cites them by symbol, comment only. The sys_capability read (objectui#7611 C9) is untouched.", "Also ran 8 consumer test files that render the two pages, and three lint.yml gates (vi-mock-override-shape, test-path-roots, lint-coverage), beyond the dispatch's clue list.", "Commit trailers use objectui AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), which takes precedence over the harness reminder's model-named Co-Authored-By line.", "Behaviour beyond the hatch case, declared in the PR and the changeset: an item the server refuses for its own ADR-0010 lock no longer auto-opens in design mode. It is the same served answer and the same gate." ], "files_changed": [ "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/packagedItemWriteGate-12169.test.tsx", ".changeset/12169-packaged-item-edit-gate.md" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
ACCEPT: PR objectui#12181 (head
92b0df61ab) ·domain:uiseat 1 ·session_016djJF12Qt14ejKR5Vjt4sK· 2026-10-12T03:23ZReviewed against GitHub and the diff, not the report (
os-dev-report6117149945).Verdict
main. The first line isFixes #12169, andClause-②: nosits at a line start.os-steve.check-governed-merges --pr objectstack-ai/objectui#12181: NOT governed, 468 changed lines.origin/main.getMetaItemLayeredserveseditablefromservedLockState(packages/metadata-protocol/src/protocol.ts,item-lock.ts). That is the write door's own per-item verdict.read-lock-flags-write-doorpin holds it for a hatch-elevated packaged item:editable: false.6116342111, "If served").ResourceEditPage:itemWritable= the type tier ∧ (createMode∨ servededitable !== false), computed once. The designer auto-on effect andcanWriteboth read it.editable; the effect copied only the type half.PermissionMatrixEditor: the artifact arm isallowEnvironmentOverlay && servedEditable !== false.lockedByArtifactTiernow also covers a sealed item on an elevated type, so it shows the artifact caption and offers "Clone to customize" instead of a Save the server refuses with 403.allowEnvironmentOverlay: false,allowRuntimeCreate: true) is unchanged.editablekeeps today's behaviour (undefined⇒ open).overrideSource. Its only appearances in the diff are comments and the test fixture's case labels. That is triage's ⛔, kept.packagedItemWriteGate-12169.test.tsxbuilds one fixture and three cases on both pages:editable: false) showing the served answer, notoverrideSource, decides.e531116its tooltip says the hatch "never [opens] an item a package ships".MetadataPageShell, which is exported: a published type change, which the claim forbids.Clause-②: no: no export, prop, type member or locale key moves. Changeset:@object-ui/app-shellpatch.Out-of-scope findings
manage_metadata, the same auto-on effect puts a writable runtime-only item into design mode with no Save. The probe render showedediting: true,save: false, and the capability banner. That tier is epic objectui#7611's. A pointer goes on that card in this act.views/metadata-admin/i18n.ts, and a per-item badge would need the published prop above.PermissionMatrixEditorreads no ADR-0010editable. No producer of such a set is known. Acceptance notes.Acceptance notes
AGENTS.md[WIP] Update documentation for project #11.PermissionMatrixEditor's type-gate block had three stale cross-file line citations; it now cites by symbol. That is comment only, and the file'ssys_capabilityread (objectui#7611 C9) is untouched, as declared on [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) objectstack#15194.Landing: ready and auto-merge through the merge queue once every check on
92b0df6issuccessor skipped by design.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Landed: PR objectui#12181 →
main5b20aed·domain:uiseat 1 ·session_016djJF12Qt14ejKR5Vjt4sK· 2026-10-12T03:58Zb0c5f9d(PR objectui#12177, this seat's objectui#12171). The card closed ascompletedthroughFixes #12169, and no other card closed with it.5b20aedto the accepted head92b0df61ab, which ACCEPT6117171952covers. The sibling that merged just before shares no file with this PR.mainnow:OS_METADATA_WRITABLEhatch elevates, a packaged item opens read-only in the metadata editor. It no longer auto-enters design mode.editablefromGET /meta/:type/:name/layers. Neither readsoverrideSource.6117177535). It is a one-line follow-up now thatitemWritableis onmain.pm:dispatchedis removed in this act.domain:ui,area:studioand the grade stay.Generated by Claude Code