[AVAILABLE], shipped commit
f779d68 (2026-08-02), RFC-0021. See docs/CLAIMS.md §3.11
for the full evidence list this page draws from.The gap this closes
An approved execution has always left cryptographic evidence behind: the Execution Trust Record and its signature (see Execution Trust Records). A refused one used to leave only an HTTP response and whatever the caller’s own logs happened to capture, nothing durable, nothing independently checkable. A Refusal Record is the REJECT-path counterpart to an Execution Trust Record: durable, signed, and third-party-verifiable the same way, without requiring a caller credential or database access to check it. Scope is deliberately narrow. A Refusal Record is produced only for a policy decision thatRuntimeEngine.execute actually reaches: an ordinary PolicyEngine.evaluate REJECT, or a
SignalIntentBinder binding-violation REJECT (see Policies and the
decision). Caller-authentication failures and webhook
signature failures are a separate, unsigned audit-sink capability (RFC-0021 Non-Goals) — treat
any claim about Refusal Records as “a policy decided to reject,” not “every rejection of any
kind.”
What’s in the record
RefusalRecordBuilder (packages/runtime/src/RefusalRecordBuilder.ts) builds the record from
the rejected transaction, its Decision, and any binding violations. RefusalCrypto
(packages/crypto/src/RefusalCrypto.ts) hashes and signs it with the same
FileKeyProvider/DEFAULT_KEY_ID stack every other signed artifact in this system uses — one
root of trust, not a separate one for refusals. At most one RefusalRecord exists per
businessTransactionId: unlike an Execution Trust Record, a refusal is a single terminal event,
not an append-only aggregate.
Two routes, two different trust models
POST /refusal/verify— verifies a submitted Refusal Record’s signature, returns{ valid }. Deliberately mounted ahead of caller-auth middleware (packages/api/src/app.ts): no API key, no lookup by ID, nothing but the artifact itself and Parmana’s public key. This is what makes a refusal independently checkable by whoever actually received the rejection, not only by Parmana.GET /refusal/:businessTransactionId— looks up a Refusal Record from Parmana’s own storage by transaction ID. Unlike the route above, this one stays behind caller-auth and ownership scoping (isOwnedByCaller), identically to/verify,/verification, and/trust-records— the underlying transaction content (signals, intent parameters) may be sensitive, even though the signature-verification capability above is intentionally open.
Two scope caveats, both load-bearing
Related
POST /audit/verifycloses the analogous gap for caller-authentication and Razorpay-webhook audit trails — one route, verifies either aCallerAuditEventor aRazorpayWebhookAuditEvent, also mounted ahead of caller-auth. See Error catalog for both routes’ error shapes.- Execution Trust Records — the APPROVE-path counterpart.
- Policies and the decision — how a
Decisionreaches REJECT in the first place, includingSignalIntentBinderbinding violations. docs/rfcs/RFC-0021-Refusal-Record.mdin the repository — the original design document.