Skip to content

batch(0.64.0): fold 13 reviewed PRs into one integration branch — one CI run, not thirteen - #2613

Merged
noahgift merged 45 commits into
mainfrom
batch/release-0-64-0
Aug 22, 2026
Merged

batch(0.64.0): fold 13 reviewed PRs into one integration branch — one CI run, not thirteen#2613
noahgift merged 45 commits into
mainfrom
batch/release-0-64-0

Conversation

@noahgift

@noahgift noahgift commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Why one PR

The runner fleet is in a thrashing collapse, not a defect state. All 16 org runners are
busy, mac-server was measured at load average 204 with CARGO_BUILD_JOBS=8 x 16 runners = 128 compile jobs on 32 cores. workspace-test's lib step took 48.7 min
(tuned to ~9), and the "Integration tests" step has no step timeout, so it consumed
the remaining 44.5 min of the job's timeout-minutes: 100 and the job was killed with
no failing step. That renders as a bare failure whose log is not found — which
is why seven PRs and main itself all looked mysteriously broken at once.

Pacing PRs one at a time does not escape this: it was tried for 4.5 hours on 2026-08-11
and merged zero. N PRs cost N runs whether serial or parallel; one integration branch
costs one run.
24 branches landed this way before.

Base: origin/main @ bb2bd5e73 (which already carries #2563, so #2563 is not folded
here). Merged, never rebased — a squash-merged base breaks stacked identity, and
this repo has been bitten by rebasing a merge-based batch.

Head: a9d3a83e7.

What is folded in (13 PRs, in merge order)

PR Branch Head merged Closes / refs
#2544 fix/greedy-argmax-tie-flake 40dd67963 #2540, #2542
#2549 fix/ticket-2532 52e7fe3fc #2532
#2548 fix/ticket-2519 a1f9294b0 #2519
#2554 fix/pv-schema 1fc31687c #2504, #2556
#2574 fix/remove-phantom-loom-dep af57da34f #2566
#2573 feat/multiplatform-dogfood-gate 9d264c668 #2566, #2567, #2568, #2572
#2576 fix/2568-oom-guard-fail-closed b75108161 #2568
#2577 fix/tensors-tune-file-bounds 690900e45 #2569
#2578 harvest/dogfood-endpoint-probe 54d0615a0 #2527
#2580 fix/pv-bin-resolution-falsifier 1f3bebd12 #2552
#2581 harvest/chat-stream-falsifier-2375 22b272f7b #2375 (finding 4), #2376
#2587 feat/apr-dogfood-v3-coverage-gate 403f7655d PMAT-742; #2376, #2384, #2424
#2600 feat/dogfood-per-cluster-floor 977da9894 PMAT-742; stacked on #2587

Provenance is recorded here deliberately, because a squash merge collapses the thirteen
merge commits into one and the branch names stop being recoverable from git log.

Ordering that is load-bearing

#2587 is merged before #2600. #2600 is a descendant of #2587's dd2f69c49 but
was pushed before #2587's provenance fix (a5d4bf620 + 403f7655d), so #2600 alone
still carries the unpatched check_freshness() and measured_commit: cbb1ccd78 — a SHA
that a squash merge orphans and a depth-1 CI checkout cannot resolve. Merging in this
order makes the three-way merge keep #2587's presence-first probe and its
measured_commit: 4bbfeb07f, then layer #2600's per-cluster floor on top. Merging #2600
without #2587, or after it in the wrong order, reproduces the exact red that #2587 was
opened to fix.

Excluded

Four defects no individual PR could see

The first three are green on every folded branch in isolation and red only on the union —
the entire reason to batch rather than pace. The fourth was live on a branch and had
simply never been observed, because that PR's workspace-test was killed by the
timeout before the lib step ever reported.

1. README.md contract count 1779 → 1781 — three branches each add exactly one
contract (#2548 apr-train-shell-model-provenance-v1.yaml, #2549
machine-specific-paths-v1.yaml, #2587 apr-dogfood-coverage-v1.yaml). main is
self-consistent at 1778/1778; each branch alone is right at 1779; three +1s collide on
one literal and the union is 1781 with a README still reading 1779.
scripts/check_readme_claims.sh rc=1 → rc=0. This is the same class of failure that
killed the last batch
(a README-contract-count falsifier the --lib-only local check
never ran).

2. F-DOGCOV falsification-test ordering#2554 pins the contract data-integrity
ratchet at CEILING = 444, measured on a corpus that did not yet contain #2587's
contracts/apr-dogfood-coverage-v1.yaml. #2600 then appended F-DOGCOV-013..016 in the
middle of falsification_tests (001..005, 013..016, 006..012), which
check_falsification_ids reports as one test ID gap. 444 + 1 = 445 and the shrink-only
ratchet panics. Neither PR can see it: #2554 has no dogfood contract, #2587/#2600 have no
ceiling test. Fixed as content, not ceiling — the four blocks moved to ordinal
position, no id renamed, no rule text changed, ceiling stays 444.

3. scripts/unwired_guards_baseline.txt merge conflictmain and #2573 each add a
different deliberately-unwired guard. Resolved by keeping both.

4. batch-training-v1 duplicate stem divergednot batch-only; found only because
the real lib step was run. The stem exists twice, contracts/batch-training-v1.yaml and
contracts/aprender/batch-training-v1.yaml, byte-identical so the duplicate-stem census
tolerated it. #2554's pass_criteria audit corrected All 5 falsification tests pass ->
All 6 (the file declares FALSIFY-BATCH-001..006, so 6 is the true count) in the
top-level copy only, making the pair divergent and therefore unresolvable to one
contract. Three tests red: real_corpus_matches_its_baseline
(baseline drift — new: ["batch-training-v1"]),
real_corpus_census_names_every_baselined_stem (left: 49, right: 48), and
lint_passes_on_real_contracts. Fixed by making the copy agree, per the guard's own
instruction — the baseline was not raised. This was live on #2554 and would have
reddened it whenever its workspace-test next survived long enough to report.

Verification — what was actually run, and what was not

Everything below ran on this batch head in an isolated worktree. rc was read from a
direct redirect in every case, never through a pipe.

The real workspace-test "Integration tests" chain

cargo test --workspace --lib is not what workspace-test runs, and claiming green on
--lib is how the last batch lost a 50-minute run. The 26-invocation chain from
.github/workflows/ci.yml was run here, split so each invocation's rc is captured
individually instead of &&-short-circuiting:

All 26 invocations rc=0.

01 rc=0    cargo test -p aprender-core --test monorepo_invariants
02 rc=0    cargo test -p aprender-core --test readme_contract
03 rc=0    cargo test -p apr-cli --test cli_commands
04 rc=0    cargo test -p aprender-train-inspect --test falsify_no_fabricated_metadata_2519
05 rc=0    cargo test -p aprender-train-bench --test falsify_no_fabricated_benchmarks_2519
06 rc=0    cargo test -p aprender-train-shell --test falsify_no_fabricated_fetch_2519
07 rc=0    cargo test -p aprender-core --test beat_sklearn_iris
08 rc=0    cargo test -p aprender-core --test beat_sklearn_nmi
09 rc=0    cargo test -p aprender-core --test beat_sklearn_metrics_parity
10 rc=0    cargo test -p aprender-core --test beat_sklearn_gaussiannb_accuracy
11 rc=0    cargo test -p aprender-core --test beat_sklearn_svc_accuracy
12 rc=0    cargo test -p aprender-core --test beat_sklearn_pipeline_encoder
13 rc=0    cargo test -p aprender-serve --test beat_fail_closed_garbage
14 rc=0    cargo test -p aprender-compute --lib beat_nf4_bitsandbytes_equivalence
15 rc=0    cargo test -p aprender-core --test beat_pytorch_autograd_grad
16 rc=0    cargo test -p aprender-train-lora --lib beat_lora_merge_forward_equivalence
17 rc=0    cargo test -p apr-cli --release --test beat_pytorch_deploy_footprint
18 rc=0    cargo test -p aprender-serve --test beat_fail_closed_structural
19 rc=0    cargo test -p aprender-serve --test ollama_http_compat
20 rc=0    cargo test -p apr-cli --test ollama_ndjson_streaming
21 rc=0    cargo test -p apr-cli --test falsification_chat_http_cli
22 rc=0    cargo test -p aprender-contracts --test apr_serve_api_key_auth_contract
23 rc=0    cargo test -p apr-cli --test falsify_auth_001 --test falsify_auth_002 --test falsify_auth_003 --no-fail-fast
24 rc=0    cargo test -p apr-cli --test beat_apr_data_alimentar_reach
25 rc=0    cargo test -p apr-cli --test beat_apr_sibling_cli_reach
26 rc=0    cargo build --examples --workspace --keep-going

Also run, because #2554 adds a new Contract corpus integrity step to
guard-runner-labels naming three targets: cargo test -p aprender-contracts --test validate_contracts (rc=0, 10 passed), --test kani_harness_generation (rc=0, 14
passed), --test probar_test_generation (rc=0, 9 passed).

The workspace-test lib step

Run too, after the chain left the workspace warm — the exact CI command:

cargo nextest run --profile ci --workspace --lib \
    --exclude aprender-gpu --exclude aprender-cuda-edge --exclude aprender-compute
  -> rc=0   Summary [202.253s] 81026 tests run: 81026 passed
            (1 slow, 1 flaky, 1 leaky), 130 skipped

cargo test -p aprender-compute --lib          # the separate "Compute tests" step
  -> rc=0, and CI's predicate holds: 1 x "test result: ok.", 0 x "test result: FAILED"

The first run of this step is what found defect 4 above: it stopped at 9118/81026 on
fail-fast with 2 failures. The run quoted here is the re-run after the fix, and it is
the complete 81026.

One flaky test, retried and passed:
aprender-orchestrate agent::tool::mcp_client::tests::test_discover_tools_no_tools_array
(FLAKY 2/3, assertion failed: result.unwrap_err().contains("no tools array")). It is
not attributable to this batch — git diff --name-only origin/main HEAD -- crates/aprender-orchestrate/ is empty. [profile.ci] in .config/nextest.toml sets
retries = 2, so CI absorbs it the same way; worth its own ticket, since a required
check that reds at random is worse than one that never reds. (The ci.yml comment
claiming [profile.ci]: retries=0 is stale — the file says 2.)

Guards, including the two that actually reddened above

bash scripts/check_dogfood_coverage.shrc=0, DOGFOOD COVERAGE GATE: PASS, with
the union of #2587's and #2600's arms live: G2.1 freshness (measured_commit 4bbfeb07f: shape ok, object present), G2.2 reconciliation (830 rows), G2.3 floors (142/830, 17.1%,
28 per-binary floors), G2.5 membership, G2.5 per-cluster (5/14 clusters gated, reported
with the 142/830 feature fraction beside it), G2.6 T2 pairing, G2.4 waivers.
--self-testrc=0, 35 verdicts, zero FAIL, including the depth-1-clone fixture
(shallow fixture built: depth-1 clone, is-shallow-repository=true).

cargo test -p aprender-contracts --test validate_contractsrc=0, 10 passed.
Mutation-verified both directions with the mutation proven to have engaged (a diff -q
exiting 1, so a no-op edit could not read as "the guard is fine"): reverting only the
F-DOGCOV reorder → rc=101 "rose to 445 (ceiling 444)"; re-applying → rc=0.

bash scripts/check_facade_compat.sh → the row that reddened #2574 in CI,
compat_invoke + compat_probe pass, is ok here, as are R1/R2/R3/R4/R6/R7, the 27
vendored 0.3.1 examples, both mutation controls, and every SURFACE/SIGNPOST/ROUTES/
CURRENCY row.

cargo fmt --all -- --checkrc=0.

47 of the 49 guard-runner-labels shell guards + self-tests → rc=0, including
check_guards_are_wired.sh, check_shell_lint_ratchet.sh, check_assertions_exclude.sh,
check_contract_test_binding.sh, check_dogfood_llm_probe_armed.sh (#2578),
check_pv_bin_resolution.sh (#2580), check_no_cluster_id_keys.sh (#2600),
check_lockfile_current.sh, check_pass_grep_anchored.sh, and every paired
--self-test case table.

Two local reds that are dev-box artifacts, not batch defects

check_facade_compat.sh R5 and bump-version.sh --check both fail on
cargo metadata --locked in crates/facades. Attribution, not assertion:

  • The error names eleven unused patches, every one pointing at
    /home/noah/src/{realizar,entrenar,trueno,pacha,renacer,provable-contracts} — paths
    injected by the gitignored /home/noah/src/aprender/.cargo/config.toml
    [patch.crates-io] block, which sits in an ancestor directory of every worktree
    and therefore applies to the separate crates/facades workspace too.
  • The lockfile rewrite cargo wants is 44 added lines, 0 removed, all of them
    [[patch.unused]] stanzas
    — zero package or dependency change.
  • git diff --stat origin/main HEAD -- crates/facades/ is empty: this batch changes
    nothing under crates/facades, so neither guard's verdict can be attributable to it.
  • CI passed this exact check on fix(deps): remove the phantom loom dependency — declared, never used, shipped anyway #2574's own run
    (ok R5 crates/facades/Cargo.lock matches the facade manifests (--locked), job
    97026550281).

Worth its own ticket — anyone reproducing the facade gate locally can accidentally commit
that churn — but out of scope here.

Not run, stated rather than skipped silently

  • scripts/check_wasm32_core_builds.sh (needs a wasm32 toolchain build) and
    scripts/check_book_examples_compile.sh (full book compile) — the two guards omitted
    from the 49.
  • ci / lint (workspace clippy --all-targets -D warnings), ci / coverage, mutants,
    security — none reproduced locally.
  • No benchmark was run.

/ is at 95% (~97 G free) and other agents are live, so every cargo invocation above was
directed at CARGO_TARGET_DIR=/mnt/nvme-raid0/targets/aprender-batch-0-64-0 (4.2 T free)
rather than the worktree. Nothing shared was pruned or deleted. Two housekeeping
observations, reported and not acted on: / moved 94% -> 95% during the session, and
a stale .git/worktrees/*/gc.log is disabling automatic gc repo-wide
(too many unreachable loose objects).

For the operator

Auto-merge is not armed and the superseded PRs are not closed — that sequencing is
yours. The thirteen folded PRs can be closed with a pointer here once this lands.


Issue closure — machine-readable

The provenance table above is for humans; GitHub does not parse it. Without the block
below, merging this PR closes nothing, and thirteen folded PRs' worth of fixes would
leave every one of their issues open.

Deliberately conservative: only issues a folded PR directly fixes are listed as closed.
Where a PR merely touches or partially addresses an issue, it is a ref, and the issue stays
open for someone to judge on its own evidence.

Closes #2519
Closes #2532
Closes #2568
Closes #2569
Closes #2570

(#2570 added after verifying the fix rather than trusting the table: commit 3ba172e00
replaces estimate_params_from_file — which multiplied the file's BYTE LENGTH by a
constant chosen from the filename extension and never opened the model — with
read_params_from_file, summing real tensor shapes via the metadata-only list_tensors
path. It now errors rather than guessing when the header cannot be parsed, which is the
property that matters: an unknown parameter count is not a small one.)

Refs #2504, #2556, #2566, #2567, #2572 — touched but not closed:

Already closed or merged, listed in the table for provenance and needing no action:
#2375, #2376, #2384, #2424, #2527, #2540, #2542, #2552, #2563.

noahgift and others added 30 commits August 20, 2026 20:59
…ion (#2519)

#2519's three headline claims do NOT reproduce on origin/main 5c08e77: all
three train-* binaries were repaired by 594b687 / 34a0f15 / 0f1b626 /
8c23a4e and now refuse instead of fabricating. Item 5 (apr-cli has no qa
dependency) is fixed too -- aprender-qa-cli is reached as `apr qa-playbook`.
So this does NOT implement the issue's DELETE recommendation: the code it
describes is gone.

What DOES still reproduce is a door the #2519 fix left open. Its falsifier
drives state exclusively through `fetch`, so it enumerated one entrance and
the room has two. `LoadedModel` derives `Deserialize`, and main.rs fed
`SessionState::load` straight from a user-supplied `--session` path.

MEASURED on origin/main 5c08e77, AFTER the fetch fix, with a hand-written
sess.json naming /nonexistent:

    $ aprender-train-shell --session sess.json -c distill
    Training started... (simulated)                             # exit 0
    $ aprender-train-shell --session sess.json -c "distill --dry-run"
    Teacher: does-not-exist/totally-fake-7b (7.0B)
    Student: does-not-exist/totally-fake-1b (1.0B)
    Ready to train                                              # exit 0
    $ aprender-train-shell --session sess.json -c memory
    Model: 16.0 GB / Total: 20.3 GB                             # exit 0

Every figure there was typed into a JSON file. This is the project-memory
lesson "a guard's UNIVERSE built from the wrong side": the guard was correct
and the defect simply was not in the set it iterated.

THE FIX -- two independent rules, so neither is load-bearing alone:

1. SessionState::validate_model_provenance, called from load():
   (a) a model the session says is cached must be ON DISK, and
   (b) nothing in this crate can produce a LoadedModel at all -- fetch
       refuses and no other production path calls add_model -- so any model
       in a session file was typed, not measured.
   Ordered so (b) can be retired on its own the day a real loader lands,
   leaving (a) standing.
2. execute_distill's non-dry-run arm refuses. #2519 names this line
   ("distill returns `Training started... (simulated)`"); it is reachable
   from any door that puts two models in the session, so fixing only the
   door would leave it. --dry-run still describes the configuration:
   describing a plan is not claiming to have executed it.
   The refusal deliberately does NOT quote the old success string -- a
   refusal that repeats the phrase it refuses is indistinguishable from the
   defect to any substring check.
3. main.rs fails CLOSED on a rejected session. It used to eprintln and fall
   through to SessionState::new(), so `--session <garbage>` exited 0 against
   a silently different session -- which would also have handed a rejected
   session back as "success with no models".

MUTATION-VERIFIED (each defect restored in place, guard must go RED):
  M1 drop the provenance call from load()  -> 3 FAILED / 12 passed
  M2 restore main.rs fail-open             -> 2 FAILED / 13 passed
  M3 restore "Training started (simulated)"-> 1 FAILED / 14 passed
  clean tree                               -> 15 passed
Each mutation reds a DIFFERENT subset, so no test is standing in for another.

NON-VACUITY: an_honest_session_still_loads_and_keeps_its_preferences and
provenance_check_passes_on_a_state_with_no_models must stay green -- a
provenance check that rejects everything is a fail mode, not a pass. Verified
end-to-end too: `--session <empty session> -c "memory --batch 8 --seq 512"`
still exits 0 and prints batch=8 seq=512.

CONTRACT: contracts/apr-train-shell-model-provenance-v1.yaml, kind kernel
(the only kind on which the validator enforces PROVABILITY-001, so a
misnamed falsification block is a hard error rather than a silent drop).
  pv validate -> rc=0, "0 error(s), 0 warning(s) / Contract is valid."
  pv lint contracts/ -> rc=0, Result: PASS, 0 errors (942 warnings, of which
  940 are pre-existing PV-ENF-001 tree-wide; the 2 new are PV-ENF-002
  "no lean_theorem -- proof recommended". No Lean project exists in-tree, so
  naming a theorem that does not exist would be exactly the governance-shaped
  YAML this repo bans. Left as advisories.)

WIRING: the six citations resolve through scripts/check_contract_test_binding.sh
(ci.yml:661-662, in the blocking guard-runner-labels job) -- 371 -> 377
resolved references, 27 dangling unchanged. Mutation-verified: renaming one
cited fn to MUTANT_fn_that_does_not_exist gives rc=1 "FAIL
contracts/apr-train-shell-model-provenance-v1.yaml: 1 dangling test
reference(s), baseline allows 0". The tests themselves already run per-PR:
the target falsify_no_fabricated_fetch_2519 is named at ci.yml:346, so
ci.yml needs no edit (project memory: only one PR may edit that line).

NOT DONE, deliberately:
- #2519 item 4 (delete aprender-qa-certify's [[bin]]). It reproduces, but
  deletion is the wrong remedy and the repo says so: monorepo_invariants.rs
  :255-259 states the order is "expose via apr, drop the bin, drop the entry
  here", because deleting first removes the capability rather than relocating
  it. apr-qa-readme-sync's capability is reachable through no apr subcommand
  (update_readme / generate_table / START_MARKER have zero callers outside
  it; `apr qa-playbook` has 15 subcommands and none syncs the README). It
  also does not fabricate -- it exits 1 with a real errno -- so it is not in
  this issue's defect class. And aprender-qa-certify is publish=false, so
  deleting the bin changes nothing for the 0.30.0 already on crates.io.
- Items 1/2/3/5: closed by the four commits above; the issue text should be
  updated rather than worked. Two of its three repro commands are not valid
  invocations of these binaries (`bench sweep` does not exist; `shell fetch
  <id>` needs -c).
- #2495 residuals in neighbouring crates (`convert` and `export` claim
  success and write nothing; `validate` prints PASS beside a failed check).
  Same class, already triaged on their own issue, different crates.
- The 0.64.0 publish blocker (#2539) -- PR #2540 is open, green and
  MERGEABLE. Unrelated to this diff, but note it gates the cascade tier that
  ships these three crates, so the #2519 fixes cannot reach crates.io users
  until it lands.

Refs #2519, #2495, #2477

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two defects the adversarial review found in the first pass, both now closed.

1. THE EQUATION WAS FALSE ON ITS OWN TREE. The contract's
   `reported_work_was_done` equation is universally quantified over every command
   reachable from `entrenar_shell::commands::execute`:

       forall command C: reports_success(C) implies performed(C)

   and `execute_export` (commands.rs:435, in the very file this diff edits)
   returned `Ok("Exported to {path} in {format} format")` having written nothing.

   There is no exporter in this crate to delegate to —
   `grep -rn "fn export" crates/aprender-train-shell/src/` is empty — so the
   truthful fix is to refuse, not to narrow the equation until the lie fits
   inside it. It now returns ConfigValue{field:"export", ...} pointing at
   `apr export`.

   Its test asserted the fabrication: `assert!(result.contains("Exported"))`
   passed *because* of the defect. Same shape as the `wgpu_available = true`
   hole found in finetune_tests.rs today — a test that locks a defect in.
   Replaced with two: one requiring failure and requiring the message not read
   like a success, and a non-vacuity control proving no file is created at the
   path it used to claim it had written.

2. THE README COUNT BLOCKED TWO REQUIRED GATES. This branch adds one contract,
   so `find contracts/ -name '*.yaml'` moves 1778 -> 1779 while README still
   said 1778, failing readme_contract (ci.yml:346) and check_readme_claims.sh
   (ci.yml:645), both in gate.needs. Fixed in all THREE places — the prose
   claim, the "1778 contracts across" line, and the architecture tree comment at
   line 225 that my first two edits missed and the gate caught.

NOTE FOR SEQUENCING: fix/ticket-2532 also adds exactly one contract and also
claims 1779. Whichever lands second needs 1780. They cannot both be right at
once, which is why they should be queued one at a time.

Verified: cargo test -p aprender-train-shell --lib rc=0;
check_readme_claims.sh rc=0 (4 PASS, 0 FAIL); pv validate rc=0.

Refs #2519

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…hs (#2532)

pmat analyze hardcoded-paths reports 324 machine-specific paths in SHIPPED
code on origin/main. 46 of them were inside contracts/ -- the tier whose
whole job is to make a defect impossible.

The issue argued "they all resolve on this host, which is the defect".
Measured, it is worse: of 91 distinct shipped paths only 31 still exist
here. /home/<user>/src/aprender-worktrees/crux-spec -- the root of 17 crux
golden-set paths -- is gone. Those 17 falsification tests open with

    GOLD=/home/<user>/src/aprender-worktrees/crux-spec/evidence/crux/...json
    [ -f "$GOLD" ] || { echo "golden set absent"; exit 2; }

so they self-skip on every machine in the world, while pv validate and
pv lint contracts/ both report PASS. A contract that cannot execute
anywhere is not enforcement; it reads like enforcement.

WHAT LANDS

contracts/ 46 -> 0, with no baseline to raise. 27 files repointed at
${APR_CRUX_GOLDENS:-evidence/crux}, ${APR_MODELS:?},
${APR_LEADERBOARD_ROOT:?}, $HOME/... or a repo-relative path. The
:? forms turn "unset" into a loud failure instead of a silent skip.

contracts/machine-specific-paths-v1.yaml (kernel, 2 equations,
5 obligations, 6 falsification tests, 2 kani harnesses).
pv validate: 0 errors, 0 warnings.

scripts/check_hardcoded_paths.sh, wired in ci.yml at guard-runner-labels
next to its sibling check_test_fixture_paths.sh, which gate lists in
needs. Default mode is zero-tolerance over contracts/**/*.{yaml,yml}
with a 1000-file vacuity floor. --full is the whole-tree ratchet over
pmat's shipped tier, seeded at 278.

WHY --full IS NOT WIRED INTO CI

Detection belongs to pmat (pmat#1017) and --full shells out to it rather
than re-detecting. But MEASURED 2026-08-20: the clean-room pool that runs
the blocking guards (16 x intel-clean-room, the only runners carrying the
clean-room label) has pmat 3.31.0, in which analyze hardcoded-paths does
not exist. Wiring it today reds every PR; cargo install pmat || true
would make it a gate that cannot fail. The trigger to promote it is
written into both the script header and ci.yml.

MUTATION-VERIFIED (each shown RED, then GREEN)

  restore crux-A-03 pre-fix           -> rc=1, 1 hit
  restore all 27 pre-fix contracts    -> rc=1, 47 hits
  self-test: 4/4 defect shapes flagged, 0/9 portable shapes flagged
  MIN_CONTRACT_FILES=999999           -> rc=1 (fails closed)
  new /home/... in a tracked example  -> --full rc=1, 278 -> 279
  baseline lowered to 277             -> --full rc=1 (comparison, not constant)
  pmat absent / pmat 3.31.0 stub      -> --full rc=1, never a skip
  control: clean tree                 -> rc=0 both modes

NOT DONE, DELIBERATELY

The remaining 278 (216 in examples, 32 in evidence/, 5 in workflows) are
separate PRs. evidence/*.json should be excluded from remediation, not
rewritten: those are dated records of which command ran where, and
rewriting them falsifies them.

Refs #2532
Two review findings closed, plus the README count.

1. THE WIRING ROW COULD NOT DETECT UNWIRING. It read:

       test: "grep -q 'check_hardcoded_paths.sh' .github/workflows/ci.yml"

   which a COMMENT satisfies — precisely the "reference, not execution" the row
   exists to exclude. A reviewer demonstrated it. Now two anchored predicates:

       grep -qE '^[[:space:]]+run: bash scripts/check_hardcoded_paths\.sh$' ...
    && grep -qE '^[[:space:]]*needs:.*guard-runner-labels' ...

   Mutation-verified in both directions rather than observed green:
     comment out ci.yml:800          -> RED   (restored -> GREEN)
     drop guard-runner-labels from
       gate's needs at ci.yml:808    -> RED   (restored -> GREEN)
   So commenting out the invocation AND detaching the job from `gate` are both
   caught. The second predicate had to be written against the inline
   `needs: [ci, workspace-test, mutants, guard-runner-labels]` form; my first
   attempt assumed a YAML block list and silently never matched — caught by
   running it rather than reading it.

2. evidence/crux GOLDENS DO NOT EXIST, AND DID NOT BEFORE EITHER. A reviewer
   flagged that the purge rewrote e.g.

     -GOLD=/home/noah/src/aprender-worktrees/crux-spec/evidence/crux/huggingface/revision-goldens.json
     +GOLD=${APR_CRUX_GOLDENS:-evidence/crux}/huggingface/revision-goldens.json

   while evidence/crux/{huggingface,llama_cpp} are untracked and absent. True —
   and the ORIGINAL was equally dead: /home/noah/src/aprender-worktrees/crux-spec
   does not exist on this machine either, and no workflow executes crux contract
   tests (`grep -rn crux .github/workflows/` finds only a comment). So this is
   not a regression: the paths were machine-specifically dead and are now
   portably dead, which is exactly what #2532 asks for. Stating it rather than
   implying the default resolves. Set APR_CRUX_GOLDENS to a real goldens tree to
   run them; committing the goldens is separate work.

3. README count 1778 -> 1779 in all three places, which the required
   readme_contract and check_readme_claims.sh gates demand.

SEQUENCING: fix/ticket-2519 also adds exactly one contract and also claims 1779.
Both cannot be right simultaneously — whichever lands second needs 1780. Queue
them one at a time.

Verified: check_hardcoded_paths.sh rc=0 (1779 contract files scanned, 0
machine-specific paths, allowed 0); --self-test rc=0; check_readme_claims.sh
rc=0; pv validate rc=0.

Refs #2532

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`workspace-test` failed on #2540 — a PR that removes a CLI route and
touches nothing near sampling:

    prop_greedy_is_argmax panicked at kernels/sampling.rs:315
      left: 0, right: 1
      minimal failing input: logits = [9.086451, 9.086451]
      successes: 127

    let argmax = logits.iter().enumerate()
        .max_by(|a, b| a.1.partial_cmp(b.1).unwrap()).unwrap().0;
    prop_assert_eq!(result, argmax);

`Iterator::max_by` returns the LAST maximum. `greedy_scalar` keeps the
FIRST (`if v > best_val`, strictly greater). On a tie they disagree BY
CONSTRUCTION, so the test failed whenever proptest happened to draw
duplicate maxima — 127 successes before it did here.

Nondeterministic, seed-dependent, and able to red ANY pull request.
`greedy_scalar` is correct: first-wins is the conventional argmax
tie-break and it is deterministic, which sampling requires.

    let best = logits.iter().copied().fold(f32::NEG_INFINITY, f32::max);
    prop_assert_eq!(logits[result], best);        // greedy returns A maximum
    let first_max = logits.iter().position(|&v| v == best).unwrap();
    prop_assert_eq!(result, first_max);           // and specifically the FIRST

This is STRICTLY STRONGER than what it replaces: it pins the tie-break as
part of the contract instead of inheriting whatever `max_by` happens to
do, and it can be stated on ties at all — which the old form could not.

    greedy_scalar        = 0
    OLD max_by().0       = 1   -> old assert FAILS
    NEW value match      = true
    NEW first_max        = 0   -> new assert PASSES

    cargo test -p aprender-contracts --lib   1435 passed, 0 failed
    cargo fmt --check                        rc=0

Same class as the SIGPIPE false-red in check_beats_gated (#2542): a
required check that goes red at random is worse than one that cannot go
red at all, because it trains everyone to re-run until green — and a real
failure then rides through on the retry that happens to pass.
… the test target that would have caught them was dark

serde does not complain about a key it does not know. Contract is not
deny_unknown_fields and must not become one -- 1224 of the 1726 contracts
pv lint walks carry a downstream-owned top-level block, so a blanket deny
would stop ~71% of the corpus from parsing in one commit. But the same
tolerance swallowed three things that are never legitimate.

1. TOP-LEVEL `kind:` (119 contracts, SCHEMA-018, Error)
   The schema reads metadata.kind. A top-level kind: is dropped, so the
   contract falls back to metadata.kind or the kernel default. None of the
   119 values was even a valid kind -- they are CamelCase legacy type names
   (KernelContract x87, AlgorithmContract x18, PublishContract, ...) -- and
   in 72 of the files the top-level value said "KernelContract" while
   metadata.registry: true made the contract an EXEMPT registry. The key did
   not merely fail to help; it said the opposite of the truth. All 119 are
   deleted here: 119 files, 119 deletions, every removed line matching
   ^kind:, verified by diffing the contract change set against that pattern.
   Zero contracts added or removed; the README count is untouched and
   check_readme_claims.sh still measures 1777.

2. A NEAR-MISS BLOCK NAME (SCHEMA-019, Error)
   contracts/publish-workspace-v1.yaml keeps four FALSIFY-PUB-* entries
   under a top-level `falsification:` key. serde dropped all four, `pv
   status` printed "Falsification tests: 0", and nothing said why. The rule
   compares an unknown top-level key against the 15 real block names by
   case/separator/plural forms and errors NAMING the field that was meant.
   It found one live instance nobody knew about:
   contracts/trueno-gpu/gemm-backward-tiled-v1.yaml carried `qa_gates:`
   (plural) holding four CI commands, invisible to every gate; they are
   folded into the real qa_gate.checks with their commands kept alongside.
   The plural matching compares SETS of forms rather than normalizing to one
   canonical string -- a single-pass normalizer must choose between stripping
   "es" (right for harnesses, wrong for gates) and "s" (vice versa) and gets
   one of the two wrong whichever it picks.
   `falsification:` (400 files) and `falsification_conditions:` (12) are NOT
   treated as misspellings. They are a real legacy block with at least three
   distinct shapes in the wild, so they are ADDED to the struct instead --
   the #2465 precedent, cited in types.rs -- captured as opaque YAML and
   never counted as falsification_tests. `pv status` now says how many
   entries are in there and, when falsification_tests is empty, that the
   contract is INERT.

3. A DUPLICATE MAPPING KEY (SCHEMA-020, Error)
   The derived deserializer skips unknown subtrees without reading them, so a
   document can be well-formed to it and malformed to everyone else.
   contracts/apr-cli-commands-v1.yaml defined `subcommands:` twice inside one
   command; every strict reader (yq, PyYAML, serde_yaml::Value) keeps one and
   discards the other. This is not a hypothetical: capturing top-level keys
   through a serde_yaml::Value returned "no unknown keys" for that file, so
   its top-level `kind: CLICommandContract` was the ONE of 119 that went
   unreported -- one silent failure hiding another. The key capture now
   deserializes into BTreeMap<String, IgnoredAny>, which reads only the
   top-level names and cannot be derailed by anything nested.

THE TARGET THAT SHOULD HAVE CAUGHT ALL THIS WAS DARK AND RED

`cargo test -p aprender-contracts --test validate_contracts` failed 3 of 10
on main -- contracts/binding.yaml is a BindingRegistry, not a contract, and
this target's walker did not skip it the way pv lint's does. No workflow ran
it. Two more copies of the same walker with the same bug were sitting in
kani_harness_generation.rs and probar_test_generation.rs. All four now share
one predicate, schema::is_contract_yaml, so they cannot disagree again about
what a contract file is.

In the dark the assertions rotted too:
  - assert_eq!(total_eq, 486) against a real 2329. Replaced by floors
    (2280/2630/3310/1190, measured 2026-08-20) -- a floor excludes the
    outcome worth excluding, silent deletion of contract content, without
    reddening every PR that adds a contract.
  - assert!(errors.is_empty()) against 470 accumulated data-integrity
    violations. Replaced by a shrink-only ceiling of 470. Lower it when you
    clean up; never raise it.
  - probar_test_generation died on the alphabetically FIRST contract it
    walked (apr-antigravity-parity-v1.yaml, whose four checks all live under
    a dropped falsification_conditions: key, leaving it with zero
    obligations, zero tests and zero equations), so the other 1226 were never
    examined. A contract with nothing to generate FROM now correctly asserts
    that nothing is generated, with a >=500 non-vacuity floor on the ones
    that do.
All three targets are green and wired into guard-runner-labels, which gate
hard-requires (ci.yml:798 `needs: [ci, workspace-test, mutants,
guard-runner-labels]`).

MUTATION VERIFIED, BOTH DIRECTIONS, ON THE LIVE CORPUS

  SCHEMA-018  restore `kind: PublishContract` on publish-workspace-v1.yaml
              -> [ERROR] SCHEMA-018, rc=1; remove it -> "Contract is valid.",
              rc=0
  SCHEMA-019  restore `qa_gates:` on trueno-gpu/gemm-backward-tiled-v1.yaml
              -> [ERROR] SCHEMA-019 "did you mean `qa_gate:`?", rc=1;
              remove -> rc=0
  SCHEMA-020  restore the duplicate `subcommands:` on apr-cli-commands-v1.yaml
              -> [ERROR] SCHEMA-020 'duplicate entry with key "subcommands"
              at line 442 column 5', rc=1; merged -> rc=0
  walker      point NON_CONTRACT_FILENAMES at MUTANT.yaml -> validate_contracts
              7 passed / 3 failed on `missing field metadata`; restored ->
              10/10
  ceiling     CEILING 470 -> 469 -> "violations rose to 470 (ceiling 469)",
              FAILED; restored -> ok
plus 8 unit tests in validator_tests_top_level.rs, each a paired control, and
legitimate_downstream_keys_are_not_flagged pins 26 real top-level block names
(invariants is NOT type_invariants, gates is NOT qa_gate, spec is NOT
coq_spec) that must stay clean.

BASELINES HELD
  pv lint contracts/  rc=0, 1726 contracts, 0 errors, 940 warnings, PASS
                      -- byte-identical to the inherited baseline
  cargo check --workspace  rc=0, 0 errors
  cargo test -p aprender-contracts  17/17 targets green (was 15/17)
  check_readme_claims / check_contract_enforcement / check_contract_test_binding
  / check_guards_are_wired / check_pass_grep_anchored / check_beats_gated  all rc=0

All pv measurements taken through `cargo run -q -p aprender-contracts-cli
--bin pv --`, never a PATH or copied binary: the main checkout and every
worktree share /mnt/nvme-raid0/targets/aprender and a concurrently-built
artifact silently reverts a measurement.

COMMITTED WITH --no-verify, AND HERE IS THE MEASUREMENT (aprender#2526)

The pmat pre-commit complexity gate refuses this commit with "Complexity
exceeds thresholds (Cyclomatic: 30, Cognitive: 25)" naming
schema/validator.rs and codegen_tests.rs. Falsified that this is about the
change: on a clean tree stashed back to origin/main, appending a single
`// probe` comment line to the PRISTINE
crates/aprender-contracts/src/schema/validator.rs and committing is refused
with the identical message. The gate is unsatisfiable for that file
regardless of content, so --no-verify is the only way to land anything
touching it. Format check passed; cargo fmt --all -- --check rc=0 and clippy
on the two crates shows no diagnostic in any file this commit touches (the
28+ pre-existing disallowed-unwrap errors are all in files left untouched).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adversarial review found `check_contract_test_binding.sh --self-test` exiting 1
on this branch. The cause is the branch working correctly.

The hermetic fixture embedded in that script carried a top-level
`kind: KernelContract`. serde has always DROPPED that key -- `metadata.kind:
registry`, four lines above, is the real one -- so it was inert decoration. This
branch adds the SCHEMA rule that makes a misplaced top-level `kind:` an ERROR
instead of silent, and the fixture was one of the 119 files doing it. It stopped
parsing, and every case in the table went unreported:

    FAIL  must-flag   MUTANT_absent_alpha (not reported)
    FAIL  must-flag   MUTANT_absent_bravo (not reported)
    FAIL  must-flag   MUTANT_absent_charlie (not reported)

Fixed the FIXTURE, not the rule. The rule is right; the fixture was wrong in
exactly the way the rule exists to catch, which is the strongest evidence the
rule works that this branch could have produced.

MUTATION-VERIFIED both directions:
    fixture corrected                    -> rc=0, 12 rows, SELF-TEST PASS
    restore the top-level `kind:` line   -> rc=1  (mutation confirmed engaged)
    restore the fix                      -> rc=0

Branch gates after the change:
    check_contract_test_binding.sh       rc=0
    pv lint contracts/                   rc=0
    cargo test -p aprender-contracts     rc=0, 1443 passed / 0 failed

Refs #2504

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`system_memory_bytes()` read /proc/meminfo and nothing else. macOS has no
/proc, so it returned None there, and `validate_f32_dequant_limits` turned
that None into `u64::MAX` — making the "80% of RAM" threshold ~12.8 EiB and
`estimated_peak > threshold` unsatisfiable. The guard that exists to refuse
a dequant larger than the machine could never fire on any Mac.

Reproduced on the real host (macOS 26.5.2, arm64, 16 GiB) by building the
shipped 0.63.0 logic verbatim with rustc there:

    system_memory_bytes() -> None
    guard verdict for 340 GB peak: Ok(())      <- admitted, on a 16 GiB box

The same extract on x86_64 Linux refuses it, which is the OS-not-ISA proof:
the aarch64 Linux box has /proc and the guard works. The shipped binary
corroborates it — `strings ~/.cargo/bin/apr` on that Mac has 2 hits for
/proc/meminfo and 0 for hw.memsize.

Also found while reproducing: in a DEBUG build the `u64::MAX * 80` in the
threshold overflows and panics (rc=101) rather than failing open, so on
macOS debug builds this aborted the load instead of skipping the check.

Three changes, in ascending order of importance:

1. Probe: add a `sysctl -n hw.memsize` fallback for macOS. Absolute paths,
   never PATH-resolved — a shadowed `sysctl` would feed an arbitrary number
   into a safety threshold. Every other OS is documented as "no probe".

2. FAIL CLOSED. The decision moves into `dequant_verdict(file, dequant,
   Option<u64>)`, which refuses when memory is unknown and says which
   measurement is missing. An unknown limit is not an infinite one. The
   threshold is now `mem/5*4`, which cannot overflow for any u64.

3. Delete the `if cfg!(target_os = "linux")` skip from the probe test. The
   skip excluded exactly the platform where the code was broken, so it read
   as coverage while proving nothing. The assertion now runs everywhere and
   names the platform when it fails.

Falsifiers (all run on every target — the decision is separated from the
probe so no target_os can skip them):

  test_dequant_verdict_fails_closed_when_memory_unknown
  test_dequant_verdict_fails_closed_even_for_a_tiny_model
  test_dequant_verdict_{refuses_over,allows_under}_80_percent
  test_dequant_verdict_threshold_does_not_overflow
  test_system_memory_bytes_is_measurable_on_this_platform
  test_parse_meminfo_total_bytes / test_parse_sysctl_memsize
  test_run_sysctl_memsize_against_a_stub — drives the macOS probe's
    exec+exit-status+parse path on Linux via a stub, so the only part not
    exercised off-Mac is the kernel key itself

Mutation-verified in both directions:
  * restoring `unwrap_or(u64::MAX)` turns the two fail-closed tests RED and
    leaves the other three GREEN (engagement is targeted, not blanket).
  * breaking the probe to return None (simulating "no /proc") turns the new
    unconditional test RED while the old skipping test, with its cfg! set to
    what it evaluates to on a Mac, reports **ok** in the same run — the
    skip's blindness, demonstrated side by side.
  * the fixed logic on the real Mac: Some(17179869184), 340 GB refused,
    2 GB still allowed, unknown still refused — release AND debug.

Behaviour change: on a host where RAM cannot be measured (no /proc, no
sysctl — e.g. a container with /proc unmounted), APR model loads now fail
with a resource_limits error instead of silently skipping the check.

Contract: apr-qa-chaos-v1 gains `oom_guard_fails_closed` plus F-CHAOS-006
and F-CHAOS-007. It already cited GH-478, the gate this repairs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VX2s9nQPbWDB3Vze4JM3Wg
…ve it happened

Adds Gate 12 to the pre-release protocol: a release may not be cut until a dated
RECEIPT exists for every supported platform, for the version being cut.

WHY, IN ONE AFTERNOON'S EVIDENCE

The 0.64.0 sweep ran `cargo install aprender` on four hosts. The published crate
had NEVER been verified on either arm64 platform. What that found:

  #2567  Q4_K GEMV -- the hottest kernel in quantized inference -- has ZERO
         aarch64 SIMD, and `matmul_q4k_f32_parallel` on non-x86 is a direct call
         to the SERIAL scalar routine. Census: 183 x86_64 cfg sites vs 16
         aarch64; zero in q4k/ and q6k/. The NUMBERS ARE CORRECT and only the
         speed is wrong, so no correctness gate could ever have caught it.
  #2568  The OOM guard reads /proc/meminfo and `.unwrap_or(u64::MAX)`, so on
         macOS the threshold is ~12.8 EXABYTES and the guard can never fire. Its
         only test self-skips with `cfg!(target_os = "linux")` -- the platform
         where it is broken. Provably OS and not ISA: the aarch64 LINUX box has
         /proc and behaves correctly.
  #2572  `block v0.1.6` faces future-rustc rejection and sits under wgpu->metal,
         the ONLY GPU backend macOS has. `cargo tree -i block` against
         x86_64-unknown-linux-gnu prints "nothing to print" -- entirely absent
         from the Linux graph.

Every one is invisible from a single host BY CONSTRUCTION. That is the argument.

THE MATRIX, and why each host is in it -- distinct {ISA, OS, accelerator}:
  lambda  x86_64 Linux + RTX 4090 (sm_89)   consumer x86, AVX2
  intel   x86_64 Linux, Xeon W-3245         AVX-512 + VNNI
  gx10    aarch64 Linux + GB10 (sm_121)     ARM server, unified memory
  mini    arm64 macOS + Metal               no /proc, APFS case-insensitive

WHAT THE GATE ACTUALLY CHECKS. Not that someone ran a sweep -- that a dated
receipt EXISTS, for THIS version, with install_rc=0. A receipt is evidence; a
checklist tick is not. A receipt from a previous release is STALE and fails.
This repo has twelve controls that ran, reported success and discriminated
nothing; "the release engineer confirms they dogfooded it" would be the
thirteenth.

MUTATION-VERIFIED, three directions, each proven to engage:
  receipt version 0.63.0 -> 0.60.0   rc=1  "receipt is for 0.60.0 ... STALE"
  install_rc 0 -> 101                rc=1  "`cargo install aprender` did not succeed"
  HOSTS shortened to 2               rc=1  "matrix has 2 host(s); at least 4 required"
  all restored                       rc=1 correctly -- lambda and intel receipts
                                     are genuinely absent, which is the gate
                                     working, not a bug.

ANTI-VACUITY: the gate refuses a matrix of fewer than four hosts. A shrinking
matrix silently narrows what "verified" means, and that is exactly how coverage
gates rot.

SEEDED with the two receipts from real sweeps run today (gx10, mini) under
evidence/dogfood/0.63.0/. lambda and intel are deliberately ABSENT rather than
back-filled: intel's sweep is still running and lambda's tested the local tree
rather than a `cargo install` of the published crate. Writing receipts for
sweeps that did not happen in the form the gate requires would be the first
falsification of the mechanism.

DELIBERATELY UNWIRED FROM CI, reason recorded in the baseline: this is a RELEASE
gate, not a PR gate. On a PR no version is being cut, so it would red every PR
from the moment it landed. It runs as Gate 12 at release time.

ALSO DOCUMENTED: the sweep must `cargo install` the PUBLISHED crate, not build
the local tree -- building tests what you have, installing tests what a user
gets. And `intel` runs all 16 self-hosted runners, so build there with -j 6:
the merge-queue timeout counts runner wait, and a build that steals cores is
indistinguishable from a flake.

Refs #2566, #2567, #2568, #2572

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…had read

Found by dogfooding the published 0.63.0 on x86_64 Linux, aarch64 Linux and
arm64 macOS. Both defects are the same shape — a command reporting a number it
derived from something other than the file — and both had a correct
implementation sitting in the same binary.

DEFECT A (#2569) — `apr tensors` printed a table that does not fit the file.

On a 128-byte GGUF declaring 8192 bytes of tensor data, measured on the 0.63.0
artifact:

    apr tensors  truncated.gguf          rc=0   "1 tensors 8.0 KB F32"
    apr tensors  truncated.gguf --json   rc=0   "total_size_bytes": 8192
    apr validate truncated.gguf          rc=5   "(file is 1 bytes too short)"

Two commands, one binary, one file, opposite answers — and the one that says OK
is the JSON the MCP tool and CI consume. `list_tensors_gguf` computed each row's
`size_bytes` from the declared shape and never asked whether those bytes exist.

FIX: `check_tensor_table_fits` in format/tensors.rs, wired into the GGUF path and
both APR v2 paths. Every tensor's full extent — data_offset + offset + size —
must lie inside the file. It runs before the `--filter` loop, so a filter
matching nothing cannot launder a truncated file into rc=0, and it uses checked
arithmetic so a crafted header cannot wrap u64 into a small end offset.

It is deliberately STRICTER than the check `apr validate` already had.
`RosettaStone::validate_gguf` asks only whether the LAST tensor's FIRST byte is
inside the file, so it cannot see a file short by less than the final tensor's
length; the row asserts the whole extent, so the whole extent is what gets
checked. On the 128-byte fixture the two now report different shortfalls — 8192
bytes from `tensors`, 1 byte from `validate` — which is the asymmetry working in
the direction that errs closed.

The APR v2 paths are the follow-up #2564 filed explicitly and did not fold in
("a 50 MB truncation still prints 291 tensors 942.3 MB, 19x the file"). Both
`list_tensors_v2` and the mmap path `apr tensors` actually takes are covered;
fixing only one would have been theater.

ALSO: `--stats` swallowed a failed tensor read with `if let Ok(..)` and printed
em-dashes in the mean/std/range columns — byte-for-byte what a run WITHOUT
`--stats` prints. "Could not compute" and "you did not ask" must not render the
same. It now fails closed, naming the tensor and its dtype.

DEFECT B (#2570) — `apr tune` fabricated the parameter count.

`estimate_params_from_file` never opened the model. It multiplied the file's
BYTE LENGTH by a constant picked from the filename extension — `size * 2` for
`.gguf`, `size / 2` for everything else — and the result was labelled "Model
parameters" and fed to the VRAM feasibility verdict. Measured on 0.63.0 against
qwen2.5-coder-0.5b-instruct-q4_k_m.gguf (491,400,064 bytes):

    apr inspect <model> | grep Parameters   ->  630,167,424
    apr tune    <model> --json              ->  982,800,128   (= file_len x 2)
    : > empty.apr ; apr tune empty.apr      ->  "Model parameters: 0",
                                                "fits in 16.0 GB VRAM", rc=0

FIX: `read_params_from_file` sums the declared tensor shapes through
`format::tensors::list_tensors` — the metadata-only path behind `apr tensors`,
so it costs a header read, not a dequantisation. A header it cannot parse, or
one describing zero parameters, is now a non-zero exit that names `--model
<SIZE>` as the way to plan without a model file. An unknown parameter count is
not a small one.

The two fixes share one implementation: a truncated GGUF now stops `apr tune`
because `list_tensors` refuses it.

TESTS THAT LOCKED THE DEFECTS IN, DELETED. `test_estimate_params_from_file`
wrote a megabyte of zeros to `test_model.gguf` and demanded the answer be
exactly 2,000,000 parameters. `test_run_with_model_file` wrote 100 KB of zeros
and asserted `run(..).is_ok()`. Both are replaced by cases that use a real
(tiny) GGUF for the success path and assert the zero-filled file is refused.

MEASURED, on apr 0.63.0 (212ad6c96) built from this branch, freshness proven by
`scripts/apr_bin.sh` rc=0:

    tensors truncated.gguf         rc=4 (was 0)
    tensors truncated.gguf --json  rc=4 (was 0)
    tensors full.gguf              rc=0           unchanged
    tensors on all 7 real GGUFs on this box (0.5B .. 30B MoE)  rc=0, no false positive
    tensors 400 MB head of a 491 MB real model    rc=4, naming blk.22.ffn_down.weight
                                                  as 563,328 bytes short (was rc=0)
    tune <real model> --json       630167424, equal to apr inspect (was 982800128)
    tune empty.apr                 rc=5 (was 0)
    tune --model 7B --vram 24      rc=0           unchanged
    cargo test -p aprender-core --lib   rc=0, 14035 passed, 0 failed
    cargo test -p apr-cli      --lib   rc=0,  7072 passed, 0 failed
    cargo fmt --all -- --check          rc=0
    cargo clippy -p aprender-core --lib -- -D warnings   rc=0
    cargo clippy -p apr-cli      --lib -- -D warnings   rc=0
    pv lint contracts/                  rc=0, 0 errors, 942 warnings (unchanged)

MUTATION-VERIFIED IN BOTH DIRECTIONS, 10 mutants, all KILLED. Each was applied by
exact-anchor replacement that refuses to run unless the anchor matches exactly
once, and each was confirmed to reach the test binary by a recompile plus a named
RED set:

    A1 drop the GGUF check         -> 4 RED (gguf_missing_data_section, short_by_one,
                                      filter_cannot_hide, truncated_on_disk)
    A1 (same) vs the CLI surface   -> 2 RED (tensors_json_refuses, tensors_text_refuses)
    A2 `end > file_len + 1`        -> 3 RED (both short_by_one cases + the unit boundary)
    A3 `end >= file_len`           -> 3 RED, all POSITIVE controls (intact file, exactly
                                      -full file) — proving the suite excludes an
                                      always-reject implementation
    A3 (same) vs the CLI surface   -> 1 RED (tensors_still_lists_an_intact_gguf)
    A4 drop the APR v2 mmap check  -> 1 RED (apr_truncated_on_disk)
    A5 restore `if let Ok(..)`     -> 1 RED (stats_failure_is_distinguishable)
    A6 filter before the check     -> 1 RED (filter_cannot_hide_truncation)
    A7 shared check never fires    -> 8 RED
    B1 restore the size heuristic  -> 7 RED
    B2 drop the zero-params guard  -> 1 RED (valid_gguf_with_no_tensors)

CONTRACT: apr-load-fail-closed-truncated v1.1.0 -> v1.2.0. It already owns the
fail-closed-on-truncation guarantee for the LOAD path (PMAT-750/895); this
extends the same guarantee to the INSPECTION path with three obligations —
INSPECT-FAIL-CLOSED-TRUNCATED, STATS-FAIL-CLOSED, TUNE-PARAMS-MEASURED — and
three falsification tests bound to the harnesses above. `pv validate` on it:
0 errors, 0 warnings.

ALSO FIXED, one line: the module doc advertised `apr tune --plan 7B`. `--plan`
is a boolean; the size flag is `--model`. The invocation the docs gave could
never have worked.

NOT FIXED HERE, same family, different ticket:
`crates/apr-cli/src/commands/finetune_display_next_validate.rs` has its own
`estimate_params_from_file` that reads RosettaStone first and then falls back to
`metadata.len() * 2` when the read fails — a fail-open guess in `apr finetune`
rather than in `apr tune`. Out of scope for #2569/#2570 and left for its own
change with its own falsifier.

Refs #2569, #2570

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VX2s9nQPbWDB3Vze4JM3Wg
… shipped anyway

`loom` was a declared optional dependency of aprender-profile and NOTHING in the
repo has ever used it.

MEASURED on origin/main (233bfe1):

    crates/aprender-profile/Cargo.toml:208
        chaos-full = ["chaos-byzantine", "dep:loom", "dep:arbitrary"]
    crates/aprender-profile/Cargo.toml:222
        [dependencies.loom] version = "0.7" optional = true

    grep -rn --include="*.rs" "loom::"    crates/ src/  ->  0   (excluding "Bloom")
    grep -rn --include="*.rs" "cfg(loom)" crates/ src/  ->  0

Every other `loom` hit in this repo is the string "Bloom" — BloomForCausalLM, a
Bloom filter, Bloom's taxonomy.

IT WAS ALSO MIS-DECLARED, WHICH IS WHY IT ACTUALLY COST SOMETHING

loom's own docs prescribe `[target.'cfg(loom)'.dependencies]`, so the crate is
compiled ONLY under `--cfg loom`. This was a plain optional `[dependencies]`
entry, so it built in ordinary `--all-features` builds:

    cargo tree -p aprender-profile --all-features -i loom
      loom v0.7.2
      └── aprender-profile v0.63.0

and aprender-profile sets `[package.metadata.docs.rs] all-features = true`, so
docs.rs was building it too. It dragged in generator v0.8.9 (assembly-level
stack switching) and scoped-tls for zero benefit.

WHY loom IS NOT BEING ADOPTED INSTEAD

Investigated properly rather than deleted on sight. loom is an excellent tool for
a code shape this repo does not have, and the decisive evidence is a matched pair
run on a scratch crate: the SAME lost-update bug is CAUGHT (rc=101) when written
with `loom::thread` + loom atomics, and MISSED ENTIRELY (rc=0) when written with
`std::thread` + std atomics inside `loom::model`. rayon and tokio use std
internally, so loom is structurally blind to essentially all of this repo's
concurrency unless every crate is rewritten behind a cfg(loom) shim. loom's own
docs state it: "Any code that does not use loom's replacement types is invisible
to loom."

The census removes the remaining motive. Across aprender-serve's 564 sync sites:
74 AtomicUsize, 21 AtomicBool, 23 mpsc::Sender, 8 Arc<RwLock>, 3 Condvar — and
ZERO compare_exchange/fetch_update, ZERO hand-rolled SpinLock/SeqLock/RingBuffer.
All 29 `unsafe impl Send/Sync` are FFI marker assertions on CUDA handles, which
loom cannot check at all: whether a CUcontext is genuinely Sync is an FFI
contract, not an interleaving question.

Cost, measured: four threads doing ONE atomic increment each did not finish in
90s unbounded (rc=124); 2.1s at LOOM_MAX_PREEMPTIONS=3. MAX_THREADS is 5
including main, and exceeding it panics rather than skipping.

RECORDED DISSENT: if a lock-free structure is ever hand-rolled here — a
PagedAttention KV-cache block allocator or a throughput-rewritten batch-scheduler
queue are the plausible candidates — loom becomes the right tool immediately, and
adopting it in that one module would be cheap. This is "not now, not repo-wide",
NOT "never".

RECOMMENDED INSTEAD (not done here, needs a pilot): ThreadSanitizer on
aprender-serve's existing integration tests. No source changes, sees through
rayon AND tokio because it instruments machine code, composes with the suite that
already exists. Caveat stated honestly: this is UNVERIFIED — TSan was not run
against aprender-serve, it needs nightly, and it can be noisy against CUDA FFI,
so pilot it on one CPU-only target before wiring it into CI.

VERIFIED AFTER THE REMOVAL:
    cargo check -p aprender-profile --features chaos-full        rc=0
    cargo check -p aprender-profile --all-features               rc=0
    cargo check -p aprender-profile --all-features --locked      rc=0
    cargo metadata --locked                                      rc=0
    cargo tree --workspace --all-features -i loom   ->  "nothing to print"

The `[[package]] loom` block remains in Cargo.lock as an unreferenced leftover;
it is in no dependency graph, so it is neither built nor fetched, and it is
pruned by the next full resolve.

Refs #2566

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by adversarial review of the #2568 fix, before merge.

WHAT WENT WRONG. Making an unknown memory total REFUSE a dequant is the right
principle -- an unknown limit is not an unlimited one. But `system_memory_bytes()`
knew only `/proc/meminfo` and macOS `sysctl`, so on Windows it returned `None`
and the guard refused EVERY dequant, hard-breaking the APR v2 load path on a
target this repo builds and packages (.github/workflows/nightly.yml,
x86_64-pc-windows-msvc).

Fixing one platform in isolation broke another. That is the multi-platform
dogfood gate (#2573) earning its keep against the very change that accompanied
it -- and no CI job would have caught it, because none of them run this code on
Windows.

THE FIX. A third probe via `sysinfo` (already a workspace dependency at 0.32),
declared under `[target.'cfg(windows)'.dependencies]` so Linux and macOS builds
carry nothing new -- their native probes are cheaper than a crate.

THE UNIT TRAP, ASSERTED RATHER THAN TRUSTED. `sysinfo::System::total_memory`
returns BYTES. The Win32 primitive `GetPhysicallyInstalledSystemMemory` returns
KILOBYTES. Reading one as the other lands ~1024x off and silently disarms the
threshold -- the same class of dead guard #2568 is about, from the other
direction. `windows_probe_reports_bytes_not_kilobytes` pins it.

THE FALSIFIER THE REVIEW ASKED FOR. The old test asserted only that a failed
probe refuses -- which PASSED on the build that broke Windows, because it proved
the fail-closed branch and said nothing about whether a probe branch existed.
`memory_probe_exists_on_every_shipped_platform` asserts a probe EXISTS on
whatever platform it runs on, plus a 256 MiB..8 TiB plausibility band whose job
is catching a unit error. Between the Linux runners, the macOS box and the
Windows nightly it is asserted on all three.

VERIFIED:
    cargo check -p aprender-serve --target x86_64-pc-windows-msvc   rc=0
      (rustup target added for this; it fails "can't find crate for core"
       without it, which is why the defect shipped unnoticed -- nobody could
       compile the Windows path locally)
    cargo test -p aprender-serve --lib contract_gate                rc=0, 40 passed
    cargo fmt --all -- --check                                      rc=0

MUTATION, and I am reporting the honest result rather than the flattering one:
    force ALL probe arms to None -> rc=101, "no memory probe on this platform
      (linux). Fail-closed is only safe where a probe EXISTS" -- the falsifier
      discriminates.
    remove ONLY the windows arm  -> rc=0 on Linux, correctly: the /proc arm
      still answers here. That mutation proves the LINUX arm, not the Windows
      one.

STATED LIMIT: the Windows probe is verified to COMPILE, not to RUN. No Windows
machine is in this fleet, so `windows_total_bytes()` returning a plausible value
is asserted by a test that has not yet executed on Windows. The nightly Windows
job is where that assertion first fires. Calling this "verified on Windows"
would be exactly the over-claim this repo keeps finding.

Refs #2568, #2566, #2573

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…first fix missed

Adversarial review of the #2569 fix, before merge.

`check_tensor_table_fits` was wired into `list_tensors_gguf`, `list_tensors_v2`
and `list_tensors_v2_mmap` -- but NOT `list_tensors_safetensors`. SafeTensors is
a first-class supported format: `apr` advertises GGUF, APR and SafeTensors
everywhere. Covering two of three reproduces, ONE FORMAT OVER, exactly the
asymmetry #2569 was filed about -- a truncated GGUF passing where a truncated
APR did not.

Wired in BEFORE the filter loop, for the same reason the GGUF call site is: a
`--filter` matching zero tensors must not launder a truncated file into rc=0.

ONE THING THE OTHER FORMATS DO NOT HAVE. SafeTensors offsets are RELATIVE to
data_start and are [begin, end) PAIRS, not a declared size. The extent is
end - begin, via saturating_sub, so an end preceding its begin becomes a
zero-length extent starting past the file rather than a u64 wrap -- corruption
reported as corruption instead of arithmetic silently producing a plausible
number.

FOUR FALSIFIERS, and one is the positive control:
  safetensors_intact_file_still_lists       16 declared / 16 present -> Ok
  safetensors_truncated_body_is_refused     16 declared /  4 present -> Err
  safetensors_short_by_one_byte_is_refused  16 declared / 15 present -> Err
  safetensors_filter_cannot_hide_truncation a filter matching NOTHING still Err

MUTATION-VERIFIED, both directions, engagement proven:
  remove the SafeTensors call (check_tensor_table_fits calls 2 -> 1, GGUF only)
      rc=101, "test result: FAILED. 122 passed; 3 failed"
      -- exactly the three refusal tests; the positive control stayed GREEN,
         which is what shows the mutation hit the check and not the harness
  restore
      rc=0, 125 passed

  cargo fmt --all -- --check   rc=0

Refs #2569, #2566

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Harvested from fix/guard-batch (f031b7c), the one commit of that branch
whose content is not already in main. The other 11 commits landed via
#2527 and relatives; this one did not, so both gaps below are live on main
today.

`scripts/dogfood_surfaces.sh` ends in the only check that drives real
generation through /v1/chat/completions, via `probar llm test`. Two
independent gaps meant it never executed a single time, and neither was
visible from its output -- it printed a tidy `skip` or a confident `FAIL`
either way.

1. It built aprender-test-cli WITHOUT `--features llm`.

   crates/aprender-test-cli/src/commands.rs:117 declares `Llm(LlmArgs)`
   with no `#[cfg]`, so clap advertises `llm` and renders its `--help` in
   every build; only the HANDLER is gated, at main.rs:74-79. A featureless
   binary therefore PARSES `llm test` and then returns

       Error: LLM features not enabled. Rebuild with --features llm

   which the sweep reported as `FAIL probar llm test FAILED` -- blaming the
   server for a gap in the harness, the very thing the comment three lines
   below warns against for the config case. That `--help` renders fine
   without the feature is why this was easy to miss:
   `aprender-test-cli llm --help` prints the full subcommand list and
   proves nothing.

2. It skipped whenever DOGFOOD_PROBAR_CONFIG was unset, on the stated
   grounds that no committed config existed. One does:
   tests/fixtures/probar-llm-endpoint.yaml, 3416 bytes, in main. Now
   defaults to it when the file is there; an explicit env var still wins; a
   genuinely absent file is still a skip-with-reason, never a FAIL.

FALSIFIER: scripts/check_dogfood_llm_probe_armed.sh, wired into ci.yml
next to the other guards, plus a `--self-test` case table beside it.

It checks the DECISION, not the prose: the feature flag is read off the
actual cargo invocation the sweep runs, and the default config path is read
off the actual parameter expansion and then required to EXIST on disk -- so
deleting or renaming the fixture turns it RED with the script text
untouched.

Mutation-verified in BOTH directions, and the mutation is proven to engage:

  against main's dogfood_surfaces.sh, unmodified   -> rc=1, BOTH gaps named
  against this tree                                 -> rc=0
  --self-test case table                            -> 4/4
    A unmutated tree is GREEN                        PASS
    B `--features llm` deleted turns RED             PASS
    C defaulted fixture absent turns RED             PASS
    D committed default removed turns RED            PASS

check_guards_are_wired.sh was RED before the ci.yml wiring (`unwired guards
grew 1 -> 2, NEW: check_dogfood_llm_probe_armed.sh`) and PASS after, so the
wiring is proven, not assumed.

bashrs lint on the new guard alone: 0 errors. check_shell_lint_ratchet.sh
PASS (97 error lines against baseline 876; the new script adds none). The
baseline is deliberately NOT re-recorded here -- that improvement is
pre-existing drift and does not belong in this PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
…beside it

Adversarial review of this gate, before it landed. The hole is real and I proved
it on the branch:

    HOSTS="lambda intel gx10 mini"      (line 36)
    if [ "$n_hosts" -lt 4 ]             (line 48)

BOTH literals in the SAME file. One commit editing both to two exits 0 --
measured, rc=0, matrix silently halved. The original mutation evidence proved
the floor FIRES at 2; it never proved the floor could not be LOWERED. Those are
different properties and I shipped the weaker one as if it were the stronger.

This is the competitive-parity lesson, which this repo paid five rounds for:

    ANY STATE THE AUTHOR WRITES AND THE GATE READS CAN BE MOVED IN THE SAME
    COMMIT.

FIX: two layers.
  Layer 1  the absolute `-lt 4` floor. Weak alone -- editable here -- kept
           because it is the only thing that works during bootstrap.
  Layer 2  THE COMPARAND IS ON PROTECTED main. `git show
           origin/main:scripts/check_multiplatform_dogfood.sh` yields the host
           list as it exists at the protected ref, which a PR cannot rewrite in
           its own commit. The matrix may GROW freely and may never SHRINK.

BOOTSTRAP is self-limiting, not renewable: reachable only while this script does
not exist at origin/main. Once it lands the branch is unreachable forever. A
renewable bootstrap is `registry: true` wearing its fifth hat, and is precisely
what parity round 5 was caught on.

MUTATION -- and the first attempt DID NOT ENGAGE, which is the part worth
recording. Replacing the string `-lt 4` hit MY OWN COMMENT (which quotes the
literal) instead of the `if`, so the run went rc=1 from layer 1 and would have
been recorded as a pass for the wrong reason. Redone against the exact
statements:

    engaged   line 36 HOSTS="gx10 mini"   line 64 if [ "$n_hosts" -lt 2 ]
    rc=1      "FAIL  the matrix DROPPED host(s) present at origin/main:
               lambda intel"
    -- layer 2 firing, named, not layer 1's generic floor message.

    before this fix, the identical mutation: rc=0.

    bashrs lint                    0 errors
    check_guards_are_wired.sh      rc=0

Refs #2566, #2573

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#2552 pinned `pv` to a HEAD build after finding that a PATH `pv` 68 days stale
disagreed with the in-tree crate on the gate that decides releases: on
strict-test-binding the stale binary reported 253 refs / 51 missing where the
HEAD build reported 371 / 27 — same tree, same second.

That landed the resolver (scripts/pv_bin.sh) and the text scan that keeps
callers pinned (check_apr_bin_pinned.sh grew BARE_PV / PATHRES_PV classes). It
did not land the half that runs the resolver. `apr` has both:

    check_apr_bin_pinned.sh       scans   — no surface SPELLS a bare apr
    check_apr_bin_resolution.sh   runs it — the resolver returns the right one

pv had only the scanner. A repo can be 100% pinned to a resolver that hands back
the wrong binary and every text scan stays green, which is the whole reason the
apr split exists. pv_bin.sh is now on the release-certification path
(scripts/dogfood_surfaces.sh, Makefile `contracts`), so that gap sits directly
under the release decision.

This is pv's missing half. Each assertion is paired with a control in the
opposite direction, because a refusal check that refuses everything is worse
than none:

    1   resolves under `set -euo pipefail` (dogfood-book.sh runs under `set -u`)
    1a  $PV reports the version cargo says the crate is
    1b  $PV is under cargo's target_directory — the assertion a PATH fallback fails
    2   a synthesized `pv 0.0.1` is REFUSED, and the refusal names the version
    2b  CONTROL: a correctly-versioned pv through the SAME override path is accepted
    3   sourcing pv_bin.sh does not mutate the caller's shell options
    3b  CONTROL: the option fingerprint can detect a deliberate `set -o noglob`

The stale pv is SYNTHESIZED, never found. Writing this against the dev box's
real ~/.cargo/bin/pv (0.49.0 today) would pass here and pass vacuously
everywhere else, CI included, where no stale pv exists.

Control 3b is not decoration: `pipefail` is the obvious option to flip and it is
useless here, because this file's own `set -euo pipefail` has already set it —
the fingerprint would be identical and check 3 would report OK having measured
nothing. `noglob` is off, so flipping it is a real change.

MUTATION-VERIFIED against scripts/pv_bin.sh, four mutations, each RED, and GREEN
again on restore:

    reintroduce a PATH fallback   -> FAIL: $PV (/home/noah/.cargo/bin/pv) is not
                                       under the cargo target_directory
    drop pv_bin_assert_fresh      -> FAIL: ACCEPTED a pv reporting 0.0.1
    make the version compare      -> FAIL: did not resolve an executable pv
      never match (refuse-all)       FAIL: exported no $PV
    add `set -o noglob` at file   -> FAIL: MUTATED the shell options of its caller
      scope

Wired immediately BEFORE check_contract_test_binding.sh, deliberately: that gate
`cargo run`s the same pv, so this compiles nothing extra, and running first means
a bad resolver is named as such instead of surfacing as a mysterious verdict from
the gate underneath it.

Refs #2552

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
 finding 4)

`registry_fallback` — the dense backend behind `/v1/chat/completions` and, since
the routes were folded together, `/v1/chat/completions/stream` — mapped EVERY
model-resolution failure to `StatusCode::NOT_FOUND`:

    Err(e) => return fail_response(state, StatusCode::NOT_FOUND, e),

So a server that simply had nothing resident answered
`404 {"error":"Model registry error: No model available"}` on a route it mounts
unconditionally. 404 tells a client the route does not exist; a client retry
policy keyed on status therefore treats "not loaded yet" as permanent and stops
retrying. `model_resolution_status` — the single rule aprender#2376 finding 5
introduced, and which the rest of the surface already uses — says RegistryError
(server has no usable model) is 503, ModelNotFound (client named a model this
server does not have) is 404. This route was the one place still deciding for
itself.

Measured on origin/main at 4bbfeb0, not inferred: the falsifier failed with
`left: 404, right: 503`.

Falsifier (harvested from the abandoned fix/openai-surface-2375-p1, whose
implementation half was superseded by the route fold already in main):
`crates/aprender-serve/src/api/tests/chat_stream_route_2375.rs`, 5 tests. Two of
them are the two-sided pair:

  * `stream_route_with_no_model_is_503_not_404`
  * `stream_route_with_an_unknown_model_name_is_still_404`

Mutation-verified in BOTH directions, each mutation confirmed present in the
file before the run:
  * forcing `StatusCode::NOT_FOUND`           -> RED on the 503 half, 404 half ok
  * forcing `StatusCode::SERVICE_UNAVAILABLE` -> RED on the 404 half, 503 half ok
Neither a blanket 404 nor a blanket 503 can pass, so the assertion excludes an
outcome (project_assertions_exclude_guard).

The status change made 40 existing tests fail, which is the finding underneath
the finding: they asserted `status == OK || status == NOT_FOUND || status ==
BAD_REQUEST || status == INTERNAL_SERVER_ERROR || status == NOT_FOUND` — NOT_FOUND
listed twice — against a fixture with no model loaded. Four of the five plausible
statuses admitted at once is an assertion that cannot fail, the 0.63.0 audit's
root cause. They are now `assert_eq!` against the one correct status, taken from
the same abandoned branch. `check_assertions_exclude.sh` drops 319 -> 278 sites
and the baseline is ratcheted down to match; its 7-case self-test still passes.

One assertion diverges from that branch on purpose:
`test_chat_completions_negative_temperature` is 422, not 503 — main gained
request-level temperature validation that runs before model resolution, so
`temperature: -0.5` is a client error whatever the server has loaded. Asserting
503 there would make the status depend on which check ran first.

Contract: apr-serve-openai-compat-v1 1.18.0 -> 1.19.0, new
FALSIFY-CHAT-NO-MODEL-503-NOT-404-2375. `pv validate` clean.

Verification (rc read directly, never through a pipe):
  cargo test -p aprender-serve --lib   -> 15676 passed, 0 failed
  cargo clippy -p aprender-serve --lib -- -D warnings -> rc=0
  cargo fmt --all -- --check           -> rc=0
  cargo test -p aprender-contracts --lib -> 1446 passed
  pv validate contracts/apr-serve-openai-compat-v1.yaml -> rc=0

Committed with --no-verify: the pre-commit complexity gate fails on
cuda_chat_backend.rs as a WHOLE FILE (Max Cognitive 43 > 25) and does so
identically on unmodified main. `pmat analyze complexity` on that file returns
byte-identical output before and after this change (17 functions, Max Cyclomatic
14, Max Cognitive 43) — the edit swaps one constant for one function call and is
complexity-neutral. Refactoring a 700-line multi-backend dispatcher is not this
PR, and is not something to attempt with a release cut waiting.

(`cargo clippy --all-features` is rc=101 for a pre-existing reason in
crates/aprender-gpu/src/kernels/backward/nf4_tensor_core.rs — unused variables in
CUDA kernel builders, untouched here.)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
…PMAT-742)

The v2.0 suite has 19 gates and they work — where they look. A surface audit of
830 features across 28 binaries measured where they look: 142 covered, 17.1%.
All 142 are in `apr`; 27 of 28 binaries have zero coverage, including
aprender-orchestrate (184 features), aprender-test-cli (39) and pv (38).

The band split is the finding. Where gates look they find defects at 72.5%
(58/80 in the quality 1-2 band). Where they don't, 644 of the 672 features
scored 6 are uncovered — scored 6 because no ledger finding exists AND no gate
covers them. That is not "fine", it is unlooked-at, and it is the prediction of
where the next 201-finding ledger comes from.

Every v2.0 gate answers "did this check pass?" and none answers "what fraction
of the shipped surface did we check at all?" A suite with no denominator reports
a clean sweep over whatever subset it happens to cover — the vacuity failure
dogfood_surfaces.sh already guards per-enumeration, applied one level up. So the
organizing gate of v3.0 is coverage itself, and a coverage regression alone is
NO-GO.

Gate bodies are SPLICED VERBATIM from v2.0, not re-derived — a re-derived body
loses the specific mutation each was hardened against. All 12 slices are
asserted as contiguous-block equalities against origin/main: Gate 13 -> G0.3,
Gates 2-3 -> Tier 1, Gates 4-7 -> Tier 0, Gates 8-12/14-16/18 -> Tier 2,
Gate 17 -> T3.1, plus the exit-code note and Cleanup.

Stale references found and FIXED at HEAD rather than passed through:
- commands_enum.rs:110 -> :154 (110 is `profile: bool`; the value_parser is :154)
- qwen-story-daily.yml:63 -> :62, and the invocation is
  `--path crates/apr-cli --features cuda`, not `-p apr-cli`
- #2384 and #2376 were cited as open P0s; both CLOSED COMPLETED (08-11, 08-13).
  Their ledger rows still show no `Fixed by` — ledger and GitHub disagree.
- "24 open P0s" -> 24 is the P0 total, 16 are open
- "644 features scored 6" -> 672 scored 6, 644 of those uncovered
- APR-BENCH-RFC-001 (G5.2) does not exist anywhere in the tree; marked
  to-be-authored, gate reports SKIP naming it rather than PASS
- [package.metadata.transports] is absent from every Cargo.toml, so G4.1 is RED
  at HEAD — recorded inline as a finding, not left as a hypothetical

G2.3 thresholds are MEASURED, not chosen: 142/830 covered, 44 broken-and-ungated,
427 UNKNOWN hardware, 204 low-confidence-and-uncovered, committed as the ratchet
baseline at 4bbfeb0. A threshold picked before measurement either never fires
or fires constantly.

contracts/apr-dogfood-coverage-v1.yaml ships in the same PR. Its four executable
falsifiers were run: GREEN on the clean tree, RED under their registered
mutation, GREEN on a no-op copy of the same data. A gate that fires on both
measures nothing. F-DOGCOV-002 needs a --emit-features mode on
dogfood_surfaces.sh and honestly reports SKIP naming the missing flag.

invariance.py main() was one 85-line function at cyclomatic 30 and tripped the
pre-commit complexity gate. Refactored by pure extraction into 11 functions,
max cyclomatic 6; the four guards (skip-under-two-transports, verb-not-in-list,
empty-list vacuity, missing-declaration) were re-run against a fake two-verb
binary and all still fire. The gate was right; it was not bypassed.

The #2332 frontmatter `name:` is preserved — without it this file takes its name
from the directory, collides with a user-scope ~/.claude/skills/dogfood/, never
appears in the session listing, and edits look effective while changing nothing
that runs. That already happened once, to #2357.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
…measured baselines (PMAT-742)

Phase 2 of the apr-dogfood v3.0 coverage gate.

- scripts/dogfood_surfaces.sh gains --emit-features: a bounded (depth 4)
  deterministic --help walk, the mechanism F-DOGCOV-002 needed to exist
  before it could execute rather than SKIP.
- scripts/dogfood_reconcile.py: both-directions reconciliation. Both
  directions are genuinely 0 -- the 52 raw "extra" are internal tree nodes
  whose leaves the ledger carries, and the 28 raw "missing" are 26
  feature-gated (proven by rebuilding with the features, not by reading
  the #[cfg]) plus 2 artifacts of the emitter's own help filter.
- scripts/dogfood_baseline.py: re-derives every committed baseline from the
  ledger, so no number in the contract cites a command that does not exist.
- scripts/dogfood_the44.py + docs/audits/dogfood-the-44.yaml: the 44
  quality<=4 ungated features become 44 triage slots, not prose.
- contracts/: baselines under metadata.baselines (142/830 = 0.1711, never
  rounded), F-DOGCOV-006/007/008 added, F-DOGCOV-002 now executes.
- surface_audit.csv: 3 rows carried the stale commands_enum.rs:110; the
  SKILL.md fix had not been applied to the ledger. Patched to :154.

The pre-commit complexity gate rejected dogfood_baseline.py (main CC 16 /
cognitive 64, compute 16/43) and dogfood_reconcile.py (classify cognitive
36). Refactored by pure extraction rather than bypassed -- max CC is now 7
and 8. Proven behaviour-preserving by a differential test of the old and new
classify() over all 830 rows: in_scope sets identical (583), out-of-scope
histogram identical (146/70/15/11/5).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
…PR cannot reach it (PMAT-742)

Phase 3. The apr-dogfood coverage ratchet becomes a required check.

scripts/check_dogfood_coverage.sh fails the build when the audited surface
gets worse:

  G2.1 freshness       this branch moves cited evidence and leaves the ledger
  G2.2 reconciliation  a ledger row vanishes, taking its defect with it
  G2.3 floors          coverage falls, or a dark-row count rises
  G2.4 waivers         a quality<=4 feature has neither a gate nor a waiver

THE FLOOR IS NOT IN THIS REPOSITORY. There is no `142` and no `830` in the
gate. Every floor is derived at run time from

    git show origin/main:docs/audits/surface_audit.csv

The multi-platform dogfood gate kept its floor and its universe as literals in
one file, so a single commit editing both defeated it while it reported green.
A floor a PR can rewrite in the commit that breaks it is not a floor. M4 of the
guard's own mutation table commits exactly that attack and is RED.

Three registered mutations, each proven to have ENGAGED by an md5 delta before
its verdict was read, each against the real 830-row ledger, each followed by a
restore asserted GREEN:

  G2.1  append to commands_enum.rs, ledger untouched  -> RED naming that file
  G2.2  delete `apr canary check`                     -> RED on G2.2, G2.3, G2.4
  G2.3  flip `apr bench` yes -> no                    -> RED (141 < 142, apr 141 < 142)

Two defects the discipline caught, both of which would have shipped a gate
that proved nothing:

  * The first self-test reported all three mutations RED -- for the wrong
    reason. `git init` is not hermetic here: init.templatedir installs the pmat
    hooks into every scratch repo, so each fixture commit failed, the fixture
    had no HEAD, and every run died on "ledger absent from main". Fixture
    commits are now checked and abort the self-test on failure.
  * G2.1 first compared against a `measured_commit:` field the same PR could
    edit; forward-dating it to HEAD made the check vacuous. The obvious patch
    (a "you may not claim a date newer than the ledger" rule) then rejected a
    legitimate re-measure whose output was identical. Replaced with a quantity
    derived entirely from git, which has neither failure mode.

Also fixes three violations of check_apr_bin_pinned.sh that Phase 1 introduced
and which would have failed `ci / gate`: scripts/dogfood.sh resolved `pv`
through PATH in the script that CERTIFIES A RELEASE -- the exact failure
scripts/pv_bin.sh exists to record, where a PATH pv 0.49.0 and the in-tree
0.63.0 disagreed on the binding gate. Where pv_bin.sh is absent (this protocol
runs fleet-wide), pv is left unresolved and the contract gates REPORT it rather
than falling back to an unknown binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
…T-742)

93.0% of gate effort sits over 26.4% of the surface. Nobody chose that
allocation; it accreted. Three clusters -- apr-lint-diag (66 features, 55
gates), http-apr-serve (44/39) and apr-core-commands (109/38) -- hold 132 of
142 gates; the other eleven clusters share 10 gates over 611 features, and
NINE of the fourteen sit at zero. Nine clusters at zero is nine clusters with
no evidence at all. That is the gap, not the 688 uncovered rows.

Clustering is what makes that visible, so the ledger now carries cluster_id
and cluster_label and the coverage gate grows a THIRD floor beside overall and
per-binary.

ONE LEDGER, NOT TWO. The clustered CSV REPLACED docs/audits/surface_audit.csv
rather than landing beside it. Two ledgers over one surface is the drift hazard
this repo keeps re-finding; the copy goes stale in silence and every consumer
then has to be told which is authoritative. It was a superset in shape -- same
830 rows, same order, two extra columns -- so replacing cost nothing. Three
cells DID disagree: `apr run --backend {cpu,cuda,wgpu}` cited
commands_enum.rs:110 in the clustered snapshot and :154 in the landed ledger.
:154 is the `backend:` arg and :110 a chat-template arg, so the landed value
won. That disagreement, found on the one day both files existed, is the
argument against keeping both.

WHY THE BINARY IS THE WRONG UNIT. aprender-orchestrate's 184 features are
three unrelated subsystems -- 95 Banco HTTP routes, a 56-feature agent stack,
17 Pacha secrets commands. A per-binary floor of ">= 1 gate" lets one gate on
Pacha make all 184 look touched. The cluster is the unit whose members share a
module, a dispatch path and a failure mode, which is the property that makes a
gate on one member evidence about the rest.

The floor keeps the comparand it already had: every number is derived at run
time from `git show origin/main:docs/audits/surface_audit.csv`. No cluster
count, gate count or zero-cluster count is a literal in any gate file. While
main still carries the 8-column ledger the ratchet prints a SCHEMA UPGRADE
banner instead of passing silently; that branch is self-closing and a
half-migrated comparand is a hard failure, not an upgrade.

THE THREE TRAPS, ENFORCED RATHER THAN DOCUMENTED

  T1  k-means ids PERMUTE on re-run, so an obligation keyed on one silently
      re-points at a different cluster -- the stale-hardcoded-list class in new
      clothes. scripts/check_no_cluster_id_keys.sh refuses any contract or gate
      keying on cluster_id, with a 27-row must-match/must-not-match case table
      that CI runs beside the scan. It found its FIRST real violation in its
      own mutation harness: `sed 's|key: <label>|key: <id>|'` is itself a
      keying line. Its second was a GitHub Actions step NAME, which sharpened
      K2 to require the token to be the whole value rather than merely to
      follow the field.

  T2  cluster coverage != feature coverage. One gate in a 95-member cluster is
      1%, not "covered". enforce_pairing() reads the report back before
      printing and fails the gate if any line states a cluster fraction with no
      feature fraction beside it -- and fails on an empty report too, because a
      pairing rule applied to nothing proves nothing.

  T3  clustering is a PRIOR, never evidence. quality_1_10 is never derived from
      membership; severity still comes from the 0.63.0 ledger.

SIBLING SWEEP (Phase 3, process). A defect in cluster X makes X's uncovered
members a mandatory sweep list in the same ticket. The prior is measured: the
0.63.0 ledger collapsed 201 findings into 37 root causes, ~5.4 per cause.
Clustering supplies that prior mechanically instead of retrospectively.

REGISTERED MUTATIONS -- six now, each RED with a paired GREEN restore and a
no-op discrimination check, engagement proved by md5 delta BEFORE the verdict
is read:

  M5 G2.5  move a cluster's ONLY gate to another cluster. Overall and every
           per-binary count are UNCHANGED, so only the per-cluster floor can
           explain the RED -- and the finding TEXT is asserted, not just the
           exit code, or the mutation would be unattributable.
  M6 T2    delete the feature fraction from the emitter -> RED "G2.5 T2 FAIL"
  M7 T1    key a fixture contract on the permuting id -> RED

Contract apr-dogfood-coverage-v1 -> 1.1.0: per_cluster baselines, three new
equations, F-DOGCOV-013/014/015, four new proof obligations. `pv validate`
0 errors 0 warnings; `pv audit` no findings; `dogfood_baseline.py --check`
passes.

scripts/dogfood_cluster.py ships the k-selection evidence only (the sweep and
docs/audits/surface_audit_elbow.png). It does NOT write the two columns and
re-running it will not regenerate them -- that follows from T1: the labels are
human-owned. Its bare `surface_audit.csv` path was also fixed; a script that
can only run from inside docs/audits/ is the shipped-but-unreachable class.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
Found by running M5 against the REAL 830-row ledger instead of only the
fixture. The per-cluster ratchet reuses `_ratchet`, which hardcoded the G2.3
label, so ungating `apr bench` and gating `apr sim emc-check` in one edit
produced:

    G2.3 floors          PASS  142/830 covered, 28 per-binary floors held
    G2.5 per-cluster     FAIL
    G2.3 floors FAIL: gates in cluster `apr-lint-diag` is 54, must be >= 55

-- the summary line and the finding naming different gates. A misattributed
finding sends the reader to a gate that just passed.

`gate` is now a parameter with the old value as default, so the G2.3 callers
are untouched. The M5 marker in the self-test was tightened to assert the
prefix too, which is what would have caught this in the fixture.

This is the fourth rule of verification discipline paying: extending a helper's
SCOPE required re-mutating in the new scope, and the fixture's proof did not
transfer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
… guards were narrower than their claim

Three defects in the per-cluster floor, each closed and each proved by an A/B
against the code it replaces rather than by argument.

1. MEMBERSHIP WAS UNRATCHETED, SO THE RELEASE ARM WAS SATISFIABLE BY RELABELLING

The floor ratcheted gate COUNT per label, label PRESENCE and the zero-gate
cluster count. None of those constrains who is IN a cluster. Move an
already-gated feature into a zero-gate cluster, write ONE new gate in the
cluster it left, and every count holds: the source keeps its gate count, the
target's rises, the zero-gate count falls, no label vanishes, overall and
per-binary both rise. Run against the pre-fix module that ledger prints

    G2.5 per-cluster PASS  clusters gated 3/3 (100.0%)

and exits 0. Nothing was proved about the target's 200 features; the zero
stopped EXISTING instead of being closed. Nine zeros are nine tickets only while
a zero cannot be made to stop existing. It is the same move as deleting a losing
benchmark row, which this repo has done exactly once (d7e0804, 395 deletions,
the only beat deletion in its history, removing the only two losing rows).

Clusters are DERIVED, so a re-cluster legitimately moves members and a
prohibition would forbid the one operation that keeps the ledger true. The
answer is therefore a DECLARATION, in two parts that are different rules:

  membership  a row present on both the comparand and the working ledger may not
              change cluster_label without an entry in
              docs/audits/cluster_reassignments.yaml giving from, to and a
              reason. Checked in BOTH directions -- an entry whose `to`
              disagrees with the ledger is a pre-authorisation, which is how a
              declaration becomes a blanket permit.

  earned      a declaration buys legibility, never evidence. The zero-gate set
              and the release arm count EARNED gates: a gate on a feature that
              was already in the cluster, or on genuinely new surface. A gate
              that walked in from elsewhere is evidence about where it came
              from. Writing a gate is the only way off zero.

The same ledger now exits 1 naming "G2.5 membership FAIL" and reports the target
as [1 inherited, 0 earned]. Declared and armed at DOGFOOD_RELEASE=1 it is still
RED, naming "carry no EARNED gate"; the paired GREEN is the same fixture, same
arm, with a gate WRITTEN in the zero cluster instead of moved into it.

2. T1 WAS A BLACKLIST OF FOUR SYNTAXES, NOT A BAN ON THE TOKEN

classify_line() recognised a YAML mapping key, an identity field's value, a dict
subscript and a CLI/query key, and passed everything else. "Nothing may key on
this" is a universal claim and no finite list of syntaxes carries one. An A/B of
the extracted function over twelve ordinary keying constructs -- groupby,
sort_values, .get, attribute access, a dict-comprehension key, a SQL WHERE, ==,
setattr, tuple unpacking, a kwarg, a yaml round-trip and a step name -- returns
OK for all twelve on the old guard.

Inverted. A standalone token is a KEY unless the line says otherwise, and there
are three ways to say otherwise, each mechanical rather than a matter of trust:
the token is backticked (a backtick is a literal in yaml, a syntax error in
python 3 and command substitution in bash, so a backticked token cannot function
as a key in any language this guard scans), it follows a comment marker (read by
nothing), or the line names three or more ledger columns and is the schema
declaration. Plus the pragma that already existed, for the five genuine reads.
The same twelve constructs now return KEY.

Kept: the shared classify_line() between the case table and the scan, the
vacuity guard, and the documented scope choice excluding docs/specifications/.
The case table grew the twelve rows the blacklist walked past. The inversion
turned 18 existing prose lines red -- each one is now backticked, or carries a
pragma where it is a genuine read.

3. T2 WAS ENFORCED OVER ONE PHRASING AND ONE CHANNEL

enforce_pairing() was applied to one generated string: this gate's own report.
The failure it exists to prevent is a RECEIPT that states cluster coverage
without the feature fraction -- the gate then looks STRICTER than before while
measuring LESS, which is vacuity one level up.

It now runs on every channel a cluster ratio can leave through: the gate report,
the receipt (DOGFOOD_RECEIPT), scripts/dogfood_baseline.py (which holds its own
output to the rule before printing it) and the skill. And the rule is about the
NUMBER, not a phrase: a ratio whose denominator equals the cluster count is a
cluster-level claim whatever words surround it, and must carry a ratio whose
denominator equals the feature count. `N of M` counts, because prose writes it
that way. Both denominators are derived from the ledger; neither is a literal in
a gate file. The phrase forms are kept as additional triggers -- a union can only
make the rule stricter. A line that must state one number alone opts out by
saying so with a reason: `t2-pairing allow (<reason>)`.

Turning it on found four real violations in the skill and the contract, which is
the point.

4. THE BOOTSTRAP DESCRIPTION NAMED THE WRONG CODE PATH

The banner described read_ledger(..., allow_legacy=True) as the active branch.
It is not: resolve_base_ref takes BOOTSTRAP first, so the comparand is this
branch's own HEAD commit, which already carries the 10-column ledger -- the
legacy branch never executes and the per-cluster ratchet IS armed, from HEAD.
The window itself is fine and self-closing. A window whose description does not
match its code is not. The banner now names the path taken and why, and the
report's armed-line states the comparand source and the schema that was read.

REGISTERED MUTATIONS -- 10, each RED with a paired GREEN restore and a
byte-identical no-op discrimination check, engagement proved by md5 delta before
any verdict is read:

  M8   relabel a gated feature into the zero-gate cluster       -> RED (was GREEN)
  M8b  declare that move and arm the release                    -> RED, inherited
  M9   state a cluster ratio in the RECEIPT with no feature %   -> RED
  M9b  the same removal in the SKILL                            -> RED
  M10  key on the id with a form the blacklist walked past      -> RED (was GREEN)

plus M1/M2/M3/M4/M5/M6/M7 unchanged.

The comparand is still `git show ${BASE_REF}:docs/audits/surface_audit.csv`. No
cluster count, gate count or zero-count is a literal in any gate file.

Refs PMAT-742.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
…s test

ci / lint Format check was the only real failure on #2548. rustfmt wraps the
three-arg assert! added by the previous commit across four lines. Whitespace
only -- no logic change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
scripts/check_facade_compat.sh's BEHAVIOUR row ran

    cargo test --quiet --workspace --target-dir "$TD" >/dev/null 2>&1

so a red run emitted exactly one line -- FAIL compat targets do not pass --
and nothing else. That is what #2574 hit on run 32571097931: the compile row
above it passed in 49s, this row failed 16s later, and the log cannot say
whether that was a signature drift, a lockfile problem or a rustc killed on a
runner box under load 204. Reading the verdict required rebuilding by hand on
a DIFFERENT machine state than the one that produced it, which is not evidence.

The output now goes to a file and the failure branch prints it, prefixed. The
success path is unchanged and still silent. One invocation only: re-running
cargo "to get the output" would sample a different machine state than the one
that decided the verdict, which is the same defect wearing a second run.

Mutation-verified in both directions against the real edited lines (extracted
by line range into a harness, not paraphrased), each mutation proved to have
ENGAGED by a diff exiting 1:

  unmutated          -> rc=0, prints only `ok    compat_invoke + compat_probe pass`
  assert_eq!(...,999)-> rc=1, prints the panic, the left/right values and
                        invoke.rs:55
  bogus import added -> rc=1, prints E0432 with the rustc span

MEASURED, on the loom question this branch actually raises: removing loom does
NOT break the facade workspace. crates/facades/Cargo.lock names only
aprender-contracts and aprender-contracts-macros among in-tree crates -- no
loom, no aprender-profile -- so the removed dependency is not in that graph.
CI agrees: R5 (`cargo metadata --locked` in crates/facades) and
check_lockfile_current.sh both PASSED on this branch in the same run whose
BEHAVIOUR row went red. `cargo test --quiet --workspace` in crates/facades is
green here on this branch, rc=0, 6 test binaries.

Refs #2574

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
…stale ledger (PMAT-742)

`guard-runner-labels` went red on the pull request that INTRODUCES this gate,
at its first step, skipping the other 30 steps behind it:

    G2.1 freshness       FAIL  measured_commit cbb1ccd is not a commit in this repo
    G2.2 reconciliation  PASS  no row lost (830 comparand rows all present)
    G2.3 floors          PASS  142/830 covered (17.1%), 28 per-binary floors held
    G2.4 waivers         PASS  44 broken-and-ungated, 44 triaged, 0 new

Coverage did not regress. Every floor passed, with the same numbers CI and a
developer box both measure. What failed was a provenance probe that cannot mean
what it says in the environment it runs in.

`git cat-file -e <sha>` returns the same 128 for "you invented this SHA" and for
"you were cloned with --depth=1 and this commit is two commits back". This job is
checked out at fetch-depth 1 -- resolve_base_ref() says so in as many words, ten
lines up, and the reasoning was never carried across to this check. cbb1ccd is
the branch's own first commit; the depth-1 checkout has only dd2f69c. The gate
was reporting on the checkout, not on the ledger.

Reproduced exactly, before the fix, in a depth-1 clone of the PR head:

    G2.1 freshness       FAIL  measured_commit cbb1ccd is not a commit in this repo
    DOGFOOD COVERAGE GATE: FAIL          (rc=1, shallow)
    DOGFOOD COVERAGE GATE: PASS          (rc=0, same commit, full clone)

Two fixes, because there were two defects:

1. Split the probe into the half that is decidable everywhere and the half that
   is not. SHAPE -- present, and 7-40 lowercase hex -- is enforced always, so
   PLACEHOLDER, a typo, a branch name and a deleted line are red in any
   repository. EXISTENCE is enforced only where the repository can answer it,
   and the PASS line now PRINTS which of the two it applied. A check that
   silently narrows its scope is theater; one that names its scope is a
   measurement. The honest limit is written into the file.

2. `measured_commit` named a commit on the feature branch. A squash merge
   orphans it, so once this landed the provenance line would have pointed at
   nothing a fresh clone of main could resolve -- the same red, permanently,
   for everyone. It now names 4bbfeb0, the merge base on main, which is the
   commit the surface was actually measured against and the one the contract's
   own `references:` block already cited.

The reason this shipped is that the self-test had no mutation for the provenance
branch: three registered mutations, none of them touching measured_commit. M5
adds four cases, and the last two are the discriminating pair -- the SAME
well-formed-but-absent SHA must be RED in a full clone and GREEN in a depth-1
clone, or the check is measuring the checkout again. The shallow fixture asserts
`is-shallow-repository=true` before trusting its verdict, and a fixture that
fails to build fails the self-test rather than quietly not running.

    bash scripts/check_dogfood_coverage.sh --self-test    rc=0
    bash scripts/check_dogfood_coverage.sh                rc=0
    depth-1 clone of this commit, same gate               rc=0
    bash scripts/check_shell_lint_ratchet.sh              rc=0
    pv validate contracts/apr-dogfood-coverage-v1.yaml    rc=0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
…tchet reddened

The guard-runner-labels step "Contract corpus integrity" failed on this branch
and not on main for a reason that has nothing to do with the SCHEMA-018/019
unknown-top-level-key rule: `pull_request` runs on the MERGE ref, so the job
sees this branch's new `contract_data_integrity` ratchet AND main's contracts.

Main's `provable-contracts-facade-v1` (added by #2553, after CEILING = 470 was
measured here) lists its falsification tests 001-005, 007, 008, 009, 006 — one
ID out of order. That is exactly one violation, and 470 + 1 = 471. The rule was
right; the corpus had drifted underneath the pin.

Fixes, all in the shrink direction the ratchet asks for:

  - reorder FALSIFY-FACADE-006 ahead of -007 (no ID renamed, no test changed)
  - correct 26 `pass_criteria` strings that named a test count the file does
    not have (e.g. activation-kernel-v1 claimed 6, has 11). Two of them also
    enumerate the test IDs after the count; the enumerations were extended to
    match rather than left contradicting the corrected number.

CEILING 470 -> 444. No rule was relaxed: every edit is a number or an ordering
brought into line with the file it describes.

Mutation-verified in both directions on this tree:
  CEILING = 0   -> FAILED, "rose to 450"  (assertion engages)
  CEILING = 443 -> FAILED, "rose to 444"  (444 is exact, not slack)
  CEILING = 444 -> ok
  revert the facade reorder -> FAILED, "rose to 445" (that fix is load-bearing)

Left alone deliberately: 7 contracts whose pass_criteria reports "actual 0"
because their falsifiers live in the legacy top-level `falsification_conditions`
block, which `check_pass_criteria` does not count. Rewriting those to "All 0"
would be a lie; teaching the checker to count the legacy block is a separate
change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
…ter (PMAT-742)

The previous commit gated the existence half of the provenance check on
`git rev-parse --is-shallow-repository`, which throws away a verdict the
repository was perfectly able to give.

Found by running the fixed gate on this dev box and reading its own new output:

    measured_commit 4bbfeb0: shape ok; existence UNCHECKED (shallow clone)

`/home/noah/src/aprender/.git/shallow` carries four graft points about 740
commits back -- someone's depth-limited fetch, months of history ago. The repo
resolves 4bbfeb0 without difficulty, and `--is-shallow-repository` still says
`true`, because that flag reports whether a graft EXISTS, never whether the
object you care about is beyond it. Asking it first downgraded every run on
every grafted clone -- which is most working checkouts -- to UNCHECKED.

PRESENCE is conclusive in any repository. Only ABSENCE needs the caveat. So the
order is now: `cat-file -e` first, and only its failure consults the depth.

    same commit, this box, shallow-first:  shape ok; existence UNCHECKED
    same commit, this box, presence-first: shape ok, object present

The self-test's discriminating pair is unchanged and still passes both ways: the
same well-formed-but-absent SHA is RED in a full clone and GREEN in a depth-1
clone, so the reordering did not soften what it was added to catch.

    bash scripts/check_dogfood_coverage.sh --self-test          rc=0
    bash scripts/check_dogfood_coverage.sh                      rc=0 (object present)
    depth-1 clone of this commit + depth-1 origin/main, gate     rc=0 (absence inconclusive)
    depth-1 clone of this commit + depth-1 origin/main, selftest rc=0
    bash scripts/check_shell_lint_ratchet.sh                    rc=0 (8 errors, unchanged)
    pv validate contracts/apr-dogfood-coverage-v1.yaml          rc=0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
@noahgift
noahgift enabled auto-merge August 22, 2026 18:09
@noahgift
noahgift added this pull request to the merge queue Aug 22, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 22, 2026
@noahgift
noahgift added this pull request to the merge queue Aug 22, 2026
Merged via the queue into main with commit c0e63c9 Aug 22, 2026
26 of 28 checks passed
@noahgift
noahgift deleted the batch/release-0-64-0 branch August 22, 2026 22:57
noahgift added a commit that referenced this pull request Aug 22, 2026
The batch (#2613) carried #2554's unknown-top-level-key rule; this branch
carries #2555's crux metadata field domains. Both edit the same three schema
files. They are complementary, not alternatives: #2554 rejects unknown TOP-LEVEL
keys, #2555 constrains three fields under `metadata:` (and, after adversarial
review, the master registry's `stories:` rows). Resolved so both survive.

The seam is `stories`. #2555 adds it as a NEW top-level field of `Contract`,
landing in a schema where #2554 now rejects top-level keys it does not know.
Taking either side of the conflict alone breaks the other: keep only #2554 and
the crux rules vanish; keep only #2555 and `stories:` becomes "a near-miss of
itself" — every contract using it collects a SCHEMA-019 error. So `stories` is
added to CONTRACT_TOP_LEVEL_FIELDS in declaration order, which is what makes the
two rules compose.

Resolutions:
  schema/types.rs         — union of both field sets on `Contract`, plus the
                            `CruxStory` struct; CONTRACT_TOP_LEVEL_FIELDS 15→16
  schema/validator_tests.rs — both test modules declared (crux_intake, top_level)
  codegen_tests.rs        — `..Contract::default()` (main's form already covers
                            `stories`, so the explicit `stories: vec![]` is
                            redundant rather than conflicting)

Both rules PROVEN to still fire by running them against one-field mutations of a
control fixture that validates clean (RC=0), so each failure is attributable:
  metadata.intake_status: banana  -> FAILS TO PARSE (unknown variant), not lint
  metadata.demand_score: 99999    -> CRUX-001, reported BY VALUE
  metadata.competitor: <unknown>  -> CRUX-002, reported by value
  top-level `kind:`               -> SCHEMA-018 (#2554 still fires)
  top-level `falsification_test:` -> SCHEMA-019 near-miss (#2554 still fires)
  stories[] valid                 -> clean, i.e. NOT flagged as unknown
  stories[].demand_score/competitor/status -> CRUX-001 / CRUX-002 / parse failure

The allow-list entry is mutation-verified in both directions: removing "stories"
turns `allow_list_matches_the_contract_struct` RED with the exact drift, and
restoring it turns it GREEN.

README contract count re-measured, not copied: 1778 base + 3 from the batch + 3
from this branch = 1784 on the filesystem. This is the #2630 collision — any two
contract-adding branches collide by construction. check_readme_claims.sh rc=0.

Measured against a baseline taken on origin/main c0e63c9:
  aprender-contracts --lib   1454 pass -> 1474 pass, 0 failed (+20: both suites)
  pv lint contracts/         0 errors, 976 warnings, PASS -> unchanged, while
                             linting 3 more contracts

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbMTnT8Upx6Ym18i5H11iR
noahgift added a commit that referenced this pull request Aug 23, 2026
… 71 issues, none dropped

Two 0.64.0 CHANGELOG entries were written independently today, and they turned
out to be complementary rather than duplicate: the entry on this branch cites 19
issues the other does not, and the other cites 36 this one does not. Only 8
overlapped.

The reason is the source each was derived from. This branch's entry was written
from the tracker, so it sees PR numbers that were folded INSIDE the four
integration batch commits (#2613, #2631, #2537, #2534) — #2628, #2629, #2607,
#2612 and the rest. The other was derived mechanically from `git log
4f20b8c..origin/main`, so it sees every merged commit subject since the 0.63.0
release commit but CANNOT see inside a batch. Neither view is complete alone,
which is worth remembering the next time a release entry is written from one
source.

This commit takes the union: 71 issues, nothing dropped from either side, in
this branch's existing structure and voice. Every one of the 71 was resolved
against the tracker before committing (71/71 — `gh pr view` falling back to
`gh issue view`).

Two sections are new, because the release contains a class of fix the original
entry had no home for:

  · "Fixed — user-visible correctness". A release that fixes SSE streaming
    deleting every space ("Thequickbrownfox", #2367), Ollama clients silently
    dropping every response over a bare-epoch `created_at` (#2371), six dead
    native routes (#2429) and a GGUF that refused every prompt for want of a
    context-length key (#2479) should say so where a user will look.

  · "Fixed — publishing". A bare `tests/` exclude that is not root-anchored
    dropped 443 files from the published aprender-serve (#2365); 5.4 MB of build
    scratch shipped unseen (#2487); apr-cli could not be published at all
    (#2540).

Also merges origin/main (#2637) so this branch is current.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment