# ReserveBeat evidence bundle — format v1 `format: reservebeat-evidence-bundle`, `format_version: 1`. This is revision 1.2 of the document (§10). It adds check T2, which reads the time each verified timestamp leg's TSA signed and compares it with the anchor (§6.4). It changes no byte of any bundle. This document specifies the bytes of an evidence bundle and the checks that verify one. It is written so a verifier can be built from it alone, without access to ReserveBeat's code or database. Every construction below is pinned by a test against the producer (`apps/api/tests/test_evidence_bundle.py`), including the test vectors in §11, which are the producer's own output. A bundle **prepares evidence for examination**. It is not an attestation, and verifying one says nothing about whether the figures in the underlying documents are true. It says whether the records, anchors and self-made payloads are the ones that were committed, in the order and at the times they claim. ## 1. Where bundles come from - `GET /v1/board/evidence/{symbol}/bundle` serves one for a symbol the public board publishes. The route is keyless and returns `404` for a symbol the board does not publish. - The operator's CLI writes the same bundle to a file. The board's evidence lives on ONE chain, the registry chain, which holds the records for every tracked token. A bundle for any symbol therefore carries the whole registry chain. Only the storage-proof payloads (§7) are specific to the symbol. ## 2. Container A bundle is a ZIP archive. Every entry is DEFLATE-compressed, and every entry carries the fixed timestamp 1980-01-01 00:00:00. The entries are: | Entry | Content | |---|---| | `manifest.json` | §3 | | `records.json` | §4 | | `anchors.json` | §5 | | `payloads/` | zero or more payload files, byte for byte as stored (§7) | Every JSON entry is the RFC 8785 (JCS) canonical serialisation of its value: UTF-8, no insignificant whitespace, and object keys sorted by UTF-16 code unit. All hashes in the bundle are SHA-256, written as 64 lowercase hexadecimal characters. The bundle is a pure function of the chain state. The same records and anchors always produce the same bytes. The serving route's strong `ETag` is the SHA-256 of those bytes. ## 3. `manifest.json` | Key | Type | Meaning | |---|---|---| | `format` | string | always `"reservebeat-evidence-bundle"` | | `format_version` | integer | `1` for this document | | `issuer` | object | `id` (UUID string), `kind` (`"REGISTRY"` or `"TENANT"`), `name` (string, or `null` for `REGISTRY`) | | `scope` | object | `symbol`: the board symbol the storage proofs were chosen for, or `null` | | `chain_head` | object or null | `seq` and `record_hash` of the chain head (§4.4) | | `as_of` | string or null | the newest evidence instant the bundle holds (§3.1) | | `counts` | object | `records`, `anchors`, `payloads_carried`, `payloads_withheld` (integers) | | `payloads_withheld` | array | §7.2 | | `files` | object | every other entry's name → SHA-256 of its bytes | **Check M1.** Every entry named in `files` exists in the archive, and the SHA-256 of its bytes equals the value in `files`. An entry other than `manifest.json` that `files` does not name is a failure too. **Check M2.** The entries describe one chain. `issuer.id`, the `issuer_id` of `records.json` and of `anchors.json`, and every anchor's `issuer_id` are equal; `chain_head` equals `records.json`'s `chain_head`; and `counts` equals what the archive holds. ### 3.1 `as_of` There is deliberately no wall-clock "generated at" time: it would make two builds of one chain state differ. `as_of` is the latest of: - the head record's `uploaded_at`; - every anchor's `created_at`, `anchored_at` and `ots_final_at`. It uses the timestamp form of §4.2. ## 4. `records.json` — the hash chain ``` {"chain_head": {...} | null, "issuer_id": "", "records": [ ... ]} ``` `records` holds every record of the chain, from seq 1 to the head, in seq order. Each record has seven fields: | Key | Type | String form | |---|---|---| | `seq` | integer | 1, 2, 3, … with no gaps | | `type` | string | the artifact type, e.g. `"MANIFEST"`, `"STORAGE_PROOF"`, `"ATTESTATION_SOURCE"` | | `period` | string or null | as stored (e.g. `"2026-05"`, `"2026-09-25"`) or `null` | | `content_sha256` | string | SHA-256 of the payload bytes | | `prev_hash` | string | §4.1 | | `uploaded_at` | string | §4.2 | | `record_hash` | string | §4.3; the stored value, which the check recomputes | ### 4.1 Genesis The `prev_hash` of seq 1 is the SHA-256 of an ASCII string. The string is the issuer id, in lowercase hyphenated UUID form, followed immediately by the literal `GENESIS`, with no separator: ``` genesis_prev_hash = SHA256( issuer_id || "GENESIS" ) ``` For every later record, `prev_hash` is the previous record's `record_hash`. ### 4.2 Timestamps `uploaded_at` (and every instant in `anchors.json` and `manifest.json`) is UTC in this exact form, with always six fractional digits and a literal `Z`: ``` YYYY-MM-DDTHH:MM:SS.ffffffZ ``` A verifier hashes this string **verbatim**. It never parses the string and re-formats it. ### 4.3 Record hash The preimage is an object with exactly these seven keys: ``` record_hash = SHA256( JCS({ "issuer_id", "seq", "type", "period", "content_sha256", "prev_hash", "uploaded_at" }) ) ``` - `issuer_id` is the lowercase UUID string. - `seq` is a JSON integer. - `period` is a JSON `null` when absent. The key is never omitted. - The other values are the strings above. The resulting JCS key order is `content_sha256`, `issuer_id`, `period`, `prev_hash`, `seq`, `type`, `uploaded_at`. §11 has two worked records. ### 4.4 Chain head `chain_head` is `{"seq": , "record_hash": ""}`, or `null` when the chain has no records. **Check C1, the chain walk.** For each record, in order: - `seq` equals its 1-based position; - `prev_hash` equals the expected value (§4.1); - the recomputed hash (§4.3) equals `record_hash`. Then `chain_head.seq` and `chain_head.record_hash` equal the last record's. Any difference is a failure, and it names the seq. ## 5. `anchors.json` — Merkle roots and timestamps ``` {"anchors": [ ... ], "issuer_id": ""} ``` Anchors are ordered by creation time, oldest first. Each anchor has these fields: | Key | Type | Meaning | |---|---|---| | `id` | string | anchor UUID | | `issuer_id` | string | as §4.3 | | `kind` | string | `"DAILY"` or `"MONTHLY"` | | `covers_from`, `covers_to` | string | calendar dates, exactly `YYYY-MM-DD` | | `covers_seq_from`, `covers_seq_to` | integer | the seq range the anchor covers | | `merkle_root` | string | §5.2 | | `manifest_sha256` | string | SHA-256 of the anchor's MANIFEST payload (§5.3) | | `status` | string | `"ANCHORED"`, `"PENDING"` or `"LATE_ANCHOR"` | | `created_at`, `anchored_at`, `ots_final_at` | string or null | §4.2 form | | `tsa` | object | `primary` and `secondary` legs, each `{provenance, note, token_b64, ca_chain_b64}` (§6) | | `ots_proof_b64` | string or null | base64 OpenTimestamps proof (§6.3) | Every `*_b64` value is standard base64 (RFC 4648 §4, with padding) of the bytes exactly as stored. A `null` means "none stored". ### 5.1 Anchor payload The anchor payload is what the timestamp legs timestamp: ``` anchor_payload = JCS({ "issuer_id", "kind", "covers_from", "covers_to", "covers_seq_from", "covers_seq_to", "merkle_root", "manifest_sha256" }) digest = SHA256(anchor_payload), as 64 lowercase hex ``` The eight values are taken verbatim from the anchor entry. The dates stay `YYYY-MM-DD` strings and the seqs stay integers. §11 has a worked payload. ### 5.2 Merkle tree The tree is RFC 6962 §2.1 (Merkle Tree Hash), cited rather than restated: - a leaf hash is `SHA256(0x00 || leaf)`; - an interior node is `SHA256(0x01 || left || right)`. The producer builds the tree bottom-up and promotes an odd last node unchanged, which gives the same root as RFC 6962's split at the largest power of two. Both the §11 vector and a test pin this against a direct transcription of the RFC. A leaf is the 32 **bytes** of a hex hash, never the 64-character string. The leaves depend on the anchor's kind: - **DAILY:** the `record_hash` of each record from `covers_seq_from` to `covers_seq_to`, in seq order. - **MONTHLY:** the `manifest_sha256` of each DAILY anchor the monthly rolls up, in `covers_seq_from` order. That is the list in the MONTHLY manifest's `daily_anchor_manifests`. RFC 6962 defines the root of an empty tree as `SHA256()`. The producer never builds one, because an anchor always covers at least one leaf. A zero-leaf anchor is malformed. ### 5.3 MANIFEST payloads Each anchor's MANIFEST payload is carried as `payloads/` (§7). It is JCS JSON. - **DAILY:** an object with `issuer_id`, `kind` (`"DAILY"`), `covers_from`, `covers_to`, `covers_seq_from`, `covers_seq_to`, `merkle_root` and `artifacts`. `artifacts` lists every covered record, in seq order, as `{seq, record_hash, content_sha256, type}` (`seq`, `record_hash`, `content_sha256`, `type`). - **MONTHLY:** an object with `issuer_id`, `kind` (`"MONTHLY"`), `period`, `merkle_root` and `daily_anchor_manifests`. `daily_anchor_manifests` lists the rolled-up DAILY anchors as `{anchor_commit_id, covers_from, covers_to, covers_seq_from, covers_seq_to, merkle_root, manifest_sha256}` (`anchor_commit_id` is the DAILY anchor's `id` in `anchors.json`). The MANIFEST is itself a chain record. It lands one seq **after** the range its anchor covers, and the next anchor covers it. **Check A1, per anchor.** - The MANIFEST payload is present. If it is withheld (§7.2), the anchor is UNVERIFIABLE, which is a failure. - Its SHA-256 equals `manifest_sha256`. - DAILY: its `artifacts[].record_hash` list equals the chain's `record_hash` values over the covered range. - MONTHLY: its `daily_anchor_manifests` equals the DAILY anchors in `anchors.json` whose seq range overlaps the monthly's range, in `covers_seq_from` order, field for field. - The Merkle root recomputed from those leaves equals both the anchor's `merkle_root` and the manifest's `merkle_root`. **Check A2, coverage.** - Let `anchored_to` be the largest `covers_seq_to` over all anchors. - Every record past `anchored_to` is "not timestamped by any anchor", with one exception: a `MANIFEST` record whose `content_sha256` is some anchor's `manifest_sha256`. That record is attested by its own anchor's payload. - A chain with records and no anchors at all is a failure. - The other records past `anchored_to` are the **tail**. Its age is `as_of` (§3.1, recomputed from `records.json` and `anchors.json`; the manifest's copy is not read) minus the earliest `uploaded_at` in the tail. - **PENDING** when the age is at most the anchoring deadline (§5.4). Not a failure. The verifier states the tail's size, its seq range and its age, and that its records are not yet timestamped. - **OVERDUE** when the age is more than the deadline. A failure: the bundle itself shows the producer went past its deadline without anchoring. - A tail whose age cannot be computed (an instant not in the §4.2 form) is a failure. Revision 1.0 of this document failed every tail. A verifier that still does is stricter, never looser, so every success it reports is still a success; `rb_verify.py --require-anchored` keeps that reading on request. ### 5.4 The anchoring deadline The producer runs a DAILY anchor for every chain once a day, in its nightly run at 03:00 UTC. Each one covers the chain from the end of the previous anchor's range to the chain head at that moment (§5.3), so a record waits at most one cadence, 24 hours, for the anchor that covers it. A bundle cut between two anchors therefore always carries a tail. The **anchoring deadline is 30 hours**: the 24-hour cadence plus 6 hours of grace for the nightly run's own duration and a late start. It is a constant of this document and never a value a bundle states, because a producer does not get to set the deadline its own evidence is judged against. It plays the part of Certificate Transparency's Maximum Merge Delay (RFC 6962 §3), the published bound within which a log must merge an entry it has promised to include. A PENDING tail is disclosed, not verified. Until an anchor covers them, the tail's contents and `uploaded_at` values are the producer's word, as an OpenTimestamps proof that is still pending on its calendar is only a promise. The verdict is judged at the bundle's own `as_of`, never at the time the verifier runs, so the same bytes always give the same verdict. To see whether a PENDING tail has since been anchored, fetch a newer bundle. ## 6. Timestamp legs ### 6.1 RFC 3161 legs (`tsa.primary`, `tsa.secondary`) Each leg has four fields: - `token_b64`: the DER `TimeStampResp`; - `ca_chain_b64`: the PEM CA bundle stored beside it; - `provenance`: which backend produced the leg; - `note`: the reason recorded for an absent leg. The provenance vocabulary is closed: | `provenance` | Meaning | |---|---| | `EXTERNAL_RFC3161` | a third-party TSA answered over HTTP | | `SELF_SIGNED_LOCAL` | a throwaway local CA (offline/demo mode). It verifies cryptographically and is **not** third-party evidence | | `CONTRACTED_STUB` | the contracted-TSA slot. No vendor is signed, so it never holds a token | | `null` | a leg recorded before provenance was stored | **Check T1, per leg.** - **No token:** the leg is a disclosed absence. It is counted as absent, never as verified. - **A token and no CA chain:** the leg is NOT VERIFIED, which is a failure, unless the verifier's user supplies trust roots for it (revision 1.1). Then the command below runs with that file as `-CAfile`. Examples of such a file are the system's CA bundle, or the root the TSA publishes. The output says that the leg was verified against the user's roots. This is the ordinary RFC 3161 arrangement, where the relying party chooses what it trusts. It applies only where the bundle stores no chain, and never replaces a stored one. `rb_verify.py --tsa-ca FILE` is this option. - **A token and a CA chain:** run the command below, where `` is §5.1's digest. Exit 0 means verified. ``` openssl ts -verify -digest -in -CAfile ``` A verified leg is tallied by its `provenance`, never by the shape of its token. An anchor with no verified leg is a failure. ### 6.2 What `status` means `ANCHORED` means at least one TSA leg returned a token. `PENDING` means none has yet. `LATE_ANCHOR` means the first token arrived after the late-anchor threshold. The status is a claim, and T1 is how a verifier tests it. ### 6.3 OpenTimestamps `ots_proof_b64` is an OpenTimestamps proof over the anchor payload bytes (§5.1) as a file. Checking it needs the external `ots` client and a Bitcoin node (`ots verify`), so it is optional. A proof may be pending. `ots_final_at` is the instant the producer first saw it carry a Bitcoin attestation. ### 6.4 The TSA's own time (check T2) T1 proves that a TSA signed the anchor digest. The time it signed is inside the token: the `genTime` of its `TSTInfo` (RFC 3161 §2.4.2), with an optional `accuracy`. T2 reads that time, prints it beside the leg's T1 verdict, and compares it with the bundle. The time is read from the `TSTInfo` that the token's signature covers: `TimeStampResp` → `timeStampToken` (CMS `SignedData`) → `encapContentInfo.eContent`. Its `messageImprint.hashedMessage` must equal §5.1's digest. `openssl ts -reply -in -text` prints the same value as `Time stamp:`. A verifier reads the time only from a leg T1 verified. The TSA's time is an interval, not an instant: ``` early = genTime - accuracy late = genTime + resolution + accuracy ``` `accuracy` is the token's own, or zero when the token states none. `resolution` is one unit of genTime's last digit: 1 second when genTime has no fraction, because a TSA may truncate to the second (the producer's offline TSA does), 0.1 second for one fractional digit, and so on. **Check T2, per verified leg.** - **Not before the records.** `late` is not earlier than the newest `uploaded_at` among the records the anchor covers (`covers_seq_from` to `covers_seq_to`). A token signed before a record it covers was committed is a failure: that record, or its `uploaded_at`, is not what was timestamped. - **Near `anchored_at`.** The distance from `anchored_at` to the interval is at most the **TSA time bound, 600 seconds**, in either direction. - A genTime that cannot be read, an `anchored_at` that is `null` or not in §4.2 form, and an anchor none of whose covered records is in the bundle are failures, never passes. **Why 600 seconds.** The producer requests both legs and the OpenTimestamps stamp in one anchoring run. It records `anchored_at` once that run has every answer, whether the run is the anchor's first or a later retry of a `PENDING` anchor (§6.2). It tries a TSA at most three times, with a 30-second timeout and backoffs of at most 10, 20 and 30 seconds, and it gives the OpenTimestamps stamp at most 120 seconds. By design, then, a leg's genTime precedes `anchored_at` by at most about 270 seconds, and follows it only by the difference between the TSA's clock and the producer's. On 2026-10-03, over 57 legs of the production USDC bundle (11 DigiCert legs), a development bundle and the demo fixture, genTime preceded `anchored_at` by 0.0 to 6.9 seconds. The bound is the designed worst case with more than twice that margin. Like the anchoring deadline (§5.4), it is a constant of this document and never a value a bundle states. **What a pass shows.** For an `EXTERNAL_RFC3161` leg, a pass means a third party's clock places the anchor digest, and so every record the anchor covers, no later than `late`, and places the producer's `anchored_at` within 600 seconds of the TSA's time. It does not date any one record more closely than that: a record's `uploaded_at` stays the producer's word, bounded above by its anchor's TSA time. For a `SELF_SIGNED_LOCAL` leg, genTime is the producer's own clock, so a pass shows consistency and nothing more. ## 7. Payloads ### 7.1 What is carried Only two kinds of payload are carried, as `payloads/`: - every MANIFEST that an anchor names; - the STORAGE_PROOF behind each in-scope token's newest proven supply snapshot. For a registry bundle, the in-scope tokens are the scope symbol's published tokens. For a tenant bundle, they are the proofs behind the tenant's contract checkpoints. Every other record is in the bundle **by its content hash only**. That includes every third-party document, such as issuer attestation PDFs (`ATTESTATION_SOURCE`) and bank or custodian statements. To check such a document, obtain it from its publisher and compare `SHA256(file)` with the record's `content_sha256`. **Check P1.** For each carried payload, the SHA-256 of its bytes equals its file name. That value is the `content_sha256` of at least one record. ### 7.2 Withheld payloads A payload the producer could not hand back verified is listed in `manifest.payloads_withheld` as `{seq, type, content_sha256, reason}`. It is never silently omitted and never substituted. The reason is one of: | `reason` | Meaning | |---|---| | `PURGED_BY_RETENTION` | the payload was deleted under the retention policy. The record stays in the chain | | `ABSENT_FROM_STORE` | the payload file is missing | | `STORE_HASH_MISMATCH` | the stored bytes no longer hash to the record's `content_sha256` | ### 7.3 STORAGE_PROOF A STORAGE_PROOF payload is JSON with these fields: - `header`: a finalized Ethereum block header; - `proof`: `eth_getProof` output with `accountProof`, `storageHash`, `codeHash` and `storageProof[{key, value, proof}]`; - `contract_address`; - `slot`: the total-supply storage slot; - `total_supply`. It also carries descriptive fields such as `token_symbol` and `block_number`. Keccak-256 below is Ethereum's (original Keccak padding), **not** NIST SHA3-256. **Check S1.** RLP-encode the header fields in consensus order, then hash with Keccak-256. The result equals `header.hash`. The order is: - the 15 pre-London fields, each required; - then, when present, `baseFeePerGas`, `withdrawalsRoot`, `blobGasUsed`, `excessBlobGas`, `parentBeaconBlockRoot` and `requestsHash`. Quantities are encoded minimally, big-endian. **Check S2.** Walk the Merkle-Patricia `accountProof` from `header.stateRoot` with key `keccak256(contract_address)`. The account RLP `[nonce, balance, storageHash, codeHash]` matches `proof.storageHash` and `proof.codeHash`. **Check S3.** Walk the `storageProof` entry for `slot` from `storageHash` with key `keccak256(slot as 32 big-endian bytes)`. The RLP-decoded value (absent = 0) equals both that entry's `value` and `total_supply`. ## 8. Exit contract for a verifier - A verifier reports every failure it finds and keeps checking after the first. - Its tally counts timestamp legs as verified (split by provenance), present but not verified, and recorded absent. - It reads a TSA's time only from a leg it verified, and fails a verified leg whose time it cannot read or compare (§6.4). - A record or anchor that lacks a field §4 or §5 lists, or holds a value of another type, is a failure, and nothing is verified over it. - It states success only when every check above that applies has passed and every chain head is covered (A2) or its tail is PENDING. A success over a PENDING tail says so in the same statement: how many records, and that they are not yet timestamped. `rb_verify.py`, beside this document, is a verifier built to this contract. Its exit status is 0 on success, 1 when any check failed and 2 when the input is not a readable format-1 bundle. With `--require-anchored`, a PENDING tail is a failure too. ## 9. What a bundle deliberately does not contain - Third-party documents (by hash only, §7.1). - Any tenant's records in a public bundle: the public route serves only the registry chain, for symbols the board publishes. - Database identifiers beyond the issuer and anchor UUIDs. - Storage locations, and anything else about how the producer stores data. ## 10. Versioning `format_version` is an integer. A change that alters any byte a check reads, or the meaning of a field, is a new version. Older versions stay verifiable, and their specification stays published beside the new one. Adding a manifest key that no check reads is not a new version. A change to how a check judges bytes that stay the same is a **revision** of this document, numbered `.`, and is listed here: | Revision | Date | Change | |---|---|---| | 1.0 | 2026-09-25 | First publication. | | 1.1 | 2026-09-26 | A2 judges the tail: PENDING inside the 30-hour anchoring deadline, OVERDUE past it (§5.3, §5.4). Revision 1.0 failed every tail, so a bundle cut between two daily anchors could never pass. T1 may verify a leg with no stored CA chain against trust roots the verifier's user supplies (§6.1). | | 1.2 | 2026-10-03 | New check T2 (§6.4): each verified RFC 3161 leg's genTime is printed, and fails when the TSA signed before the newest record the anchor covers, or more than 600 seconds from `anchored_at`. Every success under 1.1 was T1 over the same tokens, so T2 only adds failures, never removes one. | ## 11. Test vectors These are the producer's own outputs, and a test recomputes each one. All inputs are ASCII. Each `*.jcs` value is the complete canonical byte string, shown as text. ```text genesis.issuer_id = 3f2c1e9a-7b4d-4c8e-9a1f-2d6b5e8c0a17 genesis.prev_hash = 4ff9b4a7ebec8a739ea958b24d4176db59ab0fd40b5bcce8bdca7c4866318dbd ts.microseconds = 2026-05-31T23:59:58.123456Z ts.whole_second = 2026-06-01T00:00:00.000000Z record1.content = example payload one record1.jcs = {"content_sha256":"fb9e3b23c51240952f156da0e187c66285cae6d0e18f536fc04ea61163865661","issuer_id":"3f2c1e9a-7b4d-4c8e-9a1f-2d6b5e8c0a17","period":"2026-05","prev_hash":"4ff9b4a7ebec8a739ea958b24d4176db59ab0fd40b5bcce8bdca7c4866318dbd","seq":1,"type":"BANK_STMT","uploaded_at":"2026-05-31T23:59:58.123456Z"} record1.record_hash = 3c9aef969f1a10cd87fc05dfd8c14c5d3bb8563bc829d516402bfa6331cc2819 record2.content = example payload two record2.jcs = {"content_sha256":"f3b093049dbc46d6b933736f47bb19a9b016942b94c43acc0493dd633c241386","issuer_id":"3f2c1e9a-7b4d-4c8e-9a1f-2d6b5e8c0a17","period":null,"prev_hash":"3c9aef969f1a10cd87fc05dfd8c14c5d3bb8563bc829d516402bfa6331cc2819","seq":2,"type":"MANIFEST","uploaded_at":"2026-06-01T00:00:00.000000Z"} record2.record_hash = 4f0bc16c8ab120bf961b7d5dcf25188a4eaad94c6f75ebbae15744fbe838501e merkle.root_of_record1_record2 = 18f380d0c7e39dfd2cfc9ba9fdff7e31cf61ece67850fc66de4c803700c0c433 merkle.root_of_record1_record2_record1content = f136196816314bd89e59ccce91577d8c37a50316b4b1bd92d857d7b20a333259 anchor.manifest_content = example manifest anchor.jcs = {"covers_from":"2026-05-31","covers_seq_from":1,"covers_seq_to":2,"covers_to":"2026-06-01","issuer_id":"3f2c1e9a-7b4d-4c8e-9a1f-2d6b5e8c0a17","kind":"DAILY","manifest_sha256":"89333bc71f02c647533ab52e592ad22c609728f12eabff6fda0920109db40401","merkle_root":"18f380d0c7e39dfd2cfc9ba9fdff7e31cf61ece67850fc66de4c803700c0c433"} anchor.digest = c9be6814794a125078c21ad21d85735618166ae91264e9c1554938410618f6a2 ``` How the vectors fit together: - `record1.content_sha256` is `SHA256("example payload one")`, and `record1.prev_hash` is `genesis.prev_hash`. - Record 2 follows record 1, with `period` `null`. - The first Merkle root is over the two record hashes. The second adds a third leaf, record 1's `content_sha256`, which exercises odd-node promotion. - The anchor covers seqs 1–2 with the first root. Its `manifest_sha256` is `SHA256("example manifest")`.