Save and replay: archive a capture, reproduce the answer
At a glance
Section titled “At a glance”-
Goal: freeze a computed answer into a file that can be stored, handed to a reviewer, and replayed later — and tell replaying the capture apart from merely recomputing the case, including exactly what the checksum covers and what it does not.
-
You need: Show grounds: the capture freezes the answer whose grounds that chapter presents.
-
Run:
Terminal node --import tsx --test test/capture-replay.test.ts -
Files:
Output src/capture.ts CAPTURE_FORMAT deadline-app.capture/1src/replay.ts replayCapture, verifyCaptureIntegritysrc/server.ts POST /api/deadline/replaysrc/cli.ts replay command
Save a calculation
Section titled “Save a calculation”Calculate the worked case and write a capture file. The CLI writes a
capture only when you pass --out:
node --import tsx src/cli.ts evaluate --event 2026-03-06 --days 14 --out /tmp/case.capture.jsonThe period ends on 2026-03-20Computed from the event date and the duration by the pinned canon.rules: TagesfristEndesources: No sources anchored in this canon build — the rule names above carry the provenance.program: sha256:468e3fe17c6a28371c6ce8258ae7928c496593673d4b73a52c2a9c40cb50ee33semantic: sha256:90e53b453107dfee9422a5fe24774823430b5c82c3577b9321a0838a6e54c695result: sha256:ae9a7b19361154c868eb9b28d5ac6395b1d2844053259a1abfd6f75e300de812capture: /tmp/case.capture.jsonReplay the saved case
Section titled “Replay the saved case”Hand the file to replay:
node --import tsx src/cli.ts replay /tmp/case.capture.jsonintegrity: ok — stored document bytes match the recorded document sha256 (input, query, versions, and other metadata are not covered)replay: match — replayed answer matches the captured result hashexpected: sha256:ae9a7b19361154c868eb9b28d5ac6395b1d2844053259a1abfd6f75e300de812actual: sha256:ae9a7b19361154c868eb9b28d5ac6395b1d2844053259a1abfd6f75e300de812The command exits 0 only when both checks pass.
Understand the two checks
Section titled “Understand the two checks”The two messages report two different checks:
integritychecks the stored bytes. It decodes the stored answer document, hashes it, and compares the hash with the checksum recorded next to it. It says nothing about the input, the query, or the versions in the file.replaychecks the result. It runs the frozen query against the frozen case again and compares the freshresulthash with the recorded one. It never reads the stored document bytes.
Replay is not recompute. Recomputing runs the current case through the current canon and shows today’s answer. Replaying takes a frozen capture, checks the stored document bytes, re-evaluates its frozen case input, and compares the fresh answer with the frozen result hash. The comparison is the point: it tells the reviewer whether the same case still answers the same way.
What the capture stores
Section titled “What the capture stores”A capture is the whole evaluated case plus the answer document, under a versioned format name so a future application never misreads an old file:
export interface CapturedEvaluation { format: string; capturedAt: string; model: string; adapter: string; input: FormInput; caseInput: CaseInput; query: AppQuery; evaluationStatus: string; truthStatus?: string; hashes: Record<string, string>; // sha256 over the raw evaluation-document bytes. documentSha256: string | null; // The real document bytes, base64; null when the answer came from a host. documentBase64: string | null; documentNote: string | null; versions: CaptureVersions;}Two groups of fields make the two checks possible. documentBase64
and documentSha256 feed integrity: documentSha256 covers the
stored document bytes — the same bytes documentBase64 carries — and
nothing else. caseInput, query, and hashes.result feed replay:
the frozen case, the frozen question, and the result to compare with.
// Recompute the sha256 over the stored document bytes and compare it// with the recorded checksum. This checks the document bytes alone:// it says nothing about the input, query, versions, or metadata, and// nothing about who wrote the file (bytes and checksum travel together).export function verifyCaptureIntegrity(capture: CapturedEvaluation): IntegrityReport {The application keeps the two reports separate:
export interface IntegrityReport { ok: boolean; expectedSha256: string | null; actualSha256: string | null; detail: string;}
export interface ReplayReport { match: boolean; expectedHash: string | null; actualHash: string | null; evaluationStatus: string; detail: string;}What each check detects
Section titled “What each check detects”Because replayCapture never reads the document bytes, the two checks
fail differently, and the tests pin each cell:
| Edit | Integrity | Replay |
|---|---|---|
| One document byte flipped | fails | skipped |
| Display input only | passes | matches — the edit is not detected |
| Stored case facts | passes | mismatches |
| Stored query | passes | mismatches |
| Recorded result hash | passes | mismatches |
| Version strings | passes | matches — the edit is not detected |
| Document plus its checksum, replaced together | passes | matches — no forgery protection is claimed |
| No document bytes (host answer) | fails | skipped |
“Passes undetected” is not a bug in the table — it is the documented boundary. The checksum proves the stored bytes match their recorded checksum; it does not prove who wrote the file, because bytes and checksum travel together. Version strings are advisory metadata with no external record to check them against. A reviewer who needs provenance must compare against a digest kept elsewhere.
Replay over HTTP and on another machine
Section titled “Replay over HTTP and on another machine”The server exposes the same flow over HTTP: POST /api/deadline/replay
takes a capture id, verifies the stored file, replays it, and returns
the comparison. Export is an explicit user action in the form too: it
downloads only on its buttons — Save summary for the
{view, captureId} answer JSON, Download capture
(GET /api/deadline/capture?captureId=…) for the full stored
archive. Only the full archive is replayable.
Calculate in the form (or by POST), download the full archive, then carry the file to another machine:
curl -s -X POST localhost:8787/api/deadline/evaluate \ -H 'content-type: application/json' \ -d '{"eventDate":"2026-03-06","durationDays":14,"legalTime":"2026-09-17"}' \ -o /tmp/answer.jsonID=$(node -e "console.log(JSON.parse(require('fs').readFileSync('/tmp/answer.json','utf8')).captureId)")curl -s "localhost:8787/api/deadline/capture?captureId=$ID" -o /tmp/capture.jsonStop the first server. In a second checkout with the pinned dependencies installed — no access to the first server’s store — replay the carried file:
node --import tsx src/cli.ts replay /tmp/capture.jsonintegrity: ok — stored document bytes match the recorded document sha256 (input, query, versions, and other metadata are not covered)replay: match — replayed answer matches the captured result hashThe suite pins this portability: the downloaded bytes are copied to a directory outside the server store and replayed from there, with integrity ok and replay match.
Limits and errors
Section titled “Limits and errors”- Comparison stays within one SDK. The TypeScript and Python SDKs
return the same statuses and values, but their canonical bytes and
resulthashes differ. A capture saved by one SDK is replayed and compared by the same SDK only; cross-SDK comparison would report a mismatch on identical outcomes. - A document-checksum failure rejects the file before any evaluation:
a capture whose bytes do not hash to its
documentSha256is not replayed, and no answer is shown from it. - The format string is checked first. A capture naming another format is refused with the expected name, not parsed hopefully.
- Captures of host answers carry no document bytes and always fail integrity with the recorded note. Only local answers are replayable.
Check your understanding
Section titled “Check your understanding”Run the matrix:
node --import tsx --test test/capture-replay.test.tsok 1 - capture round-trips: replay matches the recorded result hashok 2 - tampered document bytes fail integrity, replay is skippedok 3 - an input-only edit passes undetectedok 4 - a case edit passes integrity and mismatches replayok 5 - a query edit passes integrity and mismatches replayok 6 - a versions edit passes undetectedok 7 - an edited result hash mismatches replayok 8 - consistent document-plus-checksum replacement passes both checksok 9 - a foreign-model capture is refused, not mixedok 10 - a host-style capture without bytes fails integrity, replay skipped# tests 10# pass 10# fail 0Predict before you open each answer.
You change the duration inside the stored case facts and replay. What do the two lines say?
integrity: ok — the stored document bytes are untouched. replay: MISMATCH — the edited case computes a different answer, so its
result hash no longer matches the recorded one.
You change only the display `input` block. Does either check notice?
No. Integrity covers only the document bytes, and replay runs the
stored caseInput, not the display input. Both pass; this edit is a
documented blind spot.
Which download do you keep to replay the calculation later?
Download capture — the full stored archive. Save summary holds only
the {view, captureId} answer JSON, which has no frozen case and no
document bytes, so it cannot be replayed.
- Change the model
- Quickstart for the single-file starting point
- Versions and capability matrix and upgrade without losing reproducibility
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.