The guarantee that an unattended (scheduled) run may not self-approve a plan
whose blast radius requires a human rests entirely on one unsigned free-text
field in a local JSON file. The same file carries a reference to the signed
receipt that would actually prove a human approved, and the gate never reads it.
The live-run gate (routes/dispatch_workflow.py:905-908) decides whether a
scheduler-channel run may proceed on a require_human plan:
_pin_rec = _pp.load(WS, wf_id) or {}
_pin_human = bool(_pin_rec.get("plan_root")) and not str(
_pin_rec.get("approved_by") or "").startswith("migration:")
if not (channel == "scheduler" and _pin_human):
return _refused(... "an unattended run may not self-approve it" ...)
So "a human approved this" reduces to "the pin's approved_by does not start with
'migration:'". The pin (primitives/plan_pin.pin) is written as plain JSON with no
signature and no integrity hash:
rec = {..., "plan_root": ..., "approved_spend_cents": ...,
"approved_requires": ..., "approved_by": approved_by,
"approval_receipt": receipt_id, ...}
json.dump(rec, fh)
Two fields in that record govern the run, and only one of them is protected.plan_root IS integrity-anchored: plan_pin.check() compares it to the freshly
recomputed, Ed25519-signed plan.workflow_root, so an attacker cannot change WHAT
runs without breaking the match. approved_by — the field that alone separates a
human approval from an un-reviewed migration auto-pin — is anchored to nothing.
And approval_receipt, the pointer to the signed approval receipt that could
prove the human decision, is written (plan_pin.py:98) and read nowhere in the
tree (a whole-repo grep finds that one write and no read).
Flipping approved_by from "migration:first-live-run" to any string not starting
with "migration:" — leaving plan_root, and therefore the signed-plan match,
untouched — makes _pin_human true, and the scheduler channel then self-approves an
irreversible / require_human workflow: exactly the action this gate exists to
forbid. plan_pin.check() continues to report "matches the approved plan", because
the root match is sound; it proves WHAT runs, never WHO approved it.
This is the same class as the staged-integration-send seal gap: a governance
record in which one field is integrity-anchored and the sibling field the gate
actually trusts is not, with a correct, verifiable value present in the record
(here approval_receipt) that the enforcement declines to use.
Reproduction steps:
- Extract the station-v1.0.0 tarball to a clean directory and put workbench/
on sys.path.
- plan_pin.pin(ws, "wf", plan, approved_by="migration:first-live-run",
acknowledged_migration=True) — the state the first live run leaves.
- Replay the gate predicate: load the pin and compute _pin_human = bool(
plan_root) and not approved_by.startswith("migration:"); confirm a scheduler
run is refused.
- Open <ws>/plan_pins/wf.json, set "approved_by" to "operator:jane" (leave
plan_root and everything else unchanged), save.
- Recompute the predicate, and call plan_pin.check(ws, "wf", plan).
Expected: whether a plan counts as human-approved for an unattended run is bound
to a verifiable artifact — the signed approval receipt the pin already
references — so relabeling a text field cannot manufacture a human approval.
Actual:
step 3 approved_by="migration:first-live-run" -> scheduler allowed = False
pin has signature: False; approval_receipt: None (never consulted)
step 4/5 approved_by="operator:jane", plan_root unchanged
-> scheduler allowed = True (require_human plan runs unattended)
plan_pin.check() -> True, "matches the approved plan"
Root cause: the pin conflates "this is the approved plan" (plan_root, effectively
signed via comparison to the signed plan) with "a human approved it" (approved_by,
an unsigned free-text label), and stores but never verifies the one field that
could bind the second claim to reality (approval_receipt).
Suggested fix: bind the human-approval decision to the signed approval receipt
the pin already carries — on the unattended path, load approval_receipt, verify
its Ed25519 signature against the install key, and confirm it attests a human
channel (session/cli) approving THIS plan_root — rather than trusting the
approved_by string. At minimum, sign the pin record (Ed25519 over the whole
record minus the signature, as every other governance artifact in this tree is
sealed) so approved_by cannot be edited without detection. The "migration:"
prefix convention is not an integrity control and should not be treated as one.