Decisions and refusals
Every outcome is a signed envelope with a stable code — and "we could not tell" is a third state that must never be reported as a denial.
The unit of output is a decision envelope. It is canonical CBOR, it is signed, and it is the same shape whether the operation completed or was refused.
Three terminal states, not two
This is the single most important distinction on the page, because collapsing it is how a system quietly lies to its operators.
| Terminality | Meaning | What a relying party may conclude |
|---|---|---|
Completed | The operation was authorized and its effect occurred. | The effect happened. |
Refused | The rules were evaluated and the answer was no. | The effect did not happen, and here is the reason. |
Indeterminate | Terminal knowledge was lost — a seat timed out, a network partition, a crash between commit and acknowledgement. | Nothing. Neither that it happened nor that it did not. |
Indeterminate must never be reported as a refusal. A refusal says we decided
against this; an indeterminate outcome says we do not know what happened.
Treating the second as the first invites a caller to retry an operation that may
already have taken effect.
The type system enforces it: no refusal code is reachable from lost terminal knowledge. There is no path from "the seat did not answer" to a signed statement that policy denied the request.
Commit precedes release
An envelope reporting Completed without a transparency log inclusion proof is
malformed — not merely suspicious. DecisionEnvelope::is_well_formed() says so,
and the refusal code RELEASE_BEFORE_COMMIT (603) exists for the case where an
executor tries it anyway.
The ordering matters because a log you can append to after releasing the secret is not evidence of anything. Commit first, then release.
Refusal codes
Codes are stable and grouped by the layer that decided. A code may be deprecated but never reused — an archived envelope must still mean in ten years what it meant when it was signed.
Protocol (1xx)
| Code | Name | Meaning |
|---|---|---|
| 100 | MALFORMED_REQUEST | Input was not canonical CBOR, or violated a schema rule. |
| 101 | UNKNOWN_SCHEMA_VERSION | Object schema version is not supported by this deployment. |
| 102 | UNSUPPORTED_CIPHERSUITE | Named ciphersuite is not implemented or not permitted here. |
| 103 | AMBIGUOUS_SEMANTICS | The request could be read more than one way; normalization refused rather than guessing. |
| 104 | OBJECT_EXPIRED | A carried object was past its expiry at evaluation time. |
Policy (2xx)
| Code | Name | Meaning |
|---|---|---|
| 200 | POLICY_DENIED | Compiled policy denied the operation. |
| 201 | CONSTITUTION_VIOLATION | The action would breach a constitutional invariant. |
| 202 | REFINEMENT_FAILED | Active policy could not be shown to be no weaker than the constitution. |
| 203 | ALGORITHM_FLOOR | Requested algorithms are below the configured floor; downgrade refused. |
| 204 | DIVERSITY_FLOOR | Quorum met numerically but failed the independence requirement. |
| 205 | UNILATERAL_AUTHORITY | A single principal would have sufficed; the no-unilateral invariant refused it. |
| 206 | MANDATE_EXCEEDED | An agent's mandate does not cover this tool, resource, counterparty or limit. |
| 207 | DELEGATION_AMPLIFIED | Child authority was not a subset of its parent. |
| 208 | BUDGET_EXHAUSTED | A value, count, destination or time budget is spent. |
Appraisal (3xx)
| Code | Name | Meaning |
|---|---|---|
| 300 | EVIDENCE_MISSING | A required evidence family was absent. |
| 301 | EVIDENCE_STALE | Evidence was outside its freshness window. |
| 302 | EVIDENCE_INVALID | Evidence failed cryptographic verification. |
| 303 | MEASUREMENT_MISMATCH | A running image's measurement did not match an approved reproducible build. |
| 304 | REFERENCE_VALUE_UNKNOWN | No approved reference value covers the presented measurement. |
| 305 | VERIFIER_DISAGREEMENT | Independent verifiers disagreed. Disagreement is failure, never an average. |
| 306 | COLLATERAL_UNAVAILABLE | Vendor collateral could not be obtained, or had expired. |
| 307 | ATTESTATION_REVOKED | Presented evidence is covered by a revocation. |
State (4xx)
| Code | Name | Meaning |
|---|---|---|
| 400 | NULLIFIER_SPENT | This nullifier was already reserved; the operation is a replay. |
| 401 | RESERVATION_EXPIRED | The reservation lapsed before contributions completed. |
| 402 | OPERATION_CANCELLED | Cancelled before commit. |
| 403 | GENERATION_RETIRED | A contribution named an authority generation that is no longer live. |
| 404 | EPOCH_MISMATCH | A contribution named a refresh epoch other than the current one. |
| 405 | QUORUM_UNAVAILABLE | Not enough eligible seats contributed before the deadline. |
| 406 | STALE_WORK | Policy, evidence, generation or epoch changed after appraisal; prior work was invalidated. |
| 407 | SEQUENCE_VIOLATION | An event arrived out of order. Distinct from malformed: the object was well-formed, the sequencing was not. |
Seat (5xx)
| Code | Name | Meaning |
|---|---|---|
| 500 | SEAT_UNKNOWN | The contributing seat is not a registered member of this authority. |
| 501 | SEAT_REVOKED | The seat was revoked or quarantined. |
| 502 | CONTRIBUTION_INVALID | The contribution failed its correctness proof. |
| 503 | CONTRIBUTION_REPLAYED | Valid — for a different request, object, policy, executor or epoch. |
| 504 | DUPLICATE_SEAT_INDEX | Two contributions claimed the same share index. |
| 505 | SEAT_UNAVAILABLE | The seat did not respond within its deadline. |
Executor (6xx)
| Code | Name | Meaning |
|---|---|---|
| 600 | EXECUTOR_UNATTESTED | The executor could not present acceptable evidence for its boundary. |
| 601 | RECIPIENT_KEY_UNBOUND | The ephemeral recipient key was not bound into the attestation evidence. |
| 602 | CAPABILITY_MISMATCH | The presented capability did not authorize the attempted action. |
| 603 | RELEASE_BEFORE_COMMIT | Release was attempted before commit and log inclusion. |
| 604 | OPERATION_NOT_PERMITTED | The requested primitive is outside the executor's permitted set. |
Transparency (7xx)
| Code | Name | Meaning |
|---|---|---|
| 700 | INCLUSION_UNPROVEN | No valid inclusion proof was available before the release deadline. |
| 701 | LOG_INCONSISTENT | A consistency proof between checkpoints failed. |
| 702 | WITNESS_QUORUM_MISSING | Too few independent witnesses co-signed the checkpoint. |
Two authorizations, never interchangeable
An envelope binds two distinct things, under separate context labels:
x_wrap— the stable wrapping authority. It says this key may be wrapped under this authority at all.x_auth— a fresh, per-request authorization. It says this specific request, against this specific object, under this policy, at this moment.
Neither is replayable as the other. Tests assert that every field of both is bound and that swapping them fails. Collapsing them is how "the key is protected" turns into "anyone who once got a wrap can do anything forever."
Explaining a decision to a human
GET /v1/operations/{digest}/explain returns a JSON rendering intended for an
operator: which rule fired, which evidence was consulted, which seats
contributed, which floor was not met.
It is a convenience, not the signed representation. The envelope is the signed thing. If the JSON and the envelope ever disagreed, the envelope is right and the JSON is a bug.