Uh oh!
There was an error while loading. Please reload this page.
docs(runtime,qa): record the /automation manage_metadata write gate in the two ledgers that describe the domain - #10600
Conversation
…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
📓 Docs Drift Check4 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ✅ What this run could not see
Coarse fallback — 24 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 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 |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10244
Two hand-maintained ledgers describe the
/automationdomain's authorizationposture in prose, and both under-described it: the
manage_metadataauthoring-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
coverskey added.1.
packages/runtime/src/route-ledger.ts— the three/automationwrite rowsPOST /automation,PUT /automation/:nameandDELETE /automation/:nameeachgain a
note, written in theresumerow's own convention (the sibling rowin the same domain block, which documents its
#3801gate in exactly thisfield: what gates it, the fail-closed property, and what the gate does not
reach). The
POSTrow carries the full statement; thePUTandDELETErowsname the same
isFlowAuthoringWritedoor and repeat only the load-bearinghalf — capability, fail-closed construction, refusal vocabulary, bypass.
2.
packages/qa/dogfood/test/authz-conformance.matrix.ts— theanonymous-deny-automationrowThe
enforcementprose gains a per-route capability clause in the/packagessibling'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 previouslydescribed only the anonymous floor, so the domain's second gate was invisible to
a reader of the ledger.
The row's
note,covers,proofandstateare untouched. Thediscover()probe for this file reads
packages/runtime/src/domains/automation.tsandmatches
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
manage_metadatadomains/automation.ts:291FLOW_AUTHORING_CAPABILITYdomains/automation.ts:382-384—ecabsent,systemPermissionsabsent, or empty all reach an emptySetand fall through to the refusalisSystembypassesdomains/automation.ts:383403+PERMISSION_DENIEDdomains/automation.ts:294-295; asserted inautomation-write-capability-gate.test.ts:181-182isFlowAuthoringWrite,domains/automation.ts:328-340— execution doors deliberately outsideANONYMOUS_DENY_STATUS = 401(core/src/security/anonymous-deny.ts:37); pinned inautomation-write-capability-gate.test.ts:278-291DELETErowrefuseUngrantedFlowWritedocblock's own "What it closes, measured" sectionOne claim was trimmed rather than shipped: an earlier draft of the
DELETEnote 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_metadatapredicate convergence (meta.tsx2,packages.ts,automation.ts) is #7020's territory, and the per-domain refusalmessages are legitimately per-domain. Nothing here touches them.
Verification — every family
scripts/pm/dispatch-gates.mjsderived, at983c9c8Re-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 commanditself, never through a pipe.
pnpm check:cross-package-test-inputsOK: 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-state✓ all classified (1 closed, 2 open, 4 output, 9 scope)pnpm --filter @objectstack/spec run check:liveness✓ every governed-type property at the walk's one-level granularity is classified …pnpm check:slot-lookup✓ 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-ledger✓ 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-aliascheck-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-resolutioncheck-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-docs✓ 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.mjsOK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.node scripts/docs-audit/check-affected-docs.mjs✓ 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:
pnpm --workspace-concurrency=2 --filter '@objectstack/runtime^...' build.d.tspnpm --filter @objectstack/runtime typechecktsc --noEmit, no diagnosticspnpm --filter @objectstack/runtime testTest Files 178 passed (178)·Tests 2670 passed (2670)Also run, outside the lock and declared as such because neither is heavy: a
TypeScript
parseDiagnosticspass over both edited files (PARSE-OKfor 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 ownauthz-conformance.test.tswas 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-emptystring cannot fail it. CI runs the suite regardless.
Changeset
None, and
skip-changesetapplied: the diff is two prose strings in aruntime-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