Skip to content

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

Description

@os-elon

Observation from #10145, filed rather than taken: two hand-maintained ledgers describe the /automation domain's authorization posture in prose, and both now under-describe it.

Nothing is broken and no gate reds — the enforcement exists and is pinned in packages/runtime/src/domains/automation-write-capability-gate.test.ts. What is stale is the description, and both ledgers exist precisely so the next author can find the decision they are standing on.

1. packages/runtime/src/route-ledger.ts — the three write rows carry no note

The resume row already documents its gate in exactly this field:

note: "generic, so the SUSPENDED NODE gates it (#3801) …"

By that convention the three rows #10145 gated could each name the capability they now require:

  • line 319 · POST /automationautomation.create
  • line 331 · PUT /automation/:nameautomation.update
  • line 332 · DELETE /automation/:nameautomation.delete

Why #10145 did not do it: that file was under a hard serial with in-flight PR #10177. The dispatch's instruction was to stop and report if the fix needed a ledger row change; it did not (the row interface carries no capability field, so the gate is fully expressible without touching the file), so the note is a follow-up to sequence behind #10177 rather than a conflict to take.

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

The /packages sibling row names its per-route capability gates inside the same enforcement field:

… per-route capability predicates run after this floor — manage_metadata for every state-changing route …

The anonymous-deny-automation row still describes only the anonymous floor, so the domain's second gate is invisible to a reader of the ledger.

This moves no ratchet. The discover() probe for this file matches shouldDenyAnonymous( only, so the new gate adds no discovered key, cannot go UNCLASSIFIED, and the completeness check is unaffected — which is exactly why this is a finding and not a defect. Per the file's own #8711 note, "one row per primitive, each in EXACTLY ONE honest state" is a hand-maintained invariant, not a checked one; a primitive whose description drifts is precisely what that note says the ratchet cannot see.

Also worth a look while in there

The manage_metadata capability check is now spelled at four sites, each with its own docblock and each identical in mechanism:

Each new one copied the last deliberately rather than inventing, so they agree today. Whether that should become one shared predicate is a judgement call — the repo's own standing note on this shape is that "a policy spelled out at three call sites is three policies that happen to agree today" — but the refusal messages are legitimately per-domain, so a naive extraction would lose information. Related: #7020.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions