Skip to content

test(tools): gate the logprob gap check on the engine preamble; add the native-MTP witness - #1357

Open
monotophic wants to merge 13 commits into
JustVugg:devfrom
monotophic:evidence/engine-transcripts
Open

monotophic wants to merge 13 commits into
JustVugg:devfrom
monotophic:evidence/engine-transcripts

Conversation

@monotophic

@monotophic monotophic commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Authored by Claude Opus in Claude Code, analysis in partnership with @monotophic

Two command-line checks that read an engine transcript and say whether it is
internally consistent.

tests/check_data_logprob_gaps.py already existed and had two weaknesses. It
read a transcript without first establishing that the transcript came from an
engine it understands, so an unrecognised header was treated as no header; and
it reported problems without saying where in the byte stream they were, which
makes a disagreement on a long capture tedious to localise. It now parses the
engine's preamble through a shared helper and fails loudly when that preamble
is unparsable
, the protocol grammar is exact, the numeric tail and the top-k
cap are enforced, and every reported problem cites the byte offset of the frame
it concerns. The numeric grammar accepts both the fixed six-decimal form the
engine ships today and an earlier %.17g form (plus nan/inf/-inf); a
token can satisfy both spellings exactly, so form classification stays
informational — a tally in the summary line — rather than a pass/fail rule.

tests/check_native_mtp_witness.py is new. It captures and validates a witness
that the native multi-token-prediction path really accepted the tokens it
claims, binding the capture to the binary, the container and the request bytes
by SHA-256 so a witness cannot be replayed against different inputs. It reuses
the gap check's frame grammar by importing it rather than restating it, so the
two cannot drift apart. Its exit statuses are distinct on purpose: 0 is a
live-capture pass; 1 is every ordinary malformed, incomplete or inconsistent
witness, including an offline replay, which is always incomplete on provenance
even when clean; and 3 is the separate case where the engine build cannot
produce this witness at all — that is a fact about the build, not a malformed
witness, so it prints its own stderr prefix and does not share an exit status
with real failures.

That last case is the common one today: the per-emission record this proof
depends on is not printed by the engine as it stands, so on a current build the
tool reports UNSUPPORTED and exits 3 rather than inventing a verdict. The
docstring says so.

Context. This PR is one of four independent contributions derived from a
single locally-verified working tree (offline evidence tooling, the engine's
ablation scoring mode, these two transcript checks, and a raw wire-format
adapter). This one carries the two transcript checks and their suites. It uses
one file from the evidence-tooling PR (#1355, #1355),
tools/engine_evidence.py, which both tools import at module load for the
preamble grammar — measured: with that PR absent both modules fail to import, and
with it present the two suites here run 105 cases green. The others are proposed
separately, each with its own evidence.

Behavioral contract

  • A transcript whose preamble cannot be parsed is a named failure quoting the
    offending line, never a silent pass.
  • Every reported problem names the byte offset of the frame it concerns.
  • Both numeric spellings the engine has used are accepted; neither is treated as
    evidence of anything by itself.
  • A witness that cannot be produced by this engine build is reported as
    unsupported, with its own exit status, and never as a pass or an ordinary
    failure.
  • No model is run by anything here, and no fixture is downloaded: every case is
    built from literals inside the suite.

Capstone matrix — one decisive artifact per claim.

claim decisive evidence
an unparsable preamble cannot pass test_unparsed_banner_is_a_named_failure_quoting_the_line, test_unparsed_loaded_is_a_named_failure_quoting_the_line, and test_full_capture_with_unparsed_preamble_fails_the_whole_check — the last one drives a complete, otherwise-valid capture and requires the whole check to fail
the preamble gate cannot fail open test_preamble_returning_none_is_defensively_named_not_silently_passed — the case where the parser returns nothing at all is asserted to be a named failure, which is the shape a "no news is good news" gate would pass
the numeric grammar is source-true, both forms test_fixed6_form_is_accepted_and_reported, test_c17g_form_is_accepted_and_reported, and test_a_token_matching_both_grammars_is_ambiguous_and_still_accepted; the fixtures use the engine's own printf formats
the grammar checks bite independently test_done_domains_and_fixed_grammar_bite_independently — each rule is shown to fail on its own input rather than being masked by a neighbour
an unsupported engine build is distinguishable at the command line test_engine_witness_unsupported_when_mtpemit_absent and test_engine_witness_unsupported_is_distinguishable_at_the_cli — the second asserts the exit status and the stderr prefix, not just the internal status
the witness bindings each bite test_every_artifact_binding_bites_independently, plus test_capture_owned_paths_cannot_be_replaced_before_binding: a capture whose file is swapped between hashing and binding is refused, and each binding is shown to fail on its own
the two tools cannot drift the witness tool loads the gap check's own module and calls its check/parse_frames; there is no second copy of the grammar to keep in step
Fuller matrix and origin accounting
requirement instrument result
preamble gate four named cases, including a full-capture case loud failure in every shape
byte offsets offsets asserted in the problem strings present on every reported problem
Windows the witness's payload-identity check needs POSIX stat semantics (fstat of the descriptor agreeing with lstat of the path on device and inode); CI's Windows job saw the two disagree on some fixture files the witness refuses on Windows with an error naming the requirement, the fixture suite skips there, and two tests that run on every platform pin the refusal and the working case; the gap check is unaffected
exact protocol grammar malformed and misordered global records, malformed framing refused by name
numeric tail and top-k cap literal fixtures in both spellings, non-dyadic values accepted; classification informational
replay and provenance offline replay of a clean capture stays incomplete on provenance by design
unsupported build absent per-emission record exit 3 with its own prefix
suites run with the tooling PR present and nothing else from the set python3 -m unittest tests.test_check_data_logprob_gaps tests.test_check_native_mtp_witness on dev plus these four files and that PR's Ran 105 tests … OK

A limitation we would rather state than have you find: the gap this check hunts
is not producible by the engine's current multiplexing loop, so on today's builds
it is regression coverage for an invariant rather than a live detector, and the
preamble check binds no binary or container digest by itself. Both facts are in
the tool's own docstring.

Origin accounting. The witness tool is new content. The gap check's changes are
re-expressed from work done against this engine earlier and brought onto current
dev; the byte offsets and the explicit classification of the capture's shape
were added during review, as was the separate exit status for the unsupported
case. No file outside the four listed is touched.

Durable vs current state: the grammars, the gate and the exit-status
contract are durable. The case count (105) is current state, measured 2026-09-04
against base e1efc68 on macOS (Apple silicon) with Python 3.12.

monotophic and others added 13 commits September 5, 2026 14:13
The evidence tooling reads the engine's own stdout. Two of its lines are
typed records -- the "== GLM C engine" banner and the "loaded in ..."
line that follows it -- and every consumer that wants a field out of
them was about to reimplement the same grammar. This adds one module
that owns the exact text, the field ranges each number may take, and
the errors a malformed line produces, so the consumers agree by
construction.

It also owns the canonical form of an ablation manifest: the engine reads
a manifest a line at a time, drops the terminator and one carriage return
before it, and digests the record followed by a single newline.
canonical_manifest_bytes() is that rule written once for the Python side,
so a checker and the engine cannot drift into hashing different bytes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
check_ablate_evidence.py re-checks one ablation manifest and its JSONL
evidence artifact against an independently supplied config.json, without
the engine and without any test binary: the config and manifest field
bounds, every record's key set and field types, the header's binding to
the config and to the manifest digest, and the truncation, replay and
mismatch cases at each record boundary.

Three cross-record invariants are enforced because the producer
guarantees them for every row it can emit -- nll is never negative, corr
agrees with the argmax-equals-gold comparison, and the argmax logit is
never below the target's. Top-k order is deliberately not enforced: it is
unsorted on the wire by design.

The manifest is reduced through the shared canonical form before it is
parsed or hashed, so a manifest saved with CRLF endings or without a
final newline -- both of which the engine accepts and normalises -- is
checked rather than refused, and binds the same digest the engine bound.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
eval_glm.py scored answers from whatever the engine printed and wrote its
results only after the whole run finished. Two consequences: a foreign
line on that stream was absorbed silently, and an interrupted run left
nothing to read.

Result rows are now appended per request, so a run that dies mid-task
leaves a valid partial file carrying an INCOMPLETE marker and still
prints the accuracy table over the rows that landed. Every line of the
scoring stream is classified against the engine's documented SCORE-mode
output and an unrecognised line is refused by name instead of ignored,
and the numeric grammar accepts both the six-decimal form the engine
ships today and the older %.17g form. Where the engine build cannot
supply the evidence record the harness would bind to, the run is marked
UNBOUND by name rather than reported as bound.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pack_python.py reaches a file two ways: by following imports out from
coli, and by spotting scripts that are launched as subprocesses. Only the
first kind had its own imports followed, so a module that only a
subprocess-launched script imports was left out of the release archive
while --check still reported the archive complete.

On dev this happens twice: tools/iq3_pack.py, which
tools/convert_fp8_to_int4.py imports inside quant_e8(), and
tools/engine_evidence.py, which this branch's eval_glm.py imports. Both
are now reached. The test suite grows a real-tree assertion as well as
fixture cases, because every existing case builds a disposable tree and
so could not have caught either omission.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The gap checker read a transcript without first establishing that the
transcript came from an engine it understands, and it reported problems
without saying where in the byte stream they were.

It now parses the engine's preamble through the shared helper and fails
loudly when that preamble is unparsable, rather than silently treating an
unrecognised header as no header. The protocol grammar is exact, the
numeric tail and the top-k cap are enforced, and every reported problem
cites the byte offset of the frame it concerns. The numeric grammar
accepts both the fixed six-decimal form the engine ships today and an
earlier %.17g form; a token can satisfy both spellings exactly, so form
classification stays informational -- a tally in the summary line -- and
never a pass/fail rule. The transcript's capture shape is classified and
enforced explicitly instead of being inferred. The docstring records that
the gap this check hunts is not producible by today's mux loop: it is
regression coverage for that invariant, and the preamble check does not
itself bind a binary or container digest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
check_native_mtp_witness.py captures and validates a witness that the
native multi-token-prediction path really accepted the tokens it claims,
binding the capture to the binary, the container and the request bytes by
SHA-256 so a witness cannot be replayed against different inputs. It
reuses the gap checker's frame grammar rather than restating it, so the
two cannot drift.

Its exit statuses are distinct on purpose: 0 is a live-capture pass, 1 is
every ordinary malformed, incomplete or inconsistent witness (including
an offline replay, which is always incomplete on provenance even when
clean), and 3 is the separate case where the engine build cannot produce
this witness at all. That last one is proof about the build, not a
malformed witness, so it prints its own stderr prefix and does not share
an exit status with real failures.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On Windows, os.kill(pid, SIGTERM) is TerminateProcess with the exit code
set to the signal number: no Python handler runs and the whole unittest
process dies with status 15, which is how the Windows UCRT64 job reported
`make: *** [test-python] Error 15` with no test summary. The behaviour the
test asserts (a real SIGTERM converted into engine-child cleanup) is a
POSIX mechanism; the SIGINT/exception half of the pair covers child
cleanup on every platform.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 0a8bc2a)
On Windows, os.kill(pid, SIGTERM) is TerminateProcess with the exit code
set to the signal number: no Python handler runs and the whole unittest
process dies with status 15, which is how the Windows UCRT64 job reported
`make: *** [test-python] Error 15` with no test summary. The behaviour the
test asserts (a real SIGTERM converted into engine-child cleanup) is a
POSIX mechanism; the SIGINT/exception half of the pair covers child
cleanup on every platform.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… its newline

The checker prints its PASS line with print(), so a Windows child hands the
test CRLF where POSIX hands LF, and the byte-exact assertion failed on the
Windows UCRT64 job (the only failure in a 914-test run). Nothing consumes
that line byte-for-byte; the pin is on the content, so the test now folds
CRLF to LF before comparing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit b8d3c67)
… its newline

The checker prints its PASS line with print(), so a Windows child hands the
test CRLF where POSIX hands LF, and the byte-exact assertion failed on the
Windows UCRT64 job (the only failure in a 914-test run). Nothing consumes
that line byte-for-byte; the pin is on the content, so the test now folds
CRLF to LF before comparing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… refuses on Windows

The witness proves a snapshot payload did not change while it was hashed by
comparing fstat of the open descriptor with lstat of the path on device,
inode, type, size and times. That agreement is POSIX-defined. On the Windows
UCRT64 job the two disagreed on 9 of 45 fixture files (file-dependent; the
exact field is not established), which surfaced as a spurious "snapshot
payload changed while hashing". The witness is an instrument for engine runs
on the Linux fleet, so it now refuses on Windows with an error that names the
requirement instead of reporting a change that did not happen. The fixture
suite skips on Windows for the same reason; two new tests, which run on every
platform, pin the refusal and the working case.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Resolves the conflict JustVugg#1364 created, so this branch does not need a rebase.

JustVugg#1364 landed the packer commit from this PR (cherry-picked, authorship
intact) plus a second edge it left open: a data file opened next to a
reached module. tools/pack_python.py therefore takes dev's version
wholesale, which is a strict superset of this branch's -- the only lines
unique here were the ones that commit replaced.

tests/test_pack_python.py takes dev's version and keeps this branch's
engine_evidence assertion as an ADDITIONAL real-tree case rather than a
replacement. On dev that assertion could not run, because
engine_evidence.py is introduced here; on this branch it pins the edge
this branch adds, next to the one that pins the historical bug.

12 tests, all green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants