The price

Ceremony

Arming is a ceremony, not a config flip. Dual custody: host-level staging plus live hardware attestation.

Stage TTL 120 s default Arm window 24 h default Daily cap $50 per UTC day Disarm no second factor, ever

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

Level
What it allows
Sentinel loaded
Observe and record only. The cheapest tier of the gauntlet.
Dispatcher enabledWATCH_DISPATCHER_ENABLED
Claims pending escalations and may spend. Still not the producer.
Producer boot gateWATCH_PRODUCER_ENABLED
A recovery and test surface. It is a gate, not an arm.
Hardware-backed producer arm
The real ceremony. The only level that lets the producer promote fires toward paid deliberation.
Disarm
Stops new claims only. In-flight provider work may finish and settles after disarm.

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

Route
Contract
POST /watch/admin/producer/arm
410 Gone. The legacy route, kept only to refuse.
POST /watch/admin/producer/arm/stage
Issues the stage challenge (v3, JCS).
POST /watch/admin/producer/arm/pending
Crash-resume for an in-flight stage.
POST /watch/admin/producer/arm/confirm
Attest and commit in a single DB transaction. Fail-closed.
POST /watch/admin/producer/disarm
Admin token OR any arm principal.

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.

Ceiling
Rule
Daily ceiling: $50 per UTC dayDAILY_SPEND_CAP_USD
May only be lowered. Raising it refuses boot.
Fanout reserve: $5WATCH_MAX_FANOUT_COST_USD
Reserved against each fanout before spend.
Signed arm window: 24 h defaultGW_ARM_WINDOW_MS
Signed into the challenge itself. Not env-extendable after the tap.
Max loss
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 rand dev 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

Knob
Effect
DEMO_GW_PORTdefault 28080
Gateway port for the isolated demo stack.
DEMO_COUNCIL_PORTdefault 28765
Council port for the isolated demo stack.
DEMO_POLL_TIMEOUTdefault 90
How long the lane waits for the loop to close.
DEMO_ALLOW_BUILD=1
Builds from this checkout. Needs about 8 GB for Docker plus BuildKit.
DEMO_PULL=1
Pulls Docker Hub tags instead. Hub tags may lag tip.

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.