The price
Ceremony
Arming is a ceremony, not a config flip. Dual custody: host-level staging plus live hardware attestation.
One armable thing, and one only
The gauntlet has exactly one armable seam: the Watch producer. That is the seam that promotes a recorded fire toward the dispatcher, paid Council deliberation, and a signed Outbox directive. Everything else you can switch on is just posture.
Not the ceremony, no matter how official it feels:
- Starting Gateway arms nothing.
- Loading or enabling a Sentinel arms nothing.
- Enabling Governed routing arms nothing.
- Enabling the dispatcher alone arms nothing.
- The producer boot gate alone arms nothing.
The control ladder
The ceremony, step by step
Six steps. Host-level staging first, live hardware attestation second. A stage that expires, a hash that changed, or a prompt you did not expect stops everything.
Step 1: Configure a principal
GW_ARM_PRINCIPALS=sovereign-op:<token>
OperatorSet this in ~/.config/irin/gateway.env (permissions 0600), then restart Gateway. The token is never a CLI argument.
Step 2: Enroll hardware
gateway/bin/arm-enroll # Touch ID / Secure Enclave gateway/bin/arm-enroll-fido2 # or a FIDO2 key
OperatorOnly the public credential record is written. Then verify the keyset hash with gateway/bin/verify-attest-keyset-hash against the sidecar boot log.
Step 3: Rehearse
gateway/bin/arm --rehearse
OperatorExpect rehearsal-ok. The producer does not start. Stop conditions: an unexpected biometric prompt, a changed credential hash, an expired stage, or a writer-claim conflict.
Step 4: Pre-arm checklist
- Council, Gateway, and Watch are healthy.
- The intended tenant and Sentinel profile are confirmed.
- Explicit spend ceilings are set.
- The dispatcher credential is present.
- The last rehearsal passed on the current build and keyset.
- No other writer holds the same
watch.db.
Step 5: Arm
gateway/bin/arm
OperatorExpect armed. The flow: stage, then an SE-P256 or FIDO2-ES256 hardware confirm bound to the content, cap, and window, then a signed active_arm row. Stages expire (default 120 s, ARM_STAGE_TTL_MS) and cannot be reused.
Step 6: Verify before any test fire
- Producer state is armed as expected.
- The writer claim is held by this runtime.
- The arm audit row exists.
- Spend metrics are visible and sane.
Ceremony routes
Disarm: the fast exit
Arming always blocks on the second factor. Emergency disarm never does. Any authorized principal can pull the plug, over the management socket, never the public listener.
gateway/bin/disarm
OperatorRuns over the management socket, never the public listener. Any authorized principal may disarm, and it never requires a second factor. An unexpected hardware prompt during disarm is an alarm, not a feature.
WATCH_PRODUCER_ENABLED=false
FallbackIf the binary path is unavailable: stop the runtime, set this, restart. The producer boot gate stays down.
During an incident
- Never delete the state volume. Never run
docker compose down -v: the volume holds diagnostic evidence. - After resolution: verify DB and ledger integrity, restart, and pass a fresh rehearsal before any later arm.
Spend ceilings: the price, capped
Thought is rare, so it gets a budget. These are the numbers the ceremony signs up for, quoted exactly.
min(daily_ceiling, charge_unit * claims_per_tick + in_flight_at_disarm). The worst case is arithmetic, not vibes.The boot triple-gate is not an arm
WATCH_PRODUCER_ENABLED + EXECUTION_MODE=LIVE + WATCH_DISPATCHER_GATEWAY_KEY starts the producer but never writes active_arm. Spend reserves fail closed without the ceremony.
The verify lane: proof without price
One command proves the whole closed loop from a fresh clone to a signed directive, with zero spend and zero hardware. This is how you check the gauntlet before you trust it.
make verify make verify-down
ProofFresh clone to signed directive in one command. Prints the signed directive_outbox row and the elapsed time; the under-120-second clock starts after image availability.
Guarantees
- No real provider keys. Throwaway
openssl randdev secrets go into.env.demo; it never touches.env. - No Touch ID or hardware arm. A software P-256 key stands in, same as CI.
- Full isolation: compose project
irin-demo, ports 28080 and 28765 (never 18080), its own volumes, and an ephemeral ledger key under.demo-state/. Safe to run alongside live stacks. - Everything else runs the real production code path.
- It never builds unattended and never pulls Docker Hub by default.
- Requirements: git, make, docker with compose v2, openssl, python3, jq.
Knobs
The honest limits
- The verify lane proves the closed loop with a software key, not the hardware path. The real Touch ID arm is Day-2 operator setup.
- Quarantine and probation:
fails_to_trigger=2, backoff 60/300/1800/3600 s, hard-kill after 5 cycles in 1 h. - Disarm is not a cancel: in-flight provider work may finish and settles after disarm.
- Backup requires BOTH the state volume and the signing key. One without the other is not a backup.