proof-aware-crypto-tooling-.../docs/runbook-latch.md
mrwulf 65772b3d2e warden hardening round: policy engine, ledger rotation, MCP UX, treasury LIVE, ops docs
Tier 2:
- request_signature decomposed into named gates (latch, freshness,
  intent, policy, signer, firewall)
- ledger: O(1) tail-read appends under a dedicated lock file (survives
  rotation rename); hash-chained segment rotation at policy
  ledger.rotate_at; verify-ledger walks all segments to genesis;
  archive tampering detected (tested)
- docs/threat-model.md (attacker matrix 1-9, proven-vs-trusted, design
  invariants) + docs/runbook-latch.md (diagnose-first recovery)
- lecture 10: executable corrupt-a-member exercise (capsule pin catches
  one appended byte), honest note on what the pin does NOT stop

Lightweight policy engine (POLICY_DENIED wired):
- policy.json: per-request/per-day amount ceilings, counterparty
  allow/deny lists, per-identity overrides; rules make their intent
  fields mandatory; daily sums from the ledger
-

Agent UX:
- signed refusal receipts travel inside MCP errors (receipt +
  receipt_path in structuredContent)
- airgap over MCP: request_signature signer=airgap + request_id,
  new airgap_pending tool; park -> list -> device answers -> complete
  (tested end-to-end)
- all 8 tools carry readOnly/destructive annotations
- sliding-window rate limiter per tool class (custody/verify/liveness);
  RATE_LIMITED refusal code; surface control, not ledgered

warden-treasury LIVE:
- treasury.py: stdlib base58, compact-u16, legacy+v0 wire parsing;
  every required signature quorum-verified over exact message bytes;
  completeness gap named in every verdict; RPC fetch uses response as
  bytes only
-
- live-quorum test: synthetic Solana tx signed with wallet key ->
  authentic via 4 proven forks; flipped byte -> not authentic

100 tests green (was 85).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:38:42 +02:00

3.4 KiB

Runbook: the custody latch fired

You are here because outbound custody is frozen and every signing request returns CUSTODY_LATCHED. This is the wallet doing its job: an unexplained quorum divergence or a firewall quarantine occurred, and the wallet refuses to certify anything — including its own refusals, which now arrive unsigned on purpose.

Do not unlatch first. Diagnose first. The latch is cheap; a released forged signature is not.

1. Read what happened (2 minutes)

pacta wallet status --wallet <dir>          # latch reason + incident ref
cat <dir>/latch.json
cat <dir>/incidents/<ref>.json              # the full divergence trail
ls <dir>/quarantine/                        # any withheld signatures
pacta wallet verify-ledger --wallet <dir>   # is the history itself intact?

The incident file names, per quorum member, its verdict and its binary hash at the moment of divergence. That table is your suspect list.

2. Classify (the incident file already did; check its work)

  • classification: semantic-edge (severity note) — the input hit a documented degenerate class (small-order R, non-canonical s). This does NOT latch by itself; if you are latched, something else also happened.
  • classification: unexplained (severity tamper) — members disagreed with no documented reason, or one errored. Assume fault or tampering until shown otherwise.

3. Investigate the three usual suspects, in order

  1. A corrupted/updated member binary. Compare each member's current hash against the capsule: sha256sum dogfood/state/quorum/pacta-verify-* vs capsule.jsonmembers[].binary_sha256. A mismatch on exactly the dissenting member is the common benign case (a rebuild happened); a mismatch you cannot explain is not benign.
  2. Hardware/memory fault. Re-run the exact input from the incident file through the quorum (payload_sha256, signature_hex, public_key_hex are all recorded). A divergence that does not reproduce points at a transient fault; log that finding in the unlatch note.
  3. Actual tampering. Divergence reproduces, hashes match the capsule, input is not a documented edge → treat the host as suspect: rebuild members from pinned sources on a machine you trust, re-run, compare.

4. Remediate

  • Benign rebuild drift → rebuild all members (pacta wallet build-quorum), then re-init or re-seal the capsule so the pins match reality again.
  • Transient fault → document it; consider the machine's RAM.
  • Suspected tamper → do not unlatch on this host. Preserve the wallet directory (it is the evidence), stand up a fresh wallet from fresh builds + fresh evidence elsewhere.

5. Unlatch — a deliberate, recorded act

pacta wallet unlatch --wallet <dir> --note "<what happened, what you checked, why it is safe now>"

The note is permanent: it lands in the hash-chained ledger next to the latch it releases, and shows up in every future audit. Write it for the auditor you hope never needs it. An empty or lazy note defeats the design; the CLI requires the flag, your discipline supplies the content.

6. Afterwards

Re-run a signing smoke test and confirm unanimous-accept; check pacta wallet status shows latched: false, chain intact, and the incident count where you expect it. If this wallet participates in a choir, expect peers to ask about the head gap — that is the system working.