Read a witness's chain of statements in audit verify (gateway ADR-0013, PR 4 of 6) - #228
Conversation
…heir digests The vectors of gateway ADR-0013's PR 1 (corpus/witness/ at c916ee9) are copied verbatim into internal/audit/testdata/witness/, for the reader of a witness's statements that audit verify gains next. testdata/witness.lock.json records the source repository, commit and path, and each file's size and SHA-256; a test holds the directory to it, so a file added, removed or edited without the lock fails. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…teway's 54 vectors A new reader in internal/audit (witness.go) reads what a checkpoint witness serves (gateway SPEC.md §8, ADR-0013): each statement held to its form by its JSON value, its signature checked once under the key its keyId names among those supplied, by the runtime's own key rule (CheckPublicKey) and equation, over "judgment-pack-gateway/witness/1:" and the statement's canonical form without its signature; then equivocation, the chain rule from index 0, and the head. The bounds of one reading are applied first and refuse rather than truncate: 16 keys, 64 MiB, and 110,000 statement lines counted as the files are split, stopping at the line past the bound. A continuation, the last statement and the latest checkpoint statement of an earlier successful reading, is read too: its two statements join the set and are checked like the others, the chain goes on from the index after its last, and a head below that index is witness-head-behind. A statements file holding a statement at or below it is refused before any signature is checked. The checkpoint member rule ParseCheckpoint applies is factored out, unchanged, so a statement's checkpoint is held to it without the bound of a checkpoint document. Every one of the gateway's vectors reads with the answer it states, compared as short strings; nothing in audit verify uses the reader yet. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…l with them jpack audit verify gains --witness-key, --witness, --witness-head, --witness-resume, --witness-save and --require-countersigned-through (gateway ADR-0013 §6). The statements are read against the identity the trail's chained records carry. The checkpoint of every checkpoint statement that verifies is held to the trail as a held checkpoint is, with the same four findings; a chain with no witness finding is credited, and a credited checkpoint joins the held ones for checkpointed, witnessed and --require-checkpoint-through. A chain with any witness finding is credited nothing. The coverage gains countersigned (not-checked, failed, through or none), the payload gains witness (the statements read and checked, where the reading began and ended, the latest checkpoint statement, the conflicts, whether the chain is retired) and requiredCountersigned, and an unmet requirement is countersigned-coverage-missing. A verification with no finding at all saves a continuation when asked, through a file renamed into place. The report states the record's fixed sentences, and two of this runtime's for what the record decides without giving words: a reading that credits no line, and a conflict statement. A report without --witness-key keeps every sentence it had; its coverage gains countersigned, not-checked. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…ves, and the continuation to its own rules Tests for gateway ADR-0013 determination 6, over chains signed in the test under a key of its own: a conflict at 100 after checkpoints at 100 and 200, continued from index 1; a conflict above the latest checkpoint, which fails either way; and a long run of conflicts after the last checkpoint, which no one step holds, each read whole and in steps with the step's bound lowered, giving the same findings and coverage. Each of the continuation's rules is broken once: a head at its last index that matches and one that differs, a head below it, statements at or below it supplied, its checkpoint held again after the trail copy changed, a statement after a retirement, a latest checkpoint statement of another kind, above its last or not the latest, either saved statement failing its signature or shape, a failing step saving nothing, and its two statements counted in the bound. The trail comparison: a rewrite caught by an earlier statement although the rewrite's later checkpoint was signed, and the same chain read late; a trail cut short; another record at a witnessed sequence; a chain with a finding whose checkpoints are still held; a copy of another trail. The report: credited checkpoints joining the held ones, countersigned coverage and its sentences word for word, current, historical and continued readings, nothing credited on a finding, and the requirement met and unmet. The split stops at line 110001 of 4194304 one-byte lines, keeping 110000. The conflict sentence is reworded so its count reads in one form. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…d their exit codes Through the command, over a project's own trail and statements whose bytes the test spells out from the gateway's SPEC.md §8.3: a reading to the head the reader fetched, credited and countersigned, its continuation saved byte for byte; the next reading continued from it with only what the witness signed since, a conflict among them; the human report's witness lines; every fixed sentence word for word; and a step that fails, by a stale head, an unmet requirement or a refusal, leaving the continuation as it was. The flags without --witness-key, seventeen keys refused before any is read, each key the key rule refuses with its reason, the files' bytes bounded by their sizes before any is read, the statements over their bound, a statement at the continuation's last, standard input and remote paths, and a statement of another trail. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…ss's statements establishes The guide gains "Reading a witness's statements", after the stamping section: what a witness is and that no gateway release serves its statements yet, one worked command, the key, the statement, the chain, the head, what is credited and the countersigned coverage, reading in steps by continuation, the bounds and their refusals, the report's witness section, and what a credited statement establishes and does not, linking the gateway's SPEC.md §8 and ADR-0013 at the commit the vectors came from. The sentence on attempts now names --witness-key among what may be supplied. The README gains a paragraph, the audit group's help a clause, and CHANGELOG.md an Unreleased entry with the flags, findings, refusals, codes and the two sentences of this runtime's own. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…refusal made by the files' sizes Each form of a statement just inside and just outside it: the largest index and one past it, a fraction, a previous signature of no form or a number, a time in its form that no calendar holds and one of another form, a keyId in upper case, another kind, a checkpoint at sequence 0, a member given twice, an escaped name and surrounding spaces. Each chain rule at its edge: a conflict at the latest checkpoint's own sequence, a checkpoint below the latest, and a head one and two indexes past the statements supplied. audit verify's refusal over the byte bound now says, when it is made by the files' sizes before any is read, how many bytes they hold together, and, when a file grew while it was read, that it grew; the test holds the first. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
… rule A mutation that let a retirement at the head of a chain pass survived every test: no vector or test held a retirement with no checkpoint statement before it. One now does, and fails the chain. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
… tests to it A continuation whose latest checkpoint statement stands above its last is broken by the walk after it too, by the rule that checkpoint sequences increase, so a mutation that dropped the continuation's own rule survived on the finding's name alone. The continuation's rules are now held by the detail that names each, as the report gives it. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…panic A test that reads the first finding's detail of a report with none now fails as a test, not by an index out of range, so a mutation it catches is caught by its assertion. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…n runs past the head A mutation that named the highest index read in place of the head's survived: every current reading tested ended at its head. A chain that runs past the head it was read to now holds the sentence to the head's index. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
… endings On Windows the checkout ended each line of the copied vectors with a carriage return, so the copy no longer matched the lock of its digests, and a vector's files were not their bytes. The vectors are marked -text, as the release-pinned artifacts are. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…d, and check both of a continuation's statements under every corruption Review round 1 found that every member of a statement was decoded into a map before its closed shape was checked: a 4.69 MB statement of 400,000 unknown members allocated about 150 MB before it was refused. A statement, its checkpoint and a continuation are now read a member at a time from a token stream: the first member whose name the form does not allow, or that was given before, ends the reading, and a member whose value must be a string, a number or null is refused at its first token when it is an object or an array, without reading into it. The checks of each value are unchanged, and the checkpoint is still held to ParseCheckpoint's rule. A test holds eight such lines of 4 to 10 MB, statements and continuations, to at most 1 MiB allocated each; they allocate between 1 and 11 KB. The review also found that the continuation tests corrupted a continuation whose two statements were one, so a reader that skipped the check of a latest checkpoint statement distinct from the last would pass them; with that change its probe credited a checkpoint with an all-zero signature. A test now corrupts each of two distinct statements in turn, by a zeroed signature, a member altered after signing, another key and another trail: each is a finding, and nothing is credited. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…ything but a continuation Review round 1 found the save path destructive: --witness-save renamed the continuation onto whatever it named, so with the trail, a witness key, a statements file or the head as its destination the command exited 0 and replaced that input, and a linked parent directed the write through the link. The class is closed in three parts. Every file an invocation reads is now recorded by the identity of the file itself where the App opens it: readInput (which readPack now is), openInput, loadProject for the configuration, and noteInput for the trail and companion files a project opens through its own handle, standard input included when it is a file. audit verify reads every input through these. Once every input is open, and before anything is verified, the file --witness-save names is compared with each of them by os.SameFile, looked up both by its path, following every link, and through its directory's handle; the continuation --witness-resume read is the one it may be. When something is there it must be a regular file, not a symbolic link, holding a continuation by IsContinuation; anything else is refused and left as it is (JPS-INVOCATION-AUDIT-WITNESS-SAVE, exit 3). The directory is opened once, before the inputs are read, and held to the rename: fssecure.Root gains Stat, Lstat and ReplaceByRename, which creates a temporary file beside the name exclusively, writes, syncs and closes it, checks the name still holds what was checked, renames through the handle and syncs the directory, removing the temporary file on every failure. A link among the directories of the path is followed, since a reader may name any directory, and the write goes to the directory opened. os.Root renames from Go 1.25; a build with this module's Go 1.24 floor renames by the held directory's path after checking it still names that directory. Release binaries are built with Go 1.26.5. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…destination Every flag of audit verify is classified as an input or as no file, so a flag added later fails until it is. For each input flag the App's record of what it read is checked to hold that file, and the file named as --witness-save by the same spelling, another spelling, a symbolic link and a hard link is refused, its bytes unchanged and no temporary file left; so are the sidecar and stamps file read beside the project's trail and beside a named trail, and an input read from standard input that is a file. Inputs that hold a continuation, so that only their being read can refuse them, are refused for that: a trail, a sidecar, a stamps file, a statements file, a head, and the resume file given as --witness too. The resume file saved over advances. A continuation that is no input is replaced; a file that is not one, a pack, a directory, a FIFO, a link to a continuation, a dangling link and a path naming no file are refused and left as they are; a directory that is not there is JPS-AUDIT-WITNESS-SAVE; and a save after a finding writes nothing. fssecure's own tests hold ReplaceByRename to creating and replacing whole, to refusing a name that changed after its check, a name that is not one file and one outside the directory, to leaving nothing after a failed write, and to writing to the directory held when its path is re-pointed, or, in a build before Go 1.25, refusing then. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…nd credited, and what a save may replace Review round 1 found the README saying every verified statement's checkpoint is held to the trail and crediting "a statement" with showing the lines existed, where a conflict statement carries a checkpoint offered and refused and is credited nothing. The README, the guide, the CHANGELOG and the command's help now say checkpoint statement where they mean one, and the README says a conflict statement is credited nothing. The guide gains "What a save may replace": the destination held to every input by the file's identity, the resume file the one exception, a file already there replaced only when it holds a continuation, the refusal's code and exit code, and the directory opened once with a symbolic link among its directories followed. The CHANGELOG entry and the help say the same. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…atement or a continuation, to tests The App's record of what it read is now checked for the files no flag names, the project's trail and the files beside the project's trail and a named trail, and for an input read from standard input that is a file; the project's trail is also refused as --witness-save under each spelling. A direct test shows the file a save would replace is looked up through the directory held, so an input there is refused when the path names nothing by the time of the check. A statement followed by another value or a byte is malformed, and whitespace after it is not; so is a continuation followed by another value. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…ever instead of it Review round 2 found that a refused save destination, checked before the trail was read, turned a verification with a finding (exit 1, the finding reported) into JPS-INVOCATION-AUDIT-WITNESS-SAVE (exit 3) with no finding shown. Whether the destination may be written is still decided before anything is written, as before: its directory is opened before the inputs are read and it is held to them once they are open. The verification now runs regardless, and what is reported follows it. With a finding, the report is what it is without --witness-save, exit 1, and the refusal is noted beside it: the witness section's new saveRefused, and a "note:" line in the human report. With none, the refusal is the answer as before, its code and exit code unchanged, and its message says the verification itself found nothing and nothing was saved. A destination whose directory cannot be opened, or that names no file, waits for the verification the same way. A test holds the four combinations, a finding or none and the destination acceptable or refused, to their exit codes, the finding reported as without --witness-save, the refusal noted or answered, and the destination's bytes; the save helper accepts either shape of refusal. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
… read through Review round 2 found that project.ConfigFile looked the configuration up again by its name, so the identity recorded was of whatever was at that name when asked, not of the file opened and read; a file put in its place after loading was recorded instead. The configuration is now read with fssecure.Root.ReadIdentified, which answers the identity of the opened file from its descriptor with the bytes, and the project keeps it. The App's other recording places already take it from the opened descriptor: noteInput, openInput, which records through it, and standard input. A test, the reviewer's turned around, loads a project and opens a statements file, moves each away and puts another file at its name: the file read is still the one recorded, under the name it was moved to, and the file put in its place is not. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…e guide, and claim no more elsewhere Review round 2 asked for the two residues of the save path to be stated where a reader finds them, without machinery for either. ReplaceByRename's comment and the guide's "What a save may replace" now say both: the destination's last check and the rename are two steps, so another process changing that one name in the instant between them is not detected, a file put there then being replaced and a symbolic link put there replaced itself, the file it names untouched; and a build with Go 1.25 or later renames through the directory opened, while a build with Go 1.24 renames by the directory's path after checking it still names the directory opened. The guide also says how a refused destination is reported beside a finding. The README, the CHANGELOG and the command's help said --witness-save "never replaces" or "never names" an input; they now say it refuses such a destination, and the CHANGELOG states both residues. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
…ved, for Windows On Windows a FileInfo from os.Stat finds its file's identity by its path the first time it is compared, so the test's reference to the configuration read, compared only after the move, named the file put in its place, and the test failed there while the code under it held the file read. The reference is now compared at once, while its path still names that file. The App's own records come from File.Stat on the opened descriptor, whose identity Windows fills in from the handle. Part of gateway ADR-0013 (Judgment-Pack/judgment-pack-gateway#199), PR 4 of 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
Cross-vendor adversarial reviewDrafting model: Anthropic Claude Opus 5.5 (the builder agent), 2026-10-05. Reviewing model, every run: OpenAI Runs, all of them. Five were started and three produced results.
Round 1, reviewed commit
|
| # | Severity | Finding | Disposition |
|---|---|---|---|
| 1 | HIGH | audit verify --witness-save <path> renamed the saved continuation onto whatever path it was given. With the trail, a witness key, the statements file or the head file as the destination, the command exited 0 and that input was replaced; a linked parent directory redirected the write. |
Accept, as a class (7f3c85d, abc698f, 127899f). Every file the command reads is recorded by the identity of the opened file, at the places files are opened. Before anything is written, the destination is compared with each of them and refused if it is one, under any spelling or link; the one allowed coincidence is the file --witness-resume read. An existing destination is replaced only if it is a regular file, not a link, that parses as a continuation. The write goes through a directory handle held from before the inputs are read: a temporary file created exclusively there, synced, the name checked again, renamed, the directory synced, and the temporary file removed on every failure. A test requires every flag of the command to be classified as an input or not a file. |
| 2 | MEDIUM | Every member of a statement or continuation object was read into memory before the closed shape was checked: a 4.69 MB line with 400,000 unknown members allocated 149.5 MB before it was refused. | Accept (1eab87a). Statements, their checkpoint and continuations are read member by member and refused at the first member the form does not allow, the first repeated member, or a nested value where a scalar belongs. Lines of 4 to 10 MB are refused within 1 to 11 KB, asserted as a number. |
| 3 | MEDIUM | The continuation test used one statement for both members, so a change that skipped the check of a distinct latestCheckpoint statement passed the tests. |
Accept (1eab87a). The test's two statements are distinct and each is altered in four ways; the reviewer's change now fails it. |
| 4 | LOW | README text credited "a statement" with what only a checkpoint statement shows. | Accept (4f0e706), in README, the guide, the CHANGELOG and the help text. |
Round 1 also established: all 54 gateway vectors read as they state and 516 further cases agree with the gateway's reader; the eight sentences taken from the ADR match it word for word; a report made without --witness-key is byte for byte what it was; the added payload member is allowed under this repository's rule for output version "2"; a silent edit of a copied vector fails the lock test.
Round 2 (narrow), first run at 127899f51c95c99901683cda1111a3940e4c0d61, ended before its verdict
Its results file records the round-1 LOW and both MEDIUMs as resolved, and two new items. A third came from the probe tests it left and was not yet written to the file.
| # | Severity | Finding | Disposition |
|---|---|---|---|
| 5 | MEDIUM | A refused save destination was answered before the trail was read, so a verification with a finding (exit 1) became an invocation refusal (exit 3) with no finding shown. Not a false success. | Accept (ce47031). The verification runs whatever the destination's state. A finding is reported exactly as without --witness-save, with a note when the destination was also refused; with no finding and a refused destination the refusal says that the verification found nothing and nothing was saved. |
| 6 | LOW | The configuration's recorded identity was looked up again by its path, so it named whatever was at that path later and not the file that was read. The continuation-shape rule still protected the original. | Accept (72683b6). The identity is taken from the descriptor the configuration was read through; the other recording places already did so. |
| 7 | Observation | The last check of the destination and the rename are two steps: a file put at that name by another process in between is replaced, and a link put there is replaced without its referent being touched. | Accept as a stated limit (d556217), together with the Go 1.24 limit the author had stated (a Go 1.24 build renames by the directory's path after a check; Go 1.25 and later rename through the held directory; release binaries are built with Go 1.26). Both are now in the comment on ReplaceByRename, the guide and the CHANGELOG, and the documents no longer say "never replaces". No machinery was added. The completion run was told these two are accepted limits and reviewed their wording only. |
Round 2, completion run at b53e5c2da578c21b417149331a4bee12c1d1eb00
Verdict: MERGEABLE. The round-1 HIGH is RESOLVED; findings 5 and 6 are RESOLVED; no new finding in the four commits. Five CLI tests that listen on a socket could not run in the reviewer's sandbox; CI runs them on every platform.
One mutation survives and is stated in the PR body: recording an opened input by looking its name up again at the moment of opening differs from the code only if the name changes in that instant, which no test can schedule.
Local paths in the prompts and outputs below are replaced by labels (<scratch>, <tmp>, <home>), and links to the reviewer's local files are reduced to their text; otherwise they are verbatim.
Round 1, first start (discarded): complete output
FINDINGS: None established. I could not read the diff or run checks because the execution tool crashed on every call:
code-mode host exited with status signal: 5 (SIGTRAP) (core dumped)
Even text("probe"), which invokes no shell command, failed. This blocks all repository inspection and testing. No tracked files were changed.
VERDICT: Undetermined. Neither MERGEABLE nor NOT MERGEABLE is supportable without inspecting the code; no HIGH finding has been established.
Please rerun with a working execution tool. The 12 GB address-space limit may affect the tool’s V8 host, but I could not verify that cause.
Round 1 prompt
You are the cross-vendor reviewer for Judgment-Pack/judgment-pack-runtime pull request #228, "PR 4 of 6 of gateway ADR-0013": `jpack audit verify` learns to read a checkpoint witness's chain of statements. You are in a clone at the PR head e6f8cd8; the base is main at 7a2fcfe. Read the change with: git diff 7a2fcfe...HEAD (27 of the 69 files are vectors copied from the gateway under internal/audit/testdata/witness/).
Context. The design is the gateway's ADR-0013, accepted, and the gateway's SPEC.md §8, merged with 54 vectors; both are readable in a gateway clone at <scratch>/rt13-gw (docs/adr/0013-checkpoint-witness.md, above all §6 "How the runtime's verifier reads it", determination 6 and "The maintainer's answers" 8, 9 and 11; SPEC.md §8; corpus/witness/; the gateway's own readers go/witness.go and verify-ts/src/witness.ts). A witness run by another party signs a statement over the runtime's checkpoint line and chains its statements per trail; this PR makes the runtime read such a chain under keys the reader supplies and report how far a witness's signature reaches. The change adds the flags --witness-key (at most 16, required by the others), --witness, --witness-head, --witness-resume, --witness-save and --require-countersigned-through; the findings witness-malformed, witness-signature-invalid, witness-trail-mismatch, witness-equivocation, witness-chain-broken, witness-head-unreached, witness-head-behind and countersigned-coverage-missing; refusals JPS-AUDIT-WITNESS-REFUSED (keys-over-bound, bytes-over-bound, statements-over-bound, statement-before-continuation), JPS-AUDIT-WITNESS-KEY-INVALID, JPS-INVOCATION-AUDIT-WITNESS, JPS-AUDIT-WITNESS-KEY-READ, JPS-AUDIT-WITNESS-READ and JPS-AUDIT-WITNESS-SAVE; a coverage member `countersigned`; payload members `witness` and `requiredCountersigned` with outputVersion unchanged at "2"; a continuation file {"continuationVersion":"1","last":<statement>,"latestCheckpoint":<statement>}; eight fixed sentences taken from the ADR and two the author worded where the ADR decides a case without words (nothing credited; conflict statements); a guide section and a CHANGELOG entry. The PR body lists "Decisions the ADR left to the bytes" and four places where the author found the ADR conflicting or silent: the order of checks follows SPEC §8.6 (form, signature, trail) where ADR §6 lists the trail first; a statement at or below a continuation's last index is refused as statement-before-continuation, judged from the line's form before any signature is checked; the two sentences; and the vectors are read in process because they carry no trail copy. Material-decision categories: public-surface, documented-claim, conformance, security.
Report FINDINGS FIRST, each with a severity (HIGH, MEDIUM, LOW), the file and line, the concrete failure mode, and how you showed it; then VERDICT: MERGEABLE or NOT MERGEABLE, naming the HIGH findings that decide it. Scrutinise: (1) agreement with the gateway: every one of the 54 vectors reads as it states; the reader reuses the runtime's own canonical form, key rule and signature equation and the witness prefix of SPEC §8.3; look for an input outside the vectors on which this reader and the gateway's Go reader would answer differently (statement form, integers, duplicate members, case, blank lines, CR, a head file of two statements, the head rule, retirement, conflicts), and say how you compared; (2) the comparison with the trail copy, which is the runtime's own part: show whether any presentation makes `countersigned` (or `checkpointed`, `witnessed`) cover a line the witness did not sign for this trail: a statement of another trail, a checkpoint whose record digest is not the trail's at that sequence, a rewritten or shortened trail, a conflict, a retirement, a chain with any witness finding (which must be credited nothing), two keys; and whether reading a witness can lower or raise the holder's own `held` coverage in a way ADR §6 and answer 8 do not decide; (3) continuations: what a continuation someone else wrote can and cannot do; that its two statements are verified again under a supplied key and trail; every rule of the continuation broken once; that --witness-save writes only when the verification has no finding at all, writes atomically, does not follow a link or write through one, cannot overwrite one of its own inputs or the trail, and leaves nothing behind on failure; (4) bounds and resources: 16 keys, 64 MiB and 110000 statements at and one past, counted with a continuation's two statements; bytes judged from file sizes before reading and what happens when a file grows, is a FIFO, a device, a directory or a link; that statements are counted as the files are split and the reader stops at the line past the bound without keeping the rest; any input small on disk that makes the verifier allocate or run out of proportion; any panic; (5) the report and the exit codes: the eight ADR sentences word for word against the ADR; whether the two authored sentences say more than the code establishes; that a report made without --witness-key is byte for byte what it was before this change (run the existing audit tests and golden outputs); whether adding members to the payload under outputVersion "2" agrees with this repository's own rule for output versions; --require-countersigned-through met and unmet; (6) the four decisions listed above: for each, whether the choice loses anything ADR-0013 relies on, and in particular whether refusing on an unauthenticated line (statement-before-continuation) or the order of checks lets a party who can add a line to a statements file turn a good reading into a refusal or hide a finding; (7) the command surface and the documents: flag names and help against ADR §7 row 4, the guide section and CHANGELOG sentence by sentence against what the code does, the claim-surface tests; (8) the copied vectors: the lock file and its test, the .gitattributes change, and that a silent edit of a vector fails; (9) variants: for each finding and refusal, the single change that makes the wrong outcome pass; any test that would still pass if a chain with a finding were credited, if a continuation were not verified again, or if a continuation were saved after a finding.
You may run, with GOCACHE set to a directory of your own: gofmt -l . ; go vet ./... ; go test ./... (tests that listen on a socket may be refused by the sandbox; say so) ; go run ./cmd/jpack audit verify ... on trails and statement files you craft in a temporary directory ; the gateway's readers from the gateway clone for comparison ; python3 for arithmetic ; read-only git commands. Wrap every go, python3 and built-program command you run in a capped subshell, ( ulimit -v 12000000; <command> ), because an uncapped run exhausted this machine earlier today; a watchdog stops this whole session if its processes together hold more than 14 GB. Keep every probe bounded, create no file over 100 MB except what the tests themselves create, and never print or diff a structure with millions of entries. Do not modify tracked files in place; try variants only in copies and restore nothing by git checkout.
Round 1 complete output
-
HIGH — continuation saving can destroy the trail or verification inputs. internal/cli/audit.go:325 unconditionally renames the continuation onto the requested destination. Live CLI probes using the trail, witness key, statements file, or head file as
--witness-saveeach returned exit 0 and replaced that input with continuation JSON. A symlinked parent also directed the write through that link. Protect input destinations while retaining intentional advancement of the resume continuation itself. -
MEDIUM — malformed objects cause unnecessary memory amplification. internal/audit/witness.go:198 materializes every object member before checking the closed shape. A 4.69 MB statement containing 400,000 unknown members allocated 149.5 MB before rejection; the gateway reader allocated approximately 15 KB. Continuation parsing uses the same pattern. Reject excess/unknown members during parsing. The probes demonstrated amplification, not memory exhaustion.
-
MEDIUM — tests miss verification of a distinct saved checkpoint. internal/audit/witness_test.go:359 uses the same statement for both continuation members and corrupts only the first occurrence. A mutation skipping verification solely for a distinct
latestCheckpointsurvived the existing witness and audit-verify tests. An additional probe then falsely credited a checkpoint with an all-zero signature through sequence 8. The unmodified implementation correctly rejects it; this is a regression-test gap. -
LOW — README overstates conflict statements’ guarantees. README.md:613 says every verified statement’s checkpoint is held against the trail, then attributes prefix-existence evidence to “A statement.” Conflict statements carry offered-and-refused checkpoints and receive no such credit. The passing conflict vectors demonstrate this. Both claims need the qualification “checkpoint statement.”
VERDICT: NOT MERGEABLE. The HIGH finding—destructive continuation-save destinations—decides the verdict.
The full review and mutation matrix covers all nine requested areas. Principal results:
- Both readers passed all 54 vectors and agreed on 516 additional cases. No additional semantic divergence was found.
- Coverage, continuation, two-key, boundary, growing-file, and nonregular-file probes behaved as detailed in the report.
- All eight ADR sentences matched exactly. No-key human reports were byte-identical; JSON added only
coverage.countersigned, permitted under the repository’s version"2"rule. - An unauthenticated old-index line can turn a resumed reading into a refusal and suppress detailed findings, as the ADR’s pre-reading refusal explicitly requires; it cannot obtain successful credit.
- Formatting, vet, audit tests, targeted CLI tests, claim tests and golden checks passed. The full suite encountered sandbox socket-listening refusals in CLI/timestamp tests.
- A silent vector edit failed the lock test. Tracked workspace files remain unchanged.
Cross-vendor adversarial review, continuedThe reviewer's own longer report for round 1, and the prompts and results of round 2. Local paths are replaced by labels as in the record above. Round 1: the reviewer's full report (the file its output refers to)FINDINGS FIRST
VERDICT: NOT MERGEABLE. Finding 1, the HIGH destructive continuation-save destination bug, decides this verdict. Findings 2–4 should also be addressed; they do not assert an observed signature or trail-coverage bypass in the unmodified implementation. Scope and validation. Reviewed runtime
Agreement outside the vectors. Generated 516 shared cases and ran them through runtime The runtime uses Trail comparison and coverage. Every checkpoint statement that passes form, signature and trail checks is held against its sequence and exact record digest, even when its witness chain has another finding. Wrong-trail statements are excluded; malformed/bad-signature statements never supply checkpoints. Existing tests exercise earlier signed checkpoints detecting rewrites even when a later checkpoint matches the rewritten history, shortened trails, wrong digests, conflicts and retirements. A shortened trail below checkpoint 12 retained only countersigned coverage through the earlier matching checkpoint 5; an earlier mismatch blocked later credit. Chains with a witness finding credited no statement. A separately supplied holder checkpoint through 10 retained its own matched summary when a witness file gained a malformed line. A verified but mismatching witness checkpoint at 3 lowered holder coverage through 10, even if that witness chain also had a malformed line. This follows ADR §6's express requirement to compare every verified checkpoint and allow no failed trail/checkpoint check at or before credited coverage. Witness credit can raise aggregate A chain whose successive statements were signed under two supplied keys passed; independent statements from two keys at index 0 produced Continuations. The unmodified reader rechecks both saved signatures and trail identities, their shapes, and the continuation version/closed member set. The tests break checkpoint-kind/index ordering, last-checkpoint identity, conflict sequence, retirement checkpoint repetition, and post-retirement continuation. They exercise matching/different/behind heads, refusing supplied statements at/below the saved index, changed/shortened trails, successful retirement saves, failed steps, and long runs of conflicts read in steps. Additional probes separately corrupted each distinct saved signature and shape and signed a latest checkpoint for another trail; all failed with the appropriate witness finding and no credit. A continuation written by someone else can omit earlier contradictory signed evidence. Demonstration: a whole reading found Continuation bytes are produced only for a clean witness reading with no verification finding at all. The existing tests catch saving after a trail finding and after unmet required coverage, and CLI tests preserve the previous continuation on failed steps. Writes use a temporary sibling, file sync/close, then rename; a tested rename failure returned exit 4 and left no temporary file. This provides atomic replacement but lacks the destination protections in finding 1. Swallowing save errors was another surviving CLI-test mutation: error propagation needs an explicit regression test. Bounds and resource probes. Both readers' corpora tested 16 keys, 64 MiB, and 110,000 statements at and one beyond their bounds. Additional live CLI probes established:
The line-splitting test explicitly stops at count 110,001 with only 110,000 line descriptors retained. File data itself is read before splitting, within the byte limit. Size checks precede reading the statements/head/resume files. In a live growth probe, an inotify access event triggered expansion from 32 MiB to 64 MiB + 1 after reading began; the verifier returned the “grew past” bytes-over-bound refusal. FIFO, Reports, command surface and decisions. All six flag names match ADR §7 row 4. The CLI tests cover requiring a supplied witness key, invalid keys, missing key/statement files, remote/stdin restrictions, unmet/matched required countersigned coverage, and the documented exit classes. Successful matched coverage exits 0; unmet coverage exits 1 with All eight fixed sentences matched the ADR word for word after joining prose line wraps and substituting N/K/T placeholders. The two authored sentences are appropriately limited: “nothing credited” describes credit, not the nonexistence of signatures; the conflict sentence describes the witness's assertion about an offered/refused record and does not choose either history. The guide/CHANGELOG correctly describe the principal flags, coverage, continuation trust boundary and output additions. They do not cure or promise protection from the save-path bug. The README overstatement is finding 4. Compared actual baseline/head CLI output on valid, holder-checkpointed, shortened and damaged trails without a witness key. Human output was byte-identical in all four cases. JSON was not byte-identical: it gained only The four disclosed choices resolve as follows:
Mutation results. Each following single code change was tried in a disposable copy. “Killed” means existing relevant tests failed, not that the mutant was shipped.
The shared 54-vector harness alone would still pass mutations of runtime-only credit, continuation verification and save-after-finding behavior; it calls neither trail verification nor continuation saving. The runtime-specific tests catch the broad versions of those mutations, with the narrower gap above. The live save-error and destination probes expose paths not guarded by the existing suite. Evidence is retained under Round 2, first run: promptRound 2, first run: the results file as it stood when the session endedROUND-1 LOW — RESOLVED: README.md:613, docs/building-with-packs.md:1221, CHANGELOG.md:29, internal/cli/audit.go:94. The credit-bearing statement is now explicitly a checkpoint statement; conflict statements are expressly excluded in README/CHANGELOG. Evidence: git diff e6f8cd8...HEAD -- README.md docs/building-with-packs.md CHANGELOG.md internal/cli/audit.go. Save-guarantee documentation is assessed separately against the write probes below. ROUND-1 MEDIUM (distinct continuation statements) — RESOLVED: internal/audit/witness_test.go:863-907. TestBothOfAContinuationsStatementsAreChecked uses last=index 2 conflict and latestCheckpoint=index 1 checkpoint; independently corrupts signatures, signed content, signer and trail of each, requiring failed coverage and no continuation. Evidence: ( ulimit -v 12000000; GOCACHE=/rt228-r2-cache go test ./internal/audit -run TestBothOfAContinuationsStatementsAreChecked -v ) passed all eight corruptions. ROUND-1 MEDIUM (unknown-member allocations) — RESOLVED for the reported defect: internal/audit/witness.go:302-347. TestAMemberTheFormDoesNotAllowEndsTheReading passed: 4,689,496-byte unknown-member statement allocated 1,064 bytes (10,608 with unknown members last), checkpoint case 1,248; 4,690,158-byte continuation allocated 1,080 bytes (4,400 inside last); 400,000 repeated members allocated 4,192 / 4,240 bytes, nested-array scalar 6,608 bytes. Command: ( ulimit -v 12000000; GOCACHE=/rt228-r2-cache go test ./internal/audit -run TestAMemberTheFormDoesNotAllowEndsTheReading -v ). Additional shape/trailing-content probes follow. PASS — internal/fssecure/replace.go:64-109: TestR2PartialWriteCleanup set RLIMIT_FSIZE=1024, ignored SIGXFSZ, and attempted a 4096-byte replacement. Write returned EFBIG; old destination bytes and directory entries were unchanged, with no temporary left. TestR2TemporaryCollisions used a test-only random-name override in a disposable copy: an existing name survived all bounded attempts; one collision followed by a fresh name succeeded with no leftover. TestR2NewStatContainment refused lexical and linked-directory escapes; Lstat returned the leaf symlink itself while Stat refused following it outside the root. Round 2, completion run: promptRound 2, completion run: results file, with the verdictPR #228 narrow second-round completion; head b53e5c2.
Confirmation — PASS, finding-acceptable: internal/cli/audit.go:173. CLI exit 1; the destination remained absent and no temporary file remained. Findings matched the command without --witness-save. Command: ( ulimit -v 12000000; GOCACHE=/rt228-r2b-work/cache GOTMPDIR=/rt228-r2b-work/tmp go run ./cmd/jpack audit verify --format json --witness-key /rt228-r2b-work/fixtures/key.pub --witness /rt228-r2b-work/fixtures/statements.jsonl --trail /rt228-r2b-work/fixtures/invalid.jsonl --witness-save /rt228-r2b-work/finding-acceptable/save.json ); fixture exported by newSaveFixture from internal/cli/audit_witness_save_test.go:40 in a temporary source copy. Confirmation — PASS, finding-refused: internal/cli/audit.go:173. CLI exit 1; the destination stayed byte-for-byte unchanged and no temporary file remained. Findings matched the command without --witness-save and saveRefused explained the refusal. Command: ( ulimit -v 12000000; GOCACHE=/rt228-r2b-work/cache GOTMPDIR=/rt228-r2b-work/tmp go run ./cmd/jpack audit verify --format json --witness-key /rt228-r2b-work/fixtures/key.pub --witness /rt228-r2b-work/fixtures/statements.jsonl --trail /rt228-r2b-work/fixtures/invalid.jsonl --witness-save /rt228-r2b-work/finding-refused/save.json ); fixture exported by newSaveFixture from internal/cli/audit_witness_save_test.go:40 in a temporary source copy. Confirmation — PASS, clean-acceptable: internal/cli/audit.go:173. CLI exit 0; a continuation was saved to the previously absent destination and no temporary file remained. The saved JSON parsed as continuationVersion 1. Command: ( ulimit -v 12000000; GOCACHE=/rt228-r2b-work/cache GOTMPDIR=/rt228-r2b-work/tmp go run ./cmd/jpack audit verify --format json --witness-key /rt228-r2b-work/fixtures/key.pub --witness /rt228-r2b-work/fixtures/statements.jsonl --trail /rt228-r2b-work/fixtures/valid.jsonl --witness-save /rt228-r2b-work/clean-acceptable/save.json ); fixture exported by newSaveFixture from internal/cli/audit_witness_save_test.go:40 in a temporary source copy. Confirmation — PASS, clean-refused: internal/cli/audit.go:173. CLI exit 3; the destination stayed byte-for-byte unchanged and no temporary file remained. The refusal explicitly said verification found nothing and nothing was saved. Command: ( ulimit -v 12000000; GOCACHE=/rt228-r2b-work/cache GOTMPDIR=/rt228-r2b-work/tmp go run ./cmd/jpack audit verify --format json --witness-key /rt228-r2b-work/fixtures/key.pub --witness /rt228-r2b-work/fixtures/statements.jsonl --trail /rt228-r2b-work/fixtures/valid.jsonl --witness-save /rt228-r2b-work/clean-refused/save.json ); fixture exported by newSaveFixture from internal/cli/audit_witness_save_test.go:40 in a temporary source copy.
Confirmation — PASS, independent input-alias probe: internal/cli/audit.go:1134. Saving a clean verification onto a hard link to its trail exited 3 with the input-refusal explanation, preserved both names' bytes, and left no temporary file. Command: ( ulimit -v 12000000; GOCACHE=/rt228-r2b-work/cache GOTMPDIR=/rt228-r2b-work/tmp go run ./cmd/jpack audit verify --format json --trail /rt228-r2b-work/fixtures/valid.jsonl --witness-key /rt228-r2b-work/fixtures/key.pub --witness /rt228-r2b-work/fixtures/statements.jsonl --witness-save /rt228-r2b-work/input-alias/trail-hardlink ).
VERDICT: MERGEABLE — the round-1 HIGH and both round-2 findings are resolved, all four requested outcomes passed without unintended writes, and no new defect was found; only the five socket-dependent CLI tests remain unverified because the sandbox refuses listening sockets. Round 2, completion run: final messageThe results are in /rt228-r2b-findings.md. |
|
🎉 Thank you for your contribution to Judgment Pack! If you’d like another task, choose a path that interests you: |
PR 4 of 6 of gateway ADR-0013, "checkpoint witness" (Judgment-Pack/judgment-pack-gateway#199). PR 1 (Judgment-Pack/judgment-pack-gateway#211, merged as
c916ee9) stated the witness statement and how a chain of them is read in the gateway'sSPEC.md§8, with 54 vectors incorpus/witness/. This pull request is the runtime's side, ADR-0013 §7 row 4:jpack audit verifyreads a witness's chain of statements, holds the trail to it, credits it, and reads a long chain in steps. It needs only that format and those vectors, not the witness service, which no gateway release has yet.It follows the ADR's determination 6 and the maintainer's answers 8 (reporting), 9 (complete and current) and 11 (limits), as recorded in the ADR.
Review round 2, and what changed
Round 2 (head
127899f) found the round-1 HIGH class protected in every probe it ran, and both MEDIUMs and the LOW resolved. It left three things, each answered in its own commit:A refused destination hid a finding (MEDIUM). The destination was checked before the trail was read, so a verification with a finding answered
JPS-INVOCATION-AUDIT-WITNESS-SAVE(exit 3) with no finding shown. Whether the destination may be written is still decided before anything is written; the verification now runs regardless, and the order of what is reported follows it (ce47031):--witness-save(the finding, e.g.witness-trail-mismatch);continuationSavedfalsewitness.saveRefused: "--witness-save holds something other than a continuation, and is left as it is; name a new file, or the continuation a reading saved; nothing was saved", and anote:line in the human reportcontinuationSavedtrue ("witness continuation saved")JPS-INVOCATION-AUDIT-WITNESS-SAVE: "--witness-save holds something other than a continuation, …. The verification itself found nothing, and nothing was saved."A destination refused before the inputs are read (its directory cannot be opened, or it names no file) waits for the verification the same way.
TestARefusedSaveNeverHidesAFindingholds the four combinations and the human note.The configuration's identity was looked up again by name (LOW).
project.ConfigFile()now answers the identity taken from the descriptor the configuration was read through (fssecure.Root.ReadIdentified), not whatever is at its name when asked. The App's other recording places already took it from the opened descriptor (noteInput,openInputthrough it, standard input).TestAnInputIsRecordedAsTheFileRead, the reviewer's test turned around, moves the configuration and an opened statements file away after they are read and puts other files at their names: the files read are still the ones recorded (72683b6;b53e5c2makes the test's own reference compare at once, since on Windows a FileInfo fromos.Statfinds its identity by path when first compared, whileFile.Staton a descriptor, which the App records, fills it from the handle).The two residues, stated (
d556217), inReplaceByRename's comment and the guide's "What a save may replace", and claimed no more strongly in the README, the CHANGELOG or the help, which no longer say "never replaces": (a) the destination's last check and the rename are two steps, so another process changing that one name in the instant between them is not detected, a file put there then being replaced and a symbolic link put there replaced itself, the file it names untouched; (b) a build with Go 1.25 or later renames through the directory opened, a build with Go 1.24 renames by the directory's path after checking it still names the directory opened.Mutation check for round 2: the 8 new mutants (M107 to M114) and the 29 rows in the files it touched were run in a scratch copy as before: 36 of 37 caught. M114, which records an opened input by looking its name up again at the moment it is opened, survives: it differs from the code only if the name is changed in the instant between the open and that lookup, which no test can schedule; a lookup made later, as
ConfigFilemade it, is M113, caught. The round-1 rows in files round 2 did not touch stand as reported below.Review round 1, and what changed
The first cross-vendor review (head
e6f8cd8) found the save path destructive:--witness-saverenamed the continuation onto whatever it named, so the trail, a witness key, a statements file or the head given as its destination was replaced and the command exited 0, and a linked parent directed the write through the link. The class is closed by recording every file the invocation reads where the App opens it, refusing a destination that is any of them by the file's own identity (the resume file alone excepted), refusing anything already there that is not a regular file holding a continuation, and writing through the destination's directory handle (7f3c85d, testsabc698f,127899f). It also found, and this round fixes: a statement's and a continuation's members decoded before their closed shape was checked (about 150 MB for a 4.69 MB line of 400,000 unknown members; now refused at the first, a few KB) and continuation tests whose two statements were one, so a reader that skipped checking a distinct latest checkpoint statement passed them (1eab87a); and README wording that credited "a statement" where only a checkpoint statement is credited (4f0e706).What
audit verifynow does, flag by flag--witness-key <file>, repeatable, at most 16: a witness's public key, 64 hexadecimal characters, obtained out of band. Each is held toCheckPublicKey(the guide's "Record signatures, exactly") before anything is read; one it refuses isJPS-AUDIT-WITNESS-KEY-INVALID(exit 3) withkey-not-canonical,key-small-orderorkey-not-on-curve. Seventeen are refused before any key file is read (keys-over-bound). Every other witness flag needs it (JPS-INVOCATION-AUDIT-WITNESS, exit 3).--witness <file>, repeatable: statements, one per line, as the witness serves them.--witness-head <file>: the head the reader fetched from the witness, one statement. With it the reading is current, as of the fetch; without it, historical.--witness-resume <file>: a continuation the reader's own earlier successful reading saved. Its two statements join the set and are checked again, the chain continues at the index after its last, and its checkpoint is held against the trail again.--witness-save <file>: where to save a continuation, written only when the verification has no finding at all. Before anything is verified it is held to every file the invocation reads and to what is already there (below); the continuation is written through a temporary file in its directory, renamed into place.--require-countersigned-through <sequence>: fails (countersigned-coverage-missing, exit 1) while the records up to that sequence are not all countersigned.The statements are read against the identity the trail's chained records carry. The checkpoint of every checkpoint statement that verifies is held to the trail as
--expect's are, with the same four findings (checkpoint-beyond-trail,checkpoint-not-chained,checkpoint-trail-mismatch,checkpoint-record-mismatch), credited or not. A chain with anywitness-*finding is credited nothing (answer 8). Otherwise its checkpoints join the held ones forcheckpointed,witnessedand--require-checkpoint-through, and the coverage gainscountersigned:not-checkedwithout--witness-key,failedon a witness finding, otherwisethroughthe highest credited checkpoint that matched with no failed check at or before it, ornone. The payload gainswitness(statement lines read, statements checked, keys supplied,beganindex-0orcontinuedwithcontinuedAfter,statusreadorfailed,readingcurrentorhistorical,headIndex,highestIndex,latestCheckpoint{index, sequence, witnessedAt},conflicts(the first 100) andconflictsTotal,retired,countersignedAt,continuationSaved) andrequiredCountersigned. Additive output:outputVersionstays"2", and a report without--witness-keykeeps every sentence it had.Bounds (answer 11), refused before any statement is checked, never truncated (
JPS-AUDIT-WITNESS-REFUSED, exit 3, the reason in the message): more than 16 keys (keys-over-bound); the--witness,--witness-headand--witness-resumefiles over 64 MiB together, measured by their sizes before any is read (bytes-over-bound); more than 110,000 statements, the continuation's two counted first and every non-blank line counted as the files are split, stopping at the line past the bound (statements-over-bound); and a statements file holding a statement at or below the continuation's last index (statement-before-continuation). One Ed25519 verification per distinct statement, under the one key itskeyIdnames.What
--witness-savemay replace. The App records every file it reads, by the file's identity (os.FileInfofrom the opened handle), where it opens it:readInput(whichreadPacknow is),openInput,loadProjectfor the configuration, andnoteInputfor the trail and the sidecar and stamps file a project opens through its own handle; standard input is recorded when it is a file.audit verifyreads every input through these, so an input added later through them is covered without naming it in the check. Once every input is open, and before anything is verified, the destination is looked up by its path (following every link) and through its directory's handle, and compared with each recorded input byos.SameFile: another spelling, a symbolic link or a hard link to an input is the same file. The continuation--witness-resumeread is the one input it may be, so--witness-resume X --witness-save Xadvances it; if that file is also given as another input, it is refused. When something is already there it must be a regular file, not a symbolic link, holding a continuation byIsContinuation(the continuation's own form); anything else, a directory, a FIFO or a device included, is refused and left untouched.New refusals:
JPS-INVOCATION-AUDIT-WITNESS-SAVE, exit 3, when the verification itself found nothing (the message says so, and that nothing was saved): the destination is a file this verification reads (the message names which, e.g. "is the trail, which this verification reads, and saving would replace it"); is a symbolic link; is not a regular file; holds something other than a continuation; or names no file (a path ending in a separator,.or..).JPS-AUDIT-WITNESS-SAVE, exit 4: the destination's directory cannot be opened, when the verification found nothing; or the write failed, or the file there changed after it was checked, in which case the verification had no finding and the destination is as it was.--witness-save, exit 1, and the refusal is noted in the witness section'ssaveRefused("--witness-save ; nothing was saved") and on anote:line of the human report (review round 2).Findings
witness-malformed,witness-signature-invalid,witness-trail-mismatch,witness-equivocation,witness-chain-broken,witness-head-unreached,witness-head-behind, andcountersigned-coverage-missing. A witness finding is about no line of the trail: itslineis 0, and it moves no line's coverage.Fixed sentences
The record's eight, verbatim (N, T and K filled in):
Two of this runtime's, for what the record decides without giving words (see "Decisions the ADR left to the bytes"):
They follow the stamp sentences and precede the sentence on attempts, which stays last.
The vectors
internal/audit/testdata/witness/holds the gateway's 54 vectors copied verbatim fromcorpus/witness/atc916ee933dff0b72ebf7741c1ce8022487bfb807.internal/audit/testdata/witness.lock.jsonrecords the repository, commit and path, and each file's size and SHA-256, in the form ofinternal/artifacts' lock;TestTheWitnessVectorsAreTheGatewaysCopyholds the directory to it, so a file added, removed or edited fails..gitattributesmarks them-text, as the release-pinned artifacts are: the first Windows run of this branch failed that test, its checkout having ended every line with a carriage return.TestTheGatewaysWitnessVectorsReadAsTheyStatereads every vector throughPrepareWitnessand the reading, writing thepartsfiles out in full first, and compares each answer as a short string: a refusal by its reason, findings as a set of names, every member of a reading with none. All 54 read as stated, and the family counts match the gateway's README.Tests
internal/audit/witness_test.go, over chains a test witness signs under a seed of its own:witness-equivocation); a head below it (witness-head-behind); statements at or below it supplied (refused); its checkpoint held again after the trail copy changed (checkpoint-record-mismatch,checkpoint-beyond-trail); a conflict last with its latest before it (passes); its latest of another kind, above its last, not its last when that is a checkpoint, below a conflict last, or not the one a retirement last repeats (witness-chain-broken, each by the detail naming its rule); either saved statement failing its signature or shape, and the envelope out of shape; a failing step saving nothing, including on a finding of the trail alone; its two statements counted in the bound; a successful save at a retirement, read again with nothing new, and a statement after it.witness-chain-broken); a trail cut short below the latest; another record at a witnessed sequence; a chain with a hole whose checkpoints are still held; a copy of another trail.heldstaying the holder's own;countersigned; the sentences word for word, current, historical, continued and conflict; a current reading whose chain runs past its head; nothing credited on a finding; the requirement met and unmet;--require-checkpoint-throughmet by a witness; no witness key, no witness member or sentence.TestEveryReportSaysTheTrailIsSilentAboutAttemptsgains three shapes: a witness credited, an unmet countersigned requirement, and a witness finding.internal/cli/audit_witness_test.go, through the command, over a project's own trail, with statement bytes spelled out fromSPEC.md§8.3 rather than built by the audit package: a reading to a head with--witness-save, the continuation compared byte for byte; the next reading from it with what the witness signed since, a conflict among them; the human report's lines; every sentence; a stale head, an unmet requirement and a refusal each leaving the continuation as it was; and the flags without a key, seventeen keys refused before any is read, each key the rule refuses, the byte bound by the files' sizes (a sparse file), the statement bound, a statement at the continuation's last, standard input and remote paths, an absent file, and a statement of another trail.Added in review round 1:
TestAMemberTheFormDoesNotAllowEndsTheReading: eight lines of 4 to 10 MB, unknown members before and after the eight, one member given 400,000 times, unknown members in the checkpoint, an array of 2,100,000 elements where a string stands, and the same for continuations (unknown members, unknown members in its last statement, a member given 400,000 times). Each is refused, and the bytes allocated while reading it, compared as a number, must be at most 1 MiB; they are 1 to 11 KB.TestBothOfAContinuationsStatementsAreChecked: a continuation whose last (a conflict) and latest checkpoint statement are distinct, each corrupted in turn by a zeroed signature, a member altered after signing, another key and another trail: each is its finding, nothing is credited and nothing saved. The reviewer's mutation (skip the check of a distinct latest checkpoint statement) is M83 below, caught here.TestAuditVerifyNeverSavesOverAnInput: every flag ofaudit verifyis classified as an input or as no file, so a flag added later fails until it is; the App's record is checked to hold every input each flag names, the trail and the files beside the project's trail and a named trail, and standard input that is a file; each input as the destination, by the same spelling, another spelling, a symbolic link and a hard link, is refused, its bytes unchanged and no temporary file left; inputs that hold a continuation (trail, sidecar, stamps file, statements, head, the resume file given as--witnesstoo, the project's sidecar and stamps file), which only their being read can refuse, are refused for that; and standard input that is the destination.TestAuditVerifySavesOnlyOverAContinuation: resume-equals-save advances in place; a continuation that is no input is replaced; a file that is not one, a pack, a directory, a FIFO, a link to a continuation, a dangling link and a path naming no file are refused and unchanged; a directory that is not there isJPS-AUDIT-WITNESS-SAVE; a save after a finding (an unmet requirement, a chain begun late, a held checkpoint that does not match) writes nothing.TestTheDestinationIsLookedUpInTheDirectoryHeld: an input in the directory held is refused when the path names nothing by the time of the check.internal/fssecure/replace_test.go: whole creation and replacement leaving nothing beside; a name that changed after its check (something appeared, another file, the file gone, a link put in its place), a name that is not one file and one outside the directory refused with nothing left; an unwritable directory leaves nothing; and a path re-pointed after the open: the write goes to the directory held (Go 1.25 and later), or is refused (Go 1.24). Run under Go 1.26.5 and Go 1.24.0.No test compares or prints a structure that can hold millions of entries: answers are short strings and counts.
Mutation check
Each mutant was applied to a scratch copy of the branch, never to this branch, compiled with
go vet, and run against the witness and save tests of its package under a 12 GB memory cap and a timeout; the copy was restored after each and deleted at the end. A mutant that did not compile, or that a panic or a timeout stopped, does not count as caught: three that did not compile were rewritten until they did.bb4d81e,39dadd5,1fe9a51,e0619c8).ReplaceByRename's checks, cleanup and handle-bound rename), and M13, M72 and M73 rewritten for the changed code; one first-round mutant, M78, was an equivalent change and was rewritten to read the value whole. The 73 others were rerun against the new code; M14 (ParseCheckpoint's closed set) now runs against the checkpoint tests, since the reader holds a statement's checkpoint to its own set (M14w).Final run at
127899f: 107 of 107 caught, every one compiling, none by a panic or a timeout. Not mutation-tested, because no test can observe them: the directory sync after the rename, and the exclusivity of the temporary file's creation under a random name.audit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/bound-keys-one-past,TestAReadingIsRefusedInItsOrdercli/audit.goTestAuditVerifyWitnessFlagsAreCheckedaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/key-not-canonical-identity,TestAReadingIsRefusedInItsOrderaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/key-small-order-all-zero,TestAReadingIsRefusedInItsOrderaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/bound-bytes-one-past,TestAReadingIsRefusedInItsOrdercli/audit.goTestAuditVerifyWitnessFlagsAreCheckedaudit/witness.goTestAContinuationIsHeldToItsOwnRules/the_continuation's_two_statements_are_counted,TestStatementLinesAreCountedAsTheyAreSplitaudit/witness.goTestStatementLinesAreCountedAsTheyAreSplitaudit/witness.goTestAContinuationIsHeldToItsOwnRules/the_continuation's_two_statements_are_counted,TestStatementLinesAreCountedAsTheyAreSplitaudit/witness.goTestAContinuationIsHeldToItsOwnRules/statements_at_or_below_its_last_index,TestAReadingIsRefusedInItsOrderaudit/witness.goTestAContinuationIsHeldToItsOwnRules/statements_at_or_below_its_last_index,TestAReadingIsRefusedInItsOrderaudit/witness.goTestAContinuationIsHeldToItsOwnRules/a_saved_statement_whose_signature_fails,_or_out_of_shape,TestTheGatewaysWitnessVectorsReadAsTheyState/malformed-witness-versionaudit/witness.goTestAMemberTheFormDoesNotAllowEndsTheReadingaudit/checkpoint.goTestACheckpointIsCanonicalAndReadStrictly/an_unknown_memberaudit/witness.goTestEveryReportSaysTheTrailIsSilentAboutAttempts/a_witness's_statementsaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/malformed-index-minus-zeroaudit/witness.goTestEachFormAndRuleHoldsAtItsEdgeaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/malformed-signature-upper-caseaudit/witness.goTestEachFormAndRuleHoldsAtItsEdgeaudit/witness.goTestEachFormAndRuleHoldsAtItsEdgeaudit/witness.goTestEachFormAndRuleHoldsAtItsEdgeaudit/witness.goTestEachFormAndRuleHoldsAtItsEdgeaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/begins-late-index-oneaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/begins-late-index-oneaudit/witness.goTestAContinuationIsHeldToItsOwnRules/a_saved_statement_whose_signature_fails,_or_out_of_shape,TestBothOfAContinuationsStatementsAreChecked/its_latest_checkpoint_statement,_a_signature_zeroedaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/signature-key-id-names-anotheraudit/witness.goTestAWitnessedCheckpointIsHeldAgainstTheTrailCopy/a_copy_of_another_trail,TestBothOfAContinuationsStatementsAreChecked/its_latest_checkpoint_statement,_of_another_trailaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/signature-before-trailaudit/witness.goTestAChainReadInStepsAnswersAsAWholeReadingDoes/a_conflict_at_100_after_checkpoints_at_100_and_200,TestAContinuationIsHeldToItsOwnRulesaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/malformed-head-two-statementsaudit/witness.goTestAContinuationIsHeldToItsOwnRules/a_head_at_its_last_index_that_differs,TestTheGatewaysWitnessVectorsReadAsTheyState/equivocation-head-differsaudit/witness.goTestEveryReportSaysTheTrailIsSilentAboutAttempts/a_witness's_statements_with_a_finding,TestAWitnessedCheckpointIsHeldAgainstTheTrailCopy/a_rewrite_caught_by_an_earlier_statementaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/begins-late-nothing-suppliedaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/chain-skips-an-indexaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/chain-first-names-a-previousaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/chain-sequence-not-increasingaudit/witness.goTestEachFormAndRuleHoldsAtItsEdgeaudit/witness.goTestEachFormAndRuleHoldsAtItsEdgeaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/chain-conflict-firstaudit/witness.goTestAChainReadInStepsAnswersAsAWholeReadingDoes/a_conflict_above_the_latest_checkpoint,TestTheGatewaysWitnessVectorsReadAsTheyState/chain-conflict-above-latestaudit/witness.goTestEachFormAndRuleHoldsAtItsEdgeaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/chain-retirement-not-repeatingaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/chain-retirement-not-lastaudit/witness.goTestEachFormAndRuleHoldsAtItsEdgeaudit/witness.goTestAWitnessReportSaysHowFarTheWitnessReaches/a_current_reading,TestEachFormAndRuleHoldsAtItsEdgeaudit/witness.goTestTheGatewaysWitnessVectorsReadAsTheyState/head-unreached,TestEachFormAndRuleHoldsAtItsEdgeaudit/witness.goTestAContinuationIsHeldToItsOwnRules/a_head_at_its_last_index_that_is_its_lastaudit/witness.goTestAContinuationIsHeldToItsOwnRules/a_head_below_its_last_indexaudit/witness.goTestAContinuationIsHeldToItsOwnRules/its_latest_checkpoint_statement_of_another_kindaudit/witness.goTestAContinuationIsHeldToItsOwnRules/its_latest_checkpoint_statement_above_its_lastaudit/witness.goTestAContinuationIsHeldToItsOwnRules/its_last_a_checkpoint_statement,_and_its_latest_anotheraudit/witness.goTestAContinuationIsHeldToItsOwnRules/its_last_a_conflict_above_its_latestaudit/witness.goTestAContinuationSavedAtARetirementEndsTheChainaudit/witness.goTestAContinuationSavedAtARetirementEndsTheChainaudit/witness.goTestAChainReadInStepsAnswersAsAWholeReadingDoes/a_conflict_at_100_after_checkpoints_at_100_and_200,TestAContinuationIsHeldToItsOwnRulesaudit/witness.goTestAChainReadInStepsAnswersAsAWholeReadingDoes/a_conflict_at_100_after_checkpoints_at_100_and_200,TestAContinuationSavedAtARetirementEndsTheChainaudit/witness.goTestAContinuationIsHeldToItsOwnRules/a_saved_statement_whose_signature_fails,_or_out_of_shapeaudit/verify.goTestAContinuationIsHeldToItsOwnRules/a_step_that_fails_saves_nothing,TestAWitnessReportSaysHowFarTheWitnessReaches/an_unmet_requirementaudit/verify.goTestAChainReadInStepsAnswersAsAWholeReadingDoes/a_conflict_above_the_latest_checkpoint,TestAContinuationIsHeldToItsOwnRules/a_head_at_its_last_index_that_differsaudit/verify.goTestAWitnessedCheckpointIsHeldAgainstTheTrailCopy/a_record_other_than_the_one_witnessed,_and_a_chain_with_a_findingaudit/verify.goTestAWitnessedCheckpointIsHeldAgainstTheTrailCopy/a_rewrite_caught_by_an_earlier_statementaudit/verify.goTestAWitnessedCheckpointIsHeldAgainstTheTrailCopy/a_rewrite_caught_by_an_earlier_statementaudit/verify.goTestEveryReportSaysTheTrailIsSilentAboutAttempts/a_witness's_statements,TestAWitnessReportSaysHowFarTheWitnessReaches/credited_checkpoints_join_the_held_onesaudit/verify.goTestEveryReportSaysTheTrailIsSilentAboutAttempts/an_unmet_countersigned_requirement,TestAWitnessReportSaysHowFarTheWitnessReaches/nothing_credited_on_a_findingaudit/verify.goTestEveryReportSaysTheTrailIsSilentAboutAttempts/a_witness's_statementsaudit/verify.goTestAChainReadInStepsAnswersAsAWholeReadingDoes/a_conflict_at_100_after_checkpoints_at_100_and_200,TestAContinuationIsHeldToItsOwnRulesaudit/verify.goTestAWitnessReportSaysHowFarTheWitnessReaches/a_continued_readingaudit/verify.goTestAWitnessReportSaysHowFarTheWitnessReaches/credited_checkpoints_join_the_held_onesaudit/verify.goTestAWitnessReportSaysHowFarTheWitnessReaches/nothing_credited_on_a_findingaudit/verify.goTestAWitnessReportSaysHowFarTheWitnessReaches/a_current_reading_whose_chain_runs_past_its_headaudit/verify.goTestAWitnessReportSaysHowFarTheWitnessReaches/credited_checkpoints_join_the_held_onesaudit/verify.goTestAWitnessReportSaysHowFarTheWitnessReaches/credited_checkpoints_join_the_held_onescli/audit.goTestAuditVerifyWitnessFlagsAreCheckedcli/audit.goTestAuditVerifySavesOnlyOverAContinuation,TestAuditVerifyReadsAWitnessesChainAndContinuesItcli/audit.goTestAuditVerifySavesOnlyOverAContinuation,TestAuditVerifyReadsAWitnessesChainAndContinuesItcli/audit.goTestAuditVerifyReadsAWitnessesChainAndContinuesItaudit/witness.goTestAChainReadInStepsAnswersAsAWholeReadingDoes/a_long_run_of_conflicts_after_the_last_checkpoint,TestAContinuationIsHeldToItsOwnRules/a_conflict_last,_and_its_latest_checkpoint_statement_before_itaudit/witness.goTestEachFormAndRuleHoldsAtItsEdge,TestAMemberTheFormDoesNotAllowEndsTheReadingaudit/witness.goTestAMemberTheFormDoesNotAllowEndsTheReadingaudit/witness.goTestEachFormAndRuleHoldsAtItsEdgeaudit/witness.goTestAContinuationIsHeldToItsOwnRules/a_saved_statement_whose_signature_fails,_or_out_of_shapeaudit/witness.goTestAMemberTheFormDoesNotAllowEndsTheReadingaudit/witness.goTestAMemberTheFormDoesNotAllowEndsTheReadingaudit/witness.goTestBothOfAContinuationsStatementsAreChecked/its_latest_checkpoint_statement,_a_signature_zeroedcli/audit.goTestAuditVerifyNeverSavesOverAnInput,TestTheDestinationIsLookedUpInTheDirectoryHeldcli/audit.goTestAuditVerifySavesOnlyOverAContinuation,TestAuditVerifyReadsAWitnessesChainAndContinuesItcli/audit.goTestAuditVerifyNeverSavesOverAnInput,TestTheDestinationIsLookedUpInTheDirectoryHeldcli/audit.goTestAuditVerifyNeverSavesOverAnInputcli/audit.goTestTheDestinationIsLookedUpInTheDirectoryHeldcli/audit.goTestAuditVerifySavesOnlyOverAContinuationcli/audit.goTestAuditVerifySavesOnlyOverAContinuationcli/audit.goTestAuditVerifySavesOnlyOverAContinuationcli/audit.goTestAuditVerifyNeverSavesOverAnInput,TestAuditVerifySavesOnlyOverAContinuationcli/audit.goTestAuditVerifySavesOnlyOverAContinuationcli/app.goTestAuditVerifyNeverSavesOverAnInput,TestTheDestinationIsLookedUpInTheDirectoryHeldcli/app.goTestAuditVerifyNeverSavesOverAnInputcli/packs.goTestAuditVerifyNeverSavesOverAnInputcli/audit.goTestAuditVerifyNeverSavesOverAnInputcli/audit.goTestAuditVerifyNeverSavesOverAnInputcli/audit.goTestAuditVerifyNeverSavesOverAnInputcli/audit.goTestAuditVerifyNeverSavesOverAnInputfssecure/replace.goTestReplaceByRenameReplacesOnlyWhatWasCheckedfssecure/replace.goTestReplaceByRenameReplacesOnlyWhatWasCheckedfssecure/replace.goTestReplaceByRenameReplacesOnlyWhatWasCheckedfssecure/rename_root.goTestReplaceByRenameWritesToTheDirectoryHeldfssecure/replace.goTestReplaceByRenameReplacesOnlyWhatWasCheckedfssecure/replace.goTestReplaceByRenameReplacesOnlyWhatWasCheckedcli/audit.goTestARefusedSaveNeverHidesAFinding/a_finding,_the_destination_refusedcli/audit.goTestARefusedSaveNeverHidesAFinding/no_finding,_the_destination_refused,TestAuditVerifyNeverSavesOverAnInputcli/audit.goTestARefusedSaveNeverHidesAFinding/a_finding,_the_destination_refused,TestAuditVerifyNeverSavesOverAnInputcli/audit.goTestARefusedSaveNeverHidesAFindingcli/audit.goTestARefusedSaveNeverHidesAFinding/no_finding,_the_destination_refused,TestAuditVerifyNeverSavesOverAnInputcli/audit.goTestARefusedSaveNeverHidesAFinding/a_finding,_the_destination_refused,TestAuditVerifyNeverSavesOverAnInputproject/project.goTestAnInputIsRecordedAsTheFileReadcli/app.goDecisions the ADR left to the bytes
The save's directory (review round 1, 1d). The destination's directory is opened once, with
os.Root, before any input is read, and held until the rename. A symbolic link among the directories of the path is followed when it is opened: a reader may name any directory, so a linked parent is ordinary. Once it is open, the write goes to that directory and no other, even if the path is re-pointed meanwhile:fssecure.Root.ReplaceByRenamecreates the temporary file in it exclusively (O_CREATE|O_EXCL, a random hidden name), writes, syncs and closes it, checks that the name still holds what was checked (nothing, or the same regular file, not a link), renames through the handle (os.Root.Rename) and syncs the directory, removing the temporary file on every failure path. A symbolic link at the leaf is refused.os.Root.Renamearrived in Go 1.25 and this module's floor is Go 1.24: a build with Go 1.24 renames by the held directory's path after checking it still names the directory held, and refuses otherwise (a narrower window, not none); release binaries are built with Go 1.26.5, which renames through the handle. The last check of the name and the rename are two steps: another process changing that one name in the instant between them is not detected (a file put there then is replaced; a symbolic link put there is replaced itself, the file it names untouched). A process killed between the create and the rename can leave the hidden temporary file; no error path does.The order of a statement's checks. ADR §6 lists the trail check first;
SPEC.md§8.6, written after it in PR 1, fixes form, then signature, then trail, a statement taking the first it fails and no other (vectorsignature-before-trail). This followsSPEC.md.The trail being verified is the identity of the trail copy's last chained record, the
trailthe report states. A copy with no chained record makes every statementwitness-trail-mismatch."Refused before anything is read." ADR §6 says statements at or below a continuation's index "are refused before anything is read, never passed over", with no finding named. It is a refusal,
statement-before-continuation, decided from each--witnessline's form alone, its index, before any signature is checked. The head is not held to it: ADR §6 judges a head at the continuation's index (its last, orwitness-equivocation) and below it (witness-head-behind).The continuation's bytes:
{"continuationVersion":"1","last":<statement>,"latestCheckpoint":<statement>}and a newline, its canonical form, each statement as a JSON object in its canonical form. A continuation out of shape, not JSON, of another version, with other members, or with a statement out of form, iswitness-malformed, a finding, not a refusal. A continuation with nothing new supplied is a reading that ends at its last, current when a head at that index is supplied; from index 0 an empty set fails, asSPEC.mdsays.The continuation's latest is the latest. Beyond ADR §6's rule (a checkpoint statement at or before the last), the parts of "the latest checkpoint statement at or before it" a reader can check are held: the last itself when it is a checkpoint statement, at or above the sequence of a conflict last, and the checkpoint a retirement last repeats. Each is
witness-chain-broken, naming its rule.Counting. The continuation's two statements are counted first, then the statements files in order, the head file last.
statementsReadreports that count;statementsCheckedthe distinct statements, two copies with the same canonical bytes being one. The CLI bounds the bytes by the files' sizes before reading any, and a file that grows while read is refused with its own detail.Findings' granularity. One
witness-malformedper distinct malformed line, onewitness-signature-invalidorwitness-trail-mismatchper failing statement, onewitness-equivocationper index, onewitness-chain-brokennaming the first break, and one head finding. Theirlineis 0, and they are recorded as the signature sidecar's are, moving no line's coverage.What is held, and what is credited. Every checkpoint statement that passes form, signature and trail is held against the trail, the continuation's latest included, even when the chain has a finding; conflict and retirement statements never are, a conflict naming another record by design. A failed hold is the holder's finding with the detail prefixed "the checkpoint of the witness statement at index N: ", and voids coverage at and after it as a holder's does.
Coverage beside
held.checkpointedandwitnessedtake the highest of a holder's and the credited witness's.held(latest,status) stays the holder's own, and is absent without--expect. With a witness read and nothing credited,checkpointedisfailed; the scope ischeckpointwhenever a witness key is given. The human status line names a witness's checkpoint when it reaches further than a held one.Sentences. The two of this runtime's above: the record gives
countersignedanoneandfailedbut no sentence for them, and says a conflict "is reported with a fixed sentence" without giving one; the conflict sentence is written from ADR §3's table. The independence, submitter and time sentences are in every report with--witness-key; the reading, continued and conflict sentences only when the statements had no finding.Lists and codes.
conflictslists the first 100, as the report lists discontinuities and segments, withconflictsTotal. New codes:JPS-INVOCATION-AUDIT-WITNESS,JPS-AUDIT-WITNESS-REFUSED,JPS-AUDIT-WITNESS-KEY-INVALID(exit 3);JPS-AUDIT-WITNESS-KEY-READ,JPS-AUDIT-WITNESS-READ,JPS-AUDIT-WITNESS-SAVE(exit 4). A key file reads as--public-key's does.JSON. A statement, its checkpoint and a continuation are read a member at a time from
encoding/json's token stream (streamedObject): the first member whose name the form does not allow, or that was given before, ends the reading before anything after it is read; a member whose value must be a string, a number or null is refused at its first token when it is an object or an array; content after the object is refused. Integers are digits alone, and a string that is not UTF-8 or a lone surrogate decodes to U+FFFD and so fails every form a statement's strings are held to, thewitness-malformedSPEC.mdasks for. A statement inside a line has no bound of its own: the checkpoint member is held toParseCheckpoint's rule (checkpointOfMembers, factored out, unchanged).Not in this PR
witnesscommand forgateway conform --impl:audit verifyneeds a trail copy, which the vectors do not carry, so the vectors are read in-process by the same reader.Checks
go fmt ./...(no change),go vet ./...on linux, darwin and windows, and with Go 1.24.0: clean.go test ./internal/fssecure ./internal/auditwith Go 1.24.0: pass (the path-renaming fallback).go test ./...: every package passes, under a 12 GB address-space cap.go run ./cmd/jpack spec test-conformance --quiet, and--spec-version 0.2.0-draft: exit 0.CGO_ENABLED=0 go build -trimpath ./cmd/jpack: OK.CONFORMANCE.mdline. Changed prose lines are at most 100 columns, but for the two pinned links.Material-decision impact: public-surface, documented-claim, conformance, security; review: #228 (comment) (round 1 at
e6f8cd8: not mergeable, one HIGH, two MEDIUM, one LOW; round 2 completed atb53e5c2: mergeable)🤖 Generated with Claude Code