Repository navigation
release(0.69): batch — 15 receipted PRs + fleet-pv andon fix, one CI run, one queue slot - #3669
Merged
Merged
Conversation
…YTE index (#3338) `pv proof-status contracts/ --table` exits 101 on `contracts/`: byte index 40 is not a char boundary; it is inside '∈' (bytes 39..42) of `for Q4_K_M Qwen2.5-Coder, quantization ∈ {Q4_K, Q6_K}` The column width is a byte count (`property.len()`, capped at 40) and `truncate` sliced `&s[..max]`, so any property whose byte 40 lands inside a multi-byte char panics. Eight contracts in `contracts/` do; the first one walked is `apr-inspect-quantization-v1.yaml`. The budget stays a byte budget — the table is laid out in bytes — and the cut now walks back to the nearest char boundary. Two tests, both RED before this commit (each panicked at obligation_matrix.rs:169): the helper row, and one through `format_obligation_table` because that is the path the operator hit. The helper fixture asserts `!s.is_char_boundary(40)` first, so it cannot silently stop proving anything. Pmat-Ticket: PMAT-3347 Refs #3347, #3338 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…obligations ticked (#3347) `obligation_matrix` computed `l2_tested` as `idx < falsification_tests.len()`. Obligation 3 was "tested" because the contract happened to hold 4 tests, whoever those tests were about. All 7 obligations of `qwen35-e2e-verification-v1` showed ✓ before a single test existed. The fallback was a substring match between the obligation's `property` prose and a test's `rule` prose, which is an inference, not a claim. WHICH LINK, decided by counting the corpus (1,842 files, 3,792 obligations, 4,691 falsification tests), not by preference: proof_obligations[].discharged_by 89 (62 `falsification_tests[N]`, 13 a test id, 1 a YAML sequence, rest comma lists / prose / a kani id) falsification_tests[].binds_to 38 falsification_tests[].obligation 26 (12 name an obligation id, 6 the exact property text, 8 dangle) proof_obligations[].id 180 of 3,792 kani_harnesses[].obligation 1,918 — that is the L3 column, not this one All three L2 spellings are DECLARATIONS by the contract author, so all three are read. `binds_to` is a serde alias of `obligation`, which is safe only because no entry carries both keys — checked, because serde turns that into a `duplicate field` parse error rather than a silent pick. Not read: `applies_to`. 12 `binds_to` values match one, but `AppliesTo` is an enum with `#[serde(other)] Other`, so the string is discarded at parse and there is nothing left to compare. Those 12 report `?`, not a false ✗. WHAT THE COLUMN NOW SAYS. Three values, because "no test covers this" and "nothing here says which test covers what" are different facts: ✓ Tested a test in this contract cites this obligation ✗ Untested the contract's links resolve and none names it — or it ships no falsification test at all, which is a reading, not a gap ? Unknown no readable link; not measured. An unread window is Unknown, never a tick MEASURED over `contracts/`, and the drop IS the point — it is what the old column was hiding: before after L2 ✓ 3,573 86 L2 ✗ 180 65 L2 ? 0 3,602 (3,753 obligation rows, 873 contracts) Nothing was adjusted to keep the number up, and no threshold was added. The two link fields did not exist on the structs, so both keys were written to disk and silently dropped on parse — the shape of #3314 (`id`) and #2465 (`test_harness`). `discharged_by` is typed `Citation` (scalar | comma list | YAML sequence) because `Option<String>` failed the WHOLE corpus on `publish-manifest-v1`: `invalid type: sequence, expected a string`. RED first: `l2_does_not_tick_for_an_obligation_no_test_cites` — two obligations, two tests, both citing OB-A. It asserted through the rendered table (the surface that was lying, and API-stable), so it is the SAME test before and after: on the old code `Beta holds | ✓`. Its OB-A arm keeps a fix that merely stopped ticking everything from passing. Out of scope_paths, and forced: `lint/strict_test_binding.rs` holds the only EXHAUSTIVE `FalsificationTest` literal in the tree (no `..Default::default()`), so no schema field can compile without that one line. Pmat-Ticket: PMAT-3347 Refs #3347, #3091, #3114 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… — it now refuses (#3347) The gate resolves cited test names against a source index rooted at the contract path's PARENT. The directory form gets the repo root and finds `crates/`; the single-file form gets `contracts/`, which holds no source, so every cited ref resolves to nothing and all of them are reported missing. Measured on `contracts/pv-artifact-kinds-v1.yaml`, a control whose eight refs all resolve: pv lint contracts/pv-artifact-kinds-v1.yaml --strict-test-binding total_refs 8, existing 0, missing 8 pv lint contracts/ --strict-test-binding total_refs 548, existing 521, missing 27 <- none of the 27 is this one A control contract failing identically to a broken one is a gate that cannot discriminate, and it fails SILENTLY: `passed` is true in non-strict mode, so the run still says `Result: PASS` while printing eight false findings. REFUSED rather than repaired. The scan root is computed in `provable_contracts::lint::run_lint`, outside this ticket's scope; a refusal lives in the caller, is honest, and cannot be mistaken for a clean bill. If the root is later made explicit (a `LintConfig` field the CLI can feed, which is what `--crate-dir` does NOT do today), this refusal is what should be deleted. Exit 1, not the exit-2 `decline:` class: exit 2 belongs to `ZeroContracts`, whose message ("0 contracts under ...") would be false here — there IS a contract; it is the gate that cannot run over it. Two tests, in the CI-wired `cli_integration` target: the refusal names the flag and prints no PASS and no findings, and a directory-form control proves the refusal is specific to the single-FILE form rather than to the flag. Pmat-Ticket: PMAT-3347 Refs #3347 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…racts, 25 tickets, registry edit + mutation proof Research of ../autogluon (1.6.3 @ 77946149) as a competitive-research source for aprender, landed the way category N landed linfa and burn (#3169): the competitor is admitted to the CLOSED registry, every story is a real contract with falsification gates, every story is a registry row, and every missing/partial story has a GitHub issue and a roadmap fragment. What AutoGluon is, measured from the tree rather than recalled: three predictors. TabularPredictor (65 public methods, 11 presets, 24 model families of which 8 are tabular foundation models added in 1.4-1.6), TimeSeriesPredictor (Chronos-2/Toto-2 pretrained, 30+ local/deep models, 16 metrics incl. WQL/MASE/RMSSE, auto backtesting since 1.5) and MultiModalPredictor. Evidence under evidence/crux/autogluon/. What aprender has, measured at eb262f8: automl/ is a single-estimator hyperparameter tuner (TPE, grid, random, DE, TimeBudget, EarlyStopping); time_series/ is one univariate ARIMA; encoders, calibration, SHAP/LIME/ permutation importance and KFold/cross_validate exist as building blocks. No predictor-level fit(label), no leaderboard, no bagging, stacking or greedy weighted-ensemble selection, no panel forecasting, no quantile forecast metrics. The gap is the AutoML UX, not the algorithms. Category O — AutoML Parity — 24 stories: 9 P0 (the README hello-world: fit(label), problem-type inference, presets, leaderboard, feature pipeline, weighted ensemble, time budget, panel forecaster, quantile metrics), 9 P1 (bagging, stacking, importance, threshold calibration, deployment artifact, tabular foundation model, backtesting, local baselines, pretrained forecaster), 6 P2 (refit_full, distill, infer_limit, fit diagnostics, memory-aware fit, covariates). MultiModalPredictor, autogluon.cloud, MLZero and Ray-parallel fits are CUT on the epic with reasons. CRUX_COMPETITORS: [&str; 14] -> [&str; 15] + autogluon Not a BEAT pillar: aprender claims no pinned-benchmark win over AutoGluon. Both registry tests that keep BEAT_INCUMBENTS and CRUX_COMPETITORS apart are extended, not worked around. Tickets: epic #3370, stories #3371-#3394, label pareto-autogluon. Roadmap: 25 fragments under docs/roadmaps/entries/, roadmap.yaml regenerated by the aggregator (idempotent check passes). Spec: docs/specifications/crux-competitive-research-ux-workflows.md v2.2 -> v2.3 — §3 gains rows for linfa+burn (category N, which #3169 never recorded there) and AutoGluon; §5 gains Category O; §6 notes that coverage_intake in the YAML is the source of truth. coverage_intake 267 -> 291 (partial 72 -> 77, missing 152 -> 171). Verification: - pv built from THIS tree validates 25/25 (24 new + master). The stale ~/.cargo/bin/pv rejects crux-O-01 with CRUX-002 — the behavioural delta proves the registry edit engaged. - Mutation-verified: deleting "autogluon" from CRUX_COMPETITORS turns competitor_registry_covers_the_corpus_vocabulary RED with "autogluon is used by contracts/ and must stay in CRUX_COMPETITORS" and the_real_crux_registry_rows_are_all_in_domain RED. Restored: 20/20. - cargo test -p aprender-contracts --lib: 1526 passed, 0 failed. - Every falsification gate is LIVE-PENDING prose (no `::`), so strict-test-binding has nothing to refuse; the obligations are RECORDED as unfalsifiable-by-absence, not satisfied. - README CONTRACT_COUNT regenerated 1835 -> 1866 by readme_sync.sh. - Guards: roadmap fragment/ids/sorted/additive/completion, contract test-binding and enforcement, shell-lint ratchet, hardcoded paths, readme claims, grep -q ratchet — all rc=0. Pmat-Ticket: PMAT-3370 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…epic's acceptance criteria describe the programme, not this diff Round 2 of the quorum read the epic (PMAT-3370) as the ticket and refused the admission for not implementing the 24 stories. The admission is its own unit of work with its own done-when; this fragment says so. Closes #3401 Pmat-Ticket: PMAT-3401 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nts (its own included) and the scaffold CATEGORY_NAMES edit Quorum round 3 lane 1 refused on two literal mismatches between the ticket and the diff: '25 roadmap fragments' (there are 26 once this ticket's own fragment lands) and an unlisted edit to scripts/crux_scaffold_contracts.py. The title now lists every path. Pmat-Ticket: PMAT-3401 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… in CI (proof credit from runs, not declarations), then a Verus pilot on one dequant/parser function (#3495) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, measured; author claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…v2, extract:parity-receipt, and the 7 back-filled
Until this row the logit-parity records under evidence/parity/** had NO validator
of any kind. Not a weak one — none. That is why seven of them sat in the tree
carrying no comparator for months: there was nothing that could have noticed.
WHAT LANDS
· contracts/parity-receipt-v2.yaml — three shapes: parity-receipt-complete
(closed, ignoredProperties empty), parity-comparator-self, -oracle. The
subset has no sh:or, so the comparator split is two shapes over two
subclasses the extractor assigns by kind.
· contracts/parity-receipt-v1.yaml — the retired layout, recorded with NO
shape: all instances were migrated, and a shape whose target class nothing
instantiates passes vacuously. The enforcement that replaces it fires — the
extractor refuses an unmigrated record BY NAME, and so does the predicate.
· ontology/extract/parity_receipt.rs — record / unmigrated / other, and
skipping is never silent.
· scripts/parity_receipt_denominator.sh + evidence/parity/EXPECTED_RECEIPTS —
the count is pinned by an INDEPENDENT predicate. An extractor checked
against a number the extractor produced proves nothing.
· Unknown{ExtractorMiss}, exit 2 — a new element of the verdict lattice. An
extractor that silently saw the wrong corpus reports the same "no
violations" as one that saw all of it.
· the 7 records migrated to v2 and back-filled with comparator {kind: self,
reason}, in this commit, as the row requires.
THE DENOMINATOR IS 7, NOT 8. The #3574 receipt is in PR #3575, still open; it is
not on main. Measured: 113 files under evidence/parity, 7 parity records, 0 with
a comparator. #3575 bumps it to 8 when it lands — this row's own falsifier on its
first real use.
check_parity_receipt.sh IS NOT TOUCHED, and that is the finding, not an omission.
It validates the THROUGHPUT family (instrument, protocol_ref, lanes[],
decode_tok_per_sec, the #2696 cross-class defect); a logit record has never
carried one of those keys. Folding it in — as item 6 asked — would have deleted
the #2696 validator from a family nobody was watching. One validator per artifact
family, and the discriminator is the artifact's required keys, never its filename.
THE BACK-FILL IS A RELABEL. `raw` is the original apr parity --json document key
for key; every envelope field is quoted from committed evidence named in each
record's provenance.record. model_sha256 is carried only by the one record that
measured it: hashing the files today and attaching that to a receipt about
2026-09-06 would be a claim about a different world wearing a witness's clothes.
One derived field was wrong first time and is worth recording: result.verdict
copied raw.parity — apr's own per-position flag — which says PASS for the two
1.5B cells their own RECORD.md calls RED. It now resolves the threshold from
thresholds.yaml and reproduces all seven readings the RECORD.md files state,
both REDs included. No threshold is ever typed into a shape.
NOTHING IS ARMED. armed_shapes lives in lint-baseline.json, a shared file this
row may not touch (decision 7); the shapes are computed and reported, as
ladder-green was at ONT-4c1. Arming is a follow-up with the label.
CONTROLS, both directions: 7 violations with the comparator stripped from all
seven → 0 as committed; exactly 1 for a single plant, naming focus node and
property; widening sh:in to accept `oracle` turns ont4c3_parity_receipts RED, and
mutating only the real contract turns the fixture-drift test RED; an unmigrated
record declines at exit 2 naming the file; 2 receipts against a denominator of 1
declines naming both numbers; pass / fail / decline are 0 / 1 / 2.
cargo test -p aprender-contracts --lib ontology::extract::parity_receipt 10 ok
cargo test -p aprender-contracts-cli --test ont4c3_parity_receipts 8 ok
bash scripts/parity_receipt_denominator.sh --self-test 4 ok
pv lint contracts --gate {sigma,relations,shapes} Pass, 0 violations
pv extract contracts --check rc 0
ONT-4c3 is NOT bound in the ONT-001 ledger: that ledger is paiml/infra's
paiml-ontology.md, where v4.8 still defines ONT-4c3 as KERNEL receipts. The
re-scope is an infra PR, not this one — raised with the cop rather than left as a
checked box. Receipt: docs/audits/impl-PMAT-3577-receipt.md.
Refs #3577, #3576, #3269, #3575, #3567
Pmat-Ticket: PMAT-3577
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…pposite is a defect `ExtractorMissing` already meant the opposite thing: an extractor that does not exist. `ExtractorMiss` would have sat beside it in the same 18-element lattice, one letter apart, with the shorter a PREFIX of the longer — `grep ExtractorMiss` matches both, and any substring test over the reasons merges them silently. That is the defect class this repo keeps paying for (#3573 today), so the name now says what happened: the extractor RAN and read a corpus the tree does not declare. Renamed before it reached main, when a rename is a sed and not a migration. Caught in review by the cop. Refs #3577, #3573 Pmat-Ticket: PMAT-3577 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ft in a thread Two repos holding different definitions of one identifier is a row, not a flag in a message: nothing collides where a tool would see it, so it collides in a person's head months later when they implement the ledger's meaning and find their correct work unusable. Refs #3577, paiml/infra#814 Pmat-Ticket: PMAT-3577 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ntinel set so the required field manufactures nothing `removed_by` had 0 occurrences tree-wide, no schema and no validator. Every refusal #3597 writes would have minted an unenforced convention, and a tree full of consistent-looking `removed_by:` lines reads as validated when it is decoration. PHASE 0 — ITS OWN CONTRACT, AND NOT BECAUSE IT IS EASIER TO WRITE Option B is refused on a fact: `parity-receipt-v1` DOES NOT EXIST AT HEAD. It is in unmerged #3600, and there it is deliberately the RETIRED layout with no shape and no instances. A live validated field on a superseded contract with zero focus nodes is a field nothing can carry. Independently, by the discriminator this tree has now used twice — an artifact family is named by its REQUIRED KEYS, never by its filename — a parity receipt requires host, backend, comparator, threshold_source and per-position metrics; a refusal requires a verb, a reason, an exit code and removed_by. They share no required key. A THIRD option was considered and refused, and it is the more attractive one: put removed_by on apr-cli-commands-v1.yaml, where the verb already IS the focus node and the universe is already the trustworthy 111. Refused because the registry is the UNIVERSE and #3597's method is to DIFF the registry against the buckets. If the buckets live in the registry, the denominator and the numerator are the same artifact and the diff is vacuous by construction. The registry says what EXISTS; a refusal says what was TRIED. Keeping them apart is what lets that diff be a real diff. THE FORCED-BINDING TRAP, AND WHY minCount 1 IS SAFE HERE A required field with no escape manufactures false data: `pv validate` requires kani_harnesses so authors fabricate one, and apex#57 had lean_theorem copied verbatim into twelve contracts resolving to nothing, gate green throughout. The escape is a closed sentinel set, so "there is legitimately nothing here" is SAYABLE and still CHECKABLE: v<major>.<minor> the release that removes it never refused permanently by design unscheduled a defect with no release chosen tbd, soon, n/a, pending, a bare `0.70`, a patch-level `v0.70.1` and a git sha are all RED. A version rather than a sha because a refusal answers "which release do I need?" and a sha is precise about the tree and silent about the boundary; the sha form is made INVALID rather than discouraged, because a shape that permits two spellings gets both. BOTH ARMS, and the second is the point: 9 cases over 8 fixtures. refusal-ok carries all three declared forms and passes with 3 focus nodes; refusal-undeclared-sentinel plants the PLAUSIBLE `tbd` and must go red. Mutation proved red-capable: widening the pattern to accept tbd fails exactly a_plausible_but_undeclared_sentinel_is_refused and accept_and_refuse_are_distinct_answers, and restoring makes 9/9 green. Fixtures carry the real contract byte for byte, so widening it without them is caught too. No new extractor: entity {type: json, ref} + vocabulary is the existing machinery for "validate this document", and a second reader for one more family is the thing this tree keeps filing against. done_when 6 — the interim re-check list is EMPTY: `removed_by` still has 0 occurrences at HEAD, so no refusal written under the interim needs revisiting. The ledger is seeded with the one refusal already measured (`apr bench` refuses qwen35) so the shape has a real focus node instead of passing vacuously over an empty list. WHICH verbs land there is #3597's bucket, not this row's. Refs #3605, #3597, #3600, #3080 Pmat-Ticket: PMAT-3605 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…robe reads ABSENT otherwise The spec's probe asks `by_entity_type["parity-receipt"] == 7`. Measured on this branch before the change: the map carried pv-contract, gguf, apr-model, code and lean, and NO parity-receipt key. The seven records were there — by_shape showed parity-receipt-complete=7 — but the entity-type map did not carry them, so the probe would have read ABSENT. AN ABSENT KEY IS NOT ZERO. A consumer treating it as one measures nothing and calls it a pass — the same shape as #3610, one map over. Registering the entity type in Sigma was not enough; it has to be counted where the probe looks. A test now asserts the key exists AND that it equals the shape's own focus-node count, so the two numbers cannot drift apart. Refs #3577, #3610, paiml/infra#831 Pmat-Ticket: PMAT-3577 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eipt # Conflicts: # contracts/census.json
…der-contract # Conflicts: # contracts/census.json
… — the shared target dir served another branch's binary The merge commit's contracts.nt was 107 triples short: every parity-receipt and parity-comparator node was missing, because `cargo metadata`'s target_directory is shared across worktrees and the pv on PATH had been built from a different branch minutes earlier. AND `pv extract contracts --check` PASSED ON IT, because the check re-derives the graph with the same binary. A stale tool comparing an artifact against its own re-derivation agrees with itself about nothing being there — the derived file and the checker were wrong in the same direction, which is the only way that gate can fail to fire. Rebuilt with CARGO_TARGET_DIR pinned to this worktree; the 107 triples return and by_entity_type[parity-receipt] reads 7 rather than absent. Refs #3577 Pmat-Ticket: PMAT-3577 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The registry is derived from the tree and drifts the moment a test that reads the tree is added without listing it. Each of the three new tests failed this guard on its OWN branch — not a shared commit, as first read. Pmat-Ticket: PMAT-3598 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The registry is derived from the tree and drifts the moment a test that reads the tree is added without listing it. Each of the three new tests failed this guard on its OWN branch — not a shared commit, as first read. Pmat-Ticket: PMAT-3598 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…release judge I asked "who else reads this?" for the <unk> assumption and did not ask it for my own migration. Two consumers read the legacy top-level metrics: crates/apr-cli/src/commands/parity_admission.rs -> mac-check RED scripts/check_model_parity.sh -> C14, the RELEASE judge The second is the one that matters: C14 is the pre-publish dogfood's parity gate, and on the migrated records it reported "no per-position metrics in the output" for a record that is fine. The migration would have taken the release gate down. THE RULE, STATED ONCE IN BOTH READERS: a v2 receipt EMBEDS the raw `apr parity --json` document under `raw`, so look inside the envelope when there is one. A fresh `apr parity` run is the raw document itself and carries the readings at the top level. Those are two different INPUTS — a tool's output and an archived receipt quoting it — not two spellings of one, which is the distinction that makes this a rule rather than the permissiveness #3613 refuses. The self-test's fixture builder read the same way, so the must-RED twin was being built from a KeyError and that control could not have fired. Verified: 7B sentinel PASS, 1.5B sentinel RED (unchanged from pre-migration), check_model_parity.sh --self-test 26/26, parity_admission 19 tests. Refs #3600, #3577 Pmat-Ticket: PMAT-3577 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…on, device) and leaves a receipt f2_validate_qwen35 proves the CUDA hybrid forward against a 64-position CPU reference before it will serve a token, on EVERY apr run. Measured (#3598 row 1): 67 % of a 14 s time-to-first-token, 90-93 % of it the CPU forward. The guard is right to exist and wrong to run per call. Now: the first run of a (model sha256, apr version, device) triple validates and writes a receipt; a later run whose triple matches reads it and skips the forward; `apr run --revalidate` forces a fresh run and rewrites it. MEASURED HERE, RTX 4090, Qwen3.5-4B-Q4_K_M, the 144-word row, --max-tokens 1, GPU occupancy recorded before every run, binary built from this tree under a PINNED target dir (the shared one handed me another worktree's binary first): --revalidate, warm cache 17.18 s wall guard 9,747 ms on 65 positions receipt hit, warm cache 7.39 s wall guard 0 ms sha256 1,173 ms -9.8 s wall; guard 9,747 -> 0; the key costs 1.17 s/run (2.74 GB at 2.3 GB/s) and is printed separately so it cannot hide in either number. THE RECEIPT IS THE VALIDATION, WHICH IS WHY IT IS STRICT. Every path that is not "three keys match" validates, and the three planted-receipt falsifiers were run END TO END in the real binary, not only as unit tests: wrong model sha256 -> re-validated: "receipt is for model bbbb…, this file is 00fe…" wrong apr version -> re-validated: "receipt written by apr 0.61.0, this is 0.68.2" wrong device -> re-validated: "receipt written for NVIDIA GB10, this device is …4090" corrupt file -> re-validated: "receipt unreadable (…: not a receipt)" missing file -> re-validated: "no receipt for this model" Absence is never consent, and absence and unreadability are told apart. A RECEIPT IS WRITTEN ONLY AFTER A VALIDATION THAT JUDGED SOMETHING. The guard has three early exits that let the GPU serve without comparing a position — SKIP_PARITY_GATE=1, a probe under two tokens, a CPU reference that would not run. f2_validate_qwen35 now returns F2Verdict {Accepted{positions_judged}, Rejected, NotJudged} instead of bool, and only Accepted writes; otherwise a one-token prompt would "validate" the triple for every prompt after it. The decision table (f2_receipt.rs) is pure and CUDA-free, so its 13 tests run on every build. --revalidate reaches the guard through the same env seam the guard already reads SKIP_PARITY_GATE from, rather than a 38th positional parameter on run_entry::run and six forward signatures #3606 is changing. done_when 5 is partial and says so: [source=receipt|fresh] is on the guard's stderr line; the `apr run --json` field lands with #3606's StageTimings, and F2Outcome{source, validate_ms, sha256_ms, receipt_path} is returned to the call site for exactly that. #3606 and this PR both edit f2_validate_qwen35's return path; whichever lands second reconciles ~10 lines, and #3606's lane is told. Evidence: evidence/perf/3604/MEASUREMENT.md + the stderr of all nine runs. Refs #3604, #3596, #3598, #3606 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pmat work status PMAT-3604 refused ('Item not found'): #3604 was minted as a
GitHub issue with a done_when but never as a roadmap entry, and the quorum
script hard-requires the work item. Fragment + aggregate, nothing else.
Refs #3604, #3634
ont-delta: none — a roadmap entry; no ontology entity, shape, reason or resolution
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…had no caller (#3602) `apr run --gpu` on a model whose GPU attempt is rejected at runtime printed a result and exited 0. Measured on lambda (RTX 4090 sm_89, apr 0.68.2 e6f77c9, qwen2.5-coder-0.5b-instruct-q4_k_m): exit=0 stderr: warning: GPU output diverges from CPU at position 1 (cosine 0.4153) stdout: { "used_gpu": false, "inference_time_ms": 33646.13 } 33.6 seconds on the CPU, reported as a successful --gpu run, with `used_gpu: false` as the only signal — the same value a deliberate CPU run reports. The decision was already made, recorded and unit-tested. `registry::after_generation` implements it (R-0b, #3002/#3042): a FORCED accelerator that fell to CPU is a refusal, a DEFAULT selection that fell to CPU gets a corrective line. A git grep found its only callers were its own tests. `registry::announce` and `registry::parity_line` are in the same state, which is why a real run prints zero `selected:` and zero `parity:` lines. So this is not a new policy. It is the recorded one, reached from `apr run`: - `dispatch.rs` classifies the request ONCE via `registry::Request::wanted()` rather than re-deriving "forced" — two spellings of one rule drift apart. - `run_entry::run` calls `reconcile_accelerator` before any success output. - `--json` gains a `backend` object: `requested` / `ran` / `fell_back`, because `used_gpu: false` alone collapses "ran on CPU deliberately" with "asked for the GPU and was refused it". `accel::tests::every_accelerator_surface_calls_the_refusal` passed throughout: it guards `ensure_available`, the BUILD-time refusal, not the RUNTIME one. A guard over one of two refusals reads as coverage of both. Falsifier, both directions (`run_tests_accel_reconcile.rs`, 6 cases). Planting the pre-fix behaviour (`reconcile_accelerator` → `Ok(None)`) turns `a_forced_accelerator_that_ran_on_cpu_is_refused` and `the_json_distinguishes_a_deliberate_cpu_run_from_a_rejected_gpu_run` RED while `a_forced_accelerator_that_actually_ran_on_gpu_says_nothing` stays green — so the tests discriminate rather than just failing. `used_gpu: None` is Unknown, not a fallback: refusing on it would make every non-reporting path a hard error. NOT included, deliberately: the rejection's REASON (cosine, position) is on stderr but not in the JSON. It is produced inside realizar's F2 gate and no channel carries it to the CLI; adding one is a #3606-shaped follow-up, not something to approximate with a guess here. Refs #3602, #3483 Pmat-Ticket: PMAT-3602
…annot reproduce QE2E-INV-001 could not be judged because nothing in the tree held a MEASURED Qwen3.5 tensor inventory to judge against. This adds one: the 320 tensors of ~/models/Qwen3.5-0.8B-Q4_K_M.gguf (sha256 bd258782...dc517), read straight from the GGUF header rather than from a model card. It already falsifies the current arithmetic. Dense/GQA accounting applied to that file gives 644,400,128 against a measured 752,393,024 — short by 107,992,896, 14.4% of the model, because 18 of the 24 layers are Gated DeltaNet and no term here counts their conv, gate, state or output projections. Two shapes in the file are also not what dense accounting predicts, and both are pinned: attn_q is [1024, 4096] = 2 * num_heads * head_dim (the q projection emits the attention output gate alongside the query; attn_output [2048, 1024] confirms num_heads * head_dim = 2048), and attn_q_norm/attn_k_norm are present at head_dim. The file is also TIED — it has no output.weight. Refs #3346 Pmat-Ticket: PMAT-3346 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… a hybrid model can be counted contracts/model-families/qwen3_5.yaml declares inner_size, state_size, conv_kernel, group_count and full_attention_interval under constraints:, and ModelConstraints carried none of them. A Gated DeltaNet layer's parameters live entirely in those dimensions, so every consumer of the descriptor counted Qwen3.5 as if three quarters of its layers did not exist. Carried through as ModelConstraints::deltanet: Option<DeltaNetShape> — the runtime YAML loader (parsing.rs) and the compiled-in registry (build_parsing.rs + build_codegen.rs) both populate it, and FALSIFY-MF-QWEN35-010 pins that the declared values survive the trip and that no other family acquires a shape it never declared. qwen3_5.yaml is the only descriptor with these keys, so every other family keeps byte-identical accounting. model_arithmetic gains gated_deltanet_layer_params (one term per GGUF tensor: attn_qkv, attn_gate, ssm_conv1d, ssm_alpha/beta, ssm_a, ssm_dt.bias, ssm_norm, ssm_out) and hybrid_layers (the interleaved schedule). attention_layer_params gained two terms the real file has and dense accounting did not model: the gated q projection (2*n_h*d_k) and the q/k norm vectors. Falsified against a real model, not against itself: fed the 0.8B configuration, the equation now reproduces the 320-tensor inventory of Qwen3.5-0.8B-Q4_K_M.gguf EXACTLY — 752,393,024, both layer kinds matching tensor for tensor. QE2E-INV-001 is still NOT asserted, and no range was widened to make it pass. The 9b descriptor now yields 8,344,907,136, up from 8,208,519,168 but still 0.655B below [9.0B, 9.2B]. The remaining gap looks like descriptor drift rather than missing arithmetic: 9b keeps inner_size 2048 — the value the 0.8B uses at hidden_dim 1024 — while quadrupling hidden_dim, and its group_count 8 fails 8 * 128 == 2048, a consistency the measured 0.8B satisfies at 16 * 128. Only a real Qwen3.5-9B file can settle it; none is on this box. Refs #3346, #3347 Pmat-Ticket: PMAT-3346 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…osed The note said the obligation was undischarged because ModelConstraints does not carry the DeltaNet shape keys. It does now, and the 0.8B count reproduces the real GGUF exactly. What actually blocks the obligation is descriptor drift at the 9b variant. Pmat-Ticket: PMAT-3346 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd pv extract shrank the graph by 356 triples instead of refusing d3cc76f rewrote the note on the QE2E-INV-001 binding and dropped the trailing `"` — contracts/binding.yaml stopped being valid YAML at line 764 (`found unexpected end of stream`). Nothing in the PR noticed because `pv extract contracts` does not refuse a binding registry that will not parse: it emitted a graph with 15,244 triples where main has 15,600 — every bound symbol AFTER the broken entry (prune::run, distill::run, harness_ir::*, ptx_explain::run, …) silently gone — and `--check` would have agreed with itself. Found while regenerating the derivative for this adoption, by the drop, not by any gate. One character. With it, binding.yaml parses (156 entries, same as main) and the extraction is byte-identical to main's committed contracts.nt, so this PR owes no graph change after all. Refs #3346, #3350 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…gfood G2.1, ONT ratchet) RED 1: check_dogfood_coverage.sh G2.1 freshness. #3604 (69aff21) inserted 4 lines (`--revalidate`) after line 138 of crates/apr-cli/src/commands_enum.rs, and the ledger was not touched. 55 rows carry 64 citations of that file. All of them were re-derived: 42 at lines >= 139 shifted by +4, and 22 at <= 138 are unchanged. For every citation, the script asserted that the cited line's text at HEAD equals the old line's text at 79a3af7, and it asserted that the diff is a pure +4 insertion at 138 before touching anything. `measured_commit` does NOT move. The contract's own rule for a partial re-audit (the PERF-046/#2742 precedent) says so, and a batch-branch SHA would not survive the squash merge. The partial re-audit is recorded beside that rule. bash scripts/check_dogfood_coverage.sh G2.1 freshness PASS 78 cited evidence files, 1 changed by this branch G2.2 reconciliation PASS no row lost (837 comparand rows all present) DOGFOOD COVERAGE GATE: PASS RED 2: check_ont_ratchet.sh "contracts_anchored rose 3 -> 6 while consumer_present=false" on gx10-pool2, whose bare pv is the fleet-clobbered one with no `census` (root cause #3679, 0.70, not fixed here). This is a restamp through the sanctioned path, with the repo-pinned pv first on PATH: . scripts/pv_bin.sh && PATH="$(dirname "$PV"):$PATH" make ont-ratchet PV=target/debug/pv, pv 0.68.2 (aprender provable-contracts verifier), census present stamp ont: anchored 3->6, shaped 3->5, total 1852->1881, entity_types 9->10, extractors 6->7, shapes_unarmed 3->6, bindable 297 (unchanged) bash scripts/check_ont_ratchet.sh --check PASS (pinned pv) runner condition, a stub pv with no census on PATH: new stamp --check rc=0 PASS (consumer_present=false, 6 == 6) old stamp --check rc=1 "FAIL contracts_anchored rose 3 -> 6" (control) Local gates, all at this commit: bash scripts/guard_tree.sh --no-cargo 76 checks, 0 failed bash scripts/check_complexity_ratchet.sh rc=0 cargo fmt --all -- --check rc=0 pv validate contracts/apr-dogfood-coverage-v1.yaml 0 errors cargo test -p aprender-contracts --lib 1684 passed Refs #3669 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…2 adds three shapes, as the test predicted workspace-test shard 1/3 (gx10-pool2, job 106269529690) failed on ont4b_shapes_gate::the_tracked_repo_graph_is_fresh: `shapes_n == 6` on a folded tree that has 9. The test's own comment predicted this: "a sibling PR that also adds a shape (#3600's parity-receipt-v2) will need the number raised again at merge -- that is the ratchet working". The count stays hardcoded and all nine shapes are named in the assertion message. ./target/debug/pv extract contracts --check (pv 0.68.2, built from this tree) {"triples": 15851, "shapes_n": 9, "written": [], "check": []} rc=0 shapes.ttl: ont-shapes-v1, ladder-measured, ladder-green, bound-symbols-resolve, lean-statements-grounded, refusal-receipt-v1, parity-receipt-complete, parity-comparator-self, parity-comparator-oracle (the last three from contracts/parity-receipt-v2.yaml, #3600) cargo test -p aprender-contracts-cli --test ont4b_shapes_gate 11 passed Refs #3669 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 tasks
…ntainer runners — a git failure is ENV exit 2 naming git (PMAT-3682)
workspace-test shard 3/3 (gx10-pool1, job 106269529629) failed
ont4c3_parity_receipts::the_committed_tree_agrees_with_its_own_denominator:
"EXPECTED_RECEIPTS says 7; the tree holds 0". On the docker runners git
refuses the bind-mounted checkout ("detected dubious ownership", exit 128).
The listing `(cd "$root" && git ls-files … 2>/dev/null | sort -u)` ran inside
a process substitution, so both git's message and its exit status were
discarded, and the loop counted an empty universe as zero.
- The listing is now `git -c safe.directory="$root" -C "$root" ls-files …`
captured into a variable, with git's stderr kept.
- A git failure returns ENV 2 with a message naming git, never a count, and
`verify` propagates it (`|| return $?`, not `|| return 1`).
- Self-test row: a PATH `git` stub that exits 128 with the dubious-ownership
message must give exit 2 and a message naming git, and must NOT say "holds 0".
Proof:
bash scripts/parity_receipt_denominator.sh --self-test 5/5 ok, rc=0
bash scripts/parity_receipt_denominator.sh PASS 7 receipt(s) ... says 7
MUTANT (pre-fix count_and_check from 55b123f) --self-test rc=1
"FAIL git refused and the result was exit 1: FAIL evidence/parity/
EXPECTED_RECEIPTS says 2; the tree holds 0." <- CI's exact shape
cargo test -p aprender-contracts-cli --test ont4c3_parity_receipts 9 passed
bashrs lint: 0 errors · guard_tree --no-cargo 76 checks, 0 failed (incl.
check_no_pipe_into_grep_q) · complexity rc=0 · fmt rc=0
Sibling sweep (git grep 'git ls-files' and git calls with 2>/dev/null in
scripts/ and crates/*/tests/), on lines the batch ADDED:
ci_test_tier.sh:180, :252 fail closed (not docs-only / tier=full)
lib_baseline_ratchet.sh:300, :302 fail closed, but misattribute the cause
("this branch WROTE it") -- filed
release/lib_release_params.sh:39 not a test path; a git failure silently
falls back to the tree root -- filed
No other zero-count site. Pre-existing sites outside the batch are not in
this change.
Refs #3682 #3669 #3581
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 21, 2026
Lane 1 (pro-high) measured in the judged worktree. Lane 2 (pro-low) is a static PASS. Lane 3 (3.6-flash) worked in stale scratch clones and is VOID under the cop's rule (paiml-implement#317); its seat goes to the non-author cross-inspection. Refs #3682 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Member
Author
|
Seat-fill, PMAT-3682 (lane 3 VOID: its store references 0 of the judged worktree, 33 of The mutant reproduces the shard-3/3 symptom on gx10-pool1 exactly. Seats: lane 1 VALID (measured in-tree), lane 2 static PASS, lane 3 filled by this check. 3 of 3. — cop |
This was referenced Sep 21, 2026
This was referenced Sep 21, 2026
Merged
noahgift
added a commit
that referenced
this pull request
Sep 24, 2026
… stale on main #4095 rewrites one cli.rs doc comment line-for-line, which trips G2.1. Rather than touch the ledger, every pv/apr pv row was re-pointed at its own Commands::<Variant> declaration and read back; 22 were exact, 54 had drifted +6/+12 since 2026-08-22. measured_commit does not move (partial re-audit, same rule as #2742/#3669). Pmat-Ticket: PMAT-4080 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 25, 2026
guyernest
pushed a commit
to guyernest/aprender
that referenced
this pull request
Sep 29, 2026
…paiml#3707) * fix(guard): check_pr_review_counts.sh was RED on main and ran nowhere — a comment classified it out of guard-tree (PMAT-3646) PRQ-013 (paiml#2985) deleted ci.yml's receipt job and, with it, the step names that stated six of the counts this guard checks. The guard went RED ("6 row(s) disagree with the tree") and nobody saw it, because a comment at :196 naming the build tool matched guard_tree.sh's CARGO_RE. That dropped the guard from guard-tree's --no-cargo run, and guard-cargo never names it. - The site table drops the five ci.yml rows whose sentences left with the job (the roadmap records them as a STATED LOSS). ci.yml's "43-row" goes from 2 sites to 1. The bats_tests derivation is removed: no file states that count any more, and a derivation with no site checks nothing. check_pr_review_wiring.sh's "43-row" joins the table as a new site, with a self-test row (stale-wiring-rows). - The :196 comment no longer names the tool, so `guard_tree.sh --dry-run --no-cargo` prints `run: scripts/check_pr_review_counts.sh`. Restoring the old wording moves it back to --cargo-only (mutation-verified). - In --list mode, mutate-guard.sh and mutate_quorum_arm.sh now require only awk. They demanded bats/jq/minisign/check-jsonschema before printing a catalogue that uses none of them, so the counts derivation exited ENV=2 on a runner without them. Reproduced under a PATH without those four: rc=2 before, rc=0 after. guard-tree runs on the clean-room hosts, including the gx10/yoga docker runners, so wiring the guard without this would have swapped a dark guard for a red job. The real sweeps still refuse to run without the tools (rc=1). Verified: check rc=0 (22 rows); --self-test 16/16; guard_tree_test.sh 23/23; check_pr_review_wiring.sh PASS, self-test 11/11; check_guards_are_wired.sh PASS, self-test 7/7. Item 2 of paiml#3646 needs no code. check_pr_review_receipt.sh is invoked with a receipt by check_pr_review_arm4.sh (A3 and A4), which pr-review-quorum.yml runs. The ledger line paiml#3647 adds for this guard is deleted here once paiml#3647 lands (the ledger is shrink-only). Refs paiml#3646 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3076: F16/BF16 CPU matvec reads the row with AVX2+F16C+FMA instead of a fn pointer per weight float16_matmul called the decode through a `fn(u16) -> f32` pointer for every element: an indirect call plus a bounds check per weight, which nothing could inline or vectorise. On a Threadripper 7960X, Qwen2.5-0.5B-Instruct-f16 decoded at ~10 tok/s; BRICK-PROFILE put ~99% of the time in the F16 matvecs, and the 272 MB LM head read at ~27 GB/s. Each row is now one float16_row_dot over its bytes. x86_64 CPUs with avx2+fma (+f16c for F16) take a fused kernel, runtime-detected: vcvtph2ps, or u16->u32 <<16 for BF16, into four FMA accumulators. Everything else takes a chunked decode-then-dot the compiler vectorises. The loop structure is unchanged, and a row that runs past the end of the buffer is dotted over the whole elements that exist, as the per-element bounds check did. Tests: parity against an f64 reference at 16 lengths that reach every loop and tail (both kinds, and the portable path on its own); NaN, inf and subnormals at every lane position; and float16_matmul against a transcription of the pre-fix loop, including a truncated buffer with an odd trailing byte. Mutation-checked: skipping the tail, BF16 <<15, F16 routed to the BF16 kernel, dropping the portable remainder, and removing the end clamp each turn tests RED. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(release): batching is the default merge path — scripts/batch_fold.sh + APR-RELEASE-001 §13 (paiml#3673) Refs paiml#3673 paiml#3669 paiml#3650 paiml-implement#317 Operator 2026-09-21: "we are arbitrarily going slow because each PR adds time, but in most cases 80% of PR can be batched." Code handed off by the cop (aprender-04) as an uncommitted draft; rewritten here. scripts/batch_fold.sh -- fold receipted PR branches into release/<train>-batch, --no-ff, in order. It resolves ONLY the generated set: roadmap.yaml, census.json, contracts.nt, shapes.ttl (pv extract writes it), and the README CONTRACT_COUNT blocks. Every other conflict aborts that branch: SKIP + paths. - README is merged three-way with the counts normalised out (git merge-file), so a prose conflict is a SKIP. The draft took README wholesale with --theirs, which silently dropped the batch side's prose. - A CLEAN fold that touches the generated set marks it stale: two PRs that each add a contract merge textually and the census is then off. Stale without --regen ends `REGEN REQUIRED`, exit 3, never silent. - --regen regenerates once with the tree-built pv (pv_bin.sh), asserts the three fixed points (make roadmap-aggregate-check, pv extract --check, readme_sync --check), refuses writes outside the set, commits. - refuses a dirty batch worktree; exit 0/1/2/3 documented. scripts/check_batch_fold.sh -- the case table, 21 rows over throwaway repos with stub pv/make/readme_sync, plus --self-test with 4 mutants, each RED: README wholly generated (the draft's rule), every conflict taken, a clean fold never stale, a failed fixed point ignored. It is a check_*.sh on purpose: the draft's own `--self-test)` case made check_guards_are_wired.sh RED ("NEW: batch_fold.sh", measured), and guard_tree.sh --no-cargo dispatches this file with no workflow edit. The draft, judged by this table: 13 of 21 rows RED (3 of them only on output wording). APR-RELEASE-001: - §13 (new): roles (author stops at the receipt; never arms, never merges main to stay current), assembly, the generated set, left-out rules, what needs a new quorum, push/disarm, body/arm/close, a red batch, what it costs (a SQUASH queue lands a batch as ONE commit: bisect on main stops at the batch, so the batch branch is kept until the next tag), falsifiers FX-B1..3. - hard rule 9 AMENDED. It read "No hand-squash of batches ... each PR lands as its own commit ... never hand-bisected", which the new default contradicts. The old text is kept verbatim as superseded, with the reason: the queue it described (group size 8) was not the queue we had (SQUASH, maximumEntriesToMerge=1, measured on paiml#3658). - §8 stop "a hand-squashed batch" -> "a batch assembled by hand instead of scripts/batch_fold.sh (§13)". - revision note 2026-09-21. Gates: check_batch_fold 21/21 + self-test 4/4; bashrs 0 errors on both files; bashrs-gate PASS; guards-are-wired PASS; guard_tree dispatches it; guard_tree_test 23/0; pipe-into-grep-q at ceiling; hardcoded-paths +0; apr pinned; no-timing-in-required; check_ci_unwedge and check_ont_ratchet (read the spec) green; roadmap aggregate + sorted/unique/additive; fmt, deny, aprender-contracts --lib 1666. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3673: AD-04 quorum receipt for paiml#3674 — 3/3 PASS, all three lanes VOID under the paiml#317 rule Lanes gemini-3.1-pro-high / gemini-3.1-pro-low / gemini-3.6-flash-high at eebd642 (diff_sha256 33accb56…), PASS/PASS/PASS. Per APR-RELEASE-001 §13.4 (and the cop's rule while paiml-implement#317 is open), none of them counts as a measurement: no lane's agy conversation store references this worktree. lane 1 (cf256148): Cwd ~/.gemini/antigravity-cli/scratch x18, /tmp x6; rebuilt batch_fold.sh from the diff by heredoc lane 2 (bdf62808): no commands; a static read of the inline diff lane 3 (059ef063): Cwd scratch x112, scratch/wt-aprender-main x18, /tmp/tmp.* x12; ran copies of the two scripts under /tmp The seat is filled by a NON-author's measured check at the PR head, posted on paiml#3674. Refs paiml#3673 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3076: the greedy-parity gate against pinned llama.cpp, and the measured receipts Round-1 quorum (3/3 FAIL, correctly): the acceptance quoted in the fragment requires "a greedy-parity gate against llama.cpp on an unquantized ≤1B GGUF", and the diff had none. It also reported tok/s with nothing backing it. scripts/check_float16_greedy_parity.sh runs the pinned apr and llama-completion at the llama_pin.toml build_commit (the gate refuses any other build), greedy and CPU, over six prompts, and compares the trimmed text. `apr run` templates an instruct prompt twice (paiml#3672), so the models are copies with tokenizer.chat_template removed; the gate refuses a model apr would template. With --baseline <pre-change apr>, a divergence the old binary reproduces byte-for-byte is KNOWN rather than FAIL. Receipts in evidence/pmat-3076-f16-bf16-matvec/: - Qwen2.5-0.5B F16: 6/6 identical to llama.cpp. - Qwen3-0.6B BF16: 5/6 identical, plus 1 KNOWN (char 83, which the pre-change binary reproduces exactly). - Negative control: a deliberately broken build (F16 sent to the BF16 kernel, BF16 <<15) FAILs 12/12 at char 0 with rc 1. - findings.json: decode A/B with raw per-rep numbers, model sha256, host and binaries. F16 goes 92.7 -> 47.2 ms/token at 48 threads, and 6.2 -> 68.6 tok/s at RAYON_NUM_THREADS=8. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3076: the parity gate is a host-side gate, not a tree guard; it measures agreement only scripts/guard_tree.sh runs every check_*.sh bare inside a required job. The parity gate needs the models and the pinned llama.cpp, so run bare it refused (rc 2), a red row for a guard that was never given its input. It is now scripts/float16_greedy_parity.sh, alongside the other host-side probes, and it is not wired into CI: that needs models on a runner and a workflow edit. check_no_competing_harnesses.sh correctly classed the gate as a second benchmark harness, because it drove apr/llama.cpp AND recorded tok/s. The rate capture is removed. Throughput lives in the measured A/B (findings.json), and the gate judges agreement only. Receipts regenerated with the final script: F16 6/6, BF16 5/6 + 1 KNOWN, negative control 12/12 FAIL. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(tokenizer): the unknown token is the model's, not BPE's — Qwen3.5 serves; a model with no vocabulary refuses by name (PMAT-3609) BPETokenizer::new required `<unk>` in every vocabulary. Qwen3.5 has none: it declares tokenizer.ggml.eos_token_id and no unknown_token_id key at all. So `apr serve` refused a real, complete vocabulary, while a GGUF/APR with NO vocabulary loaded, because paimlGH-226's placeholder synthesised `token{i}` with `<unk>` in slot 0. The less-specified input was the one accepted. This follows the operator's three constraints (paiml#3609, 2026-09-20) and the cop's rulings (2026-09-21): - The unknown token is Option: `new(vocab, merges, impl Into<Option<&str>>)`. A named token must exist. With no unknown token, CONSTRUCTION refuses, naming the first byte that has neither a `<0xNN>` token nor a byte-level glyph token (with_merges also requires every glyph), so encode never meets an unencodable byte: nothing is dropped and nothing is synthesised. With an unknown token present, encode is unchanged. - No vocabulary refuses by name at all 13 placeholder sites (apr-cli serve x7, aprender-serve cli x6). - apr serve's tokenizer.json path uses the DECLARED model.unk_token. The vocabulary-only AppState constructors use `vocabulary_unk_token` (the vocabulary's own `<unk>`, else none), the by-name interim paiml#3675 replaces. - Prose: the paimlGH-226 comment, the book's api-server example and examples/model_cache.rs no longer teach `<unk>` as a requirement. - Fixtures: REAL Qwen3.5-0.8B and TinyLlama GGUF headers, every key and scalar verbatim, only the per-token arrays sliced (generate.py, MANIFEST.json with each source header's sha256). They use the `.gguf-header` extension because .gitignore ignores `*.gguf`. Verified: aprender-serve lib 15892 passed (tokenizer 215, 9 new); apr-cli lib 7283 passed; clippy -D warnings clean on both; cargo check with cuda; contracts lib 1666; deny ok. MUTANT: restoring the unconditional `get("<unk>").ok_or_else(..)` turns the real-Qwen3.5 row RED with the original error "Unknown token '<unk>' not in vocabulary". Found and filed separately: paiml#3677 (greedy encode reads Latin-1 characters as byte-level glyph tokens, so `é` decodes to U+FFFD). Closes paiml#3609 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3076: wire the parity gate. The case table runs on every PR, the model-backed run at pre-publish Cop ruling, no workflow edit: - scripts/float16_parity_lib.sh holds the comparison helpers (first_diff, trim, json_str). It is a sourced library and option-neutral, and check_sourced_libs_option_neutral covers it: injecting `set -e` turns that guard RED. It sits at scripts/ top level because the guard resolves sourced basenames there, and a copy under scripts/lib/ was measured as unchecked. - scripts/check_float16_greedy_parity.sh is the helpers' case table. guard_tree runs it on every PR (`--dry-run` lists it), and check_guards_are_wired PASSes again (the unwired count is back to 3). - scripts/float16_greedy_parity.sh, run with no arguments, is release mode, declared in Cargo.toml [package.metadata.dogfood]. For each committed receipt it runs the model that receipt names, checks it against the receipt's sha256, and uses the receipt as the baseline (a divergence is KNOWN only if apr's text is byte-identical to the recorded row). A model absent from the host is a FAIL. It has no --self-test arm of its own; the case table is the PR-time guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3076: say in the declaration who runs it; round 4's two FAILs read the gates list as a PR-path list Round 4 (FAIL, FAIL, PASS). Lanes 1 and 2 held that the `gates` array "runs on PRs via guard_tree.sh", and that nothing was declared under [package.metadata.dogfood]. Measured, both are wrong: - The array IS [package.metadata.dogfood].gates (the header is at Cargo.toml:606, outside the hunk's context lines). - Its only executor is scripts/dogfood.sh at release. check_verifier_pinning.sh (ci.yml:1627) scans it, and passes with this entry (rc 0; it lists the file). - guard_tree --no-cargo: 74 checks, and the only failure is the host-environment check_fleet_pv_shapes_gate, which fails identically on origin/main. The comment was the ambiguous part: "the PR-time half ... run by check_float16_greedy_parity.sh" read as though that script belonged in the list. It now states which list this is and who runs it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3076: AD-04 quorum receipt, 3/3 PASS on 97093d2 Round 5: gemini-3.1-pro-high, gemini-3.1-pro-low and gemini-3.6-flash-high (the trio aprender-04 ruled), three distinct conversation ids. Lane 2's store references this worktree (52 times: a measured review). Lanes 1 and 3 judged the diff text only. Under paiml-implement#317 those two seats are VOID, and are to be filled by a non-author re-run of the acceptance command at 97093d2. Earlier rounds: round 1 3/3 FAIL (the llama.cpp gate was missing; taken, the gate was built). Round 2 PASS on a head that tripped guard_tree. Round 3 PASS with a void lane 3. Round 4 FAIL/FAIL/PASS, refuted by measurement: the gates list is [package.metadata.dogfood], and the declaration comment was clarified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * roadmap(PMAT-3609): the three repo-layout facts round 2 lane 1 misread, each checked at the PR head Refs paiml#3609 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * WIP PMAT-3672: single chat-template application (parked for P0 paiml#3685) NOT READY. Remaining: aprender-orchestrate/src/agent/driver/realizar.rs:67 builds InferenceConfig as a struct literal and needs force_chat_template; aprender-serve lib tests have 2 compile errors to resolve; then tests, mutation, the real-binary measurement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci(diet): coverage on tags only, perf-gate table path-scoped, model suites nightly, mutants only on .rs, gate's skipped rule tightened (paiml#3676) Refs paiml#3676 paiml#3668 paiml#3658 Operator 2026-09-21: aprender releases as fast as possible, intel idle; "YES, coverage on tags release only." All five workflow items approved by Noah directly (CLAUDE.md check-in for .github/workflows edits). 1. COVERAGE -- ci.yml `skip_coverage: true` on the sovereign-ci call. That job gated nothing (no coverage_min) and measured nothing (`--lib` on the facade root: 0 tests, per the existing NOTE), at 237 s per run (main 35563537942). coverage-nightly.yml now also triggers on `push: tags: ['v*']` (COV_FLOOR via `make coverage`), concurrency per ref so a tag run and the nightly do not cancel each other. Every consumer enumerated first (the rmedia trap aprender-04 relayed): the pre-publish dogfood's `make coverage-check` (produces its own llvm-cov), the queue steward (reacts only to `failure`), two tests reading an embedded string / the Makefile, a legacy prepare-release.sh (local llvm-cov). None reads CI's number. The one output lost: that job's advisory Codecov upload. NEW scripts/check_coverage_has_producers.sh asserts the chain: R1 `make -n coverage-check` reaches llvm-cov; R2 dogfood runs it (non-comment); R3 coverage-nightly's `on:` block has schedule AND tags [v*] (comments and other top-level keys don't count); R4 numeric COV_FLOOR. 10 rows, each link broken -> RED; missing file -> ENV 2. NOT done here, stated: making T-4 WAIT on the tag's coverage run is an autopilot change, deliberately not made on the 0.69 cut day. 2. BOOK -- no change. book.yml and book-contracts.yml are already path-filtered; book-contracts watches crates/aprender-core/src/** because chapter examples run aprender-core code, which check_workflow_path_filters.sh RULE 2 requires ("a gate that runs code must watch the code it runs"). Narrowing it would break that rule. 3a. PERF GATE TABLE -- the explicit guard-tree step (405 s on main 35563537942) is replaced by NEW scripts/check_perf_gate_selftest_scoped.sh, dispatched by guard_tree.sh --no-cargo: it runs `perf_gate.sh --selftest` only when the tree diff vs origin/main touches its DERIVED scope -- every $ROOT/ path perf_gate.sh reads, followed transitively through the scripts it calls (8 paths today) -- and otherwise prints a SUMMARY skip line. Unknown comparand or a failed diff -> RUN, never skip. The table runs whole nightly (guards-nightly.yml + manifest, 405 s). 11-row case table; mutants RED: scope not transitive, renames hiding the old side, unknown -> skip, prefix-string matching. Registered in check_no_timing_in_required.sh's META_GUARDS (reads no clock). 3b. MODEL SUITES -- the PR step keeps ONLY falsification_spec_v10_tests: it holds the SATD ratchet measured against origin/main, the one property here a diff can change (and vacuous on the nightly, where HEAD == origin/main). The stress and gpu_state suites judge the whole tree; guards-nightly.yml already runs all three. check_model_tests_wired: all 3 still wired. 4. MUTANTS -- the diff step emits has_rs (`git diff --no-renames --name-only`); image pull, `cargo install cargo-mutants`, the run and the upload are gated on it. A diff with no .rs passes quickly instead of installing the tool. 5. GATE -- the mutants rule, between GATE-MUTANTS-RULE markers: on a pull_request mutants must SUCCEED (a no-.rs diff succeeds); `skipped` passes only on other events, where the job's own `if:` skips it. The old rule ("success/skipped both pass") passed a PR whose mutants never ran. NEW scripts/check_ci_gate_mutants_rule.sh EXTRACTS that block from ci.yml and executes it for 10 (event, result) pairs; its --self-test plants the old rule and it goes RED (3 rows); no block -> ENV 2. Gates: guard_tree --no-cargo: all PASS except check_fleet_pv_shapes_gate.sh, the known lambda-box fleet-pv condition (identical on main; fixed in paiml#3669); guard_tree_test 23/0; guards-are-wired, guards-nightly-manifest (8 entries), no-timing-in-required, model-tests-wired PASS; bashrs 0 errors on the new files, bashrs-gate PASS; actionlint: the same 7 shellcheck infos as main, nothing new; roadmap aggregate idempotent; fmt, deny, contracts 1666. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * audit(PMAT-3609): quorum round 3 receipt, judged at 9cc89e3 AGREED 3/3 PASS (gemini-3.1-pro-high, gemini-3.1-pro-low, gemini-3.6-flash-high). Worktree check against each lane's agy conversation store: no lane references the pmat-3609 worktree. Lane 3 read the stale scratch clone (wt-aprender-main 46, lane-3-check-eval 31). Lanes 1 and 2 read no repo tree and judged the prompt's diff (diff_sha256 516acb17…) alone. Seat rulings are the cop's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci(diet): quorum round 1 findings — book nightly + path-filter RULE 3, perf-gate smoke row, gate-rule self-test independent of ci.yml (paiml#3676) Refs paiml#3676 Round 1 (e2d12bd) was FAIL/FAIL/PASS. The two FAIL lanes held the diff to the ticket's text and were right on three counts: 1. Item 2 says the book jobs are path-filtered "and nightly always". They were filtered, but NO path-filtered workflow had a schedule. book.yml, book-contracts.yml and install-script.yml now carry a nightly cron (22:30 / 22:45 / 23:00 UTC, ~5h early for the paiml#3292 dispatch delay; book.yml's pages deploy stays push-only by its own `if:`). 2. Item 5 says check_workflow_path_filters.sh is updated. It gains RULE 3: a path-FILTERED workflow must also trigger on `schedule:` with a cron -- the filter is a claim that nothing outside it can break the gate, and the nightly is what re-checks it (book.yml sat dark three months, per the guard's own header). scripts/lib/workflow_path_filters.py emits a SCHEDULE row. Self-test 4 -> 6: no schedule -> RED; `schedule: []` -> RED. On the tree before the crons: 3 workflows RED; after: PASS. 3. Item 3 says "The PR path keeps a smoke row" for the perf table. On a skip, check_perf_gate_selftest_scoped.sh now requires `perf_gate.sh --list-selftests` to exit 0 and enumerate >= 1 case (109 today, ~60 ms), and says so on the SUMMARY line. Three end-to-end rows (skip + smoke; in scope -> full table; a table that cannot enumerate -> RED). 14 rows; five mutants RED (not transitive, renames, unknown -> skip, prefix match, smoke removed). Lane 1's other finding also held: check_ci_gate_mutants_rule.sh checked for ci.yml before --self-test, so its self-test could not run without the workflow. The check now sits on the bare-run path only. Gates: guard_tree --no-cargo all PASS except check_fleet_pv_shapes_gate.sh (the lambda-box fleet-pv condition, fixed in paiml#3669); guard_tree_test 23/0; bashrs 0 errors on every changed script, bashrs-gate PASS; actionlint: no finding beyond main's shellcheck infos. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(guard): a pinned pv that panics was UNMEASURED fleet state — only pv's own exit 2 is (PMAT-3671) The andon (984fc76, PMAT-3670) keyed reason=pv-env on the verdict PARSER's exit 2, which means only "lint.json did not parse". pv's own rc reached the message and nothing else, so a pinned pv that panicked (101) or was killed by a signal became UNMEASURED, exit 0. A panic can be caused by a contract in the PR's own tree: tree state, not fleet state. judge now captures pv's rc as pvrc and keys the verdict on <parser rc>:<pv rc>. 2:2 stays UNMEASURED reason=pv-env. 2:* is FAIL reason=pv-no-verdict, exit 1, and names pv's rc and its first stderr line. Case table 15 -> 19 rows: panic 101, SIGKILL 137 and rc 1 with no output are each RED. A mutant keying pv-env on the parser alone (2:2 -> 2:*) sends the panic back to UNMEASURED exit 0. Rows 13-15 failed before the fix with exactly the issue's "UNMEASURED reason=pv-env ... (rc=101) ... panicked", rc 0. Also fixed in the same rows: the existing pv-env mutant was vacuous. Its sed swapped the arm's ' for ", so the mutant was a syntax error (bash -n rc 2) that "exited non-zero" without reaching the arm. Both mutants now use a backreference, must pass bash -n, and assert their exact outcome. --help's hardcoded 2,44p range is replaced by the leading comment block. Closes paiml#3671 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): a model file that fails to parse is named by its magic bytes — a truncated GGUF is no longer "Invalid APR format" (PMAT-3661) `CliError::InvalidFormat` Displays "Invalid APR format: {0}", and `apr inspect` and `apr tensors` wrapped every parse failure in it, so a truncated GGUF or a random-bytes .safetensors was reported as an invalid APR file. One of them contradicted itself: "Invalid APR format: … Unknown model format". New variant `CliError::InvalidModelFile { format, message }`, displayed as "Invalid {format} file: …", keeps exit 4. It is built by `CliError::invalid_model_file(path, context, &e)`, which reads the format from the file's magic bytes (hex.rs's existing detect_format, wrapped as `detected_model_format`; "model" when none match) and takes a core FormatError's message, not its Display. inspect, tensors and the SafeTensors profile path use it. InvalidFormat itself is unchanged. The double prefix is fixed at its source: aprender-core's `Failed to parse GGUF: {e}` wrapped a FormatError's Display. Case table (6 rows, the real inspect::run / tensors::run, rendered as main.rs prints it): truncated GGUF → "Invalid GGUF file: …", random .safetensors → "Invalid model file: …", truncated .apr → still "Invalid APR …". No row contains "Invalid model format", and every row exits 4. Mutants: the pre-fix call sites fail 14 checks, the old Display fails 9, and the old core wrap fails 1. apr-cli lib: 7294 passed. aprender-core format::tensors: 131 passed. Follow-up paiml#3691: six more InvalidFormat sites that name GGUF/SafeTensors (hex, trace, the SafeTensors header reader). Closes paiml#3661 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(release): the publish universe's size is derived, never 74 — ORDER must equal the universe as a set (PMAT-3657) publish_strict.sh asserted the 0.68.2-era universe size three times (`-eq 74` on the order, the universe and the unique count, and `$n -eq 74` at final verification). That is a literal of the class paiml#3618 removed, and it would stop a later cascade for a reason unrelated to publish safety. Measured in a private clone: with aprender-tsp `publish = false` and dropped from the order (a legitimate universe of 73), the pre-fix script stops with "order=73 universe=73, expected 74/74". The fixed script plans all 73. N is now ${#EXPECT[@]}, the universe cascade_universe.py reads from cargo metadata of every workspace at the tag. ORDER must equal it as a SET, and each failure names crates, not counts: twice in the order, in the order but not the universe, in the universe but not the order ("so never uploaded"). Negative controls (missing / twice / extra) each stop, rc 1, naming the crate. check_release_scripts_derive_identity.sh gains R3: no numeric test against a literal of two or more digits on a non-comment line. The planted row is publish_strict.sh's own 0.68.2 line, verbatim. 23 rows (was 14). A mutant with R3 disabled turns exactly rows 12-17 red. The guard over the pre-fix tree names lines 39, 41 and 90; the old guard passed that tree. Closes paiml#3657 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(guard): check_pr_review_counts.sh leaves the unwired-guards ledger — guard-tree runs it now (PMAT-3646) paiml#3644 (batch paiml#3669) recorded check_pr_review_counts.sh in scripts/unwired_guards_baseline.txt as dark and RED on main, "so it cannot be wired until that is fixed: paiml#3646". This branch is that fix. With main merged in, the entry and its comment are deleted, as criterion 1 asks. Measured on the merged tree: check_guards_are_wired.sh "did not grow (1 removed) vs a877fa0"; check_pr_review_counts.sh PASS, --self-test 16/16; guard_tree.sh --no-cargo 78 checks (was 76), 0 failed, including check_pr_review_counts.sh [self-test] and [run]. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3672: apr run templates the prompt ONCE, in realizar; --chat travels as a flag, not as pre-wrapped text `apr run` on an instruct model wrapped the prompt twice. apr-cli's dispatch_run (paimlGH-638) pre-wrapped it in hard-coded ChatML, and realizar's prepare_tokens (paimlGH-278) then applied the model's own template on top, escaping the inner special tokens to `<\u{200B}|`. Measured on Qwen2.5-0.5B-Instruct-f16: 52 prompt tokens where llama.cpp has 21 for the same conversation, with each U+200B encoded as three token-0s. - realizar: InferenceConfig::force_chat_template (plus a builder). prepare_tokens_{gguf,safetensors,apr} OR it into their one template decision, so the template is applied exactly once and is the model's own. - apr-cli: run_prompt_and_chat() returns the RAW prompt plus the chat flag (--chat, or the paimlGH-638 instruct/chat source-name heuristic). The flag reaches InferenceConfig through RunOptions.chat_template. - The three tests that re-implemented the old ChatML wrap inline (they could not fail, and they pinned the double template as the contract) are replaced by four tests of run_prompt_and_chat. Mutation: restoring the pre-wrap turns two RED. - Every realizar InferenceConfig struct literal gains the field, including aprender-orchestrate's driver. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * batch: regenerate the generated set once (batch_fold.sh --regen) roadmap aggregate, pv census, pv extract (contracts.nt + shapes.ttl) and the README CONTRACT_COUNT blocks, regenerated with the pv built from this tree; fixed points asserted: make roadmap-aggregate-check, pv extract --check, readme_sync.sh --check. * batch: regenerate the generated set once (batch_fold.sh --regen) roadmap aggregate, pv census, pv extract (contracts.nt + shapes.ttl) and the README CONTRACT_COUNT blocks, regenerated with the pv built from this tree; fixed points asserted: make roadmap-aggregate-check, pv extract --check, readme_sync.sh --check. * fix(release): a blank line in publish-order.txt is refused by its line number, not as an empty crate (PMAT-3696) At 6fd2416 a blank line in publish-order.txt reached the set checks as an empty string. bash printed "EXPECT: bad array subscript", and the cascade stopped with "in the publish order but not in the universe: ", naming nothing. Found by aprender-7a seat-filling paiml#3657. Every blank or whitespace-only line is now listed by number and refused before ORDER is built: "STOP publish: blank line 36 in publish-order.txt: an empty crate name is not a crate". Measured in a private clone with --plan: the clean file still plans 74 rows, rc 0; blank at 36 gives that message, rc 1, with no subscript error; whitespace at 3 plus a blank gives "blank line 3, 37". Closes paiml#3696 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * batch: regenerate the generated set once (batch_fold.sh --regen) roadmap aggregate, pv census, pv extract (contracts.nt + shapes.ttl) and the README CONTRACT_COUNT blocks, regenerated with the pv built from this tree; fixed points asserted: make roadmap-aggregate-check, pv extract --check, readme_sync.sh --check. * batch: regenerate the generated set once (batch_fold.sh --regen) roadmap aggregate, pv census, pv extract (contracts.nt + shapes.ttl) and the README CONTRACT_COUNT blocks, regenerated with the pv built from this tree; fixed points asserted: make roadmap-aggregate-check, pv extract --check, readme_sync.sh --check. * fix(batch-1): float16_greedy_parity.sh's two messages named `apr` as prose — check_apr_bin_pinned read them as bare invocations paiml#3707's guard-cargo (intel-clean-room-11, "Every execution-surface `apr` reference must be pinned") failed with 2 BARE-APR hits, both in PMAT-3076's scripts/float16_greedy_parity.sh, and both message text rather than invocations: line 98 `die2 "jq is required (apr --json parsing and the receipt)"` and line 206 `base_note=" (apr text identical to …)"`. PMAT-3076 never had a PR of its own, so batch-1 was the first CI run of the guard on it. The two messages now say "the pinned binary". No invocation changed. check_apr_bin_pinned.sh: rc 1 with 2 hits before, rc 0 after ("306 execution-surface file(s) scanned, every `apr` reference is pinned"). check_float16_greedy_parity.sh case table: 0 failures. Refs paiml#3076 paiml#3707 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Revert "Merge commit 'f7b25a6c6' into release/0.70-batch-1" This reverts commit 9a0c3cb, reversing changes made to 6dccdc4. * fix(claims): three tok/s literals in AppState docs moved to docs/BEATS.md check_no_claim_literals.sh (guard-cargo) went red on batch-1: mod_app_state_gpu.rs carried "755+ tok/s (2.6x Ollama)" twice and "~1-10 tok/s" once. Their baseline entries were keyed on the pre-shift lines :193/:317/:456, so once the lines moved the entries stopped matching and the literals counted as new. The docs now point at docs/BEATS.md (the gated scoreboard) instead of quoting a number, and the three dead baseline lines are pruned. The ratchet shrinks 441 -> 441 known, 3 removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
guyernest
pushed a commit
to guyernest/aprender
that referenced
this pull request
Sep 29, 2026
…ne CI run (paiml#3772) * fix(guard): check_pr_review_counts.sh was RED on main and ran nowhere — a comment classified it out of guard-tree (PMAT-3646) PRQ-013 (#2985) deleted ci.yml's receipt job and, with it, the step names that stated six of the counts this guard checks. The guard went RED ("6 row(s) disagree with the tree") and nobody saw it, because a comment at :196 naming the build tool matched guard_tree.sh's CARGO_RE. That dropped the guard from guard-tree's --no-cargo run, and guard-cargo never names it. - The site table drops the five ci.yml rows whose sentences left with the job (the roadmap records them as a STATED LOSS). ci.yml's "43-row" goes from 2 sites to 1. The bats_tests derivation is removed: no file states that count any more, and a derivation with no site checks nothing. check_pr_review_wiring.sh's "43-row" joins the table as a new site, with a self-test row (stale-wiring-rows). - The :196 comment no longer names the tool, so `guard_tree.sh --dry-run --no-cargo` prints `run: scripts/check_pr_review_counts.sh`. Restoring the old wording moves it back to --cargo-only (mutation-verified). - In --list mode, mutate-guard.sh and mutate_quorum_arm.sh now require only awk. They demanded bats/jq/minisign/check-jsonschema before printing a catalogue that uses none of them, so the counts derivation exited ENV=2 on a runner without them. Reproduced under a PATH without those four: rc=2 before, rc=0 after. guard-tree runs on the clean-room hosts, including the gx10/yoga docker runners, so wiring the guard without this would have swapped a dark guard for a red job. The real sweeps still refuse to run without the tools (rc=1). Verified: check rc=0 (22 rows); --self-test 16/16; guard_tree_test.sh 23/23; check_pr_review_wiring.sh PASS, self-test 11/11; check_guards_are_wired.sh PASS, self-test 7/7. Item 2 of #3646 needs no code. check_pr_review_receipt.sh is invoked with a receipt by check_pr_review_arm4.sh (A3 and A4), which pr-review-quorum.yml runs. The ledger line #3647 adds for this guard is deleted here once #3647 lands (the ledger is shrink-only). Refs #3646 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3076: F16/BF16 CPU matvec reads the row with AVX2+F16C+FMA instead of a fn pointer per weight float16_matmul called the decode through a `fn(u16) -> f32` pointer for every element: an indirect call plus a bounds check per weight, which nothing could inline or vectorise. On a Threadripper 7960X, Qwen2.5-0.5B-Instruct-f16 decoded at ~10 tok/s; BRICK-PROFILE put ~99% of the time in the F16 matvecs, and the 272 MB LM head read at ~27 GB/s. Each row is now one float16_row_dot over its bytes. x86_64 CPUs with avx2+fma (+f16c for F16) take a fused kernel, runtime-detected: vcvtph2ps, or u16->u32 <<16 for BF16, into four FMA accumulators. Everything else takes a chunked decode-then-dot the compiler vectorises. The loop structure is unchanged, and a row that runs past the end of the buffer is dotted over the whole elements that exist, as the per-element bounds check did. Tests: parity against an f64 reference at 16 lengths that reach every loop and tail (both kinds, and the portable path on its own); NaN, inf and subnormals at every lane position; and float16_matmul against a transcription of the pre-fix loop, including a truncated buffer with an odd trailing byte. Mutation-checked: skipping the tail, BF16 <<15, F16 routed to the BF16 kernel, dropping the portable remainder, and removing the end clamp each turn tests RED. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(release): batching is the default merge path — scripts/batch_fold.sh + APR-RELEASE-001 §13 (#3673) Refs #3673 #3669 #3650 paiml-implement#317 Operator 2026-09-21: "we are arbitrarily going slow because each PR adds time, but in most cases 80% of PR can be batched." Code handed off by the cop (aprender-04) as an uncommitted draft; rewritten here. scripts/batch_fold.sh -- fold receipted PR branches into release/<train>-batch, --no-ff, in order. It resolves ONLY the generated set: roadmap.yaml, census.json, contracts.nt, shapes.ttl (pv extract writes it), and the README CONTRACT_COUNT blocks. Every other conflict aborts that branch: SKIP + paths. - README is merged three-way with the counts normalised out (git merge-file), so a prose conflict is a SKIP. The draft took README wholesale with --theirs, which silently dropped the batch side's prose. - A CLEAN fold that touches the generated set marks it stale: two PRs that each add a contract merge textually and the census is then off. Stale without --regen ends `REGEN REQUIRED`, exit 3, never silent. - --regen regenerates once with the tree-built pv (pv_bin.sh), asserts the three fixed points (make roadmap-aggregate-check, pv extract --check, readme_sync --check), refuses writes outside the set, commits. - refuses a dirty batch worktree; exit 0/1/2/3 documented. scripts/check_batch_fold.sh -- the case table, 21 rows over throwaway repos with stub pv/make/readme_sync, plus --self-test with 4 mutants, each RED: README wholly generated (the draft's rule), every conflict taken, a clean fold never stale, a failed fixed point ignored. It is a check_*.sh on purpose: the draft's own `--self-test)` case made check_guards_are_wired.sh RED ("NEW: batch_fold.sh", measured), and guard_tree.sh --no-cargo dispatches this file with no workflow edit. The draft, judged by this table: 13 of 21 rows RED (3 of them only on output wording). APR-RELEASE-001: - §13 (new): roles (author stops at the receipt; never arms, never merges main to stay current), assembly, the generated set, left-out rules, what needs a new quorum, push/disarm, body/arm/close, a red batch, what it costs (a SQUASH queue lands a batch as ONE commit: bisect on main stops at the batch, so the batch branch is kept until the next tag), falsifiers FX-B1..3. - hard rule 9 AMENDED. It read "No hand-squash of batches ... each PR lands as its own commit ... never hand-bisected", which the new default contradicts. The old text is kept verbatim as superseded, with the reason: the queue it described (group size 8) was not the queue we had (SQUASH, maximumEntriesToMerge=1, measured on #3658). - §8 stop "a hand-squashed batch" -> "a batch assembled by hand instead of scripts/batch_fold.sh (§13)". - revision note 2026-09-21. Gates: check_batch_fold 21/21 + self-test 4/4; bashrs 0 errors on both files; bashrs-gate PASS; guards-are-wired PASS; guard_tree dispatches it; guard_tree_test 23/0; pipe-into-grep-q at ceiling; hardcoded-paths +0; apr pinned; no-timing-in-required; check_ci_unwedge and check_ont_ratchet (read the spec) green; roadmap aggregate + sorted/unique/additive; fmt, deny, aprender-contracts --lib 1666. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3673: AD-04 quorum receipt for #3674 — 3/3 PASS, all three lanes VOID under the #317 rule Lanes gemini-3.1-pro-high / gemini-3.1-pro-low / gemini-3.6-flash-high at eebd642c8 (diff_sha256 33accb56…), PASS/PASS/PASS. Per APR-RELEASE-001 §13.4 (and the cop's rule while paiml-implement#317 is open), none of them counts as a measurement: no lane's agy conversation store references this worktree. lane 1 (cf256148): Cwd ~/.gemini/antigravity-cli/scratch x18, /tmp x6; rebuilt batch_fold.sh from the diff by heredoc lane 2 (bdf62808): no commands; a static read of the inline diff lane 3 (059ef063): Cwd scratch x112, scratch/wt-aprender-main x18, /tmp/tmp.* x12; ran copies of the two scripts under /tmp The seat is filled by a NON-author's measured check at the PR head, posted on #3674. Refs #3673 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3076: the greedy-parity gate against pinned llama.cpp, and the measured receipts Round-1 quorum (3/3 FAIL, correctly): the acceptance quoted in the fragment requires "a greedy-parity gate against llama.cpp on an unquantized ≤1B GGUF", and the diff had none. It also reported tok/s with nothing backing it. scripts/check_float16_greedy_parity.sh runs the pinned apr and llama-completion at the llama_pin.toml build_commit (the gate refuses any other build), greedy and CPU, over six prompts, and compares the trimmed text. `apr run` templates an instruct prompt twice (#3672), so the models are copies with tokenizer.chat_template removed; the gate refuses a model apr would template. With --baseline <pre-change apr>, a divergence the old binary reproduces byte-for-byte is KNOWN rather than FAIL. Receipts in evidence/pmat-3076-f16-bf16-matvec/: - Qwen2.5-0.5B F16: 6/6 identical to llama.cpp. - Qwen3-0.6B BF16: 5/6 identical, plus 1 KNOWN (char 83, which the pre-change binary reproduces exactly). - Negative control: a deliberately broken build (F16 sent to the BF16 kernel, BF16 <<15) FAILs 12/12 at char 0 with rc 1. - findings.json: decode A/B with raw per-rep numbers, model sha256, host and binaries. F16 goes 92.7 -> 47.2 ms/token at 48 threads, and 6.2 -> 68.6 tok/s at RAYON_NUM_THREADS=8. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3076: the parity gate is a host-side gate, not a tree guard; it measures agreement only scripts/guard_tree.sh runs every check_*.sh bare inside a required job. The parity gate needs the models and the pinned llama.cpp, so run bare it refused (rc 2), a red row for a guard that was never given its input. It is now scripts/float16_greedy_parity.sh, alongside the other host-side probes, and it is not wired into CI: that needs models on a runner and a workflow edit. check_no_competing_harnesses.sh correctly classed the gate as a second benchmark harness, because it drove apr/llama.cpp AND recorded tok/s. The rate capture is removed. Throughput lives in the measured A/B (findings.json), and the gate judges agreement only. Receipts regenerated with the final script: F16 6/6, BF16 5/6 + 1 KNOWN, negative control 12/12 FAIL. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(tokenizer): the unknown token is the model's, not BPE's — Qwen3.5 serves; a model with no vocabulary refuses by name (PMAT-3609) BPETokenizer::new required `<unk>` in every vocabulary. Qwen3.5 has none: it declares tokenizer.ggml.eos_token_id and no unknown_token_id key at all. So `apr serve` refused a real, complete vocabulary, while a GGUF/APR with NO vocabulary loaded, because GH-226's placeholder synthesised `token{i}` with `<unk>` in slot 0. The less-specified input was the one accepted. This follows the operator's three constraints (#3609, 2026-09-20) and the cop's rulings (2026-09-21): - The unknown token is Option: `new(vocab, merges, impl Into<Option<&str>>)`. A named token must exist. With no unknown token, CONSTRUCTION refuses, naming the first byte that has neither a `<0xNN>` token nor a byte-level glyph token (with_merges also requires every glyph), so encode never meets an unencodable byte: nothing is dropped and nothing is synthesised. With an unknown token present, encode is unchanged. - No vocabulary refuses by name at all 13 placeholder sites (apr-cli serve x7, aprender-serve cli x6). - apr serve's tokenizer.json path uses the DECLARED model.unk_token. The vocabulary-only AppState constructors use `vocabulary_unk_token` (the vocabulary's own `<unk>`, else none), the by-name interim #3675 replaces. - Prose: the GH-226 comment, the book's api-server example and examples/model_cache.rs no longer teach `<unk>` as a requirement. - Fixtures: REAL Qwen3.5-0.8B and TinyLlama GGUF headers, every key and scalar verbatim, only the per-token arrays sliced (generate.py, MANIFEST.json with each source header's sha256). They use the `.gguf-header` extension because .gitignore ignores `*.gguf`. Verified: aprender-serve lib 15892 passed (tokenizer 215, 9 new); apr-cli lib 7283 passed; clippy -D warnings clean on both; cargo check with cuda; contracts lib 1666; deny ok. MUTANT: restoring the unconditional `get("<unk>").ok_or_else(..)` turns the real-Qwen3.5 row RED with the original error "Unknown token '<unk>' not in vocabulary". Found and filed separately: #3677 (greedy encode reads Latin-1 characters as byte-level glyph tokens, so `é` decodes to U+FFFD). Closes #3609 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3076: wire the parity gate. The case table runs on every PR, the model-backed run at pre-publish Cop ruling, no workflow edit: - scripts/float16_parity_lib.sh holds the comparison helpers (first_diff, trim, json_str). It is a sourced library and option-neutral, and check_sourced_libs_option_neutral covers it: injecting `set -e` turns that guard RED. It sits at scripts/ top level because the guard resolves sourced basenames there, and a copy under scripts/lib/ was measured as unchecked. - scripts/check_float16_greedy_parity.sh is the helpers' case table. guard_tree runs it on every PR (`--dry-run` lists it), and check_guards_are_wired PASSes again (the unwired count is back to 3). - scripts/float16_greedy_parity.sh, run with no arguments, is release mode, declared in Cargo.toml [package.metadata.dogfood]. For each committed receipt it runs the model that receipt names, checks it against the receipt's sha256, and uses the receipt as the baseline (a divergence is KNOWN only if apr's text is byte-identical to the recorded row). A model absent from the host is a FAIL. It has no --self-test arm of its own; the case table is the PR-time guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3076: say in the declaration who runs it; round 4's two FAILs read the gates list as a PR-path list Round 4 (FAIL, FAIL, PASS). Lanes 1 and 2 held that the `gates` array "runs on PRs via guard_tree.sh", and that nothing was declared under [package.metadata.dogfood]. Measured, both are wrong: - The array IS [package.metadata.dogfood].gates (the header is at Cargo.toml:606, outside the hunk's context lines). - Its only executor is scripts/dogfood.sh at release. check_verifier_pinning.sh (ci.yml:1627) scans it, and passes with this entry (rc 0; it lists the file). - guard_tree --no-cargo: 74 checks, and the only failure is the host-environment check_fleet_pv_shapes_gate, which fails identically on origin/main. The comment was the ambiguous part: "the PR-time half ... run by check_float16_greedy_parity.sh" read as though that script belonged in the list. It now states which list this is and who runs it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3076: AD-04 quorum receipt, 3/3 PASS on 97093d2a5 Round 5: gemini-3.1-pro-high, gemini-3.1-pro-low and gemini-3.6-flash-high (the trio aprender-04 ruled), three distinct conversation ids. Lane 2's store references this worktree (52 times: a measured review). Lanes 1 and 3 judged the diff text only. Under paiml-implement#317 those two seats are VOID, and are to be filled by a non-author re-run of the acceptance command at 97093d2a5. Earlier rounds: round 1 3/3 FAIL (the llama.cpp gate was missing; taken, the gate was built). Round 2 PASS on a head that tripped guard_tree. Round 3 PASS with a void lane 3. Round 4 FAIL/FAIL/PASS, refuted by measurement: the gates list is [package.metadata.dogfood], and the declaration comment was clarified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * roadmap(PMAT-3609): the three repo-layout facts round 2 lane 1 misread, each checked at the PR head Refs #3609 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * WIP PMAT-3672: single chat-template application (parked for P0 #3685) NOT READY. Remaining: aprender-orchestrate/src/agent/driver/realizar.rs:67 builds InferenceConfig as a struct literal and needs force_chat_template; aprender-serve lib tests have 2 compile errors to resolve; then tests, mutation, the real-binary measurement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci(diet): coverage on tags only, perf-gate table path-scoped, model suites nightly, mutants only on .rs, gate's skipped rule tightened (#3676) Refs #3676 #3668 #3658 Operator 2026-09-21: aprender releases as fast as possible, intel idle; "YES, coverage on tags release only." All five workflow items approved by Noah directly (CLAUDE.md check-in for .github/workflows edits). 1. COVERAGE -- ci.yml `skip_coverage: true` on the sovereign-ci call. That job gated nothing (no coverage_min) and measured nothing (`--lib` on the facade root: 0 tests, per the existing NOTE), at 237 s per run (main 35563537942). coverage-nightly.yml now also triggers on `push: tags: ['v*']` (COV_FLOOR via `make coverage`), concurrency per ref so a tag run and the nightly do not cancel each other. Every consumer enumerated first (the rmedia trap aprender-04 relayed): the pre-publish dogfood's `make coverage-check` (produces its own llvm-cov), the queue steward (reacts only to `failure`), two tests reading an embedded string / the Makefile, a legacy prepare-release.sh (local llvm-cov). None reads CI's number. The one output lost: that job's advisory Codecov upload. NEW scripts/check_coverage_has_producers.sh asserts the chain: R1 `make -n coverage-check` reaches llvm-cov; R2 dogfood runs it (non-comment); R3 coverage-nightly's `on:` block has schedule AND tags [v*] (comments and other top-level keys don't count); R4 numeric COV_FLOOR. 10 rows, each link broken -> RED; missing file -> ENV 2. NOT done here, stated: making T-4 WAIT on the tag's coverage run is an autopilot change, deliberately not made on the 0.69 cut day. 2. BOOK -- no change. book.yml and book-contracts.yml are already path-filtered; book-contracts watches crates/aprender-core/src/** because chapter examples run aprender-core code, which check_workflow_path_filters.sh RULE 2 requires ("a gate that runs code must watch the code it runs"). Narrowing it would break that rule. 3a. PERF GATE TABLE -- the explicit guard-tree step (405 s on main 35563537942) is replaced by NEW scripts/check_perf_gate_selftest_scoped.sh, dispatched by guard_tree.sh --no-cargo: it runs `perf_gate.sh --selftest` only when the tree diff vs origin/main touches its DERIVED scope -- every $ROOT/ path perf_gate.sh reads, followed transitively through the scripts it calls (8 paths today) -- and otherwise prints a SUMMARY skip line. Unknown comparand or a failed diff -> RUN, never skip. The table runs whole nightly (guards-nightly.yml + manifest, 405 s). 11-row case table; mutants RED: scope not transitive, renames hiding the old side, unknown -> skip, prefix-string matching. Registered in check_no_timing_in_required.sh's META_GUARDS (reads no clock). 3b. MODEL SUITES -- the PR step keeps ONLY falsification_spec_v10_tests: it holds the SATD ratchet measured against origin/main, the one property here a diff can change (and vacuous on the nightly, where HEAD == origin/main). The stress and gpu_state suites judge the whole tree; guards-nightly.yml already runs all three. check_model_tests_wired: all 3 still wired. 4. MUTANTS -- the diff step emits has_rs (`git diff --no-renames --name-only`); image pull, `cargo install cargo-mutants`, the run and the upload are gated on it. A diff with no .rs passes quickly instead of installing the tool. 5. GATE -- the mutants rule, between GATE-MUTANTS-RULE markers: on a pull_request mutants must SUCCEED (a no-.rs diff succeeds); `skipped` passes only on other events, where the job's own `if:` skips it. The old rule ("success/skipped both pass") passed a PR whose mutants never ran. NEW scripts/check_ci_gate_mutants_rule.sh EXTRACTS that block from ci.yml and executes it for 10 (event, result) pairs; its --self-test plants the old rule and it goes RED (3 rows); no block -> ENV 2. Gates: guard_tree --no-cargo: all PASS except check_fleet_pv_shapes_gate.sh, the known lambda-box fleet-pv condition (identical on main; fixed in #3669); guard_tree_test 23/0; guards-are-wired, guards-nightly-manifest (8 entries), no-timing-in-required, model-tests-wired PASS; bashrs 0 errors on the new files, bashrs-gate PASS; actionlint: the same 7 shellcheck infos as main, nothing new; roadmap aggregate idempotent; fmt, deny, contracts 1666. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * audit(PMAT-3609): quorum round 3 receipt, judged at 9cc89e3a9 AGREED 3/3 PASS (gemini-3.1-pro-high, gemini-3.1-pro-low, gemini-3.6-flash-high). Worktree check against each lane's agy conversation store: no lane references the pmat-3609 worktree. Lane 3 read the stale scratch clone (wt-aprender-main 46, lane-3-check-eval 31). Lanes 1 and 2 read no repo tree and judged the prompt's diff (diff_sha256 516acb17…) alone. Seat rulings are the cop's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci(diet): quorum round 1 findings — book nightly + path-filter RULE 3, perf-gate smoke row, gate-rule self-test independent of ci.yml (#3676) Refs #3676 Round 1 (e2d12bdd1) was FAIL/FAIL/PASS. The two FAIL lanes held the diff to the ticket's text and were right on three counts: 1. Item 2 says the book jobs are path-filtered "and nightly always". They were filtered, but NO path-filtered workflow had a schedule. book.yml, book-contracts.yml and install-script.yml now carry a nightly cron (22:30 / 22:45 / 23:00 UTC, ~5h early for the #3292 dispatch delay; book.yml's pages deploy stays push-only by its own `if:`). 2. Item 5 says check_workflow_path_filters.sh is updated. It gains RULE 3: a path-FILTERED workflow must also trigger on `schedule:` with a cron -- the filter is a claim that nothing outside it can break the gate, and the nightly is what re-checks it (book.yml sat dark three months, per the guard's own header). scripts/lib/workflow_path_filters.py emits a SCHEDULE row. Self-test 4 -> 6: no schedule -> RED; `schedule: []` -> RED. On the tree before the crons: 3 workflows RED; after: PASS. 3. Item 3 says "The PR path keeps a smoke row" for the perf table. On a skip, check_perf_gate_selftest_scoped.sh now requires `perf_gate.sh --list-selftests` to exit 0 and enumerate >= 1 case (109 today, ~60 ms), and says so on the SUMMARY line. Three end-to-end rows (skip + smoke; in scope -> full table; a table that cannot enumerate -> RED). 14 rows; five mutants RED (not transitive, renames, unknown -> skip, prefix match, smoke removed). Lane 1's other finding also held: check_ci_gate_mutants_rule.sh checked for ci.yml before --self-test, so its self-test could not run without the workflow. The check now sits on the bare-run path only. Gates: guard_tree --no-cargo all PASS except check_fleet_pv_shapes_gate.sh (the lambda-box fleet-pv condition, fixed in #3669); guard_tree_test 23/0; bashrs 0 errors on every changed script, bashrs-gate PASS; actionlint: no finding beyond main's shellcheck infos. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(guard): a pinned pv that panics was UNMEASURED fleet state — only pv's own exit 2 is (PMAT-3671) The andon (984fc76a9, PMAT-3670) keyed reason=pv-env on the verdict PARSER's exit 2, which means only "lint.json did not parse". pv's own rc reached the message and nothing else, so a pinned pv that panicked (101) or was killed by a signal became UNMEASURED, exit 0. A panic can be caused by a contract in the PR's own tree: tree state, not fleet state. judge now captures pv's rc as pvrc and keys the verdict on <parser rc>:<pv rc>. 2:2 stays UNMEASURED reason=pv-env. 2:* is FAIL reason=pv-no-verdict, exit 1, and names pv's rc and its first stderr line. Case table 15 -> 19 rows: panic 101, SIGKILL 137 and rc 1 with no output are each RED. A mutant keying pv-env on the parser alone (2:2 -> 2:*) sends the panic back to UNMEASURED exit 0. Rows 13-15 failed before the fix with exactly the issue's "UNMEASURED reason=pv-env ... (rc=101) ... panicked", rc 0. Also fixed in the same rows: the existing pv-env mutant was vacuous. Its sed swapped the arm's ' for ", so the mutant was a syntax error (bash -n rc 2) that "exited non-zero" without reaching the arm. Both mutants now use a backreference, must pass bash -n, and assert their exact outcome. --help's hardcoded 2,44p range is replaced by the leading comment block. Closes #3671 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(release): the publish universe's size is derived, never 74 — ORDER must equal the universe as a set (PMAT-3657) publish_strict.sh asserted the 0.68.2-era universe size three times (`-eq 74` on the order, the universe and the unique count, and `$n -eq 74` at final verification). That is a literal of the class #3618 removed, and it would stop a later cascade for a reason unrelated to publish safety. Measured in a private clone: with aprender-tsp `publish = false` and dropped from the order (a legitimate universe of 73), the pre-fix script stops with "order=73 universe=73, expected 74/74". The fixed script plans all 73. N is now ${#EXPECT[@]}, the universe cascade_universe.py reads from cargo metadata of every workspace at the tag. ORDER must equal it as a SET, and each failure names crates, not counts: twice in the order, in the order but not the universe, in the universe but not the order ("so never uploaded"). Negative controls (missing / twice / extra) each stop, rc 1, naming the crate. check_release_scripts_derive_identity.sh gains R3: no numeric test against a literal of two or more digits on a non-comment line. The planted row is publish_strict.sh's own 0.68.2 line, verbatim. 23 rows (was 14). A mutant with R3 disabled turns exactly rows 12-17 red. The guard over the pre-fix tree names lines 39, 41 and 90; the old guard passed that tree. Closes #3657 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): a model file that fails to parse is named by its magic bytes — a truncated GGUF is no longer "Invalid APR format" (PMAT-3661) `CliError::InvalidFormat` Displays "Invalid APR format: {0}", and `apr inspect` and `apr tensors` wrapped every parse failure in it, so a truncated GGUF or a random-bytes .safetensors was reported as an invalid APR file. One of them contradicted itself: "Invalid APR format: … Unknown model format". New variant `CliError::InvalidModelFile { format, message }`, displayed as "Invalid {format} file: …", keeps exit 4. It is built by `CliError::invalid_model_file(path, context, &e)`, which reads the format from the file's magic bytes (hex.rs's existing detect_format, wrapped as `detected_model_format`; "model" when none match) and takes a core FormatError's message, not its Display. inspect, tensors and the SafeTensors profile path use it. InvalidFormat itself is unchanged. The double prefix is fixed at its source: aprender-core's `Failed to parse GGUF: {e}` wrapped a FormatError's Display. Case table (6 rows, the real inspect::run / tensors::run, rendered as main.rs prints it): truncated GGUF → "Invalid GGUF file: …", random .safetensors → "Invalid model file: …", truncated .apr → still "Invalid APR …". No row contains "Invalid model format", and every row exits 4. Mutants: the pre-fix call sites fail 14 checks, the old Display fails 9, and the old core wrap fails 1. apr-cli lib: 7294 passed. aprender-core format::tensors: 131 passed. Follow-up #3691: six more InvalidFormat sites that name GGUF/SafeTensors (hex, trace, the SafeTensors header reader). Closes #3661 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(guard): check_pr_review_counts.sh leaves the unwired-guards ledger — guard-tree runs it now (PMAT-3646) #3644 (batch #3669) recorded check_pr_review_counts.sh in scripts/unwired_guards_baseline.txt as dark and RED on main, "so it cannot be wired until that is fixed: #3646". This branch is that fix. With main merged in, the entry and its comment are deleted, as criterion 1 asks. Measured on the merged tree: check_guards_are_wired.sh "did not grow (1 removed) vs a877fa056"; check_pr_review_counts.sh PASS, --self-test 16/16; guard_tree.sh --no-cargo 78 checks (was 76), 0 failed, including check_pr_review_counts.sh [self-test] and [run]. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3672: apr run templates the prompt ONCE, in realizar; --chat travels as a flag, not as pre-wrapped text `apr run` on an instruct model wrapped the prompt twice. apr-cli's dispatch_run (GH-638) pre-wrapped it in hard-coded ChatML, and realizar's prepare_tokens (GH-278) then applied the model's own template on top, escaping the inner special tokens to `<\u{200B}|`. Measured on Qwen2.5-0.5B-Instruct-f16: 52 prompt tokens where llama.cpp has 21 for the same conversation, with each U+200B encoded as three token-0s. - realizar: InferenceConfig::force_chat_template (plus a builder). prepare_tokens_{gguf,safetensors,apr} OR it into their one template decision, so the template is applied exactly once and is the model's own. - apr-cli: run_prompt_and_chat() returns the RAW prompt plus the chat flag (--chat, or the GH-638 instruct/chat source-name heuristic). The flag reaches InferenceConfig through RunOptions.chat_template. - The three tests that re-implemented the old ChatML wrap inline (they could not fail, and they pinned the double template as the contract) are replaced by four tests of run_prompt_and_chat. Mutation: restoring the pre-wrap turns two RED. - Every realizar InferenceConfig struct literal gains the field, including aprender-orchestrate's driver. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * batch: regenerate the generated set once (batch_fold.sh --regen) roadmap aggregate, pv census, pv extract (contracts.nt + shapes.ttl) and the README CONTRACT_COUNT blocks, regenerated with the pv built from this tree; fixed points asserted: make roadmap-aggregate-check, pv extract --check, readme_sync.sh --check. * batch: regenerate the generated set once (batch_fold.sh --regen) roadmap aggregate, pv census, pv extract (contracts.nt + shapes.ttl) and the README CONTRACT_COUNT blocks, regenerated with the pv built from this tree; fixed points asserted: make roadmap-aggregate-check, pv extract --check, readme_sync.sh --check. * fix(release): a blank line in publish-order.txt is refused by its line number, not as an empty crate (PMAT-3696) At 6fd241645 a blank line in publish-order.txt reached the set checks as an empty string. bash printed "EXPECT: bad array subscript", and the cascade stopped with "in the publish order but not in the universe: ", naming nothing. Found by aprender-7a seat-filling #3657. Every blank or whitespace-only line is now listed by number and refused before ORDER is built: "STOP publish: blank line 36 in publish-order.txt: an empty crate name is not a crate". Measured in a private clone with --plan: the clean file still plans 74 rows, rc 0; blank at 36 gives that message, rc 1, with no subscript error; whitespace at 3 plus a blank gives "blank line 3, 37". Closes #3696 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * batch: regenerate the generated set once (batch_fold.sh --regen) roadmap aggregate, pv census, pv extract (contracts.nt + shapes.ttl) and the README CONTRACT_COUNT blocks, regenerated with the pv built from this tree; fixed points asserted: make roadmap-aggregate-check, pv extract --check, readme_sync.sh --check. * batch: regenerate the generated set once (batch_fold.sh --regen) roadmap aggregate, pv census, pv extract (contracts.nt + shapes.ttl) and the README CONTRACT_COUNT blocks, regenerated with the pv built from this tree; fixed points asserted: make roadmap-aggregate-check, pv extract --check, readme_sync.sh --check. * fix(test): no lib test asserts a truncated duration is positive — the v0.69.0 clean-room stopper, and a guard that sees the shape (PMAT-3703) v0.69.0's clean-room GATE B2 failed on gx10-pool1 (infra run 35608872676): aprender-verify-ml test_pipeline_generate_small asserted `stats.generation_time_ms > 0 || stats.total_generated < 10`, where generation_time_ms is `elapsed.as_millis()`. A host that generates 10 programs in under a millisecond reads 0. - done_when 1: the test asserts what generate() promises, `stats.total_generated == programs.len()` (true by construction), and no clock. Two more of the same shape were fixed rather than recorded: bug_hunter tests_patterns.rs `duration_ms > 0` and instruct_trainer.rs `total_time_ms > 0`. One more, outside the integer shape: test_monotonic_ns_non_zero slept BEFORE the call that initializes the epoch, so as the process's first caller it read the gap between two back-to-back clock reads (0 on a coarse timer). It now initializes, sleeps, and asserts the second read is ahead of the first. - done_when 3, measured: with generation_time_ms forced to 0 (an instantaneous generator), the fixed test PASSES. The old clause under the same mutant FAILS with the clean-room's exact message. - done_when 2: check_no_timing_in_required.sh PART 4 scans crates/*/src for an integer duration (as_millis/as_micros/as_secs, or a _ms/_us/_secs name) asserted > 0, >= 1 or != 0, behind a 15-row case table. There are 101 hits, every one read and classified, recorded in scripts/wallclock_assert_baseline.txt as 32 assertion TEXTS with counts. The ratchet is `keyed`: no text may appear and no count may rise. The ledger is keyed on text, not path:line, because the coordinate key was measured to be a trap. One unrelated line inserted above a recorded assertion made the entry stale, and the re-pointed entry was then refused by the set-aperture ratchet, since the guard itself hadn't changed. Guard mutants M0-M10 are in the roadmap fragment: the #3703 line restored, a copy of a recorded line, an emptied ledger, a raised count, a new key, a malformed line, a narrowed pattern and an empty scan all fail. The unrelated-insertion case passes. Not in this row: the slow-host twin (`elapsed < N` upper bounds) is #3705. Refs #3703 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(batch-1): float16_greedy_parity.sh's two messages named `apr` as prose — check_apr_bin_pinned read them as bare invocations #3707's guard-cargo (intel-clean-room-11, "Every execution-surface `apr` reference must be pinned") failed with 2 BARE-APR hits, both in PMAT-3076's scripts/float16_greedy_parity.sh, and both message text rather than invocations: line 98 `die2 "jq is required (apr --json parsing and the receipt)"` and line 206 `base_note=" (apr text identical to …)"`. PMAT-3076 never had a PR of its own, so batch-1 was the first CI run of the guard on it. The two messages now say "the pinned binary". No invocation changed. check_apr_bin_pinned.sh: rc 1 with 2 hits before, rc 0 after ("306 execution-surface file(s) scanned, every `apr` reference is pinned"). check_float16_greedy_parity.sh case table: 0 failures. Refs #3076 #3707 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Revert "Merge commit 'f7b25a6c6' into release/0.70-batch-1" This reverts commit 9a0c3cbad3336f8f126af56b3599d246eeb9de40, reversing changes made to 6dccdc46f13fe144bedc6683fa90f69ee0656db8. * PMAT-3694: parity_receipt_denominator.sh classifies with awk, not python — the UNMEASURED exit 3 is gone (#3694) The fleet is python-free for automation (infra#708), so on a runner without python3 the independent receipt predicate could not classify anything and #3695 made that an UNMEASURED exit 3 as a stop-gap. This is the root fix. classify() is now a strict RFC 8259 parse in POSIX awk (LC_ALL=C): iterative, UTF-8-validating, root keys only, a later duplicate key wins. Still an independent implementation and still a parse, never a substring match. The rule is unchanged. NaN/Infinity are not JSON, so they are "other" now, which is also what serde_json (the extractor's parser) does. A raw NUL is decided in bash so the answer is the same under a C-string awk. Removed: PY_BIN/PARITY_PYTHON, the UNMEASURED return 3, the --self-test early exit, and ont4c3_parity_receipts' acceptance of exit 3. A missing or silent awk is ENV exit 2, RED. Self-test 7 -> 9 rows: a python-free PATH (only git awk sort grep head tr wc) measures the same PASS; a missing classifier is ENV 2; a 35-row classification table. Measured: PASS 7 on the real corpus, with and without python3 on PATH, and in debian:bookworm-slim (the sovereign-ci base) with no python3 and no jq. 8 mutants RED. See docs/roadmaps/entries/PMAT-3694.yaml. Refs #3694 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3694: regenerate roadmap.yaml (make roadmap-aggregate) — +21 lines, the PMAT-3694 entry only Generated from docs/roadmaps/entries/. Kept as its own commit so a batch fold can drop it and regenerate once (#3650). Refs #3694 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(PMAT-3703): every remaining timing hit listed with its reason, plus the 3/3 quorum receipt for 8c08dd8dd The issue author amended #3703's done_when 2 (comment, 2026-09-21T14:49:28Z): the sweep is satisfied by LISTING each remaining hit with its reason, and the slow-host `elapsed < N` class is the named remainder in #3705. docs/audits/pmat-3703-timing-sweep.md is that listing: a snapshot at 8c08dd8dd plus the commands that regenerate it (58 + 45 = 103 lines). - 58 upper bounds on a duration, reason `#3705`. - 45 other lower bounds, each with a reason: 10 sleep-guarded, 8 tautologies, 8 not clock readings, 17 nanosecond or float readings over real work, 1 hidden upper bound routed to #3705, and 1 `#[ignore]`d test. The PART 4 ledger (enforced by text and count) points at it. It doesn't hold the list itself, because a coordinate list there would read as enforced while it drifts. docs/audits/quorum-PMAT-3703.json: round 2 AGREED, 3/3 PASS on 8c08dd8dd (gemini-3.1-pro-high x2, gemini-3.1-pro-low; lane 2 fell through gpt-oss-120b-medium on a 503). Round 1 (1 PASS, 2 NO-VERDICT from flash lanes that wrote no verdict object) is archived, not committed. This commit changes text only: no code, guard, or ledger data line. Closes #3703 Refs #3705 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): apr hex / apr trace name a GGUF or SafeTensors file by its format, not "Invalid APR" (PMAT-3691) #3661 moved `apr inspect` and `apr tensors` to the format-aware `CliError::InvalidModelFile`. hex, its SafeTensors header reader and trace still wrapped GGUF/SafeTensors failures in `InvalidFormat`, whose Display is "Invalid APR format: {0}", so `apr hex` on a truncated GGUF printed "error: Invalid APR format: Failed to parse GGUF: Invalid model format: Unexpected EOF …", both self-contradictory and double-prefixed. Ten sites move, each keeping exit code 4: - The issue's six. hex.rs `parse_gguf` / `get_gguf_tensor_f32` and trace's `trace_gguf` / `trace_safetensors` use `CliError::invalid_model_file(path, context, &e)`. The header reader's UTF-8 and JSON refusals have no path and no `AprenderError`, so they use a new sibling constructor, `CliError::invalid_model_bytes(bytes, message)`. It follows the same rule (the format comes from the bytes' magic via a new `hex::detected_format_of`, never assumed). - The rest of that reader, which done_when 2 names as a unit: the size and header-length checks, and "not a JSON object" in both callers. These are string literals, so the issue's `format!(` grep missed them. Not moved: hex.rs:353, the GGUF tensor read. It also fires on a `--tensor` name that isn't in the file, so moving it would call a healthy file invalid. It is classified below as needing a split. Case table `commands/model_file_error_tests_3691.rs`: 13 rows, a child of `hex`. There is one row per moved site, a second-caller row and an APR control. Each row checks the rendered `error: …` prefix, no "Invalid APR" for a non-APR file, no leaked "Invalid model format", and exit 4. The real `hex::run`/`trace::run` drive every site a file can reach. The 3 sites behind hex's `detect_format` (4-byte size check, array header ×2) are called directly. #3661's fixtures are shared (`pub(super)`), not copied. Mutants, all RED: each of the 10 sites reverted alone fails exactly its own row. All 10 reverted fails the 12 non-control rows (29 checks) and reproduces the issue's strings. `invalid_model_bytes` hardcoded to "SafeTensors" fails the 3 "model" rows. `detected_format_of` returning "model" fails the 8 GGUF/SafeTensors rows. apr-cli lib: 7295 passed, 0 failed, 12 ignored. clippy -p apr-cli --lib -D warnings: rc 0. fmt --all --check: rc 0. ## `CliError::InvalidFormat` sites in crates/apr-cli/src, classified (done_when 3) Measured at the PR base `0517e75a7` (PMAT-3661). `git grep -n 'CliError::InvalidFormat(' -- crates/apr-cli/src` gives **125** lines; `git grep -n 'CliError::InvalidFormat\b'` adds **1** function-pointer use (`dispatch_run.rs:242`, `.map_err(CliError::InvalidFormat)`), so there are **126 sites**. The issue's 130 at `d0771c5c5` was `git grep 'InvalidFormat('` over the same path: 128 `CliError::` lines, the variant's declaration, and its exit-code arm. #3661 moved 3 of the 128, which leaves 125. A site's class is decided by **what input reaches it**, not by the words in its message. | Class | Count | Meaning | |---|---:|---| | MOVED here | 10 | `apr hex` / `apr trace` parsing a file already identified as GGUF or SafeTensors | | MOVE: model file | 33 | the input is a model file that may be GGUF, SafeTensors, or unrecognised, so "Invalid APR format" can be false. Target: `InvalidModelFile` | | MOVE: not a model file | 58 | CLI arguments, JSON/CSV/corpus inputs, HF responses, report serialisation, IO. Target: `InvalidInput` or the matching variant | | KEEP: APR-only input | 15 | reached only with an APR file (or on a command that requires APR), so "Invalid APR format" is true | | TEST | 10 | a test asserting on the variant. It moves with the site it tests | | **Total** | **126** | | ### MOVED here (10) hex.rs:323 `parse_gguf` · hex.rs:350 `get_gguf_tensor_f32` (the reader open) · safe_tensors_header.rs:167 (too small) · :175 (header length) · :180 (UTF-8) · :182 (JSON) · :192 (`run_safetensors`, not an object) · :418 (`slice_safetensors`, not an object) · trace_likely_has_repeated.rs:267 `trace_gguf` · :332 `trace_safetensors` ### MOVE: model file (33), follow-up - **Format undetected** ("Invalid APR format: Cannot detect format …" contradicts itself): hex.rs:150 · trace_likely_has_repeated.rs:219 · :450, :454 (`validate_header`, magic neither APR nor GGUF) · trace.rs:391 (unknown extension) · canary.rs:127 · validate.rs:46 · ptx_map.rs:479 · serve/handlers.rs:983, :990 · check.rs:181 · compare_hf.rs:69 - **Any-format inspection** (rosetta / diff over GGUF, SafeTensors, or APR): compare_hf.rs:83 · debug.rs:167, :191 · diff.rs:256 · flow.rs:70 · tree.rs:151 · oracle_flags.rs:238 · probar.rs:101, :104 - **Known GGUF or SafeTensors, outside this issue's six**: hex.rs:353 (GGUF tensor read; mixed, because "not found" is a bad `--tensor` argument while a corrupt block is the file, so it needs a split and not a blanket move) · safe_tensors_header.rs:437 (tensor data past EOF) · slice.rs:11, :19, :29, :36, :43 (`extract_st_tensor_info`, a malformed SafeTensors entry) · slice.rs:66, :77, :88 (tensor bytes shorter than the slice) · chat_load_tokenizers.rs:12 (GGUF tokenizer) · ptx_map.rs:481 ("requires a GGUF model", which fires on an APR/SafeTensors input) ### MOVE: not a model file (58), follow-up - **CLI arguments**: dispatch_run.rs:242 (`--offset`) · safe_tensors_header.rs:372, :378, :381, :383, :393, :396 (`--slice`/`--tensor`) · :422 (tensor not found) · :429 (slice end) · slice.rs:57, :98, :116, :121, :152, :157, :162 · oracle_flags.rs:181, :354 · oracle_family.rs:22 · run.rs:84 - **Non-model input files**: gguf_generate_result.rs:280, :289 (feature JSON/CSV) · ppl.rs:30 (log-probs JSON) · probar.rs:512 (golden manifest) · run_resolve_tokenizer.rs:225, :234 (token ids) · test_llm.rs:217, :246 (corpus, baseline) · test_llm_band.rs:495, :747, :750, :762, :783 (corpus, witness JSON) - **Remote responses**: oracle.rs:414 · serve_plan.rs:445 (HF config.json) - **Serialising our own output** (never an input): gguf_generate_result.rs:311 · pretrain.rs:908 · probar.rs:399, :437 · test_llm.rs:120 · tokenize.rs:53, :135, :370, :575, :1569, :1577, :2146, :2201, :2214, :2373, :2386 - **IO / filesystem**: hex.rs:147, :190 ("Failed to read file") · test_llm_band.rs:517, :1258, :1278, :1512 · chat.rs:396 (no tokenizer file found) ### KEEP: APR-only input (15) compile.rs:357, :361, :369 (`compile` requires APR v2) · debug.rs:130, :277 (`debug`'s native path: `debug::run` sends GGUF/SafeTensors to rosetta first) · hex.rs:193 (`run_apr`, dispatched on APR magic) · inspect.rs:466, :476, :483, :489, :496, :520 (`read_and_parse_header`: `inspect::run` routes GGUF/SafeTensors to rosetta first, so the "GGUF format detected" branch is dead and defensive) · prune.rs:507 (APR v2 header) · slice.rs:147, :149 (`slice_apr`) ### TEST (10) chat_command_find_qwen.rs:42 · construction.rs:73, :91, :109 · tests.rs:201 · tune_tests.rs:260 · error.rs:332, :423, :496, :511 Closes #3691 Refs #3661 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3699: the release bump PR body passes §6 R-2 by construction, and prepare_bump.sh --ship refuses one that does not `prepare_bump.sh 0.69.0 --ship` opened #3698 with a body that failed ci.yml's §6 R-2 step: "non-closing ref(s) with no keep-open reason: #3080 #3545 #3571 #3602 #3658". The body embeds the CHANGELOG section, which cites the epic and context refs. 0.68.2's bump #3498 hit the same thing. The step reads the frozen event payload, so each hand fix cost a body edit, a cancel, a force-cancel and a close+reopen. --ship now builds the body BEFORE the cargo checks, the commit and the push. It asks the guard which refs owe a reason, through a new `check_pr_closes_issue.sh --list-owed`. That prints the same classify_refs() the judge uses (refactored out of check_body_text, behaviour unchanged), so the generator cannot drift from the judge. --ship appends ONE line, `keep-open: #A #B -- cited by the CHANGELOG for context; each closes via its own PR; the release EPIC closes at T-4` (done_when 1's reason, verbatim), and adds no line when nothing is owed. The reason carries no `#`, so it cannot add a closing reference. Then `check_pr_closes_issue.sh --body` runs, and --ship dies before anything is committed, pushed or opened if it fails. The epic closes at T-4 by hand today (0.68.2's #3477). autopilot's close step comments on it and closes nothing, and #3708 makes that mechanical. scripts/check_release_bump_pr_body.sh runs the REAL prepare_bump.sh --ship in a hermetic fixture: a local bare origin, stubs for gh, cargo and the arm helper, and ref kinds from the guard's own seam. Rows: guard-case-table, epic-and-refs, no-keep-open, adds-no-close, prs-only, landmine. guard-case-table runs check_pr_closes_issue.sh's own case table, which no CI job ran (guard_tree skips it as wired-with-args; ci.yml calls it only with --body). Two committed mutants: drop-keep-open is killed by epic-and-refs, and drop-refusal is killed by landmine. It is a check_*.sh, so guard_tree runs it bare with no workflow edit. Live replay of the real CHANGELOG [0.69.0] through --ship, with gh api real and only `pr create` stubbed: the keep-open line names exactly #3080 #3545 #3571 #3602 #3658 (the guard owes 5 of the 61 cited #N), and the unmodified guard with live resolution PASSes the body. Pmat-Ticket: PMAT-3699 Refs #3699 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3694: quorum receipt — AGREED 3/3 PASS on 0aac053f2 Lanes: gemini-3.1-pro-high, gemini-3.1-pro-high (lane 2 fell through gpt-oss-120b-medium on 503 capacity, PMAT-321), gemini-3.7-flash-high. 2 distinct model ids across 3 lanes, recorded in partial_reasons (PMAT-125). Round 1 (lane 2 gemini-3.8-flash-high NO-VERDICT, 2 PASS) archived, not committed. receipt-lint: complete. Refs #3694 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3699: quorum receipt — 3 PASS at 030af09a2 (round 3) Rounds 1-2 FAILed on done_when 1's text: first the keep-open reason, now verbatim; then the literal "every #N", which the issue author amended on #3699 (comment 2026-09-21T15:17:48Z) to the guard's --list-owed set. Round 3: gemini-3.1-pro-high x2 (gpt-oss-120b-medium 503 -> fell through) and gemini-3.1-pro-low; 2 distinct model ids, recorded in partial_reasons. Pmat-Ticket: PMAT-3699 Refs #3699 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * wip(PMAT-3676): coverage_on: tag + v* tag trigger + producers R3/R5 + closes-issue --self-test (provisional pin 5997b41) * audit(PMAT-3691): quorum round 2 receipt, 3/3 PASS, judged at fc54dd609 Lanes: gemini-3.1-pro-high, gemini-3.1-pro-high (lane 2 was configured as gpt-oss-120b-medium, got 503 no-capacity twice, and fell through per PMAT-321), gemini-3.7-flash-high. No lane is in the author's family (claude-opus-5). The artifact records only 2 distinct model ids across 3 lanes (PMAT-125). receipt-lint: complete. Round 1 (archived, not committed): PASS / NO-VERDICT / PASS. Lane 2 (gemini-3.8-flash-high) finished with status SUCCESS but emitted no verdict object. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ci): coverage on tags only, done so sovereign-ci's gate reads it — `coverage_on: tag` on the merged #77 pin, not `skip_coverage: true` (PMAT-3676) `skip_coverage: true` on sovereign-ci@70e51ec made `ci / gate` RED on every PR: that reusable's gate counts a skipped coverage job as a mandatory failure ("coverage did not succeed (result: skipped)"). #3688's own CI (run 35585906113) and batch-1 #3707 both hit it, and #3707 left this row out. - sovereign-ci is pinned to paiml/.github f713290c86fcd70d6f26a0faab14e67e6713586f: #74's `coverage_on` input, plus #77, which keeps #73's roadmap-fragment-parity NOT-ACTIVE while roadmap.yaml is tracked at the base (aprender still commits it; #3709 migrates it). - `coverage_on: tag` replaces `skip_coverage: true`, and ci.yml gets `push: tags: ['v*']`, the reusable's second required edit. - coverage-nightly's v* tag trigger is removed, so ci.yml measures each tag once. - check_coverage_has_producers.sh R3/R5 assert all of this. Self-test 16/16, including "skip_coverage: true instead of coverage_on -> RED". On f7b25a6c6 it reports R3+R5 RED; after the change it PASSES. - ci.yml now runs `check_pr_closes_issue.sh --self-test` beside `--body` (guard_tree skips it as wired-with-args, so its case table ran in no job; found by aprender-f0), and the comment claiming otherwise is corrected. Local: guard_tree --no-cargo 82/0 (after merging main 225b2a9ab). The proof before re-folding into batch-2 is this PR's own CI: green, with the gate logging `coverage: NOT MEASURED` and `roadmap-fragment-parity: NOT-ACTIVE`. Refs #3676 #3707 #3709 paiml/.github#77 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3708: the autopilot never inherits a dogfood GO -- R5 is judged at T-1 by the function T-4 uses; close closes the epic; the bump requires ladder receipts v0.69.0's autopilot INHERITED the parent's T-2 GO at T-1 ("bump diff = version surface only"). check_publish_preflight.sh R5 at T-4 then found no receipt in the tag's worktree and refused, 43 min after tagging. The real dogfood ran then, with the tag already public. The parent's GO is also at the OLD version, so version-keyed rows (check_model_ladder) were never measured at the release version. - autopilot dogfood step: the inherit branch is deleted. It runs dogfood.sh --phase pre-publish in $AP/wt, then `check_publish_preflight.sh --receipt-only` (R2+R5), and STOPs before the tag on a refusal. - check_publish_preflight.sh: R5 is one function, rule_r5(), called by the full gate at T-4 and by --receipt-only at T-1, on the same receipt file. The self-test gains 6 receipt-only rows (25/25). - autopilot close: open items other than the epic STOP it, naming them. Otherwise it closes the epic (only if OPEN, so a re-run is safe), then the milestone. 0.68.2's epic #3477 was closed by hand. - prepare_bump.sh --ship (obligation added on the issue): refuses unless check_model_ladder.sh --version <V>, the dogfood's own judge, is green in the bump tree, and refuses gitignored receipts. It runs before anything is committed, pushed or opened. Case tables, wired as scripts/check_*.sh: the new check_release_autopilot_dogfood_close.sh drives the REAL autopilot through its from/to steps: 8 rows, plus 9 mutants, one per claim. check_release_bump_pr_body.sh gains ladder-missing-gx10 and ladder-ignored, plus 2 mutants (12 rows). Stacked on PMAT-3699 (0cb1e4bd6): both change prepare_bump.sh. Pmat-Ticket: PMAT-3708 Refs #3708 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3708: quorum receipt — 3 PASS at 91fc42d16 (round 1) Lanes gemini-3.1-pro-high x2 (gpt-oss-120b-medium 503 -> fell through) and gemini-3.1-pro-low; 2 distinct model ids, recorded in partial_reasons. Judged diff base 0cb1e4bd6 (PMAT-3699's receipted head; this branch is stacked on it). Pmat-Ticket: PMAT-3708 Refs #3708 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(claims): three tok/s literals in AppState docs moved to docs/BEATS.md check_no_claim_literals.sh (guard-cargo) went red on batch-1: mod_app_state_gpu.rs carried "755+ tok/s (2.6x Ollama)" twice and "~1-10 tok/s" once. Their baseline entries were keyed on the pre-shift lines :193/:317/:456, so once the lines moved the entries stopped matching and the literals counted as new. The docs now point at docs/BEATS.md (the gated scoreboard) instead of quoting a number, and the three dead baseline lines are pruned. The ratchet shrinks 441 -> 441 known, 3 removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3717: the model matrix runs at T-1 on BOTH hosts before any tag, and R7 re-reads the committed receipts at T-4 with the same judge #3712 done_when 3, split into #3717 so the measuring side (aprender-62) and this wiring don't mint one fragment twice. - scripts/release/models_t1.sh: lambda (local) and gx10 (over the operator-authorized SSH) in parallel. Each builds `apr --features cuda` from the release commit, proves `apr --version` = "apr <v> (<sha9>)" before measuring, runs model_ladder.sh (the frozen interface), and checks the receipt's apr_version. check_model_ladder.sh then judges both. gx10 builds in a per-release dir, $HOME/.cache/aprender-release/rel-<v>, derived on the remote, deleting every other rel-* first and never touching ~/src/aprender/target. It refuses up front (ENV) below the measured 5,524,629 KiB. An unreachable host, a failed build, a stale binary, a missing receipt, a red cell or a judge decline is NO-GO. - autopilot: a new `models` step between dogfood and tag. It STOPs on NO-GO with nothing tagged, and says "MODELS GO at <MC> on lambda and gx10". - check_publish_preflight.sh R7: the receipts committed by the bump, judged at T-4 by the same judge. A decline or a missing judge FAILs. Self-test 28/28. - scripts/check_release_models_t1.sh: 13 rows (8 T-1 through the real autopilot step, 5 T-4 on the extracted rule_r7) and 14 mutants, one per row plus the autopilot's own STOP. Stacked on PMAT-3708 (629143eeb). Pmat-Ticket: PMAT-3717 Refs #3717, #3712 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(ladder): the model gate's universe is each host's measured Q4_K inventory; no Q4_K rung is optional Operator 2026-09-21: "you must ensure all models Q4_K CUDA work; the end". The gate measured a hand-picked list with an escape hatch (qwen3-8b-q4km required: false, red on lambda at 0.68.2 and 0.69.0), and a full fleet sweep then found reds that were on no list at all. This row is #3712's done_when 1 + 2 (the gate side). - contract: `ladder.inventory` {dirs, case-insensitive *q4_k*/*q4k* .gguf/.apr patterns, backends [cuda]}; MCL-INV-006/007 and FALSIFY-MCL-011..014. - model_ladder.sh: measures ladder ∪ the host's inventory with ONE measure() function (a rung and an inventory model cannot drift), and writes receipt v2: `inventory` [{file, sha256, bytes}], `file` per row, `apr_sha` (full 40-hex). - check_model_ladder.sh FAILs on: `required` not true on a Q4_K rung, a Q4_K rung that does not claim cuda, a ladder with no inventory, a receipt that is not v2 / has no or an EMPTY inventory, an inventory model MISSING from the run (named), and an inventory-only model not green on cuda (skip, fallback and rc != 0 are RED). - case table 18 -> 26, each red for its own reason; 3 self-mutants, each killed by its case; `--case` naming no case is RED; the root comes from the script path. qwen3-8b-q4km stays `required: false` in the contract by cop ruling: flipping it turns `pv lint contracts/` red on every PR (the armed ladder-green shape reads the committed 0.68.2 lambda receipt, "Empty output"; measured rc 0 -> 1 with only that flip). The gate now refuses the key at T-2, so the release cannot ship on it; the flip folds into 0.69.1 with the qwen3-8b fix and a green lambda receipt. Refs #3712 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3717: quorum receipt — 3 PASS at d00665a7f (round 1) Lanes gemini-3.1-pro-high x2 (gpt-oss-120b-medium 503 -> fell through) and gemini-3.1-pro-low; 2 distinct model ids, recorded in partial_reasons. Judged diff base 629143eeb (PMAT-3708's receipted head; this branch is stacked on it). Pmat-Ticket: PMAT-3717 Refs #3717, #3712 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3711: apr qa golden_output — a GPU error is a FAIL naming it, and the runtime rung names the backend the dispatch reported (#3711) On main a CUDA init or GPU generation error in the dense golden gate went to note_gpu_golden_skip and the gate said PASSED ("3 golden test cases passed"). The runtime rung (qwen35, cpu-only archs) labelled every cuda build "GPU hybrid forward" and ignored used_gpu, while the qwen35 GPU dispatch falls back to the CPU on any GPU failure, so a broken GPU passed with a GPU label. - GpuGoldenLeg {Passed, WrongAnswer, Errored, NotRun(why)}: Errored fails the gate naming the error; NotRun only for no cuda feature / no device / not GGUF (gpu_golden_not_run) or the hybrid fail-safe, and the pass message names it. - runtime_golden_backend(used_gpu, gpu_not_run): the label is the dispatch's used_gpu; a CPU fallback where the GPU should serve fails the gate. - 10 case rows; 4 mutants RED. Live on the RTX 4090: judged / SKIPPED-no-device / served-by-GPU / served-by-CPU labels, and one OOM-contended run that main would have passed as GPU now FAILs naming the fallback. The ladder already requires golden_output passed and not skipped. Re-measuring lambda + gx10 with the 0.69.1 binary is the train's step, not done here. Refs #3711 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3711: regenerate roadmap.yaml (make roadmap-aggregate) — the PMAT-3711 entry only Generated from docs/roadmaps/entries/. Its own commit so a batch fold can drop it and regenerate once (#3650). Refs #3711 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3717: the T-1 ladder runs under choom -n 1000 and the wrapper takes no GPU flock (one lock owner) The cop's GPU-sharing rule, after gx10's 15:56Z global OOM killed 18 CI containers: the release measurement must be the OOM victim, never the CI pool. Both legs now run `choom -n 1000 -- bash scripts/model_ladder.sh …`. oom_score_adj is inherited across fork, so every apr the ladder starts is covered. No flock here. #3712 row B puts `flock /tmp/apr-gpu.lock` around every apr call inside model_ladder.sh, and a second flock on the same file at this level would deadlock it. The cop ruled one owner: the ladder. Builds run under neither. Table: a new oom-victim row (choom/flock stubs record what wrapped each ladder and build), and mutants no-choom and wrapper-flock, both killed. 30 rows. The requirement is posted on #3717 and quoted in the fragment. Pmat-Ticket: PMAT-3717 Refs #3717, #3712 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(ladder): every GPU apr call runs under the fleet lock, choom'd to 1000, with a bounded wait At 15:56:08Z interactive apr runs on gx10 drove the host into a global OOM and the kernel killed CI containers (oom_score_adj 500), not apr (0): 18 kills in 10 s. Cop ruling: the lock lives in model_ladder.sh, per apr call; T-1 wraps the script in choom only (a second flock outside would deadlock on this one). - model_ladder.sh: apr_locked = flock -E 75 -w ${MODEL_LADDER_LOCK_WAIT:-1800} /tmp/apr-gpu.lock choom -n 1000 -- "$APR"; the only GPU apr call site. A lock still held after the wait is an ENV decline (exit 2) naming the holder pid from /proc/locks (lslocks leaves PATH empty for files it cannot resolve), never a hang and never a model verdict. --lock-probe runs one call through it for the judge's table. - check_model_ladder.sh: lock_audit (static; also in the REAL run, where a raw "$APR" <subcommand> call is RED) and lock_probe (a fake apr sees lock=LOCKED and oom=1000; a held lock declines naming the pid). Four producer mutants, each killed for its own reason: raw-apr-call, no-lock, no-choom, unbounded. - contract: the precondition, and FALSIFY-MCL-015. - fragment: where check_model_ladder.sh runs (dogfood only; unwired on PRs), which a round-1 lane misread as a PR red. Refs #3712 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3717: quorum receipt — 3 PASS at 8a51105dc (round 2: the choom-only GPU rule) Round 2 judges the whole #3717 diff (base 629143eeb) including the one-owner GPU ruling posted on the issue. Lanes gemini-3.1-pro-high x2 (gpt-oss 503 -> fell through) and gemini-3.1-pro-low. Pmat-Ticket: PMAT-3717 Refs #3717, #3712 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3711: quorum receipt — AGREED 3/3 PASS on 8e1214835 Lanes: gemini-3.1-pro-high, gemini-3.1-pro-high (lane 2 fell through gpt-oss-120b-medium on two 503s, PMAT-321), gemini-3.7-flash-high. 2 distinct model ids across 3 lanes, recorded in partial_reasons (PMAT-125). receipt-lint: complete. Refs #3711 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3543: the post-publish dogfood asserts the version IS published, and the banner names the phase that ran On the 0.68.2 train, `dogfood.sh --phase post-publish` printed "[FAIL] version-unpublished aprender 0.68.2 is ALREADY on crates.io" under the banner "dogfood pre-release". A pre-publish gate that a successful publish turns RED forever, under a banner naming the wrong phase: the post-publish dogfood could never say GO. - The sparse-index lookup is one function, index_version_state, moved verbatim from the pre-publish branch. version_row(phase, state) decides the row. - pre-publish: version-unpublished, unchanged. - post-publish: a NEW version-published row, PASS only when the version IS in the index AND `cargo install <crate> --version =<v> --locked` from crates.io works (every installed binary reports <v>): "on the index, resolvable, and cargo install of it works", as the issue asks. Absent, 404 or unknown is FAIL. The dry-run still runs, because row 10 reads its exit code. full is unchanged. - The banner prints DOGFOOD_PHASE. scripts/check_dogfood_version_row.sh runs the REAL "2 + 10" section with stubs for mark, the dry-run/install and curl (a fixture index): 15 rows, both directions in both phases (the issue's falsifier), plus 18 mutants, each killed. Supersedes PR #3202, which makes post-publish PASS unconditionally and misses the banner. Pmat-Ticket: PMAT-3543 Refs #3543 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * WIP PMAT-3718: run --json prompt_tokens/completion_tokens/finish_reason; serve never reports a cut as stop Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PMAT-3715: release-readiness-v1 — the tag is refused unless every release cell has a fresh Pass receipt; absence is a violation by construction Operator, 2026-09-21: "you need to improve our dogfood process and pv SHACL so leaks don't exist pre-release. this is clown town." Every 0.69.0 leak was absence read as conformance. - receipts.rs reads apr-model-ladder-receipt v1 AND v2 (#3712). Before this, the first committed v2 receipt would have turned `pv lint --gate shapes` exit 3 on every PR (a foreign schema is refused by name, and still is for v3). - extract:release-evidence (Σ entity type release-evidence), built ONLY under a release subject: Relea…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release batch for 0.69: the cut-blockers in one CI run and one queue slot
Operator: "work smarter … batching PRs". The pool is the bottleneck and every fragment-minting PR conflicts on
roadmap.yamlwith the one ahead of it (#3650). This PR integrates the four receipted cut-blockers plus the andon fix, so we get one CI run, one queue entry, androadmap.yamlresolved once.APderived (0.68.2 history proof)docs/audits/quorum-PMAT-3618.json, 3/3, cross-inspectedUNMEASURED/SUMMARYlinedocs/audits/quorum-PMAT-3651.json, 3/3, cross-inspecteddocs/audits/quorum-PMAT-3577.json, 3/3, cross-inspectedtier=full(HEAD^2 gate vs a SQUASH queue); docs-only →tier=none;--no-renamesdocs/audits/quorum-PMAT-3658.json, 3/3; aprender-b3's independent self-test 93/93andon/fleet-pv-unmeasured, aprender-b3)check_fleet_pv_shapes_gate.sh: fleet state (pin mismatch, no JSON, no judge, no binary, incapable) isUNMEASURED, never FAIL. Main's guard-tree went red on every intel runner because another repo's CI rewrites intel's pin (paiml-implement#315). A Fail verdict from the pinned binary and a control that does not fire still go RED.New content beyond the constituents: the andon commit, and merge commits whose only resolutions are the generated
docs/roadmaps/roadmap.yaml, regenerated withmake roadmap-aggregate. The contract derivatives were regenerated under the pinned in-treepvand are byte-identical (census.json,contracts.nt: no diff).#3567 evidence, host-side on the batch tree (lambda, fleet-pinned
~/.cargo/bin/pv0.68.2):PASS verdict=Pass focus_nodes=2329 corpus={…"parity-receipt":7,"pv-contract":1738} planted=3 pc_shape=fired. Onorigin/mainthe same binary returns rc 2 on the un-migrated records; #3600 is what makes the verdict possible. The runner-side row appears in this PR's own guard-tree log via #3653'sSUMMARYline.After this merges, the four constituent PRs close as "landed in #".
Closes #3618
Closes #3651
Refs #3658, #3567, #3577
keep-open: #3658 done_when item 7 is observed after merge (5 merge-group shard-1
tier=lines); #3567 closes only on a runner-sideSUMMARY PASS verdict=row from a fleet runner; #3577's ONT-4c3 ledger binding is infra's row. #3655 #3653 #3600 #3664 are the constituent PRs, closed by hand once this lands.Batch 2 folded in (operator 08:00Z: "most PRs can be batched")
Eleven more receipted, cross-inspected PRs, merged in order.
roadmap.yaml,census.json,contracts.nt,shapes.ttland the README count were then regenerated once under the tree-builtpv(extract --checkrc 0,readme_sync --checkrc 0, aggregate idempotent over 97 fragments):removed_bygets a shape: refusal-receipt-v1apr run --gpureports a CPU fallback as a refusal, not a success--tablebyte-cut panicLeft out: #3271 (its three new
printf | grep -qsites tookcheck_no_pipe_into_grep_qfrom 74 to 77) and #3622 (it adds torun_shapes_gate, which is over the complexity threshold); both go to the next batch. Also #3606 (a real code conflict with #3634 inrun_entry.rsandforward_qwen35.rs; the reconciliation is on #3606) and #3642 (itscontracts/apr-verb-classification-v1.yamlfailspv census: "missing fieldmetadata"). Both stay open for their owners.Closes #3604
Closes #3605
Closes #3644
Closes #3662
Closes #3338
Closes #3401
Closes #3601
Closes #3656
Refs #3602, #3346, #3495
keep-open: #3602 item 2 is owed to #3606 (not in this batch); #3346 and #3495 stay open on their own later rows. #3634 #3613 #3638 #3637 #3647 #3665 #3271 #3350 #3351 #3395 #3496 #3660 #3271 #3622 #3606 #3642 are PR numbers (batch constituents or left out), closed or kept by hand.
ont-delta: none — integration of receipted changes; the parity-receipt entity type rides #3600's own receipt
🤖 Generated with Claude Code
Round 2: reds found by the first CI run on
55b123f2f(run 35579370078), fixed in one pushFour reds, all fold interactions between constituents. None of them was visible in a constituent's own CI.
check_dogfood_coverage.shcommands_enum.rs, and 55 ledger rows cite it by line. Re-audited the moved rows (measured_commitintentionally unmoved: partial re-audit rule, PERF-046/#2742)check_ont_ratchet.shcontracts_anchored3 → 6, restamped viamake ont-ratchet. Proved under the runner's condition: a stub pv withoutcensusPASSes on the new stamp and REDs on the old. The root cause (barepvprobe) is #3679, 0.70ont4b_shapes_gateont4c3_parity_receiptson gx10-pool1 (docker)parity_receipt_denominator.shturned git's container refusal into a count of 0. It now reads git withsafe.directory, and a git failure is an ENV exit 2 that names git. Stub-git row, mutation-provedAlso in this push: #3681's fragment and receipt, covering b3's two complexity extractions (
1f0929f04,55b123f2f), which were re-judged under the ticket that describes them.Closes #3681
Closes #3682