Overview
Callers meet three kinds of “no”. A rule refusal means a rung of the Right Path, or a named person, said no. A platform refusal means something on our side failed, so nothing was allowed. A contract error means the request itself did not meet the contract and never reached the Right Path. Each has a different envelope, a different effect on the chain and the meter, and a different right response.
Three kinds of no
A rung said no — STOP, identity, write door, a pre-gate, the versioned rules, the guardrail — or a person did, or a hold expired.
Typed code · proof record written · 0.4 governed action
A dependency failed. elan1 fails closed: if elan1 is down, allows stop; a dependency outage produces a platform refusal, never an allow.
Typed code · recorded where the chain is reachable · never billed
The request did not meet the contract: its shape, its token, its tenant, or a version pin such as “latest” on a production key.
4xx · no Right Path · no record · no meter
| Question | Rule refusal | Platform refusal | Contract error |
|---|---|---|---|
| Who decided | Your rules, or a named person | Nobody: elan1 could not decide | Nobody: the request was malformed |
| Reached the Right Path | Yes | Yes, until the failure | No |
| Proof record | Written | Written where the chain writer is reachable | None |
| Meter | 0.4 of a governed action | Never billed | Nothing |
| Final for its write_id | Yes | Yes | No — nothing was recorded |
| What to do | Show a person; fix the cause; send a new intent | Do not write; try later as a new intent | Fix the request; resend |
The refusal envelope
A refusal comes back in the same envelope as every other outcome, with a refusal block added. The block is built for two readers: your code, which branches on class, and a person, who reads the plain words.
HTTP/1.1 200 OK
{
"outcome": "refused",
"rung": 3,
"write_id": "wr_specimen_7c3",
"refusal": {
"code": "G4_GST_RATE_MISMATCH",
"class": "rule",
"reason": "The invoice charges 12% GST; the rate table says 18% for HSN 8483.",
"remedy": "Correct the rate on the invoice, or update the rate table under dual control.",
"contact": "usr_specimen_controller"
},
"record": { "id": "00000000-0000-0000-0000-000000000000", "version": 0 },
"proof": { "seal": "elan1.proof.seal.v6", "seq": 1043, "digest": "sha256:4be190c7…" },
"meter": { "governed_actions": 0.4, "inr": 0.64 }
}
# Metered at 0.4 of a governed action: ₹0.64 at L1, beyond the allowance.HTTP/1.1 503 Service Unavailable
{
"outcome": "refused",
"write_id": "wr_specimen_7c4",
"refusal": {
"code": "<platform code from the frozen register>",
"class": "platform",
"reason": "A dependency of the gate check did not answer. Nothing was allowed."
},
"meter": { "governed_actions": 0, "inr": 0 }
}
# Ours, not a rule. Never billed. Never an allow.| Field | Meaning | Proof record |
|---|---|---|
outcome | Always refused. | — |
rung | The rung where the intent stopped: 0b STOP to 7 ACT. | Bound to the record |
refusal.code | A typed code from the closed register. An opaque string: compare it, never parse it. | Bound to the record |
refusal.class | rule or platform. Branch on this. | — |
refusal.reason | What happened, in plain words, from the declared policy — never from a model. | — |
refusal.remedy | What to fix. | — |
refusal.contact | Who to ask. | — |
record | A zeroed record_id and record_version 0, because nothing changed. | F12 · F13 |
proof | The seal, sequence and digest of the refusal's own proof record. | F02 · digest |
meter | 0.4 of a governed action, held there by a database check. Within the allowance, it uses 0.4 of an included action; beyond it, it costs 40% of your level's rate, for example ₹0.64 at L1. | — |
Field names are illustrative; the exact names are in the KernelPort v1 reference. Conformance test C8 checks the shape: a register code, the rung, a zeroed record_id, record_version 0 and a meter quantity of 0.4.
Refusal codes
Every refusal code comes from one closed register. A code that is not in the register cannot be emitted, so a caller can always tell a known refusal from anything else. The register, its codes and the rung each code belongs to are part of KernelPort v1 and are shared with customers; this site uses a handful of illustrative codes, always marked so.
| Code (illustrative) | Class | Rung | Where you will see it |
|---|---|---|---|
G4_GST_RATE_MISMATCH | rule | 3 PRE-GATES | The GST gate (G4) finds an invoice rate that does not match the rate table. |
GATE.PRECONDITION_FAILED | rule | 3 PRE-GATES | The precondition gate (G9) fails against your data. |
APPROVER_REFUSED | rule | 6 AUTONOMY | The named approver says no. |
APPROVAL_EXPIRED | rule | 6 AUTONOMY | Nobody answers before the hold expires. Expiry is a refusal. |
Rules for callers
- Treat a code as an opaque, case-sensitive string. The two styles above — underscored and dotted — are both illustrative; do not depend on either.
- Branch on
outcomeandrefusal.class. Use the code for display, logging and support, not for control flow you cannot change later. - Never invent a code, and never map an unknown code to an allow. An unknown code is still a refusal.
Where refusals come from
Any rung can refuse. The rung tells a person where to look; the code tells them exactly what failed.
| Rung | Refuses when | Model involved | Code |
|---|---|---|---|
| 0b STOP | Writes are stopped for this tenant. | No | From the frozen register |
| 1 IDENTITY | The asker cannot be identified, or is acting outside its scope. | No | From the frozen register |
| 2 WRITE DOOR | The write type is not on the declaration's list of allowed write types. | No | From the frozen register |
| 3 PRE-GATES | One of the nine pre-gates, G1–G9, fails against your own data. Six can never be skipped. | No | G4_GST_RATE_MISMATCH, GATE.PRECONDITION_FAILED (illustrative) |
| 4 evaluate() | The versioned rules say no. | No | From the frozen register |
| 5 GUARDRAIL | The blast radius is beyond the declared limit. | No | From the frozen register |
| 6 AUTONOMY | The named approver refuses, or the hold expires. | No | APPROVER_REFUSED, APPROVAL_EXPIRED (illustrative) |
| 7 ACT | In Mode A, the single writer cannot make the change as approved. | No | From the frozen register |
Rungs 8 PROOF and 9 METERED do not refuse a write on its merits. If the chain writer cannot seal, nothing is allowed and the intent ends as a platform refusal. Rungs 3 to 8 cannot reach a model client: a model may produce a write intent, never an allow or a refusal reason.
Handling each case
| You receive | Your system should | Show a person | Retry |
|---|---|---|---|
| Committed | Store proof.seq and proof.digest with the change. In Mode B, make the write now. | Nothing, or a receipt | — |
| Escalated | Not write. Wait for the obligation to resolve by webhook, or read it. | Who decides, and by when | No. Wait. |
| Refused · rule | Not write. Keep the code, rung and proof sequence with the intent. | The code, in plain words; what to fix; who to ask | Not with the same write_id. Fix the cause, then send a new intent. |
| Refused · platform | Not write. Fail closed. | That nothing was written, and why | Later, as a new intent with a new write_id. |
| Contract error | Fix the request. Nothing was recorded. | Usually nobody: this is a defect in the caller | After fixing it. |
| No answer | Not write. Treat silence as no. | That the change is waiting | Yes, with the same write_id; you get the original outcome. |
A handler, end to end
The same logic as code. It persists the write_id before sending, retries only with the same write_id, fails closed on silence, and writes only on committed.
// Illustrative. Exact field names are in the KernelPort v1 reference.
type Outcome = "committed" | "refused" | "escalated";
async function propose(intent: WriteIntent): Promise<void> {
await store.saveIntent(intent); // persist write_id first
let res: Response;
try {
res = await sendWithRetry(intent); // same write_id on every retry
} catch {
return hold(intent, "elan1 did not answer. Nothing was written."); // fail closed
}
if (res.status >= 400 && res.status < 500) {
const { error } = await res.json();
return fixRequest(intent, error); // contract error: no record exists
}
const body = await res.json();
switch (body.outcome as Outcome) {
case "committed":
await writer.apply(intent, body.proof); // Mode B: your system writes now
return;
case "escalated":
return waitFor(body.obligation); // webhook resolves it; expiry is a refusal
case "refused":
if (body.refusal.class === "platform") {
return hold(intent, body.refusal.reason); // ours; never billed; try later as a new intent
}
return showRefusal(intent, body.refusal, body.rung); // a rule or a person said no; 0.4 of a governed action
}
}HTTP/1.1 400 Bad Request
{
"error": {
"class": "contract",
"reason": "write_type 'journal_entry.repost' is not a write type this API knows.",
"field": "write_type"
}
}
# The request never reached the Right Path. No record.Showing a refusal to a person
A refusal is only useful if the person who must act on it can read it. elan1 Work’s refusal card is the pattern to follow: a typed code in plain words, what to fix, and who to ask.
- Heading
- REFUSED, never an error colour alone: always the word.
- Code
- The typed code in a monospace face, exactly as received.
- Where
- The rung where it stopped, for example 3 PRE-GATES.
- Why
- refusal.reason in plain words.
- Next
- refusal.remedy and refusal.contact.
- Evidence
- “Recorded · 0.4 governed action”, with the proof sequence so anyone can find the record.
Testing your handling
Four conformance tests from the C1–C14 suite exercise exactly this behaviour. A third-party agent makes no claim to run on elan1 Govern until all fourteen pass.
| Test | Checks |
|---|---|
| C5 Fail-closed matrix | Every dependency outage produces a platform refusal, never an allow. |
| C7 Idempotency | Replaying an intent returns the original outcome, sequence and digest, and no second meter event. |
| C8 Refusal shape | A refusal returns a register code, the rung, a zeroed record_id, record_version 0 and a meter quantity of 0.4. |
| C11 Approval separation | No approval where the approver is the requester, and none by an agent token. |
See Conformance for the whole suite and how to run it.
What the customer reference covers
- The refusal register: every code, its class and its rung. Codes on this site are illustrative.
- The names of the
refusalfields, includingremedyandcontact. - The exact HTTP status codes for contract errors and platform refusals.
- How a reused write_id with a different payload is answered. Design as if it is refused.
These are fixed in KernelPort v1 and the record shape, and are listed in the full reference customers receive with their workspace.