ZIFFER home

Support: who to write to, what we promise, and what every refusal means

For the admin who owns the integration.

For the admin who owns the integration. The table in section 4 has a row for every refusal your SDK, your Executor, the decision API and the Policy Engine can give you: sdk.md section 5, the Executor's alerts, and the names the API and the engine answer with.


1. The two ways to reach us

AddressUse it forAnswered
Writehello@ziffer.ioeverything. Questions, refusals you cannot explain, a leg that looks slow, a document that is wrong.in the times in section 2
Chatthe chat button on ziffer.iothe same, when you want an answer while you are at the keyboard.in working hours

Chat and email reach the same people. A refusal is not an incident. A refusal is the system working; section 4 says what each one means and who moves next.

To know the state of our deployment, write or use the chat: we answer in the times in section 2.

2. Severity, and what each one buys you

Severity is set by what you can no longer do, never by how the request was written. We will move a ticket's severity up or down and say why in the ticket.

SeverityWhat qualifiesFirst responseUpdates until it closesPaged on our side
1No decision is being reached at all, or a control did not hold: an action ran that a receipt did not authorise, an audit chain gap, a replayed receipt that executed.1 hour, in working hourshourlyyes, immediately
2Decisions are reached but a path is unusable: a deferred release never releases, one door refuses everything, receipts arrive but cannot be delivered.4 working hoursevery 4 working hoursyes, in working hours
3A refusal you cannot explain, or behaviour that disagrees with a document. Nothing is down.1 working daydailyno
4A question, a documentation defect, a feature request.3 working dayson changeno

Working hours are 09:00 to 18:00 Paris time (Europe/Paris), Monday to Friday, except French public holidays.

What "paged on our side" is not. The alert classes in section 4 are raised to the audience your own signed bundle names in its alert_targets.json, one entry per class, and a class naming nobody is refused when the bundle is loaded. That audience is yours. Our being paged is a separate arrangement over our own deployment, and it is not a substitute for yours: if your alert_targets.json names an address nobody reads, nobody reads it.

By class, on our side:

Alert classOur severityWhy
AUDIT_INTEGRITY, LEDGER_REPLAY, ATTESTATION_INVALID1each is a control reporting that something it exists to prevent was attempted
SIGNING_SUBSTRATE, BUNDLE_INVALID1nothing can be signed or nothing can be loaded, so nothing is being decided
RECOMPUTATION_MISMATCH, CONTEXT_STORE, DEFERRED_RELEASE, FLOOD_CAP, ATTESTATION_QUEUE_DEPTH2one path is unusable and the others are not
SUITE_BELOW_FLOOR, RECEIPT_INVALID3in almost every case a floor or a build on your side, and the receipt was correctly refused

SIGNING_SUBSTRATE, BUNDLE_INVALID and ATTESTATION_QUEUE_DEPTH are three of the twelve classes your bundle must name an audience for. They are not in section 4's table because neither onboarding document publishes them as something you can be handed: they are raised inside our perimeter.

3. What to put in a report

Five things. With them a refusal is usually answered on the first reply; without them the first reply asks for them.

  1. Your tenant. The label, exactly as it appears in your bundle's manifest.
  2. The attestation id, if the action got as far as one.
  3. The receipt, base64 or as bytes, or the proposal if no receipt came back.
  4. The refusal name: the clause id or the alert class, spelled as section 4 spells it. This is the machine-readable half. The message beside it is for a human and is not part of the contract, so quote the id and not the sentence.
  5. When, in UTC, and what you were doing.

Never send a private key, a seed, or an API key. Nothing we ask for needs one, and a report is not a channel we treat as secret.

4. Every refusal, what it means, and what to do now

How to read "who fixes it". It names who owns the fix once the cause is known, not who reports it. you means the change is in your configuration, your build or your data. us means the change is in ours. you and us means the first move is yours and the fix is likely ours, or the cause genuinely sits across the boundary.

How to read "refusal" and "Rule". A refusal your SDK's verify raises is listed by its name, the first word its error prints; its Rule is the rule of the specification it was refused under, linked to that rule's section, and the error prints it last, in brackets. Every other row is listed by the name or the rule it arrives with.

How to read "documented in". The first rows are refusals your own code raises: what your SDK's verify refuses on (sdk.md section 5) and what your Executor alerts about (the Executor's alerts). The rows below them are ours. the decision API is what a call to our API answers with, in the error member of a 4xx or 5xx body. the Policy Engine is the clause a rule refused under, and the clause an approval is refused under: the API does NOT serve it (the caller may be the AI agent being contained, and a clause served one decision at a time maps your rules), so you read it in the console and in your audit chain. the refusal category is what the API serves in its place, in a decision's refusal_category member: four names that say whether to try again and nothing about which rule.

Most of these must not be retried, and the ones that may are marked. A refusal is deterministic on the same input: asking again gets the same answer one round trip later. What is not a refusal in that sense says so in its own row — QueueFull, RateLimited and FeedbackRateLimited name the seconds to wait in a Retry-After header, and GatewayUnavailable, AdmissionUnavailable, FeedbackUnavailable and DP-37 are something on our side failing to answer rather than answering no. A dependency that could not answer on YOUR side is a different thing again, and is not in this table: there the Executor reaches no verdict, reports nothing, and the work stays queued for the next poll.

refusalwhat it meanswhat to do nowwho fixes itdocumented inRule
ReceiptVersionUnsupportedWhat you passed as the receipt is not a version 3 receipt.Pass the receipt exactly as the decision API returned it, unchanged. If you did, report it with the receipt.yousdk.md section 5AB-0
ReceiptTooLargeThe receipt's signed body is larger than the 4,096 bytes a receipt is signed within.Do not act on the receipt and do not retry. Report it with the receipt.ussdk.md section 5AB-6
SuiteUnknownThe receipt names a signature suite this verifier does not know or names none, or the suite floor you configured is not a suite it knows.Check the spelling of the suite floor you configured (ZIFFER_SUITE_FLOOR), then update the SDK. If both are right, report it with the receipt.you and ussdk.md section 5CR-1
SuiteBelowFloorThe receipt, or an approval sent with it, is not signed with a suite that contains every algorithm your suite floor requires.Confirm the suite floor you configured is the one you meant. If it is, do not act on the receipt and report it with the receipt.you and ussdk.md section 5CR-4
NotCanonicallyEncodableThe receipt, an approval or the proposal you passed holds a value with no canonical encoding, such as a fractional number, or the proposal bytes are not UTF-8 JSON.Pass the exact proposal bytes you sent and the receipt exactly as it was returned. Remove any step that re-encodes either one on the way.yousdk.md section 5AT-8a
SignatureInvalidThe receipt's signature does not verify under your trust anchor's keys.Do not act on the receipt and do not retry. Confirm your trust anchor is the one on your handover sheet, then report it with the receipt.you and ussdk.md section 59.3-1
DecisionNotAllowThe receipt records a decision other than ALLOW.Do not run the action. Report it with the receipt.ussdk.md section 59.3-2
ReceiptNotBoundToProposalThe receipt is not bound to the proposal you passed.Pass the exact proposal this receipt answered. When several calls are in flight, match each receipt to its own proposal before verifying.yousdk.md section 59.3-3
ReceiptTimeUnreadableThe receipt's issue or expiry time is missing or is not an RFC 3339 UTC instant.Do not act on the receipt. Pass it exactly as the decision API returned it; if you did, report it with the receipt.ussdk.md section 59.3-5
ReceiptExpiredYour clock is past the receipt's expiry time.Keep the verifying host's clock on UTC time, and verify a receipt as soon as it arrives: it is valid for 120 seconds. Propose the action again for a new receipt.yousdk.md section 59.3-5
ReceiptIssuedInFutureThe receipt's issue time is more than 5 seconds ahead of your clock.Synchronise the verifying host's clock with UTC time, then verify the receipt again.yousdk.md section 59.3-5
ValidityWindowTooLongThe receipt is valid for longer than the 120 seconds a receipt may be.Do not act on the receipt and do not retry. Report it with the receipt.ussdk.md section 5L-14
NonceMalformedA nonce in the receipt, or in an approval sent with it, is not b64: followed by padded base64.Do not act on the receipt and do not retry. Report it with the receipt.ussdk.md section 5WE-4
ReceiptNonceWrongSizeThe receipt's nonce is well formed but is not 128 bits long.Do not act on the receipt and do not retry. Report it with the receipt.ussdk.md section 5L-17
ApprovalsUnverifiableThe approvals sent with the receipt cannot be matched to the list the receipt signed: a list is malformed, an approval is not on it, or the verifier was given no attester registry.Give the verifier your attester registry (ZIFFER_ATTESTER_REGISTRY, or the registry argument). If you did, do not act on the receipt and report it with the receipt.you and ussdk.md section 5AB-1
ApprovalDigestsUnsortedThe receipt's list of approval digests is not in sorted order.Do not act on the receipt and do not retry. Report it with the receipt.ussdk.md section 5AB-2
ApprovalDigestsDuplicatedThe receipt's list of approval digests names one approval more than once.Do not act on the receipt and do not retry. Report it with the receipt.ussdk.md section 5AB-3
ApprovalCountMismatchThe number of approvals sent with the receipt is not the number of approvals the receipt signed.Pass the receipt and its approvals exactly as the decision API returned them. If you did, report it with the receipt.you and ussdk.md section 5AB-4
ApprovalsMissingThe receipt commits to approvals and none were sent with it.Pass the receipt with its attestations exactly as the decision API returned them. If you did, report it with the receipt.you and ussdk.md section 5INV-1-HIGH
ApprovalObjectMissingAn approval sent with the receipt carries no approval object.Do not act on the receipt and do not retry. Report it with the receipt.ussdk.md section 5AT-8
ApprovalObjectMalformedAn approval object lacks a required field, carries a field that is not defined, or holds a field of the wrong type.Do not act on the receipt and do not retry. Report it with the receipt.ussdk.md section 5AT-8b
ApprovalNonceWrongSizeAn approval's nonce is well formed but is not 128 bits long.Do not act on the receipt and do not retry. Report it with the receipt.ussdk.md section 5AT-1
ApprovalSignatureInvalidAn approval's signature does not verify against the key your attester registry holds for that approver, or your registry does not enrol that approver.Check that the approver is enrolled in your attester registry with their current key. If they are, do not act on the receipt and report it with the receipt.you and ussdk.md section 59.3-7b-i
ApprovalNotBoundToProposalAn approval sent with the receipt is not bound to the proposal you passed.Do not act on the receipt and do not retry. Report it with the receipt and the proposal.ussdk.md section 59.3-7b-ii
ApprovalContextMismatchAn approval names another policy version than the receipt, expired before the receipt was issued, or has an unreadable policy, expiry or operator field.Propose the action again so that it is approved under the current policy. If it happens again, report it with the receipt.you and ussdk.md section 59.3-7b-iii
ApprovalsDisagreeOnOperatorThe approvals sent with the receipt name different operators.Do not act on the receipt and do not retry. Report it with the receipt.ussdk.md section 59.3-7b-iii-a
ApprovalQuorumSizeMismatchAn approver consented to a number of required approvals other than the quorum your attester registry sets.Propose the action again so that it is approved under the current policy. If it happens again, report it with the receipt.you and ussdk.md section 5AT-9
ApprovalIdMismatchAn approval's transmitted identifier is not the identifier computed from its content.Do not act on the receipt and do not retry. Report it with the receipt.ussdk.md section 5Y1b
ApproverAssuranceTooLowAn approver is enrolled at an assurance level below your attester registry's floor, or at a level outside AS0 to AS2.Enrol the approver at the assurance level your registry requires, or have an approver enrolled at that level approve instead.yousdk.md section 5AT-10
QuorumNotMetFewer distinct approvers approved the action than the quorum your attester registry sets.Do not run the action. Check your registry's approvers and roles against who approved; propose the action again once enough approvers can answer.yousdk.md section 5AT-3
OperatorApprovedOwnActionThe operator who proposed the action is counted among its approvers.Do not run the action. Have approvers other than the operator approve it.yousdk.md section 5AT-2
ApproverKeyTypeMismatchAn approval claims a kind of key, a person's passkey or a machine key, that is not the kind your attester registry enrols for that approver.Do not act on the receipt and do not retry. Report it with the receipt.ussdk.md section 5WebauthnAlgMismatch
PasskeyCounterNotAdvancedA passkey or security key's signature counter did not advance, so the credential was copied or the approval was replayed.Do not act on the receipt. Treat the approver's authenticator as compromised and re-enrol it before it approves anything else.yousdk.md section 5WebauthnCounter
ApprovalMalformedA person's approval, or the key your attester registry holds for them, is not in the shape a passkey approval takes.Check the approver's entry in your attester registry against the credential they enrolled. If it matches, report it with the receipt.you and ussdk.md section 5Malformed
PasskeyAssertionTypeInvalidA person's approval is not a passkey sign-in assertion.Ask the approver to approve again from your approval page.you and ussdk.md section 5WebauthnType
PasskeyChallengeMismatchA person's approval answers a different request than this approval.Do not act on the receipt. Ask the approver to approve again from your approval page; if it happens again, report it with the receipt.you and ussdk.md section 5WebauthnChallenge
PasskeyOriginMismatchA person's approval was made on a site other than your approval page.Ask the approver to approve again from your own approval page. If they did, report it with the receipt.yousdk.md section 5WebauthnOrigin
PasskeyAuthenticatorDataInvalidA person's approval names another site, or does not show that the person was present and verified.Ask the approver to approve again and to complete the fingerprint, face or PIN step their authenticator asks for.yousdk.md section 5WebauthnAuthenticatorData
PasskeySignatureInvalidA person's approval signature does not verify against the passkey or security key your attester registry holds for them.Check that the approver's entry in your attester registry holds their current credential. If it does, do not act on the receipt and report it with the receipt.you and ussdk.md section 5WebauthnSignature
9.3-4The receipt's policy basis is not the bundle the Executor holds. Almost always the window right after a policy publish: readers pick up a new epoch on their own poll, so for up to two intervals — 30 seconds at the default 15 — one process can still be an epoch behind another. The receipt is refused rather than executed under rules the Executor cannot see.An exception to the rule above, and the only policy refusal that is one. Retry the proposal once, 30 seconds later. If it persists after your publish workflow printed active, stop and report it with the receipt: outside that window it means an Executor that never picked up a bundle, or a receipt from another deployment.yousdk.md section 5—
LEDGER_REPLAYA consumed identifier was claimed again, or an origin was rebound. Either work you already executed came back, or something replayed a receipt.Check first whether your Executor completed what it consumed: delivery is at-least-once and completing sloppily makes this alert routine. If your completions are clean, treat it as an incident and page.you and usthe Executor's alerts—
RECOMPUTATION_MISMATCHThe risk or reversibility the receipt states is not what your signed bundle says it should be. The verifier recomputes both and does not take the receipt's word.Do not act on the receipt. Report it with the receipt and your bundle's tree hash.usthe Executor's alerts—
SUITE_BELOW_FLOORThe same condition as CR-4, reached by the Executor rather than by your own verify.Confirm the floor in your bundle is the floor you meant, then report it.you and usthe Executor's alerts—
CR-4The clause under SUITE_BELOW_FLOOR: something the Executor verified is signed with a suite that does not contain every algorithm your bundle's suite floor requires.Same as SUITE_BELOW_FLOOR: confirm the floor in your bundle is the floor you meant, then report it.you and usthe Executor's alerts—
CONTEXT_STOREThe live capability check at execution time disagrees with what was decided. The world moved between the decision and the action.Check whether the operator's capability changed after the proposal was made. If nothing changed on your side, report it.youthe Executor's alerts—
AUDIT_INTEGRITYThe audit chain or one of its anchors did not hold.Write to us at once. Do not treat any receipt from the affected window as settled until we answer.usthe Executor's alerts—
ATTESTATION_INVALIDThe approval quorum does not hold: too few valid attesters, a duplicate, or an attester the bundle does not name.Check your bundle's attester list and roles against who actually approved. If they agree, page.you and usthe Executor's alerts—
HM-3An approver's key kind and the approval's declared algorithm disagree: a person's entry answering for a machine's suite, or the other way round.Do not retry. Report it with the receipt: neither side of that disagreement is yours to set.usthe Executor's alerts—
HM-4A human approval did not verify: the wrong site, no user verification, a challenge that answers something else, or a signature counter that did not advance. The last one means the credential has been copied or the approval was replayed.Ask the approver to approve again from your own approval page. If it happens twice for one person, treat their authenticator as compromised and re-enrol it before doing anything else.you and usthe Executor's alerts—
DEFERRED_RELEASESomething in the hold, the notice, or an acknowledgement did not hold for an action that was waiting to be released.Check that your notice recipients are reachable and that acknowledgements are being sent. Then report it with the action id.youthe Executor's alerts—
RECEIPT_INVALIDAnything else the Executor refuses on the receipt's own bytes. The catch-all, so the clause id in the alert is the useful part.Report it with the clause id from the alert, not just the class.usthe Executor's alerts—
CL-2The single-use claim failed: the ledger already holds this identifier, or it could not confirm the identifier is absent and the Executor failed closed.If the same receipt was delivered twice, that is expected and is not an anomaly. If it was delivered once, page: an unconfirmable ledger is our outage.you and usthe Executor's alerts—
CL-3A consumed identifier class was claimed twice: a receipt nonce, an attestation id, or an epoch mark. One receipt executes once.Confirm your Executor completes every unit of work it consumes. Report it if it does and this still fires.youthe Executor's alerts—
DS-6eThe re-drive bound for one action was reached. Re-drives are capped per action by your bundle, and the cap is there because an unbounded retry loop against a target that answers "indeterminate" is an amplification channel.Stop re-driving that action and find out what the target is actually doing. Raise the cap in your bundle only once you know.youthe Executor's alerts—
DS-6fA re-drive named an origin that is not the one the ledger pinned for that proposal, or something tried to rebind an existing proposal to a different origin.Do not retry. This is a replay control firing; page.you and usthe Executor's alerts—
TR-8The risk level recomputed from your signed bundle is not the one the receipt states. The clause under RECOMPUTATION_MISMATCH for risk.Do not act on the receipt. Report it with the receipt and your bundle's tree hash.usthe Executor's alerts—
RV-3The reversibility class recomputed from your signed bundle is not the one the receipt states. The clause under RECOMPUTATION_MISMATCH for reversibility.Do not act on the receipt. Report it with the receipt and your bundle's tree hash.usthe Executor's alerts—
9.3-9The clause under CONTEXT_STORE: the capability check at execution time did not agree with the decision.Same as CONTEXT_STORE. Check what changed in the operator's capabilities between the two moments.youthe Executor's alerts—
AU-7The audit record that must cover an action was not there, or was not covering, before the action was released. Ordering, not presence: the record comes first.Page. This is the clause under AUDIT_INTEGRITY and it is ours.usthe Executor's alerts—
DR-15An action was released and the Executor could not obtain its release receipt from us, so no receipt is readable for it yet. The release itself is recorded; nothing is lost.Report it with the locator. The request is idempotent, so we can re-issue the same receipt; until then the decision reads as decided with no receipt, and your gate must not act on it.usthe Executor's alerts—
ApiKeyUnknown401. The key presented is not one we hold. Missing, unknown, revoked and expired are one answer, because telling them apart would tell a key guesser which strings were once credentials.Check the key is being sent and is the one issued for this tenant. whoami names the tenant a working key is bound to and the date it expires; if it has expired or was rotated, issue a new one.youthe decision API—
TenantMismatch403. The body's tenant_id is not the tenant the key belongs to. The key decides the tenant and the body is refused rather than quietly rewritten, because the proposal is signed material further down.Set tenant_id to the tenant the key belongs to. whoami names it.youthe decision API—
ProposalMalformed400, or a decision's refusal_category — one name, one meaning: fix the submission. The body is not one wire proposal: over the size cap, a duplicate key at any depth, not NFC, or a member the schema does not declare. One name for all of them, so the answer cannot be used to enumerate what we accept.Compare your body against the proposal in sdk.md section 4. lint_proposal in our MCP server names the bad field on your own machine, before the call.youthe decision API—
ListQueryMalformed400. GET /v1/decisions was called with a query string that is not a legal one — a cursor that is not ours, a limit out of range, a since older than we keep. One name for every departure, and it does not say which.Read the grammar in sdk.md section 6 and send what it names. Drop every parameter, confirm the call works, then add them back one at a time.youthe decision API—
DecisionUnknown404. No decision with that locator exists for your tenant. Another tenant's locator gets the same answer, because existence is tenant scoped.Check the locator is one propose returned under this same key. If it is, report it with the locator: a decision we handed you and cannot find again is ours.you and usthe decision API—
AdmissionUnavailable502. The admission path yielded no verdict — the door could not be reached, or refused with an empty body. Nothing decided your proposal, and a door refusal is spelled this way rather than turned into a refusal name the gateway invented.Do not read it as a decision. The submission is recorded and GET will answer pending about it for ever, because nothing will ever decide it. Report it, then send the proposal again: a resubmission is a fresh admission and nothing can have executed, since no receipt was issued.usthe decision API—
GatewayUnavailable503. The process serving the API could not answer at all — its store, its clock. Never one of the named refusals: an outage spelled ApiKeyUnknown would turn every caller away as unauthenticated.Nothing decided anything, so a short retry is reasonable here where it is not for a refusal. If it persists past a few minutes it is a severity 2 under section 2.usthe decision API—
QueueFull503 with Retry-After. The approval queue for that risk is at its configured depth, so the submission was not taken at all: no row was written, no locator came back, and nothing was decided.Wait the seconds Retry-After names and send the same bytes again — one of the few answers here that you do resend, because nothing was decided and nothing was written. If it keeps happening the queue is not being cleared — check that your approvers are answering — and report it.you and usthe decision API—
FeedbackMalformed400. The POST /v1/feedback body is not {tool, question, context?}. One name for every departure, as ProposalMalformed is.Send those three members and nothing else; context is the only optional one.youthe decision API—
FeedbackTooLarge413. The feedback body is over the route's 4 KiB cap.Shorten it to the part that matters rather than a whole log. hello@ziffer.io takes the long version.youthe decision API—
RateLimited429 with Retry-After. This key's allowance on the decision routes (propose, read a decision, list, whoami) is spent: 60 requests at once, then 10 a second. Nothing is unavailable and nothing was decided or written — the request was refused before any of that.Wait the seconds Retry-After names, then send the same request again; the SDKs already do this. If an AI agent hits it in normal use it is looping: find the loop rather than spreading the load over more keys.youthe decision API—
FeedbackRateLimited429 with Retry-After. This key's feedback allowance is spent. Nothing is unavailable — the route is working and this key has had its share, which is why it is not a 503.Wait the seconds Retry-After names. Do not let an SDK retry policy ride it out: that is exactly the traffic the limit refused.youthe decision API—
FeedbackUnavailable503. The feedback route has nowhere to write, or the write failed. Never a refusal of your message — the body was fine.Do not edit the message and resend it; it was not the problem. hello@ziffer.io reaches the same people.usthe decision API—
8.4-1The proposal is not the shape the wire schema declares. Step 1 of the twelve, checked at the engine after the API has already checked the same thing on the way in.Same fix as ProposalMalformed: compare the body against sdk.md section 4. Reaching this clause rather than a 400 means the two checks disagreed, so report it with the body.youthe Policy Engine—
PR-1The proposal parsed, and its envelope is not the one the wire format defines: a member missing, or one the envelope does not carry.Compare the envelope against sdk.md section 4, member for member. lint_proposal names the departure before you call.youthe Policy Engine—
WE-6The proposal did not arrive in the canonical encoding the specification names. The engine re-encodes what arrived and compares byte for byte, so a body that was re-serialised in transit is refused even when every field in it is right.If you build the body yourself, send the canonical bytes and check that no proxy, logger or framework re-encodes them on the way out. If you send it through one of our SDKs, which canonicalise for you, report it with the bytes.you and usthe Policy Engine—
B-6Either your signed bundle registers no adapter for that schema_id, or the fidelity class the submission stamps is not the one the bundle registers for it. An unregistered schema_id would buy a fidelity class nobody signed.Register the schema_id in your bundle's adapter table and publish, or send one that is already registered. policy-by-example.md has the entry.youthe Policy Engine—
8.4-3Your own signed rules refused this action, or no rule grades it at all — an action nothing classifies is refused rather than graded, because unknown is never LOW. This is the ordinary policy no and the clause you will see most.Read the rule that decided it: explain_policy in our MCP server, or policy-by-example.md. If the action should be allowed, change the rule and publish. Nothing about this one is ours to change.youthe Policy Engine—
8.4-4The operator does not hold the capability this action needs over every target it names. One target short is a refusal, not a partial allow.Check the operator against every target in the proposal, not just the first. If they should hold it, that is an access change on your side and the file it lands in is ours to place.you and usthe Policy Engine—
8.4-5The tenant the proposal names could not be resolved to a signed policy: no tenant named, a tenant_id that is not a DNS label, no bundle placed for it, a subtree that could not be read, a bundle or key that did not verify, or a bundle whose manifest governs somebody else.Check the tenant_id first, spelling and case. If it is right and this installation serves you, the placement is ours: report it with the tenant_id and the time.you and usthe Policy Engine—
8.4-6Segregation of duties. The operator making the proposal is an enrolled approver in the same bundle, so it was refused before anyone was asked to approve it.Propose under an operator identity that is not in your attester registry. If one person genuinely has to do both, that is a change to the registry and it is yours to make deliberately.youthe Policy Engine—
8.4-12The pool of approvers eligible for this action is smaller than your bundle's quorum_k, so the quorum could never be reached and the proposal is refused rather than parked.Count the eligible approvers, not the enrolled ones: the operator does not count for their own proposal. Enrol another in the registry approvers.md describes, or lower quorum_k.youthe Policy Engine—
CP-2The capability store answered, and the answer is no: the operator, the resource or the capability is not one your entitlements name, or the snapshot was too stale to use. Which of them it was stays in our log on purpose — a reason on the wire is an oracle over who holds what.If the operator should hold that capability, send us the access change. If they should not, this is the control working. Ask us for the reason code with the time and the operator id.you and usthe Policy Engine—
DP-37A store this decision needed could not answer at all — the capability store, the receipt store, or the table of approvals waiting to be answered. A store that cannot answer is never read as a store that answered no.Do not retry and do not read it as a policy refusal. Report it with the time: which of the three it was is in our log and deliberately not in the clause.usthe Policy Engine—
PB-1The signed policy bundle did not read: none is active for that tenant, or the one that is did not parse as policy.If you have just published, check that your workflow printed active — a bundle that never activated leaves the engine nothing to grade under, and policy-ci.md section 7 is the publisher's side. Otherwise report it with the tenant_id and the time.you and usthe Policy Engine—
DP-58The signing substrate refused or could not be reached, so no receipt could be signed. It propagates a refusal rather than being softened into a hold: an approved proposal still needs a receipt.Do not retry the proposal. Report it with the time; the key custody is ours.usthe Policy Engine—
DP-59The approval could not be presented: no presentation service is configured for a kind of approver your registry enrols, or the render failed. Nobody could be shown what they were being asked to approve.Report it, and say which kinds of approver your registry enrols. If you have just added a person's passkey where there were only machines, name that: it is the usual cause.usthe Policy Engine—
DP-90A receipt was signed and the engine could not hand it on to your Executor. The decision is refused rather than left as a receipt nobody holds.Report it with the time and the locator. Nothing executed: a receipt that never reached an Executor cannot be acted on.usthe Policy Engine—
AT-4The decision was re-run once the quorum was in, and the rules no longer support it. Between the proposal and the last approval, the policy, the entitlements or the capability data moved.Expected right after a policy publish or an access change. Propose again under the settled policy. If it repeats with nothing changing on your side, report it with the locator.youthe Policy Engine—
AT-5Nothing is pending under that proposal hash, or the approval window has closed. The window is yours, out of your bundle's limits.json.Have the operator propose again, and answer it inside the window. If approvals are routinely late, raise attestation_window_seconds in your bundle rather than asking anyone to answer faster.youthe Policy Engine—
AB-1The submitted approval is not the four-member entry the specification digests, so there was nothing well formed to verify.Report it with the time. If your approvers answer from the page we serve, nothing about that entry is yours to build.usthe Policy Engine—
AT-1The approval binds a different proposal than the one it was submitted for. It is consent to something else.Do not resubmit it. Check whether two approvals were in flight and got crossed; if only one was, report it with both proposal hashes — an approval bound to the wrong proposal is ours.you and usthe Policy Engine—
9.3-7b-iThe approver is not enrolled in your signed attester registry. Somebody approved who your bundle does not name.Enrol them in the registry and publish, or have someone already enrolled approve instead. approvers.md is that registry's own page.youthe Policy Engine—
AT-2One of two, and they are one rule: the operator tried to approve their own proposal, or an approver who has already signed it signed again. One approver, one vote.Have a different enrolled approver answer. If nobody else is enrolled, 8.4-12 is the refusal the next proposal gets — enrol another.youthe Policy Engine—
AT-9The number of approvals the approver was shown is not your bundle's quorum_k, so the consent given was for a different quorum than the one in force. Almost always a bundle published between the moment the request was rendered and the moment it was answered.Ask the approver to reload the request and approve again. If it repeats with no publish in between, report it with the time.you and usthe Policy Engine—
AQ-3The engine-side name for the condition QueueFull answers with: the approval queue for that risk is at its configured depth, so the item was refused rather than queued. A bound that admits the item anyway measures a backlog instead of preventing one.Clear the queue — the approvals in it are waiting on your approvers. The depth is set on our side, so if the queue is the right size and is still full, report it.you and usthe Policy Engine—
DR-19This operator proposed more approval-needing (floor-HIGH) actions in the last hour than your signed limits.json allows (floor_high_proposals_per_operator_per_hour, 60 when you set none). A flood of approval requests is an attack on your approvers' attention, so the engine refuses the excess and raises a critical alert of class FLOOD_CAP. The API serves it as RateBounded.Retry later, when the hour has room. If this operator legitimately needs more for a window, two of your approvers can declare break-glass (DR-18), which raises the cap for that operator and shows a red banner in the console for its whole window; otherwise raise the limit and publish.youthe Policy Engine—
PolicyRefusedA rule refused the action: your signed policy, the scope of the key, or something on our side that answered no. Which rule is in the console and your audit chain, not in the answer.Never retry the same proposal. Look the decision up in the console to see the clause, and change the rule or the proposal if it should have been allowed.youthe refusal category—
PolicyBasisMovedThe policy basis moved under the decision: the window right after a policy publish, when one reader still holds the previous epoch.Retry once, after the activation window (30 seconds at the default poll). If it persists after your publish printed active, report it with the locator.youthe refusal category—
RateBoundedA rate bound refused it: too many approval-needing proposals from this operator in the hour, too many decisions in the minute, or a full approval queue.Retry later. Nothing about the proposal is wrong.youthe refusal category—

5. Two things that look like refusals and are not

A dependency that could not answer. If the audit service, the Context Store or the ledger cannot be reached, no verdict is reached, nothing is reported, and the work stays queued. The next poll asks again. That is not in the table above because there is no refusal name to act on. If it persists past a few minutes, it is a severity 2.

The same receipt arriving twice. Delivery is at-least-once and execution is not. A poll marks nothing, so work comes back until you complete it. Being handed the same receipt twice is normal. Executing it twice is what CL-2 and LEDGER_REPLAY exist to stop.

6. How we answer

  • Ask us about the state of our deployment. Write to hello@ziffer.io or use the chat.
  • The times in section 2 are commitments about answering, not about availability, and nothing here is a service credit.
  • We answer in working hours, as section 2 defines them.
  • One address, not a named individual. hello@ziffer.io reaches whoever is on the rota, so an answer never waits on one person being available.

On this page