[AVAILABLE].
packages/runtime/src/RefusalRecordBuilder.ts,
packages/crypto/src/RefusalCrypto.ts.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 by design, 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
- Caller Audit Trail closes the analogous gap for
caller-authentication events, signed, durable, and, per caller, chained.
POST /audit/verifymirrors this page’sPOST /refusal/verifyexactly: mounted ahead of caller-auth, structurally checks and verifies any signed audit event’s signature. 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. - Security and Limitations for the wider threat model this evidence trail supports.