# The Seal1618 protocol — Sealed Evidence Pack & Root History formats, verification specification

**Version 1.12.0 · Published 2026-09-14 · Creation1618**

> 1.12.0 makes the leaf rule exact, names the producer, publishes the vectors
> for the other three formats, adopts a format version and support policy
> (§7) and carries the kit's licence. (a) §2 rule 1: a leaf payload re-encodes
> only when it parses to an object holding exactly `id` and `v`; a payload
> carrying any other key fails item integrity, since that key sits under the
> seal where no reader is shown it. No document a conforming producer ever
> emitted changes verdict; the vector `payload-extra-key` carries the one that
> does. (b) §2.0: `issuer.platform` is the producer line, written by the
> producing software from its own identity module and never by an operator or
> a caller; a verifier prints it from the document and says when it is absent.
> (c) Conformance vectors for `txra.reconpack.v1` (eleven),
> `txra.reconhistory.v1` (thirteen) and `txra.roothistory.v1` (fourteen) join
> the kit, generated by the producer and graded by this release's verifier.
> (d) `sample-reconpack.json` is republished from the current producer and now
> carries the `chain.adapter` `scope` field of 1.9.0. (e) §7 adopts the format
> version and support policy drafted on 2026-09-13; this release is its first
> application, and under it the leaf rule is a tightening to the format as
> written, not a new tag. (f) `LICENSE.md` travels with the kit for the first
> time. The reference verifier also stops printing "#null" for a reconciliation
> sealed before its producer's first anchor and states that no anchor is named.
> Every 1.11.1 document a conforming producer emitted verifies unchanged.
> 1.11.1 states three things the second implementation had to infer from the
> reference rather than read here, and adds a twenty-first conformance vector
> for one of them. (a) A `FAILED` verdict names its failing checks in
> procedure order. (b) `INCOMPLETE` is reached only when every other check
> passes: an unreadable key over a broken chain is `FAILED`. (c) Under JCS a
> JSON number is an IEEE double, so an integer beyond 2^53 canonicalises as the
> double it parses to — a verifier that keeps such integers exact computes a
> different hash and is not conformant; the new vector `large-integer-detail`
> carries one. No verdict on any existing document changes.
> 1.11.0 adds a second implementation of the agent-trail profile (§3.3) to the
> kit: `txra_agenttrail.py`, written in Python from the text of this
> specification and the draft, sharing no code with the reference verifier and
> using the `openssl` command for signatures. It verifies and produces trails.
> It reaches the stated verdict on all twenty (now twenty-one) conformance vectors; a trail it
> signs verifies under the reference with its key, and the reference's sample
> verifies under it; the two canonicalisers agree byte for byte on RFC 8785's
> own vectors. This is what the compatibility note (§6) had recorded as absent
> — with the limit stated there: a second implementation, not a second party.
> No format, rule or verdict changes; every 1.10.0 document verifies unchanged.
> 1.10.0 adds the agent audit trail (§3.3): the Seal1618 profile of the IETF
> Internet-Draft `draft-sharif-agent-audit-trail-01`, recorded at 1.8.0 as
> decided-not-shipped (§6) and shipped here. A trail travels as
> `txra.agenttrail.v1` — the draft's records verbatim under a tag — or in the
> draft's own native shapes (a JSON array of records, or its JSONL export), and
> a conforming verifier checks all three identically. A sealed pack may commit
> a trail as external evidence under §2.2, which is the profile's whole point:
> the draft gives a trail tamper-evidence from the inside; the seal gives it an
> outside holder and an outside date. The sample pack grows from nine leaves to
> ten with exactly such a commitment. The reference verifier gains `--key` for
> the draft's optional signatures, checked only against a key the reader
> supplies. A 1.9.0 verifier meeting `txra.agenttrail.v1` refuses it by name as
> an unknown tag — the correct cautious reading — and every 1.9.0 document
> verifies unchanged under 1.10.0.
> 1.9.0 adds `scope` to the reconciliation pack's `chain.adapter` record
> (§2.4): `rail` says a real chain was queried, `scope` says **whose** —
> `public`, or `producer_devnet` for a local devnet the producer operates. The
> field exists because the first rail ever read through this format is a
> producer-operated devnet, which earns `rail` (real chain, real RPC) and must
> never render like a public network: a devnet reading proves the producer's
> rail integration, not third-party supply attestation, and a conforming
> verifier carries that sentence into its own summary. A rail-basis document
> stating no recognisable scope is reported as proving rail integration only.
> Packs sealed before `scope` existed all carry `kind` `none` or `fixture` and
> are unaffected; a 1.8.0 verifier still verifies a 1.9.0 pack's seal, and
> what it misses is a rendering caveat, not a seal rule.
> 1.8.0 names the protocol, and changes nothing normative. The formats and
> the verification recipe this document specifies are the **Seal1618
> protocol**. The name is new; the thing named is not: every format tag,
> every verdict and every recipe below is exactly as 1.7.0 published them,
> and a verifier conforming to 1.7.0 conforms to 1.8.0. Attest1618 — the
> first implementer of the Seal1618 protocol — produces the artifacts this
> kit ships; the protocol itself remains free to implement and free to
> verify, and this document is its whole normative surface. 1.8.0 also adds
> §6, a versioned compatibility-notes section, whose first entry records a
> decided-but-not-yet-shipped profile so a reader is never left inferring
> the difference between a decision and a shipped format.
> 1.7.0 completes the browser verifier and renames its page.
> `browser-verify.html` now verifies every format this document describes —
> both packs, the root history, and the reconciliation history — routing on
> the tag a document carries, exactly as the CLI does. It supersedes 1.6.0's
> `roothistory-verify.html`, which lived one version: the page outgrew its
> name the day the remaining formats landed, and carrying the old name
> forward would have described a quarter of what the file does. A 1.6.0 kit
> already downloaded keeps working; its page still verifies root histories,
> and its checksums still match its own files.
> 1.6.0 adds `roothistory-verify.html` — the root-history verifier as one
> self-contained browser page, so the document that carries the chain witness
> can be checked by a reader who will never run a CLI. It embeds the same
> browser implementation the HTML receipts embed, pinned to the reference
> verifier sentence-for-sentence by test; it makes no network request of any
> kind; and it carries one verdict the CLI does not need: INCOMPLETE, for a
> receipt whose signature key the browser cannot import — an inability, named
> as one, never a finding. No format changed.
> 1.5.0 corrects what §3.1a says a verifier establishes. The block number, block
> hash, sending address and block timestamp a chain receipt states are NOT in the
> signed transaction and cannot be derived from it; they are now checked for shape
> and reported as ASSERTED, and a conforming verifier MUST NOT present them as
> established. It also records why the reference verifier makes no network call,
> and that no sample in this kit carries an `evm` witness.
> 1.4.0 adds the `evm` witness (§3.1a) — a root published to a public Ethereum
> network, carried as the signed transaction itself so a reader re-derives its id,
> its chain and its root from the bytes alone. A 1.3.0 verifier meeting one refuses
> it by name as an unknown witness kind, which is the correct cautious reading.
> 1.3.0 renames the `source` value that means "a real backend was read" from a
> vendor's product name to `platform` (§2.3). The vocabulary was never meant to
> name anyone's infrastructure, and a published contract is the wrong place to
> do it. The change is safe in both directions: no pack has ever been sealed
> with the old value outside a demonstration, and a 1.2.0 verifier meeting a
> `platform` pack derives `demonstration` — the cautious reading — rather than
> a wrong one.
> 1.2.0 adds the sealed reconciliation pack (§2.4), the reconciliation
> history (§3.2), the external-evidence rules (§2.2) and their two sample
> documents — content that landed 2026-08-17 while the header still read
> 1.1.0; this bump corrects that, and §5's own lesson applies to us too.
> 1.1.0 added the optional, unsealed `issuer` / `verification` envelope
> (§2.0) and the conforming HTML receipt (§2.0.1).
> Packs and verifiers built against 1.0.0 remain valid in both directions:
> the envelope is optional to emit and must never affect a verdict.

This document is the Seal1618 protocol: it specifies, completely, how to
verify the six document kinds the
family's origination platform produces: a sealed evidence pack
(`txra.ddpack.v1`), a selective disclosure drawn from one, a sealed
reconciliation (`txra.reconpack.v1`), its reconciliation history
(`txra.reconhistory.v1`), the public
anchor-root history (`txra.roothistory.v1`), and an agent's audit trail
(`txra.agenttrail.v1`, §3.3 — the profile of an IETF Internet-Draft, also
accepted in the draft's own native shapes). Everything needed to verify is
here or in the files themselves; nothing requires an account, a network
connection, or any software of ours — the reference verifier
(`txra-verify.cjs`, checksum in `SHA256SUMS.txt`) is one dependency-free
file you can read in full before running.

**What is deliberately not here:** the production fact set — which facts a
production pack commits, how they are selected, named and grouped, and the
disclosure defaults. That is the product. This specification covers the
envelope and the verification algorithm, which are open on purpose: a format
you cannot independently check is a format you are being asked to trust.

---

## 1. The hash tree (RFC 6962)

All commitments use the Merkle Tree Hash of RFC 6962 §2.1 over SHA-256:

```
leaf  = SHA-256( 0x00 || payload-bytes )
node  = SHA-256( 0x01 || left || right )
```

For `n > 1` leaves the tree splits at `k`, the largest power of two strictly
less than `n` — never by duplicating the odd node (the duplication construction
admits CVE-2012-2459-style collisions). The empty tree is `SHA-256("")`.
This is the Certificate Transparency construction, unmodified.

## 2. `txra.ddpack.v1` — the sealed evidence pack

```json
{
  "version":       "txra.ddpack.v1",
  "dealReference": "…",
  "root":          "<64-hex>",
  "leafCount":     8,
  "leaves": [
    { "id": "…", "group": "…", "label": "…", "value": "…", "payload": "…" }
  ]
}
```

Each leaf's `payload` is the *stable JSON* encoding (object keys sorted
recursively, no whitespace) of `{ "id": <leaf id>, "v": <the fact> }`. Binding
the id into the hashed payload means two leaves that happen to share a value
still produce distinct leaf hashes.

**What the root does not cover.** Only `id` and `value` enter the payload.
`label`, `group`, `dealName`, `sealedAt`, the policy fingerprint and the
anchoring block are carried alongside the tree and are **outside** the root —
deliberately, so a pack stays verifiable when presentation is re-rendered, but
with a consequence worth stating plainly: those fields are the entire readable
surface of a receipt, and a pass says nothing about them. A relabelled leaf
still verifies. Where it matters which question a value answers, check the
`id`.

**Verification recipe:**

1. Every leaf's `payload` must re-encode from its `id` and `value` — a payload
   that disagrees with its own fields is a tampered item. Concretely: parse
   `payload` as JSON to `{ "id": …, "v": … }`, then require

   ```
   parsed.id == leaf.id
   (typeof parsed.v == "string" ? parsed.v : stableStringify(parsed.v)) == leaf.value
   ```

   `value` is the DISPLAY form of `v`: identical when `v` is a string, and the
   stable-JSON encoding of it otherwise. That asymmetry is why the check is
   written out — comparing `leaf.value` to `JSON.stringify(parsed.v)` fails on
   every string-valued leaf, and comparing the raw values fails on every
   object-valued one.

   **Exactly two keys (1.12.0).** A payload that parses to anything but an
   object holding exactly `id` and `v` does not re-encode, whatever those two
   say: a key besides them sits under the seal where no reader is shown it, and
   the item fails integrity (vector `payload-extra-key`). A producer conforming
   to this section has never emitted such a payload, so no conforming document
   changes verdict; a document that verified under 1.11.1 only because its
   extra key went unread was never conformant.
2. **Leaves are hashed in the order the pack publishes them**: the
   `provenance.data_source` leaf first, the remainder ascending by `id`.
   Order is part of the tree — **do not re-sort**. Recompute
   `root = MTH([leafHash(payload) …])` per §1 over the array as given.

   > This sentence read "leaves are ordered by `id` ascending" until
   > 2026-08-16. That was true when the format was published and stopped
   > being true when the provenance leaf (§2.3) was prepended: it sorts
   > before nothing, so it is pinned in front rather than sorted in. A
   > verifier following the old wording recomputes a different root and
   > reports `FAILED` on both samples in this kit — which is exactly the
   > failure this specification exists to make impossible, so it is
   > recorded here rather than quietly corrected.
3. The recomputed root must equal `root` byte-for-byte. There is no
   tolerance and no partial pass.

A verifier must **fail closed**: any document it cannot fully parse is
reported unverified, never passing. One exception is not a relaxation: a
leading UTF-8 byte-order mark is not JSON and is in no hash this
specification computes over parsed content, so a verifier MAY strip one
before parsing (the §2.2 evidence digest is taken over the raw file bytes and
is unaffected). The reference implementations do.

### 2.0 The envelope (`issuer`, `verification`) — deliberately NOT sealed

A pack may carry two envelope blocks so it works as a standalone
deliverable — a recipient who was handed the file by a third party can see
what it is and how to check it:

```json
{ "issuer": { "sealedBy": "…", "platform": "…", "coBrand": "…|null" },
  "verification": { "spec": "…", "verifier": "…", "checksums": "…",
                    "command": "…", "independence": "…" } }
```

**Neither block enters the Merkle root**, and that is a deliberate design
decision with two reasons. Practically, a co-brand or a moved URL would
otherwise invalidate every pack ever issued. More importantly it is the
honest construction: the block is a **pointer, not a trust anchor**. A
recipient who trusts a tool because the document handed them the link has
gained nothing — anyone who can rewrite the document can rewrite the link.

So the rules for a verifier are:

1. **Never** let the envelope affect the verdict. The root is the authority.
2. **Do** carry your own copy of the publisher's address, and warn loudly if
   a document points somewhere else. The reference verifier does exactly
   this: its `issuer` check reports who claims to have sealed the file, and
   flags any pack whose
   `verification.verifier` is not under the published home — while still
   returning VERIFIED if the seal is intact, because the seal *is* intact.
3. Treat a pack with no envelope as valid. The envelope is optional.

**The producer line (1.12.0).** `issuer.platform` names the software that
produced the document, and is written by that software from its own identity
module: never from the operator's tenant record, never from a caller, and never
editable by whoever runs the instance. `issuer.coBrand` is the operator's line;
`issuer.platform` is the producer's. A conforming verifier prints the producer
line from the document and, where the field is absent or empty, says so (the
reference verifier prints "producer not stated") rather than assuming one. The
line stays in the unsealed envelope: it is advisory, it is not evidence, and the
seal decides the verdict without it.

**For recipients:** get the verifier from the publisher's site and check it
against the published checksums. Do not run a tool that arrived with the
document it is meant to check.

### 2.0.1 The HTML receipt (a presentation, never a substitute)

A pack may be rendered as a single self-contained HTML file that displays
the sealed facts and **recomputes the root in the reader's own browser**,
offline, with nothing installed and no network request of any kind. It is
intended for the recipient who will never run a command line — the client
of whoever issued the pack.

Rules a conforming receipt follows:

1. **One file.** No external stylesheet, font, script or image; no fetch,
   no beacon. A receipt that phones home is not an offline proof.
2. **It carries the pack verbatim.** The embedded copy must verify
   identically to the original; display formatting must never reach the
   hashed payloads.
3. **Content is escaped, never trusted as markup.** A pack is a document
   from elsewhere.
4. **It states its own weakness.** A receipt carries its own copy of the
   verification code, so it can only report what it computes. It must say
   so and point the reader at the published tool for an independent check.

The receipt is a presentation of a pack, never a replacement for one: the
pack is the artifact, and the published verifier is the authority.

### 2.1 Selective disclosure

A disclosure document carries a subset of leaves, each with its derivation
path to the sealed root. Withheld items stay withheld; membership of the
disclosed ones is still provable.

```json
{
  "version": "txra.ddpack.v1",
  "root": "<the sealed root, 64-hex>",
  "leafCount": 9,
  "withheldCount": 7,
  "items": [
    {
      "leaf":  { "id": "…", "group": "…", "label": "…", "value": "…", "payload": "…" },
      "proof": [ { "hash": "<64-hex sibling>", "side": "left" }, … ]
    }
  ]
}
```

**`side` names where the SIBLING sits, not where you sit.** Walk the path from
the leaf upward, carrying an accumulator:

```
acc = leafHash(item.leaf.payload)
for step in item.proof:            # leaf → root order
    acc = step.side == "left"  ?  nodeHash(step.hash, acc)
                               :  nodeHash(acc, step.hash)
verified = (acc == root)
```

Getting `side` backwards produces a wrong root on every asymmetric tree and a
*correct* one on a two-leaf tree, so test against a pack with more than two
leaves. `sample-pack.json` has nine.

**Some leaves may never be withheld, and which ones depends on the format.**
A disclosure carries the `version` of the pack it came from; the required set
is looked up from it:

| format | may never be withheld |
|---|---|
| `txra.ddpack.v1` | `provenance.data_source` |
| `txra.reconpack.v1` | `provenance.data_source`, `result.basis`, `result.claim`, `chain.adapter` |

A disclosure missing any of them is **`INCOMPLETE`**, and the verifier names
which. Every inclusion proof can hold perfectly and the document still not be
usable: a reconciliation disclosed without its basis and claim is a
register-only comparison that reads as a token reconciliation, which is the
one reading those leaves exist to prevent.

**A disclosure whose `version` is absent or unrecognised is `UNVERIFIED`** —
not "checked under the default rules". Falling back on a missing tag would make
*deleting the tag* a way of deleting the rule with it.

**And the set is derived from the CONTENT as well as the tag.** Closing the
paragraph above by keying on the version alone simply moves the forgery to the
version: relabel a reconciliation disclosure `txra.ddpack.v1`, strip the three
limit leaves, and the DD rule — which requires only provenance — is satisfied.
So a disclosure carrying any `register.*`, `result.*`, `period.*`, `scope.*` or
`chain.*` leaf is treated as a reconciliation **whatever its tag says**, and
inherits the reconciliation's obligations. Leaf ids are inside the root and
bound into their own payloads; unlike the tag, they cannot be rewritten without
breaking the very proofs that make the document worth reading. A rule keyed on
a self-declared field is only ever as good as that field.

### 2.2 External evidence (`group: "external"`)

An `external.*` leaf commits a document produced by ANOTHER system:

```json
{ "format": "assetdna.export.v1", "fileName": "…", "sha256": "<64-hex>",
  "sourceOrg": "…", "exportedAt": "…", "headHash": "<64-hex>|null", "eventCount": 6 }
```

Given the pack and the file: (1) `SHA-256(file bytes)` must equal the
committed `sha256`; (2) for a self-verifying format, re-derive the document's
own integrity under **its** published rule and check the derived head equals
both the file's integrity block and the committed `headHash`. For
`assetdna.export.v1` the published rule is

```
event.hash = SHA-256( prevHash | type | payload | at | propertyId )
```

pipes literal, `at` exactly the ISO-8601 string the file carries, null
`propertyId` as the empty string, genesis `"0" × 64`, events 1-based and
contiguous; the final hash must equal `integrity.headHash`.

**(3) The event COUNT must be checked, in both directions.** The file's
`integrity.events` and the leaf's committed `eventCount` must each equal the
number of events the file actually carries. Until 2026-08-17 both were parsed
and neither was compared, and the consequence is the reason this clause is
normative rather than advisory: an export carrying **zero** events derives a
null head, so `derivedHead !== integrity.headHash` and
`derivedHead !== committed headHash` both become `null !== null` — false — and
every check passed. A file claiming 412 events and carrying none verified, and
was reported as *"0 events re-derived from genesis under the published rule."*

**(4) An empty export is `INCOMPLETE`, never `VERIFIED`.** A file with no events
evidences nothing about the asset; the digest matching says only that it is the
file the pack committed to. There is no chain to re-derive, and reporting a
re-derivation over it dresses an absence as a check having been run. This is an
inability rather than a finding, so it must not be reported as `FAILED` either.

**A second self-verifying format (1.10.0): `txra.agenttrail.v1`**, an agent's
audit trail under §3.3. Its published rule is the IETF draft's own chain: the
file must verify under §3.3 **in full**; `eventCount` must equal the number of
records the file carries; and `headHash` must equal
`hex(SHA-256(JCS(last record)))` — the hash of the trail's final record as
stored, which for a closed session carries the `session_hash` and so commits
every record before it. The trail's every check and every not-checked line
MUST be carried into the evidence result: a pack must never make a trail look
more checked than the trail's own verifier says it is. A trail with no records
fails its own rule (a session begins with a genesis record), so clause (4)
above does not arise for it.

A verified external leaf proves *which* record was relied on, down to the
byte — never that the record is true.

### 2.3 `provenance.data_source` — the leaf that cannot be withheld

Every pack carries a leaf with id `provenance.data_source`, **inside the root**,
whose value is a stable-JSON object:

```json
{
  "status":  "demonstration" | "live",
  "build":   "demo" | "pilot",
  "source":  "mock" | "platform",
  "dataset": "seeded-demo" | "production" | "unknown"
}
```

`status` is **derived, never asserted** — `live` requires all three of the other
fields to be positive: a pilot build, reading a real backend (`platform`),
holding data someone has vouched for. A producer cannot declare a pack real; the
facts have to be true.

`source` names *whether* a real backend was read, never *which one*. `mock` means
no real backend; `platform` means the producer's own datastore. The vocabulary is
deliberately vendor-neutral, so a licensee running its own infrastructure emits
the same two values and a conforming verifier needs no knowledge of anyone's
stack.

The third field exists because the first two describe the **reader**, and
neither can see what is in the database it is reading. A pilot build pointed at
a real backend that had been loaded with demonstration rows satisfied both — and
sealed invented figures as `live`. `dataset` is the **database's own statement
about itself**, so it travels with the data rather than with the build.

`"dataset": "unknown"` means the database carries no such statement, and it
resolves to `demonstration`. Silence is never read as real. The consequence is
deliberate: a genuine production instance has to be marked as one, on purpose,
once — the same rule as every other unrecomputable fact in this format.

**Why it is a leaf and not an envelope field.** §2.0's envelope is deliberately
outside the root so presentation can be re-rendered. That is exactly what makes
it the wrong place for this: an envelope marker can be deleted and the pack still
verifies. Delete this one and the root stops re-deriving.

**A verifier must refuse to withhold it.** Selective disclosure (§2.1) may drop
any other leaf — that is what it is for. Not this one. A disclosure missing it
returns `INCOMPLETE`, because the alternative is a document of proven facts,
against a real root, with a withheld *count* that does not say which, and no
indication the figures were invented. Withholding a price is a commercial
decision; withholding "these facts are not real" is a different act.

**A pack that states no provenance is `INCOMPLETE`, not `VERIFIED`.** The seal
may be perfectly intact — that is reported — but a reader who sees `VERIFIED`
and no warning will assume the numbers describe something real. Silence is never
read as `live`.

If you implement this format: `status: "demonstration"` must be **the most
prominent thing** your renderer shows. The reference receipt puts it above the
verification result, because a reader who takes one thing from the page should
take this rather than the tick.

## 2.4 `txra.reconpack.v1` — a sealed reconciliation

A second pack format, not a widened first one. `txra.ddpack.v1` is about a deal;
this is about a **period and a scope**, and it carries no `dealReference`. The
two share §1's tree and §2.3's provenance rule and nothing else.

It answers: *does the token supply match the register?* — the question a
tokenisation platform's auditors and its clients' lenders ask, and one usually
answered with a screenshot of an admin panel.

**Envelope:** `version`, `subject`, `period`, `scope`, `sealedAt`, `issuer`,
`verification`, `headline` — plus `root`, `leafCount`, `leaves` as in §2.

**Four leaves may never be withheld**, by the producer or by a hand-edited
disclosure:

| id | what it holds |
|---|---|
| `provenance.data_source` | §2.3 — whether the figures are real or demonstration |
| `result.basis` | `register_internal_only` or `register_vs_chain` |
| `result.claim` | the limit of the comparison, in prose, sealed verbatim |
| `chain.adapter` | which supply source answered, of what **kind**, and the reason when none did |

The reason is the same one §2.3 gives, sharpened. A reconciliation stripped of
these reads as a **token** reconciliation whatever it actually was — most
convincingly to someone three forwards downstream who never saw where it came
from. Withholding a listing's price is a commercial decision; withholding *what
was compared* is a different act.

**`chain.adapter` wire format.** JSON, sealed as the leaf's value:

```json
{
  "kind": "rail" | "fixture" | "none",
  "scope": "public" | "producer_devnet" | null,
  "connected": true,
  "adapter": "evm-sepolia",
  "network": "sepolia",
  "contract": "0x…",
  "totalSupply": 100,
  "unavailableReason": null
}
```

`kind` says **what produced the figures**, and it is the field the basis turns
on. It must be stamped by whatever answered, never chosen by the caller:

- `rail` — a blockchain was queried.
- `fixture` — figures supplied by hand, for a demonstration or a test.
- `none` — nothing answered; `unavailableReason` says why.

`scope` says **whose rail**, when `kind` is `rail`, and is null for every other
kind. `rail` deliberately answers only "was a real chain queried" — and a local
devnet the producer boots on their own machine satisfies it: real chain, real
RPC, zero independence from the party whose register it corroborates. The scope
is the sealed record of the difference, stamped by the adapter like the kind:

- `public` — a network the producer does not control.
- `producer_devnet` — a devnet operated by the producer of the document. The
  reading proves the producer's **rail integration** — the figures were read
  from chain state — and is **not third-party supply attestation**. A verifier
  MUST carry that distinction into its own summary: a producer-operated devnet
  must never render like a public network.

A rail-basis document whose chain record states no recognisable scope cannot
support the distinction either way. A verifier MUST NOT resolve the doubt in
the producer's favour: it reports the reading as proving rail integration only,
and says in its limits that the document does not state whose rail was read.

**`basis` is derived, never asserted.** It reads `register_vs_chain` only when
`kind` is `rail` **and** a supply figure came back. Two consequences a
producer cannot escape:

- A producer setting `connected: true` with no supply still gets
  `register_internal_only`.
- A **fixture gets `register_internal_only` however complete its figures
  are.** A fixture may drive every comparison in the document — that is how
  the path is exercised before a rail exists — and may never be described as
  one. Findings sourced from a fixture must name it (`the fixture figures
  report …`), not say `the rail`.

A listing the source did not mention is **absent** from its supply map, never
zero. "This rail does not track that listing" and "this rail says nothing
exists" are different statements, and reporting the first as the second
manufactures a discrepancy the size of the whole listing.

**`period.anchorId` is the LATEST anchor at sealing, not a covering one.**
Anchoring lags the head of the chain by design, so a reconciliation sealed
between anchor runs sits past the last anchored block. The pack therefore seals
the anchor's own range — `period.anchorBlockFrom` / `period.anchorBlockTo` — and
**coverage is the verifier's derivation**: the position is anchored only when
`anchorBlockFrom <= asAtBlock <= anchorBlockTo`.

A verifier MUST NOT treat the presence of an anchor id as coverage, and MUST say
so when the range does not contain the as-at block — this is a statement about
the world, not a defect in the document, so it belongs in the limits rather than
in the verdict. Where the range is absent (a pack sealed before 2026-08-17, when
the leaf was labelled *"Covering anchor"* and chosen by a predicate that never
tested coverage), a verifier MUST report that coverage cannot be derived rather
than assuming it either way.

**Verification recipe:** §2's four seal checks (count, item integrity, root,
version), then:

5. **Limits** — all four leaves above present. Missing any is **`INCOMPLETE`**,
   never `VERIFIED`, even when the seal is perfect.
6. **Basis** — printed in the summary *before* the verdict, so a reader who
   takes one clause takes what was compared rather than the word VERIFIED.
7. **Basis against chain record** — if `basis` is `register_vs_chain` while
   `chain.adapter.kind` is anything other than `rail`, the document is
   **`CONTRADICTED`**: report it as a failure and do not repeat the claim.

   This is a fifth verdict, and it is not a seal failure — the root is intact
   and every leaf is the one that was sealed. It means the **producer** sealed
   a claim its own evidence denies, which a verifier can catch precisely
   because both halves travel in the same document. Tampering is someone
   editing a pack after signing; this is worse, and a verifier that reported
   `VERIFIED (register vs chain)` here would be laundering it.

**A verifying reconciliation does not mean the figures reconcile.** The seal
proves they are the figures that were sealed. Whether they agree is in
`register.*.agrees` and `register.*.findings`, and a document full of
disagreements verifies exactly as well as one without — which is the point: a
tool that refused to carry bad findings would fail precisely when a counterparty
needed it.

**What is never reconciled**, printed on every run: on-chain supply where no
adapter answered; whether the holders named are who they say they are (this
compares quantities, never identities); and anything that happened outside the
platform — a transfer agreed on paper and never entered is invisible to every
figure in the document.

**Conformance vectors (1.12.0):** `reconpack-vectors.json` — eleven documents,
each with the verdict a conforming verifier reaches and the checks that fail:
two valid packs (one whose figures disagree), an edited value, a re-encoded
value, a payload with an extra key, an edited root, reordered leaves, a limit
leaf withheld and one removed, and two packs whose basis their own chain record
does not support.

## 3. `txra.roothistory.v1` — the public anchor-root history

The platform commits its append-only ledger under RFC 6962 roots on a
schedule; the root history publishes every root ever minted, with the
`(id, hash, prevHash)` skeleton of the blocks each covers — hashes only,
no payloads — plus, where present, external witness receipts.

```json
{
  "schema": "txra.roothistory.v1",
  "anchorCount": 1,
  "anchors": [
    {
      "anchorId": 8,
      "blockFrom": 1, "blockTo": 1, "blockCount": 1,
      "root": "<64-hex>",
      "algo": "sha256-rfc6962",          // or "sha256-btc-style", see §3.1
      "anchorChain": "off-chain",
      "anchoredAt": "<ISO-8601>",
      "anchorEventBlock": { "id": 2, "hash": "<64-hex>" },   // or null
      "blocks": [ { "id": 1, "hash": "<64-hex>", "prevHash": "<64-hex>" } ],
      "witnesses": [                      // absent on documents before 2026-08-15
        { "kind": "rfc3161", "provider": "…", "witnessedAt": "<ISO-8601>",
          "token": "<base64 DER TimeStampResp>" }
      ]
    }
  ]
}
```

**Every hash is 64 LOWERCASE hex characters.** Mixed case is refused at the
structure check, because a leaf is SHA-256 of the published string and the
uniqueness rule compares strings — accepting both cases would let one hash be
spelled two ways.

**Genesis `prevHash` is `"0" × 64`.** A history that starts mid-ledger is
valid; the verifier says so rather than failing, and names the unpublished
prefix as something it did not check.

**Witness encoding.** `kind` is `rfc3161`, `rekor` or `evm`. An `rfc3161` token
is the base64 of the DER `TimeStampResp`; verify that it parses as GRANTED and
that its message imprint equals the anchor's `root` decoded from hex. A
`rekor` token is the base64 of the log entry's JSON body; verify that its
`hashedrekord` payload digest equals the same root. An `evm` token is a JSON
object carrying a signed transaction — see §3.1a. **None of the three is
verified against its authority by this document** — that step is yours, against
the authority itself (`openssl ts -verify`, `rekor-cli verify`, any block
explorer or node), and the reference verifier says so on every run rather than
implying it did it for you.

**Verification recipe (all six, in order, fail closed):**

1. **Structure** — schema tag, well-formed anchors, `anchorCount` consistent.
2. **Tiling** — anchors ordered, ranges never overlapping, each anchor's
   `blockCount` equal to its published block list.
3. **Linkage** — across the entire concatenated block list, each block's
   `prevHash` equals the previous block's `hash`. A withheld block breaks the
   very next link, so linkage is also the completeness check. **No block hash
   may appear twice.** On honest data that is free — every block hash commits
   to its predecessor, so a repeat would be a cycle — and it is what makes a
   root minted under the retired construction in §3.1 binding.
4. **Roots** — every root re-derives from its own published block hashes,
   **under the construction its own `algo` field names**: `sha256-rfc6962`
   per §1, `sha256-btc-style` per §3.1. An anchor naming anything else is
   neither skipped nor assumed — see the verdicts below.

### 3.0 The verdicts, and why the middle ones exist

| verdict | exit | meaning |
|---|---|---|
| `VERIFIED` | 0 | every check ran and held |
| `INCOMPLETE` | 1 | nothing contradicted; a check could not be **run** |
| `CONTRADICTED` | 1 | the seal is intact and the document denies itself |
| `FAILED` | 1 | something contradicted |
| `UNVERIFIED` | 1 | the document could not be parsed at all |

`INCOMPLETE` is returned when an anchor names a Merkle construction this
verifier does not implement. That is an inability, not a finding, and calling
it `FAILED` would accuse an honest publisher of forgery for minting under a
newer tree than your copy of the tool knows about. It is still a non-zero
exit: **a check that did not run is not a check that passed.** If you script
this tool, treat exit 0 as the only pass.

`CONTRADICTED` separates *the document was altered* from *the producer sealed
a claim its own evidence denies* (§2.4 step 7). Both are non-zero, and the
distinction is what a reader needs: `FAILED` sends them back to whoever
forwarded the file, `CONTRADICTED` sends them back to whoever issued it.

`UNVERIFIED` means this tool could not read the document — wrong version,
malformed structure. Treat it as unverified, **not** as altered: an
unrecognised format is your tool's limit, not evidence of tampering.

### 3.1 `sha256-btc-style` — the retired construction

Anchors minted before 2026-08-11 were computed under a Bitcoin-*style* tree.
They are append-only history whose roots were honestly computed at the time,
so they are re-derived under that construction rather than re-minted: rewriting
a sealed record to satisfy a verifier is the one thing this format exists to
make impossible.

**Read this carefully — the obvious reading is wrong.** Every operation is on
**lowercase hex strings**, *not* on the raw digest bytes:

```
leaf(h)        = SHA-256( utf8( h ) )                 h = the 64-char block-hash hex string
node(l, r)     = SHA-256( utf8( l ‖ r ) )             l, r hex strings; ‖ is STRING concatenation
                                                       (the preimage is 128 ASCII characters)
odd layer      → duplicate its own last node
empty tree     = SHA-256( utf8( "empty" ) )           a literal magic string, NOT the empty digest
domain separation — none
```

Contrast §1, where RFC 6962 hashes raw bytes with `0x00`/`0x01` prefixes. Read
`SHA-256(left ‖ right)` as byte concatenation here and every root you compute
will differ, and you will wrongly conclude the anchors are forged.

Test vectors — block hashes are `SHA-256("b1")` … `SHA-256("b5")`, so you can
regenerate the inputs yourself:

| leaves | root |
|---|---|
| 0 | `2e1cfa82b035c26cbbbdae632cea070514eb8b773f616aaeaf668e2f0be8f10d` |
| 1 | `d22e9cd6ff821781f0b3f2041416f8aa86328feb0320136532bfe02fccc834cb` |
| 2 | `6f977454ed61f9be3be2f324d6e3a4c68b49dd22d98a8ae1283fca3de430cfbf` |
| 3 | `79886c0c7db6d68e82ee2655addef65562b0dec24b2c877b1cf28909960b65f4` |
| 4 | `b3d04d8e8f328d4917ba4a662c81b707c0dc4aac460ba3c615c703be71db023a` |
| 5 | `7b347a3dbced96b9295792f4ba0ed782ca2919b984bd917fe91677ecb3e10c86` |

**What a re-derived legacy root does not give you.** Duplicating the last node
of an odd layer is CVE-2012-2459: a leaf set and the same set with trailing
leaves repeated produce an identical root. It is not only the trailing *leaf* —
`[A,B,C,D,E,F]` and `[A,B,C,D,E,F,E,F]` collide too, which is why checking that
no block equals its immediate neighbour is not enough. So unlike an RFC 6962
root, a `sha256-btc-style` root does not by itself determine its leaf set.

**What the collision can and cannot do.** Every collision this tree admits
appends a repeat of a trailing subtree. It follows that the honest block list is
always a *prefix* of any colliding alternative: an attacker can invent phantom
blocks on the end, and can never remove, alter or reorder a real one. That was
checked rather than argued — an exhaustive census over short lists found 106,375
colliding pairs and zero exceptions to the prefix property, and a separate
exhaustive search over all-distinct lists (986,409 of them) found no collision
at all.

Which is the bound: every collision requires a published block hash to appear
twice, and **check 3 refuses that**. On honest data the rule costs nothing,
because a chained hash commits to its predecessor and so cannot repeat.

Three honest limits on that reassurance:

1. **The guarantee is structural, not intrinsic.** It comes from check 3, not
   from the tree. A legacy root re-derived by a tool that does not enforce hash
   uniqueness is *not* binding. If you reimplement this spec, the uniqueness
   rule is load-bearing — treating it as a redundant sanity check reopens the
   hole. (Deleting exactly that rule from the reference verifier, and nothing
   else, makes a forged document return `VERIFIED` with every check green.)
2. **The absent domain separation is out of reach only by width.** A leaf
   preimage is 64 hex characters and an internal preimage is 128, and check 1
   refuses any block hash that is not exactly 64 lowercase hex characters, which
   demotes a free substitution to a ~2^128 search. Remove that width constraint
   and the argument goes with it.
3. **Case is part of the format.** Hashes are compared verbatim and a leaf is
   SHA-256 of the published string, so mixed case would let one hash be spelled
   two ways. Check 1 requires lowercase.

Anchors minted from 2026-08-11 use RFC 6962, where the root determines the
leaves on its own. Re-anchoring a legacy range under RFC 6962 remains strictly
better than relying on a rule that lives somewhere else in the file.
5. **Entanglement** — each anchor writes a bookkeeping block one past its own
   range; a later anchor sweeps it in. Except for the newest (awaiting the
   next sweep), each anchor's bookkeeping block must appear, byte-identical,
   in the covering anchor's published list — so every root transitively
   commits to every root before it.
6. **Witnesses** — where receipts are present: an RFC 3161 token must parse
   as GRANTED, sign over exactly the anchor root as its message imprint, and
   carry a signed time matching the recorded one; a transparency-log entry
   must be about exactly the root and its signature must verify under the
   public key it carries. A failed receipt fails the document; an absent one
   is reported, never invented.

**What a verified history means, exactly:** rewriting any covered block now
contradicts every copy of this document anyone holds. **What it does not
mean:** it does not verify block payloads (not published), and receipt
completion — the timestamp token's CMS signature against the authority's
certificate, and the log's inclusion of the entry — is the relying party's
step with standard tooling, deliberately, so it cannot depend on us:

```
openssl ts -verify -in receipt.tsr -digest <root> -sha256 -CAfile <tsa-ca>
rekor-cli verify --uuid <uuid>
```

### 3.1a `evm` — a root published to a public chain

An `evm` witness says one thing: *this root is inside a transaction on a public
network, and here are the exact bytes of that transaction.* The token is JSON:

```json
{
  "v": 1,
  "chainId": 8453,
  "txHash": "0x…",            // 32 bytes
  "rawTx":  "0x02f8…",        // the SIGNED EIP-1559 transaction, verbatim
  "from":   "0x…",
  "to":     "0x…",
  "blockNumber": 21500000,
  "blockHash":   "0x…",
  "witnessedAt": "<ISO-8601>"  // the BLOCK's timestamp, never the producer's clock
}
```

**Why the raw transaction and not just the hash.** A transaction hash on its own
is a pointer: to learn what it carries you must ask a node. The raw bytes make
the receipt self-describing offline — its Keccak-256 IS the transaction hash, so
the bytes and the id it is filed under cannot disagree, and the chain id and the
calldata are read out of the signed payload rather than taken on anyone's word.

**What to check, offline, in order (fail closed):**

1. **Hash binding** — `keccak256(rawTx) == txHash`. Do this first: every later
   finding is then about the transaction the hash names.
2. **Type** — `rawTx` begins `0x02`, an EIP-1559 typed transaction, and its RLP
   body has exactly 12 fields. A different type is refused, not half-read: a
   field list read at the wrong offsets would put the calldata check on the
   wrong bytes.
3. **Chain** — the `chainId` inside the signed payload equals the `chainId` the
   token declares. A signature does not cover a label beside it.
4. **Value** — the transaction moves nothing (`value == 0`).
5. **Calldata** — exactly 36 bytes: the four-byte selector
   `keccak256("anchorRoot(bytes32)")[0..4)` = `0xc50b037b`, then this anchor's
   `root`. Trailing bytes are refused rather than ignored — a transaction that
   also carried something unread is not a witness over a known payload.

`anchorRoot(bytes32)` is a real ABI signature on purpose. Today the transaction
may be a self-send to an ordinary account and the calldata inert; if a registry
contract with that method is ever deployed, the same bytes become a call to it
and every receipt already issued stays readable under one decoder.

**What this document does NOT check, and why that is the point.** It does not
check that the chain included the transaction. That is the whole reason a public
chain is worth using: you can establish it yourself, from a block explorer or
your own node, without this producer's cooperation, now or in ten years. A
verifier that reported inclusion on the producer's say-so would have spent the
only thing the chain buys.

The reference verifier has **no option that does it for you**, and that is a
deliberate limit rather than an unfinished one: the tool makes no network call
at all, so verifying a document you hold cannot be observed, logged or made
conditional by anyone — including the producer. Adding a network call, even an
opt-in one to an endpoint you chose, would change that property, so it is not a
thing the tool acquires as a side effect of a release.

### The block facts are the producer's word

`blockNumber`, `blockHash`, `from` and `witnessedAt` are **not** in the signed
transaction. Nothing in the bytes carries them, so nothing offline can derive
them; they reach the receipt only because the producer wrote them there. This
matters more than it first appears, because they are the fields that look most
like proof — a block height beside a green tick reads as *confirmed*.

They are therefore checked for exactly two things, and reported as **asserted**:

- **Shape.** A block height must be a non-negative integer; a block hash must be
  32 bytes of hex; a sending address must be 20 bytes of hex. A malformed value
  is refused rather than printed.
- **Internal agreement.** A root history files each receipt at a `witnessedAt`,
  and the receipt states one of its own. They must be the same instant. This
  proves the document is coherent — **it does not prove the time is true**, and
  a verifier must not imply otherwise.

⚠ **Do not reason about `witnessedAt` the way you reason about an RFC 3161
`genTime`.** A timestamp authority *signs* its time, so the token itself carries
the authority's word and comparing it means something. A chain does not put its
block time inside the transaction. Both copies here are unsigned producer prose
sitting next to signed bytes, and only a look at the chain settles them.

A conforming verifier MUST NOT present any of these values as established. This
one prints them with the words *"none of which is in the signed bytes or checked
here"*, and names them again in what it did not check.

**The sender is not recovered.** `from` could in principle be recovered from the
signature; this verifier does not do it, and says so rather than leaving a reader
to assume the address was checked. It changes little either way: anyone may
publish anyone's root, so who paid for the transaction is not what makes the
receipt good — inclusion is.

**A test network is a rehearsal, not permanence.** Chain ids `11155111`
(Ethereum Sepolia), `84532` (Base Sepolia) and `421614` (Arbitrum Sepolia) are
test networks: their state is reset or pruned at their operators' discretion and
nothing on them is undertaken to survive. A receipt from one is a genuine
receipt and it is **not** evidence that anything is permanent. The reference
verifier reports that in words next to the receipt rather than letting a passing
check imply the opposite, and it says the same about a chain id it does not
recognise. Treat both as *the pipeline works*, never as *the record will outlive
the producer*.

**`anchorChain` and witnesses are different fields.** An anchor's `anchorChain`
describes how the anchoring run itself recorded the root; an `evm` witness is a
separate, later publication over that root. `"anchorChain": "off-chain"` beside
an `evm` witness is the normal, honest combination and not a contradiction: the
producer's ledger stays the record of authority, and the chain holds a witness
over it. Nothing about a root's meaning changes when a witness is added.

**No personal data is ever published.** The calldata is one 32-byte root and
nothing else — never an event, a payload, or anything derived from a person. A
public chain cannot forget, and a right to erasure is not negotiable; a root
reveals nothing and reverses to nothing. A producer who put anything else in
that transaction would be outside this specification.

### No sample in this kit carries an `evm` witness

Deliberately. At the time of writing no root has been published to a chain, and
a fabricated receipt inside a kit whose purpose is checking receipts would be the
exact thing this kit exists to detect. When a real one exists it will be
published here and this paragraph will say so instead.

**Conformance vectors (1.12.0):** `roothistory-vectors.json` — fourteen
documents: a valid entangled history, then one per check (an edited root,
broken linkage, a withheld block, a repeated hash, a miscount, an upper-case
hash, overlapping anchors, an altered bookkeeping block, an unknown
construction) and four that verify on their own, among them a consistent
rewrite and a withdrawn newest anchor, which only a copy held earlier can
contradict.

## 3.2 `txra.reconhistory.v1` — every reconciliation that was sealed, and every stretch where none was

A single sealed reconciliation (§2.4) answers *did these figures agree on this
date*. It cannot answer *was this register actually being checked* — and a
register checked once and shown forever is the more common failure.

**The attack this format is shaped around is not tampering.** The ledger is
hash-chained and anchored, so an edited seal breaks a root anyone can
re-derive. The attack is **selective sealing**: reconcile daily, and seal only
the days it agrees. Every published pack is then individually true,
individually verifiable, and reports zero disagreements, while the register
could be wrong for most of the year. Nothing inside a single pack can detect
it, because nothing in a single pack is false.

**Shape:**

```json
{
  "schema": "txra.reconhistory.v1",
  "register": "reg_demo_0001",
  "coverage": { "from": "2026-05-01T02:00:00.000Z", "to": "2026-07-30T02:00:00.000Z" },
  "cadence": { "everyDays": 1, "statedBy": "the operator, in its pilot terms" },
  "cadenceCommitments": [
    {
      "ledgerBlockId": 2519,
      "committedAt": "2026-04-22T02:00:00.000Z",
      "everyDays": 1,
      "statedBy": "the operator, in its pilot terms"
    }
  ],
  "entries": [
    {
      "ledgerBlockId": 2540,
      "sealedAt": "2026-05-01T02:00:00.000Z",
      "root": "0101…",
      "leafCount": 37,
      "packVersion": "txra.reconpack.v1",
      "basis": "register_internal_only",
      "asAtBlock": 2536,
      "scopeSelector": "every listing on this register",
      "listingCount": 15,
      "disagreeingCount": 0
    }
  ],
  "claim": "…the limit, in prose, verbatim…",
  "issuer": { "platform": "…", "generatedAt": "…" }
}
```

**The producer states facts only.** No gap count, no longest silence, no
"cadence met" flag, no verdict. Every derived figure is the verifier's to
compute from `entries`. A producer able to assert *no gaps* would assert it.

**`cadence` may be null, and null is not neutral.** Without a declared cadence,
*"we reconcile when we feel like it"* and *"we reconcile daily and hid four
months"* produce identical documents. A history with no cadence is
**`INCOMPLETE`**: the entries may be internally sound, and the document still
cannot support a statement about completeness.

**A lax declaration buys nothing.** The verifier reports the observed longest
silence in days regardless of what was declared, so a history claiming annual
cadence still shows its 300-day gap.

**`cadenceCommitments` — the declaration must be a promise, not a choice made
afterwards.** A `cadence` block is a number written when the document was
written. On its own it cannot excuse a silence: nothing stops an issuer reading
their own entries, seeing a 77-day gap, and declaring a cadence of 90. Every
entry is true, the arithmetic is right, and the standard was fitted to the
evidence — the selective-sealing attack moved from the seals to the standard
they are judged by.

`cadenceCommitments` is the ordered series of cadence commitments recorded on
the register's own chain, each carrying the ledger block that recorded it and
the moment it was made. A commitment made **strictly before** `coverage.from`
cannot have been chosen to suit that window.

- The field is **optional, and absent is not empty.** Absent means the producer
  does not publish commitments; `[]` means the chain was asked and held none —
  the operator promised nothing. A verifier MUST report these differently: only
  one of them is the operator's doing.
- The **whole series is published**, including commitments made during or after
  the window. Filtering them in the producer would hide a promise revised to fit
  its own silence, inside the half of the system that is not trusted.
- The commitment **in force** is the latest one committed strictly before
  `coverage.from`. Where there is none, the declared cadence is unbacked and the
  document is **`INCOMPLETE`**.
- Where a commitment is in force and `cadence.everyDays` differs from
  `everyDays`, the document is **`CONTRADICTED`**: its own published
  commitments contradict its declaration, the entries are sound, and the
  reader's next move is the issuer rather than whoever forwarded the file. This
  holds in **both** directions — a stricter declaration than was committed is
  still a standard nobody committed to.
- A commitment made **inside** the window is reported by name, with its block,
  and does **not** become the standard the silence is measured against.
  Otherwise widening the promise mid-period would work. This specification does
  not say whether such a change is honest; a narrowing is an operator holding
  themselves to more, and a verifier that judged would be wrong often.
- An entry that is not a well-formed commitment (numeric `ledgerBlockId`,
  parseable `committedAt`, positive `everyDays`) is reported and **never counted
  as a promise**. A `cadenceCommitments` that is not an array is treated as
  absent, never as empty.

As with `entries`, this document **cannot prove the series is complete** — a
commitment omitted from it cannot be detected by reading it. The block ids are
what make it checkable against the anchored root history.

**`coverage` is the period a claim is being made about, not the span of the
entries.** A window inferred from the first and last seal can never show a gap
at either end: a register reconciled twice in January and never again would
report a two-day window and perfect cadence. Silence before the first entry
and after the last one counts as silence.

**Verification recipe:**

1. **Schema and structure** — `entries` an array, `coverage.from`/`to`
   parseable, `register` and `claim` present. Otherwise **`UNVERIFIED`**.
2. **Roots** — every entry's root is 64 lowercase hex.
3. **Basis vocabulary** — every entry's basis is one this tool recognises
   (§2.4). An unrecognised one means the tool cannot say what was compared.
4. **Ordering** — `ledgerBlockId` strictly ascending. A repeated id overstates
   the count of reconciliations performed, which is the one figure a reader
   takes at face value.
5. **Coverage** — every entry's `sealedAt` falls inside the window the document
   claims to cover.
6. **Cadence** — declared or not (see above).
7. **Commitment** — resolve the commitment in force (latest strictly before
   `coverage.from`), compare it with the declaration, and report any commitment
   made inside or after the window. No commitment in force ⇒ **`INCOMPLETE`**;
   a mismatch ⇒ **`CONTRADICTED`**.
8. **Observed cadence** — compute, from the entries and the window edges: the
   longest silence and where it fell, and the count of expected reconciliations
   absent. Print them whether or not the cadence was met.

**Report the selective-sealing shape, and report it as an
indistinguishability.** When every entry has `disagreeingCount: 0` *and* the
longest silence exceeds twice the declared cadence, say so — and say that an
honest register which agreed and sealed irregularly produces the identical
document. Naming the ambiguity is the useful act. Deciding it is not this
document's job, and a verifier that accused would be wrong roughly as often as
it was right.

**Two things a pass does not mean**, printed on every run, passing or not:

- **Not that the register reconciles.** Every entry can report disagreements
  and the document still verifies. Whether the figures agreed is inside each
  sealed pack.
- **Not that the list is COMPLETE.** Seal blocks are not contiguous in the
  ledger — other events sit between them — so a document that omits an
  inconvenient reconciliation is internally consistent, and no check here can
  see it. Completeness is established only against the anchored root history
  (§3), by confirming the block range holds no seal the document skipped.

**The exit code is about the document, never about the register.** A history
whose cadence was plainly not met still exits 0, exactly as a reconciliation
full of disagreements does (§2.4). The verdict line says whether the document
can be trusted to say what it says; the silence is the finding, and it is in
the summary, not the exit status. Do not script an alert on the exit code and
believe you are monitoring a register.

**Not handled by the in-browser verifier.** Like §3, this format is checked by
the command-line tool only. The browser page refuses unknown formats as
`UNVERIFIED` rather than guessing, so a receipt handed this document fails
closed.

**Conformance vectors (1.12.0):** `reconhistory-vectors.json` — thirteen
documents: a history whose committed cadence was met, one silent past it, one
with an entry left out that no check can see, the cadence family (none
declared, a declaration that is not the commitment, a commitment made inside
the window, commitments absent and empty) and the structural defects (an entry
twice, entries reordered, an entry outside the window, a malformed root, the
claim missing).

## 3.3 `txra.agenttrail.v1` — an agent's audit trail (the profile of `draft-sharif-agent-audit-trail-01`)

An autonomous agent's session, recorded exactly as the IETF Internet-Draft
`draft-sharif-agent-audit-trail-01` (2026-08-19) lays it out, carried under a
Seal1618 tag so a holder can be handed it, check it offline, and have a sealed
pack commit to it.

```json
{
  "schema": "txra.agenttrail.v1",
  "profile": "draft-sharif-agent-audit-trail-01",
  "records": [ { "record_id": "…", "timestamp": "…", "agent_id": "…", "…": "…" }, … ],
  "integrity": { "terminalHash": "<64-hex>", "recordCount": 5 },
  "issuer": { "platform": "…", "recordedBy": "…|null", "generatedAt": "<ISO-8601>" }
}
```

**The profile adds nothing inside a record and removes nothing.** `records` are
the draft's records exactly as the recorder stored them, in chain order. The
wrapper exists for three things the draft does not do: it names the revision
the records claim to conform to; it states the producer's own view of where
the chain ends (`integrity` — **re-derived by every verifier, never trusted**,
the same role as the integrity block of `assetdna.export.v1` in §2.2); and it
carries an unsealed envelope saying who produced the file, under §2.0's rule
— a pointer, never a trust anchor. A conforming verifier MUST also accept the
draft's native shapes — a bare JSON array of records, and the JSONL export of
the draft's §10.1 (one record per line, chain order) — and MUST verify them
identically. A document is routed on what it is, not on what it was called.

### Hashing

The draft's chain uses **RFC 8785 (JSON Canonicalization Scheme)** and
SHA-256. JCS is defined in terms of ECMAScript's `JSON.stringify`: strings
and numbers serialise exactly as it serialises them, object members are
sorted by the UTF-16 code units of their names, arrays keep their order, and
there is no whitespace. Non-finite numbers are not JSON and MUST be refused
rather than serialised as `null`. **A JSON number is an IEEE 754 double under
JCS**: an integer beyond 2^53 in a document canonicalises as the double it
parses to (`9007199254740993` hashes as `9007199254740992`), whatever a
language with exact integers would keep — a verifier that hashes the exact
integer is not conformant, and the vector `large-integer-detail` carries one.
Producers SHOULD NOT emit integers beyond 2^53; a value that large belongs in
a string. Then, for every record N > 0:

```
prev_hash(N)  = hex(SHA-256(JCS(record(N-1))))        — over the record AS STORED,
                                                        its signature field included
session_hash  = hex(SHA-256(prev_hash(1) ‖ … ‖ prev_hash(N)))
                                                      — RAW 32-byte digests, N = the close record
terminalHash  = hex(SHA-256(JCS(last record)))         — what a pack commits as headHash
```

### Verification procedure

A conforming verifier performs the draft's §6.3 procedure and its structural
rules, and reports each as a **named check** — the reference verifier's names
are given in brackets:

1. **[schema]** The tag and the profile revision. A wrapper naming a revision
   the verifier does not implement is `UNVERIFIED`, by name — never `FAILED`:
   nothing has contradicted anything.
2. **[records]** Every record carries the twelve mandatory fields of the
   draft's §3.1 with their registered vocabularies (action types, §7; outcomes,
   §3.1; trust levels L0–L4; the three phases); `action_detail` is a non-empty
   object carrying the fields §7 REQUIRES for its action type; no field of
   `action_detail` begins with the reserved prefix `aat_`; no record exceeds
   256 KB serialised (§3.3); `nonce`, if present, is lowercase hex of at least
   32 characters; `signature`, if present, is Base64url of exactly 64 bytes; a
   tombstone (§9.3) carries `tombstone_hash`. A record id that is a UUID but
   **not version 4** is reported as a conformance deviation and does not fail
   the trail — the chain does not depend on the version nibble, and the draft's
   own examples are not version-4.
3. **[genesis]** §8.1: the first record is `lifecycle` / `session_start`, with
   `parent_record_id` null, `prev_hash` null and `record_phase` `concurrent`.
4. **[chain]** For every N > 0, `prev_hash(N)` re-derives as above. Where
   record N−1 is a tombstone, `prev_hash(N)` MAY instead equal its
   `tombstone_hash` (§9.3), and the verifier MUST report how many records are
   tombstoned and that their content is gone — a deletion that verifies is
   still a deletion, and the reader is told.
5. **[linkage]** `parent_record_id(N)` equals `record_id(N−1)`; record ids are
   distinct; every record carries the genesis record's `session_id`; a
   `tool_response`'s `parent_call_id` names an earlier `tool_call` in the same
   session.
6. **[timestamps]** RFC 3339 with an offset, monotonically non-decreasing,
   compared **at the precision the document carries** — microseconds are not
   rounded to milliseconds and then called equal.
7. **[phases]** §4.2: `decision`/`denied`, `decision`/`escalated` and
   `delegation`/`denied` are `pre_execution`.
8. **[nonces]** Distinct within the session, where present.
9. **[close]** If a `session_end` record exists it is last, its `record_phase`
   is `post_execution`, its `session_hash` re-derives as above, and its
   `record_count`, if present, equals the number of records. **Without a close
   record the trail verifies as an OPEN SESSION** and the verifier MUST say the
   end is not established by anything inside the document — a trail cut short
   after its last record is indistinguishable from one that ended there.
10. **[signatures]** §6.2: ECDSA P-256 with SHA-256 over `JCS(record without
    its signature field)`, Base64url of the 64-byte IEEE P1363 `r‖s`. Checked
    **only against a key the reader supplies** (the reference CLI's `--key`;
    the browser page's key field). A signature present with no key is reported
    as **not checked**, by name, and does not fail the trail — the draft's
    step 3 is conditional on having the key. A key that cannot be read is
    `INCOMPLETE`. A key carried inside the document is never used: anyone who
    can rewrite the document can rewrite the key.
11. **[integrity]** The wrapper's `terminalHash` and `recordCount` re-derive
    from its records. A wrapper that disagrees with its own records `FAILED`s
    — it contradicts itself.

**Verdicts:** `VERIFIED`; `VERIFIED (OPEN SESSION — no close record)`;
`FAILED` — a broken link is reported in the draft's own word, *tampered*;
`INCOMPLETE` for an inability (an unreadable key); `UNVERIFIED` for a document
that is not a trail, an empty one, or an unimplemented revision. A `FAILED`
verdict **names its failing checks, in procedure order** (`FAILED — chain,
linkage, close`); the conformance vectors state their expected summaries in
that form. `INCOMPLETE` is reached **only when every other check passes** — an
unreadable key over a chain that is also broken is `FAILED`, because a finding
outranks an inability.

### What a conforming verifier MUST report as not checked — on every run

- **The truth of what the records describe.** A verified chain proves this
  is the trail that was written, in this order — not that the agent acted well
  or that its `action_detail` is accurate.
- **Omissions, when the trail is self-recorded** (the draft's §5.1:
  `recording_mode` `self`, or no `recording_component` distinct from
  `agent_id`). A record never written leaves no trace inside the trail. When a
  separate recorder is named, its independence is the deployment's claim, not
  a property of the bytes.
- **The agent's identity.** `agent_id` is a URI the producer asserts; at L0
  and L1 nothing in the document binds it to a key.
- **The clock.** Every timestamp is the recorder's own. `external_timestamp`
  tokens, where present, are not related to any record by this specification,
  because the draft does not state what their message imprint covers.
- **The completeness of the tail**, for an open session.

### What the seal adds — why this is a profile of Seal1618 and not a copy of the draft

The draft gives a trail tamper-evidence **from the inside**: alter or reorder
a record and the next link fails. It cannot give what no self-kept record can
— an outside date and an outside holder. A sealed pack (§2) may commit a trail
as an `external.*` leaf under §2.2 with `format` `txra.agenttrail.v1`, `sha256`
over the file's bytes, `headHash` = `terminalHash` and `eventCount` = the record
count; the pack's root then enters the published root history (§3) and its
witnesses (§3.1, §3.1a). After that, a record omitted or altered contradicts a
document other people already hold, dated by parties other than the recorder.
Verification of such a leaf runs the whole procedure above and carries every
check and every not-checked line into the pack's evidence result.

**No sample in this kit carries a signature.** The producer of the sample
recorded at trust level L0 in `self` mode, unsigned, because that is honestly
what it is; the reference implementation's test suite proves signature
verification both ways with generated keys. A sample that carried a signature
from a key we hold would teach a reader to trust a key that arrived with the
document — the one thing this section says not to do.

### Conformance vectors

Two implementations of this section ship in the kit — the reference
(`txra-verify.cjs`, and the browser page) and `txra_agenttrail.py` — and both
are held to the vectors below. Where they disagree about a document, that is a
finding about one of them, which is the reason both exist.

`agenttrail-vectors.json` carries twenty-one documents with the verdict a
conforming verifier reaches on each and the named check(s) that fail: an
intact trail in each shape this profile accepts (wrapper, native array), an
open session, a tombstoned record, and one document per tamper class —
altered field, reordered records, backdated timestamp, duplicate nonce, a
denied decision recorded after the fact, a wrong session_hash, a record
removed before the close, a record after the close, the reserved prefix, a
wrapper that contradicts its own records, an unimplemented profile revision,
an empty trail — and four signature cases (key supplied, no key, a flipped
byte, an unreadable key). Every value is invented; every hash is real. The
signed vectors are signed with a **test key** whose public half travels in the
file as the reader-supplied key for the "key supplied" cases; it signs nothing
else, and a real agent's key is obtained from its operator, never from a
document. An independent implementation that agrees with every vector on
verdict and failing checks has implemented this section; the
`summaryStartsWith` field is the reference verifier's wording and matching it
is optional. Both reference verifiers are pinned to every vector by test.

## 4. What we refuse to claim

- We do not claim the facts inside a sealed pack are true. We prove which
  facts were relied on, under which written policy, sealed before the outcome
  was known.
- We do not claim "tamper-proof". The claim is tamper-evident, and its exact
  scope is §3 above.
- We do not claim a chain is necessary. Verification here needs none: the
  proof is portable and chain-agnostic, and it holds offline, on no chain and
  on every chain. Where a root is additionally published to a public chain
  (§3.1a) that is a **witness over** the record, never the record itself — the
  register stays private, and no token, on-chain asset or on-chain claim is
  asserted by it.
- We do not claim that a receipt from a test network is permanence. It is a
  rehearsal, the verifier says so beside it, and §3.1a says why.
- We do not claim certifications we do not hold, and our demonstration data
  never names a real body as having registered or certified an invented thing.
- We do not claim a receipt exists where none does. Unwitnessed anchors are
  counted and reported by the verifier on every run.
- We do not claim a verified agent audit trail (§3.3) means the agent acted
  well, or that a self-recorded trail is complete. It proves the trail that was
  written, in that order; the outside date and the outside holder come from the
  seal, and the verifier prints the difference on every run.

## 5. Files in this kit

| File | What it is |
|---|---|
| `txra-verify.cjs` | The reference verifier — one dependency-free file. `node txra-verify.cjs <file>` |
| `sample-pack.json` | A **ten-leaf** synthetic pack (every value invented), including the sealed provenance leaf of §2.3 and two external-evidence commitments under §2.2: the AssetDNA export and the agent audit trail below |
| `assetdna-export-synthetic.json` | A synthetic external-evidence document whose chain is real and re-derives |
| `agenttrail-vectors.json` | Conformance vectors for §3.3: twenty-one documents with the verdict a conforming verifier reaches and the check(s) that fail — intact in each accepted shape, open, tombstoned, one per tamper class, four signature cases with a test key. Regenerated from the producer (`scripts/make-agenttrail-vectors.ts`), never hand-edited |
| `reconpack-vectors.json` | Conformance vectors for §2.4: 11 documents with the verdict a conforming verifier reaches and the checks that fail. Generated by the producer (`scripts/vectors/make-draft-vectors.ts` in the attest1618 repository), graded by this kit's `txra-verify.cjs`, never hand-edited |
| `reconhistory-vectors.json` | Conformance vectors for §3.2: 13 documents, same shape and provenance |
| `roothistory-vectors.json` | Conformance vectors for §3: 14 documents, same shape and provenance |
| `txra_agenttrail.py` | A second implementation of §3.3 in Python — verifier and producer, written from this text, no code shared with the reference. `python3 txra_agenttrail.py verify sample-agenttrail.json`; `python3 txra_agenttrail.py vectors agenttrail-vectors.json`; `python3 txra_agenttrail.py produce --sign key.pem`. Standard library only; signatures through the `openssl` command |
| `sample-agenttrail.json` | A synthetic agent audit trail (§3.3): one DD-copilot session — genesis, a hashed model call and its hashed response, the generated analysis, and a close record carrying the session hash — five records, produced by the same code that writes real trails, under a fixed clock and seeded ids. Every hash is over invented content; the chain is real. Recorded at trust level L0 in `self` mode, unsigned, because that is honestly what the producer is; the verifier's not-checked lines say what that leaves open. The sample pack commits it as `external.agent-trail-1`. `node txra-verify.cjs sample-agenttrail.json` — or hand the verifier its records as a JSON array or JSONL and get the same checks |
| `sample-reconpack.json` | A synthetic sealed reconciliation — `node txra-verify.cjs sample-reconpack.json` routes on its version tag |
| `roothistory-sample.json` | A real root-history document from a development stack — three anchors, entangled, each carrying two real receipts: an RFC 3161 timestamp token and a public transparency-log entry you can fetch yourself by its log index. Regenerated from the producer, never hand-edited |
| `sample-reconhistory.json` | A synthetic reconciliation history (§3.2) that deliberately **does not look good**: fourteen daily seals, then seventy-seven days of silence, every entry reporting zero disagreements — against a daily cadence **committed nine days before the window opened**, so the standard is demonstrably a promise rather than a number chosen once the gap was known. A sample whose cadence was met would teach a reader that the verdict line is where the information is. It is not — the document is well-formed either way. The silence is the information |
| `sample-receipt.html` | The HTML receipt of §2.0.1 for `sample-pack.json` — open it in a browser; it checks itself offline, with the verifier inlined |
| `browser-verify.html` | Every format in this kit, verified in a browser: paste or open a document — evidence pack, sealed reconciliation, root history, reconciliation history, agent audit trail (its wrapper or raw JSONL) — and the page routes it by its tag, exactly as the CLI does, with no network request of any kind. Try it on any sample here |
| `LICENSE.md` | The licence: CC BY 4.0 for this document and the samples, Apache-2.0 for the verifier files, a patent non-assertion, and a names clause. In the kit since 1.12.0; the text is marked pending counsel and binds as written until a later version replaces it |
| `SPEC.md` | This document |
| `SHA256SUMS.txt` | Checksums for every file above except itself |

The pack was described here as "eight-leaf" until 2026-08-16. It became nine
when the provenance leaf was added, and the count went unmaintained — a small
thing on its own, and the same drift that put a wrong leaf ordering in §2 (see
the note there). The kit's own conformance test now counts the files and the
leaves rather than trusting this table.

```
node txra-verify.cjs sample-pack.json --evidence assetdna-export-synthetic.json --evidence sample-agenttrail.json
node txra-verify.cjs sample-agenttrail.json
node txra-verify.cjs roothistory-sample.json
python3 txra_agenttrail.py verify sample-agenttrail.json
python3 txra_agenttrail.py vectors agenttrail-vectors.json
```

Format tags are producer-scoped (`txra.*`); this specification — the Seal1618
protocol — is versioned independently of any product name, including its
first implementer's.

## 6. Compatibility notes

Versioned, dated records of how this protocol relates to adjacent formats.
An entry here records a decision, and says plainly whether the thing decided
has shipped — the two are different facts, and a compatibility note that
blurred them would be a claim this kit could not back.

- **`draft-sharif-agent-audit-trail` (IETF Internet-Draft) — SHIPPED at
  1.10.0 as §3.3, against revision -01** (2026-08-19; the draft expires
  2027-02-19 unless refreshed). *Recorded at 1.8.0 as decided, not shipped;
  that entry is superseded by this one.* What is implemented: the record
  format (draft §3), the pre-execution rules (§4.2), the hash chain (§6.1),
  the signature envelope (§6.2), the chain verification procedure (§6.3), the
  action-detail fields (§7), the session structure (§8), tombstones (§9.3),
  and the JSONL export (§10.1). Not implemented, and not claimed: retention
  (§9.1–9.2), the Syslog and CSV exports (§10.2–10.3), the regulatory mappings
  (§11), and anything about the MCPS agent passports the draft references. The
  profile is checked against the draft's **text**. At 1.11.0 the kit carries a
  **second implementation** of it (`txra_agenttrail.py`, Python, written from
  the text, no code shared with the reference): it agrees with the reference on
  every conformance vector, each verifies trails the other produces — signed
  ones with the other's key — and the two canonicalisers agree on RFC 8785's
  own vectors. What that shows is that the profile is implementable from what
  is written. What it does not show, and is not claimed: interoperability with
  an **independent party's** implementation — both implementations are this
  kit's, and a second author reading the same text differently would still be
  a finding. A stranger's trail that verifies here would be that evidence; none
  has arrived. When the draft moves, the profile string
  moves with it in a new version of this specification; a document naming a
  revision this kit does not implement is `UNVERIFIED` by name. The draft's
  own Appendix A example is illustrative, not a conformance vector: its hashes
  are truncated to sixteen characters and its `session_id` is not a UUID, so a
  conforming verifier refuses it at the record-format check — as this kit's
  does. The conformance vectors this kit publishes (§3.3) are complete.

An Internet-Draft is a work in progress of its authors, not a standard; a
note here records our intent toward it and asserts nothing on its behalf.

## 7. Format version and support policy (adopted at 1.12.0)

Drafted in the protocol's repository on 2026-09-13 and adopted by this
release, which is its first application. Six rules:

1. **A breaking change is a new tag, never a mutation of an existing one.** A
   change is breaking if, after it, a document that conformed to its format and
   verified would reach a different verdict. Such a change ships as a new format
   tag (`txra.<format>.v2`), and the old tag keeps verifying. A verifier
   tightening that only refuses documents which never conformed — 1.12.0's
   exact leaf rule is one — is a new SPEC version naming what it refuses, not a
   new tag.
2. **Every published tag stays verifiable.** In every later release the
   reference verifier keeps verifying every tag it has ever verified, under
   every construction it has ever accepted, retired ones included (§3.1).
3. **A producer keeps emitting a superseded tag for at least 24 months after
   its successor ships**, so a receiver moves on its own schedule. A producer
   may emit both tags during the overlap.
4. **A published version never mutates, and stays readable.** The text under a
   published version line never changes; a correction, however small, is a new
   version with its own note. Each released SPEC stays served at its own
   checksum.
5. **A change is announced in three places in the same release:** this
   header and `CHANGELOG.md`, `SHA256SUMS.txt` regenerated over the released
   files, and the creation1618.com/verify page that serves the release. A
   change that reaches one without the other two has not been released.
6. **The conformance vectors move with each release.** A release that adds or
   changes a rule adds or changes, in that same release, the vectors that
   exercise it. The vectors are regenerated by their producer, never edited.

Questions, or a pack of your own to check: via creation1618.com/connect.
