Skip to content

docs(runtime,qa): record the /automation manage_metadata write gate in the two ledgers that describe the domain - #10600

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-10244-record-automation-write-gate
Aug 21, 2026
Merged

docs(runtime,qa): record the /automation manage_metadata write gate in the two ledgers that describe the domain#10600
os-elon merged 1 commit into
mainfrom
claude/issue-10244-record-automation-write-gate

Conversation

@os-elon

@os-elonos-elon commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Fixes#10244

Two hand-maintained ledgers describe the /automation domain's authorization
posture in prose, and both under-described it: the manage_metadata
authoring-write gate shipped in #10145 (PR #10242) and neither ledger said so.
This records the gate where the next author looks. Prose only — no behaviour
change, no ratchet moved, no covers key added.

1. packages/runtime/src/route-ledger.ts — the three /automation write rows

POST /automation, PUT /automation/:name and DELETE /automation/:name each
gain a note, written in the resume row's own convention (the sibling row
in the same domain block, which documents its #3801 gate in exactly this
field: what gates it, the fail-closed property, and what the gate does not
reach). The POST row carries the full statement; the PUT and DELETE rows
name the same isFlowAuthoringWrite door and repeat only the load-bearing
half — capability, fail-closed construction, refusal vocabulary, bypass.

2. packages/qa/dogfood/test/authz-conformance.matrix.ts — the anonymous-deny-automation row

The enforcement prose gains a per-route capability clause in the
/packages sibling's shape — that row spells its own as
… ; per-route capability predicates run after this floor — CAPABILITY for ROUTE-CLASS (THE ROUTES), and this row now follows it. The row previously
described only the anonymous floor, so the domain's second gate was invisible to
a reader of the ledger.

The row's note, covers, proof and state are untouched. The discover()
probe for this file reads packages/runtime/src/domains/automation.ts and
matches shouldDenyAnonymous\s*\( only (authz-conformance.test.ts:104-108),
so this adds no discovered key and cannot go UNCLASSIFIED or STALE.

Every clause re-verified against the code at head, not copied from the card

Claim written downVerified at
capability is manage_metadatadomains/automation.ts:291FLOW_AUTHORING_CAPABILITY
fail-closed by constructiondomains/automation.ts:382-384ec absent, systemPermissions absent, or empty all reach an empty Set and fall through to the refusal
isSystem bypassesdomains/automation.ts:383
refusal is 403 + PERMISSION_DENIEDdomains/automation.ts:294-295; asserted in automation-write-capability-gate.test.ts:181-182
which routesisFlowAuthoringWrite, domains/automation.ts:328-340 — execution doors deliberately outside
gate is the SECOND layer (anonymous is 401, not 403)ANONYMOUS_DENY_STATUS = 401 (core/src/security/anonymous-deny.ts:37); pinned in automation-write-capability-gate.test.ts:278-291
the measured escalation quoted on the DELETE rowthe refuseUngrantedFlowWrite docblock's own "What it closes, measured" section

One claim was trimmed rather than shipped: an earlier draft of the DELETE
note attributed the response body {deleted:true} to the #10145 measurement.
That body is recorded for the anonymous case (#5519); the #10145 docblock
records only "all 200". The note now says what was measured.

Out of scope, deliberately

The four-site manage_metadata predicate convergence (meta.ts x2,
packages.ts, automation.ts) is #7020's territory, and the per-domain refusal
messages are legitimately per-domain. Nothing here touches them.

Verification — every family scripts/pm/dispatch-gates.mjs derived, at 983c9c8

Re-derived with no path arguments after the final commit; the script named 10
families and this card's brief named none, so all 10 are additions. Each ran
under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 · held the lock 505s (8m25s) · waited 22s), with each exit code captured from the command
itself, never through a pipe.

FamilyExitThe runner's own verdict line
pnpm check:cross-package-test-inputs0OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob. (+ All 60 self-test cases passed.)
pnpm --filter @objectstack/spec run check:empty-state0✓ all classified (1 closed, 2 open, 4 output, 9 scope)
pnpm --filter @objectstack/spec run check:liveness0✓ every governed-type property at the walk's one-level granularity is classified …
pnpm check:slot-lookup0✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new, and every file in the population parsed.
pnpm --filter @objectstack/spec run check:strictness-ledger0✓ strictness ledger: 61 file(s) across 5 triaged director(ies) — every row names a live sited file, no undeclared schema files.
pnpm check:test-source-alias0check-test-source-alias OK — 72 packages with tests scanned; 61 registered as still resolving a workspace dep through dist/; 44 published subpath(s) resolved through every alias table.
pnpm check:type-source-resolution0check-type-source-resolution OK — 76 packages with a tsconfig.json scanned; 51 registered as still resolving a workspace dep's types through dist/.
pnpm --filter @objectstack/spec run check:variant-docs0✓ variant/doc gate: 18 discriminated union(s) — 8 governed (every variant mentioned in a bound doc), 10 exempt.
node scripts/check-cross-package-test-inputs.mjs0OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
node scripts/docs-audit/check-affected-docs.mjs0✓ affected-docs self-test: 281 cases pass. (its unreachable-row report is standing repo state, unchanged by this diff)

Targeted, same lock hold, same sha:

CommandExitVerdict
pnpm --workspace-concurrency=2 --filter '@objectstack/runtime^...' build0dependency closure built before anything read a .d.ts
pnpm --filter @objectstack/runtime typecheck0tsc --noEmit, no diagnostics
pnpm --filter @objectstack/runtime test0Test Files 178 passed (178) · Tests 2670 passed (2670)

Also run, outside the lock and declared as such because neither is heavy: a
TypeScript parseDiagnostics pass over both edited files (PARSE-OK for each —
the change is entirely inside string literals, so a broken quote is the one real
syntactic risk), and a control-byte self-scan over both files (no match).

One declared narrowing

packages/qa/dogfood's own authz-conformance.test.ts was not run locally:
its dependency closure is a near-whole-repo build, which is not a proportionate
local cost for a prose edit under a contended shared lock. What makes the
narrowing safe rather than a skip is that the only mechanical contract over the
field I edited is a presence check — packages/verify/src/conformance.ts:142,
if (r.state === 'enforced' && !r.enforcement) — and lengthening a non-empty
string cannot fail it. CI runs the suite regardless.

Changeset

None, and skip-changeset applied: the diff is two prose strings in a
runtime-internal ledger module and a test fixture. Nothing published changes
behaviour, so there is no release for a changeset to describe.


Generated by Claude Code

…n the two ledgers that describe the domain
The #10145 authoring-write gate shipped and is pinned, but neither
hand-maintained ledger that describes the /automation domain's
authorization posture says so, and both exist precisely so the next
author finds the decision they are standing on.
- route-ledger.ts: the three write rows (POST /automation, PUT and
DELETE /automation/:name) each carry a note in the resume row's own
convention — the capability, the fail-closed construction, the 403
PERMISSION_DENIED refusal, the isSystem bypass, and the one
isFlowAuthoringWrite predicate that selects them.
- authz-conformance.matrix.ts: the anonymous-deny-automation row's
enforcement prose gains the per-route capability clause in the
/packages sibling's shape, so the domain's second gate stops being
invisible to a reader of the ledger.
Prose only. No behaviour changes, no ratchet moves, no covers key
added — the discover() probe for the matrix file matches
shouldDenyAnonymous( only, so this gate adds no discovered key.
Part of #10244
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

4 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ✅

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: /automation/:name (route, 30 pages)
  • 2 name(s) were too generic to anchor anything (single lowercase words)

Coarse fallback — 24 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 5a616d5580433c161e7cb6777f00ea3048a35469packageMentionDocs.

Which tree this was computed on

This run read content/docs from 554c68e00f8622ef0bca399b22996248241883bb — the merge of head 983c9c8ecadf0b0a247c8642e68658a680dd5c85 into base 5a616d5580433c161e7cb6777f00ea3048a35469, 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 554c68e00f8622ef0bca399b22996248241883bb && git checkout 554c68e00f8622ef0bca399b22996248241883bb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5a616d5580433c161e7cb6777f00ea3048a35469 983c9c8ecadf0b0a247c8642e68658a680dd5c85 && git checkout -B drift-repro 5a616d5580433c161e7cb6777f00ea3048a35469 && git merge --no-ff 983c9c8ecadf0b0a247c8642e68658a680dd5c85
node scripts/docs-audit/affected-docs.mjs --json 5a616d5580433c161e7cb6777f00ea3048a35469

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

@os-elonos-elon added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 21, 2026 — with Claude
@os-elon
os-elon marked this pull request as ready for review August 21, 2026 05:01
@os-elon
os-elon added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit d5e7b9fAug 21, 2026
39 checks passed
@os-elon
os-elon deleted the claude/issue-10244-record-automation-write-gate branch August 21, 2026 05:14
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Record the /automation manage_metadata write gate in the two ledgers that describe the domain (route-ledger notes + authz conformance matrix row)

2 participants

@os-elon@claude