What a proof record is
A proof record is the evidence that one write intent took the governed path. elan1 Govern writes it at rung 8 PROOF, synchronously, before the caller receives its answer. A commit writes one. A refusal writes one. A hold that expires and becomes a refusal writes one. There is no outcome of assert that leaves no record.
- Frozen identifier
elan1.proof.seal.v6- Fields
- Sixteen, F01 to F16, in a fixed order. No optional fields, no seventeenth field, no v6.1. A refusal carries zeroes, not omissions.
- Hash
- SHA-256 over a length-prefixed preimage: the 19-octet domain string, then each field with its length in front.
- Chain
- One chain per tenant. F03 prev_digest holds the digest of the record before. Chains never cross tenants.
- Storage rule
- Evidence rows cannot be updated or deleted: there is no UPDATE or DELETE on them.
- Personal data
- None in evidence. F14 holds a hash of the payload, not the payload.
- Assurance
- At L1: tamper-evident to you; not signed, and checking needs elan1. Signed and checkable offline from L2.
- Status
- Available · L1 Guarded and L2 Attested
Every value on this page is an illustrative specimen, not customer data. The two specimen records are the same ones the inspector on the proof-record page hashes.
The sixteen fields
Field names and order come from Seal v6. The example column is record seq 1042 from the specimen chain: an approver committing a journal entry for Kolam Components*.
| Field | Name | What it holds |
|---|---|---|
| F01 | tenant_id | The tenant this record belongs to. Chains never cross tenants. Specimen: tnt_specimen_01 |
| F02 | seq | The record's position in this tenant's chain. It only ever goes up by one. Specimen: 1042 |
| F03 | prev_digest | The digest of the record before this one. It is what links the chain. Specimen: sha256:8e37839e9548dfa9a4a8948b723c1517afb532a7eaceb617a7db6eff0ffdeac0 |
| F04 | write_id | The identifier of the write intent. Every invoice line must carry one. Specimen: wr_specimen_7c2 |
| F05 | write_type | The kind of write, from the declaration's list of allowed write types. Specimen: journal_entry.post |
| F06 | declaration_id | The declaration — the process's rules in seven blocks — that was checked. Specimen: decl_month_end_close |
| F07 | declaration_version | Which version of that declaration was in force at the time. Specimen: 7 |
| F08 | policy_version | The version of the policy rules evaluate() ran. No model, only rules. Specimen: 2027.06.1 |
| F09 | actor_id | Who acted: the named approver, or the agent that proposed the write. Specimen: usr_specimen_approver |
| F10 | actor_kind | Whether the actor was a human or an agent. Specimen: human |
| F11 | autonomy_level | The autonomy level A0–A5 the write ran at; above the ceiling a person decides. Specimen: A2 |
| F12 | record_id | The record in your system of record that changed, or would have. Specimen: je_specimen_5531 |
| F13 | record_version | The version of that record after the write. Specimen: 2 |
| F14 | payload_hash | A hash of the payload, not the payload itself, so personal data stays out of evidence. Specimen: sha256:e1eb0d8f0a7399695abf290e8fd2b6abc738ffd2fbdb8297f9ae44eb1a32ab6a |
| F15 | lexicon_hash | A hash of the vocabulary the write was expressed in, so meanings cannot drift. Specimen: sha256:fd8e4d0b12b24bf22a591e735f211d8d7bb2bdf480058fdf89bb9d44666b2633 |
| F16 | recorded_at | When the record was sealed, with the time-zone offset. Specimen: 2027-07-14T10:42:07+05:30 |
*Fictional company; all data is illustrative. Illustrative specimen. Not customer data.
Read the fields in seven groups
Which tenant's chain, which position in it, and the digest of the record before.
The intent's identifier and the kind of write it asked for. write_id is also the idempotency key: replaying an intent returns the original outcome.
The declaration, the version of it in force, and the version of the policy rules evaluate() ran. No model sits on the allow path.
The named approver or the proposing agent, whether that actor was a human or an agent, and the autonomy level A0–A5 the write ran at.
The record in your system of record that changed, or would have, and its version afterwards. On a refusal, record_id is zeroed and record_version is 0.
A hash of the payload and a hash of the vocabulary it was expressed in, so content stays out of evidence and meanings cannot drift.
When the record was sealed, with its time-zone offset.
Commit and refusal, side by side
| Field | seq 1041 · refused | seq 1042 · committed |
|---|---|---|
| F05 write_type | purchase_invoice.post | journal_entry.post |
| F06 declaration_id | decl_ap_purchase | decl_month_end_close |
| F09 actor_id | agt_specimen_ap01 | usr_specimen_approver |
| F10 actor_kind | agent | human |
| F11 autonomy_level | A1 | A2 |
| F12 record_id | 00000000-0000-0000-0000-000000000000 | je_specimen_5531 |
| F13 record_version | 0 | 2 |
Illustrative specimen. Not customer data. Both records share F01 tenant_id and F08 policy_version; the refusal still takes a place in the chain.
Beside the record, never in it
Three things a reader needs are not fields: the outcome (committed, refused or escalated), the approver’s display name, and the refusal code. Interfaces show them in a rendering strip next to the record. The record itself stays at sixteen fields, so the preimage never changes shape between outcomes.
- Outcome
- Refused at rung 3 · PRE-GATES
- Approver
- None. The write stopped before approval.
- Refusal code
- GATE.PRECONDITION_FAILED (illustrative)
- Meter
- 0.4 governed action
Rendering strip for specimen seq 1041. Illustrative specimen. Not customer data. Refusal codes come from one closed register; real codes are in the KernelPort v1 reference customers receive. See Errors and refusals.
The preimage and the digest
The digest of a record is SHA-256 over its preimage. The preimage is the 19-octet domain string elan1.proof.seal.v6, followed by the sixteen fields in order, each written as its length and then its bytes. Because every field carries its own length, no two different records can produce the same bytes: "ab" then "c" can never be confused with "a" then "bc". The domain string keeps a Seal v6 digest from ever colliding with a hash computed for another purpose.
elan1.proof.seal.v6, 19 octets, no length prefixStart with the domain string.
Write the 19 octets of
elan1.proof.seal.v6.Append every field, in order.
For F01 to F16, write a 4-octet big-endian length, then the field's bytes. Never skip a field; a refusal's zeroed record_id is still written.
Hash once.
Run SHA-256 over the whole preimage. The 32-octet result, written as sha256: and 64 hex characters, is the record's digest.
Carry it forward.
The next record in the tenant's chain stores that digest in F03 prev_digest.
Chaining by prev_digest
Each tenant has exactly one chain. F02 seq goes up by one for every record, and F03 prev_digest on record n + 1 equals the digest of record n, octet for octet. That single rule is what makes the chain tamper-evident: the digest of every record is committed to by the record after it.
| If someone… | What breaks | How a walk finds it |
|---|---|---|
| Changes one octet of a past record | That record’s digest | The next record’s F03 no longer matches the recomputed digest. |
| Deletes a record | The sequence | F02 seq jumps by two; the following F03 matches nothing you hold. |
| Swaps two records | Order and links | seq runs backwards and both links fail. |
| Inserts a record | A link | Either seq repeats or a prev_digest points at the wrong record. |
| Rewrites everything after a point | The head digest | The head no longer matches a digest you noted at an earlier check. |
| Moves a record to another tenant | F01 and the link | Chains never cross tenants; the tenant changes mid-walk. |
The last row is why a periodic check matters at L1: anyone able to rewrite a whole stretch of the chain can also recompute its digests. Note the head digest each time you check and compare it next time. From L2, signatures and outside timestamps close that gap, because a rewritten stretch would no longer carry valid signatures or anchors.
Recompute a chain at L1
At L1, the verify call recomputes digests and walks the chain for you. You can also do it yourself, with the records exported to you and a few lines of code. The script below uses only the Python standard library. Run against the two specimen records it prints the same head digest that the browser inspector computes.
Export the stretch you want to check.
One record per line, sixteen named fields each, in seq order. The export shape below is illustrative; at L1 the records come to you from elan1.
Rebuild each preimage.
The domain string, then each field as a 4-octet big-endian length and its bytes.
Hash and compare.
SHA-256 each preimage and compare it with F03 prev_digest on the next record. They must be identical.
Walk the whole stretch.
Check that F01 never changes and F02 always goes up by one. Note the head digest for next time.
{"tenant_id":"tnt_specimen_01","seq":"1041","prev_digest":"sha256:058169bb8da3e9f6b1b8511193b43b0636ffe1ecc6e4c46ce8a8a905964023f8","write_id":"wr_specimen_7c1","write_type":"purchase_invoice.post","declaration_id":"decl_ap_purchase","declaration_version":"4","policy_version":"2027.06.1","actor_id":"agt_specimen_ap01","actor_kind":"agent","autonomy_level":"A1","record_id":"00000000-0000-0000-0000-000000000000","record_version":"0","payload_hash":"sha256:5f105d2d818bc7fe36a7c1b7e367196610df5ee6e8d4c5296c9d8cc4b28428d8","lexicon_hash":"sha256:1d967c9d1c583efd0a3135e57aa1035ebdd5ab4766f0572f10304feae7516d53","recorded_at":"2027-07-14T10:39:12+05:30"}
{"tenant_id":"tnt_specimen_01","seq":"1042","prev_digest":"sha256:8e37839e9548dfa9a4a8948b723c1517afb532a7eaceb617a7db6eff0ffdeac0","write_id":"wr_specimen_7c2","write_type":"journal_entry.post","declaration_id":"decl_month_end_close","declaration_version":"7","policy_version":"2027.06.1","actor_id":"usr_specimen_approver","actor_kind":"human","autonomy_level":"A2","record_id":"je_specimen_5531","record_version":"2","payload_hash":"sha256:e1eb0d8f0a7399695abf290e8fd2b6abc738ffd2fbdb8297f9ae44eb1a32ab6a","lexicon_hash":"sha256:fd8e4d0b12b24bf22a591e735f211d8d7bb2bdf480058fdf89bb9d44666b2633","recorded_at":"2027-07-14T10:42:07+05:30"}# verify_chain.py — recompute a stretch of a tenant's chain (illustrative, L1 method)
import hashlib, json, struct, sys
DOMAIN = b"elan1.proof.seal.v6" # 19 octets
FIELDS = ["tenant_id", "seq", "prev_digest", "write_id", "write_type",
"declaration_id", "declaration_version", "policy_version", "actor_id", "actor_kind",
"autonomy_level", "record_id", "record_version", "payload_hash", "lexicon_hash", "recorded_at"]
def preimage(rec):
out = bytearray(DOMAIN)
for name in FIELDS: # F01..F16, in order, none optional
b = str(rec[name]).encode("utf-8")
out += struct.pack(">I", len(b)) + b # 4-octet big-endian length, then the bytes
return bytes(out)
def digest(rec):
return "sha256:" + hashlib.sha256(preimage(rec)).hexdigest()
def walk(records):
for prev, cur in zip(records, records[1:]):
if cur["tenant_id"] != prev["tenant_id"]:
return f"FAIL tenant change at seq {cur['seq']}"
if int(cur["seq"]) != int(prev["seq"]) + 1:
return f"FAIL gap after seq {prev['seq']}"
if cur["prev_digest"] != digest(prev):
return f"FAIL link broken between seq {prev['seq']} and {cur['seq']}"
return f"OK {len(records)} records, seq {records[0]['seq']}-{records[-1]['seq']}"
if __name__ == "__main__":
records = [json.loads(line) for line in open(sys.argv[1], encoding="utf-8")]
print(walk(records))
print("head", digest(records[-1]))$ python3 verify_chain.py chain.jsonl
OK 2 records, seq 1041-1042
head sha256:f2423aa3d4559b7fd54b390a724f2a989744c10db034caa4a0e047cb5aca2c18# Change one character of F13 record_version on seq 1041 ("0" → "1") and run again:
$ python3 verify_chain.py chain.jsonl
FAIL link broken between seq 1041 and 1042
head sha256:f2423aa3d4559b7fd54b390a724f2a989744c10db034caa4a0e047cb5aca2c18 # record 1042 is untouched; its F03 no longer matchesThe script shows the method on the specimen encoding (every field as UTF-8 text). It does not check signatures, because at L1 there are none, and it cannot tell you who sealed a record. At L1 the records are tamper-evident to you; they are not signed.
What a check proves, and what it does not
| A passing walk shows | It does not show |
|---|---|
| The records have not changed since they were sealed, as far as the head you trust | That the business decision was right |
| Which declaration, which version and which policy version applied | That the rules were good |
| Who acted, as human or agent, and at what autonomy level | That nothing happened outside elan1 (in Mode B, the caller holds the credential) |
| That a refusal happened, at which rung, and that it still joined the chain | That the transaction itself was genuine |
In Mode B a record shows that a write intent was evaluated against your rules and recorded. It is not evidence of enforcement, because elan1 did not hold the credential. The word enforced applies to Mode A only. See Modes and assurance levels.
What L2 adds
The record does not change between levels: the same sixteen fields, the same preimage, the same chain. L2 Attested adds four things on top, so that someone who does not trust our infrastructure can check a record without us.
| Addition | What it does | Status |
|---|---|---|
| Signing | Each record's digest is signed with Ed25519, with a key per residency domain. India first: domain "in". The signature travels with the record, never as a seventeenth field; at L1 it is absent, not stubbed. | Available |
| Anchoring | Records are batched into Merkle roots and anchored to two RFC 3161 timestamp authorities, one of them in India, within 15 minutes. An anchor shows a record existed, in that form, by that time. We do not run a blockchain. | Available |
| Evidence bundle | An eight-member folder that needs nothing from us: hashes, not payloads, and never key material. | Available |
| Offline verifier | elan1-verify, free, with no dependencies and no network, built so anyone can reproduce the build. | Available |
manifest.json
seals.jsonl
chain.json
anchors/
keys.json
declarations/
lexicon/
VERIFY.txt
$ elan1-verify --bundle <path> --profile <path>
PASS | FAIL_INTEGRITY | FAIL_PROFILE | INCOMPLETE | UNSUPPORTED | USAGE- Signing is switched on for a tenant at a recorded point in the chain. Records written before it stay L1 and are labelled L1 for good; we never say past records were signed.
- Signing a Mode B record does not make it enforced. The record keeps the mark that the caller made the write, in every export.
- L3 Proven is reached when an outside auditor accepts an L2 bundle in writing. It has no date.
What the customer reference covers
Field names and order are fixed by Seal v6. These parts of the surrounding reference are set out in full in the reference customers receive with their workspace.
Canonical per-field encoding
How identifiers, integers, digests and timestamps are written as octets in the production preimage. The specimen encoding here writes everything as UTF-8 text.
In the customer reference
Export format at L1
The file shape in which records are handed to you for your own walk. The JSON Lines shape above is illustrative.
In the customer reference
Refusal codes
The closed register of typed refusal codes shown beside a record. Codes on this site are illustrative.
In the customer reference
Where the record physically lives
The record shape is fixed; its physical home follows your deployment: hosted in your residency domain, or in your own VPC or on premises.
Per deployment
Signing-key custody
L2 signing keys are held per residency domain. Custody details are in the customer reference and on the verification page.
In the customer reference
Erasure and an append-only chain
Payloads are hashed, not stored, and evidence rows cannot be changed. A lawful erasure request is honoured where the personal data lives; the record keeps only a hash. See the library note on DPDP and the append-only chain.
Library note