Skip to content

PMAT-3430 M1: one ggml tensor-type table, extracted from upstream rather than typed - #3581

Merged
noahgift merged 7 commits into
mainfrom
PMAT-3430-m1-ggml-type-leaf
Sep 20, 2026
Merged

noahgift merged 7 commits into
mainfrom
PMAT-3430-m1-ggml-type-leaf

Conversation

@noahgift

@noahgift noahgift commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

Executes the operator ruling of 2026-09-20 on #3430 (clauses 1–6, amendment 2'/2a/2b). #3452's plan is ruled; this is M1's leaf. Behaviour is unchanged — no consumer imports it yet.

What lands

crates/aprender-quant/src/ggml_type.rs GgmlType (35 live variants at upstream ids), QuantTraits, TRAITS[43] dense and indexed by id, GgmlFamily, GgmlTypeError, and the complete API the three enums it will replace carry today
scripts/extract_ggml_traits.sh + .py the committed extraction command (clause 3)
crates/aprender-quant/fixtures/ggml_traits.json the fixture, recording the sha it was read from
contracts/ggml-type-v1.yaml 2 equations, 5 obligations, 5 falsifiers, 1 kani harness — pv validate clean, pv audit no findings
scripts/extract_ggml_traits_selftest.sh the extractor's 7-case table, synthetic inputs, no llama.cpp checkout needed

Clause 2': the oracle is named by reference, never as a sha

The extractor reads build_commit from scripts/llama_pin.toml through llama_pin_get — the repo's one reader of that file — and refuses a checkout that is not at that commit (exit 3) or is dirty (exit 4). Today it resolves to d1d3c3396; the ruling's 3173a5647 is struck by amendment 2' and appears nowhere here.

Clause 3: why a probe, not a parse

ggml's table says .type_size = sizeof(block_q4_1) — a C expression. Every in-tree table that got these numbers wrong got them wrong by hand. So the extractor links the pinned checkout's own libggml-base and calls ggml_type_size / ggml_blck_size / ggml_type_name / ggml_is_quantized.

It still parses the header, as a cross-check that can fail: a row is Removed because its line is commented, never because the comment says so (// GGML_TYPE_Q4_0_4_8 = 32, says nothing — the pass that read wording returned 44/41/3 instead of 43/35/8 during the ruling), and that must agree with the compiled table's blck_size == 0 or nothing is emitted (exit 7).

Independently reproduces clause 2b: 43 slots, 35 live, 8 removed.

Clause 3's mutation proof — all RED, every control GREEN

mutation what turned red
TRAITS[9].type_size 36 → 40 (gguf-py's Q8_1, refuted by ggml's static_assert) traits_table_equals_the_upstream_fixture_row_for_row, computes_fifteen_sizes_are_unchanged
TRAITS[30].name BF16 → Bf16 serves_sixteen_strings_still_mean_what_they_meant + 1
TRAITS[4].family Removed → Affine the row comparison and the is_quantized cross-check, independently
a fake id 43 in the fixture 3 tests
fixture pin → 39173bcac (the superseded comparator) falsify_ggml_001, naming both shas and the regeneration command
a row deleted from the fixture falsify_ggml_002 ("the fixture lists 42 of 43 ids")
(control) 8 + 3 GREEN

Clause 2a: a pin bump turns this RED in the bump's own PR

falsify_ggml_001 asserts fixture.pin_build_commit == llama_pin.toml build_commit. It lives in aprender-core/tests/monorepo_invariants.rs, not in aprender-quant, because trueno_quant is published and a packaged crate has no scripts/llama_pin.toml — a test there could only skip, and a skip is how this becomes decoration. That target is already CI-wired; the new quant target is wired by a ci/explicit-test-commands.d/ fragment, so no workflow file is touched.

Found while extracting — NOT fixed here (clause 4 input for #3431)

  • aprender-core::ggml_dtype_element_size is wrong in 10 of 31 rows. From index 16 the table follows the order of its own comment (… I8, I16, BF16, I32, I64, F64, IQ1_M) instead of ggml id order (24 I8, 25 I16, 26 I32, 27 I64, 28 F64, 29 IQ1_M, 30 BF16), and the IQ rows carry values belonging to no type. Measured consequence: BF16 read 0.375 B/elem instead of 2.0, a 5.3× under-estimate, so a truncated BF16 GGUF passed the apr tensors reports a declared tensor table that does not fit the file, while apr validate rejects it #2569 extent check (fail-open) and apr tensors printed a size 5.3× too small. PMAT-869 corrected four K-quant rows of this same table in 2026-06 and stopped.
  • GgufTensor::byte_size sizes Q4_1 at 18 where upstream (and core's own shape.rs) say 20 — two tables in one crate disagreeing. It has no non-test caller, so no GGUF was written wrong by it; clause 5's "refusal on load" has no load path to attach to. Stated rather than silently skipped.

Both fixes land next, as lookups into TRAITS — not a fourth hand-typed table. That is also why they are not in this PR: doing both at once would make the leaf's "changes no behaviour" claim unverifiable.

Verification

cargo test -p aprender-quant                 19 lib + 8 fixture + 1 doc, all pass
cargo test -p aprender-core --test monorepo_invariants   15 pass (3 new)
bash scripts/extract_ggml_traits_selftest.sh 7 cases, 0 failures
cargo clippy -p aprender-quant --all-targets -- -D warnings   clean
cargo fmt --all -- --check · cargo deny check advisories      clean
pv validate contracts/ggml-type-v1.yaml      0 errors · pv audit: no findings
cargo test -p aprender-contracts --lib       1665 pass (corpus lint, sigma gate included)
bash scripts/check_explicit_test_commands.sh · check_shell_lint_ratchet.sh · check_include_files.sh   PASS
cargo package -p aprender-quant --list       fixture + module + test all packaged

Receipt with the full measurement table and every deviation from plan v4: docs/audits/impl-PMAT-3430-m1-leaf-receipt.md.

Next (this ticket): the two table fixes above, then Phases 3–4 (the three enums become re-exports, each crate keeps its admitted id set exactly, check_one_ggml_type_enum.sh ships green with them).

Refs #3430, #3421, #3431, #3452, #3563

ont-delta: none this PR touches README.md only to update a GENERATED CONTRACT_COUNT block (1799 -> 1800, written by make readme-sync, required by guard-cargo's FALSIFY-README-002); it adds no prose, no concept and no new ontology surface, and the one contract it adds is already a row in contracts/census.json and contracts/contracts.nt.

keep-open: every reference here is to work this PR does not finish — #3430 still owes Phases 3-4 (the three enums become re-exports and each crate keeps its admitted id set), #3421 is the epic, #3431 is M2 and this PR only supplies its input rows, #3563 is the general form of the pin assertion this PR instantiates once, and #3452 is the plan PR whose disposition is tab 1's call.

🤖 Generated with Claude Code

…her than typed (#3430)

Three enums named ggml tensor types here and disagreed — serve 16 ids, core 12,
compute 15, against 35 live upstream — and at least three separate tables gave
their byte sizes. Nothing compared any of them to ggml, so NVFP4 (40), Q1_0 (41)
and Q2_0 (42) arrived upstream with no signal in this tree.

This lands the leaf the three will re-export: `trueno_quant::GgmlType` (35 live
variants at their upstream ids) and `TRAITS[43]`, dense and indexed by id, with
the 8 removed slots kept so the index is the id.

EVERY NUMBER IS EXTRACTED, NOT WRITTEN. ggml's own table says
`.type_size = sizeof(block_q4_1)`; every in-tree table that got these numbers
wrong got them wrong by hand. scripts/extract_ggml_traits.sh compiles a probe
against the pinned checkout's libggml-base and asks ggml
(ggml_type_size/ggml_blck_size/ggml_type_name/ggml_is_quantized), then writes
crates/aprender-quant/fixtures/ggml_traits.json.

The oracle is named BY REFERENCE (operator ruling 2026-09-20, clause 2'): the
extractor reads `build_commit` from scripts/llama_pin.toml through the repo's
one reader for it, refuses a checkout that is not at that commit (exit 3) or is
dirty (exit 4), and records the sha in the fixture. FALSIFY-GGML-001 then fails
if that sha stops being the pin — so a comparator bump turns this RED in the
bump's own PR (clause 2a; the general case is #3563).

The header is still parsed, as a cross-check that can FAIL. `enum ggml_type`
has 8 commented-out rows and only some say "removed" — `// GGML_TYPE_Q4_0_4_8 =
32,` says nothing — and a pass that classified by wording returned 44/41/3
instead of 43/35/8 during the ruling. A row is Removed because its line is
commented, and that must agree with the compiled table's blck_size == 0 or the
extractor emits nothing (exit 7). Seven synthetic cases pin both polarities, and
falsify_ggml_003 refuses a zero-case run.

MUTATION (clause 3), all RED, controls GREEN:
  TRAITS[9].type_size 36 -> 40 (gguf-py's Q8_1, refuted by ggml's static_assert)
  TRAITS[30].name BF16 -> Bf16 · TRAITS[4].family Removed -> Affine
  a fake id in the fixture · the fixture's pin -> 39173bcac · a row deleted

Measured while extracting, and NOT fixed here (clause 4 input for #3431):
  core's ggml_dtype_element_size is wrong in 10 of 31 rows — from index 16 it
  follows the order of its own COMMENT instead of ggml id order, so BF16 read
  0.375 B/elem instead of 2.0 and the #2569 truncation check was fail-open on
  BF16 GGUFs. PMAT-869 fixed four K-quant rows of that same table and stopped.
  core's GgufTensor::byte_size sizes Q4_1 at 18 (upstream 20) while core's own
  shape.rs says 20 — two tables in one crate disagreeing. It has no non-test
  caller, so nothing was written wrong by it.
Both fixes land next, as lookups into TRAITS rather than a fourth hand-typed
table. Receipt: docs/audits/impl-PMAT-3430-m1-leaf-receipt.md.

Behaviour: unchanged. No consumer imports the leaf yet; serve's 16 strings and
compute's 15 sizes are asserted identical, and aprender-quant's six published
K-quant constants are now const-asserted against TRAITS, so a drift is a
compile error.

Refs #3430, #3421, #3431, #3452, #3563

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@noahgift

Copy link
Copy Markdown
Member Author

quorum-review (AD-04): NOT agreed (auto_merge: checked=true was_armed=false disarmed=false)

{
 "ticket": "PMAT-3430",
 "head": "862f7fa6543b35eb0e73fc2ac91d10d059a86037",
 "width": 3,
 "executor": "agy",
 "agreed": false,
 "auto_merge": {
  "checked": true,
  "was_armed": false,
  "disarmed": false,
  "note": "auto-merge not armed"
 },
 "lanes": [
  {
   "lane": 1,
   "verdict": "NO-VERDICT",
   "findings": 0
  },
  {
   "lane": 2,
   "verdict": "NO-VERDICT",
   "findings": 0
  },
  {
   "lane": 3,
   "verdict": "PASS",
   "findings": 5
  }
 ]
}

@noahgift

Copy link
Copy Markdown
Member Author

quorum-review (AD-04): NOT agreed (auto_merge: checked=true was_armed=false disarmed=false)

{
 "ticket": "PMAT-3430",
 "head": "862f7fa6543b35eb0e73fc2ac91d10d059a86037",
 "width": 3,
 "executor": "agy",
 "agreed": false,
 "auto_merge": {
  "checked": true,
  "was_armed": false,
  "disarmed": false,
  "note": "auto-merge not armed"
 },
 "lanes": [
  {
   "lane": 1,
   "verdict": "PASS",
   "findings": 0
  },
  {
   "lane": 2,
   "verdict": "NO-VERDICT",
   "findings": 0
  },
  {
   "lane": 3,
   "verdict": "PASS",
   "findings": 5
  }
 ]
}

…s container refuses to say

`workspace-test-shard (3/3)` turned red on `falsify_ggml_003` — not on anything
it judges, but on `git rev-parse --show-toplevel` inside the container:

    fatal: detected dubious ownership in repository at '/workspace'

The bind-mounted tree is owned by uid 1000 and the container runs as root, so
git refuses the repo. Neither script ever needed git for this: the self-test
locates its sibling emitter, and the extractor locates scripts/llama_pin.toml.
Both now derive the root from `$0` and ask git only about the llama.cpp
checkout, which is the one question that IS about a repository.

Proved under the container's exact condition, with `git` shadowed by a stub
that exits 128 with that message: the self-test still reports 7 cases 0
failures, and the extractor now fails where it should — at the checkout, after
resolving the root and reading the pin.

Refs #3430

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@noahgift

Copy link
Copy Markdown
Member Author

quorum-review (AD-04): three PASS — agreed (auto_merge: checked=true was_armed=false disarmed=false)

{
 "ticket": "PMAT-3430",
 "head": "9bbcaa42b7ada310dcdbb5900b9e42fe6e86acd6",
 "width": 3,
 "executor": "agy",
 "agreed": true,
 "auto_merge": {
  "checked": true,
  "was_armed": false,
  "disarmed": false,
  "note": "auto-merge not armed"
 },
 "lanes": [
  {
   "lane": 1,
   "verdict": "PASS",
   "findings": 0
  },
  {
   "lane": 2,
   "verdict": "PASS",
   "findings": 0
  },
  {
   "lane": 3,
   "verdict": "PASS",
   "findings": 5
  }
 ]
}

Three independent lanes, none in the author's family, all PASS, no dissent:
gemini-3.1-pro-high, gemini-3.8-flash-high, gemini-3.7-flash-high (each model
MEASURED from its own agy log line, not merely declared). receipt-lint accepts.

It took four rounds and none of the three losses was the diff: round 1 lost
lanes 1 and 2 to `UNAVAILABLE (503)` while both carried PASS text the reducer
correctly refused to count; round 2 and 3 lost lane 2 to a SUCCESS envelope
with no verdict object; round 3 I also cut short at my own 570 s cap. No lane
in any round returned FAIL or named a finding.

Refs #3430, #3562

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

§13.11 rung 1 — quorum shadow verdict

S13-SHADOW pr=3581 head=d51343bee9aedd099e7aa944996007f1e18dcd2a verdict=REFUSE class=Q1 arm_rc=1

Shadow mode: this records a verdict and merges nothing. A refusal
to arm is not a block (§13 adds zero rows to §7) — the pull request is
exactly as green as it was.

…e both

Adding contracts/ggml-type-v1.yaml turned three CI checks red, all the same
omission: the contract CORPUS has tracked derivatives, and a PR that adds a
contract owes them in the same commit.

  guard-cargo   contracts/census.json .n_files=1799 but HEAD carries 1800
  shard (1/3)   contracts/contracts.nt differs from a fresh extraction (R-18)

Regenerated with the tools that own them, not by hand: `pv census contracts`
and `pv extract contracts`. Both diffs are exactly the one contract —
census +1 file, +1 kernel, +1 unanchored, new id_set_sha256; the graph +6
triples. Local: `cargo test -p aprender-contracts-cli --test ont4b_shapes_gate`
11 passed.

Worth stating because it is not discoverable from the failure: the census red
named its own remedy ("run make contracts"), the graph red named its own
("run pv extract"), and neither names the other — so a contributor who fixes
the first still gets the second on the next run, one CI round later.

Refs #3430

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@noahgift noahgift added the touches-shared-contracts PR diff touches contracts/census.json or contracts/lint-baseline.json — queue group of one label Sep 20, 2026
noahgift and others added 2 commits September 20, 2026 15:37
…hon3; and a third derivative

Two reds on 3c0f4b3, both mine, both from assuming an environment.

1. shard (3/3): `python3: command not found`. The classifier behind the
extractor was python and the self-test drove it with python3. The
CONTAINERIZED workspace-test job has no python. I had reasoned it did, from
ci.yml running `python3 scripts/lib/roadmap_merge.py --selftest` — a step in a
DIFFERENT job, on the host runner. A case table that cannot execute where the
code executes proves nothing about it, so the interpreter had to go rather than
the check.

Ported scripts/extract_ggml_traits.py -> scripts/extract_ggml_traits.awk, and
took python3 out of the self-test's assertions too (sed on the emitted JSON;
the real parser is serde_json in the Rust consumer). awk is in the container
and in every base image this fleet uses.

PROOF THE PORT IS FAITHFUL: regenerating the fixture with awk produces a file
BYTE-IDENTICAL to the committed one that python wrote (`cmp` clean). PROOF IT
RUNS WHERE CI RUNS: executed under `env -i PATH=<coreutils only>` with python3
absent from PATH entirely — 7 cases, 0 failures.

2. guard-cargo: `FALSIFY-README-002 contract_count: the CONTRACT_COUNT block
states 1799, the merge tree carries 1800`. A THIRD tracked derivative of adding
one contract file, after census.json and contracts.nt — and it was MASKED by
the census red until that one was fixed, so it could not have been found in the
same round. `make readme-sync` (2 blocks).

The self-test's row 4 red is confirmed gone: it was the documented downstream
effect of the stale census (aprender#3516), not a structural failure of
contract-adding PRs. Measured 7/7 with the census regenerated, not assumed.

Refs #3430, #3587

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three tracked derivatives and a fourth guard, surfaced one per CI round because
each was masked by the one before it (#3587):

  census.json -> guard-cargo · contracts.nt -> shard 1/3 (R-18)
  README CONTRACT_COUNT -> guard-cargo, INVISIBLE until the census was fixed
  §11.1 ont-delta -> guard-tree, because touching README.md at all reclassifies
                     the PR as a sweep

The fourth is the one worth recording: it is not a derivative of the contract,
it is a consequence of the REMEDY for the third. `make readme-sync` is
mandatory (guard-cargo refuses without it) and README.md is a §11.1 prose sink,
so fixing guard-cargo is what made guard-tree fail. Neither guard can see the
other and neither message says so.

Also records the two environment assumptions that cost a round each — git
refusing the bind-mounted tree, and python3 being absent from the container —
both of which I reasoned about instead of measuring.

The PR body now carries the `ont-delta: none` line §11.1 requires. Verified
against the real guard in both polarities: with it PASS, without it
`FAILED: §11.1 — a sweep that ends in prose and nothing else is not finished`.

Re-checked after editing the contract's reference line: contracts.nt and
census.json are both still byte-identical to a fresh extraction, so that edit
does not feed either derivative.

Refs #3430, #3587

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@noahgift

Copy link
Copy Markdown
Member Author

quorum-review (AD-04): NOT agreed (auto_merge: checked=true was_armed=false disarmed=false)

{
 "ticket": "PMAT-3430",
 "head": "d51343bee9aedd099e7aa944996007f1e18dcd2a",
 "width": 3,
 "executor": "agy",
 "agreed": false,
 "auto_merge": {
  "checked": true,
  "was_armed": false,
  "disarmed": false,
  "note": "auto-merge not armed"
 },
 "lanes": [
  {
   "lane": 1,
   "verdict": "PASS",
   "findings": 1
  },
  {
   "lane": 2,
   "verdict": "NO-VERDICT",
   "findings": 0
  },
  {
   "lane": 3,
   "verdict": "NO-VERDICT",
   "findings": 0
  }
 ]
}

@noahgift

Copy link
Copy Markdown
Member Author

quorum-review (AD-04): three PASS — agreed (auto_merge: checked=true was_armed=false disarmed=false)

{
 "ticket": "PMAT-3430",
 "head": "d51343bee9aedd099e7aa944996007f1e18dcd2a",
 "width": 3,
 "executor": "agy",
 "agreed": true,
 "auto_merge": {
  "checked": true,
  "was_armed": false,
  "disarmed": false,
  "note": "auto-merge not armed"
 },
 "lanes": [
  {
   "lane": 1,
   "verdict": "PASS",
   "findings": 3
  },
  {
   "lane": 2,
   "verdict": "PASS",
   "findings": 2
  },
  {
   "lane": 3,
   "verdict": "PASS",
   "findings": 6
  }
 ]
}

Merged via the queue into main with commit 840c9c7 Sep 20, 2026
25 of 26 checks passed
@noahgift
noahgift deleted the PMAT-3430-m1-ggml-type-leaf branch September 20, 2026 18:36
noahgift added a commit that referenced this pull request Sep 20, 2026
gemini-3.1-pro-high, gemini-3.1-pro-low, gemini-3.6-flash-high — each model
measured from its own agy log, none in the author's family, no dissent. One
round: the Pro variants and 3.6-flash returned a verdict object first time,
where 3.8/3.7-flash-high had returned none in five of six lane-runs on #3581.

Refs #3430, #3562

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
noahgift added a commit that referenced this pull request Sep 20, 2026
#3617 went CONFLICTING after #3614 landed: #3581 and this branch each regenerated
contracts/census.json, contracts/contracts.nt and README.md for their own contract
additions, and git saw two edits to the same generated bytes.

Resolved the only correct way for a derived file: merge origin/main (taking either
side on the conflict, since it is overwritten next), then REGENERATE all three from
the merged tree with the pinned pv (scripts/pv_bin.sh, pv 0.68.2) -- never by hand.
A hand-merged contracts.nt is a derived file no derivation produced.

Only census.json hard-conflicted. contracts.nt and README.md would have auto-merged
line-wise, which is the worse case: combined derived output that happens to apply
cleanly. Regenerated anyway and compared: contracts.nt is byte-identical to what git
produced (15605 lines both ways) -- verified, not assumed. census 1800 -> 1801 (main's
one plus this branch's one); README CONTRACT_COUNT 1801.

  cargo test -p aprender-contracts --lib      1666 passed, 0 failed
  the_tracked_repo_graph_is_fresh             ok
  check_readme_claims.sh                      rc=0
  check_tag_step_gated.sh --self-test         rc=0
  check_pr_ont_delta.sh on the merged file set  PASS

Refs #3080, #3459, #3569

Pmat-Ticket: PMAT-3459
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
noahgift added a commit that referenced this pull request Sep 21, 2026
… census, graph, README — regenerated together

This PR adds 24 contracts and regenerated none of the tracked artifacts
derived from the corpus. On #3581 that omission surfaced one per CI round,
each masked by the one before it. All three here, at once, with a pv built
from this tree under a pinned target dir:

  contracts/census.json   1800 -> 1824  (+24, the contracts added)
  contracts/contracts.nt  15,600 -> 15,696 triples  (+96 = 24 x 4; GREW —
                          a drop is the tell for a malformed input or a stale
                          binary; binding.yaml still parses, 156 entries)
  README CONTRACT_COUNT   2 blocks -> 1824 via make readme-sync

The merge took main's generated README blocks over the branch's hand-typed
1866, then readme-sync wrote the measured 1824; the branch's number described
a tree that never existed on main.

Verified: all 24 contracts pv-validate under the pinned binary (control:
main's crux-A-01 valid under the same one); lint_passes_on_real_contracts
green, so the sigma prose ratchet holds; 1666 engine tests; ont4b shapes gate
11/11; test-binding ratchet; readme-sync-check; FALSIFY-README-002; roadmap
additive added=26 deleted=0, aggregate idempotent, 26 fragments present.

Refs #3401

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
noahgift added a commit that referenced this pull request Sep 21, 2026
…ntainer runners — a git failure is ENV exit 2 naming git (PMAT-3682)

workspace-test shard 3/3 (gx10-pool1, job 106269529629) failed
ont4c3_parity_receipts::the_committed_tree_agrees_with_its_own_denominator:
"EXPECTED_RECEIPTS says 7; the tree holds 0". On the docker runners git
refuses the bind-mounted checkout ("detected dubious ownership", exit 128).
The listing `(cd "$root" && git ls-files … 2>/dev/null | sort -u)` ran inside
a process substitution, so both git's message and its exit status were
discarded, and the loop counted an empty universe as zero.

- The listing is now `git -c safe.directory="$root" -C "$root" ls-files …`
  captured into a variable, with git's stderr kept.
- A git failure returns ENV 2 with a message naming git, never a count, and
  `verify` propagates it (`|| return $?`, not `|| return 1`).
- Self-test row: a PATH `git` stub that exits 128 with the dubious-ownership
  message must give exit 2 and a message naming git, and must NOT say "holds 0".

Proof:
  bash scripts/parity_receipt_denominator.sh --self-test    5/5 ok, rc=0
  bash scripts/parity_receipt_denominator.sh                PASS 7 receipt(s) ... says 7
  MUTANT (pre-fix count_and_check from 55b123f) --self-test   rc=1
    "FAIL git refused and the result was exit 1: FAIL evidence/parity/
     EXPECTED_RECEIPTS says 2; the tree holds 0."   <- CI's exact shape
  cargo test -p aprender-contracts-cli --test ont4c3_parity_receipts   9 passed
  bashrs lint: 0 errors · guard_tree --no-cargo 76 checks, 0 failed (incl.
  check_no_pipe_into_grep_q) · complexity rc=0 · fmt rc=0

Sibling sweep (git grep 'git ls-files' and git calls with 2>/dev/null in
scripts/ and crates/*/tests/), on lines the batch ADDED:
  ci_test_tier.sh:180, :252            fail closed (not docs-only / tier=full)
  lib_baseline_ratchet.sh:300, :302    fail closed, but misattribute the cause
                                       ("this branch WROTE it") -- filed
  release/lib_release_params.sh:39     not a test path; a git failure silently
                                       falls back to the tree root -- filed
No other zero-count site. Pre-existing sites outside the batch are not in
this change.

Refs #3682 #3669 #3581

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@noahgift noahgift mentioned this pull request Sep 21, 2026
guyernest pushed a commit to guyernest/aprender that referenced this pull request Sep 29, 2026
…ed becomes a number (paiml#3616)

* PMAT-3587 step 1 (RED): the falsifier for fail-fast discarding verdicts

RED FIXTURE FIRST. Only the case table moves; scripts/ci_run_explicit_test_commands.sh
is untouched, so these rows fail against the runner as it stands today.

`bash scripts/check_explicit_test_commands.sh --self-test` on THIS commit: rc=1,
6 failing of 72 rows (66 ok -- the fixture discriminates, it does not fail everything):

  FAIL row 30  a failing command no longer stops the run (rc 1 = ran, measured failure)
  FAIL row 32    ...and the command AFTER it ran too -- no fail-fast
  FAIL row 33  the receipt reports ran/skipped/total as NUMBERS, never an absence
  FAIL row 34    ...a command's own rc is NOT the runner's rc (7 must not leak out)
  FAIL row 35  --fail-fast stops early and is UNKNOWN (rc 2), not Fail
  FAIL row 37  all ran, none failed -> rc 0 and skipped=0

This is the ticket's own falsifier: plant a failing command early in the list and
require the EXACT count that still ran and the EXACT count skipped. A receipt that
reports only the failure fails the control.

The exit vocabulary these rows assert -- the runner's rc is its OWN verdict and never
a passthrough of a command's rc, because propagating one would let a test exiting 2
mean the same thing as "commands were not run":

  0  every command ran, none failed
  1  every command ran, F >= 1 FAILED     (a measured failure)
  2  UNMEASURED commands exist            (Unknown, doctrine 4)

Row 34 exists specifically to pin that: the fixture's failing command exits 7, and
the runner must NOT exit 7.

Two defects in my own fixture, found by running it rather than reading it, both worth
recording because each produced a wrong count:
  - a malformed `notfact ... ; test $? -eq 7` aborted the self-test after row 33 under
    `set -e`, hiding rows 34-37. A fail-fast abort concealing later rows, inside the
    harness written to fix exactly that -- it reported 3 failures when there were 6.
  - the all-pass fixture used `true` for both fragments; the runner correctly REFUSED
    a duplicate command (rc 2). The runner was right and the fixture was wrong.

Refs paiml#3587, paiml#3581, paiml#3586, paiml#3561, paiml#3080

Pmat-Ticket: PMAT-3587
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3587 step 2 (GREEN): fail-fast off by default; skipped is a number, not an absence

Same fixture, unchanged. Only scripts/ci_run_explicit_test_commands.sh moves.
`--self-test` now rc=0, 72/72 rows.

THE DEFAULT FLIPS, rather than a flag being added for the workflow to pass. The
operator's framing is "fail-fast is a verdict-discarding DEFAULT", and a flag would
leave the caller choosing, per invocation, whether to discard verdicts -- which is
the defect restated, not removed. paiml#3581 is what it cost: one test failing on a
missing interpreter produced "fail-fast: the 15 command(s) after it did not run",
and one shard hid 8,576 tests. Fifteen unknowns became an absence, and an absence
is indistinguishable from "passed" to every consumer downstream.

  default      every command runs; a red run still measures everything else
  --fail-fast  opt-in, restores the old stop-at-first behaviour

A RESULT line is printed on BOTH paths, always:

  RESULT ran=N skipped=S total=T failed=F
  RESULT first-failure [i/T] exit R: <cmd>

skipped is a NUMBER. That is the whole point: an absence reads as "nothing to
report"; a 2 reads as two commands nobody measured.

EXIT CODE is the runner's OWN vocabulary and never a passthrough of a command's rc.
Propagating one would let a test exiting 2 mean the same thing as "commands were not
run" -- two meanings, one spelling, the defect this repo keeps finding:

  0  every command ran, none failed
  1  every command ran, F >= 1 FAILED     -- a measured failure
  2  UNMEASURED commands exist            -- Unknown, doctrine 4; also every refusal

Measured end to end on a 4-command fixture whose 2nd exits 7:
  default      ran=4 skipped=0 total=4 failed=1   rc=1   (NOT 7 -- row 34 pins this)
  --fail-fast  ran=2 skipped=2 total=4 failed=1   rc=2   UNKNOWN

Callers checked on origin/main before flipping, not locally: the only invocation is
ci.yml:634, a plain `run:` step whose rc is the step's rc, so 1 and 2 both fail the
step as before and nothing branches on the value. ci.yml is NOT touched by this PR.
Arg parsing takes --shard and --fail-fast in either order; both orders measured.

Gates, fresh worktree off origin/main 00e5d2d:
  check_explicit_test_commands.sh and --self-test   rc=0  (72 rows)
  check_hardcoded_paths.sh --full-if-capable        rc=0
  check_bashrs_gate.sh · check_apr_bin_pinned.sh    rc=0
  check_no_pipe_into_grep_q.sh                      rc=0
  check_shell_lint_ratchet.sh                       rc=0
  check_guards_are_wired.sh                         rc=0
  check_sourced_libs_option_neutral.sh              rc=0

Refs paiml#3587, paiml#3581, paiml#3586, paiml#3561, paiml#3080

Pmat-Ticket: PMAT-3587
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
guyernest pushed a commit to guyernest/aprender that referenced this pull request Sep 29, 2026
… gate inside cut_tag(), 3 mutants killed (paiml#3617)

* PMAT-3459: the tag step cannot be reached without a clean milestone

Closes the exit criterion paiml#3459 is actually about. v0.68.1 was tagged 2026-09-17
15:06:29Z by an autopilot copy living OUTSIDE the repository, six minutes after
paiml#3455 merged the milestone gate; `grep -c check_milestone_cut autopilot.sh` was 0.
The spec required the read and no code path that cuts a tag made it.

THE GATE IS INSIDE THE FUNCTION THAT TAGS. cut_tag() calls
scripts/check_milestone_cut.sh ahead of `git tag`, and there is no path through it
that reaches `git tag` with the gate unsatisfied. Both non-zero codes are fail-closed
and they are DIFFERENT failures, named separately:

  rc 0  -> MILESTONE-GATE <v> clean at the cut; the tag is cut
  rc 1  -> the milestone holds open item(s): no tag, no publish
  rc 2+ -> the gate could not judge: no tag. Unknown is not a pass.

THE GUARD IS BEHAVIOURAL, NOT A grep. "The file mentions the gate" is satisfied by a
comment. scripts/check_tag_step_gated.sh EXTRACTS cut_tag() from the autopilot, runs
it against a stubbed gate once per outcome, and asserts on whether a GIT-TAG appears
in the transcript. Its --self-test then builds three mutants and requires each to be
RED, and the real subject to be GREEN so a red subject cannot masquerade as a killed
mutant:

  mutant 1  gate call deleted (the paiml#3459 defect, restored)  -> RED, 2 failing rows
  mutant 2  gate verdict discarded with `|| true`           -> RED
  mutant 3  cut_tag() removed entirely                      -> ENV rc=2, never a pass

That covers paiml#3459's three required mutations: gate call removed => RED, gate exit 1
=> no tag and no publish, gate exit 2 => no tag.

contracts/tag-step-milestone-gate-v1.yaml (kind: pattern) lands in the same PR, per
the ticket. `pv validate` and `pv lint` both PASS, and every one of its six
falsification tests was RUN, not assumed -- which is how FALSIFY-TSMG-006 was caught
asserting `test -x` on a file the repo deliberately keeps non-executable (guard_tree.sh
invokes its whole universe as `bash scripts/...`, so the executable bit is not the
property). The test was wrong, not the file; corrected to `test -f`.

WIRING, with no .github/workflows edit and no operator check-in: guard_tree.sh's
universe is exactly `git ls-files 'scripts/check_*.sh'`, a flat glob, so the guard runs
in the guard-tree job -- and `ci / gate` reads needs.guard-tree.result, so it is gated
by the required check transitively. paiml#3459 notes that wiring a self-test literally inside
the `ci` job would be a workflow edit; this route does not need one.

SEC011 ("missing validation for 'd' before rm -rf") was real in the new guard and is
fixed with ONE validated rmtree() used by every call site, not suppressed and not
repeated per site.

Gates, fresh worktree off origin/main 00e5d2d -- all 12 rc=0:
  check_tag_step_gated.sh and --self-test · check_hardcoded_paths.sh --full-if-capable
  check_bashrs_gate.sh and --self-test · check_apr_bin_pinned.sh
  check_no_pipe_into_grep_q.sh · check_shell_lint_ratchet.sh · check_guards_are_wired.sh
  check_contract_enforcement.sh · check_contract_test_binding.sh
  check_sourced_libs_option_neutral.sh

NOT in this PR, deliberately: V/T/MS/EPIC and AP are still 0.68.2's. Parameterising
them is its own row -- mixing it here would make the gate change unreviewable, which is
the lesson this ticket has been teaching all day. The hazard is stated in the PR body:
the hardcoded-path ratchet does NOT match /mnt (paiml#3592, proved with a committed
two-path probe scoring +1 not +2), so AP=/mnt/... passes every gate we own and must be
changed by hand with nothing to remind anyone.

Refs paiml#3080, paiml#3445, paiml#3455, paiml#3592

Pmat-Ticket: PMAT-3459
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3459: give the tag-gate self-test a caller — it was running nowhere

Found while answering paiml#3459's exit criterion 4. The self-test added in the previous
commit was DISCOVERED BY NOTHING and therefore ran in no job at all:

  before:  PASS  scripts/check_tag_step_gated.sh [run]
  after:   PASS  scripts/check_tag_step_gated.sh [self-test]
           PASS  scripts/check_tag_step_gated.sh [run]

guard_tree.sh decides a guard "advertises --self-test" by looking for the literal
substring `self-test` in that guard's OWN `--help` output (guard_tree.sh:47,
advertises_self_test). check_tag_step_gated.sh had no --help handler, so it got one
row instead of two and its three mutants were never executed by CI. The mutation
proof existed and nothing ran it -- a facility with a self-test and no caller, in the
commit that added the self-test. It is the same class this ticket keeps producing,
and it was invisible to reading: the guard works, its self-test works, and the wiring
between them did not exist.

The fix is a --help handler printing the header comment, which is the idiom the rest
of the tree uses (ci_run_explicit_test_commands.sh:147 and others).

Verified by running guard_tree.sh --no-cargo, not by reading it: 66 checks, 0 failed,
with both rows present.

Refs paiml#3080, paiml#3459

Pmat-Ticket: PMAT-3459
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3459: the contract's formals carry Σ symbols; census, graph and README regenerated

Fixes the three REAL reds on this PR's first run (the rest were env: gx10 ENOSPC
and an intel "dep info ... No such file" mid-build, paiml#2822 class).

1. sigma gate (shard 3/3, lint_passes_on_real_contracts): two errors.
   - `proof_obligations[1].formal` carried an em-dash, which Σ does not declare.
   - formal_prose rose 1464 -> 1467: three `formal:` entries with no Σ symbol,
     against a shrink-only baseline. `prose: true` exempts the glyph check but
     NOT the ratchet (sigma_symbols.rs:107 counts before :110 checks the flag),
     so the fix is real formals, not a marker:
       ∀ rc ∈ {1, 2}: cut_tag(v, t, mc) with gate(v) = rc ⇒ ¬ tagged(t) ∧ gate(v) = 0 ⇒ tagged(t)
       ∀ m ∈ {gate_call_deleted, verdict_discarded, cut_tag_removed}: guard(m) ≠ 0 ∧ guard(subject) = 0
       scripts/release/autopilot.sh ∈ tracked(main) ∧ cut_tag ∈ autopilot.sh
     The shell commands that were there belong in falsification_tests, where they
     already are. This is a better contract, not just a passing one.
   My local `pv lint` had reported "not armed: sigma" and I read that as clean.
   CI's test arms it. The CLAUDE.md pre-push checklist names
   `cargo test -p aprender-contracts --lib`, which reproduces CI exactly; I did
   not run it before the first push. Run now: 1666 passed, 0 failed.

2. the_tracked_repo_graph_is_fresh (shard 1/3): contracts/contracts.nt lacked the
   new contract's 5 triples. `pv extract contracts` with the pinned binary.

3. README claims must match measurement (guard-cargo): contracts/census.json said
   1799, HEAD carries 1800. `pv census contracts --format json > contracts/census.json`
   (the Makefile's form -- bare `pv census` prints and writes nothing) and
   `scripts/readme_sync.sh --write`, CONTRACT_COUNT now 1800.

All three derivatives produced with `. scripts/pv_bin.sh` (pv 0.68.2 from this
tree's target dir), never a bare pv. Each of the three failing CI tests reproduced
locally and re-run green.

Refs paiml#3080, paiml#3459, paiml#3569

Pmat-Ticket: PMAT-3459
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3459: regenerate census, graph and README from the merged tree

paiml#3617 went CONFLICTING after paiml#3614 landed: paiml#3581 and this branch each regenerated
contracts/census.json, contracts/contracts.nt and README.md for their own contract
additions, and git saw two edits to the same generated bytes.

Resolved the only correct way for a derived file: merge origin/main (taking either
side on the conflict, since it is overwritten next), then REGENERATE all three from
the merged tree with the pinned pv (scripts/pv_bin.sh, pv 0.68.2) -- never by hand.
A hand-merged contracts.nt is a derived file no derivation produced.

Only census.json hard-conflicted. contracts.nt and README.md would have auto-merged
line-wise, which is the worse case: combined derived output that happens to apply
cleanly. Regenerated anyway and compared: contracts.nt is byte-identical to what git
produced (15605 lines both ways) -- verified, not assumed. census 1800 -> 1801 (main's
one plus this branch's one); README CONTRACT_COUNT 1801.

  cargo test -p aprender-contracts --lib      1666 passed, 0 failed
  the_tracked_repo_graph_is_fresh             ok
  check_readme_claims.sh                      rc=0
  check_tag_step_gated.sh --self-test         rc=0
  check_pr_ont_delta.sh on the merged file set  PASS

Refs paiml#3080, paiml#3459, paiml#3569

Pmat-Ticket: PMAT-3459
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3459: AD-04 quorum receipt — 3/3 PASS, three independent lanes

docs/audits/quorum-PMAT-3459.json from quorum-review.sh --base origin/main --pr 3617
--author-model claude-opus-5, executor agy, width 3, judged at head b0e4be8:

  lane 1  gemini-3.1-pro-high    PASS  4 findings (all confirmations, 2 cited / 2 asserted)
  lane 2  gemini-3.8-flash-high  PASS
  lane 3  gemini-3.7-flash-high  PASS
  dissent: none

Independence inspected, not assumed: every lane file (lane-{1,2,3}.{json,err}) has zero
references to a sibling lane, to $WORK or to the .lanes/ dir -- the recorded failure
where a 3/3 was one vote read through $WORK. Three models, none in the author's family.

This receipt was owed before arming and was not there: the PR was armed through
arm_pr_automerge.sh, which asserts guard-tree on the head and by its own header says
nothing about review. Pulled from the merge queue, judged, re-armed on this receipt.

The ticket text the lanes judged against came from paiml#3461's registration (PMAT-3459's
fragment lives only on that unmerged branch); it was placed in the working tree for
`pmat work status` and never staged, so the diff the lanes judged is base...HEAD
unchanged -- the six files this PR carries.

Pmat-Ticket: PMAT-3459
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
guyernest pushed a commit to guyernest/aprender that referenced this pull request Sep 29, 2026
…l#3621)

* PMAT-3430 Phase 1: characterize all 9 boundaries BEFORE touching them

paiml#3430 Q1-c claims Phases 3-4 are behaviour-preserving: no crate accepts an id
or a name it refused before. This makes that a measurement instead of a promise.

12 tests, all written against UNCHANGED code and all green on it now. They
must pass UNCHANGED after the re-exports and admission functions land, with
ONE edit named in advance (below).

  serve, 7 boundaries — the id sweep is 0..=255 at every one, because the
  property is about what is REFUSED as much as what is accepted and a sweep
  over the admitted set alone cannot see a widening:
    gguf/dtype.rs      apr_qtype_to_dtype (+ its refusal names the id),
                       apr_dtype_to_byte
    infer/mod.rs       qtype_to_dtype_str
    apr/mod.rs         the dtype_byte arm of TensorEntry::from_binary —
                       exercised through the REAL parser with a minimal entry,
                       not a re-typed copy of the expression, which would pass
                       while the real path changed
    apr/special_tokens MappedAprModel::dtype_to_qtype
    apr/dequant        dtype_to_ggml_qtype, which deliberately admits LESS
                       than the enum knows (F32/F16/BF16 -> None)
  compute, 1 boundary — from_u32 over 0..=255, plus all 15 rows of block
    geometry and the partial-block rounding, since compute is the only one of
    the three enums that carried sizes and the extraction agreed with it exactly
  core, 0 boundaries — measured, not assumed: nothing in core turns an integer
    or a string into a GgmlType. Its whole exposure is ONE exhaustive match,
    GgufTensor::byte_size, snapshotted here at 24 (dtype, elements) points

Each lives in a #[cfg(test)] child module of the boundary's own module, so the
private items are reachable without widening anything; the only non-test edits
in this commit are four `mod` lines.

THE ONE ROW EXPECTED TO MOVE, named now so the diff cannot hide it: core sizes
Q4_1 at 18 bytes/block where upstream says 20 AND core's own shape.rs already
says 20. A second test asserts that disagreement DIRECTLY, so it cannot be
fixed by accident; Phase 3 deletes that test and moves one row in the case
table, 54 -> 60, in the same commit.

Refs paiml#3430

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3430 Phases 3-4: three ggml enums become one, and the count is now checkable

The ticket's headline. `crates/aprender-serve` (16 ids), `crates/aprender-core`
(12) and `crates/aprender-compute` (15) each declared their own ggml
tensor-type enum; a fourth private table, QTYPE_LABELS (13 rows, paiml#3405), named
the same ids again. All four are gone. The tree now holds ONE definition:

  $ grep -rnE '^\s*pub enum (GgmlType|GgmlQuantType)\b' crates/*/src
  crates/aprender-quant/src/ggml_type.rs:140:pub enum GgmlType {

BEHAVIOUR IS PRESERVED, AND THAT IS MEASURED, NOT ASSERTED. Phase 1's 12
characterization tests were written against the unchanged code and sweep every
boundary over ids 0..=255. All of them pass UNCHANGED through this commit,
with exactly the two edits named in advance:

  - compute: `GgmlType::from_u32(x)` -> `from_u32(x)`, because an inherent
    method cannot follow a type defined in another crate. The admitted SET is
    untouched — still exactly 15 ids, now stated once in `const ADMITTED`.
  - core: the Q4_1 row, below.

Each crate keeps its own admitted set, stated once and enforced at every
boundary, because the leaf knows all 35 live ggml types and no crate may start
accepting an id it used to refuse:

  compute  ADMITTED[15] + `from_u32`, at its one GGUF parse boundary
  serve    ADMITTED[16] + `admitted_from_id` / `admitted_from_name`, at all
           SEVEN boundaries (gguf/dtype x2, infer, apr/mod's from_binary arm,
           apr/special_tokens, apr/dequant, and the loader test that mirrors
           the predicate)
  core     none, and none is needed — measured: nothing in core turns an
           integer or a string into a GgmlType

THE ONE VALUE THAT CHANGES, AND THE COUNT THE COLLAPSE IS WORTH. core sized
Q4_1 at 18 bytes per block where ggml says 20 (2 x f16 for scale AND min, plus
16 nibble bytes) and where core's OWN other size table, format/gguf/shape.rs,
already said 20. Two tables in one crate disagreeing, and the wrong one was the
one nothing called — `export_tensors_to_gguf` sizes tensors with data.len(), so
no GGUF was ever written wrong by it. A unit test had asserted the 18-byte
value since it was written, pinning the defect.

So the honest ledger for this collapse: four tables -> one, ONE wrong value
removed (Q4_1) and one omission (QTYPE_LABELS had no row for Q8_1, so a refusal
printed `qtype 9`). The larger find is not in this diff: measuring against the
extracted table is what exposed `ggml_dtype_element_size`, a FIFTH id-indexed
table, wrong in 10 of 31 rows including BF16 at 0.375 B/elem instead of 2.0 —
that is paiml#3583, deliberately not folded in here.

QTYPE_LABELS's replacement goes through `from_id`, never `TRAITS[id]`: it takes
an arbitrary u32 from a file and a raw index would panic for any id >= 43. It
is also deliberately NOT filtered by the admitted set — it is a diagnostic, and
naming id 21 in a refusal beats printing `qtype 21`.

GUARD: scripts/check_one_ggml_type_enum.sh, green on this tree, with a 15-row
case table (a re-export, a commented-out definition, GgmlTypeError, GgufValue,
QuantType and a private enum all correctly NOT matched) and a zero-match arm
that FAILS rather than passing vacuously. Mutation: re-adding a second
definition to compute turns it RED naming both files; restoring turns it GREEN.
guard_tree discovers it automatically via `git ls-files 'scripts/check_*.sh'`,
so committing it is what wires it.

Verified: 30,168 tests across serve+core+quant (nextest, ci profile) and 3,514
in compute, all passing; `cargo check --workspace --all-targets` clean; clippy
-D warnings clean on all four crates; fmt clean.

Refs paiml#3430, paiml#3421, paiml#3583

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3430 Phases 3-4: receipt

The falsifier met, the 12 characterization tests that make behaviour
preservation a measurement, the one value that changed and why it was never
reachable, and an honest count of what the collapse was worth.

Refs paiml#3430

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3430 Phases 3-4: quorum verdict 3/3 on 5420b7e (AD-04)

gemini-3.1-pro-high, gemini-3.1-pro-low, gemini-3.6-flash-high — each model
measured from its own agy log, none in the author's family, no dissent. One
round: the Pro variants and 3.6-flash returned a verdict object first time,
where 3.8/3.7-flash-high had returned none in five of six lane-runs on paiml#3581.

Refs paiml#3430, paiml#3562

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
guyernest pushed a commit to guyernest/aprender that referenced this pull request Sep 29, 2026
…run, one queue slot (paiml#3669)

* fix(pv): --table panicked on the real corpus — it cut a property by BYTE index (#3338)

`pv proof-status contracts/ --table` exits 101 on `contracts/`:

    byte index 40 is not a char boundary; it is inside '∈' (bytes 39..42)
    of `for Q4_K_M Qwen2.5-Coder, quantization ∈ {Q4_K, Q6_K}`

The column width is a byte count (`property.len()`, capped at 40) and
`truncate` sliced `&s[..max]`, so any property whose byte 40 lands inside a
multi-byte char panics. Eight contracts in `contracts/` do; the first one
walked is `apr-inspect-quantization-v1.yaml`.

The budget stays a byte budget — the table is laid out in bytes — and the
cut now walks back to the nearest char boundary.

Two tests, both RED before this commit (each panicked at
obligation_matrix.rs:169): the helper row, and one through
`format_obligation_table` because that is the path the operator hit. The
helper fixture asserts `!s.is_char_boundary(40)` first, so it cannot
silently stop proving anything.

Pmat-Ticket: PMAT-3347
Refs #3347, #3338

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(pv): the L2 column read an INDEX, not a link — so 3,573 of 3,753 obligations ticked (#3347)

`obligation_matrix` computed `l2_tested` as `idx < falsification_tests.len()`.
Obligation 3 was "tested" because the contract happened to hold 4 tests,
whoever those tests were about. All 7 obligations of
`qwen35-e2e-verification-v1` showed ✓ before a single test existed. The
fallback was a substring match between the obligation's `property` prose and a
test's `rule` prose, which is an inference, not a claim.

WHICH LINK, decided by counting the corpus (1,842 files, 3,792 obligations,
4,691 falsification tests), not by preference:

  proof_obligations[].discharged_by   89  (62 `falsification_tests[N]`, 13 a
                                           test id, 1 a YAML sequence, rest
                                           comma lists / prose / a kani id)
  falsification_tests[].binds_to      38
  falsification_tests[].obligation    26  (12 name an obligation id, 6 the
                                           exact property text, 8 dangle)
  proof_obligations[].id             180 of 3,792
  kani_harnesses[].obligation      1,918 — that is the L3 column, not this one

All three L2 spellings are DECLARATIONS by the contract author, so all three
are read. `binds_to` is a serde alias of `obligation`, which is safe only
because no entry carries both keys — checked, because serde turns that into a
`duplicate field` parse error rather than a silent pick.

Not read: `applies_to`. 12 `binds_to` values match one, but `AppliesTo` is an
enum with `#[serde(other)] Other`, so the string is discarded at parse and
there is nothing left to compare. Those 12 report `?`, not a false ✗.

WHAT THE COLUMN NOW SAYS. Three values, because "no test covers this" and
"nothing here says which test covers what" are different facts:

  ✓ Tested   a test in this contract cites this obligation
  ✗ Untested the contract's links resolve and none names it — or it ships
             no falsification test at all, which is a reading, not a gap
  ? Unknown  no readable link; not measured. An unread window is Unknown,
             never a tick

MEASURED over `contracts/`, and the drop IS the point — it is what the old
column was hiding:

              before   after
  L2 ✓         3,573      86
  L2 ✗           180      65
  L2 ?             0   3,602
                       (3,753 obligation rows, 873 contracts)

Nothing was adjusted to keep the number up, and no threshold was added.

The two link fields did not exist on the structs, so both keys were written to
disk and silently dropped on parse — the shape of #3314 (`id`) and #2465
(`test_harness`). `discharged_by` is typed `Citation` (scalar | comma list |
YAML sequence) because `Option<String>` failed the WHOLE corpus on
`publish-manifest-v1`: `invalid type: sequence, expected a string`.

RED first: `l2_does_not_tick_for_an_obligation_no_test_cites` — two
obligations, two tests, both citing OB-A. It asserted through the rendered
table (the surface that was lying, and API-stable), so it is the SAME test
before and after: on the old code `Beta holds | ✓`. Its OB-A arm keeps a fix
that merely stopped ticking everything from passing.

Out of scope_paths, and forced: `lint/strict_test_binding.rs` holds the only
EXHAUSTIVE `FalsificationTest` literal in the tree (no `..Default::default()`),
so no schema field can compile without that one line.

Pmat-Ticket: PMAT-3347
Refs #3347, #3091, #3114

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(pv): single-file --strict-test-binding reported every ref missing — it now refuses (#3347)

The gate resolves cited test names against a source index rooted at the
contract path's PARENT. The directory form gets the repo root and finds
`crates/`; the single-file form gets `contracts/`, which holds no source, so
every cited ref resolves to nothing and all of them are reported missing.

Measured on `contracts/pv-artifact-kinds-v1.yaml`, a control whose eight refs
all resolve:

    pv lint contracts/pv-artifact-kinds-v1.yaml --strict-test-binding
        total_refs 8, existing 0, missing 8
    pv lint contracts/ --strict-test-binding
        total_refs 548, existing 521, missing 27  <- none of the 27 is this one

A control contract failing identically to a broken one is a gate that cannot
discriminate, and it fails SILENTLY: `passed` is true in non-strict mode, so
the run still says `Result: PASS` while printing eight false findings.

REFUSED rather than repaired. The scan root is computed in
`provable_contracts::lint::run_lint`, outside this ticket's scope; a refusal
lives in the caller, is honest, and cannot be mistaken for a clean bill. If
the root is later made explicit (a `LintConfig` field the CLI can feed, which
is what `--crate-dir` does NOT do today), this refusal is what should be
deleted.

Exit 1, not the exit-2 `decline:` class: exit 2 belongs to `ZeroContracts`,
whose message ("0 contracts under ...") would be false here — there IS a
contract; it is the gate that cannot run over it.

Two tests, in the CI-wired `cli_integration` target: the refusal names the
flag and prints no PASS and no findings, and a directory-form control proves
the refusal is specific to the single-FILE form rather than to the flag.

Pmat-Ticket: PMAT-3347
Refs #3347

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(crux): AutoGluon becomes a CRUX competitor — category O, 24 contracts, 25 tickets, registry edit + mutation proof

Research of ../autogluon (1.6.3 @ 77946149) as a competitive-research
source for aprender, landed the way category N landed linfa and burn
(#3169): the competitor is admitted to the CLOSED registry, every story
is a real contract with falsification gates, every story is a registry
row, and every missing/partial story has a GitHub issue and a roadmap
fragment.

What AutoGluon is, measured from the tree rather than recalled: three
predictors. TabularPredictor (65 public methods, 11 presets, 24 model
families of which 8 are tabular foundation models added in 1.4-1.6),
TimeSeriesPredictor (Chronos-2/Toto-2 pretrained, 30+ local/deep models,
16 metrics incl. WQL/MASE/RMSSE, auto backtesting since 1.5) and
MultiModalPredictor. Evidence under evidence/crux/autogluon/.

What aprender has, measured at eb262f8eb: automl/ is a single-estimator
hyperparameter tuner (TPE, grid, random, DE, TimeBudget, EarlyStopping);
time_series/ is one univariate ARIMA; encoders, calibration, SHAP/LIME/
permutation importance and KFold/cross_validate exist as building
blocks. No predictor-level fit(label), no leaderboard, no bagging,
stacking or greedy weighted-ensemble selection, no panel forecasting,
no quantile forecast metrics. The gap is the AutoML UX, not the
algorithms.

Category O — AutoML Parity — 24 stories: 9 P0 (the README hello-world:
fit(label), problem-type inference, presets, leaderboard, feature
pipeline, weighted ensemble, time budget, panel forecaster, quantile
metrics), 9 P1 (bagging, stacking, importance, threshold calibration,
deployment artifact, tabular foundation model, backtesting, local
baselines, pretrained forecaster), 6 P2 (refit_full, distill,
infer_limit, fit diagnostics, memory-aware fit, covariates).
MultiModalPredictor, autogluon.cloud, MLZero and Ray-parallel fits are
CUT on the epic with reasons.

  CRUX_COMPETITORS: [&str; 14] -> [&str; 15]   + autogluon

Not a BEAT pillar: aprender claims no pinned-benchmark win over
AutoGluon. Both registry tests that keep BEAT_INCUMBENTS and
CRUX_COMPETITORS apart are extended, not worked around.

Tickets: epic #3370, stories #3371-#3394, label pareto-autogluon.
Roadmap: 25 fragments under docs/roadmaps/entries/, roadmap.yaml
regenerated by the aggregator (idempotent check passes).

Spec: docs/specifications/crux-competitive-research-ux-workflows.md
v2.2 -> v2.3 — §3 gains rows for linfa+burn (category N, which #3169
never recorded there) and AutoGluon; §5 gains Category O; §6 notes
that coverage_intake in the YAML is the source of truth.
coverage_intake 267 -> 291 (partial 72 -> 77, missing 152 -> 171).

Verification:
- pv built from THIS tree validates 25/25 (24 new + master). The stale
  ~/.cargo/bin/pv rejects crux-O-01 with CRUX-002 — the behavioural
  delta proves the registry edit engaged.
- Mutation-verified: deleting "autogluon" from CRUX_COMPETITORS turns
  competitor_registry_covers_the_corpus_vocabulary RED with "autogluon
  is used by contracts/ and must stay in CRUX_COMPETITORS" and
  the_real_crux_registry_rows_are_all_in_domain RED. Restored: 20/20.
- cargo test -p aprender-contracts --lib: 1526 passed, 0 failed.
- Every falsification gate is LIVE-PENDING prose (no `::`), so
  strict-test-binding has nothing to refuse; the obligations are
  RECORDED as unfalsifiable-by-absence, not satisfied.
- README CONTRACT_COUNT regenerated 1835 -> 1866 by readme_sync.sh.
- Guards: roadmap fragment/ids/sorted/additive/completion, contract
  test-binding and enforcement, shell-lint ratchet, hardcoded paths,
  readme claims, grep -q ratchet — all rc=0.

Pmat-Ticket: PMAT-3370

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

* chore(crux): bind the admission PR to its own ticket PMAT-3401 — the epic's acceptance criteria describe the programme, not this diff

Round 2 of the quorum read the epic (PMAT-3370) as the ticket and refused
the admission for not implementing the 24 stories. The admission is its
own unit of work with its own done-when; this fragment says so.

Closes #3401
Pmat-Ticket: PMAT-3401

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

* chore(crux): PMAT-3401 title inventories the diff exactly — 26 fragments (its own included) and the scaffold CATEGORY_NAMES edit

Quorum round 3 lane 1 refused on two literal mismatches between the
ticket and the diff: '25 roadmap fragments' (there are 26 once this
ticket's own fragment lands) and an unlisted edit to
scripts/crux_scaffold_contracts.py. The title now lists every path.

Pmat-Ticket: PMAT-3401

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

* roadmap(PMAT-3495): VERIFY-001 on 0.71.0 — run the 132 Kani harnesses in CI (proof credit from runs, not declarations), then a Verus pilot on one dequant/parser function (#3495)

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

* quorum(PMAT-3496 brief, PR #3496): 3/3 PASS on 57e0e4dfb — gemini lanes, measured; author claude-fable-5-1

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

* PMAT-3577: the logit-parity receipts under contract — parity-receipt-v2, extract:parity-receipt, and the 7 back-filled

Until this row the logit-parity records under evidence/parity/** had NO validator
of any kind. Not a weak one — none. That is why seven of them sat in the tree
carrying no comparator for months: there was nothing that could have noticed.

WHAT LANDS
  · contracts/parity-receipt-v2.yaml — three shapes: parity-receipt-complete
    (closed, ignoredProperties empty), parity-comparator-self, -oracle. The
    subset has no sh:or, so the comparator split is two shapes over two
    subclasses the extractor assigns by kind.
  · contracts/parity-receipt-v1.yaml — the retired layout, recorded with NO
    shape: all instances were migrated, and a shape whose target class nothing
    instantiates passes vacuously. The enforcement that replaces it fires — the
    extractor refuses an unmigrated record BY NAME, and so does the predicate.
  · ontology/extract/parity_receipt.rs — record / unmigrated / other, and
    skipping is never silent.
  · scripts/parity_receipt_denominator.sh + evidence/parity/EXPECTED_RECEIPTS —
    the count is pinned by an INDEPENDENT predicate. An extractor checked
    against a number the extractor produced proves nothing.
  · Unknown{ExtractorMiss}, exit 2 — a new element of the verdict lattice. An
    extractor that silently saw the wrong corpus reports the same "no
    violations" as one that saw all of it.
  · the 7 records migrated to v2 and back-filled with comparator {kind: self,
    reason}, in this commit, as the row requires.

THE DENOMINATOR IS 7, NOT 8. The #3574 receipt is in PR #3575, still open; it is
not on main. Measured: 113 files under evidence/parity, 7 parity records, 0 with
a comparator. #3575 bumps it to 8 when it lands — this row's own falsifier on its
first real use.

check_parity_receipt.sh IS NOT TOUCHED, and that is the finding, not an omission.
It validates the THROUGHPUT family (instrument, protocol_ref, lanes[],
decode_tok_per_sec, the #2696 cross-class defect); a logit record has never
carried one of those keys. Folding it in — as item 6 asked — would have deleted
the #2696 validator from a family nobody was watching. One validator per artifact
family, and the discriminator is the artifact's required keys, never its filename.

THE BACK-FILL IS A RELABEL. `raw` is the original apr parity --json document key
for key; every envelope field is quoted from committed evidence named in each
record's provenance.record. model_sha256 is carried only by the one record that
measured it: hashing the files today and attaching that to a receipt about
2026-09-06 would be a claim about a different world wearing a witness's clothes.

One derived field was wrong first time and is worth recording: result.verdict
copied raw.parity — apr's own per-position flag — which says PASS for the two
1.5B cells their own RECORD.md calls RED. It now resolves the threshold from
thresholds.yaml and reproduces all seven readings the RECORD.md files state,
both REDs included. No threshold is ever typed into a shape.

NOTHING IS ARMED. armed_shapes lives in lint-baseline.json, a shared file this
row may not touch (decision 7); the shapes are computed and reported, as
ladder-green was at ONT-4c1. Arming is a follow-up with the label.

CONTROLS, both directions: 7 violations with the comparator stripped from all
seven → 0 as committed; exactly 1 for a single plant, naming focus node and
property; widening sh:in to accept `oracle` turns ont4c3_parity_receipts RED, and
mutating only the real contract turns the fixture-drift test RED; an unmigrated
record declines at exit 2 naming the file; 2 receipts against a denominator of 1
declines naming both numbers; pass / fail / decline are 0 / 1 / 2.

  cargo test -p aprender-contracts --lib ontology::extract::parity_receipt  10 ok
  cargo test -p aprender-contracts-cli --test ont4c3_parity_receipts         8 ok
  bash scripts/parity_receipt_denominator.sh --self-test                     4 ok
  pv lint contracts --gate {sigma,relations,shapes}          Pass, 0 violations
  pv extract contracts --check                                            rc 0

ONT-4c3 is NOT bound in the ONT-001 ledger: that ledger is paiml/infra's
paiml-ontology.md, where v4.8 still defines ONT-4c3 as KERNEL receipts. The
re-scope is an infra PR, not this one — raised with the cop rather than left as a
checked box. Receipt: docs/audits/impl-PMAT-3577-receipt.md.

Refs #3577, #3576, #3269, #3575, #3567

Pmat-Ticket: PMAT-3577

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3577: name it WrongCorpus, not ExtractorMiss — a prefix of its opposite is a defect

`ExtractorMissing` already meant the opposite thing: an extractor that does not
exist. `ExtractorMiss` would have sat beside it in the same 18-element lattice,
one letter apart, with the shorter a PREFIX of the longer — `grep ExtractorMiss`
matches both, and any substring test over the reasons merges them silently. That
is the defect class this repo keeps paying for (#3573 today), so the name now says
what happened: the extractor RAN and read a corpus the tree does not declare.

Renamed before it reached main, when a rename is a sed and not a migration.
Caught in review by the cop.

Refs #3577, #3573

Pmat-Ticket: PMAT-3577

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3577: the ONT-4c3 divergence is filed as paiml/infra#814, not left in a thread

Two repos holding different definitions of one identifier is a row, not a flag in
a message: nothing collides where a tool would see it, so it collides in a
person's head months later when they implement the ledger's meaning and find
their correct work unusable.

Refs #3577, paiml/infra#814

Pmat-Ticket: PMAT-3577

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* #3605: removed_by gets a shape — refusal-receipt-v1, with a closed sentinel set so the required field manufactures nothing

`removed_by` had 0 occurrences tree-wide, no schema and no validator. Every
refusal #3597 writes would have minted an unenforced convention, and a tree full
of consistent-looking `removed_by:` lines reads as validated when it is
decoration.

PHASE 0 — ITS OWN CONTRACT, AND NOT BECAUSE IT IS EASIER TO WRITE

Option B is refused on a fact: `parity-receipt-v1` DOES NOT EXIST AT HEAD. It is
in unmerged #3600, and there it is deliberately the RETIRED layout with no shape
and no instances. A live validated field on a superseded contract with zero focus
nodes is a field nothing can carry. Independently, by the discriminator this tree
has now used twice — an artifact family is named by its REQUIRED KEYS, never by
its filename — a parity receipt requires host, backend, comparator,
threshold_source and per-position metrics; a refusal requires a verb, a reason,
an exit code and removed_by. They share no required key.

A THIRD option was considered and refused, and it is the more attractive one:
put removed_by on apr-cli-commands-v1.yaml, where the verb already IS the focus
node and the universe is already the trustworthy 111. Refused because the
registry is the UNIVERSE and #3597's method is to DIFF the registry against the
buckets. If the buckets live in the registry, the denominator and the numerator
are the same artifact and the diff is vacuous by construction. The registry says
what EXISTS; a refusal says what was TRIED. Keeping them apart is what lets that
diff be a real diff.

THE FORCED-BINDING TRAP, AND WHY minCount 1 IS SAFE HERE

A required field with no escape manufactures false data: `pv validate` requires
kani_harnesses so authors fabricate one, and apex#57 had lean_theorem copied
verbatim into twelve contracts resolving to nothing, gate green throughout. The
escape is a closed sentinel set, so "there is legitimately nothing here" is
SAYABLE and still CHECKABLE:

  v<major>.<minor>   the release that removes it
  never              refused permanently by design
  unscheduled        a defect with no release chosen

tbd, soon, n/a, pending, a bare `0.70`, a patch-level `v0.70.1` and a git sha are
all RED. A version rather than a sha because a refusal answers "which release do
I need?" and a sha is precise about the tree and silent about the boundary; the
sha form is made INVALID rather than discouraged, because a shape that permits
two spellings gets both.

BOTH ARMS, and the second is the point: 9 cases over 8 fixtures. refusal-ok
carries all three declared forms and passes with 3 focus nodes;
refusal-undeclared-sentinel plants the PLAUSIBLE `tbd` and must go red. Mutation
proved red-capable: widening the pattern to accept tbd fails exactly
a_plausible_but_undeclared_sentinel_is_refused and
accept_and_refuse_are_distinct_answers, and restoring makes 9/9 green. Fixtures
carry the real contract byte for byte, so widening it without them is caught too.

No new extractor: entity {type: json, ref} + vocabulary is the existing machinery
for "validate this document", and a second reader for one more family is the
thing this tree keeps filing against.

done_when 6 — the interim re-check list is EMPTY: `removed_by` still has 0
occurrences at HEAD, so no refusal written under the interim needs revisiting.

The ledger is seeded with the one refusal already measured (`apr bench` refuses
qwen35) so the shape has a real focus node instead of passing vacuously over an
empty list. WHICH verbs land there is #3597's bucket, not this row's.

Refs #3605, #3597, #3600, #3080

Pmat-Ticket: PMAT-3605

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3577: count parity-receipt in by_entity_type — ONT-001 v4.10's probe reads ABSENT otherwise

The spec's probe asks `by_entity_type["parity-receipt"] == 7`. Measured on this
branch before the change: the map carried pv-contract, gguf, apr-model, code and
lean, and NO parity-receipt key. The seven records were there — by_shape showed
parity-receipt-complete=7 — but the entity-type map did not carry them, so the
probe would have read ABSENT.

AN ABSENT KEY IS NOT ZERO. A consumer treating it as one measures nothing and
calls it a pass — the same shape as #3610, one map over.

Registering the entity type in Sigma was not enough; it has to be counted where
the probe looks. A test now asserts the key exists AND that it equals the shape's
own focus-node count, so the two numbers cannot drift apart.

Refs #3577, #3610, paiml/infra#831

Pmat-Ticket: PMAT-3577

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3577: regenerate contracts.nt with a pv built from THIS worktree — the shared target dir served another branch's binary

The merge commit's contracts.nt was 107 triples short: every parity-receipt and
parity-comparator node was missing, because `cargo metadata`'s target_directory
is shared across worktrees and the pv on PATH had been built from a different
branch minutes earlier.

AND `pv extract contracts --check` PASSED ON IT, because the check re-derives the
graph with the same binary. A stale tool comparing an artifact against its own
re-derivation agrees with itself about nothing being there — the derived file and
the checker were wrong in the same direction, which is the only way that gate can
fail to fire.

Rebuilt with CARGO_TARGET_DIR pinned to this worktree; the 107 triples return and
by_entity_type[parity-receipt] reads 7 rather than absent.

Refs #3577

Pmat-Ticket: PMAT-3577

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* register the new CLI test in scripts/tree_reader_tests.txt

The registry is derived from the tree and drifts the moment a test that reads the
tree is added without listing it. Each of the three new tests failed this guard on
its OWN branch — not a shared commit, as first read.

Pmat-Ticket: PMAT-3598

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* register the new CLI test in scripts/tree_reader_tests.txt

The registry is derived from the tree and drifts the moment a test that reads the
tree is added without listing it. Each of the three new tests failed this guard on
its OWN branch — not a shared commit, as first read.

Pmat-Ticket: PMAT-3598

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* #3600: the migration broke TWO readers of the records, including the release judge

I asked "who else reads this?" for the <unk> assumption and did not ask it for my
own migration. Two consumers read the legacy top-level metrics:

  crates/apr-cli/src/commands/parity_admission.rs  -> mac-check RED
  scripts/check_model_parity.sh                    -> C14, the RELEASE judge

The second is the one that matters: C14 is the pre-publish dogfood's parity gate,
and on the migrated records it reported "no per-position metrics in the output"
for a record that is fine. The migration would have taken the release gate down.

THE RULE, STATED ONCE IN BOTH READERS: a v2 receipt EMBEDS the raw
`apr parity --json` document under `raw`, so look inside the envelope when there
is one. A fresh `apr parity` run is the raw document itself and carries the
readings at the top level. Those are two different INPUTS — a tool's output and an
archived receipt quoting it — not two spellings of one, which is the distinction
that makes this a rule rather than the permissiveness #3613 refuses.

The self-test's fixture builder read the same way, so the must-RED twin was being
built from a KeyError and that control could not have fired.

Verified: 7B sentinel PASS, 1.5B sentinel RED (unchanged from pre-migration),
check_model_parity.sh --self-test 26/26, parity_admission 19 tests.

Refs #3600, #3577

Pmat-Ticket: PMAT-3577

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3604: the F2 hybrid guard runs once per (model sha256, apr version, device) and leaves a receipt

f2_validate_qwen35 proves the CUDA hybrid forward against a 64-position CPU
reference before it will serve a token, on EVERY apr run. Measured (#3598
row 1): 67 % of a 14 s time-to-first-token, 90-93 % of it the CPU forward.
The guard is right to exist and wrong to run per call.

Now: the first run of a (model sha256, apr version, device) triple validates
and writes a receipt; a later run whose triple matches reads it and skips the
forward; `apr run --revalidate` forces a fresh run and rewrites it.

MEASURED HERE, RTX 4090, Qwen3.5-4B-Q4_K_M, the 144-word row, --max-tokens 1,
GPU occupancy recorded before every run, binary built from this tree under a
PINNED target dir (the shared one handed me another worktree's binary first):

  --revalidate, warm cache   17.18 s wall   guard 9,747 ms on 65 positions
  receipt hit, warm cache     7.39 s wall   guard 0 ms      sha256 1,173 ms

  -9.8 s wall; guard 9,747 -> 0; the key costs 1.17 s/run (2.74 GB at
  2.3 GB/s) and is printed separately so it cannot hide in either number.

THE RECEIPT IS THE VALIDATION, WHICH IS WHY IT IS STRICT. Every path that is
not "three keys match" validates, and the three planted-receipt falsifiers
were run END TO END in the real binary, not only as unit tests:

  wrong model sha256   -> re-validated: "receipt is for model bbbb…, this file is 00fe…"
  wrong apr version    -> re-validated: "receipt written by apr 0.61.0, this is 0.68.2"
  wrong device         -> re-validated: "receipt written for NVIDIA GB10, this device is …4090"
  corrupt file         -> re-validated: "receipt unreadable (…: not a receipt)"
  missing file         -> re-validated: "no receipt for this model"

Absence is never consent, and absence and unreadability are told apart.

A RECEIPT IS WRITTEN ONLY AFTER A VALIDATION THAT JUDGED SOMETHING. The guard
has three early exits that let the GPU serve without comparing a position —
SKIP_PARITY_GATE=1, a probe under two tokens, a CPU reference that would not
run. f2_validate_qwen35 now returns F2Verdict {Accepted{positions_judged},
Rejected, NotJudged} instead of bool, and only Accepted writes; otherwise a
one-token prompt would "validate" the triple for every prompt after it.

The decision table (f2_receipt.rs) is pure and CUDA-free, so its 13 tests run
on every build. --revalidate reaches the guard through the same env seam the
guard already reads SKIP_PARITY_GATE from, rather than a 38th positional
parameter on run_entry::run and six forward signatures #3606 is changing.

done_when 5 is partial and says so: [source=receipt|fresh] is on the guard's
stderr line; the `apr run --json` field lands with #3606's StageTimings, and
F2Outcome{source, validate_ms, sha256_ms, receipt_path} is returned to the call
site for exactly that. #3606 and this PR both edit f2_validate_qwen35's return
path; whichever lands second reconciles ~10 lines, and #3606's lane is told.

Evidence: evidence/perf/3604/MEASUREMENT.md + the stderr of all nine runs.

Refs #3604, #3596, #3598, #3606

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* roadmap: mint PMAT-3604 so the AD-04 quorum for #3634 can run

pmat work status PMAT-3604 refused ('Item not found'): #3604 was minted as a
GitHub issue with a done_when but never as a roadmap entry, and the quorum
script hard-requires the work item. Fragment + aggregate, nothing else.

Refs #3604, #3634
ont-delta: none — a roadmap entry; no ontology entity, shape, reason or resolution

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(run): --gpu that fell back to CPU reported success — the refusal had no caller (#3602)

`apr run --gpu` on a model whose GPU attempt is rejected at runtime printed a
result and exited 0. Measured on lambda (RTX 4090 sm_89, apr 0.68.2 e6f77c98c,
qwen2.5-coder-0.5b-instruct-q4_k_m):

  exit=0
  stderr: warning: GPU output diverges from CPU at position 1 (cosine 0.4153)
  stdout: { "used_gpu": false, "inference_time_ms": 33646.13 }

33.6 seconds on the CPU, reported as a successful --gpu run, with `used_gpu:
false` as the only signal — the same value a deliberate CPU run reports.

The decision was already made, recorded and unit-tested. `registry::after_generation`
implements it (R-0b, #3002/#3042): a FORCED accelerator that fell to CPU is a
refusal, a DEFAULT selection that fell to CPU gets a corrective line. A git grep
found its only callers were its own tests. `registry::announce` and
`registry::parity_line` are in the same state, which is why a real run prints
zero `selected:` and zero `parity:` lines.

So this is not a new policy. It is the recorded one, reached from `apr run`:

- `dispatch.rs` classifies the request ONCE via `registry::Request::wanted()`
  rather than re-deriving "forced" — two spellings of one rule drift apart.
- `run_entry::run` calls `reconcile_accelerator` before any success output.
- `--json` gains a `backend` object: `requested` / `ran` / `fell_back`, because
  `used_gpu: false` alone collapses "ran on CPU deliberately" with "asked for
  the GPU and was refused it".

`accel::tests::every_accelerator_surface_calls_the_refusal` passed throughout:
it guards `ensure_available`, the BUILD-time refusal, not the RUNTIME one. A
guard over one of two refusals reads as coverage of both.

Falsifier, both directions (`run_tests_accel_reconcile.rs`, 6 cases). Planting
the pre-fix behaviour (`reconcile_accelerator` → `Ok(None)`) turns
`a_forced_accelerator_that_ran_on_cpu_is_refused` and
`the_json_distinguishes_a_deliberate_cpu_run_from_a_rejected_gpu_run` RED while
`a_forced_accelerator_that_actually_ran_on_gpu_says_nothing` stays green — so
the tests discriminate rather than just failing. `used_gpu: None` is Unknown,
not a fallback: refusing on it would make every non-reporting path a hard error.

NOT included, deliberately: the rejection's REASON (cosine, position) is on
stderr but not in the JSON. It is produced inside realizar's F2 gate and no
channel carries it to the CLI; adding one is a #3606-shaped follow-up, not
something to approximate with a guess here.

Refs #3602, #3483
Pmat-Ticket: PMAT-3602

* test(PMAT-3346): the measured Qwen3.5-0.8B inventory the dense path cannot reproduce

QE2E-INV-001 could not be judged because nothing in the tree held a MEASURED
Qwen3.5 tensor inventory to judge against. This adds one: the 320 tensors of
~/models/Qwen3.5-0.8B-Q4_K_M.gguf (sha256 bd258782...dc517), read straight from
the GGUF header rather than from a model card.

It already falsifies the current arithmetic. Dense/GQA accounting applied to
that file gives 644,400,128 against a measured 752,393,024 — short by
107,992,896, 14.4% of the model, because 18 of the 24 layers are Gated DeltaNet
and no term here counts their conv, gate, state or output projections.

Two shapes in the file are also not what dense accounting predicts, and both are
pinned: attn_q is [1024, 4096] = 2 * num_heads * head_dim (the q projection
emits the attention output gate alongside the query; attn_output [2048, 1024]
confirms num_heads * head_dim = 2048), and attn_q_norm/attn_k_norm are present
at head_dim. The file is also TIED — it has no output.weight.

Refs #3346

Pmat-Ticket: PMAT-3346
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(PMAT-3346): ModelConstraints carries the gated-DeltaNet shape, so a hybrid model can be counted

contracts/model-families/qwen3_5.yaml declares inner_size, state_size,
conv_kernel, group_count and full_attention_interval under constraints:, and
ModelConstraints carried none of them. A Gated DeltaNet layer's parameters live
entirely in those dimensions, so every consumer of the descriptor counted
Qwen3.5 as if three quarters of its layers did not exist.

Carried through as ModelConstraints::deltanet: Option<DeltaNetShape> — the
runtime YAML loader (parsing.rs) and the compiled-in registry (build_parsing.rs
+ build_codegen.rs) both populate it, and FALSIFY-MF-QWEN35-010 pins that the
declared values survive the trip and that no other family acquires a shape it
never declared. qwen3_5.yaml is the only descriptor with these keys, so every
other family keeps byte-identical accounting.

model_arithmetic gains gated_deltanet_layer_params (one term per GGUF tensor:
attn_qkv, attn_gate, ssm_conv1d, ssm_alpha/beta, ssm_a, ssm_dt.bias, ssm_norm,
ssm_out) and hybrid_layers (the interleaved schedule). attention_layer_params
gained two terms the real file has and dense accounting did not model: the
gated q projection (2*n_h*d_k) and the q/k norm vectors.

Falsified against a real model, not against itself: fed the 0.8B configuration,
the equation now reproduces the 320-tensor inventory of
Qwen3.5-0.8B-Q4_K_M.gguf EXACTLY — 752,393,024, both layer kinds matching
tensor for tensor.

QE2E-INV-001 is still NOT asserted, and no range was widened to make it pass.
The 9b descriptor now yields 8,344,907,136, up from 8,208,519,168 but still
0.655B below [9.0B, 9.2B]. The remaining gap looks like descriptor drift rather
than missing arithmetic: 9b keeps inner_size 2048 — the value the 0.8B uses at
hidden_dim 1024 — while quadrupling hidden_dim, and its group_count 8 fails
8 * 128 == 2048, a consistency the measured 0.8B satisfies at 16 * 128. Only a
real Qwen3.5-9B file can settle it; none is on this box.

Refs #3346, #3347

Pmat-Ticket: PMAT-3346
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* contracts(binding): the QE2E-INV-001 note blamed a gap that is now closed

The note said the obligation was undischarged because ModelConstraints does not carry the DeltaNet shape keys. It does now, and the 0.8B count reproduces the real GGUF exactly. What actually blocks the obligation is descriptor drift at the 9b variant.

Pmat-Ticket: PMAT-3346
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3346 (adoption): the QE2E-INV-001 note lost its closing quote, and pv extract shrank the graph by 356 triples instead of refusing

d3cc76f1f rewrote the note on the QE2E-INV-001 binding and dropped the
trailing `"` — contracts/binding.yaml stopped being valid YAML at line 764
(`found unexpected end of stream`). Nothing in the PR noticed because
`pv extract contracts` does not refuse a binding registry that will not
parse: it emitted a graph with 15,244 triples where main has 15,600 —
every bound symbol AFTER the broken entry (prune::run, distill::run,
harness_ir::*, ptx_explain::run, …) silently gone — and `--check`
would have agreed with itself. Found while regenerating the derivative
for this adoption, by the drop, not by any gate.

One character. With it, binding.yaml parses (156 entries, same as main)
and the extraction is byte-identical to main's committed contracts.nt, so
this PR owes no graph change after all.

Refs #3346, #3350

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(roadmap): mint PMAT-3602 as a work item so the AD-04 quorum can resolve it

`quorum-review.sh` refuses without `pmat work status <id>`, and `pmat work`
reads the aggregated roadmap rather than an id counter. The branch carried the
trailer `Pmat-Ticket: PMAT-3602` while no such work item existed — a GitHub
issue number is not a work item.

The id is DERIVED from the issue (`pmat work add --github-issue` calls that
"the path to prefer": GitHub allocates centrally, so two agents cannot be handed
the same id, which `max(id)+1` cannot promise). Fragment, not a direct edit to
roadmap.yaml: additive, one file, and it does not make every stacked branch
dirty on the same lines.

Acceptance is hand-entered from the issue's done_when — `pmat work add` derives
neither `spec:` nor `acceptance_criteria:` (paiml-mcp-agent-toolkit#1414).

Guards: diff_additive, fragment_required, ids_unique, sorted,
completion_is_cited all exit 0; `make roadmap-aggregate-check` reports
`roadmap.yaml == aggregate(48 fragment(s)), idempotent`.

Refs #3602
Pmat-Ticket: PMAT-3602

* roadmap: PMAT-3637 — the registration row this PR is, so its quorum judges fidelity not implementation

Round 0 ran with --ticket PMAT-3604 and all three lanes FAILed for the same
reason: the diff implements none of PMAT-3604's criteria. Correct — it was
never meant to; it registers the ticket so #3634's quorum can start. The
receipt is kept as quorum-PMAT-3637-r0-misframed-as-3604.json (a FAIL is a
record, not a mistake to erase). PMAT-3637 is the row this diff satisfies:
fidelity of the PMAT-3604 entry to issue #3604's done_when, additive
aggregate, nothing outside docs/roadmaps/. Round 1 runs against it.

Refs #3604, #3634
ont-delta: none — roadmap entries only

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(roadmap): mint PMAT-3605 as a work item so the AD-04 quorum can resolve it

`quorum-review.sh` refuses without `pmat work status <id>`, and `pmat work`
reads the aggregated roadmap rather than an id counter. This branch carried the
trailer while no such work item existed — a GitHub issue number is not a work
item until something mints it.

The id is DERIVED from the issue: `pmat work add --github-issue` calls that
"the path to prefer", because GitHub allocates centrally and `max(id)+1` cannot
promise two agents different ids. A fragment rather than a direct roadmap.yaml
edit — additive, one file, and it does not make every stacked branch dirty on
the same lines.

Acceptance hand-entered from the issue's done_when; `pmat work add` derives
neither `spec:` nor `acceptance_criteria:` (paiml-mcp-agent-toolkit#1414).

Guards: diff_additive, fragment_required, ids_unique, sorted,
completion_is_cited all exit 0; `make roadmap-aggregate-check` idempotent.

Refs #3605
Pmat-Ticket: PMAT-3605

* PMAT-3346: the roadmap row, so the AD-04 quorum for #3350 has a ticket to judge against

Acceptance transcribed from issue #3346 as this PR answers it (the type that
reads the descriptor was wrong, not the range or the descriptor), with the 9B
range instantiation explicitly out of scope until a real 9B GGUF exists.

Refs #3346

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* roadmap: round 1 said two true things — fix both

Lanes 1+2 (gemini-3.1-pro-high, gemini-3.8-flash-high) FAILed PMAT-3637 on
(a) the round-0 receipt committed under docs/audits/, which criterion (3)
forbade — dropped; the receipt judged the wrong question and its ticket
field would read as a verdict on PMAT-3604; and (b) a criterion in the
PMAT-3604 notes that issue #3604 does not state ('Only an Accepted verdict
writes a receipt') — it is PR #3634's design decision, not a done_when
item; removed from the transcription. Criterion (3) now admits this row's
own receipt at docs/audits/quorum-PMAT-3637*.json, which it must, or the
PASS receipt could never be committed.

Refs #3604, #3634
ont-delta: none — roadmap entries only

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(run): quorum round 1 found the --json surface skipped and a branch that could never run

Two findings from lane 1 (gemini-3.1-pro-high) of the AD-04 quorum, both
correct, both fixed. Lane 3 passed the PR without seeing either.

1. `reconcile_accelerator(...)?` ran BEFORE `print_run_output`, so a rejected
   `--gpu` run early-returned and `--json` emitted nothing at all — the exact
   surface #3602 item 1 names. Machine surfaces (`--json`, `--stream`) now emit
   before the refusal propagates; the human surface still prints nothing.

   This is a DELIBERATE deviation from `after_generation`'s contract, which says
   the caller "must print NO output" on a forced refusal. Named rather than
   quiet: that rule exists so a CPU result is never read as a GPU success, and a
   document carrying `"backend": {"fell_back": true}` beside exit 14 cannot be
   read that way, while a human-formatted success blob can. The protective half
   is kept; the half that blinded `--json` consumers is not.

2. The caller's `if let Some(note)` arm was UNREACHABLE. `announced` was
   `Some("gpu")` exactly when `forced` was true, and `after_generation`'s
   corrective-line branch requires `forced == false` — so the Some arm could
   never execute. A branch with no reachable caller, which is precisely the
   defect this PR fixes, reproduced one layer down while fixing it.

   `reconcile_accelerator` now returns `Result<()>`, wires the FORCED half only,
   and says in its doc comment why the default-selection half is not wired:
   nothing calls `registry::announce`, so there is no recorded announcement for
   a default selection, and manufacturing one would assert a choice this process
   never made. That is REG-8, not this PR.

Verification: cargo test -p apr-cli --lib = 7289 passed, 0 failed, 12 ignored;
cargo fmt --all --check = 0; cargo clippy -p apr-cli --lib -D warnings = 0.

Refs #3602
Pmat-Ticket: PMAT-3602

* PMAT-3346 (adoption): a bias vector is as wide as the projection it biases

Found by the AD-04 quorum on #3350 (lane 1, gemini-3.1-pro-high, cited
model_arithmetic.rs:144): the projection term used q_out for a gated
family's q matrix (2*n_h*d_k — attn_q emits the output gate, MEASURED in
Qwen3.5-0.8B) while the bias term still used q_dim. A bias narrower than its
projection is not a model. One token: q_dim -> q_out in the bias sum.

Why a delta-0 measurement did not catch it: no shipped family exercises the
case. Qwen3.5 has no attention bias; Qwen2.5 has biases but is not gated, so
q_out == q_dim there. The test that pinned 12 (a q_dim bias under a q_out
matrix) now asserts 16 and says why, and a second test holds the other
polarity — a non-gated family with biases is unchanged at 12.

115 model_arithmetic + model_family tests pass; oracle 218; clippy clean.

Refs #3346, #3350

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(run): quorum round 2 — a --json --benchmark leak, a comment claiming coverage it lacks, and a stray artifact I swept in

Three findings, two lanes, all correct.

1. LANE 1 — `--json --benchmark` leaked a human success blob on a refused run.
   The refusal path carried its OWN copy of "is this a machine surface"
   (`stream || output_format == "json"`) and dropped `!benchmark`. So that
   combination entered the branch, matched neither machine arm inside
   `print_run_output`, and fell through to the human benchmark rendering — for a
   run being refused. Two spellings of one condition drifting apart in the gap
   between them, which is the defect this PR's own dispatch comment warns about.

   One spelling now: `emits_machine_output(stream, output_format, benchmark)`.
   `the_machine_output_predicate_matches_print_run_output` pins it to the arms
   it describes over every flag combination; planting the pre-fix spelling turns
   it and `json_plus_benchmark_is_not_a_machine_surface` RED, verified.

2. LANE 1 — the classifier comment claimed `--gpu-layers all|n` was among the
   flags handled here. It is not: `Commands::Run` carries `gpu` and `no_gpu` and
   nothing else; `--gpu-layers` belongs to `apr serve`. So
   `layers_want_accelerator: false` is CORRECT and the comment was the defect —
   a comment asserting coverage the code does not have. Both comments now say
   the input is genuinely absent for this surface rather than stubbed.

3. LANE 3 — `trace-1789944278.json` (232 lines) was committed to the repo root.
   A runtime byproduct of `test_print_chrome_trace_creates_file`, which writes
   `trace-<epoch>.json` into the CWD when given no `--trace-output`. I swept it
   in with `git add -A` immediately after a quorum round — the exact thing my
   own notes say never to do there. Removed, and this commit stages files by
   name. The test writing into the repo root is a separate defect and is not
   fixed here.

Round 2 was 2 FAIL / 1 NO-VERDICT. Lane 2 has now returned NO-VERDICT twice with
`envelope_status: SUCCESS`, `transport_status: SUCCESS` and non-empty
`raw_bytes` — it answers, and the harness cannot extract a verdict from what it
returns, so width 3 has been delivering two votes.

Verification: cargo test -p apr-cli --lib = 7291 passed, 0 failed, 12 ignored;
cargo fmt --all --check = 0.

Refs #3602
Pmat-Ticket: PMAT-3602

* roadmap: transcribe #3604 verbatim — round 2's pro lane was right

Criterion (5) had grown an implementation-status clause from PR #3634's
report ('the --json field lands with #3606 StageTimings…') that issue #3604
does not state, and the out-of-scope list was a paraphrase. PMAT-3637 (1)
says every criterion present, none added: the notes now carry the issue's
done_when 1-6, admission rule and out-of-scope list as written.

Refs #3604, #3634
ont-delta: none — roadmap entries only

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* audit: quorum-PMAT-3637 — 3/3 PASS, round 3 on the ruled trio

pro / 3.7-flash / 3.6-flash, three conversations, no silences. Rounds 0-2
each said something true about this PR: round 0 judged the wrong ticket;
round 1 found a receipt outside scope and an invented criterion; round 2
found an implementation-status clause in a 'none added' transcription.
None is committed — each judged a diff that no longer exists.

Refs #3604, #3634
ont-delta: none — audit artifact only

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3346: quorum verdict 3/3 on e060c7e2c (AD-04)

Round 0 was 1 FAIL / 1 no-verdict / 1 PASS and the FAIL was real (the bias
width, fixed in e060c7e2c). Round 1 on the fixed head: 3/3 PASS,
gemini-3.1-pro-high / pro-low / 3.6-flash-high, each measured, no dissent.

Refs #3346, #3350

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(audits): AD-04 quorum receipt for PMAT-3577 — AGREED 3/3

Three independent agy lanes on distinct models, none in the author's family
(author measured as Opus 5 / claude):

  gemini-3.1-pro-high  = PASS
  gemini-3.7-flash-high = PASS
  gemini-3.6-flash-high = PASS

Lane models set per-invocation via PAIML_IMPLEMENT_CONFIG rather than by editing
the shared config, which three sessions were launching against concurrently.

THE MERGE DID NOT CHANGE WHAT WAS JUDGED. The lanes ran against 7f82e2cd3; the
PR head is 4fbd2ec31, a merge of main. The trees differ by four files, but all
four arrived FROM main, and the PR's own contribution relative to main is
byte-identical across the merge:

  files, pre-merge  : 49        files, post-merge : 49
  only in post      : (none)    only in pre       : (none)
  sha256(diff vs merge-base), pre-merge  : bf000a5ea9f94ce5c4b5e7d470f38e84bd6b6dac9760b970c1d5f89945c64d35
  sha256(diff vs merge-base), post-merge : bf000a5ea9f94ce5c4b5e7d470f38e84bd6b6dac9760b970c1d5f89945c64d35

So this is the same head for review purposes and does not need a new round. A
naive `git diff origin/main..HEAD | sha256sum` from my local head disagrees only
because that head carries this receipt commit, which the lanes never saw —
comparing the receipt's parent is what makes the two comparable.

Refs #3600
Pmat-Ticket: PMAT-3577

* fix(tests): the shape-count ratchet was red — #3605 adds a 6th shape (quorum round 1)

`the_tracked_repo_graph_is_fresh` asserts `shapes_n == 5` and names the five.
`refusal-receipt-v1` makes six, so this PR shipped a CI red that a quorum lane
found and I did not.

MEASURED, not guessed: `pv extract contracts --check` on this branch reports
`shapes_n: 6, triples: 15620`.

The hardcoded count stays hardcoded. A shape added without anyone noticing is
exactly what this assertion exists to prevent, so adding one is SUPPOSED to turn
it red and make you name the new shape. Noted in the comment that the counter is
shared across branches — a sibling PR adding a shape (#3600's parity-receipt-v2)
will need it raised again at merge, which is the ratchet working rather than a
conflict to route around.

Refs #3605
Pmat-Ticket: PMAT-3605

* chore(audits): AD-04 quorum receipt for PMAT-3602 — AGREED 3/3

gemini-3.1-pro-high = PASS, gemini-3.1-pro-low = PASS, gemini-3.6-flash-high = PASS.
Author measured as Opus 5 (claude); no lane in the author's family. Lane models
set per-invocation via PAIML_IMPLEMENT_CONFIG, never by editing the shared
config that other sessions were launching against.

This trio was chosen on measured odds after flash-class lanes returned
NO-VERDICT intermittently (3.7-flash 3/7, 3.6-flash 1/7, pro-high 0/7 across my
earlier runs). All 15 lanes voted in this batch.

Refs #3638
Pmat-Ticket: PMAT-3602

* chore(audits): AD-04 quorum receipt for PMAT-3605 — AGREED 3/3

gemini-3.1-pro-high = PASS, gemini-3.1-pro-low = PASS, gemini-3.6-flash-high = PASS.
Author measured as Opus 5 (claude); no lane in the author's family. Lane models
set per-invocation via PAIML_IMPLEMENT_CONFIG, never by editing the shared
config that other sessions were launching against.

This trio was chosen on measured odds after flash-class lanes returned
NO-VERDICT intermittently (3.7-flash 3/7, 3.6-flash 1/7, pro-high 0/7 across my
earlier runs). All 15 lanes voted in this batch.

Refs #3613
Pmat-Ticket: PMAT-3605

* PMAT-3351 (adoption): re-id the fragment — PMAT-3347 is #3348's row on main

Both #3348 (merged; bound the qwen35-e2e equations, closed #3347) and this PR
(the L2 column reads a link, not an index) address issue #3347, and both
claimed roadmap id PMAT-3347. Two PRs cannot share a row: main's PMAT-3347
stays as #3348's, this PR's fragment becomes PMAT-3351 (github_issue 3347,
same title, same notes), and the aggregate is rebuilt from main's roadmap.yaml
plus this branch's fragments — 935 + 1 = 936, nothing re-serialised.

Refs #3347, #3348

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3351: notes transcribe issue #3347's Ask verbatim (bullets 2 and 3; bullet 1 was #3348)

The quorum lanes read pmat work status, i.e. this fragment's notes, not the
GitHub issue. Empty notes would have them judge against a title. The issue
has no done_when section, so its Ask and its two defect statements are
transcribed as written, with the out-of-scope bullet named and no count
offered as a criterion.

Refs #3347

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3338: register the second ticket this PR closes, so the quorum judges the truncate fix as in scope

Quorum round 0 on a4b76397f was 2 FAIL / 1 PASS, both FAILs on one point: the
branch's first commit fixes #3338 (--table panicked on a byte-index cut inside
a multi-byte char) and PMAT-3351 does not ask for it. Both lanes called the
in-scope work correct. The fix is a prerequisite — the L2 column is exercised
through --table on the real corpus, which panicked before it — and #3338 is
an OPEN issue this PR genuinely closes. So it gets its row, transcribed from
the issue, and round 1 runs with --ticket PMAT-3351,PMAT-3338.

Refs #3338, #3347

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3604: the receipt's temp file is private to its writer — a shared name made the atomicity claim false

AD-04 quorum round 0 on #3634, lane 1 (gemini-3.1-pro-high), cited
f2_receipt.rs:180: write_receipt used one shared <sha>.json.tmp, so two apr
runs validating the same model at once could have writer B truncate the file
writer A was about to rename, and A rename B's partial into place. The reader
would classify it Unreadable and validate — the safe direction, done_when 4 —
but the docstring said 'never a truncated one', and that was false.

The temp name now carries the pid and a per-process counter; a failed rename
removes its own temp. A racing test spawns two writers on one path forty
times and parses the survivor each time: it is always one writer's WHOLE
receipt. 14 tests.

Lane 1's other two findings, for the record: --revalidate is done_when 2
verbatim (the fragment carries it), not scope creep; and F2Outcome's fields
are the values the stderr line already prints and done_when 5's data path
for #3606's JSON, not dead code. Two lanes passed the same diff.

Refs #3604

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3604: quorum verdict 3/3 on dd546592b (AD-04)

Round 0 was 2 PASS / 1 FAIL; the FAIL was the shared temp-file race, fixed in
dd546592b. Round 1 on the fixed head: 3/3 PASS, gemini-3.1-pro-high /
pro-low / 3.6-flash-high, each measured, no dissent.

The PMAT-3604 roadmap row was on this checkout UNCOMMITTED for the resolver
(pmat work status reads the checkout); it lands via #3637. The lanes judged
the committed diff origin/main...HEAD.

Refs #3604, #3637

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3351 + PMAT-3338: quorum verdict 3/3 on d915b8f16 (AD-04)

Round 0 (--ticket PMAT-3351 alone) was 2 FAIL / 1 PASS, both FAILs on the
#3338 truncate fix being out of scope; both called the in-scope work correct.
Round 1 with both tickets registered and named: 3/3 PASS, gemini-3.1-pro-high
/ pro-low / 3.6-flash-high, each measured, no dissent.

Refs #3347, #3338

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(contracts): the v1 contract had no equations, and its own denominator guard was never wired

Two reds on this PR, both mine, and the second is the better finding.

1. `contract_data_integrity` is a SHRINK-ONLY ratchet at 444 and this branch made
   it 445. The 445th is `parity-receipt-v1: no equations`. Measured which one
   rather than guessed — `parity-receipt-v2` is not in the list.

   `kind: pattern` correctly declares no SHAPE (a shape over a class nothing
   instantiates passes vacuously, which is #3610's whole lesson), but equations
   are not shapes, and this contract does assert things: PRC1-INV-001 and
   PRC1-INV-002 already said them. They are now written as the two equations the
   integrity check reads — `no_instances` and `refused_by_name` — rather than as
   new claims invented to satisfy a counter.

2. `check_guards_are_wired.sh`: `NEW: parity_receipt_denominator.sh`. I ADDED A
   GUARD IN THIS PR AND WIRED IT INTO NOTHING — the second, independent reader of
   the receipt corpus, shipped where no workflow names it.

   Minutes before finding this I wrote into that same contract's equations, as a
   precondition: "both readers are wired into CI — a refusal nothing runs is not
   a refusal." My own precondition, violated by my own PR, in the same file.
   `check_guards_are_wired.sh` caught what I had just finished writing down.

   Now named in `ci.yml` beside the other cargo-free, model-free case tables. Its
   4-case self-test passes: a legacy record refused by name, a receipt added
   without bumping the denominator disagreeing, bumping it making them agree.

NOTE — THIS TOUCHES `.github/workflows/ci.yml`, which CLAUDE.md lists as a
check-in item. Additive only: one step in an existing guard block, no matrix,
trigger or gate logic changed. Wiring an unwired guard is the minimum the failing
check asks for, and leaving it unwired to avoid the file would be keeping a
refusal that never runs.

VERIFICATION
  cargo test -p aprender-contracts --test validate_contracts contract_data_integrity   1 passed
  bash scripts/parity_receipt_denominator.sh --self-test                               rc 0, 4/4
  bash scripts/check_guards_are_wired.sh                                               PASS (4 -> 3)
  pv validate contracts/parity-receipt-v1.yaml                                         0 errors, valid

DIFF MOVEMENT, for the receipt: the judged diff DID change — an equations block
and one CI step. No behaviour the lanes reviewed was altered; the extractor, the
shapes and the denominator script are byte-identical. AD-04 re-review is the
inspector's call.

Refs #3577
Pmat-Ticket: PMAT-3577

* PMAT-3401 (adoption): the three derivatives 24 new contracts oblige — census, graph, README — regenerated together

This PR adds 24 contracts and regenerated none of the tracked artifacts
derived from the corpus. On #3581 that omission surfaced one per CI round,
each masked by the one before it. All three here, at once, with a pv built
from this tree under a pinned target dir:

  contracts/census.json   1800 -> 1824  (+24, the contracts added)
  contracts/contracts.nt  15,600 -> 15,696 triples  (+96 = 24 x 4; GREW —
                          a drop is the tell for a malformed input or a stale
                          binary; binding.yaml still parses, 156 entries)
  README CONTRACT_COUNT   2 blocks -> 1824 via make readme-sync

The merge took main's generated README blocks over the branch's hand-typed
1866, then readme-sync wrote the measured 1824; the branch's number described
a tree that never existed on main.

Verified: all 24 contracts pv-validate under the pinned binary (control:
main's crux-A-01 valid under the same one); lint_passes_on_real_contracts
green, so the sigma prose ratchet holds; 1666 engine tests; ont4b shapes gate
11/11; test-binding ratchet; readme-sync-check; FALSIFY-README-002; roadmap
additive added=26 deleted=0, aggregate idempotent, 26 fragments present.

Refs #3401

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(run): extract reconcile_and_emit — run() hit cognitive 27 against a 25 ceiling

`check_complexity_ratchet.sh` went RED with:

  RED  NEW  crates/apr-cli/src/commands/run_entry.rs::run  cyclomatic 21 cognitive 27
            (over a threshold, absent from the comparand)

The reconcile-then-emit block I added in this PR is what took `run` over. Moved
into `reconcile_and_emit`, which also gives the ordering decision — machine
surfaces emit on a refusal, the human surface does not — a place to be documented
that is not the middle of a 30-argument entry point.

Now: PASS (D2): e6f77c98c vs 19750b384 measured by pmat 3.41.1 — none new, none grown.

NOTE FOR THE NEXT PERSON: I first re-ran the ratchet against my WORKING TREE and
saw the identical cognitive 27, and briefly concluded the extraction had not
helped. It had. The script measures two REVISIONS — it prints
`merge HEAD <sha>` — so an uncommitted fix is invisible to it. Commit, then
measure.

Tests: cargo test -p apr-cli --lib = 7291 passed, 0 failed, 12 ignored;
fmt --check = 0; clippy -D warnings clean.

Refs #3602
Pmat-Ticket: PMAT-3602

* PMAT-3496: a registration row for the registration PR — the author's earlier receipt named this ticket but no row existed on the branch

* PMAT-3496: clause (1) transcribes issue #3495's title verbatim (132 Kani harnesses; Verus pilot), not a paraphrase

* fix(guard): a step NAME read as a bare guard_tree.sh dispatch hid four dark guards; the #3305 guard runs nowhere and dies on intel's python 3.10 (#3644)

Refs #3644 #3646 #3305 #3626

THE FINDING, proven by mutation before it was argued. check_guards_are_wired.sh
counts a guard wired-by-dispatch when a workflow runs guard_tree.sh in a mode
whose --dry-run RUN set contains it. Its invocation regex accepts `guard_tree.sh`
followed by `'` (for a quoted run: string). ci.yml has two step NAMES,
`"guard_tree.sh's own case table …"` and `"guard_tree.sh's parallel dispatcher
…"`; the apostrophe matched, neither line says --no-cargo, so each was read as a
bare dispatch in mode "all" -- 55 cargo-classified guards counted wired that
`guard-tree` (--no-cargo) never runs and `guard-cargo` never names. Rewriting
those two names (nothing else) turns the meta-guard RED: 3 -> 7 unwired --
check_model_ladder.sh, check_pathonly_devdeps_unused_in_src.sh,
check_pr_review_counts.sh, check_pr_review_receipt.sh. Three of the four are
cargo-classified by a COMMENT that says the build tool's name; none of those
three invokes it.

check_pathonly_devdeps_unused_in_src.sh (#3305: src/ may not use a dev-dep that
publishing deletes; clean-room red 8/8 across two releases) has therefore run
nowhere since it was written on 2026-09-15. And it could not have run on the
intel hosts: `import os, re, sys, tomllib` at module level, python 3.10.12
there with neither tomllib nor tomli (measured on mac-server tonight; the
sovereign-ci container has no python3 at all). Reproduced with python3.10 -S:
the traceback is swallowed by `out=$(scan … || true)` and the case table prints
"FAIL: missed hit_use" ×4 -- the regex reported broken by an interpreter that
never ran it. Same class as #3626's guard on intel-clean-room-6.

WHAT CHANGES

scripts/check_guards_are_wired.sh
  - `_not_a_name_line` drops `name:` lines before the invocation match, in
    dispatcher_wired() and the per-guard scan. A step name is documentation
    whatever it contains.
  - rows 8-10: the exact ci.yml fixture (`- name: "guard_tree.sh's own case
    table …"`, no run: line) must leave the dispatched guard unwired; a QUOTED
    run: line still dispatches (the control the `'` exists for); a step named
    after a guard wires nothing. Mutant (drop removed): rows 8 and 10 RED.
  - the ledger's ratchet is now set-aperture, owned by this file.

scripts/lib_baseline_ratchet.sh, scripts/check_baseline_ratchets.sh
  - set-aperture gains a NAME-entry admission for sets of FILES: an added entry
    with no `:` is admitted iff the comparand carries that file (at the entry's
    path, or beside the owning guard -- the ledger names siblings by basename
    and guard_tree.sh reads it so) AND the owning guard changed in the diff.
    A file this branch created is refused (PERF-028's shape). Six rows: predates
    -> admitted; branch WROTE it -> refused; no guard edit -> refused; escaping
    path -> refused; beside the OWNER -> admitted; beside a different owner ->
    refused. Lib mutants (always admit / branch removed / sibling resolution
    removed) each turn rows RED; a separate absolute-path check survived its
    mutant because `git cat-file -e` already refuses those, so it is not there.
  - classify: unwired_guards_baseline.txt -> set-aperture, owner named.

scripts/unwired_guards_baseline.txt  3 -> 6, as APERTURE REVEALS with a reason
  per line (each may only leave): check_model_ladder.sh (T-2 release gate, the
  multiplatform_dogfood class); check_pr_review_counts.sh (RED on main today,
  "6 row(s) disagree" -- #3646); check_pr_review_receipt.sh (takes a receipt
  path; nothing passes one -- #3646). check_pathonly_devdeps_unused_in_src.sh
  is NOT ledgered: see below.

scripts/check_pathonly_devdeps_unused_in_src.sh
  - readers tomllib -> tomli -> ENV rc=2 naming python version and $RUNNER_NAME.
    No purpose-built manifest reader: a second TOML implementation over 78
    manifests, where a wrong "has source" is a silent PASS on exactly the
    #3305 class, is not a fallback worth shipping. UNMEASURED-with-a-name is
    the honest state on a runner with no TOML library.
  - the scanner ends with `SCAN-DONE manifests=N reader=…`; the shell REQUIRES
    it. Output without it (no reader, /bin/false, no python3) is ENV rc=2 --
    never rc=1, never "no violations". The selftest and the tree scan
    propagate 2 instead of swallowing it.
  - five death rows: both readers absent (sys.modules nulled so the imports
    fail for real); interpreter exits 1 silently; no interpreter (127); the
    working interpreter as control; and THE WHOLE GUARD under the intel shape
    (nested, recursion-guarded) -- the call site is where `|| true` hid it.
    Mutants: trailer not required (3 rows RED); trailer printed on the
    no-reader path (1 RED); `|| true` restored (the whole-guard row RED).
  - three comments and one FAIL string reworded so the file no longer says
    the build tool's name with a space after it: it invokes none, and that
    substring is what CARGO_RE classifies on. guard_tree.sh --dry-run
    --no-cargo now lists it as `run:`; bare run on this tree rc=0 in 3 s.

Passes: check_guards_are_wired --self-test 10/10; check_baseline_ratchets
--self-test 61 rows; pathonly --selftest under python 3.13.1 and 3.10.12
(tom…
guyernest pushed a commit to guyernest/aprender that referenced this pull request Sep 29, 2026
…ate turns down, instead of "parity disproven" (5) (paiml#3689)

* fix(pv): --table panicked on the real corpus — it cut a property by BYTE index (#3338)

`pv proof-status contracts/ --table` exits 101 on `contracts/`:

    byte index 40 is not a char boundary; it is inside '∈' (bytes 39..42)
    of `for Q4_K_M Qwen2.5-Coder, quantization ∈ {Q4_K, Q6_K}`

The column width is a byte count (`property.len()`, capped at 40) and
`truncate` sliced `&s[..max]`, so any property whose byte 40 lands inside a
multi-byte char panics. Eight contracts in `contracts/` do; the first one
walked is `apr-inspect-quantization-v1.yaml`.

The budget stays a byte budget — the table is laid out in bytes — and the
cut now walks back to the nearest char boundary.

Two tests, both RED before this commit (each panicked at
obligation_matrix.rs:169): the helper row, and one through
`format_obligation_table` because that is the path the operator hit. The
helper fixture asserts `!s.is_char_boundary(40)` first, so it cannot
silently stop proving anything.

Pmat-Ticket: PMAT-3347
Refs #3347, #3338

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(pv): the L2 column read an INDEX, not a link — so 3,573 of 3,753 obligations ticked (#3347)

`obligation_matrix` computed `l2_tested` as `idx < falsification_tests.len()`.
Obligation 3 was "tested" because the contract happened to hold 4 tests,
whoever those tests were about. All 7 obligations of
`qwen35-e2e-verification-v1` showed ✓ before a single test existed. The
fallback was a substring match between the obligation's `property` prose and a
test's `rule` prose, which is an inference, not a claim.

WHICH LINK, decided by counting the corpus (1,842 files, 3,792 obligations,
4,691 falsification tests), not by preference:

  proof_obligations[].discharged_by   89  (62 `falsification_tests[N]`, 13 a
                                           test id, 1 a YAML sequence, rest
                                           comma lists / prose / a kani id)
  falsification_tests[].binds_to      38
  falsification_tests[].obligation    26  (12 name an obligation id, 6 the
                                           exact property text, 8 dangle)
  proof_obligations[].id             180 of 3,792
  kani_harnesses[].obligation      1,918 — that is the L3 column, not this one

All three L2 spellings are DECLARATIONS by the contract author, so all three
are read. `binds_to` is a serde alias of `obligation`, which is safe only
because no entry carries both keys — checked, because serde turns that into a
`duplicate field` parse error rather than a silent pick.

Not read: `applies_to`. 12 `binds_to` values match one, but `AppliesTo` is an
enum with `#[serde(other)] Other`, so the string is discarded at parse and
there is nothing left to compare. Those 12 report `?`, not a false ✗.

WHAT THE COLUMN NOW SAYS. Three values, because "no test covers this" and
"nothing here says which test covers what" are different facts:

  ✓ Tested   a test in this contract cites this obligation
  ✗ Untested the contract's links resolve and none names it — or it ships
             no falsification test at all, which is a reading, not a gap
  ? Unknown  no readable link; not measured. An unread window is Unknown,
             never a tick

MEASURED over `contracts/`, and the drop IS the point — it is what the old
column was hiding:

              before   after
  L2 ✓         3,573      86
  L2 ✗           180      65
  L2 ?             0   3,602
                       (3,753 obligation rows, 873 contracts)

Nothing was adjusted to keep the number up, and no threshold was added.

The two link fields did not exist on the structs, so both keys were written to
disk and silently dropped on parse — the shape of #3314 (`id`) and #2465
(`test_harness`). `discharged_by` is typed `Citation` (scalar | comma list |
YAML sequence) because `Option<String>` failed the WHOLE corpus on
`publish-manifest-v1`: `invalid type: sequence, expected a string`.

RED first: `l2_does_not_tick_for_an_obligation_no_test_cites` — two
obligations, two tests, both citing OB-A. It asserted through the rendered
table (the surface that was lying, and API-stable), so it is the SAME test
before and after: on the old code `Beta holds | ✓`. Its OB-A arm keeps a fix
that merely stopped ticking everything from passing.

Out of scope_paths, and forced: `lint/strict_test_binding.rs` holds the only
EXHAUSTIVE `FalsificationTest` literal in the tree (no `..Default::default()`),
so no schema field can compile without that one line.

Pmat-Ticket: PMAT-3347
Refs #3347, #3091, #3114

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(pv): single-file --strict-test-binding reported every ref missing — it now refuses (#3347)

The gate resolves cited test names against a source index rooted at the
contract path's PARENT. The directory form gets the repo root and finds
`crates/`; the single-file form gets `contracts/`, which holds no source, so
every cited ref resolves to nothing and all of them are reported missing.

Measured on `contracts/pv-artifact-kinds-v1.yaml`, a control whose eight refs
all resolve:

    pv lint contracts/pv-artifact-kinds-v1.yaml --strict-test-binding
        total_refs 8, existing 0, missing 8
    pv lint contracts/ --strict-test-binding
        total_refs 548, existing 521, missing 27  <- none of the 27 is this one

A control contract failing identically to a broken one is a gate that cannot
discriminate, and it fails SILENTLY: `passed` is true in non-strict mode, so
the run still says `Result: PASS` while printing eight false findings.

REFUSED rather than repaired. The scan root is computed in
`provable_contracts::lint::run_lint`, outside this ticket's scope; a refusal
lives in the caller, is honest, and cannot be mistaken for a clean bill. If
the root is later made explicit (a `LintConfig` field the CLI can feed, which
is what `--crate-dir` does NOT do today), this refusal is what should be
deleted.

Exit 1, not the exit-2 `decline:` class: exit 2 belongs to `ZeroContracts`,
whose message ("0 contracts under ...") would be false here — there IS a
contract; it is the gate that cannot run over it.

Two tests, in the CI-wired `cli_integration` target: the refusal names the
flag and prints no PASS and no findings, and a directory-form control proves
the refusal is specific to the single-FILE form rather than to the flag.

Pmat-Ticket: PMAT-3347
Refs #3347

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(crux): AutoGluon becomes a CRUX competitor — category O, 24 contracts, 25 tickets, registry edit + mutation proof

Research of ../autogluon (1.6.3 @ 77946149) as a competitive-research
source for aprender, landed the way category N landed linfa and burn
(#3169): the competitor is admitted to the CLOSED registry, every story
is a real contract with falsification gates, every story is a registry
row, and every missing/partial story has a GitHub issue and a roadmap
fragment.

What AutoGluon is, measured from the tree rather than recalled: three
predictors. TabularPredictor (65 public methods, 11 presets, 24 model
families of which 8 are tabular foundation models added in 1.4-1.6),
TimeSeriesPredictor (Chronos-2/Toto-2 pretrained, 30+ local/deep models,
16 metrics incl. WQL/MASE/RMSSE, auto backtesting since 1.5) and
MultiModalPredictor. Evidence under evidence/crux/autogluon/.

What aprender has, measured at eb262f8eb: automl/ is a single-estimator
hyperparameter tuner (TPE, grid, random, DE, TimeBudget, EarlyStopping);
time_series/ is one univariate ARIMA; encoders, calibration, SHAP/LIME/
permutation importance and KFold/cross_validate exist as building
blocks. No predictor-level fit(label), no leaderboard, no bagging,
stacking or greedy weighted-ensemble selection, no panel forecasting,
no quantile forecast metrics. The gap is the AutoML UX, not the
algorithms.

Category O — AutoML Parity — 24 stories: 9 P0 (the README hello-world:
fit(label), problem-type inference, presets, leaderboard, feature
pipeline, weighted ensemble, time budget, panel forecaster, quantile
metrics), 9 P1 (bagging, stacking, importance, threshold calibration,
deployment artifact, tabular foundation model, backtesting, local
baselines, pretrained forecaster), 6 P2 (refit_full, distill,
infer_limit, fit diagnostics, memory-aware fit, covariates).
MultiModalPredictor, autogluon.cloud, MLZero and Ray-parallel fits are
CUT on the epic with reasons.

  CRUX_COMPETITORS: [&str; 14] -> [&str; 15]   + autogluon

Not a BEAT pillar: aprender claims no pinned-benchmark win over
AutoGluon. Both registry tests that keep BEAT_INCUMBENTS and
CRUX_COMPETITORS apart are extended, not worked around.

Tickets: epic #3370, stories #3371-#3394, label pareto-autogluon.
Roadmap: 25 fragments under docs/roadmaps/entries/, roadmap.yaml
regenerated by the aggregator (idempotent check passes).

Spec: docs/specifications/crux-competitive-research-ux-workflows.md
v2.2 -> v2.3 — §3 gains rows for linfa+burn (category N, which #3169
never recorded there) and AutoGluon; §5 gains Category O; §6 notes
that coverage_intake in the YAML is the source of truth.
coverage_intake 267 -> 291 (partial 72 -> 77, missing 152 -> 171).

Verification:
- pv built from THIS tree validates 25/25 (24 new + master). The stale
  ~/.cargo/bin/pv rejects crux-O-01 with CRUX-002 — the behavioural
  delta proves the registry edit engaged.
- Mutation-verified: deleting "autogluon" from CRUX_COMPETITORS turns
  competitor_registry_covers_the_corpus_vocabulary RED with "autogluon
  is used by contracts/ and must stay in CRUX_COMPETITORS" and
  the_real_crux_registry_rows_are_all_in_domain RED. Restored: 20/20.
- cargo test -p aprender-contracts --lib: 1526 passed, 0 failed.
- Every falsification gate is LIVE-PENDING prose (no `::`), so
  strict-test-binding has nothing to refuse; the obligations are
  RECORDED as unfalsifiable-by-absence, not satisfied.
- README CONTRACT_COUNT regenerated 1835 -> 1866 by readme_sync.sh.
- Guards: roadmap fragment/ids/sorted/additive/completion, contract
  test-binding and enforcement, shell-lint ratchet, hardcoded paths,
  readme claims, grep -q ratchet — all rc=0.

Pmat-Ticket: PMAT-3370

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

* chore(crux): bind the admission PR to its own ticket PMAT-3401 — the epic's acceptance criteria describe the programme, not this diff

Round 2 of the quorum read the epic (PMAT-3370) as the ticket and refused
the admission for not implementing the 24 stories. The admission is its
own unit of work with its own done-when; this fragment says so.

Closes #3401
Pmat-Ticket: PMAT-3401

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

* chore(crux): PMAT-3401 title inventories the diff exactly — 26 fragments (its own included) and the scaffold CATEGORY_NAMES edit

Quorum round 3 lane 1 refused on two literal mismatches between the
ticket and the diff: '25 roadmap fragments' (there are 26 once this
ticket's own fragment lands) and an unlisted edit to
scripts/crux_scaffold_contracts.py. The title now lists every path.

Pmat-Ticket: PMAT-3401

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

* roadmap(PMAT-3495): VERIFY-001 on 0.71.0 — run the 132 Kani harnesses in CI (proof credit from runs, not declarations), then a Verus pilot on one dequant/parser function (#3495)

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

* quorum(PMAT-3496 brief, PR #3496): 3/3 PASS on 57e0e4dfb — gemini lanes, measured; author claude-fable-5-1

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

* PMAT-3577: the logit-parity receipts under contract — parity-receipt-v2, extract:parity-receipt, and the 7 back-filled

Until this row the logit-parity records under evidence/parity/** had NO validator
of any kind. Not a weak one — none. That is why seven of them sat in the tree
carrying no comparator for months: there was nothing that could have noticed.

WHAT LANDS
  · contracts/parity-receipt-v2.yaml — three shapes: parity-receipt-complete
    (closed, ignoredProperties empty), parity-comparator-self, -oracle. The
    subset has no sh:or, so the comparator split is two shapes over two
    subclasses the extractor assigns by kind.
  · contracts/parity-receipt-v1.yaml — the retired layout, recorded with NO
    shape: all instances were migrated, and a shape whose target class nothing
    instantiates passes vacuously. The enforcement that replaces it fires — the
    extractor refuses an unmigrated record BY NAME, and so does the predicate.
  · ontology/extract/parity_receipt.rs — record / unmigrated / other, and
    skipping is never silent.
  · scripts/parity_receipt_denominator.sh + evidence/parity/EXPECTED_RECEIPTS —
    the count is pinned by an INDEPENDENT predicate. An extractor checked
    against a number the extractor produced proves nothing.
  · Unknown{ExtractorMiss}, exit 2 — a new element of the verdict lattice. An
    extractor that silently saw the wrong corpus reports the same "no
    violations" as one that saw all of it.
  · the 7 records migrated to v2 and back-filled with comparator {kind: self,
    reason}, in this commit, as the row requires.

THE DENOMINATOR IS 7, NOT 8. The #3574 receipt is in PR #3575, still open; it is
not on main. Measured: 113 files under evidence/parity, 7 parity records, 0 with
a comparator. #3575 bumps it to 8 when it lands — this row's own falsifier on its
first real use.

check_parity_receipt.sh IS NOT TOUCHED, and that is the finding, not an omission.
It validates the THROUGHPUT family (instrument, protocol_ref, lanes[],
decode_tok_per_sec, the #2696 cross-class defect); a logit record has never
carried one of those keys. Folding it in — as item 6 asked — would have deleted
the #2696 validator from a family nobody was watching. One validator per artifact
family, and the discriminator is the artifact's required keys, never its filename.

THE BACK-FILL IS A RELABEL. `raw` is the original apr parity --json document key
for key; every envelope field is quoted from committed evidence named in each
record's provenance.record. model_sha256 is carried only by the one record that
measured it: hashing the files today and attaching that to a receipt about
2026-09-06 would be a claim about a different world wearing a witness's clothes.

One derived field was wrong first time and is worth recording: result.verdict
copied raw.parity — apr's own per-position flag — which says PASS for the two
1.5B cells their own RECORD.md calls RED. It now resolves the threshold from
thresholds.yaml and reproduces all seven readings the RECORD.md files state,
both REDs included. No threshold is ever typed into a shape.

NOTHING IS ARMED. armed_shapes lives in lint-baseline.json, a shared file this
row may not touch (decision 7); the shapes are computed and reported, as
ladder-green was at ONT-4c1. Arming is a follow-up with the label.

CONTROLS, both directions: 7 violations with the comparator stripped from all
seven → 0 as committed; exactly 1 for a single plant, naming focus node and
property; widening sh:in to accept `oracle` turns ont4c3_parity_receipts RED, and
mutating only the real contract turns the fixture-drift test RED; an unmigrated
record declines at exit 2 naming the file; 2 receipts against a denominator of 1
declines naming both numbers; pass / fail / decline are 0 / 1 / 2.

  cargo test -p aprender-contracts --lib ontology::extract::parity_receipt  10 ok
  cargo test -p aprender-contracts-cli --test ont4c3_parity_receipts         8 ok
  bash scripts/parity_receipt_denominator.sh --self-test                     4 ok
  pv lint contracts --gate {sigma,relations,shapes}          Pass, 0 violations
  pv extract contracts --check                                            rc 0

ONT-4c3 is NOT bound in the ONT-001 ledger: that ledger is paiml/infra's
paiml-ontology.md, where v4.8 still defines ONT-4c3 as KERNEL receipts. The
re-scope is an infra PR, not this one — raised with the cop rather than left as a
checked box. Receipt: docs/audits/impl-PMAT-3577-receipt.md.

Refs #3577, #3576, #3269, #3575, #3567

Pmat-Ticket: PMAT-3577

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3577: name it WrongCorpus, not ExtractorMiss — a prefix of its opposite is a defect

`ExtractorMissing` already meant the opposite thing: an extractor that does not
exist. `ExtractorMiss` would have sat beside it in the same 18-element lattice,
one letter apart, with the shorter a PREFIX of the longer — `grep ExtractorMiss`
matches both, and any substring test over the reasons merges them silently. That
is the defect class this repo keeps paying for (#3573 today), so the name now says
what happened: the extractor RAN and read a corpus the tree does not declare.

Renamed before it reached main, when a rename is a sed and not a migration.
Caught in review by the cop.

Refs #3577, #3573

Pmat-Ticket: PMAT-3577

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3577: the ONT-4c3 divergence is filed as paiml/infra#814, not left in a thread

Two repos holding different definitions of one identifier is a row, not a flag in
a message: nothing collides where a tool would see it, so it collides in a
person's head months later when they implement the ledger's meaning and find
their correct work unusable.

Refs #3577, paiml/infra#814

Pmat-Ticket: PMAT-3577

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* #3605: removed_by gets a shape — refusal-receipt-v1, with a closed sentinel set so the required field manufactures nothing

`removed_by` had 0 occurrences tree-wide, no schema and no validator. Every
refusal #3597 writes would have minted an unenforced convention, and a tree full
of consistent-looking `removed_by:` lines reads as validated when it is
decoration.

PHASE 0 — ITS OWN CONTRACT, AND NOT BECAUSE IT IS EASIER TO WRITE

Option B is refused on a fact: `parity-receipt-v1` DOES NOT EXIST AT HEAD. It is
in unmerged #3600, and there it is deliberately the RETIRED layout with no shape
and no instances. A live validated field on a superseded contract with zero focus
nodes is a field nothing can carry. Independently, by the discriminator this tree
has now used twice — an artifact family is named by its REQUIRED KEYS, never by
its filename — a parity receipt requires host, backend, comparator,
threshold_source and per-position metrics; a refusal requires a verb, a reason,
an exit code and removed_by. They share no required key.

A THIRD option was considered and refused, and it is the more attractive one:
put removed_by on apr-cli-commands-v1.yaml, where the verb already IS the focus
node and the universe is already the trustworthy 111. Refused because the
registry is the UNIVERSE and #3597's method is to DIFF the registry against the
buckets. If the buckets live in the registry, the denominator and the numerator
are the same artifact and the diff is vacuous by construction. The registry says
what EXISTS; a refusal says what was TRIED. Keeping them apart is what lets that
diff be a real diff.

THE FORCED-BINDING TRAP, AND WHY minCount 1 IS SAFE HERE

A required field with no escape manufactures false data: `pv validate` requires
kani_harnesses so authors fabricate one, and apex#57 had lean_theorem copied
verbatim into twelve contracts resolving to nothing, gate green throughout. The
escape is a closed sentinel set, so "there is legitimately nothing here" is
SAYABLE and still CHECKABLE:

  v<major>.<minor>   the release that removes it
  never              refused permanently by design
  unscheduled        a defect with no release chosen

tbd, soon, n/a, pending, a bare `0.70`, a patch-level `v0.70.1` and a git sha are
all RED. A version rather than a sha because a refusal answers "which release do
I need?" and a sha is precise about the tree and silent about the boundary; the
sha form is made INVALID rather than discouraged, because a shape that permits
two spellings gets both.

BOTH ARMS, and the second is the point: 9 cases over 8 fixtures. refusal-ok
carries all three declared forms and passes with 3 focus nodes;
refusal-undeclared-sentinel plants the PLAUSIBLE `tbd` and must go red. Mutation
proved red-capable: widening the pattern to accept tbd fails exactly
a_plausible_but_undeclared_sentinel_is_refused and
accept_and_refuse_are_distinct_answers, and restoring makes 9/9 green. Fixtures
carry the real contract byte for byte, so widening it without them is caught too.

No new extractor: entity {type: json, ref} + vocabulary is the existing machinery
for "validate this document", and a second reader for one more family is the
thing this tree keeps filing against.

done_when 6 — the interim re-check list is EMPTY: `removed_by` still has 0
occurrences at HEAD, so no refusal written under the interim needs revisiting.

The ledger is seeded with the one refusal already measured (`apr bench` refuses
qwen35) so the shape has a real focus node instead of passing vacuously over an
empty list. WHICH verbs land there is #3597's bucket, not this row's.

Refs #3605, #3597, #3600, #3080

Pmat-Ticket: PMAT-3605

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3577: count parity-receipt in by_entity_type — ONT-001 v4.10's probe reads ABSENT otherwise

The spec's probe asks `by_entity_type["parity-receipt"] == 7`. Measured on this
branch before the change: the map carried pv-contract, gguf, apr-model, code and
lean, and NO parity-receipt key. The seven records were there — by_shape showed
parity-receipt-complete=7 — but the entity-type map did not carry them, so the
probe would have read ABSENT.

AN ABSENT KEY IS NOT ZERO. A consumer treating it as one measures nothing and
calls it a pass — the same shape as #3610, one map over.

Registering the entity type in Sigma was not enough; it has to be counted where
the probe looks. A test now asserts the key exists AND that it equals the shape's
own focus-node count, so the two numbers cannot drift apart.

Refs #3577, #3610, paiml/infra#831

Pmat-Ticket: PMAT-3577

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3577: regenerate contracts.nt with a pv built from THIS worktree — the shared target dir served another branch's binary

The merge commit's contracts.nt was 107 triples short: every parity-receipt and
parity-comparator node was missing, because `cargo metadata`'s target_directory
is shared across worktrees and the pv on PATH had been built from a different
branch minutes earlier.

AND `pv extract contracts --check` PASSED ON IT, because the check re-derives the
graph with the same binary. A stale tool comparing an artifact against its own
re-derivation agrees with itself about nothing being there — the derived file and
the checker were wrong in the same direction, which is the only way that gate can
fail to fire.

Rebuilt with CARGO_TARGET_DIR pinned to this worktree; the 107 triples return and
by_entity_type[parity-receipt] reads 7 rather than absent.

Refs #3577

Pmat-Ticket: PMAT-3577

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* register the new CLI test in scripts/tree_reader_tests.txt

The registry is derived from the tree and drifts the moment a test that reads the
tree is added without listing it. Each of the three new tests failed this guard on
its OWN branch — not a shared commit, as first read.

Pmat-Ticket: PMAT-3598

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* register the new CLI test in scripts/tree_reader_tests.txt

The registry is derived from the tree and drifts the moment a test that reads the
tree is added without listing it. Each of the three new tests failed this guard on
its OWN branch — not a shared commit, as first read.

Pmat-Ticket: PMAT-3598

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* #3600: the migration broke TWO readers of the records, including the release judge

I asked "who else reads this?" for the <unk> assumption and did not ask it for my
own migration. Two consumers read the legacy top-level metrics:

  crates/apr-cli/src/commands/parity_admission.rs  -> mac-check RED
  scripts/check_model_parity.sh                    -> C14, the RELEASE judge

The second is the one that matters: C14 is the pre-publish dogfood's parity gate,
and on the migrated records it reported "no per-position metrics in the output"
for a record that is fine. The migration would have taken the release gate down.

THE RULE, STATED ONCE IN BOTH READERS: a v2 receipt EMBEDS the raw
`apr parity --json` document under `raw`, so look inside the envelope when there
is one. A fresh `apr parity` run is the raw document itself and carries the
readings at the top level. Those are two different INPUTS — a tool's output and an
archived receipt quoting it — not two spellings of one, which is the distinction
that makes this a rule rather than the permissiveness #3613 refuses.

The self-test's fixture builder read the same way, so the must-RED twin was being
built from a KeyError and that control could not have fired.

Verified: 7B sentinel PASS, 1.5B sentinel RED (unchanged from pre-migration),
check_model_parity.sh --self-test 26/26, parity_admission 19 tests.

Refs #3600, #3577

Pmat-Ticket: PMAT-3577

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3604: the F2 hybrid guard runs once per (model sha256, apr version, device) and leaves a receipt

f2_validate_qwen35 proves the CUDA hybrid forward against a 64-position CPU
reference before it will serve a token, on EVERY apr run. Measured (#3598
row 1): 67 % of a 14 s time-to-first-token, 90-93 % of it the CPU forward.
The guard is right to exist and wrong to run per call.

Now: the first run of a (model sha256, apr version, device) triple validates
and writes a receipt; a later run whose triple matches reads it and skips the
forward; `apr run --revalidate` forces a fresh run and rewrites it.

MEASURED HERE, RTX 4090, Qwen3.5-4B-Q4_K_M, the 144-word row, --max-tokens 1,
GPU occupancy recorded before every run, binary built from this tree under a
PINNED target dir (the shared one handed me another worktree's binary first):

  --revalidate, warm cache   17.18 s wall   guard 9,747 ms on 65 positions
  receipt hit, warm cache     7.39 s wall   guard 0 ms      sha256 1,173 ms

  -9.8 s wall; guard 9,747 -> 0; the key costs 1.17 s/run (2.74 GB at
  2.3 GB/s) and is printed separately so it cannot hide in either number.

THE RECEIPT IS THE VALIDATION, WHICH IS WHY IT IS STRICT. Every path that is
not "three keys match" validates, and the three planted-receipt falsifiers
were run END TO END in the real binary, not only as unit tests:

  wrong model sha256   -> re-validated: "receipt is for model bbbb…, this file is 00fe…"
  wrong apr version    -> re-validated: "receipt written by apr 0.61.0, this is 0.68.2"
  wrong device         -> re-validated: "receipt written for NVIDIA GB10, this device is …4090"
  corrupt file         -> re-validated: "receipt unreadable (…: not a receipt)"
  missing file         -> re-validated: "no receipt for this model"

Absence is never consent, and absence and unreadability are told apart.

A RECEIPT IS WRITTEN ONLY AFTER A VALIDATION THAT JUDGED SOMETHING. The guard
has three early exits that let the GPU serve without comparing a position —
SKIP_PARITY_GATE=1, a probe under two tokens, a CPU reference that would not
run. f2_validate_qwen35 now returns F2Verdict {Accepted{positions_judged},
Rejected, NotJudged} instead of bool, and only Accepted writes; otherwise a
one-token prompt would "validate" the triple for every prompt after it.

The decision table (f2_receipt.rs) is pure and CUDA-free, so its 13 tests run
on every build. --revalidate reaches the guard through the same env seam the
guard already reads SKIP_PARITY_GATE from, rather than a 38th positional
parameter on run_entry::run and six forward signatures #3606 is changing.

done_when 5 is partial and says so: [source=receipt|fresh] is on the guard's
stderr line; the `apr run --json` field lands with #3606's StageTimings, and
F2Outcome{source, validate_ms, sha256_ms, receipt_path} is returned to the call
site for exactly that. #3606 and this PR both edit f2_validate_qwen35's return
path; whichever lands second reconciles ~10 lines, and #3606's lane is told.

Evidence: evidence/perf/3604/MEASUREMENT.md + the stderr of all nine runs.

Refs #3604, #3596, #3598, #3606

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* roadmap: mint PMAT-3604 so the AD-04 quorum for #3634 can run

pmat work status PMAT-3604 refused ('Item not found'): #3604 was minted as a
GitHub issue with a done_when but never as a roadmap entry, and the quorum
script hard-requires the work item. Fragment + aggregate, nothing else.

Refs #3604, #3634
ont-delta: none — a roadmap entry; no ontology entity, shape, reason or resolution

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(run): --gpu that fell back to CPU reported success — the refusal had no caller (#3602)

`apr run --gpu` on a model whose GPU attempt is rejected at runtime printed a
result and exited 0. Measured on lambda (RTX 4090 sm_89, apr 0.68.2 e6f77c98c,
qwen2.5-coder-0.5b-instruct-q4_k_m):

  exit=0
  stderr: warning: GPU output diverges from CPU at position 1 (cosine 0.4153)
  stdout: { "used_gpu": false, "inference_time_ms": 33646.13 }

33.6 seconds on the CPU, reported as a successful --gpu run, with `used_gpu:
false` as the only signal — the same value a deliberate CPU run reports.

The decision was already made, recorded and unit-tested. `registry::after_generation`
implements it (R-0b, #3002/#3042): a FORCED accelerator that fell to CPU is a
refusal, a DEFAULT selection that fell to CPU gets a corrective line. A git grep
found its only callers were its own tests. `registry::announce` and
`registry::parity_line` are in the same state, which is why a real run prints
zero `selected:` and zero `parity:` lines.

So this is not a new policy. It is the recorded one, reached from `apr run`:

- `dispatch.rs` classifies the request ONCE via `registry::Request::wanted()`
  rather than re-deriving "forced" — two spellings of one rule drift apart.
- `run_entry::run` calls `reconcile_accelerator` before any success output.
- `--json` gains a `backend` object: `requested` / `ran` / `fell_back`, because
  `used_gpu: false` alone collapses "ran on CPU deliberately" with "asked for
  the GPU and was refused it".

`accel::tests::every_accelerator_surface_calls_the_refusal` passed throughout:
it guards `ensure_available`, the BUILD-time refusal, not the RUNTIME one. A
guard over one of two refusals reads as coverage of both.

Falsifier, both directions (`run_tests_accel_reconcile.rs`, 6 cases). Planting
the pre-fix behaviour (`reconcile_accelerator` → `Ok(None)`) turns
`a_forced_accelerator_that_ran_on_cpu_is_refused` and
`the_json_distinguishes_a_deliberate_cpu_run_from_a_rejected_gpu_run` RED while
`a_forced_accelerator_that_actually_ran_on_gpu_says_nothing` stays green — so
the tests discriminate rather than just failing. `used_gpu: None` is Unknown,
not a fallback: refusing on it would make every non-reporting path a hard error.

NOT included, deliberately: the rejection's REASON (cosine, position) is on
stderr but not in the JSON. It is produced inside realizar's F2 gate and no
channel carries it to the CLI; adding one is a #3606-shaped follow-up, not
something to approximate with a guess here.

Refs #3602, #3483
Pmat-Ticket: PMAT-3602

* test(PMAT-3346): the measured Qwen3.5-0.8B inventory the dense path cannot reproduce

QE2E-INV-001 could not be judged because nothing in the tree held a MEASURED
Qwen3.5 tensor inventory to judge against. This adds one: the 320 tensors of
~/models/Qwen3.5-0.8B-Q4_K_M.gguf (sha256 bd258782...dc517), read straight from
the GGUF header rather than from a model card.

It already falsifies the current arithmetic. Dense/GQA accounting applied to
that file gives 644,400,128 against a measured 752,393,024 — short by
107,992,896, 14.4% of the model, because 18 of the 24 layers are Gated DeltaNet
and no term here counts their conv, gate, state or output projections.

Two shapes in the file are also not what dense accounting predicts, and both are
pinned: attn_q is [1024, 4096] = 2 * num_heads * head_dim (the q projection
emits the attention output gate alongside the query; attn_output [2048, 1024]
confirms num_heads * head_dim = 2048), and attn_q_norm/attn_k_norm are present
at head_dim. The file is also TIED — it has no output.weight.

Refs #3346

Pmat-Ticket: PMAT-3346
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(PMAT-3346): ModelConstraints carries the gated-DeltaNet shape, so a hybrid model can be counted

contracts/model-families/qwen3_5.yaml declares inner_size, state_size,
conv_kernel, group_count and full_attention_interval under constraints:, and
ModelConstraints carried none of them. A Gated DeltaNet layer's parameters live
entirely in those dimensions, so every consumer of the descriptor counted
Qwen3.5 as if three quarters of its layers did not exist.

Carried through as ModelConstraints::deltanet: Option<DeltaNetShape> — the
runtime YAML loader (parsing.rs) and the compiled-in registry (build_parsing.rs
+ build_codegen.rs) both populate it, and FALSIFY-MF-QWEN35-010 pins that the
declared values survive the trip and that no other family acquires a shape it
never declared. qwen3_5.yaml is the only descriptor with these keys, so every
other family keeps byte-identical accounting.

model_arithmetic gains gated_deltanet_layer_params (one term per GGUF tensor:
attn_qkv, attn_gate, ssm_conv1d, ssm_alpha/beta, ssm_a, ssm_dt.bias, ssm_norm,
ssm_out) and hybrid_layers (the interleaved schedule). attention_layer_params
gained two terms the real file has and dense accounting did not model: the
gated q projection (2*n_h*d_k) and the q/k norm vectors.

Falsified against a real model, not against itself: fed the 0.8B configuration,
the equation now reproduces the 320-tensor inventory of
Qwen3.5-0.8B-Q4_K_M.gguf EXACTLY — 752,393,024, both layer kinds matching
tensor for tensor.

QE2E-INV-001 is still NOT asserted, and no range was widened to make it pass.
The 9b descriptor now yields 8,344,907,136, up from 8,208,519,168 but still
0.655B below [9.0B, 9.2B]. The remaining gap looks like descriptor drift rather
than missing arithmetic: 9b keeps inner_size 2048 — the value the 0.8B uses at
hidden_dim 1024 — while quadrupling hidden_dim, and its group_count 8 fails
8 * 128 == 2048, a consistency the measured 0.8B satisfies at 16 * 128. Only a
real Qwen3.5-9B file can settle it; none is on this box.

Refs #3346, #3347

Pmat-Ticket: PMAT-3346
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* contracts(binding): the QE2E-INV-001 note blamed a gap that is now closed

The note said the obligation was undischarged because ModelConstraints does not carry the DeltaNet shape keys. It does now, and the 0.8B count reproduces the real GGUF exactly. What actually blocks the obligation is descriptor drift at the 9b variant.

Pmat-Ticket: PMAT-3346
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3346 (adoption): the QE2E-INV-001 note lost its closing quote, and pv extract shrank the graph by 356 triples instead of refusing

d3cc76f1f rewrote the note on the QE2E-INV-001 binding and dropped the
trailing `"` — contracts/binding.yaml stopped being valid YAML at line 764
(`found unexpected end of stream`). Nothing in the PR noticed because
`pv extract contracts` does not refuse a binding registry that will not
parse: it emitted a graph with 15,244 triples where main has 15,600 —
every bound symbol AFTER the broken entry (prune::run, distill::run,
harness_ir::*, ptx_explain::run, …) silently gone — and `--check`
would have agreed with itself. Found while regenerating the derivative
for this adoption, by the drop, not by any gate.

One character. With it, binding.yaml parses (156 entries, same as main)
and the extraction is byte-identical to main's committed contracts.nt, so
this PR owes no graph change after all.

Refs #3346, #3350

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(roadmap): mint PMAT-3602 as a work item so the AD-04 quorum can resolve it

`quorum-review.sh` refuses without `pmat work status <id>`, and `pmat work`
reads the aggregated roadmap rather than an id counter. The branch carried the
trailer `Pmat-Ticket: PMAT-3602` while no such work item existed — a GitHub
issue number is not a work item.

The id is DERIVED from the issue (`pmat work add --github-issue` calls that
"the path to prefer": GitHub allocates centrally, so two agents cannot be handed
the same id, which `max(id)+1` cannot promise). Fragment, not a direct edit to
roadmap.yaml: additive, one file, and it does not make every stacked branch
dirty on the same lines.

Acceptance is hand-entered from the issue's done_when — `pmat work add` derives
neither `spec:` nor `acceptance_criteria:` (paiml-mcp-agent-toolkit#1414).

Guards: diff_additive, fragment_required, ids_unique, sorted,
completion_is_cited all exit 0; `make roadmap-aggregate-check` reports
`roadmap.yaml == aggregate(48 fragment(s)), idempotent`.

Refs #3602
Pmat-Ticket: PMAT-3602

* roadmap: PMAT-3637 — the registration row this PR is, so its quorum judges fidelity not implementation

Round 0 ran with --ticket PMAT-3604 and all three lanes FAILed for the same
reason: the diff implements none of PMAT-3604's criteria. Correct — it was
never meant to; it registers the ticket so #3634's quorum can start. The
receipt is kept as quorum-PMAT-3637-r0-misframed-as-3604.json (a FAIL is a
record, not a mistake to erase). PMAT-3637 is the row this diff satisfies:
fidelity of the PMAT-3604 entry to issue #3604's done_when, additive
aggregate, nothing outside docs/roadmaps/. Round 1 runs against it.

Refs #3604, #3634
ont-delta: none — roadmap entries only

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(roadmap): mint PMAT-3605 as a work item so the AD-04 quorum can resolve it

`quorum-review.sh` refuses without `pmat work status <id>`, and `pmat work`
reads the aggregated roadmap rather than an id counter. This branch carried the
trailer while no such work item existed — a GitHub issue number is not a work
item until something mints it.

The id is DERIVED from the issue: `pmat work add --github-issue` calls that
"the path to prefer", because GitHub allocates centrally and `max(id)+1` cannot
promise two agents different ids. A fragment rather than a direct roadmap.yaml
edit — additive, one file, and it does not make every stacked branch dirty on
the same lines.

Acceptance hand-entered from the issue's done_when; `pmat work add` derives
neither `spec:` nor `acceptance_criteria:` (paiml-mcp-agent-toolkit#1414).

Guards: diff_additive, fragment_required, ids_unique, sorted,
completion_is_cited all exit 0; `make roadmap-aggregate-check` idempotent.

Refs #3605
Pmat-Ticket: PMAT-3605

* PMAT-3346: the roadmap row, so the AD-04 quorum for #3350 has a ticket to judge against

Acceptance transcribed from issue #3346 as this PR answers it (the type that
reads the descriptor was wrong, not the range or the descriptor), with the 9B
range instantiation explicitly out of scope until a real 9B GGUF exists.

Refs #3346

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* roadmap: round 1 said two true things — fix both

Lanes 1+2 (gemini-3.1-pro-high, gemini-3.8-flash-high) FAILed PMAT-3637 on
(a) the round-0 receipt committed under docs/audits/, which criterion (3)
forbade — dropped; the receipt judged the wrong question and its ticket
field would read as a verdict on PMAT-3604; and (b) a criterion in the
PMAT-3604 notes that issue #3604 does not state ('Only an Accepted verdict
writes a receipt') — it is PR #3634's design decision, not a done_when
item; removed from the transcription. Criterion (3) now admits this row's
own receipt at docs/audits/quorum-PMAT-3637*.json, which it must, or the
PASS receipt could never be committed.

Refs #3604, #3634
ont-delta: none — roadmap entries only

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(run): quorum round 1 found the --json surface skipped and a branch that could never run

Two findings from lane 1 (gemini-3.1-pro-high) of the AD-04 quorum, both
correct, both fixed. Lane 3 passed the PR without seeing either.

1. `reconcile_accelerator(...)?` ran BEFORE `print_run_output`, so a rejected
   `--gpu` run early-returned and `--json` emitted nothing at all — the exact
   surface #3602 item 1 names. Machine surfaces (`--json`, `--stream`) now emit
   before the refusal propagates; the human surface still prints nothing.

   This is a DELIBERATE deviation from `after_generation`'s contract, which says
   the caller "must print NO output" on a forced refusal. Named rather than
   quiet: that rule exists so a CPU result is never read as a GPU success, and a
   document carrying `"backend": {"fell_back": true}` beside exit 14 cannot be
   read that way, while a human-formatted success blob can. The protective half
   is kept; the half that blinded `--json` consumers is not.

2. The caller's `if let Some(note)` arm was UNREACHABLE. `announced` was
   `Some("gpu")` exactly when `forced` was true, and `after_generation`'s
   corrective-line branch requires `forced == false` — so the Some arm could
   never execute. A branch with no reachable caller, which is precisely the
   defect this PR fixes, reproduced one layer down while fixing it.

   `reconcile_accelerator` now returns `Result<()>`, wires the FORCED half only,
   and says in its doc comment why the default-selection half is not wired:
   nothing calls `registry::announce`, so there is no recorded announcement for
   a default selection, and manufacturing one would assert a choice this process
   never made. That is REG-8, not this PR.

Verification: cargo test -p apr-cli --lib = 7289 passed, 0 failed, 12 ignored;
cargo fmt --all --check = 0; cargo clippy -p apr-cli --lib -D warnings = 0.

Refs #3602
Pmat-Ticket: PMAT-3602

* PMAT-3346 (adoption): a bias vector is as wide as the projection it biases

Found by the AD-04 quorum on #3350 (lane 1, gemini-3.1-pro-high, cited
model_arithmetic.rs:144): the projection term used q_out for a gated
family's q matrix (2*n_h*d_k — attn_q emits the output gate, MEASURED in
Qwen3.5-0.8B) while the bias term still used q_dim. A bias narrower than its
projection is not a model. One token: q_dim -> q_out in the bias sum.

Why a delta-0 measurement did not catch it: no shipped family exercises the
case. Qwen3.5 has no attention bias; Qwen2.5 has biases but is not gated, so
q_out == q_dim there. The test that pinned 12 (a q_dim bias under a q_out
matrix) now asserts 16 and says why, and a second test holds the other
polarity — a non-gated family with biases is unchanged at 12.

115 model_arithmetic + model_family tests pass; oracle 218; clippy clean.

Refs #3346, #3350

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(run): quorum round 2 — a --json --benchmark leak, a comment claiming coverage it lacks, and a stray artifact I swept in

Three findings, two lanes, all correct.

1. LANE 1 — `--json --benchmark` leaked a human success blob on a refused run.
   The refusal path carried its OWN copy of "is this a machine surface"
   (`stream || output_format == "json"`) and dropped `!benchmark`. So that
   combination entered the branch, matched neither machine arm inside
   `print_run_output`, and fell through to the human benchmark rendering — for a
   run being refused. Two spellings of one condition drifting apart in the gap
   between them, which is the defect this PR's own dispatch comment warns about.

   One spelling now: `emits_machine_output(stream, output_format, benchmark)`.
   `the_machine_output_predicate_matches_print_run_output` pins it to the arms
   it describes over every flag combination; planting the pre-fix spelling turns
   it and `json_plus_benchmark_is_not_a_machine_surface` RED, verified.

2. LANE 1 — the classifier comment claimed `--gpu-layers all|n` was among the
   flags handled here. It is not: `Commands::Run` carries `gpu` and `no_gpu` and
   nothing else; `--gpu-layers` belongs to `apr serve`. So
   `layers_want_accelerator: false` is CORRECT and the comment was the defect —
   a comment asserting coverage the code does not have. Both comments now say
   the input is genuinely absent for this surface rather than stubbed.

3. LANE 3 — `trace-1789944278.json` (232 lines) was committed to the repo root.
   A runtime byproduct of `test_print_chrome_trace_creates_file`, which writes
   `trace-<epoch>.json` into the CWD when given no `--trace-output`. I swept it
   in with `git add -A` immediately after a quorum round — the exact thing my
   own notes say never to do there. Removed, and this commit stages files by
   name. The test writing into the repo root is a separate defect and is not
   fixed here.

Round 2 was 2 FAIL / 1 NO-VERDICT. Lane 2 has now returned NO-VERDICT twice with
`envelope_status: SUCCESS`, `transport_status: SUCCESS` and non-empty
`raw_bytes` — it answers, and the harness cannot extract a verdict from what it
returns, so width 3 has been delivering two votes.

Verification: cargo test -p apr-cli --lib = 7291 passed, 0 failed, 12 ignored;
cargo fmt --all --check = 0.

Refs #3602
Pmat-Ticket: PMAT-3602

* roadmap: transcribe #3604 verbatim — round 2's pro lane was right

Criterion (5) had grown an implementation-status clause from PR #3634's
report ('the --json field lands with #3606 StageTimings…') that issue #3604
does not state, and the out-of-scope list was a paraphrase. PMAT-3637 (1)
says every criterion present, none added: the notes now carry the issue's
done_when 1-6, admission rule and out-of-scope list as written.

Refs #3604, #3634
ont-delta: none — roadmap entries only

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* audit: quorum-PMAT-3637 — 3/3 PASS, round 3 on the ruled trio

pro / 3.7-flash / 3.6-flash, three conversations, no silences. Rounds 0-2
each said something true about this PR: round 0 judged the wrong ticket;
round 1 found a receipt outside scope and an invented criterion; round 2
found an implementation-status clause in a 'none added' transcription.
None is committed — each judged a diff that no longer exists.

Refs #3604, #3634
ont-delta: none — audit artifact only

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3346: quorum verdict 3/3 on e060c7e2c (AD-04)

Round 0 was 1 FAIL / 1 no-verdict / 1 PASS and the FAIL was real (the bias
width, fixed in e060c7e2c). Round 1 on the fixed head: 3/3 PASS,
gemini-3.1-pro-high / pro-low / 3.6-flash-high, each measured, no dissent.

Refs #3346, #3350

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(audits): AD-04 quorum receipt for PMAT-3577 — AGREED 3/3

Three independent agy lanes on distinct models, none in the author's family
(author measured as Opus 5 / claude):

  gemini-3.1-pro-high  = PASS
  gemini-3.7-flash-high = PASS
  gemini-3.6-flash-high = PASS

Lane models set per-invocation via PAIML_IMPLEMENT_CONFIG rather than by editing
the shared config, which three sessions were launching against concurrently.

THE MERGE DID NOT CHANGE WHAT WAS JUDGED. The lanes ran against 7f82e2cd3; the
PR head is 4fbd2ec31, a merge of main. The trees differ by four files, but all
four arrived FROM main, and the PR's own contribution relative to main is
byte-identical across the merge:

  files, pre-merge  : 49        files, post-merge : 49
  only in post      : (none)    only in pre       : (none)
  sha256(diff vs merge-base), pre-merge  : bf000a5ea9f94ce5c4b5e7d470f38e84bd6b6dac9760b970c1d5f89945c64d35
  sha256(diff vs merge-base), post-merge : bf000a5ea9f94ce5c4b5e7d470f38e84bd6b6dac9760b970c1d5f89945c64d35

So this is the same head for review purposes and does not need a new round. A
naive `git diff origin/main..HEAD | sha256sum` from my local head disagrees only
because that head carries this receipt commit, which the lanes never saw —
comparing the receipt's parent is what makes the two comparable.

Refs #3600
Pmat-Ticket: PMAT-3577

* fix(tests): the shape-count ratchet was red — #3605 adds a 6th shape (quorum round 1)

`the_tracked_repo_graph_is_fresh` asserts `shapes_n == 5` and names the five.
`refusal-receipt-v1` makes six, so this PR shipped a CI red that a quorum lane
found and I did not.

MEASURED, not guessed: `pv extract contracts --check` on this branch reports
`shapes_n: 6, triples: 15620`.

The hardcoded count stays hardcoded. A shape added without anyone noticing is
exactly what this assertion exists to prevent, so adding one is SUPPOSED to turn
it red and make you name the new shape. Noted in the comment that the counter is
shared across branches — a sibling PR adding a shape (#3600's parity-receipt-v2)
will need it raised again at merge, which is the ratchet working rather than a
conflict to route around.

Refs #3605
Pmat-Ticket: PMAT-3605

* chore(audits): AD-04 quorum receipt for PMAT-3602 — AGREED 3/3

gemini-3.1-pro-high = PASS, gemini-3.1-pro-low = PASS, gemini-3.6-flash-high = PASS.
Author measured as Opus 5 (claude); no lane in the author's family. Lane models
set per-invocation via PAIML_IMPLEMENT_CONFIG, never by editing the shared
config that other sessions were launching against.

This trio was chosen on measured odds after flash-class lanes returned
NO-VERDICT intermittently (3.7-flash 3/7, 3.6-flash 1/7, pro-high 0/7 across my
earlier runs). All 15 lanes voted in this batch.

Refs #3638
Pmat-Ticket: PMAT-3602

* chore(audits): AD-04 quorum receipt for PMAT-3605 — AGREED 3/3

gemini-3.1-pro-high = PASS, gemini-3.1-pro-low = PASS, gemini-3.6-flash-high = PASS.
Author measured as Opus 5 (claude); no lane in the author's family. Lane models
set per-invocation via PAIML_IMPLEMENT_CONFIG, never by editing the shared
config that other sessions were launching against.

This trio was chosen on measured odds after flash-class lanes returned
NO-VERDICT intermittently (3.7-flash 3/7, 3.6-flash 1/7, pro-high 0/7 across my
earlier runs). All 15 lanes voted in this batch.

Refs #3613
Pmat-Ticket: PMAT-3605

* PMAT-3351 (adoption): re-id the fragment — PMAT-3347 is #3348's row on main

Both #3348 (merged; bound the qwen35-e2e equations, closed #3347) and this PR
(the L2 column reads a link, not an index) address issue #3347, and both
claimed roadmap id PMAT-3347. Two PRs cannot share a row: main's PMAT-3347
stays as #3348's, this PR's fragment becomes PMAT-3351 (github_issue 3347,
same title, same notes), and the aggregate is rebuilt from main's roadmap.yaml
plus this branch's fragments — 935 + 1 = 936, nothing re-serialised.

Refs #3347, #3348

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3351: notes transcribe issue #3347's Ask verbatim (bullets 2 and 3; bullet 1 was #3348)

The quorum lanes read pmat work status, i.e. this fragment's notes, not the
GitHub issue. Empty notes would have them judge against a title. The issue
has no done_when section, so its Ask and its two defect statements are
transcribed as written, with the out-of-scope bullet named and no count
offered as a criterion.

Refs #3347

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3338: register the second ticket this PR closes, so the quorum judges the truncate fix as in scope

Quorum round 0 on a4b76397f was 2 FAIL / 1 PASS, both FAILs on one point: the
branch's first commit fixes #3338 (--table panicked on a byte-index cut inside
a multi-byte char) and PMAT-3351 does not ask for it. Both lanes called the
in-scope work correct. The fix is a prerequisite — the L2 column is exercised
through --table on the real corpus, which panicked before it — and #3338 is
an OPEN issue this PR genuinely closes. So it gets its row, transcribed from
the issue, and round 1 runs with --ticket PMAT-3351,PMAT-3338.

Refs #3338, #3347

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3604: the receipt's temp file is private to its writer — a shared name made the atomicity claim false

AD-04 quorum round 0 on #3634, lane 1 (gemini-3.1-pro-high), cited
f2_receipt.rs:180: write_receipt used one shared <sha>.json.tmp, so two apr
runs validating the same model at once could have writer B truncate the file
writer A was about to rename, and A rename B's partial into place. The reader
would classify it Unreadable and validate — the safe direction, done_when 4 —
but the docstring said 'never a truncated one', and that was false.

The temp name now carries the pid and a per-process counter; a failed rename
removes its own temp. A racing test spawns two writers on one path forty
times and parses the survivor each time: it is always one writer's WHOLE
receipt. 14 tests.

Lane 1's other two findings, for the record: --revalidate is done_when 2
verbatim (the fragment carries it), not scope creep; and F2Outcome's fields
are the values the stderr line already prints and done_when 5's data path
for #3606's JSON, not dead code. Two lanes passed the same diff.

Refs #3604

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3604: quorum verdict 3/3 on dd546592b (AD-04)

Round 0 was 2 PASS / 1 FAIL; the FAIL was the shared temp-file race, fixed in
dd546592b. Round 1 on the fixed head: 3/3 PASS, gemini-3.1-pro-high /
pro-low / 3.6-flash-high, each measured, no dissent.

The PMAT-3604 roadmap row was on this checkout UNCOMMITTED for the resolver
(pmat work status reads the checkout); it lands via #3637. The lanes judged
the committed diff origin/main...HEAD.

Refs #3604, #3637

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* PMAT-3351 + PMAT-3338: quorum verdict 3/3 on d915b8f16 (AD-04)

Round 0 (--ticket PMAT-3351 alone) was 2 FAIL / 1 PASS, both FAILs on the
#3338 truncate fix being out of scope; both called the in-scope work correct.
Round 1 with both tickets registered and named: 3/3 PASS, gemini-3.1-pro-high
/ pro-low / 3.6-flash-high, each measured, no dissent.

Refs #3347, #3338

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(contracts): the v1 contract had no equations, and its own denominator guard was never wired

Two reds on this PR, both mine, and the second is the better finding.

1. `contract_data_integrity` is a SHRINK-ONLY ratchet at 444 and this branch made
   it 445. The 445th is `parity-receipt-v1: no equations`. Measured which one
   rather than guessed — `parity-receipt-v2` is not in the list.

   `kind: pattern` correctly declares no SHAPE (a shape over a class nothing
   instantiates passes vacuously, which is #3610's whole lesson), but equations
   are not shapes, and this contract does assert things: PRC1-INV-001 and
   PRC1-INV-002 already said them. They are now written as the two equations the
   integrity check reads — `no_instances` and `refused_by_name` — rather than as
   new claims invented to satisfy a counter.

2. `check_guards_are_wired.sh`: `NEW: parity_receipt_denominator.sh`. I ADDED A
   GUARD IN THIS PR AND WIRED IT INTO NOTHING — the second, independent reader of
   the receipt corpus, shipped where no workflow names it.

   Minutes before finding this I wrote into that same contract's equations, as a
   precondition: "both readers are wired into CI — a refusal nothing runs is not
   a refusal." My own precondition, violated by my own PR, in the same file.
   `check_guards_are_wired.sh` caught what I had just finished writing down.

   Now named in `ci.yml` beside the other cargo-free, model-free case tables. Its
   4-case self-test passes: a legacy record refused by name, a receipt added
   without bumping the denominator disagreeing, bumping it making them agree.

NOTE — THIS TOUCHES `.github/workflows/ci.yml`, which CLAUDE.md lists as a
check-in item. Additive only: one step in an existing guard block, no matrix,
trigger or gate logic changed. Wiring an unwired guard is the minimum the failing
check asks for, and leaving it unwired to avoid the file would be keeping a
refusal that never runs.

VERIFICATION
  cargo test -p aprender-contracts --test validate_contracts contract_data_integrity   1 passed
  bash scripts/parity_receipt_denominator.sh --self-test                               rc 0, 4/4
  bash scripts/check_guards_are_wired.sh                                               PASS (4 -> 3)
  pv validate contracts/parity-receipt-v1.yaml                                         0 errors, valid

DIFF MOVEMENT, for the receipt: the judged diff DID change — an equations block
and one CI step. No behaviour the lanes reviewed was altered; the extractor, the
shapes and the denominator script are byte-identical. AD-04 re-review is the
inspector's call.

Refs #3577
Pmat-Ticket: PMAT-3577

* PMAT-3401 (adoption): the three derivatives 24 new contracts oblige — census, graph, README — regenerated together

This PR adds 24 contracts and regenerated none of the tracked artifacts
derived from the corpus. On #3581 that omission surfaced one per CI round,
each masked by the one before it. All three here, at once, with a pv built
from this tree under a pinned target dir:

  contracts/census.json   1800 -> 1824  (+24, the contracts added)
  contracts/contracts.nt  15,600 -> 15,696 triples  (+96 = 24 x 4; GREW —
                          a drop is the tell for a malformed input or a stale
                          binary; binding.yaml still parses, 156 entries)
  README CONTRACT_COUNT   2 blocks -> 1824 via make readme-sync

The merge took main's generated README blocks over the branch's hand-typed
1866, then readme-sync wrote the measured 1824; the branch's number described
a tree that never existed on main.

Verified: all 24 contracts pv-validate under the pinned binary (control:
main's crux-A-01 valid under the same one); lint_passes_on_real_contracts
green, so the sigma prose ratchet holds; 1666 engine tests; ont4b shapes gate
11/11; test-binding ratchet; readme-sync-check; FALSIFY-README-002; roadmap
additive added=26 deleted=0, aggregate idempotent, 26 fragments present.

Refs #3401

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(run): extract reconcile_and_emit — run() hit cognitive 27 against a 25 ceiling

`check_complexity_ratchet.sh` went RED with:

  RED  NEW  crates/apr-cli/src/commands/run_entry.rs::run  cyclomatic 21 cognitive 27
            (over a threshold, absent from the comparand)

The reconcile-then-emit block I added in this PR is what took `run` over. Moved
into `reconcile_and_emit`, which also gives the ordering decision — machine
surfaces emit on a refusal, the human surface does not — a place to be documented
that is not the middle of a 30-argument entry point.

Now: PASS (D2): e6f77c98c vs 19750b384 measured by pmat 3.41.1 — none new, none grown.

NOTE FOR THE NEXT PERSON: I first re-ran the ratchet against my WORKING TREE and
saw the identical cognitive 27, and briefly concluded the extraction had not
helped. It had. The script measures two REVISIONS — it prints
`merge HEAD <sha>` — so an uncommitted fix is invisible to it. Commit, then
measure.

Tests: cargo test -p apr-cli --lib = 7291 passed, 0 failed, 12 ignored;
fmt --check = 0; clippy -D warnings clean.

Refs #3602
Pmat-Ticket: PMAT-3602

* PMAT-3496: a registration row for the registration PR — the author's earlier receipt named this ticket but no row existed on the branch

* PMAT-3496: clause (1) transcribes issue #3495's title verbatim (132 Kani harnesses; Verus pilot), not a paraphrase

* fix(guard): a step NAME read as a bare guard_tree.sh dispatch hid four dark guards; the #3305 guard runs nowhere and dies on intel's python 3.10 (#3644)

Refs #3644 #3646 #3305 #3626

THE FINDING, proven by mutation before it was argued. check_guards_are_wired.sh
counts a guard wired-by-dispatch when a workflow runs guard_tree.sh in a mode
whose --dry-run RUN set contains it. Its invocation regex accepts `guard_tree.sh`
followed by `'` (for a quoted run: string). ci.yml has two step NAMES,
`"guard_tree.sh's own case table …"` and `"guard_tree.sh's parallel dispatcher
…"`; the apostrophe matched, neither line says --no-cargo, so each was read as a
bare dispatch in mode "all" -- 55 cargo-classified guards counted wired that
`guard-tree` (--no-cargo) never runs and `guard-cargo` never names. Rewriting
those two names (nothing else) turns the meta-guard RED: 3 -> 7 unwired --
check_model_ladder.sh, check_pathonly_devdeps_unused_in_src.sh,
check_pr_review_counts.sh, check_pr_review_receipt.sh. Three of the four are
cargo-classified by a COMMENT that says the build tool's name; none of those
three invokes it.

check_pathonly_devdeps_unused_in_src.sh (#3305: src/ may not use a dev-dep that
publishing deletes; clean-room red 8/8 across two releases) has therefore run
nowhere since it was written on 2026-09-15. And it could not have run on the
intel hosts: `import os, re, sys, tomllib` at module level, python 3.10.12
there with neither tomllib nor tomli (measured on mac-server tonight; the
sovereign-ci container has no python3 at all). Reproduced with python3.10 -S:
the traceback is swallowed by `out=$(scan … || true)` and the case table prints
"FAIL: missed hit_use" ×4 -- the regex reported broken by an interpreter that
never ran it. Same class as #3626's guard on intel-clean-room-6.

WHAT CHANGES

scripts/check_guards_are_wired.sh
  - `_not_a_name_line` drops `name:` lines before the invocation match, in
    dispatcher_wired() and the per-guard scan. A step name is documentation
    whatever it contains.
  - rows 8-10: the exact ci.yml fixture (`- name: "guard_tree.sh's own case
    table …"`, no run: line) must leave the dispatched guard unwired; a QUOTED
    run: line still dispatches (the control the `'` exists for); a step named
    after a guard wires nothing. Mutant (drop removed): rows 8 and 10 RED.
  - the ledger's ratchet is now set-aperture, owned by this file.

scripts/lib_baseline_ratchet.sh, scripts/check_baseline_ratchets.sh
  - set-aperture gains a NAME-entry admission for sets of FILES: an added entry
    with no `:` is admitted iff the comparand carries that file (at the entry's
    path, or beside the owning guard -- the ledger names siblings by basename
    and guard_tree.sh reads it so) AND the owning guard changed in the diff.
    A file this branch created is refused (PERF-028's shape). Six rows: predates
    -> admitted; branch WROTE it -> refused; no guard edit -> refused; escaping
    path -> refused; beside the OWNER -> admitted; beside a different owner ->
    refused. Lib mutants (always admit / branch removed / sibling resolution
    removed) each turn rows RED; a separate absolute-path check survived its
    mutant because `git cat-file -e` already refuses those, so it is not there.
  - classify: unwired_guards_baseline.txt -> set-aperture, owner named.

scripts/unwired_guards_baseline.txt  3 -> 6, as APERTURE REVEALS with a reason
  per line (each may only leave): check_model_ladder.sh (T-2 release gate, the
  multiplatform_dogfood class); check_pr_review_counts.sh (RED on main today,
  "6 row(s) disagree" -- #3646); check_pr_review_receipt.sh (takes a receipt
  path; nothing passes one -- #3646). check_pathonly_devdeps_unused_in_src.sh
  is NOT ledgered: see below.

scripts/check_pathonly_devdeps_unused_in_src.sh
  - readers tomllib -> tomli -> ENV rc=2 naming python version and $RUNNER_NAME.
    No purpose-built manifest reader: a second TOML implementation over 78
    manifests, where a wrong "has source" is a silent PASS on exactly the
    #3305 class, is not a fallback worth shipping. UNMEASURED-with-a-name is
    the honest state on a runner with no TOML library.
  - the scanner ends with `SCAN-DONE manifests=N reader=…`; the shell REQUIRES
    it. Output without it (no reader, /bin/false, no python3) is ENV rc=2 --
    never rc=1, never "no violations". The selftest and the tree scan
    propagate 2 instead of swallowing it.
  - five death rows: both readers absent (sys.modules nulled so the imports
    fail for real); interpreter exits 1 silently; no interpreter (127); the
    working interpreter as control; and THE WHOLE GUARD under the intel shape
    (nested, recursion-guarded) -- the call site is where `|| true` hid it.
    Mutants: trailer not required (3 rows RED); trailer printed on the
    no-reader path (1 RED); `|| true` restored (the whole-guard row RED).
  - three comments and one FAIL string reworded so the file no longer says
    the build tool's name with a space after it: it invokes none, and that
    substring is what CARGO_RE classifies on. guard_tree.sh --dry-run
    --no-cargo now lists it as `run:`; bare run on this tree rc=0 in 3 s.

Passes: check_guards_are_wired --self-test 10/10; check_baseline_ratchets
--self-test 61 rows; pathonly --selftest under py…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

touches-shared-contracts PR diff touches contracts/census.json or contracts/lint-baseline.json — queue group of one

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant