Skip to content

Remote gate (service + fail-closed client)

The same pure check() lifts behind FastAPI, so the engine and policies can live in one place and agents call it over the wire. The client is fail-closed: an unreachable gate never means "allow."

# server — the gate service
from evidence_gate import Gate, PolicySet, Signer, create_app

app = create_app(Gate(PolicySet.from_dir("policies")), signer=Signer(b"secret-key"))
# uvicorn: `create_app` returns a FastAPI app with /v1/check, /v1/review, /v1/audit
# client — wrap existing tools by name pattern, no per-call-site rewrite
from evidence_gate import RemoteGate

gate = RemoteGate("https://gate.internal")
gate.auto_instrument(tools, {"stripe_*": "billing.issue_refund"})

tools.stripe_refund(45, "late package")   # gated transparently; runs only on ALLOW/RESTRICT
#   BLOCK            -> raises ClearanceDenied(.reason, .request_id)
#   gate unreachable -> raises GateUnreachable (tool never runs)

On ALLOW/RESTRICT the service returns a short-lived HMAC-signed clearance token (Signer/Verifier); the client verifies it on receipt. The same key signs the audit chain — with key=None the chain hash is byte-identical to the plain hash, so signing is a backwards-compatible drop-in.

A downstream service that actually executes the effect can make that token load-bearing — refusing any call that doesn't carry a fresh, action-bound one:

from evidence_gate import Verifier, require_clearance

@require_clearance(Verifier(b"secret-key"), action="billing.issue_refund")
def execute_refund(amount):          # the token is consumed by the guard, not forwarded
    ledger.debit(amount)

execute_refund(45, clearance_token=token)   # runs only if the token verifies for this action
#   missing / forged / expired token -> raises ClearanceRequired (effect never runs)
#   token minted for another action  -> raises ClearanceRequired