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 }}
Repository navigation
app-shell (Setup catalog editors): show History and Audit only to a caller the trail doors admit — objectstack#23546 narrows /meta history, audit and diff away from setup.access #12475
Origin:objectstack-ai/objectstack#23546. The maintainer's ruling there (6190477105, Q1 → A1) narrows the trail doors and asks for this objectui companion in the same landing. Filed by objectstack domain:cli seat 2 (objectstack#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU, per cross-repo rule 3. ⛔ Not a claim. Lane and grade are triage's.
One line: in Setup's catalog item editors, the History and Audit actions are shown only to a caller that GET /meta/:type/:name/history and /audit admit after objectstack#23546: system, studio.access or manage_metadata. An organization admin under a wall, who holds setup.access only, no longer sees two buttons that answer 403.
What objectstack#23546 changes (the server side)
GET /meta/:type/:name/history, /audit and /diff stop admitting setup.access. They keep system, studio.access and manage_metadata.
GET /packages/:id/commits keeps system and studio.access. No objectui organization-admin consumer of it was found.
An organization admin (setup.access, no studio.access) goes from 200 to 403 on these doors, measured on a walled kernel (os-dev-report6189071639 on objectstack#23546).
Where objectui reads these doors (read at objectui maind25585c, not driven in a browser)
Setup's Access Control group (Positions, Capabilities, Permission Sets) opens the metadata-admin editors under setup.access. Under a wall they are read-only (useWalledCatalogReadOnly, packages/app-shell/src/views/metadata-admin/catalog-scope.ts:136; objectui#12183), not hidden.
ResourceEditPage.tsx: the History sheet (HistoryPanel) and the Audit sheet (AuditPanel.tsx:133, client.audit) appear under !createMode, with no capability gate.
PermissionMatrixEditor.tsx: the History button routes to ResourceHistoryPage.tsx:162 (client.history), with no capability gate.
Acceptance (a proposal; the lane's call)
The History and Audit entries render only when the caller holds studio.access or manage_metadata, or is system. Read the caller's capabilities the way the app shell already gates other Studio-only actions; find that idiom rather than adding a new one.
A Setup user with setup.access only sees neither entry, under a wall or not. A studio.access author sees both, as today.
The pin bump follows through the repo's bump script, after objectstack#23546's PR lands, so that the server and the UI agree from one release.
Read fresh. This is the objectui companion that the maintainer's ruling on objectstack-ai/objectstack#23546 (6190477105, Q1 A1, /diff included) asks for.
Setup's catalog editors show History and Audit to a walled organization admin (ResourceEditPage.tsx, PermissionMatrixEditor.tsx, with no capability gate).
Those doors are narrowing to system, studio.access and manage_metadata.
Grade p1 + target:v18: it is the consumer follow-up of a protocol-class narrowing, and without it users meet buttons that always answer 403 (A2's consequence, which the ruling chose A1 to avoid).
Lane domain:ui (packages/app-shell, metadata-admin).
Not blocked, despite the body's Blocked-by: line:
Hiding the buttons from callers the narrowed doors refuse only removes what the ruling removes, so the two PRs may land in either order.
The claimant links the objectstack PR (domain:cli seat 2, under claim 6188504102) and aims to land in the same window, as the ruling asks.
Scope:
History, Audit and the diff entry are shown only to a caller holding system, studio.access or manage_metadata.
Claim: PM loop round 1
Session: session_01XVp19E1MYUTFM3dnkwz8gB
Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12475-trail-doors-gate
Worktree: objectui-issue-12475
Domain: domain:ui
Seat: domain:ui#2
File surface: packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx (the History and Audit entries only), packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx (its History button only), any diff entry that reads /meta/:type/:name/diff from these editors, the existing capability-reading idiom's module only to reuse it (⛔ no new gate idiom), the tests beside each, and one .changeset/12475-*.md (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui reads no path-derived mandate)
Clause-②: no
Responsibility: n/a — not a defect card
Thread-read: 6191531169
Serial constraints cleared: no in-flight claim or open PR touches ResourceEditPage.tsx or PermissionMatrixEditor.tsx. objectui#12440 (PR #12473, ListView.tsx / ObjectView.tsx), objectui#12464 (PR #12485, types / page.tsx / the console sample) and objectui#12478 (claimed alongside, one app-shell test) are disjoint. The pin bump the body mentions is not this card's: triage scoped it to the buttons, and they may land in either order with objectstack-ai/objectstack#23546.
{
"issue": 12475,
"status": "done",
"branch": "claude/issue-12475-trail-doors-gate",
"pr": "https://github.com/objectstack-ai/objectui/pull/12495",
"session": "session_01XVp19E1MYUTFM3dnkwz8gB — subagent, parent's id",
"premise_still_valid": true,
"summary": "Added one predicate, useCanReadAuthoringTrail(), in packages/app-shell/src/hooks/useCanAuthorMetadata.ts beside useCanAuthorMetadata. It uses the same usePermissions().hasCapabilities idiom and answers ANY of studio.access or manage_metadata; an unreported capability set fails open. ResourceEditPage now renders the History and Audit buttons and both sheets only under !createMode && canReadTrail. PermissionMatrixEditor renders its History button (routed and embedded) and the embedded sheet only under canReadTrail. A1 held, with one difference: in the current PermissionMatrixEditor, embedded mode opens HistoryPanel in a sheet, and standalone mode navigates to ./history. No entry in these editors calls /meta/:type/:name/diff. DraftReviewPanel and LayeredDiff compare locally from the layered read. `system` is an isSystem principal flag that /auth/me/permissions does not report, so it has no client half. File-surface note: three existing tests mocked the hook module without spreading the original module (ResourceEditPage.setupCatalog-7611, PermissionMatrixEditor.setupCatalog-7611, PermissionMatrixEditor.cloneToCustomize). Each now spreads importOriginal(); that is a 2-line edit per file. A3: the routed MetadataResourceHistoryPage is unguarded. Reached by URL, a setup-only caller sees HistoryPanel's error state with the server's 403 message, and no toast. Guarding it needs a refusal render, which is not a one-line reuse, so it is left as is and reported.",
"tests": "All at head b5b00b7f0. (1) Pins: `pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/views/metadata-admin/trailDoorsGate-12475.test.tsx` gives Tests 7 passed (7). (2) Ablation via objectstack scripts/ablation-replace.mjs: the predicate body becomes `return true`. The tool reported 'ok mutation landed: anchor 1 to 0, blob a310f6a66200 to cfd334cfc0c0', then Tests 2 failed | 5 passed (7). The failures were 'a setup.access-only caller sees neither entry' and 'a setup.access-only caller sees no History, routed or embedded'. The tool then reported 'ok restored: blob == HEAD (a310f6a66200) and git diff HEAD is empty'. Tests import the source, so no dist rebuild was needed. A first draft pinned the ?tab=audit deep link; that pin stayed green under the ablation, the deep link was found dead on main, and the pin was dropped. (3) Closure build `pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build`: VERDICT command-exit 0. (4) `pnpm --filter @object-ui/app-shell run type-check` (tsc --noEmit plus tsconfig.test.json, which includes *.test.tsx): VERDICT command-exit 0. (5) `pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/views/metadata-admin/ packages/app-shell/src/hooks/`: Test Files 557 passed (557), Tests 6184 passed | 1 skipped, VERDICT command-exit 0, lock held 28m11s on a shared box. (6) Lint narrowed to the 7 changed files with the package's own `eslint .` config (inline config on): exit 0, eslint --format json counts 7 files, 0 errors, 0 messages on changed lines. The config has no projectService or type-checked preset, so the diff cannot move a verdict on an untouched file. Run with --no-inline-config, it shows 3 errors at the existing eslint-disable sites for react-hooks/static-components in ResourceEditPage; those lines are untouched. (7) Pre-tsc battery, all exit 0: check:phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes. check-changeset-fixed exit 0; check-changeset-presence exit 0. check:spec-floors exits 1 with no-artifact; it is known red on main (objectui#12334) and is recorded here, not fixed. CI: in_progress, not awaited.",
"mcp_calls": "0 — no MCP tools used",
"api_writes": "1 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectui/pulls, draft, plus POST /repos/objectstack-ai/objectui/issues/12495/assignees baozhoutao). Read-back: 5865 bytes identical. This report comment is the second write (POST /repos/objectstack-ai/objectui/issues/12475/comments). Labels: zero writes, none named by the dispatch. git pushes are not REST writes: 1 empty branch push and 3 commit pushes.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door is the console URL `?tab=audit`, `layers`, `references` or `related` on a metadata item page, measured with the real MetadataResourceEditPage in a vitest dom env, not a browser: window.location.search was ?tab=audit before mount and the reader effect logged an empty search · evidence: ResourceEditPage declares the effect that syncs openSheet into ?tab= (the comment 'keep the URL `?tab=` query in sync so deep-links round-trip') before the first-mount effect that reads ?tab=. On mount the sync runs first with openSheet null and deletes tab, so the reader sees nothing and the deep link opens no sheet for any caller. The links the page writes for refresh or share therefore do not round-trip · dedupe words: ResourceEditPage tab deep link, openSheet replaceState, ?tab=references, deep-link round-trip",
"carrier: 承接者:无 · routed MetadataResourceHistoryPage is unguarded for a setup-only caller (shows HistoryPanel's error with the server 403 message). With the entry hidden, no editor leads there. Noted in the PR's Acceptance notes; no card filed."
]
}
useCanReadAuthoringTrail() sits beside useCanAuthorMetadata in hooks/useCanAuthorMetadata.ts. It answers ANY of studio.access and manage_metadata through the same usePermissions().hasCapabilities idiom, asking each capability on its own because hasCapabilities answers "holds all".
Unknown fails open, as useCanAuthorMetadata does, and the server enforces regardless.
system is a principal flag that /auth/me/permissions does not report, so it has no client half.
The module is not re-exported from the package entry (exports is . and ./styles.css, and src/index.ts names neither symbol), so Clause-②: no holds.
The gated entries:
ResourceEditPage: the History and Audit buttons AND both sheets render only under !createMode && canReadTrail, so the CONTROL (create mode) is unchanged.
PermissionMatrixEditor: the History button (routed and embedded) and the embedded History sheet render only under canReadTrail.
No entry in these editors calls /meta/:type/:name/diff; DraftReviewPanel and LayeredDiff compare locally from the layered read.
Pins and ablation: 7 pins. A setup.access-only caller sees neither entry, routed or embedded; a studio.access author and a manage_metadata author see both; create mode is unchanged. Making the predicate return true fails the 2 setup-only pins. Restore was proven by blob hash.
Test harness edits: three existing tests mocked the hook module without spreading the original. Each now spreads importOriginal(), a 2-line edit with no assertion changed.
Changeset:@object-ui/app-shell: patch.
Evidence:metadata-admin/ plus hooks/ give 6184 passed and 1 skipped under the lock. The closure build and the app-shell type-check exit 0, and so does the battery.
Acceptance note (not filed): the routed MetadataResourceHistoryPage is unguarded. A setup-only caller who reaches it by URL sees HistoryPanel's error state with the server's 403 message. No editor leads there any more.
Filed from the dev report (class a): objectui#12496. A ?tab=<sheet> deep link on a metadata item page never opens its sheet, because the URL-sync effect deletes tab before the first-mount reader runs.
Activity
objectstack-fleet commentedon Oct 15, 2026
Triage: first grade ·
enhancement·priority:p1·target:v18·domain:ui·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-15T11:59ZRead fresh. This is the objectui companion that the maintainer's ruling on objectstack-ai/objectstack#23546 (
6190477105, Q1 A1,/diffincluded) asks for.ResourceEditPage.tsx,PermissionMatrixEditor.tsx, with no capability gate).system,studio.accessandmanage_metadata.Grade p1 +
target:v18: it is the consumer follow-up of a protocol-class narrowing, and without it users meet buttons that always answer 403 (A2's consequence, which the ruling chose A1 to avoid).Lane
domain:ui(packages/app-shell, metadata-admin).Not blocked, despite the body's
Blocked-by:line:domain:cliseat 2, under claim6188504102) and aims to land in the same window, as the ruling asks.Scope:
system,studio.accessormanage_metadata.setup.access-only caller sees neither button;studio.accessauthor sees both;objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1
Session:
session_01XVp19E1MYUTFM3dnkwz8gBAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12475-trail-doors-gateWorktree:
objectui-issue-12475Domain:
domain:uiSeat:
domain:ui#2File surface:
packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx(the History and Audit entries only),packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx(its History button only), any diff entry that reads/meta/:type/:name/difffrom these editors, the existing capability-reading idiom's module only to reuse it (⛔ no new gate idiom), the tests beside each, and one.changeset/12475-*.md(stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuireads no path-derived mandate)Clause-②: no
Responsibility:
n/a — not a defect cardThread-read: 6191531169
Serial constraints cleared: no in-flight claim or open PR touches
ResourceEditPage.tsxorPermissionMatrixEditor.tsx. objectui#12440 (PR #12473,ListView.tsx/ObjectView.tsx), objectui#12464 (PR #12485,types/page.tsx/ the console sample) and objectui#12478 (claimed alongside, one app-shell test) are disjoint. The pin bump the body mentions is not this card's: triage scoped it to the buttons, and they may land in either order with objectstack-ai/objectstack#23546.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{ "issue": 12475, "status": "done", "branch": "claude/issue-12475-trail-doors-gate", "pr": "https://github.com/objectstack-ai/objectui/pull/12495", "session": "session_01XVp19E1MYUTFM3dnkwz8gB — subagent, parent's id", "premise_still_valid": true, "summary": "Added one predicate, useCanReadAuthoringTrail(), in packages/app-shell/src/hooks/useCanAuthorMetadata.ts beside useCanAuthorMetadata. It uses the same usePermissions().hasCapabilities idiom and answers ANY of studio.access or manage_metadata; an unreported capability set fails open. ResourceEditPage now renders the History and Audit buttons and both sheets only under !createMode && canReadTrail. PermissionMatrixEditor renders its History button (routed and embedded) and the embedded sheet only under canReadTrail. A1 held, with one difference: in the current PermissionMatrixEditor, embedded mode opens HistoryPanel in a sheet, and standalone mode navigates to ./history. No entry in these editors calls /meta/:type/:name/diff. DraftReviewPanel and LayeredDiff compare locally from the layered read. `system` is an isSystem principal flag that /auth/me/permissions does not report, so it has no client half. File-surface note: three existing tests mocked the hook module without spreading the original module (ResourceEditPage.setupCatalog-7611, PermissionMatrixEditor.setupCatalog-7611, PermissionMatrixEditor.cloneToCustomize). Each now spreads importOriginal(); that is a 2-line edit per file. A3: the routed MetadataResourceHistoryPage is unguarded. Reached by URL, a setup-only caller sees HistoryPanel's error state with the server's 403 message, and no toast. Guarding it needs a refusal render, which is not a one-line reuse, so it is left as is and reported.", "tests": "All at head b5b00b7f0. (1) Pins: `pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/views/metadata-admin/trailDoorsGate-12475.test.tsx` gives Tests 7 passed (7). (2) Ablation via objectstack scripts/ablation-replace.mjs: the predicate body becomes `return true`. The tool reported 'ok mutation landed: anchor 1 to 0, blob a310f6a66200 to cfd334cfc0c0', then Tests 2 failed | 5 passed (7). The failures were 'a setup.access-only caller sees neither entry' and 'a setup.access-only caller sees no History, routed or embedded'. The tool then reported 'ok restored: blob == HEAD (a310f6a66200) and git diff HEAD is empty'. Tests import the source, so no dist rebuild was needed. A first draft pinned the ?tab=audit deep link; that pin stayed green under the ablation, the deep link was found dead on main, and the pin was dropped. (3) Closure build `pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build`: VERDICT command-exit 0. (4) `pnpm --filter @object-ui/app-shell run type-check` (tsc --noEmit plus tsconfig.test.json, which includes *.test.tsx): VERDICT command-exit 0. (5) `pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/views/metadata-admin/ packages/app-shell/src/hooks/`: Test Files 557 passed (557), Tests 6184 passed | 1 skipped, VERDICT command-exit 0, lock held 28m11s on a shared box. (6) Lint narrowed to the 7 changed files with the package's own `eslint .` config (inline config on): exit 0, eslint --format json counts 7 files, 0 errors, 0 messages on changed lines. The config has no projectService or type-checked preset, so the diff cannot move a verdict on an untouched file. Run with --no-inline-config, it shows 3 errors at the existing eslint-disable sites for react-hooks/static-components in ResourceEditPage; those lines are untouched. (7) Pre-tsc battery, all exit 0: check:phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes. check-changeset-fixed exit 0; check-changeset-presence exit 0. check:spec-floors exits 1 with no-artifact; it is known red on main (objectui#12334) and is recorded here, not fixed. CI: in_progress, not awaited.", "mcp_calls": "0 — no MCP tools used", "api_writes": "1 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectui/pulls, draft, plus POST /repos/objectstack-ai/objectui/issues/12495/assignees baozhoutao). Read-back: 5865 bytes identical. This report comment is the second write (POST /repos/objectstack-ai/objectui/issues/12475/comments). Labels: zero writes, none named by the dispatch. git pushes are not REST writes: 1 empty branch push and 3 commit pushes.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: public door is the console URL `?tab=audit`, `layers`, `references` or `related` on a metadata item page, measured with the real MetadataResourceEditPage in a vitest dom env, not a browser: window.location.search was ?tab=audit before mount and the reader effect logged an empty search · evidence: ResourceEditPage declares the effect that syncs openSheet into ?tab= (the comment 'keep the URL `?tab=` query in sync so deep-links round-trip') before the first-mount effect that reads ?tab=. On mount the sync runs first with openSheet null and deletes tab, so the reader sees nothing and the deep link opens no sheet for any caller. The links the page writes for refresh or share therefore do not round-trip · dedupe words: ResourceEditPage tab deep link, openSheet replaceState, ?tab=references, deep-link round-trip", "carrier: 承接者:无 · routed MetadataResourceHistoryPage is unguarded for a setup-only caller (shows HistoryPanel's error with the server 403 message). With the entry hidden, no editor leads there. Noted in the PR's Acceptance notes; no card filed." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Review: ACCEPT — objectui PR #12495 ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-15T13:47ZReviewed on GitHub at head
b5b00b7, against triage's scope (6191531169) and the claim (6191990090), not against the dev's report.main. Its body opensFixes #12475, the only closing keyword, followed byClause-②: no, and it names rest/runtime: under a wall, the /meta history, audit and package-commits doors admit setup.access, which organization admins hold, so an organization admin reads the environment-wide authoring trail of any item objectstack#23546 as the server half. The governed sweep reads NOT governed, 8 files.useCanReadAuthoringTrail()sits besideuseCanAuthorMetadatainhooks/useCanAuthorMetadata.ts. It answers ANY ofstudio.accessandmanage_metadatathrough the sameusePermissions().hasCapabilitiesidiom, asking each capability on its own becausehasCapabilitiesanswers "holds all".useCanAuthorMetadatadoes, and the server enforces regardless.systemis a principal flag that/auth/me/permissionsdoes not report, so it has no client half.exportsis.and./styles.css, andsrc/index.tsnames neither symbol), soClause-②: noholds.ResourceEditPage: the History and Audit buttons AND both sheets render only under!createMode && canReadTrail, so the CONTROL (create mode) is unchanged.PermissionMatrixEditor: the History button (routed and embedded) and the embedded History sheet render only undercanReadTrail./meta/:type/:name/diff;DraftReviewPanelandLayeredDiffcompare locally from the layered read.setup.access-only caller sees neither entry, routed or embedded; astudio.accessauthor and amanage_metadataauthor see both; create mode is unchanged. Making the predicatereturn truefails the 2 setup-only pins. Restore was proven by blob hash.importOriginal(), a 2-line edit with no assertion changed.@object-ui/app-shell: patch.metadata-admin/plushooks/give 6184 passed and 1 skipped under the lock. The closure build and the app-shell type-check exit 0, and so does the battery.MetadataResourceHistoryPageis unguarded. A setup-only caller who reaches it by URL seesHistoryPanel's error state with the server's 403 message. No editor leads there any more.?tab=<sheet>deep link on a metadata item page never opens its sheet, because the URL-sync effect deletestabbefore the first-mount reader runs.filter=latest, 37 runs), all 16 test shards included.Next:
pr_ready+automerge_enable.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-15T13:59Zmain720f6d4. The card closedcompletedby the PR'sFixes #12475, which is the only closing keyword. objectui#12462, which closed in the same minute, was closed by seat 1's PR feat(app-shell): the list header leaves a bulk-wired, record-scoped list_toolbar action to the selection bar (objectui#12462) #12494, not by this one.origin/main, all 8 files the PR touches are blob-identical to the accepted headb5b00b7. They areuseCanAuthorMetadata.ts,ResourceEditPage.tsx,PermissionMatrixEditor.tsx, the app-shell (Setup catalog editors): show History and Audit only to a caller the trail doors admit — objectstack#23546 narrows /meta history, audit and diff away from setup.access #12475 pins, the three adapted harnesses and.changeset/12475-trail-doors-gate.md.pm:dispatchedis removed in the same stroke as this comment.Generated by Claude Code