fix(release): apr-cli could not be published — it depended on a publish=false crate - #2540
Conversation
|
Housekeeping — this PR's diff is misleading and I will rebuild it. The branch was cut from The actual fix is 2 commits, 599 patch lines: Since #2537 will be squash-merged, its 48 original commits won't be recognised as already-merged, so rebasing this branch afterwards would conflict rather than reduce to the delta. Plan: once #2537 lands, I'll rebuild this as a minimal PR off the new |
`workspace-test` on #2540: beat_apr_sibling_cli_reach.rs — 12 passed; 3 failed every_apr_qa_command_is_reachable_through_apr_qa_playbook apr_qa_playbook_list_reads_the_real_registry the_validated_tree_is_the_whole_tree A real failure, not the transient dep-info/ENOSPC class seen elsewhere today (checked: 0 "could not parse/generate dep info" lines in that job). #2493 added this beat to prove every consolidated sibling CLI is reachable through `apr`, and qa-playbook was one of them. Removing that route (#2539 — it made apr-cli impossible to publish) necessarily invalidates those assertions. * dropped the two qa-playbook-specific tests * removed `qa-playbook` from the whole-tree expectation list, with a comment pointing at the reason so it is not "restored" later * removed the now-unused QA_PLAYBOOK_COMMANDS constant `apr qa` is untouched throughout — different command, different crate. This is the SIXTH surface this one removal touched: enum, dispatch, manifest, contract, registered_commands, depth-2 vacuity floors, book page + SUMMARY, and now the reach beat. Every surface after the first four was found by a guard rather than by memory, which is the machinery working — and a fair measure of how much surface one CLI command owns here. Verified: cargo test -p apr-cli --test beat_apr_sibling_cli_reach 13 passed, 0 failed cargo test -p apr-cli --test cli_commands 12 passed, 0 failed cargo test -p apr-cli --test beat_apr_data_alimentar_reach 3 passed, 0 failed cargo fmt --check -p apr-cli rc=0
…sh=false crate RELEASE BLOCKER (#2539), found by running pre-release Gate 11 against the batch tree BEFORE cutting 0.64.0: cargo publish -p apr-cli --dry-run error: failed to prepare local package for uploading Caused by: no matching package named `aprender-qa-cli` found ## Five whys 1. apr-cli cannot publish -> it depends on aprender-qa-cli. 2. Why the dependency? -> #2493 routed `apr qa-playbook` into it. 3. Why did that pattern work for the other five siblings it routed? -> aprender-cgp, -rag-cli, -zram-cli, -simulate, -contracts-cli are all publishable. 4. Why is aprender-qa-cli different? -> it carries publish = false # Internal QA harness; reached through `apr qa` and has since at least 0.61.0. 5. Why did a route into an internal-only crate get added at all? -> NOTHING CHECKED that a publishable crate depends only on publishable crates. Root cause. ## The fix respects the existing boundary The `publish = false` is deliberate, documented and three releases old; the QA closure is build/CI tooling (certify, gen, runner, report). Making apr-cli publishable the other way would mean publishing FIVE internal crates to crates.io permanently — an irreversible one-way door — to route one subcommand. So the defect is #2493's route, not the boundary. Removed `apr qa-playbook` from every surface, so nothing advertises what the published binary cannot do (the `apr test llm` lesson: a command advertised in --help whose own remedy was impossible): * commands_enum.rs variant * dispatch.rs arm * crates/apr-cli/Cargo.toml dep * contracts/apr-cli-commands-v1.yaml * tests/cli_commands.rs registered_commands (the third surface — FALSIFY-CLI-001 caught this one, not me) * book/src/cli/qa-playbook.md + its PCU contract + the SUMMARY link `apr qa` is untouched: it is implemented in apr-cli and never depended on this crate. Two depth-2 vacuity floors dropped below their threshold because the real count went 128 -> 113. Lowered 120 -> 105, deliberately NOT to just-under 113: a floor pinned tight to the current surface re-breaks on every ordinary command change and trains people to bump it reflexively, which hollows out the parser-health check it exists to be. ## Poka-yoke — the half that matters scripts/check_publishable_deps_publishable.sh, wired into ci.yml with its case table. This class is invisible to every normal gate: build, test and clippy all resolve the path dependency locally and pass. It only appears at `cargo publish` — mid-release-cascade, the worst possible time. The v0.50.0 cascade died exactly this way at 29 of 68 crates. self-test 3/3 — includes the control (all-publishable must NOT be flagged) and the "unpublishable -> unpublishable is fine" row, since those are never uploaded clean tree PASS, 78 members scanned MUTATION re-add the real dep -> RED, naming apr-cli -> aprender-qa-cli vacuity floor fails if the enumeration sees < 15 members It also records a tested dead end: `optional = true` does NOT launder an unpublishable dependency — cargo validates optional deps at publish time too. Verified, so nobody rediscovers it the expensive way. ## Verification cargo publish -p apr-cli --dry-run rc=0 Packaged 606 files, 9.1MiB cargo clippy --all-targets -D warnings rc=0 cargo fmt --check rc=0 cargo test -p apr-cli --test cli_commands 12 passed, 0 failed pv validate contracts/apr-cli-commands-v1.yaml Contract is valid. check_publishable_deps_publishable.sh PASS check_guards_are_wired.sh unwired 2 -> 1 (this guard is live)
…racts, 111->110 commands, 113->112 chapters
Removing `apr qa-playbook` took its PCU contract and its book chapter with
it, so three README claims went stale. Caught by check_readme_claims.sh in
guard-runner-labels:
FAIL FALSIFY-README-002 contract_count: README claims 1778, filesystem has 1777
FAIL FALSIFY-README-003 cli_command_count: README claims 111,
contracts/apr-cli-commands-v1.yaml lists 110 commands
Worth noting the second message is #2536's rewritten guard working in
production: it now names the contract file it counted, and it runs in ~1s
against the registry instead of doing a 14-minute `cargo run` that then
failed printing nothing.
`workspace-test` on #2540: beat_apr_sibling_cli_reach.rs — 12 passed; 3 failed every_apr_qa_command_is_reachable_through_apr_qa_playbook apr_qa_playbook_list_reads_the_real_registry the_validated_tree_is_the_whole_tree A real failure, not the transient dep-info/ENOSPC class seen elsewhere today (checked: 0 "could not parse/generate dep info" lines in that job). #2493 added this beat to prove every consolidated sibling CLI is reachable through `apr`, and qa-playbook was one of them. Removing that route (#2539 — it made apr-cli impossible to publish) necessarily invalidates those assertions. * dropped the two qa-playbook-specific tests * removed `qa-playbook` from the whole-tree expectation list, with a comment pointing at the reason so it is not "restored" later * removed the now-unused QA_PLAYBOOK_COMMANDS constant `apr qa` is untouched throughout — different command, different crate. This is the SIXTH surface this one removal touched: enum, dispatch, manifest, contract, registered_commands, depth-2 vacuity floors, book page + SUMMARY, and now the reach beat. Every surface after the first four was found by a guard rather than by memory, which is the machinery working — and a fair measure of how much surface one CLI command owns here. Verified: cargo test -p apr-cli --test beat_apr_sibling_cli_reach 13 passed, 0 failed cargo test -p apr-cli --test cli_commands 12 passed, 0 failed cargo test -p apr-cli --test beat_apr_data_alimentar_reach 3 passed, 0 failed cargo fmt --check -p apr-cli rc=0
7b581c3 to
7452e19
Compare
|
Rebuilt off the new #2537 landed batch B, so this branch's old base was squashed away. As flagged earlier, rebasing would have conflicted rather than reduced to the delta — the 48 original commits aren't recognisable in a squash merge. Rebuilt via
That is now the actual change, and reviewable as such. Re-verified on the rebuilt branch against the new main: The third commit is new since the original PR: |
…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>
`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.
`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.
… CI run, not thirteen (paiml#2613) * fix(train-shell): --session was a second door into the #2519 fabrication (#2519) #2519's three headline claims do NOT reproduce on origin/main 5c08e771f: all three train-* binaries were repaired by 594b687c6 / 34a0f1548 / 0f1b62673 / 8c23a4eac 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 5c08e771f, 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> * fix(train-shell): execute_export reported success while writing nothing 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> * fix(contracts): the contract tier itself held 46 machine-specific paths (#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 * fix(contracts): the wiring assertion was satisfied by a comment 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> * fix(test): prop_greedy_is_argmax reddened at random on tied logits `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. * fix(contracts): three silent-drop defects in the contract schema, and 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> * fix(contracts): the guard's own fixture was one of the 119 offenders 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> * fix(serve): the F32 dequant OOM guard failed OPEN on macOS (#2568) `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 * feat(release): dogfood every supported platform before a cut, and prove 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> * fix(tensors,tune): two commands asserted things about a file neither 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 * fix(deps): remove the phantom loom dependency — declared, never used, shipped anyway `loom` was a declared optional dependency of aprender-profile and NOTHING in the repo has ever used it. MEASURED on origin/main (233bfe105): 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> * fix(serve): the macOS OOM-guard fix broke Windows — add the third probe 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> * fix(tensors): SafeTensors was the third format, and the only one the 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> * fix(dogfood): the deepest probe in the sweep had never run once Harvested from fix/guard-batch (f031b7cbb), 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 * fix(release): the dogfood gate's own anti-vacuity floor was editable 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> * fix(contracts): pv's pinning was guarded, its RESOLVER was not #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 * fix(serve): a model-less server 404'd its own mounted chat routes (#2375 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 4bbfeb07f, 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 * feat(dogfood): apr-dogfood v3.0 — the gate suite gets a denominator (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 …
Closes #2539.
Found by running pre-release Gate 11 against the batch tree before cutting 0.64.0 — which is the point of running gates early:
Five whys
apr-clicannot publish → it depends onaprender-qa-cli.apr qa-playbookinto it.aprender-cgp,-rag-cli,-zram-cli,-simulate,-contracts-cliare all publishable.publish = false # Internal QA harness, and has since ≥0.61.0.The fix respects the existing boundary
The
publish = falseis deliberate, documented, and three releases old; the closure is build/CI tooling (certify,gen,runner,report). Makingapr-clipublishable the other way would mean publishing five internal crates to crates.io permanently — an irreversible one-way door — to route one subcommand. So the defect is #2493's route, not the boundary.apr qais untouched — it lives inapr-cliand never depended on this crate.Removed
apr qa-playbookfrom every surface, so nothing advertises what the published binary cannot do (theapr test llmlesson — a command advertised in--helpwhose own remedy was impossible):commands_enum.rsvariantdispatch.rsarmcrates/apr-cli/Cargo.tomldepcontracts/apr-cli-commands-v1.yamltests/cli_commands.rs::registered_commandsThe three-surface drift guard caught the surface I forgot. That machinery is why removing a command is safe here despite touching seven places.
On the vacuity floors
Real depth-2 count went 128 → 113, below the
>= 120floor. Lowered to 105, deliberately not to just-under-113: a floor pinned tight to the current surface re-breaks on every ordinary command change and trains people to bump it reflexively, hollowing out the parser-health check it exists to be. The reasoning is in the comment.Poka-yoke — the half that matters more
scripts/check_publishable_deps_publishable.sh, wired intoci.ymlwith its case table.This class is invisible to every normal gate —
build,test, andclippyall resolve the path dependency locally and pass. It surfaces only atcargo publish, i.e. mid-release-cascade, the worst possible time. The v0.50.0 cascade died exactly this way at 29 of 68 crates.apr-cli -> aprender-qa-cliIt also records a tested dead end:
optional = truedoes not launder an unpublishable dependency — cargo validates optional deps at publish time too. Verified empirically, so nobody rediscovers it the expensive way.Verification