Payload schema
The batch's identity: the offset of its human-approval-requested event.
One verdict per request, same order as the batch's `requests` array. A count mismatch makes the event malformed and ignored.
Who decided: a human, or the door's own timeout (expiry is always all-reject and never signed).
The human's stated reason, applying to every rejected index in this decision. It rides back to the calling script in each rejected fetch's 403 body, so the agent can read why and retry with a change. Deliberately NOT covered by the approval.v2 signature: rejections never need signatures (deny is the fail-safe direction — stream-append access already suffices to veto), so signing the reason would protect nothing. Expiry decisions never carry one.
The enrolled key that signed this decision.
Base64 raw 64-byte r‖s ECDSA P-256 signature over the canonical approval.v2 message.
Raw JSON schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"approvalRequestEventOffset": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The batch's identity: the offset of its human-approval-requested event."
},
"verdicts": {
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"approve",
"reject"
]
},
"description": "One verdict per request, same order as the batch's `requests` array. A count mismatch makes the event malformed and ignored."
},
"decidedBy": {
"type": "string",
"enum": [
"human",
"expiry"
],
"description": "Who decided: a human, or the door's own timeout (expiry is always all-reject and never signed)."
},
"reason": {
"description": "The human's stated reason, applying to every rejected index in this decision. It rides back to the calling script in each rejected fetch's 403 body, so the agent can read why and retry with a change. Deliberately NOT covered by the approval.v2 signature: rejections never need signatures (deny is the fail-safe direction — stream-append access already suffices to veto), so signing the reason would protect nothing. Expiry decisions never carry one.",
"type": "string",
"minLength": 1,
"maxLength": 1000
},
"keyId": {
"description": "The enrolled key that signed this decision.",
"type": "string"
},
"signature": {
"description": "Base64 raw 64-byte r‖s ECDSA P-256 signature over the canonical approval.v2 message.",
"type": "string"
}
},
"required": [
"approvalRequestEventOffset",
"verdicts",
"decidedBy"
]
}