Straza / decision flow

outcome
One governed tool call and the escalation trilogy A harness fires a hook; straza normalizes the event, verifies the signed snapshot, and decides locally in microseconds. The winning rule picks one of six outcomes: a plain allow or deny, or one of the three escalations serverCheck, classify, and approve. Approve has two shapes: a hold that blocks for a live human decision, and a ticket that denies now and lets a later real call consume a durable grant. Every outcome is audited asynchronously into the hash chain. 1 · LOCAL DECIDE (Tier 1, no server, no DB) agent harness Claude · Codex · GeminiTier-1 CLI PreToolUse · stdinhook straza hook · local PDP normalize → verify snapshot (fail closed) evaluate · deny-overrides · p99 < 100 µs winning rule decide effect + escalation? 2 · THE WINNING RULE PICKS ONE OUTCOME allow exit 0 · tool runs deny exit 2 · Straza: reason serverCheck strazad · POST /v1/decide server PDP re-check · live snapshot server allow offline / server deny ⇒ deny classify classifier indirection scan · no model · ≤ 1 s no signal ⇒ allow clear signal / down ⇒ deny approve · hold human decides in-line gateway blocks ≤ 90 s · socket cap 120 s approved ⇒ allow denied / timeout ⇒ deny-final approve · ticket deny-with-ticket now (never blocks) human ≤ ticketTTL 24 h · later call ⇒ grant ≤ 1 h grant ⇒ allow once within-window deny ⇒ deny-final 3 · EVERY OUTCOME IS AUDITED (async, never blocks) audit spool JSONL · async drain → /v1/audit/batch detached · rotate on drain outbox → JetStream → chain sha256(prev ‖ ce) · verify
Pick an outcome (top left), then play or step to walk one governed tool call from hook to hash chain. serverCheck, classify, and approve are the three escalations of a local allow; approve has a hold and a ticket shape.
What this shows

One local tool call inside a governed harness. The harness fires PreToolUse and spawns straza hook; the adapter normalizes the dialect to the canonical event; the cached snapshot's ed25519 signature is verified against pinned keys; rules evaluate in-process under deny-overrides (p99 < 100 µs) with no server and no database. The winning rule selects one of six outcomes. A plain allow exits 0; a plain deny exits 2 with an actionable Straza: reason. The three escalations act only on a winning allow. serverCheck re-checks at POST /v1/decide against the live server snapshot (offline past grace fails closed). classify runs the embedded heuristic classifier (interpreter-aware indirection scan, no model, 1 s deadline); a clear signal or an unavailable classifier denies. approve takes two shapes. A hold (default class) blocks the gateway lane up to timeoutSeconds (default 90, cap 3600), holding the socket itself at most 120 s (approval.gatewayHoldSeconds) before the caller gets a pending reason it can await, while the hook lane denies with a retry-after-approval reference plus a single-use exemption; denied or expired is deny-final. A ticket never blocks: it denies now, a human decides within ticketTTL (default 24 h, cap 30 d), and a later real call, even a fresh session, consumes a DB-durable grant within grantTTL (default 1 h, cap 24 h) bound to the human plus fingerprint; a within-window denial stays deny-final. Every outcome is spooled, drained to /v1/audit/batch, and appended to the tamper-evident hash chain. All paths fail closed.