Skip to content

release: 0.68.2 - #3498

Merged
noahgift merged 14 commits into
mainfrom
PMAT-3477-release-0.68.2
Sep 19, 2026
Merged

noahgift merged 14 commits into
mainfrom
PMAT-3477-release-0.68.2

Conversation

@noahgift

@noahgift noahgift commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

Release bump for 0.68.2 (06x release schedule §4.2): bump-version.sh 0.68.2 across every workspace, and the CHANGELOG section below.

When this merges, rel-0682-autopilot/autopilot.sh runs the rest of the train, fail-closed (APR-RELEASE-001 §4): T-1 deep run, pre-publish dogfood, tag + release, clean-room.yml dispatched on the tag with the run id recorded (T-3), all release assets checked with scripts/check_release_assets.sh, publish preflight, the crates.io cascade (T-4, automated per operator 2026-09-13), install.sh --version receipts on intel and gx10 plus the CUDA asset receipts on gx10 and yoga, and the epic and milestone close.

0.68.2 is an interrupt release for Qwen3 (EPIC #3477): dense Qwen3 produced garbage on CUDA in 0.68.1 because two fast paths dropped the per-head QK-norm — the manual decode graph never recorded the kernel (#3413 A) and the batched prefill never applied it (#3413 B) — and the CPU-vs-GPU guard could not see either because it probed a path generation does not use (#3413 C). All three are fixed and the guard now judges the real prefill path; a fourth finding (FP8 batched prefill fails parity on QK-norm models, #3483) is mitigated by routing those models to the serial prefill with FP8 off. Qwen3.5 real-world files (unsloth UD-IQ2_XXS / IQ4_XS) load and run on the CPU (#3432, #3091), and apr qa certifies a CPU-only architecture instead of aborting. The model-capability ladder (contracts/model-capability-ladder-v1.yaml) is green on lambda (sm_89) and gx10 (sm_121) at the cut; Qwen3-8B's optional rung stays optional pending the golden thinking-budget harness fix (#3486). #3090 (Gated DeltaNet on the GPU) is a dated non-goal carried to 0.69.0.

Added

Fixed

Changed

🤖 Generated with Claude Code

Issue refs (§6 R-2)

keep-open: #3477 — the release epic closes at the end of the train (T-5 reconcile), not at the bump.
keep-open: #3090 — Gated DeltaNet on the GPU is a dated non-goal for 0.68.2, carried to 0.69.0 (slipped_from comment on the issue).

R-23: aprender: cmd="pv lint contracts --gate shapes --format json" pv=b1c0f122a exit=0 verdict="Pass, 0 armed violations (98 unarmed warnings on bound-symbols-resolve, not armed); armed ladder-measured, ladder-green, ont-shapes-v1; pv extract contracts --check rc=0; check_model_ladder.sh: every required rung green on every required host"

bump-version.sh 0.68.2 (every workspace, facades included; --check green) and the CHANGELOG [0.68.2] section,
per docs/specifications/06x-release-schedule.md §4.2. After this merges, rel-0682-autopilot runs APR-RELEASE-001 §4 T-1..T-4 + close:
deep (T-1, local), pre-publish dogfood, tag + release, clean-room.yml dispatched on the tag (T-3, run id recorded), assets by command, preflight, cascade (T-4, automated), install, host + installer receipts, close.

Pmat-Ticket: PMAT-3477
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@noahgift noahgift added this to the 0.68.2 milestone Sep 18, 2026
@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

§13.11 rung 1 — quorum shadow verdict

S13-SHADOW pr=3498 head=b1c0f122a3f80908a4e6755228ff87d1f85ef757 verdict=REFUSE class=Q1 arm_rc=1

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

@noahgift
noahgift enabled auto-merge September 18, 2026 19:05
@noahgift noahgift closed this Sep 18, 2026
auto-merge was automatically disabled September 18, 2026 19:15

Pull request was closed

@noahgift noahgift reopened this Sep 18, 2026
@noahgift
noahgift enabled auto-merge September 18, 2026 19:15
@alfredodeza

Copy link
Copy Markdown
Contributor

Edited the body's no-close: lines to keep-open: — GitHub's closing-keyword parser reads close: inside no-close: (the hyphen is a word boundary to it), so those two lines would have closed #3477 and #3090 on merge despite the negating prefix. Confirmed against the fixed body with scripts/check_pr_closes_issue.sh: FAIL before the edit (both flagged), PASS after. Same mechanism that closed #3090/#3477 twice today (#3400, fixed in #3497, now on main) — this PR was opened before that fix landed. Also ran gh pr update-branch to pick it up so CI validates against current main rather than a stale merge-ref.

🤖 Generated with Claude Code

@alfredodeza

Copy link
Copy Markdown
Contributor

Holding this release. Auto-merge disarmed — do not re-arm without an explicit operator decision.

Operator ruling 2026-09-18 (this thread, #3477, #3090): GPU (#3090) is required for the 0.68.2 interrupt, not an optional non-goal. This PR's own changelog still reads "#3090 (Gated DeltaNet on the GPU) is a dated non-goal carried to 0.69.0" — that disposition was explicitly withdrawn. 0.68.2 does not cut until GPU is addressed (landed, or in progress with a credible plan the operator has signed off on), not merely until CPU is done.

CPU is genuinely ready (#3413/#3091/#3432 closed). GPU is not: #3413's prerequisite fix is merged, but the GDN device kernel itself has no branch or code anywhere yet.

Also already fixed on this PR: the no-close: landmine text (#3400) and the stale branch (see prior comments). Those are done; the GPU scope decision is what's holding this now.

🤖 Generated with Claude Code

noahgift and others added 5 commits September 19, 2026 15:49
…and every PR merged since v0.68.1

Pmat-Ticket: PMAT-3477

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…da (sm_89) and gx10 (sm_121) — every required rung green on both hosts; qwen3-8b optional red on lambda (#3532)

Pmat-Ticket: PMAT-3477

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…racts.nt regenerated with the two 0.68.2 ladder receipts as focus nodes (ONT-4c1, #3508)

Pmat-Ticket: PMAT-3477

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
noahgift pushed a commit that referenced this pull request Sep 19, 2026
…rmed), never pinned to one side of the arming line (Refs PMAT-3509)

aprender-ba, on #3528: the 0.68.2 bump (#3498, head 5b22fb2) arms ladder-green, so a test asserting ladder-green ∈ not_armed_shapes goes red on the release branch the moment #3528 lands under it. The row's fact is that the gate computes and reports the shape (a by_shape entry) and lists it on one side or the other; which side is the bump's, not the row's.

Pmat-Ticket: PMAT-3509
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
noahgift and others added 5 commits September 19, 2026 21:50
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ow 0)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@noahgift
noahgift enabled auto-merge September 19, 2026 20:28
@noahgift
noahgift added this pull request to the merge queue Sep 19, 2026
Merged via the queue into main with commit 338f1d4 Sep 19, 2026
25 of 26 checks passed
@noahgift
noahgift deleted the PMAT-3477-release-0.68.2 branch September 19, 2026 21:01
guyernest pushed a commit to guyernest/aprender that referenced this pull request Sep 29, 2026
…oal (decision 2026-09-19) (paiml#3503)

* docs(audit): PMAT-3477 receipt — paiml#3090 is no longer a non-goal; GPU required (decision 2026-09-19)

The receipt's row 6 and its Gaps entry still recorded the 2026-09-18 issue-comment
disposition (dated non-goal carried to 0.69.0). On 2026-09-19 Noah selected
"GPU required — build paiml#3090 now"; the GDN CUDA path is being built on
PMAT-3477-gdn-gpu and 0.68.2 waits for it. Both rows now state the decision,
the proof (apr qa Golden Output GPU on both ladder hosts, qwen35 cuda rung green),
and that bump PR paiml#3498's CHANGELOG paragraph must change before it re-arms.

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

* audit: quorum artifact for PR paiml#3503 (3/3 PASS)

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
guyernest pushed a commit to guyernest/aprender that referenced this pull request Sep 29, 2026
…table's 32 W3C SHACL-Core cases vendored in the gate, 16 accounted for by excluding form; the pinned oracle differential out of it (Refs PMAT-3509) (paiml#3528)

* PMAT-3509: file ONT-4b2 (extract:code + extract:lean, the W3C cases, the oracle differential) against paiml#3509

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

* PMAT-3509 / ONT-4b2: extract:code + extract:lean into contracts.nt (symbol/ and world/ roots), 16 vendored W3C SHACL-Core cases run by the gate, the pinned oracle differential out of gate

extract:code walks the BOUND symbols only — every module_path + function a binding.yaml declares — through a syn module-tree walk (inline mods, file mods, #[path], one re-export hop, impl methods, both [package] and [lib] crate names, a facade and the crate it re-exports tried in order). Fail-closed IN THE GRAPH: a symbol the walk cannot find is still an ont:Symbol with sym:resolved false and sym:unresolvedReason naming the file and the segment, so a ghost binding is a violation naming the symbol instead of a smaller graph. Measured on this tree: 270 bound symbols, 169 resolved, 101 genuinely absent.

extract:lean reads the in-tree theorems through ONT-2a's own scan and rule (a file carrying `sorry` grounds nothing), joins them to the contracts that cite them by ONT-2a's name forms, and counts references that name nothing (lean_refs_unresolved) rather than guessing. 410 statements on this tree.

The W3C arm is two-sided. 16 of ONT-0's 32-case table are translated into the contract dialect and RUN BY THE GATE every time (ontology::w3c); the other 16 are listed by name with the form that puts them outside the subset, so the count is over the cases the subset claims and a case refused by design is never scored as a failure. A failing case declines Unknown{Differential} — never a corpus verdict. tests/oracle/ is a scratch crate DETACHED from the workspace with shacl pinned =0.3.21 (ONT-0's verdict ORACLE, R-13 release gate only); `make oracle` validates the ORIGINAL case files and the real corpus through it and writes the tracked tests/oracle/differential.json (17 cases, 0 disagreements). A planted sh:minCount change in shapes.ttl makes it disagree — measured.

Four conformance fixes the cases and the oracle found in the in-house validator:
- sh:class and targetClass use the rdfs:subClassOf closure (SHACL 2.1.1; W3C class-001), through one index per validate() instead of a scan per node — the corpus gate went 27.2s -> 6.9s in the same commit.
- sh:datatype checks the lexical form is WELL FORMED for the type ("300"^^xsd:byte is a violation; W3C datatype-ill-formed).
- sh:node reports ONE result per failing value on the outer path, with the nested findings as its detail (W3C node-001/-002).
- sh:in is TERM equality: an entry carries the datatype its YAML scalar gives it and the export writes it typed. Before, `in: ["true"]` accepted "true"^^xsd:boolean, which the oracle refuses — 490 results of difference on the real corpus, which is how it was found.

Sigma: code and lean implemented: true, concepts Symbol and Statement. The shapes bound-symbols-resolve and lean-statements-grounded ship COMPUTED AND REPORTED, not armed (§3.9): 101 ghosts and three admitted theorem files exist on main today, and a gate red on its own target the day it merges trains bypass. Arming is one string in lint-baseline.json for the release that cleans them.

check_ont_ratchet.sh, two defects fixed with rows that go red without the fix: entity_types_registered and extractors_implemented grepped the Rust for `EntityType::` and `impl Extractor`, two forms this codebase has never used, so both measured 0 while the baseline carried 1 and 1 from an older tree — they now read Sigma; and `--write` DELETED formal_prose and legacy_unresolved_depends_on, two shrink-only ratchets the sigma and relations gates read from that same file, so updating one ratchet disarmed two others. Self-test 17 -> 23 rows.

Known red, all three the same inherited assertion and none of them this row's: lint_passes_on_real_contracts, ont4b_shapes_gate::the_repo_corpus_passes_..., ont4c1_model_receipts::the_repo_corpus_reports_... assert the real corpus PASSES, and the armed ladder-measured finds 0 witnesses until the re-measured receipts of paiml#3523 are on main (paiml#3526's waiting note). Proven by measurement: with ladder-measured unarmed for one run the same corpus is Pass, 0 violations, 113 reported.

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

* ONT-4b2: rebased onto 45decaf (ONT-4c1 + paiml#3530 + paiml#3523 + paiml#3516 on main) — ordinal 343, the sh:in message carries the path AND the terms, regenerated census / contracts.nt / tree-reader registry (Refs PMAT-3509)

The branch was built on the reconciled 4c1 scratch base; only its two own
commits were replayed (`git rebase --onto origin/main bd89c88`). One real
conflict, shapes.rs check_lexical: paiml#3530 put the property path at the front
of the `in` message (`ont:study/scale: quadratic is not one of …`) and 4b2
renders the value and the list as TERMS (`sh:in` is term equality). Merged:
`<path>: <term> is not one of [<terms>]` — paiml#3530's CLI legs
(contains path ∧ value ∧ "is not one of") and 4b2's still hold. The four
generated files took main's copy and were regenerated with this tree's pv:
`pv extract contracts --check` 0; sigma Pass, relations Pass, shapes Pass
(shapes_n=5, focus_nodes_n=2318, symbols 169 resolved / 101 unresolved
reported, W3C 16/16). Ordinal 340 was apr-cli-pixel-regression on main;
4b2 takes 343 (342 is 4c1's).

Pmat-Ticket: PMAT-3509
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ont4c1 corpus test: ladder-green is one of the reported shapes, not the only one — ONT-4b2 reports two more unarmed (Refs PMAT-3509)

On the 4b2 tree not_armed_shapes is [ladder-green, bound-symbols-resolve, lean-statements-grounded]; the corpus test pinned the list to one entry, a fact about ONT-4c1's tree rather than about ONT-4c1. Membership is what the row promises (§3.9 reported-first); the fixture test keeps its exact-list form.

Pmat-Ticket: PMAT-3509
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* lint.rs: decide_named_gate's arms are expressions, not returns; drop the unused ShapesOutcome import (clippy 1.93 -D warnings) (Refs PMAT-3509)

Pmat-Ticket: PMAT-3509
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ont4c1 corpus test: ladder-green is reported and known (armed ∪ not_armed), never pinned to one side of the arming line (Refs PMAT-3509)

aprender-ba, on paiml#3528: the 0.68.2 bump (paiml#3498, head 5b22fb2) arms ladder-green, so a test asserting ladder-green ∈ not_armed_shapes goes red on the release branch the moment paiml#3528 lands under it. The row's fact is that the gate computes and reports the shape (a by_shape entry) and lists it on one side or the other; which side is the bump's, not the row's.

Pmat-Ticket: PMAT-3509
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ONT-4b2: make oracle without cd (.ONESHELL); the facades lock learns the syn edge; the ticket says 16 vendored + 16 accounted for, not 32; the differential re-run on the rebased tree (Refs PMAT-3509)

Quorum round 2 on bb738fa, three lanes, three findings, all real:

1. (flash, measured) `make oracle` under this Makefile's .ONESHELL: line 1376
   `cd tests/oracle && cargo build` moved the shell for line 1377, whose
   `$(cd tests/oracle …)` then failed with "No such file" and tried to run
   `/release/ont-oracle`. Both lines now use --manifest-path and no cd.
   Proven by running `make oracle-check` after the change: the oracle built,
   ran, and diffed — which is the second finding.

2. tests/oracle/differential.json was the pre-rebase tree's. Fresh on
   45decaf+4b2: cases=17, disagreements=0, corpus agree=true with 103
   violations on BOTH sides (was 113/113 — the corpus moved under the rebase:
   paiml#3530's entity-property fixes and main's contracts). The oracle and pv
   still agree on every case and on the corpus; the file is the run's output.

3. (pro-high, pro-low) the ticket title said "32 vendored W3C SHACL-Core
   cases"; the diff vendors 16 and accounts for the other 16 by excluding
   form (a constraint on the node shape itself, outside the subset), which
   `the_table_of_ont_0_is_accounted_for_case_by_case` asserts as 16 + 16 =
   32 with no id twice. The title now says that, and six acceptance criteria
   are written from the diff (not the plan) — the CLI leg's `n >= 16` is
   what is vendored. Roadmap re-aggregated.

Also: crates/facades/Cargo.lock regenerated for the `syn` edge
aprender-contracts gained (guard-tree R5 on bb738fa: "Cargo.lock is
stale"); `cargo metadata --locked` rc 0 in crates/facades.

Pmat-Ticket: PMAT-3509
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* audit: quorum-PMAT-3509 round 3 AGREED 3/3 on f5dd5d7 (Refs PMAT-3509)

Round 1 lapsed to the arming-neutral ladder-green assertion (aprender-ba's finding); round 2's three findings — the .ONESHELL cd in make oracle, the stale differential, the 32-vs-16 title — were each answered in the diff.

Pmat-Ticket: PMAT-3509
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* README: the CONTRACT_COUNT block states 1799 — ONT-4b2 adds contracts/ont-code-symbols-v1.yaml (make readme-sync) (Refs PMAT-3509)

guard-cargo on the merge tree: FALSIFY-README-002 (the block is generated, an equality, not a ratchet). Regenerated with the generator the guard names; readme-sync-check and check_readme_claims.sh pass.

Pmat-Ticket: PMAT-3509
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* audit: quorum-PMAT-3509 round 4 AGREED 3/3 on a692709 (Refs PMAT-3509)

Round 3's 3/3 lapsed to the README CONTRACT_COUNT regeneration (guard-cargo FALSIFY-README-002 on the merge tree).

Pmat-Ticket: PMAT-3509
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* audit: quorum-PMAT-3509 round 6 AGREED 3/3 on 3329c7c (Refs PMAT-3509)

Round 4's 3/3 lapsed to the merge of main ebda31d (paiml#3533 lifted the same function 4b2 had already split; resolution: keep decide_named_gate/decide_shapes_gate, drop single_gate_outcome). Round 5 was two PASS and one lane timed out at 25 min (no verdict, not a finding); round 6 agreed.

Pmat-Ticket: PMAT-3509
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Noah Gift <noah@paiml.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…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants