# Verifying a TBH transaction record

A **transaction record** is a single JSON file exported from The Big Help at the end of a
transaction. It holds the listing as published, the parties, the confidentiality
instruments and their signatures, the document inventory with SHA-256 digests, the
disclosure log, and the segment of the append-only audit log that covers the transaction.
It also carries a sealed manifest: a SHA-256 digest over the whole bundle, plus the number
of rows in each section.

`verify-transaction-record.mjs` recomputes that seal on your own machine. It reads one
file. It uses only Node's built-in `node:crypto` and `node:fs` — no database, no network,
no credentials, no `npm install`, and nothing from The Big Help.

## Running it

```
node verify-transaction-record.mjs <bundle.json>
```

Node 18 or later. Download the script from `/verify/v2/verify-transaction-record.mjs` and
check it against the SHA-256 recorded in `/verify/manifest.json` before you run it.

`v2` verifies records of format `tbh.transaction-record/1` and `tbh.transaction-record/2`.
A record exported today is the bare bundle: save it and verify it directly. A record saved
before 4 September 2026 may be wrapped in the API response envelope, `{ "data": … }`; `v2`
recognises that shape and verifies the record inside it, and prints a note saying so. No
unwrap step is required for either shape.

## Exit codes

| Code | Meaning |
| --- | --- |
| `0` | Intact. The bundle still matches the seal it carries. |
| `1` | Altered or broken. The seal does not match, a section count does not match the rows present, or a link is broken where consecutive rows belong to this transaction. The printed lines say which. |
| `2` | Could not run. No file argument, the file could not be read, or it is not valid JSON. Nothing was checked. |

Treat `2` as "no result", never as a pass and never as a failure of the record.

## What it checks

1. **The seal.** The bundle is re-serialised in the canonical form (object keys sorted,
   dates as ISO strings, `undefined` dropped) and hashed. Any edit to any field changes
   the digest.
2. **The counts.** The sealed manifest carries the number of parties, instruments,
   documents, disclosures and audit rows. A section that has been truncated no longer
   matches its count.
3. **The audit linkage.** Each exported audit row carries the `hash` and `prevHash`
   written by the database at the time it was recorded. The script checks each exported
   audit row for integrity and verifies the chain wherever consecutive rows belong to this
   transaction. Where the preceding audit row belongs to another transaction, the verifier
   reports that boundary rather than treating it as an error.

   The audit chain is one chain across every transaction on the platform. A transaction
   record holds only that transaction's rows, so it is not a complete contiguous slice of
   the global chain: a row's predecessor is often a row that belongs to another transaction
   and is, correctly, not in the record. A format-2 record marks each such row
   (`prevOutsideRecord: true`, sealed into the bundle). The script verifies the link
   wherever two consecutive exported rows belong to this transaction, reports a marked
   boundary as *predecessor outside this record*, fails on a break where continuity is
   expected, and fails on a row marked as a boundary that nevertheless follows the row
   before it. A format-1 record carries no markers, so its linkage is not evaluated; the
   seal and the counts are its verification, and the script says so.

The format is **order-sensitive**. Arrays are hashed in the order they appear, because in
this record the order is part of the evidence. Reordering rows changes the seal, and
reordering the audit segment breaks the linkage as well.

## What it proves

That the file in your hands is byte-for-byte the file The Big Help sealed. Nothing in it
has been altered, reordered or truncated since.

## What it does not prove

- **It does not prove the contents are true.** The seal proves intactness, not
  truthfulness. It says these are the events The Big Help recorded, in this order, and
  that they have not changed since sealing. It does not establish that any document is
  authentic, that any party is who they say they are, or that the transaction completed.
- **It does not prove the record is complete.** An event that was never recorded leaves no
  gap to detect, so a bundle can be intact and still be silent about something.
- **It is not an external witness.** The bundle is sealed by the operator, so it cannot
  show that the operator did not rewrite the history and re-seal it before export. Closing
  that would need a trusted third-party timestamp over the audit chain. There is none
  today.
- **It does not check the audit digests themselves.** Those are computed by a function in
  the issuing database. Reimplementing that function here would create a second definition
  of the same serialisation, and the two would eventually disagree. Linkage is what can be
  checked offline, and linkage is what is checked.

## Versions

Published verifier versions are frozen. `/verify/manifest.json` names the current version
and pins the SHA-256 of every published version. A change to the verifier is published as
a new version directory; an existing one is never edited.

- **v2** (4 September 2026, current) — verifies formats 1 and 2; accepts the bare bundle and
  the historical `{ "data": … }` envelope; segment-aware linkage as described above.
- **v1** (2 September 2026, frozen) — verifies format 1 saved as the bare bundle only; does
  not unwrap the envelope; reports a legitimate cross-transaction predecessor as a break.
  Kept so a pin taken on 2 September still resolves. Use v2.
