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. Written 2026-09-07 (ACP-298). Every refusal in the table
below is derived from docs/onboarding/sdk.md section 5 and docs/onboarding/executor.md
section 6 by tools/check-support-doc.py, which fails if a refusal is documented and has no row
here, or if a row here names a refusal neither document publishes. If this file disagrees with
either of those, they are right and this one is stale.
1. The two ways to reach us
| Address | Use it for | Answered | |
|---|---|---|---|
| Write | hello@ziffer.io | everything. Questions, refusals you cannot explain, a leg that looks slow, a document that is wrong. | in the times in section 2 |
| Chat | the chat button on ziffer.io | the 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.
There is no customer status page yet, and no automatic notification when a leg of our deployment is degraded. Both are named as not-yet in section 6 rather than implied. Until they exist, ask.
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.
| Severity | What qualifies | First response | Updates until it closes | Paged on our side |
|---|---|---|---|---|
| 1 | No 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 hours | hourly | yes, immediately |
| 2 | Decisions are reached but a path is unusable: a deferred release never releases, one door refuses everything, receipts arrive but cannot be delivered. | 4 working hours | every 4 working hours | yes, in working hours |
| 3 | A refusal you cannot explain, or behaviour that disagrees with a document. Nothing is down. | 1 working day | daily | no |
| 4 | A question, a documentation defect, a feature request. | 3 working days | on change | no |
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 class | Our severity | Why |
|---|---|---|
AUDIT_INTEGRITY, LEDGER_REPLAY, ATTESTATION_INVALID | 1 | each is a control reporting that something it exists to prevent was attempted |
SIGNING_SUBSTRATE, BUNDLE_INVALID | 1 | nothing can be signed or nothing can be loaded, so nothing is being decided |
RECOMPUTATION_MISMATCH, CONTEXT_STORE, DEFERRED_RELEASE, ATTESTATION_QUEUE_DEPTH | 2 | one path is unusable and the others are not |
SUITE_BELOW_FLOOR, RECEIPT_INVALID | 3 | in 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 eleven 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.
- Your tenant. The label, exactly as it appears in your bundle's manifest.
- The attestation id, if the action got as far as one.
- The receipt, base64 or as bytes, or the proposal if no receipt came back.
- 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.
- 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.
A refusal is not a network error, and none of these should be retried. Every one of them is deterministic on the same input: asking again gets the same answer one round trip later. A dependency that could not answer is a different thing entirely, and is not in this table: there the Executor reaches no verdict, reports nothing, and the work stays queued for the next poll.
| refusal | documented in | what it means | who fixes it | what to do now |
|---|---|---|---|---|
AB-0 | sdk.md section 5 | The bytes are not a version-3 receipt. Almost always the wrong value was passed to verify. | you | Check what you handed verify. If it is exactly what our decision API returned, unchanged, report it with the bytes. |
AB-6 | sdk.md section 5 | The signed body is larger than the cap. A receipt we issued should never be. | us | Do not retry. Report it with the receipt; it is ours. |
AT-8a | sdk.md section 5 | The bytes are not UTF-8 JSON at all. Usually a transport or an encoding step between us and verify. | you | Compare the bytes on the wire with the bytes you pass. Look for a re-encoding, a truncation, or a logging step in between. |
CR-1 | sdk.md section 5 | The receipt declares a signature suite this build does not know, declares none, or your ZIFFER_SUITE_FLOOR is not a suite this build knows. An unknown suite always fails closed. | you and us | Check the spelling of ZIFFER_SUITE_FLOOR first, then your SDK version. If the SDK is current and the receipt names a suite it does not know, report it. |
CR-4 | sdk.md section 5 | The receipt's suite does not contain every primitive your floor requires. The control worked: the receipt is weaker than you asked for. | you and us | Confirm the floor you set is the floor you meant. If it is, report it: a receipt below your floor is ours to explain. |
9.3-1 | sdk.md section 5 | A signature primitive did not verify. The composition is conjunctive, so every primitive must pass and one did not. | you and us | Stop. Do not retry and do not act on the receipt. Confirm you are verifying against the bundle you activated, then report it with the receipt. |
9.3-2 | sdk.md section 5 | The decision value is not one this domain defines. | us | Do not retry. Report it with the receipt. |
9.3-3 | sdk.md section 5 | The receipt is not bound to the proposal you passed. Usually two calls in flight and the wrong pair compared. | you | Match each receipt to the proposal it answered before verifying. If they are the pair we returned together, report it. |
9.3-5 | sdk.md section 5 | Your clock says the receipt has expired. | you | Check the verifying host's clock against UTC. Clock drift is the common cause; a genuinely late receipt is the other. |
L-14 | sdk.md section 5 | The receipt's validity window is longer than the specification permits, so it was refused before its expiry was consulted. | us | Do not retry. Report it with the receipt; the window is set on our side. |
WE-4 | sdk.md section 5 | The receipt's nonce is not a well-formed 128-bit value. | us | Do not retry. Report it with the receipt; we mint the nonce. |
L-17 | sdk.md section 5 | The nonce is the right shape but not the required length. Same cause as WE-4 and the same owner. | us | Do not retry. Report it with the receipt. |
9.3-4 | sdk.md section 5 | The 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. | you | The exception to the "never retry a refusal" rule above. 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. |
LEDGER_REPLAY | executor.md section 6 | A consumed identifier was claimed again, or an origin was rebound. Either work you already executed came back, or something replayed a receipt. | you and us | 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. |
RECOMPUTATION_MISMATCH | executor.md section 6 | The 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. | us | Do not act on the receipt. Report it with the receipt and your bundle's tree hash. |
SUITE_BELOW_FLOOR | executor.md section 6 | The same condition as CR-4, reached by the Executor rather than by your own verify. | you and us | Confirm the floor in your bundle is the floor you meant, then report it. |
CONTEXT_STORE | executor.md section 6 | The live capability check at execution time disagrees with what was decided. The world moved between the decision and the action. | you | Check whether the operator's capability changed after the proposal was made. If nothing changed on your side, report it. |
AUDIT_INTEGRITY | executor.md section 6 | The audit chain or one of its anchors did not hold. | us | Write to us at once. Do not treat any receipt from the affected window as settled until we answer. |
ATTESTATION_INVALID | executor.md section 6 | The approval quorum does not hold: too few valid attesters, a duplicate, or an attester the bundle does not name. | you and us | Check your bundle's attester list and roles against who actually approved. If they agree, page. |
HM-3 | executor.md section 6 | An 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. | us | Do not retry. Report it with the receipt: neither side of that disagreement is yours to set. |
HM-4 | executor.md section 6 | A 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. | you and us | 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. |
DEFERRED_RELEASE | executor.md section 6 | Something in the hold, the notice, or an acknowledgement did not hold for an action that was waiting to be released. | you | Check that your notice recipients are reachable and that acknowledgements are being sent. Then report it with the action id. |
RECEIPT_INVALID | executor.md section 6 | Anything else the Executor refuses on the receipt's own bytes. The catch-all, so the clause id in the alert is the useful part. | us | Report it with the clause id from the alert, not just the class. |
CL-2 | executor.md section 6 | The single-use claim failed: the ledger already holds this identifier, or it could not confirm the identifier is absent and the Executor failed closed. | you and us | 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. |
CL-3 | executor.md section 6 | A consumed identifier class was claimed twice: a receipt nonce, an attestation id, or an epoch mark. One receipt executes once. | you | Confirm your Executor completes every unit of work it consumes. Report it if it does and this still fires. |
DS-6e | executor.md section 6 | The 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. | you | Stop re-driving that action and find out what the target is actually doing. Raise the cap in your bundle only once you know. |
DS-6f | executor.md section 6 | A 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. | you and us | Do not retry. This is a replay control firing; page. |
TR-8 | executor.md section 6 | The risk level recomputed from your signed bundle is not the one the receipt states. The clause under RECOMPUTATION_MISMATCH for risk. | us | Do not act on the receipt. Report it with the receipt and your bundle's tree hash. |
RV-3 | executor.md section 6 | The reversibility class recomputed from your signed bundle is not the one the receipt states. The clause under RECOMPUTATION_MISMATCH for reversibility. | us | Do not act on the receipt. Report it with the receipt and your bundle's tree hash. |
9.3-9 | executor.md section 6 | The clause under CONTEXT_STORE: the capability check at execution time did not agree with the decision. | you | Same as CONTEXT_STORE. Check what changed in the operator's capabilities between the two moments. |
AU-7 | executor.md section 6 | The 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. | us | Page. This is the clause under AUDIT_INTEGRITY and it is ours. |
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. What this does not give you yet
Stated rather than implied, because each is something an admin would reasonably assume exists.
- No customer status page.
deploy/bin/status.shanswers "is it serving, and is every reader on the same bundle" for an operator with access to the deployment, and since ACP-301 it answers the second question against a real deployment and not only against a local stack: every bundle reader serves a read-only status route that names the customers it has activated, and the command asks each of them under one operator identity. It has a--jsonmode written for a status page that does not exist yet. Until it does, ask. - No automatic notice from us when a leg is degraded. You find out by asking, or by a refusal.
- No credit or refund schedule. The response times in section 2 are commitments about answering, not about availability, and nothing here is a service credit.
- Nothing is answered outside working hours today. A 24-hour channel is not part of this offer yet; when it is, this page will say so and name it.
- No named individual.
hello@ziffer.ioreaches whoever is on the rota, which is deliberate: a named person is an availability risk, not a service.
Synced from ziffer docs/onboarding/support.md at 32bc4db; edit the source, never this page.