Repository navigation
fix(rest,runtime)!: the authoring-trail doors stop admitting setup.access, so an organization admin is refused the environment's trail - #23674
Conversation
…cess GET /meta/:type/:name/history, /audit and /diff admit isSystem, studio.access and manage_metadata through their own predicate (mayReadAuthoringTrail); the draft switches keep mayReadPendingDrafts. GET /packages/:id/commits admits isSystem and studio.access through its own gate; GET /packages, /packages/:id and /export keep the read cohort. Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU Co-authored-by: Claude <noreply@anthropic.com>
…087 disposition Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU Co-authored-by: Claude <noreply@anthropic.com>
…ail-doors-setup-access
…trail admission sites Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 3 package(s): 21 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 35 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 74bdc2d446a28efae0209636c4e88da8a54225cc && git checkout 74bdc2d446a28efae0209636c4e88da8a54225cc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8d1fb1fb2ddbd12a0c492bdfe1dca3cbe8190461 6256d1f001dd339abfbef1a54c54dceaf64b7199 && git checkout -B drift-repro 8d1fb1fb2ddbd12a0c492bdfe1dca3cbe8190461 && git merge --no-ff 6256d1f001dd339abfbef1a54c54dceaf64b7199
node scripts/docs-audit/affected-docs.mjs --json 8d1fb1fb2ddbd12a0c492bdfe1dca3cbe8190461
|
席位复核: REWORK, patch round 1 (one item, the release text) · 2026-10-15T12:28Z
What holds (read on GitHub, not from the report):
Item 1: the changeset's BREAKING list misses a cell this PR measured and its body names.
Everything else is unchanged. Generated by Claude Code |
…sed set, setup.access with or without manage_metadata Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU Co-authored-by: Claude <noreply@anthropic.com>
…ail-doors-setup-access
Contract reviewServed-tier: The review is owed by the It rests on:
Nothing was built or run. ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Contract reviewServed-tier: The review is owed by the It rests on:
Nothing was built or run. ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对✅ ACCEPT: PR #23674 at
Checklist, read against GitHub and
Accepted deviations:
Cross-repo, rule 3: objectstack-ai/objectui#12475 (filed, Out-of-scope, Acceptance notes, 「承接者:无」: Landing: Generated by Claude Code |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 38746430608 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Queue ejection: not this PR's, re-queued once · 2026-10-15T14:26Z
Generated by Claude Code |
Fixes #23546
Clause-②: yes (narrowing)
What this does
Ruling
6187280122(letter A), amended by the maintainer's ruling6190477105(Q1 → A1, Q3 → include/diff): four authoring-trail doors stop admittingsetup.access, so an organization admin is refused the environment's authoring trail. The data door on the same three tables already refused that admin (requiredPermissions: ['studio.access'], ADR-0131 D7).packages/rest/src/rest-server.ts.refuseNonAuthoringCallernow asks a new module-private predicate,mayReadAuthoringTrail. It admits a system caller,studio.accessormanage_metadata(AUTHORING_TRAIL_READ_CAPABILITIES). Its three call sites are unchanged (GET /meta/:type/:name/history,/audit,/diff); only their comments moved to name the new predicate.mayReadPendingDraftsand every draft-switch call site are untouched (ruling item 4):GET /meta/_drafts,?state=draft,?preview=draftand the analyticspreviewDraftsstill admitsetup.access.403 FORBIDDEN, nestederror). Its message now names the capabilities that admit the trail.packages/runtime/src/domains/packages.ts.GET /packages/:id/commitsgets its own gate,requireCommitTrailReadCapability: a system caller orstudio.access.GET /packages,/packages/:idand/exportkeeprequireReadCapability(studio.accessorsetup.access).manage_metadataalone is refused at commits, as before: no door gains a capability.packages/metadata-core/src/objects/sys-metadata-{audit,history,commit}.object.ts. Comment-only (ruling item 6). The comment that said these doors admitsetup.accessnow names the doors' real admission. This is the cross-lane comment declared to the engine seats.content/docs/api/client-sdk.mdx. Two code comments saiddiffItemandgetAuditadmitsetup.access. This change made them false, so they are corrected here (see Deviations)..changeset/23546-trail-doors-setup-access.md:@objectstack/restand@objectstack/runtimeminor,fix(rest,runtime)!:, the ADR-0087 markernot-required (no-migration-prescription), one BREAKING line per door, and the objectui companion 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 objectui#12475.Door × caller, before → after
Measured on a real booted stack:
@objectstack/verifybootStack(@objectstack/example-crm),multiTenant: 'posture-only',OS_TENANCY_POSTURE=isolated|group, not degraded. Before = this branch's base286eb9ef32. After =8264557efc, the implementation commit. Each user was signed up through/auth/organization/createandset-active, with grants read byresolveUserAuthzGrants. The table was taken with a scratch file, which was deleted and never committed.Walled (
isolatedandgroupanswered identically):setup.access,setup.write,manage_org_users,manage_mailboxesstudio.accessmanage_metadata(nostudio.access)GET /meta/:type/:name/historyFORBIDDENGET /meta/:type/:name/auditFORBIDDENGET /meta/:type/:name/diffFORBIDDENGET /packages/:id/commitsPERMISSION_DENIEDPERMISSION_DENIEDGET /meta/_drafts,?state=draft,?preview=draft,GET /meta/:type?preview=draftGET /packages,/packages/:id,/packages/:id/exportsingle:studio.accessmanage_metadatasetup.accessonlyFORBIDDENPERMISSION_DENIEDsetup.access-only "before" cells insinglewere not in the scratch table. The ablation below reads them: puttingsetup.accessback turns exactly those four pins red.manage_metadata" column is the shape W3 grants (wall-package-doors-refusal). It held commits throughsetup.access, and the ruling keeps commits atsystemandstudio.access, so it loses commits.Tests
New and changed pins:
packages/qa/dogfood/test/wall-authoring-trail-doors.dogfood.test.ts(new, projectisolated), on the readings' rig. Underisolatedandgroup: the org admin gets 403 at all four doors, and no part of the trail is in the body. CONTROLS: an org owner who differs from that admin bystudio.accessalone reads all four, with the operator's trail size. The draft switches answer the org admin as before (_draftslists the pending draft;?state=draftand?preview=draftserve its grants). The other three package reads still answer 200. Undersingle: amanage_metadataholder reads history, audit and diff (CONTROL). Commits still refusesmanage_metadata(CONTROL: no gain). Asetup.access-only holder is refused all four (the narrowing is not a posture branch).packages/rest/src/meta-history-diff-authoring-door.test.ts: the trail builders are nowstudio.accessandmanage_metadata. Asetup.access-only caller is refused all three doors, in the_draftsrefusal envelope and before any protocol read, and still lists/meta/_drafts. The old "refuse exactly the callers/meta/_draftsrefuses" pin becomes a strict-subset pin, caller by caller.packages/runtime/src/domains/packages-capability-gate.test.ts: commits leaves the shared read-route loop. Its own block: 403PERMISSION_DENIED(andlistCommitsnever runs) for zero-capability,setup.access-only, the org admin's full grant,manage_metadata-only andsetup.access+manage_metadata; 200 forstudio.accessandisSystem; the samesetup.accesscaller still reads the other three.Final local union at HEAD
bf1192ef1f(the last commit), under the verify lock (slotissue-23546,VERDICT command-exit 0):pnpm --filter @objectstack/rest test: 285 files passed; 5848 tests passed, 326 skipped.pnpm --filter @objectstack/runtime test: 388 files passed; 5862 tests passed, 19 skipped.pnpm --filter @objectstack/metadata-core test: 18 files; 728 tests passed.pnpm --filter @objectstack/dogfood exec vitest run --project isolated --maxWorkers=2 test/wall-authoring-trail-doors.dogfood.test.ts: 31 passed (42 s, three boots).typecheckfor rest, runtime, metadata-core and dogfood: exit 0 each. The script names echoed, so none was a zero-match filter.packages/cliis not touched, so its integration layer is not owed. Every family CI runs beyond these is CI's.Ablation
node scripts/ablation-replace.mjs, nested WRAP with restore traps (plus a shelltraprestoring both files fromHEAD), under the verify lock:'setup.access'added back toAUTHORING_TRAIL_READ_CAPABILITIES(rest), and&& !held.has('setup.access')added back to the commits gate (runtime). Each one also plants the marker stringablation-23546. On disk, the tool read anchor 1 → 0, and blobf534523de931→192df9a7b2f2(rest) and93319cef1c91→0787e7b413f8(runtime).turbo run buildfor rest and runtime, thenablation-dist-preflight.mjs(ablation-23546present in bothdist/): exit 0.setup.accessrefusals and the strict-subset pin);setup.accessat 4 doors insingle).manage_metadata. The direction was red, as expected.HEADandgit diff HEADis empty. The restore-leg rebuild ran, andablation-dist-preflight.mjs … --absentexited 0 for both packages.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths), atbf1192ef1f, derived 97 commands.--ranwith the exit codes recorded: "97 derived famil(ies) accounted for — 97 run, 0 NOT-MEASURED (a DERIVED zero — all 97 recorded an exit code and none of them is 3)."00a11d97a3) found one real red.check-system-context-censussaw two newisSystemreads (mayReadAuthoringTrailandrequireCommitTrailReadCapability) with no row oncontent/docs/permissions/system-context.mdx. Row 8b fixes it (bf1192ef1f). The same pass had two exit-3 PREREQUISITE NOT MET (check:skill-examplesandcheck:dual-build-cjs-loads, both unbuiltdist/outside this diff). Both measured exit 0 on the second pass.pnpm lint(eslint . --no-inline-config) atbf1192ef1f, under the lock: exit 0, no findings.check-adr-0087-registration --base origin/mainreads[BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription)✓.check-changeset-no-majorreads "nomajorbump" ✓ (its level axis is NOT APPLICABLE locally, because there is nopull_requestpayload; CI reads it from this PR).check-empty-changeset✓.Acceptance notes
meta-draft-read-door-census.test.ts) counts only draft switches ('draft'comparisons,previewDrafts,listDrafts) and the bare delegation ofmayReadPendingDrafts. This diff moves none of them, so the census stayed green, unedited. The whole-file reader the split did need isscripts/check-system-context-census.mjs, which wants an anchor for everyisSystemread: row 8b.manage_metadatainside its own organization (W3's shape) read commits throughsetup.access. It is refused commits now, and keeps history, audit and diff.OBJECT_SCHEMA_READ_ONLY_EXEMPT_CAPABILITIESinpackages/metadata-core/src/object-schema-fls.tsstill calls itself "the/packagesread cohort", which now holds for three of the four reads. The file is outside the surface, so it is left as is. Carrier: none.measure-test-shard-timings.mjsexcludes@objectstack/dogfood.origin/mainmoved after the last merge. It moved five commits after00a11d97a3. None touches a file in this diff; CI's merge ref covers the joint state.Deviations
content/docs/api/client-sdk.mdx(two code comments) is outside the claim's file surface. This change made both false: they saidsetup.accessadmitsdiffItemandgetAudit. Published text that a change falsifies is corrected in the same PR (the dev role's rule), so they are corrected here.content/docs/permissions/system-context.mdx(row 8b) is the whole-file census reader the claim allows "only as the split requires". It is named here because the claim's example named a different reader.FROM/TOlabel.pnpm lintwas run locally because this order asks for it. The dev role file calls repo-wide scans CI-owned; the two do not contradict each other (owed vs allowed), and the reading is above.Generated by Claude Code