Overview
KernelPort is the only door into the kernel: one write function behind six calls. A model may produce a write intent. It may never produce an allow. Every allow, refusal and hold comes from elan1 Govern climbing the Right Path from rung 0b STOP to rung 9 METERED, and every outcome writes a proof record.
| Call | Purpose | Who may call | Writes a proof record |
|---|---|---|---|
define | Registers a declaration version and the write types it allows; returns a content-addressed digest. | Control-plane token | Yes. Registering is itself a governed write, under dual control. |
assert | Sends a write intent up the Right Path and answers committed, refused or escalated, synchronously. | Tenant token, act scope | Yes, for every outcome. |
seal | Writes the record to the tenant's chain under Seal v6. At L1 it returns a hash-chain receipt. | The kernel only | It is the writer. |
verify | Recomputes preimage and digest octet for octet and walks the chain. | Read-only token | No. It reads. |
scope | Resolves blast radius and autonomy so a surface can show them. Advisory only. | Read-only token | No. It reads. |
refuse | Emits a typed refusal from a closed register: an approver's no, or a caller withdrawing an intent. | Tenant token, act scope | Yes. Metered at 0.4 of a governed action. |
Three ways in
| Transport | Mode | What elan1 may say |
|---|---|---|
| In-process SDK | Mode A · Enforced | Enforced. Govern holds the write credential; no write reaches the system of record without a gate decision and a proof record. |
| MCP server · 34 tools | Mode B · Attested advisory | Evaluated against your rules and recorded. Attested advisory, never blocked. |
| HTTP API · /v1 | Mode B · Attested advisory | Evaluated against your rules and recorded. Attested advisory, never blocked. |
The calls, their meaning and their outcomes are the same on every transport. Only the mode changes, and with it the sentence elan1 may use. A Mode B integration can move to Mode A where the credential can move: after 30 days of recorded traffic and a review.
Conventions on this page
- Shape, not schema. Requests and responses are shown as HTTP and JSON. The SDK and the MCP server carry the same fields. Call names, arguments and response shapes are fixed in KernelPort v1 and the record shape; the exact schemas are in the customer reference.
- Specimens. Every identifier containing
specimen, every digest and every amount is invented. Digests are shortened with an ellipsis for reading. Illustrative specimen. Not customer data. - Frozen identifiers.
kernelport/v1,elan1.proof.seal.v6andaction-receipt/1are the names this reference is written against. - Refusal codes. Codes on this page are illustrative and marked so. Real codes come from the frozen refusal register in KernelPort v1. See Errors and refusals.
- Fields. F01–F16 refer to the sixteen Seal v6 fields of the proof record.
Try a call
Six calls, one door. Pick a call, edit its request and send it. The outcome envelope comes back from a mock of the kernel that runs in this page: no key, no network call, nothing recorded. Try sending an assert that carries "approval": "granted".
Authorization: Bearer <tenant token, act scope> X-Tenant: tnt_specimen_01 Idempotency-Key: wr_specimen_7c2
Valid JSON
Committed at rung 9 · seq 1042 · 1 governed action
HTTP/1.1 200 OK
{
"outcome": "committed",
"rung": 9,
"record": { "id": "je_specimen_5531", "version": 2 },
"proof": {
"seal": "elan1.proof.seal.v6",
"seq": 1042,
"digest": "sha256:f2423aa3…5aca2c18",
"prev_digest": "sha256:8e37839e…0ffdeac0"
},
"meter": { "governed_actions": 1 }
}| Field | What it tells you |
|---|---|
outcome | Always one of three: committed, refused or escalated. Branch on this first. |
rung | Where on the Right Path the no came from, from rung 0b STOP to rung 9 METERED. Rung 6 is a named person or an expired hold. |
refusal.code | A typed code from the closed register in KernelPort v1. Show it to a person; do not hard-code the codes on this page, which are illustrative. |
refusal.class | rule: a rung or a person said no. platform: something on our side failed, so nothing was allowed; never billed. |
refusal.reason | One plain sentence for the person who reads the refusal. |
record.id | Zeroed: 00000000-0000-0000-0000-000000000000. Nothing changed in your system of record. |
record.version | 0, for the same reason. |
proof | The seq and digest of the proof record the refusal wrote. A refusal is recorded like any other outcome and chains to the record before it. |
meter.inr | The rupee charge for this event. A refusal is metered at 0.4 of a governed action, and a database check holds it there. Beyond the allowance, that is 40% of your level's rate, for example ₹0.64 at L1. Within the allowance, it is 0, because the refusal uses 0.4 of an included action instead. |
Mocked in your browser. Nothing was sent. Illustrative specimen. Real codes come from the frozen refusal register.
Call 1 of 6
define
define(declaration, version, actor) → escalated | committed | refused, with a declaration digest
Registers a version of a declaration — a process’s rules in seven blocks: identity, shape, permissions, write_types, gates, proof_binding and channels — and the write types it allows. It returns a content-addressed digest of that version. Later records name the declaration and the version they were checked against.
- Who may call
- A control-plane token. A control-plane token cannot read any record.
- Rungs
- All of them. Registering a declaration version is itself a governed write, under dual control, so it climbs the Right Path and waits at rung 6 for a second named person.
- Fields it governs
- F06
declaration_idand F07declaration_versionon every later record; the write types it lists are what F05write_typemay hold. - Record
- The registration writes its own proof record.
- Versioning
- A version is never edited. Change the rules and the version moves; old records still point at the rules that applied to them.
POST /v1/define
Authorization: Bearer <control-plane token>
Idempotency-Key: wr_specimen_d08
{
"kernelport": "v1",
"call": "define",
"write_id": "wr_specimen_d08",
"declaration": {
"id": "decl_month_end_close",
"version": 8,
"supersedes": 7,
"blocks": {
"identity": { "...": "who owns this process" },
"shape": { "...": "the records it may touch" },
"permissions": { "...": "who may propose and who may approve" },
"write_types": ["journal_entry.post", "journal_entry.reverse"],
"gates": { "...": "pre-gate settings and thresholds" },
"proof_binding": { "...": "what the record binds" },
"channels": { "...": "how requests may arrive" }
}
},
"actor": { "id": "usr_specimen_controller", "kind": "human" }
}HTTP/1.1 200 OK
{
"outcome": "escalated",
"rung": 6,
"reason": "Registering a declaration version is a governed write under dual control. A second named person decides.",
"obligation": "OB-specimen-0419",
"declaration": {
"id": "decl_month_end_close",
"version": 8,
"digest": "sha256:7a1d0c55…f3b2c94e"
},
"proof": { "seq": 1044, "digest": "sha256:0d6e2a91…5c7b18aa" }
}Errors and refusals
| Situation | What comes back | Record |
|---|---|---|
| The token is not a control-plane token | A contract error: the request is rejected before the Right Path. | None |
| The version already exists, or skips a number | A contract error naming the expected version. | None |
| A required block is missing or malformed | A contract error naming the block. | None |
| The second person says no, or the hold expires | Refused with a typed code, for example APPROVER_REFUSED or APPROVAL_EXPIRED (illustrative). | Written · 0.4 governed action |
Call 2 of 6
assert
assert(write_intent) → committed | refused | escalated, with seq and digest
The central call. It sends one typed write intent up the whole Right Path, in order, and answers synchronously with one of three outcomes: committed, refused, or escalated to a named person. The answer always carries the proof sequence and digest, because every outcome writes a record.
- Who may call
- A tenant token with act scope: an agent, the elan1 Decide router, elan1 Work, or a person.
- Rungs
- 0b STOP · 1 IDENTITY · 2 WRITE DOOR · 3 PRE-GATES (G1–G9) · 4 evaluate() · 5 GUARDRAIL · 6 AUTONOMY · 7 ACT · 8 PROOF · 9 METERED. Any rung can refuse. Rungs 3 to 8 cannot reach a model client.
- Fields
- All sixteen. write_id → F04, write_type → F05, declaration → F06/F07, actor → F09/F10, record → F12/F13, payload → F14 as a hash, never the payload itself.
- Idempotency
- The
write_idis the idempotency key. Replaying it returns the original outcome, sequence and digest, and no second meter event. - Meter
- One governed action when committed. A refusal is metered at 0.4 of a governed action. An escalation is not metered until it resolves.
- Mode
- In Mode A · Enforced the single writer makes the change at rung 7 with a 10-minute single-use credential, sandbox first. In Mode B · Attested advisory your system writes; elan1 evaluates and records.
Request
POST /v1/assert
Authorization: Bearer <tenant token, act scope>
X-Tenant: tnt_specimen_01
Idempotency-Key: wr_specimen_7c2
{
"kernelport": "v1",
"call": "assert",
"write_id": "wr_specimen_7c2",
"write_type": "journal_entry.post",
"declaration": { "id": "decl_month_end_close", "version": 7 },
"actor": { "id": "agt_specimen_gl02", "kind": "agent" },
"record": { "id": "je_specimen_5531", "expected_version": 1 },
"payload": {
"date": "2027-07-14",
"lines": [
{ "account": "5100 Freight inward", "debit": "18400.00" },
{ "account": "2100 Accrued expenses", "credit": "18400.00" }
]
}
}Payload fields are examples, not the published schema. The payload shape is whatever the declared write type says it is.
Three responses
HTTP/1.1 200 OK
{
"outcome": "escalated",
"rung": 6,
"reason": "Above the autonomy ceiling. A named person, not the asker, decides.",
"obligation": "OB-specimen-0412",
"proof": { "seq": 1039, "digest": "sha256:3b9a51c2…7e04d1f8" },
"meter": { "governed_actions": 0 }
}HTTP/1.1 200 OK
{
"outcome": "committed",
"rung": 9,
"record": { "id": "je_specimen_5531", "version": 2 },
"proof": {
"seal": "elan1.proof.seal.v6",
"seq": 1042,
"digest": "sha256:f2423aa3…5aca2c18",
"prev_digest": "sha256:8e37839e…0ffdeac0"
},
"meter": { "governed_actions": 1 }
}HTTP/1.1 200 OK
{
"outcome": "refused",
"rung": 3,
"refusal": {
"code": "GATE.PRECONDITION_FAILED",
"class": "rule",
"reason": "The precondition gate (G9) failed against your data."
},
"record": { "id": "00000000-0000-0000-0000-000000000000", "version": 0 },
"proof": { "seq": 1041, "digest": "sha256:8e37839e…0ffdeac0" },
"meter": { "governed_actions": 0.4, "inr": 0.64 }
}
# 0.4 of a governed action: ₹0.64 at L1, beyond the allowance.An escalation names an obligation. A named approver, who is not the asker and never an agent token, answers it. If they approve, the single writer commits and the record is sealed; if they say no, the refusal is recorded through refuse. If nobody answers before the hold expires, expiry is a refusal. Your caller learns the final outcome from a webhook or by reading the obligation; see HTTP API · webhooks.
Errors and refusals
| Situation | Outcome | Record · meter |
|---|---|---|
| Writes are stopped for this tenant (rung 0b) | Refused | Written · 0.4 governed action |
| The write type is not on the declaration's list (rung 2) | Refused | Written · 0.4 governed action |
| A pre-gate fails, for example GST rate or precondition (rung 3) | Refused G4_GST_RATE_MISMATCH, GATE.PRECONDITION_FAILED (illustrative) | Written · 0.4 governed action |
| The versioned rules say no (rung 4) | Refused | Written · 0.4 governed action |
| The blast radius is over the declared limit (rung 5) | Refused | Written · 0.4 governed action |
| Above the autonomy ceiling (rung 6) | Escalated | Written · not metered while held |
| The record changed since you read it (expected_version) | Refused | Written · 0.4 governed action |
| A dependency is down | Refused — a platform refusal, never an allow | Written · never billed |
| Malformed request, wrong token, “latest” as a version on a production key | Contract error (4xx) | None |
Which rung raises which code is fixed by the frozen register. Codes shown are illustrative.
Call 3 of 6
seal
seal(record) → receipt
The chain writer. seal takes the sixteen fields of a record, builds a length-prefixed preimage under the domain elan1.proof.seal.v6, hashes it with SHA-256, and appends the record to the tenant’s chain. The digest becomes the next record’s prev_digest. No caller outside the kernel can invoke it; you see its result in every response’s proof block and in the records you read.
- Who may call
- The kernel only.
- Rung
- 8 PROOF, for commits, refusals and escalations alike.
- Fields
- All sixteen, F01
tenant_idto F16recorded_at. F02seqonly ever goes up by one; F03prev_digestlinks the chain. Chains never cross tenants. - At L1
- A hash-chain receipt. Tamper-evident to you: a changed record no longer matches its digest, and the next record no longer points at it.
- From L2
- The same call also signs, with Ed25519 per residency domain; Merkle roots are anchored to two RFC 3161 timestamp authorities. Signed records are checkable offline from L2.
seal → receipt (L1)
{
"seal": "elan1.proof.seal.v6",
"tenant_id": "tnt_specimen_01",
"seq": 1042,
"prev_digest": "sha256:8e37839e…0ffdeac0",
"digest": "sha256:f2423aa3…5aca2c18",
"recorded_at": "2027-07-14T10:42:07+05:30"
}
# From L2, the same call also signs. A signature
# block is added; the digest and the chain do not change.Errors
If the chain writer cannot seal, nothing is committed: an allow without a record is not an allow. The intent ends as a platform refusal and is never billed. A refusal is final for its write_id, so a later attempt is a new intent with a new one. There are no UPDATE or DELETE operations on evidence rows. The full field list and the preimage layout are in Proof record.
Call 4 of 6
verify
verify(record | seq range) → intact | findings
Checks one record or a stretch of chain. It recomputes each record’s preimage and digest octet for octet, compares every prev_digest with the digest before it, and checks that seq has no gaps, duplicates or reorderings.
- Who may call
- A read-only token.
- Rungs
- None. verify reads; it writes no record and is not a governed action.
- Fields
- F02 seq and F03 prev_digest, plus the digest recomputed over all sixteen.
- At L1
- Verification needs elan1. It tells you whether the chain you hold is the chain elan1 wrote, and that nothing in it was edited afterwards.
- From L2
- Signed records and bundles are checkable offline, by anyone with the bundle, using elan1-verify.
POST /v1/verify
Authorization: Bearer <read-only token>
X-Tenant: tnt_specimen_01
{
"kernelport": "v1",
"call": "verify",
"range": { "from_seq": 1041, "to_seq": 1042 }
}HTTP/1.1 200 OK
{
"range": { "from_seq": 1041, "to_seq": 1042 },
"checked": 2,
"result": "intact",
"findings": []
}
# A broken chain answers with findings instead, for example:
# { "seq": 1042, "finding": "prev_digest_mismatch" }
# { "seq": 1043, "finding": "seq_gap", "expected": 1043, "found": 1044 }What it finds
A flipped octet, a reordered pair, a duplicate and a gap are each found; conformance test C9 proves it. Finding names above are illustrative; the exact names are in the record-shape reference.
Call 5 of 6
scope
scope(actor, write_type, record) → advisory scope
Sets out what a caller may do, so a surface can show it before anything is proposed: the tenant and residency domain, whether the write type is allowed, the blast radius, and the autonomy level against its ceiling. It is advisory only. assert resolves both again at rungs 5 and 6, so a scope answer can never be used as an allow.
- Who may call
- A read-only token.
- Rungs it previews
- 1 IDENTITY, 2 WRITE DOOR, 5 GUARDRAIL and 6 AUTONOMY. It does not run the pre-gates or evaluate().
- Fields it previews
- F01
tenant_id, F05write_type, F11autonomy_level. - Missing grant
- An actor without a grant resolves to A0 Observe.
- Isolation
- Scope never crosses tenants or residency domains. A cross-domain query is not slow; it is impossible.
GET /v1/scope?write_type=journal_entry.post&record_id=je_specimen_5531
Authorization: Bearer <read-only token>
X-Tenant: tnt_specimen_01HTTP/1.1 200 OK
{
"advisory": true,
"actor": { "id": "agt_specimen_gl02", "kind": "agent" },
"residency_domain": "in",
"write_type_allowed": true,
"autonomy": { "level": "A2", "ceiling": "A2", "person_must_approve": true },
"blast_radius": { "records": 1, "ledgers": 1, "inside_declared_limit": true }
}Call 6 of 6
refuse
refuse(obligation | write_id, actor, code) → refused
Records a typed “no”. A named approver uses it to refuse a held write; a caller uses it to withdraw an intent it no longer wants. The kernel uses the same path when a hold expires. Codes come from a closed register, so a refusal is always one of a known set.
- Who may call
- A tenant token with act scope. Refusing a held write needs the named approver; an agent token cannot answer an obligation.
- Rung
- 6 AUTONOMY for an approver's no or an expired hold; the rung where the intent stood for a withdrawal.
- Fields
- F09 actor_id and F10 actor_kind name who refused. F12 record_id is zeroed and F13 record_version is 0, because nothing changed.
- Record and meter
- A refusal still writes a proof record. It is metered at 0.4 of a governed action, and a database check holds it there.
POST /v1/refuse
Authorization: Bearer <tenant token, act scope>
X-Tenant: tnt_specimen_01
{
"kernelport": "v1",
"call": "refuse",
"obligation": "OB-specimen-0412",
"actor": { "id": "usr_specimen_approver", "kind": "human" },
"code": "APPROVER_REFUSED",
"note": "Accrual belongs in August, not July."
}HTTP/1.1 200 OK
{
"outcome": "refused",
"rung": 6,
"refusal": { "code": "APPROVER_REFUSED", "class": "rule" },
"record": { "id": "00000000-0000-0000-0000-000000000000", "version": 0 },
"proof": { "seq": 1040, "digest": "sha256:91c4e7b0…2d3f6a05" },
"meter": { "governed_actions": 0.4, "inr": 0.64 }
}
# 0.4 of a governed action: ₹0.64 at L1, beyond the allowance.Errors
| Situation | What comes back |
|---|---|
| The obligation is already resolved | The original outcome, sequence and digest. Nothing new is written. |
| The caller is the asker, or an agent token | A contract error. Approval separation (C11) applies to a no as much as a yes. |
| The code is not in the register | A contract error. Free-text reasons go in the note; the code must come from the register. |
Outcomes, at a glance
Every call that writes ends in exactly one of three outcomes. A platform failure is ours, not a rule, and is never billed. A contract error means the request never reached the Right Path.
| Outcome | Means | Proof record | Meter |
|---|---|---|---|
| Committed | The write climbed every rung. In Mode A the single writer made it; in Mode B your system makes it and elan1 has recorded the evaluation. | Written | 1 governed action |
| Refused | A rung said no, or a person did, or the hold expired. Typed code from the register. | Written | 0.4 governed action |
| Escalated | Held for a named person who is not the asker. Resolves to committed or refused. | Written | Not metered while held |
| Platform refusal | A dependency failed. elan1 fails closed: never an allow. | Written where the chain writer is reachable | Never billed |
| Contract error | The request did not meet the contract: shape, token, version pin. | None | None |
What the customer reference covers
This page is the public quick reference. Customers receive the full KernelPort v1 reference with their workspace, which sets out:
- Exact argument names, nesting and response shapes beyond the call names and the three outcomes.
- Exact HTTP paths.
/v1/<call>here is illustrative. - The refusal register: every code, its class and the rung that raises it.
- Finding names returned by
verify. - The L2 signature block added by
seal.