> ## Documentation Index
> Fetch the complete documentation index at: https://docs.parmanasystems.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Refusal Records

> The evidence a rejected transaction leaves behind: a signed, independently verifiable Refusal Record, and the two scope caveats that bound the claim.

<Info>
  **\[AVAILABLE]**. `packages/runtime/src/RefusalRecordBuilder.ts`,
  `packages/crypto/src/RefusalCrypto.ts`.
</Info>

## The gap this closes

An **approved** execution has always left cryptographic evidence behind: the Execution Trust
Record and its signature (see [Execution Trust Records](/concepts/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 that
`RuntimeEngine.execute` actually reaches: an ordinary `PolicyEngine.evaluate` REJECT, or a
`SignalIntentBinder` binding-violation REJECT (see [Policies and the
decision](/concepts/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

```typescript theme={null}
// packages/shared/src/domain/refusal-record.ts
export interface RefusalRecord {
  readonly refusalRecordId: string;
  readonly businessTransactionId: string;
  readonly decision: Decision; // the exact rejected Decision, not summarized
  readonly evaluatedIntent: RefusalIntentSnapshot;
  readonly bindingViolations?: readonly RefusalBindingViolation[]; // present only for a SignalIntentBinder REJECT
  readonly submittedBy?: string; // absent when caller-auth is disabled
  readonly refusalRecordHash: string;
  readonly signature: Signature; // same signing stack as ExecutionTrustRecord
  readonly createdAt: Date;
}
```

`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

<Warning>
  1. **Refusal Record writing fails open, deliberately, not fail-closed like
     caller-auth audit writes.** `RuntimeEngine.writeRefusalRecord` runs after the
     `Decision` is built but is explicitly barred from delaying or affecting the
     rejection response that follows it. A write failure is logged
     (`refusal_record_write_failed`) and swallowed, never thrown. **The rejection
     itself is unaffected either way**, a request that should be denied is still
     denied, correctly, whether or not its evidentiary record lands. What can be
     silently missing is the durable proof of *why*, not the correctness of the
     refusal. Making the write atomic or fail-closed with the response was
     considered and rejected: it would turn a correct policy rejection into an
     opaque `500` on a storage hiccup, a real availability regression, for no
     corresponding security gain, since the request was already unconditionally
     denied the instant `Decision` was built. 2. **Only production (Supabase) audit
     sinks sign.** `SupabaseCallerAuditSink` signs every event; the in-memory test
     sinks (`NODE_ENV=test`) do not. Existing rows written before this capability
     shipped remain unsigned, `signature_json` is nullable and additive, reflecting
     that history rather than backfilling a signature that was never actually
     produced.
</Warning>

## Related

* [Caller Audit Trail](/concepts/caller-audit-trail) closes the analogous gap for
  caller-authentication events, signed, durable, and, per caller, chained. `POST
  /audit/verify` mirrors this page's `POST /refusal/verify` exactly: mounted ahead of
  caller-auth, structurally checks and verifies any signed audit event's signature. See
  [Error catalog](/api-reference/error-catalog) for both routes' error shapes.
* [Execution Trust Records](/concepts/execution-trust-records), the APPROVE-path counterpart.
* [Policies and the decision](/concepts/policies-and-the-decision), how a `Decision` reaches
  REJECT in the first place, including `SignalIntentBinder` binding violations.
* [Security](/security/overview) and [Limitations](/security/limitations) for the wider
  threat model this evidence trail supports.
