HMAC-signed approval envelopes. The agent cannot forge them.
When the policy gate routes an action to T2 or T3, the agent requests an envelope. The server generates it, signs it with a key the agent never sees, scopes it to the exact ISIN and notional, and expires it in 90 seconds.
Bound to one action
The envelope is unforgeable not because the agent is well-behaved, but because the cryptographic scope_hash binds the approval to this exact action. Re-using the envelope for a different ISIN or notional fails verification.
@dataclass(frozen=True)
class ApprovalEnvelope:
envelope_id: str
action: str
isin: str
notional: int
side: Literal["buy", "sell"]
scope_hash: bytes # sha256(action || isin || notional || side)
approver_id: str
issued_at: datetime
expires_at: datetime # issued_at + 90s
hmac: bytes # server-side HMAC-SHA256 over the canonical payload Enforced at the API, not the UI
Segregation of duties is checked server-side on every request: the approver identity must differ from the agent caller. T3 dual-HITL requires two distinct approvers, both distinct from the caller, with manager role on at least one. A self-approval attempt produces a 403 and writes an audit row that counts toward AIR-OP-18.
Session-aggregate ledger closes split-trade bypass
A naive notional check is easy to bypass by splitting one large trade into many sub-materiality slices. BondFoundry tracks a session-aggregate materiality ledger so cumulative notional within a session triggers HITL even if no individual trade does.