Skip to content

fix(rest,runtime)!: the authoring-trail doors stop admitting setup.access, so an organization admin is refused the environment's trail - #23674

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-23546-trail-doors-setup-access
Oct 15, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-23546-trail-doors-setup-access

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23546
Clause-②: yes (narrowing)

What this does

Ruling 6187280122 (letter A), amended by the maintainer's ruling 6190477105 (Q1 → A1, Q3 → include /diff): four authoring-trail doors stop admitting setup.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. refuseNonAuthoringCaller now asks a new module-private predicate, mayReadAuthoringTrail. It admits a system caller, studio.access or manage_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.
    • mayReadPendingDrafts and every draft-switch call site are untouched (ruling item 4): GET /meta/_drafts, ?state=draft, ?preview=draft and the analytics previewDrafts still admit setup.access.
    • The refusal keeps its status, code and envelope (403 FORBIDDEN, nested error). Its message now names the capabilities that admit the trail.
  • packages/runtime/src/domains/packages.ts. GET /packages/:id/commits gets its own gate, requireCommitTrailReadCapability: a system caller or studio.access. GET /packages, /packages/:id and /export keep requireReadCapability (studio.access or setup.access). manage_metadata alone 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 admit setup.access now 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 said diffItem and getAudit admit setup.access. This change made them false, so they are corrected here (see Deviations).
  • Changeset .changeset/23546-trail-doors-setup-access.md: @objectstack/rest and @objectstack/runtime minor, fix(rest,runtime)!:, the ADR-0087 marker not-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/verify bootStack(@objectstack/example-crm), multiTenant: 'posture-only', OS_TENANCY_POSTURE=isolated|group, not degraded. Before = this branch's base 286eb9ef32. After = 8264557efc, the implementation commit. Each user was signed up through /auth/organization/create and set-active, with grants read by resolveUserAuthzGrants. The table was taken with a scratch file, which was deleted and never committed.

Walled (isolated and group answered identically):

Door Org admin: setup.access, setup.write, manage_org_users, manage_mailboxes Org owner + studio.access Org owner + manage_metadata (no studio.access) Platform operator
GET /meta/:type/:name/history 200 (5 events) → 403 FORBIDDEN 200 → 200 200 → 200 200 → 200
GET /meta/:type/:name/audit 200 (5 rows) → 403 FORBIDDEN 200 → 200 200 → 200 200 → 200
GET /meta/:type/:name/diff 200 → 403 FORBIDDEN 200 → 200 200 → 200 200 → 200
GET /packages/:id/commits 200 (2 commits) → 403 PERMISSION_DENIED 200 → 200 200 → 403 PERMISSION_DENIED 200 → 200
GET /meta/_drafts, ?state=draft, ?preview=draft, GET /meta/:type?preview=draft 200 → 200, same body 200 → 200 200 → 200 200 → 200
GET /packages, /packages/:id, /packages/:id/export 200 → 200 200 → 200 200 → 200 200 → 200

single:

Door Member, no capability studio.access manage_metadata setup.access only Platform operator
history, audit, diff 403 → 403 200 → 200 200 → 200 admitted → 403 FORBIDDEN 200 → 200
commits 403 → 403 200 → 200 403 → 403 admitted → 403 PERMISSION_DENIED 200 → 200
  • The setup.access-only "before" cells in single were not in the scratch table. The ablation below reads them: putting setup.access back turns exactly those four pins red.
  • The walled "org owner + manage_metadata" column is the shape W3 grants (wall-package-doors-refusal). It held commits through setup.access, and the ruling keeps commits at system and studio.access, so it loses commits.

Tests

New and changed pins:

  • packages/qa/dogfood/test/wall-authoring-trail-doors.dogfood.test.ts (new, project isolated), on the readings' rig. Under isolated and group: 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 by studio.access alone reads all four, with the operator's trail size. The draft switches answer the org admin as before (_drafts lists the pending draft; ?state=draft and ?preview=draft serve its grants). The other three package reads still answer 200. Under single: a manage_metadata holder reads history, audit and diff (CONTROL). Commits still refuses manage_metadata (CONTROL: no gain). A setup.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 now studio.access and manage_metadata. A setup.access-only caller is refused all three doors, in the _drafts refusal envelope and before any protocol read, and still lists /meta/_drafts. The old "refuse exactly the callers /meta/_drafts refuses" 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: 403 PERMISSION_DENIED (and listCommits never runs) for zero-capability, setup.access-only, the org admin's full grant, manage_metadata-only and setup.access + manage_metadata; 200 for studio.access and isSystem; the same setup.access caller still reads the other three.

Final local union at HEAD bf1192ef1f (the last commit), under the verify lock (slot issue-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).
  • typecheck for rest, runtime, metadata-core and dogfood: exit 0 each. The script names echoed, so none was a zero-match filter.
  • packages/cli is 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 shell trap restoring both files from HEAD), under the verify lock:

  • Mutations: 'setup.access' added back to AUTHORING_TRAIL_READ_CAPABILITIES (rest), and && !held.has('setup.access') added back to the commits gate (runtime). Each one also plants the marker string ablation-23546. On disk, the tool read anchor 1 → 0, and blob f534523de931 → 192df9a7b2f2 (rest) and 93319cef1c91 → 0787e7b413f8 (runtime).
  • turbo run build for rest and runtime, then ablation-dist-preflight.mjs (ablation-23546 present in both dist/): exit 0.
  • Red, exactly on the narrowing pins:
    • rest: 4 failed / 15 passed (the three setup.access refusals and the strict-subset pin);
    • runtime: 4 failed / 75 passed (setup-only, the org admin's full grant, setup + manage_metadata, and the cohort split);
    • dogfood: 12 failed / 19 passed (the org admin at 4 doors × 2 walls, and setup.access at 4 doors in single).
    • Every control stayed green: the studio and metadata authors, the draft switches, the other package reads, and commits refusing manage_metadata. The direction was red, as expected.
  • Restore: both blobs equal HEAD and git diff HEAD is empty. The restore-leg rebuild ran, and ablation-dist-preflight.mjs … --absent exited 0 for both packages.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths), at bf1192ef1f, derived 97 commands.

  • All 97 ran locally. Each exit code was captured before any pipe, and all were 0.
  • --ran with 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)."
  • The first pass (at 00a11d97a3) found one real red. check-system-context-census saw two new isSystem reads (mayReadAuthoringTrail and requireCommitTrailReadCapability) with no row on content/docs/permissions/system-context.mdx. Row 8b fixes it (bf1192ef1f). The same pass had two exit-3 PREREQUISITE NOT MET (check:skill-examples and check:dual-build-cjs-loads, both unbuilt dist/ outside this diff). Both measured exit 0 on the second pass.
  • pnpm lint (eslint . --no-inline-config) at bf1192ef1f, under the lock: exit 0, no findings.
  • The changeset gates: check-adr-0087-registration --base origin/main reads [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription) ✓. check-changeset-no-major reads "no major bump" ✓ (its level axis is NOT APPLICABLE locally, because there is no pull_request payload; CI reads it from this PR). check-empty-changeset ✓.
  • CI's check-runs on this PR are the record for every family outside the local list.

Acceptance notes

  • Mechanism hypothesis (a), falsified as stated. The meta-draft-read-door census (meta-draft-read-door-census.test.ts) counts only draft switches ('draft' comparisons, previewDrafts, listDrafts) and the bare delegation of mayReadPendingDrafts. This diff moves none of them, so the census stayed green, unedited. The whole-file reader the split did need is scripts/check-system-context-census.mjs, which wants an anchor for every isSystem read: row 8b.
  • Hypothesis (b), held. objectui is not edited. The companion is 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, named in the changeset.
  • A second shape loses commits, by the ruling. A walled organization owner granted manage_metadata inside its own organization (W3's shape) read commits through setup.access. It is refused commits now, and keeps history, audit and diff.
  • A docblock outside the file surface. OBJECT_SCHEMA_READ_ONLY_EXEMPT_CAPABILITIES in packages/metadata-core/src/object-schema-fls.ts still calls itself "the /packages read cohort", which now holds for three of the four reads. The file is outside the surface, so it is left as is. Carrier: none.
  • The dogfood file's wall time. About 42 s, for three boots (the W3 file boots three too). The 10 s per-file line is measured on Test Core shards; measure-test-shard-timings.mjs excludes @objectstack/dogfood.
  • origin/main moved after the last merge. It moved five commits after 00a11d97a3. 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 said setup.access admits diffItem and getAudit. 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.
  • The changeset's per-door BREAKING lines are spelled "was 200 … now 403". They carry the ordered FROM-200 / TO-403 content in the behaviour-comparison words, not a FROM/TO label.
  • pnpm lint was 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

…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>
…trail admission sites

Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 15, 2026
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-core, @objectstack/rest, @objectstack/runtime, touching 29 documentable anchor(s).

21 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 8d1fb1fb2ddbd12a0c492bdfe1dca3cbe8190461.

⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 35 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8d1fb1fb2ddbd12a0c492bdfe1dca3cbe8190461 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 74bdc2d446a28efae0209636c4e88da8a54225cc — the merge of head 6256d1f001dd339abfbef1a54c54dceaf64b7199 into base 8d1fb1fb2ddbd12a0c492bdfe1dca3cbe8190461, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8d1fb1fb2ddbd12a0c492bdfe1dca3cbe8190461 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位复核: REWORK, patch round 1 (one item, the release text) · 2026-10-15T12:28Z

domain:cli seat 2 (#22648) · sunhaolin · session_01Q9PZ5Pzqcus4woVvTZwWNU · reviewed head bf1192ef1f, against origin/main 5a33154900. Same claim (6190506257), same dev, by SendMessage.

What holds (read on GitHub, not from the report):

  • mayReadAuthoringTrail (isSystem, studio.access, manage_metadata) is asked only by refuseNonAuthoringCaller, at the three call sites. mayReadPendingDrafts and the draft switches are untouched.
  • requireCommitTrailReadCapability (isSystem, studio.access) gates GET /packages/:id/commits alone. GET /packages, /packages/:id and /export keep requireReadCapability.
  • No door gains a capability. The refusals keep their envelope and code; only the messages change.
  • The three sys-metadata-* comments, the client-sdk.mdx comments and census row 8b are accurate to the new code.
  • Narrowing in every posture is what letter A ruled (C, wall-only, was the rejected option in 6187280122).
  • check-governed-merges --pr 23674: NOT governed. git merge-tree --write-tree origin/main is clean.
  • The ADR-0087 marker reads not-required (no-migration-prescription).

Item 1: the changeset's BREAKING list misses a cell this PR measured and its body names.

  • The cell: the commits door also refuses a caller holding setup.access and manage_metadata without studio.access: W3's walled organization owner, granted manage_metadata inside its organization.
    • The report's table: "commits: … org owner + manage_metadata (no studio.access) 200 -> 403 PERMISSION_DENIED".
    • The PR body: "A second shape loses commits, by the ruling."
  • Why the shipped text is wrong:
    • The changeset's lead line scopes every door to "a caller holding setup.access and neither studio.access nor manage_metadata".
    • The commits bullet only adds that "manage_metadata alone was refused here before and still is".
    • "Who is still admitted" ends with "platform operators and metadata authors are not affected".
    • A reader who holds setup.access + manage_metadata therefore concludes that commits still answer them, and the release text is the only part that ships.
  • Fix, changeset only (.changeset/23546-trail-doors-setup-access.md):
    • The commits bullet names its own broken set: setup.access without studio.access, with or without manage_metadata, which never admitted this door. Name W3's shape (a walled organization owner granted manage_metadata): it keeps history, audit and diff, and loses commits.
    • The "not affected" sentence says which metadata authors: those holding studio.access, or manage_metadata at history, audit and diff.
    • Keep the PR body's table and Acceptance notes in step if a line there quotes the changeset.
    • No code, test or docs change. Re-run check-adr-0087-registration and check-changeset-no-major on the new head, and report their verdicts.
  • CI: at this stamp 13 checks were still running on bf1192ef1f. Before you push, read the head's check-runs. If any finished red, fold its fix into this round and say so; otherwise the push re-runs them.

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>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 82dae4a76e02a672183434d25c98cb263e8683b5
Local-runs: none

The review is owed by the Clause-②: yes (narrowing) line alone (the declaration face). There is no packages/spec/src/** path and no governed path (check-governed-merges --pr 23674: NOT governed, 0 of 11 paths hit the register), so this seat writes it itself.

It rests on:

Nothing was built or run.

① Derived judgments

  • Accept set: four read doors narrow; no door widens. Right, and exactly the ruling.
    • History, audit and diff (RestServer): refuseNonAuthoringCaller now asks mayReadAuthoringTrail, which admits isSystem, studio.access or manage_metadata, read from systemPermissions as isObjectSchemaMaskExempt reads it.
      • setup.access alone goes 200 → 403 FORBIDDEN.
      • The refusal keeps its envelope and code; only the message changes.
    • GET /packages/:id/commits (runtime): its own requireCommitTrailReadCapability admits isSystem or studio.access.
      • setup.access without studio.access, with or without manage_metadata, goes 200 → 403 PERMISSION_DENIED.
      • manage_metadata alone was refused before and still is, so no door gains a capability.
    • Unchanged:
      • mayReadPendingDrafts and every draft switch;
      • GET /packages, /packages/:id and /export, which keep requireReadCapability.
  • Posture: the narrowing applies in every posture, single included. That is letter A of ruling 6187280122; option C (wall-only) was presented and not chosen.
  • The subset relation is pinned: the trail admits a strict subset of the callers GET /meta/_drafts admits (meta-history-diff-authoring-door.test.ts).
  • Pins:
    • rest: a setup.access-only caller is refused all three doors before any read (the protocol spies are never called), and still lists _drafts. The two-predicate relation is checked caller by caller.
    • runtime: commits refuses five shapes, including setup.access + manage_metadata, and listCommits never runs. It admits studio.access and isSystem, and the same setup.access caller still reads the other three package doors.
    • dogfood (wall-authoring-trail-doors.dogfood.test.ts, the isolated project): a real bootStack(example-crm) under isolated and group, and single.
      • The organization admin is refused all four doors.
      • The controls: the studio.access owner and the operator read the trail, and the draft switches and the other package reads answer the admin as before.
      • The PR's CI skips the dogfood matrix (a roster skip). This pin's evidence is the dev's local run, 31 passed in three boots.
    • Ablation: setup.access put back on both gates turned rest 4, runtime 4 and dogfood 12 red, with every control green. The restore was proven against HEAD, with the dist preflight --absent.
  • Comments and docs:
    • The three sys-metadata-* object comments now say the trail doors admit studio.access (beside manage_metadata at history, audit and diff), and none admits setup.access.
    • The client-sdk.mdx comments on diffItem and getAudit match.
    • Census row 8b names both new isSystem reads, which check-system-context-census requires.

② Semver level

  • @objectstack/rest minor and @objectstack/runtime minor, fix(rest,runtime)!: with a BREAKING line per door: an accept-set narrowing shipped as minor under the launch-window convention. Right.
  • ADR-0087: not-required (no-migration-prescription). Right: no spec key, response field, error code, stored shape or export moves, and the new predicate and gate are module-private.
    • The dev re-ran check-adr-0087-registration and check-changeset-no-major (with the PR's own event payload, level axis included) on this head: both exit 0.
  • Patch round 1 (bf1192ef1f..82dae4a76e, the changeset only) makes the BREAKING list complete, closing the item 6192050253 raised. Read sentence by sentence:

③ Boundary flags

Implemented-by: claude/issue-23546-trail-doors-setup-access
Reviewed-by: session_01Q9PZ5Pzqcus4woVvTZwWNU

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 6256d1f001dd339abfbef1a54c54dceaf64b7199
Local-runs: none

The review is owed by the Clause-②: yes (narrowing) line alone (the declaration face). There is no packages/spec/src/** path and no governed path (check-governed-merges --pr 23674: NOT governed, 0 of 11 paths hit the register), so this seat writes it itself.

It rests on:

Nothing was built or run.

① Derived judgments

  • Accept set: four read doors narrow; no door widens. Right, and exactly the ruling.
    • History, audit and diff (RestServer): refuseNonAuthoringCaller now asks mayReadAuthoringTrail, which admits isSystem, studio.access or manage_metadata, read from systemPermissions as isObjectSchemaMaskExempt reads it.
      • setup.access alone goes 200 → 403 FORBIDDEN.
      • The refusal keeps its envelope and code; only the message changes.
    • GET /packages/:id/commits (runtime): its own requireCommitTrailReadCapability admits isSystem or studio.access.
      • setup.access without studio.access, with or without manage_metadata, goes 200 → 403 PERMISSION_DENIED.
      • manage_metadata alone was refused before and still is, so no door gains a capability.
    • Unchanged:
      • mayReadPendingDrafts and every draft switch;
      • GET /packages, /packages/:id and /export, which keep requireReadCapability.
  • Posture: the narrowing applies in every posture, single included. That is letter A of ruling 6187280122; option C (wall-only) was presented and not chosen.
  • The subset relation is pinned: the trail admits a strict subset of the callers GET /meta/_drafts admits (meta-history-diff-authoring-door.test.ts).
  • Pins:
    • rest: a setup.access-only caller is refused all three doors before any read (the protocol spies are never called), and still lists _drafts. The two-predicate relation is checked caller by caller.
    • runtime: commits refuses five shapes, including setup.access + manage_metadata, and listCommits never runs. It admits studio.access and isSystem, and the same setup.access caller still reads the other three package doors.
    • dogfood (wall-authoring-trail-doors.dogfood.test.ts, the isolated project): a real bootStack(example-crm) under isolated and group, and single.
      • The organization admin is refused all four doors.
      • The controls: the studio.access owner and the operator read the trail, and the draft switches and the other package reads answer the admin as before.
      • The PR's CI skips the dogfood matrix (a roster skip). This pin's evidence is the dev's local run, 31 passed in three boots.
    • Ablation: setup.access put back on both gates turned rest 4, runtime 4 and dogfood 12 red, with every control green. The restore was proven against HEAD, with the dist preflight --absent.
  • Comments and docs:
    • The three sys-metadata-* object comments now say the trail doors admit studio.access (beside manage_metadata at history, audit and diff), and none admits setup.access.
    • The client-sdk.mdx comments on diffItem and getAudit match.
    • Census row 8b names both new isSystem reads, which check-system-context-census requires.

② Semver level

  • @objectstack/rest minor and @objectstack/runtime minor, fix(rest,runtime)!: with a BREAKING line per door: an accept-set narrowing shipped as minor under the launch-window convention. Right.
  • ADR-0087: not-required (no-migration-prescription). Right: no spec key, response field, error code, stored shape or export moves, and the new predicate and gate are module-private.
    • The dev re-ran check-adr-0087-registration and check-changeset-no-major (with the PR's own event payload, level axis included) on this head: both exit 0.
  • Patch round 1 (bf1192ef1f..82dae4a76e, the changeset only) makes the BREAKING list complete, closing the item 6192050253 raised. Read sentence by sentence:

③ Boundary flags

Implemented-by: claude/issue-23546-trail-doors-setup-access
Reviewed-by: session_01Q9PZ5Pzqcus4woVvTZwWNU

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

✅ ACCEPT: PR #23674 at 6256d1f001 (#23546), after patch rounds 1 and 2. Every check on this head is green or a roster skip. The TIER_DEFAULT record is 6193599695 (PASS) · 2026-10-15T13:46Z

domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU · holder of the amended claim 6190506257. Rulings: 6187280122 (letter A), amended by 6190477105 (the maintainer's Q1 A1 and Q3 /diff). Reports: round 0, patch round 1 (REWORK 6192050253) and patch round 2 on #23546.

Checklist, read against GitHub and origin/main, not against the reports:

  • PR shape: draft, base main, title fix(rest,runtime)!:, assignee sunhaolin. Line 1 is Fixes #23546, the body's only closing keyword. Line 2 is Clause-②: yes (narrowing).
  • Scope: 11 files, +582 / −53.
    • Inside the claim's surface: the two gates, the three sys-metadata-* comments, census row 8b (the reader the split moved), three pin files and one changeset.
    • Outside it: client-sdk.mdx (accepted below).
    • ⛔ Not touched: registerApprovalsEndpoints / approvals-routes.ts, mayReadPendingDrafts, the draft switches, plugin-security and objectui.
    • check-governed-merges --pr 23674: NOT governed. git merge-tree is clean against origin/main 09dd537193.
  • The fix, the pins and the semver level are judged in the record 6193599695.
  • Patch round 1 (bf1192ef1f..82dae4a76e, the changeset only): the commits door's BREAKING line now names its own refused set, setup.access without studio.access with or without manage_metadata, and W3's walled owner. The "not affected" sentence says which authors.
  • Patch round 2 (merge commit 6256d1f001): main 8d1fb1fb2d was merged after seat 1's [maintainer] refactor(rest): split packages/rest/src/rest-server.ts (13,701 lines, 70 commits in 30 days) along its register*Endpoints seams, one move-only slice per PR, /meta after #22877 stage 2 #23050 slice 3 landed, per declaration 6190539679.
    • The merge is clean with nothing forced, and this PR's changed lines are identical before and after it.
    • The Test Core (1/6) red on 82dae4a76e (driver-memory) did not reproduce: the dev ran 71 files / 1732 tests on the merged tree, and the shard is green on this head. It is a reading, not a finding.
  • Gates:
    • Local, at 6256d1f001: 97 derived, 97 run, 0 unmeasured, all exit 0. pnpm lint exit 0 (round 0). check-adr-0087-registration and check-changeset-no-major (with the PR's event payload) exit 0 on the changeset head.
    • Gate evidence: the check-runs pinned to 6256d1f001, read at this stamp. 41 distinct checks: 36 success, including Lint · ESLint, the Type Check family, all six Test Core shards, Build Core, Check Changeset and Governed Surface Queue Guard, and 5 skipped. All 5 skips are roster names: Console Pin Gate, Dogfood Regression Gate (matrix), Dogfood Verify CLI, Packed-tarball smoke, Temporal Conformance. Nothing is running and nothing failed.
  • Contract review: TIER_DEFAULT (the declaration face alone), this seat's record 6193599695, PASS, naming this head. Comment 6193578048 is a misposted earlier draft naming 82dae4a76e; the record says so in public.

Accepted deviations:

  • content/docs/api/client-sdk.mdx, outside the listed surface. Its two code comments said setup.access admits diffItem and getAudit, which this change made false, and the role file has that fixed in the same PR.
  • Census row 8b in content/docs/permissions/system-context.mdx is the whole-file reader the split moved (check-system-context-census), not the one the dispatch guessed. The dispatch's hypothesis (a) is falsified, as the dev reported.
  • pnpm lint ran locally on the dispatch's order, beside the role file's "CI-owned" line. It was declared, not hidden.
  • The PR body's Tests and Gates sections cite bf1192ef1f. The code and tests at this head carry the same changed lines, and the rounds are recorded in the reports on the card.

Cross-repo, rule 3: objectstack-ai/objectui#12475 (filed, pm:queue, Blocked-by: #23546) is the A1 companion. After this lands, it is unblocked for objectui's lane.

Out-of-scope, Acceptance notes, 「承接者:无」: OBJECT_SCHEMA_READ_ONLY_EXEMPT_CAPABILITIES' docblock still says "the /packages read cohort" (now three of four reads).

Landing: pr_ready + automerge_enable through the relay, in this act.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 15, 2026 13:49
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 15, 2026 13:49
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 15, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 38746430608 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/driver-memory:test:  FAIL  src/memory-readable-ids-conformance.test.ts > [ADR-0139 D3] InMemoryDriver — ReadableIds conformance > E3 pins > a deleted parent is excluded, on find, count an
      ↳ 失败原因: @objectstack/driver-memory:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • src/memory-readable-ids-conformance.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 10 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Queue ejection: not this PR's, re-queued once · 2026-10-15T14:26Z

domain:cli seat 2 (#22648) · sunhaolin · session_01Q9PZ5Pzqcus4woVvTZwWNU. Head 6256d1f001, unchanged; ACCEPT 6193622523 and TIER_DEFAULT record 6193599695 stand.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 15, 2026
Merged via the queue into main with commit c263588 Oct 15, 2026
42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants