From 711eb73bad3def811845b109429f9f7f0a573c7c Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Thu, 17 Sep 2026 14:17:44 +0200 Subject: [PATCH 1/6] =?UTF-8?q?PMAT-3430:=20plan=20=E2=80=94=20GgmlType=20?= =?UTF-8?q?homes=20in=20aprender-quant,=2024=20keep=3F=20rows=20resolved,?= =?UTF-8?q?=20not=20yet=20reviewed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Home crate derived from cargo metadata: aprender-quant is a leaf under core, compute and serve. No code until the plan quorum is 3/3 (operator rule on #3421). Pmat-Ticket: PMAT-3430 Co-Authored-By: Claude Fable 5.1 --- docs/audits/impl-PMAT-3430-plan.md | 108 +++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 docs/audits/impl-PMAT-3430-plan.md diff --git a/docs/audits/impl-PMAT-3430-plan.md b/docs/audits/impl-PMAT-3430-plan.md new file mode 100644 index 0000000000..81e064e15f --- /dev/null +++ b/docs/audits/impl-PMAT-3430-plan.md @@ -0,0 +1,108 @@ +# PMAT-3430 plan — PP-QUANT-001 M1: one `GgmlType` + `TRAITS[43]` + +Status: **PLAN, not reviewed.** Operator rule (#3421, 2026-09-17): no code until the plan quorum is 3/3 on Q1 and X1. Inputs: Tables S / E / B / G of `docs/audits/impl-PMAT-3427-receipt.md` on draft PR #3447 (`d76b4a417`), measured on `origin/main` @ `7eb81a531`. Everything marked `[V]` below was re-measured for this plan on `origin/main` @ `ee684e94c`; `[U]` is unverified and says so. + +## 1. What is measured `[V]` + +### 1.1 Home crate — derived from `cargo metadata --locked`, normal-dependency closure, workspace members only + +| consumer | workspace crates in its closure | +|---|---| +| aprender-core | apr-format, aprender-common, aprender-compute, aprender-contracts-macros, aprender-gemm-codegen, aprender-quant | +| aprender-compute | aprender-contracts-macros, aprender-gemm-codegen, aprender-quant | +| aprender-serve | aprender-compute, aprender-contracts-macros, aprender-gemm-codegen, aprender-present-core, aprender-present-terminal, aprender-profile-core, aprender-quant | + +Intersection (self included): aprender-compute, aprender-contracts-macros, aprender-gemm-codegen, aprender-quant. Of those, three are leaves (no workspace deps): a proc-macro crate, a codegen crate, and **aprender-quant** (`[lib] name = "trueno_quant"`, one external dep `half`, 1641 lines, "K-quantization formats for GGUF/APR model weights", already exports `Q4_K_BLOCK_SIZE`/`Q4_K_BLOCK_BYTES`-style constants). + +**Proposed home: `aprender-quant`, new module `ggml_type`.** No new crate, so clean-room membership and publish order do not change. aprender-serve does not depend on aprender-core (normal deps), so core cannot be the home; compute could, but it is not the lowest and pulls 162 packages. + +### 1.2 The three enums disagree in more than their id sets + +| | compute `GgmlType` | core `GgmlType` | serve `GgmlQuantType` | +|---|---|---|---| +| file | aprender-compute/src/inference/gguf.rs:27 | aprender-core/src/format/gguf/types.rs:55 | aprender-serve/src/gguf/types.rs:101 | +| ids | 15 | 12 | 16 | +| k-quant variant spelling | `Q4K` | `Q4K` | `Q4K` | +| bf16 spelling | `Bf16` | — | `BF16` | +| only here | `Q8K` | `I8 I16 I32 I64 F64` | `IQ2XXS IQ2XS` | +| id → enum | `fn from_u32` (private) | — | `pub const fn from_id` | +| methods | `block_bytes block_size tensor_bytes` | none | `as_id as_byte as_str from_str_lossy Display` | + +**A size disagreement already exists:** compute's `block_bytes` gives `Q8_1 => 36`; Table S (gguf-py `GGML_QUANT_SIZES`) gives 40. `[U]`: ggml's C `block_q8_1` is two f16 + 32 bytes = 36, and gguf-py has carried `4 + 4 + 32` from the older f32 layout. The ticket's falsifier says "equals gguf-py for every shared id", which would bake in 40. Phase 2 must read both upstream files at `3173a5647` and the quorum is asked to rule (§2, Q1-d). + +## 2. Design (Q1) + +```rust +// crates/aprender-quant/src/ggml_type.rs (lib name: trueno_quant) +#[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum GgmlType { F32 = 0, F16 = 1, Q4_0 = 2, /* … 35 live variants, upstream ids … */ Q2_0 = 42 } + +pub enum GgmlFamily { Float, Int, Affine, KQuant, Codebook, Ternary, MicroscaleFp, Removed } +pub struct QuantTraits { pub name: &'static str, pub blck_size: u32, pub type_size: u32, pub family: GgmlFamily } +pub const TRAITS: [QuantTraits; 43] = [ /* indexed BY ID; the 8 removed ids are rows with family = Removed, sizes 0 */ ]; + +pub enum GgmlTypeError { Removed { id: u32, name: &'static str }, Unknown { id: u32 } } +impl GgmlType { + pub const fn from_id(id: u32) -> Result; // match, never transmute (unsafe_code = forbid) + pub const fn as_id(self) -> u32; pub const fn traits(self) -> &'static QuantTraits; + pub const fn name(self) -> &'static str; pub const fn blck_size(self) -> usize; pub const fn type_size(self) -> usize; + pub const fn tensor_bytes(self, n_elements: usize) -> Option; // checked: None on overflow or n % blck_size != 0 +} +``` + +Decisions put to the quorum: + +- **Q1-a. 35 variants, 43 table rows.** A removed id is not constructible; `from_id(4)` is `Err(Removed{..})`, distinct from `Err(Unknown{..})`. The table stays dense so `TRAITS[id]` is the lookup and a `const` assertion proves `TRAITS[v.as_id()].name == v.name()` for every variant. +- **Q1-b. Variant spelling.** One spelling. Proposal: keep the tree's existing `Q4K`-style (all three enums agree on it; 151 arm/ref sites in Table G use it) and `BF16`; compute's `Bf16` becomes an associated const alias `pub const Bf16: Self = Self::BF16` inside the quant crate — usable in expressions and patterns — so M1 touches no dispatch arm. Aliases are deleted in Q2. +- **Q1-c. M1 is behaviour-preserving: no crate accepts an id it refused before.** Going from 12/15/16 to 35 variants would silently widen every `match` that ends in `_ =>`. So each crate keeps a thin, named admission function (`supported(GgmlType) -> bool`, one each in compute, core and serve) that reproduces its former id set exactly, and its parser calls it. Matches without a wildcard stop compiling — that is the compiler enumerating the sites, and each gets an explicit refusal arm, not a `_`. Widening admission is M4 (#3432) / Q2, not this ticket. +- **Q1-d. Q8_1 type_size** — 36 (ggml C struct, what compute has today) or 40 (gguf-py, what the ticket's falsifier names). Proposal: the truth source for `type_size` is `sizeof` in ggml's C headers at the pinned sha; gguf-py is the cross-check; a disagreement between the two upstream sources is recorded in the contract as a named exception, never silently resolved. +- **Q1-e. Re-exports.** compute: `pub use trueno_quant::GgmlType;`. core: same. serve: `pub use trueno_quant::GgmlType as GgmlQuantType;` plus `pub use … GgmlType`. serve's `as_str`/`from_str_lossy`/`as_byte`/`Display` move into the quant crate (inherent impls cannot live downstream of a re-export); every id ≤ 42 fits a `u8`, so `as_byte` stays infallible. +- **Q1-f. #3405's private `QTYPE_LABELS` (13 rows)** is replaced by `TRAITS[id].name` in this ticket — it is a fourth table of the same fact. + +### Table E — the 24 "keep?" rows resolved + +Rule applied: `merge` iff the enum's variants are ggml tensor types and nothing else (a subset of Table S by meaning, not by spelling); `keep(concept)` names the different concept. **Execution of every `merge` below is Q2 (#3423 Phase 2), not M1** — M1 lands the target they merge into. + +| enum | file | verdict | +|---|---|---| +| `QuantKernel` | aprender-cgp profilers/quant.rs:9 | keep(kernel identity — `Nf4Gemv` is not a ggml type) | +| `QuantType` | aprender-core format/quantize.rs:39 | keep(APR's own on-disk id space: `0x01/0x02/0x10/0xFF`, has `Q8Tensor`, `Custom`) | +| `GgufValue` | aprender-cbtop quantize/gguf.rs:24 | keep(metadata value, not tensor type) | +| `AprQuantizationType` | aprender-serve apr_transformer/loader.rs:303 | merge (F32/Q4_K/Q8_0 — a ggml subset used as a loader selector) | +| `Quantization` | aprender-train-inspect convert.rs:113 | merge (Q4_0/Q8_0/F16) | +| `KvQuantType` | aprender-serve paged_kv:254 | keep(KV-cache precision: FP32/Q8/Q4 are cache layouts, not ggml block formats) | +| `QuantizedKvData` | aprender-serve paged_kv:410 | keep(data carrier, not a type id) | +| `GgufQuantization` | aprender-train gguf_writer.rs:13 | merge → `Option` (`None` variant is "don't quantize") | +| `GGUFQuantType` | aprender-train quant_type.rs:5 | merge (Q4_0/Q8_0) | +| `QuantScheme` | apr-cli quantize.rs:27 | keep(user-facing scheme: Int8/Int4/Fp16/Q4K — maps onto types, is not one) | +| `QuantizationType` | aprender-core converter_types_expectations.rs:149 | keep(same scheme concept as the row above; these two duplicate EACH OTHER — file for Q2) | +| `QuantizationType` | aprender-registry lineage/mod.rs:53 | keep(lineage metadata: has `Dynamic`) | +| `AutoQuantError` · `QuantizationError` · `QuantPublishError` | apr-cli · orchestrate · train | keep(error types) ×3 | +| `QuantizeArgvVerdict` | apr-cli quantize_flag_parity.rs:49 | keep(verdict) | +| `QuantScheme` | aprender-cbtop grammar/transform.rs:7 | keep(grammar) | +| `QuantizationType` | aprender-core demo/mod.rs:344 · stack/mod.rs:223 | keep(demo / stack descriptor) ×2 | +| `GgufValue` | aprender-core format/gguf/types.rs:84 | keep(metadata value) | +| `QuantFamily` | aprender-serve quantize/format_trait.rs:31 | keep(kernel-format family, 2 variants) — which is why §2 names the new enum `GgmlFamily`, not `QuantFamily`; revisit in Q2 | +| `QuantMethod` · `QuantGranularity` · `QuantMode` | aprender-train | keep(training-time quantization config) ×3 | + +Count: 4 merge · 20 keep(concept). + +## 3. Ordering (X1) + +Q1 (this ticket) → T1/T2 (#3433/#3434) → Q2 → Q3. Evidence: 14 of 34 serve raw-integer qtype sites (41 %, file-level) sit in a file that uses a tensor carrier; the falsifier for running T1 ∥ Q2 was < 20 %. Nothing in §1–§2 changes that: M1 retypes no `qtype: u32` field (Table B's 53 rows are untouched), so it does not collide with T1. + +## 4. Phases and acceptance commands + +| # | phase | scope_paths | `A_i` | +|---|---|---|---| +| 1 | RED: falsifier guard + size fixture | scripts/check_one_ggml_type_enum.sh, contracts/ggml-type-v1.yaml, crates/aprender-quant/tests/ | `bash scripts/check_one_ggml_type_enum.sh` exits 1 naming 3 definitions; its case table (must-match / must-not-match, incl. `GgufValueType`) passes | +| 2 | `GgmlType` + `TRAITS[43]` in aprender-quant | crates/aprender-quant/ | `cargo test -p aprender-quant --lib ggml_type` — every live id round-trips, 8 removed ids are `Err(Removed)`, `TRAITS` equals the pinned upstream size fixture row by row | +| 3 | compute + core re-export, admission functions | crates/aprender-compute/src/inference/, crates/aprender-core/src/format/gguf/ | `cargo test -p aprender-compute --lib gguf && cargo test -p aprender-core --lib format::gguf` + an admission test per crate: former id set in, everything else refused | +| 4 | serve re-export, `QTYPE_LABELS` removed | crates/aprender-serve/src/gguf/, the #3405 file | `cargo test -p aprender-serve --lib gguf::types` + `bash scripts/check_one_ggml_type_enum.sh` exits **0** | +| 5 | DoD: `pv validate`, mutation (re-add a second enum → guard RED), diff quorum, receipt, PR | docs/audits/ | `make gate` | + +Phases 3 and 4 have disjoint scopes and may run together. The guard is wired into the same PR's CI selection line or it is dark. + +## 5. Not in this ticket + +Retyping `qtype: u32` fields (Table B) — T1/Q2. The 558 bare-integer lines — #3431. `tensor_byte_size` single refusal — #3432. Executing the 4 Table E merges and the 10 merge-candidates — #3423 Phase 2. Widening any crate's admitted id set. From 3f70ae052f742866f42e941aad4015101675d222 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Thu, 17 Sep 2026 14:39:42 +0200 Subject: [PATCH 2/6] =?UTF-8?q?PMAT-3430:=20plan=20v2=20=E2=80=94=20the=20?= =?UTF-8?q?grill=20failed=20v1=203/3;=20every=20finding=20re-run,=20then?= =?UTF-8?q?=20answered?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rustc reproduces E0308 for an associated-const pattern on &self, so the Bf16 alias is withdrawn. Upstream read at the pinned sha: block_q8_1 is static_asserted at 36 bytes, gguf-py says 40. Admission moves from the GGUF parser to all 7 serve boundaries, proven by characterization tests taken before the change. Pmat-Ticket: PMAT-3430 Co-Authored-By: Claude Fable 5.1 --- docs/audits/impl-PMAT-3430-plan.md | 89 ++++++++++++++++++------------ 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/docs/audits/impl-PMAT-3430-plan.md b/docs/audits/impl-PMAT-3430-plan.md index 81e064e15f..74dba9ea31 100644 --- a/docs/audits/impl-PMAT-3430-plan.md +++ b/docs/audits/impl-PMAT-3430-plan.md @@ -1,6 +1,6 @@ # PMAT-3430 plan — PP-QUANT-001 M1: one `GgmlType` + `TRAITS[43]` -Status: **PLAN, not reviewed.** Operator rule (#3421, 2026-09-17): no code until the plan quorum is 3/3 on Q1 and X1. Inputs: Tables S / E / B / G of `docs/audits/impl-PMAT-3427-receipt.md` on draft PR #3447 (`d76b4a417`), measured on `origin/main` @ `7eb81a531`. Everything marked `[V]` below was re-measured for this plan on `origin/main` @ `ee684e94c`; `[U]` is unverified and says so. +Status: **PLAN v2 — v1 was grilled 3/3 FAIL (§6); this revision answers every finding; not yet re-reviewed.** Operator rule (#3421, 2026-09-17): no code until the plan quorum is 3/3 on Q1 and X1. Inputs: Tables S / E / B / G of `docs/audits/impl-PMAT-3427-receipt.md` on draft PR #3447 (`d76b4a417`), measured on `origin/main` @ `7eb81a531`. Everything marked `[V]` below was re-measured for this plan on `origin/main` @ `ee684e94c`; `[U]` is unverified and says so. ## 1. What is measured `[V]` @@ -20,7 +20,7 @@ Intersection (self included): aprender-compute, aprender-contracts-macros, apren | | compute `GgmlType` | core `GgmlType` | serve `GgmlQuantType` | |---|---|---|---| -| file | aprender-compute/src/inference/gguf.rs:27 | aprender-core/src/format/gguf/types.rs:55 | aprender-serve/src/gguf/types.rs:101 | +| file | crates/aprender-compute/src/inference/gguf.rs:27 | crates/aprender-core/src/format/gguf/types.rs:55 | crates/aprender-serve/src/gguf/types.rs:101 | | ids | 15 | 12 | 16 | | k-quant variant spelling | `Q4K` | `Q4K` | `Q4K` | | bf16 spelling | `Bf16` | — | `BF16` | @@ -28,7 +28,7 @@ Intersection (self included): aprender-compute, aprender-contracts-macros, apren | id → enum | `fn from_u32` (private) | — | `pub const fn from_id` | | methods | `block_bytes block_size tensor_bytes` | none | `as_id as_byte as_str from_str_lossy Display` | -**A size disagreement already exists:** compute's `block_bytes` gives `Q8_1 => 36`; Table S (gguf-py `GGML_QUANT_SIZES`) gives 40. `[U]`: ggml's C `block_q8_1` is two f16 + 32 bytes = 36, and gguf-py has carried `4 + 4 + 32` from the older f32 layout. The ticket's falsifier says "equals gguf-py for every shared id", which would bake in 40. Phase 2 must read both upstream files at `3173a5647` and the quorum is asked to rule (§2, Q1-d). +**A size disagreement already exists:** compute's `block_bytes` gives `Q8_1 => 36`; Table S (gguf-py `GGML_QUANT_SIZES`) gives 40. Both upstream sources were read at the pinned sha — see Q1-d. ## 2. Design (Q1) @@ -43,21 +43,23 @@ pub const TRAITS: [QuantTraits; 43] = [ /* indexed BY ID; the 8 removed ids are pub enum GgmlTypeError { Removed { id: u32, name: &'static str }, Unknown { id: u32 } } impl GgmlType { - pub const fn from_id(id: u32) -> Result; // match, never transmute (unsafe_code = forbid) + pub const fn from_id(id: u32) -> Option; // same signature serve's callers use today; match, never transmute + pub const fn try_from_id(id: u32) -> Result; // names WHY: Removed vs Unknown pub const fn as_id(self) -> u32; pub const fn traits(self) -> &'static QuantTraits; pub const fn name(self) -> &'static str; pub const fn blck_size(self) -> usize; pub const fn type_size(self) -> usize; - pub const fn tensor_bytes(self, n_elements: usize) -> Option; // checked: None on overflow or n % blck_size != 0 + pub const fn tensor_bytes(self, n_elements: usize) -> usize; // compute's existing signature and rounding, unchanged + pub const fn checked_tensor_bytes(self, n_elements: usize) -> Option; // None on overflow or n % blck_size != 0; M4 (#3432) adopts it } ``` Decisions put to the quorum: -- **Q1-a. 35 variants, 43 table rows.** A removed id is not constructible; `from_id(4)` is `Err(Removed{..})`, distinct from `Err(Unknown{..})`. The table stays dense so `TRAITS[id]` is the lookup and a `const` assertion proves `TRAITS[v.as_id()].name == v.name()` for every variant. -- **Q1-b. Variant spelling.** One spelling. Proposal: keep the tree's existing `Q4K`-style (all three enums agree on it; 151 arm/ref sites in Table G use it) and `BF16`; compute's `Bf16` becomes an associated const alias `pub const Bf16: Self = Self::BF16` inside the quant crate — usable in expressions and patterns — so M1 touches no dispatch arm. Aliases are deleted in Q2. -- **Q1-c. M1 is behaviour-preserving: no crate accepts an id it refused before.** Going from 12/15/16 to 35 variants would silently widen every `match` that ends in `_ =>`. So each crate keeps a thin, named admission function (`supported(GgmlType) -> bool`, one each in compute, core and serve) that reproduces its former id set exactly, and its parser calls it. Matches without a wildcard stop compiling — that is the compiler enumerating the sites, and each gets an explicit refusal arm, not a `_`. Widening admission is M4 (#3432) / Q2, not this ticket. -- **Q1-d. Q8_1 type_size** — 36 (ggml C struct, what compute has today) or 40 (gguf-py, what the ticket's falsifier names). Proposal: the truth source for `type_size` is `sizeof` in ggml's C headers at the pinned sha; gguf-py is the cross-check; a disagreement between the two upstream sources is recorded in the contract as a named exception, never silently resolved. -- **Q1-e. Re-exports.** compute: `pub use trueno_quant::GgmlType;`. core: same. serve: `pub use trueno_quant::GgmlType as GgmlQuantType;` plus `pub use … GgmlType`. serve's `as_str`/`from_str_lossy`/`as_byte`/`Display` move into the quant crate (inherent impls cannot live downstream of a re-export); every id ≤ 42 fits a `u8`, so `as_byte` stays infallible. -- **Q1-f. #3405's private `QTYPE_LABELS` (13 rows)** is replaced by `TRAITS[id].name` in this ticket — it is a fourth table of the same fact. +- **Q1-a. 35 variants, 43 table rows.** A removed id is not constructible; `try_from_id(4)` is `Err(Removed{..})`, distinct from `Err(Unknown{..})`; `from_id(4)` is `None`. The table stays dense so `TRAITS[id]` is the lookup and a `const` assertion proves `TRAITS[v.as_id()].name == v.name()` for every variant. +- **Q1-b. Variant spelling — one spelling, NO aliases.** Keep the tree's `Q4K`-style (all three enums agree on it) and `BF16`. v1 proposed an associated-const alias `Bf16`; that is withdrawn: measured with rustc, `match self { Self::Bf16 => … }` on `&self` is **E0308** (a const pattern gets no default-binding-mode deref, unlike a variant pattern), it needs `#[allow(non_upper_case_globals)]`, and a glob import does not carry it. Instead compute's `GgmlType::Bf16` sites are renamed to `BF16` in Phase 3 (2 files: `crates/aprender-compute/src/inference/gguf.rs`, `…/inference/model.rs`; the `Bf16` in `brick/tracing/quant_type.rs` belongs to a different enum and is not touched). +- **Q1-c. M1 is behaviour-preserving: no crate accepts an id or a name it refused before — enforced at every id→enum and name→enum BOUNDARY, not only at the GGUF parser.** v1 gated the parser only; the grill found boundaries outside it. The measured list (serve): `gguf/dtype.rs:17` and `:54`, `infer/mod.rs:39`, `apr/mod.rs:324` (APR dtype byte), `apr/special_tokens.rs:210`, `apr/dequant.rs:235`, test `gguf/tests/loader_tests_apr.rs:311`. compute: private `from_u32` in `inference/gguf.rs`. core: `format/gguf/reader.rs`. Mechanism: each crate gets ONE admission function (`admitted_from_id(u32) -> Option`, and in serve `admitted_from_name(&str)`) that is `GgmlType::from_id` filtered by a `const ADMITTED: [GgmlType; N]` reproducing the former set exactly (compute 15, core 12, serve 16), and every boundary above calls it instead of `from_id`/`from_str_lossy`. **Proof is a characterization test written in Phase 1, before any change:** for each boundary function, its output for every id 0..=255 (and every name in `TRAITS` plus case variants) is snapshotted on `origin/main`, and the same test must pass unchanged after Phases 3–4. Wildcard arms (`_ =>`; ~22 across 8 files that name these enums, e.g. `crates/aprender-compute/src/inference/model.rs:734` "quantized norms … return zeros") are therefore not widened in practice: they are already reached today by admitted quantized ids, and a non-admitted variant can only arrive by a new literal in new code. Phase 3/4 receipts still list every wildcard site with its disposition. Widening admission is M4 (#3432) / Q2. +- **Q1-d. Q8_1 `type_size` = 36. Measured at llama.cpp `3173a56471c1753650cd806694145ffd6dcace67`:** `ggml/src/ggml-common.h:269` is `static_assert(sizeof(block_q8_1) == 2*sizeof(ggml_half) + QK8_1, "wrong q8_1 block size/padding")` = 36 — the assert rules out padding, so `sizeof` IS the on-disk block — while `gguf-py/gguf/constants.py:5678` says `(32, 4 + 4 + 32)` = 40 (the pre-f16 layout). Truth source for `type_size` is the `static_assert`ed C struct; gguf-py is the cross-check. The falsifier becomes: `TRAITS` equals gguf-py `GGML_QUANT_SIZES` for every live id **except a named exception list, today exactly `[Q8_1]`**, each exception carrying both upstream file:line citations in `contracts/ggml-type-v1.yaml`. The test fails if an exception stops being a disagreement (so the list cannot rot). This amends the ticket's falsifier wording; the amendment goes on #3430 with the verdict grid. (Grill: lanes 2, 3 ruled 36; lane 1 ruled 40 on a padding argument the `static_assert` refutes.) +- **Q1-e. Re-exports.** compute: `pub use trueno_quant::GgmlType;`. core: same. serve: `pub use trueno_quant::GgmlType as GgmlQuantType;` plus `GgmlType`. serve's `from_id`/`as_id`/`as_byte`/`as_str`/`from_str_lossy`/`Display` and compute's `block_bytes`/`block_size`/`tensor_bytes` move into the leaf with **unchanged names and signatures** (all three lanes: no body depends on a crate-local type; `TruenoError` is used by the parser, not the enum). `from_id` stays `Option` so `apr/mod.rs:325`'s zero-arg `map_or_else` and `loader_tests_apr.rs:311`'s `.is_some()` compile untouched; `block_bytes` stays as a name alongside `type_size`. +- **Q1-f. #3405's private `QTYPE_LABELS` (13 rows, `crates/aprender-serve/src/gguf/qwen3_moe_load.rs:90`)** is replaced by `TRAITS[id].name` in this ticket — it is a fourth table of the same fact. ### Table E — the 24 "keep?" rows resolved @@ -65,25 +67,25 @@ Rule applied: `merge` iff the enum's variants are ggml tensor types and nothing | enum | file | verdict | |---|---|---| -| `QuantKernel` | aprender-cgp profilers/quant.rs:9 | keep(kernel identity — `Nf4Gemv` is not a ggml type) | -| `QuantType` | aprender-core format/quantize.rs:39 | keep(APR's own on-disk id space: `0x01/0x02/0x10/0xFF`, has `Q8Tensor`, `Custom`) | -| `GgufValue` | aprender-cbtop quantize/gguf.rs:24 | keep(metadata value, not tensor type) | -| `AprQuantizationType` | aprender-serve apr_transformer/loader.rs:303 | merge (F32/Q4_K/Q8_0 — a ggml subset used as a loader selector) | -| `Quantization` | aprender-train-inspect convert.rs:113 | merge (Q4_0/Q8_0/F16) | -| `KvQuantType` | aprender-serve paged_kv:254 | keep(KV-cache precision: FP32/Q8/Q4 are cache layouts, not ggml block formats) | -| `QuantizedKvData` | aprender-serve paged_kv:410 | keep(data carrier, not a type id) | -| `GgufQuantization` | aprender-train gguf_writer.rs:13 | merge → `Option` (`None` variant is "don't quantize") | -| `GGUFQuantType` | aprender-train quant_type.rs:5 | merge (Q4_0/Q8_0) | -| `QuantScheme` | apr-cli quantize.rs:27 | keep(user-facing scheme: Int8/Int4/Fp16/Q4K — maps onto types, is not one) | -| `QuantizationType` | aprender-core converter_types_expectations.rs:149 | keep(same scheme concept as the row above; these two duplicate EACH OTHER — file for Q2) | -| `QuantizationType` | aprender-registry lineage/mod.rs:53 | keep(lineage metadata: has `Dynamic`) | -| `AutoQuantError` · `QuantizationError` · `QuantPublishError` | apr-cli · orchestrate · train | keep(error types) ×3 | -| `QuantizeArgvVerdict` | apr-cli quantize_flag_parity.rs:49 | keep(verdict) | -| `QuantScheme` | aprender-cbtop grammar/transform.rs:7 | keep(grammar) | -| `QuantizationType` | aprender-core demo/mod.rs:344 · stack/mod.rs:223 | keep(demo / stack descriptor) ×2 | -| `GgufValue` | aprender-core format/gguf/types.rs:84 | keep(metadata value) | -| `QuantFamily` | aprender-serve quantize/format_trait.rs:31 | keep(kernel-format family, 2 variants) — which is why §2 names the new enum `GgmlFamily`, not `QuantFamily`; revisit in Q2 | -| `QuantMethod` · `QuantGranularity` · `QuantMode` | aprender-train | keep(training-time quantization config) ×3 | +| `QuantKernel` | crates/aprender-cgp/src/profilers/quant.rs:9 | keep(kernel identity — `Nf4Gemv` is not a ggml type) | +| `QuantType` | crates/aprender-core/src/format/quantize.rs:39 | keep(APR's own on-disk id space: `0x01/0x02/0x10/0xFF`, has `Q8Tensor`, `Custom`) | +| `GgufValue` | crates/aprender-cbtop/src/quantize/gguf.rs:24 | keep(metadata value, not tensor type) | +| `AprQuantizationType` | crates/aprender-serve/src/apr_transformer/loader.rs:303 | merge (F32/Q4_K/Q8_0 — a ggml subset used as a loader selector) | +| `Quantization` | crates/aprender-train-inspect/src/convert.rs:113 | merge (Q4_0/Q8_0/F16) | +| `KvQuantType` | crates/aprender-serve/src/paged_kv/mod_compute_prefix.rs:254 | keep(KV-cache precision: FP32/Q8/Q4 are cache layouts, not ggml block formats) — **dissent:** lane 1 would merge, lanes 2 and 3 keep; Q2 re-rules after reading the cache's block layout | +| `QuantizedKvData` | crates/aprender-serve/src/paged_kv/mod_compute_prefix.rs:410 | keep(data carrier, not a type id) | +| `GgufQuantization` | crates/aprender-train/src/hf_pipeline/export/gguf_writer.rs:13 | merge → `Option` (`None` variant is "don't quantize") | +| `GGUFQuantType` | crates/aprender-train/src/quant/gguf_quant/quant_type.rs:5 | merge (Q4_0/Q8_0) | +| `QuantScheme` | crates/apr-cli/src/commands/quantize.rs:27 | keep(user-facing scheme: Int8/Int4/Fp16/Q4K — maps onto types, is not one) | +| `QuantizationType` | crates/aprender-core/src/format/converter_types_expectations.rs:149 | keep(same scheme concept as the row above; these two duplicate EACH OTHER — file for Q2) | +| `QuantizationType` | crates/aprender-registry/src/lineage/mod.rs:53 | keep(lineage metadata: has `Dynamic`) | +| `AutoQuantError` · `QuantizationError` · `QuantPublishError` | crates/apr-cli/src/commands/auto_quant.rs:100 · crates/aprender-orchestrate/src/oracle/rag/quantization/error.rs:7 · crates/aprender-train/src/hf_pipeline/export/publish_pipeline.rs:66 | keep(error types) ×3 | +| `QuantizeArgvVerdict` | crates/apr-cli/src/commands/quantize_flag_parity.rs:49 | keep(verdict) | +| `QuantScheme` | crates/aprender-cbtop/src/grammar/transform.rs:7 | keep(grammar) | +| `QuantizationType` | crates/aprender-core/src/demo/mod.rs:344 · crates/aprender-core/src/stack/mod.rs:223 | keep(demo / stack descriptor) ×2 | +| `GgufValue` | crates/aprender-core/src/format/gguf/types.rs:84 | keep(metadata value) | +| `QuantFamily` | crates/aprender-serve/src/quantize/format_trait.rs:31 | keep(kernel-format family, 2 variants) — which is why §2 names the new enum `GgmlFamily`, not `QuantFamily`; revisit in Q2 | +| `QuantMethod` · `QuantGranularity` · `QuantMode` | crates/aprender-train/src/config/cli/quant_merge.rs:76 · crates/aprender-train/src/quant/granularity/types.rs:7, :19 | keep(training-time quantization config) ×3 | Count: 4 merge · 20 keep(concept). @@ -95,14 +97,31 @@ Q1 (this ticket) → T1/T2 (#3433/#3434) → Q2 → Q3. Evidence: 14 of 34 serve | # | phase | scope_paths | `A_i` | |---|---|---|---| -| 1 | RED: falsifier guard + size fixture | scripts/check_one_ggml_type_enum.sh, contracts/ggml-type-v1.yaml, crates/aprender-quant/tests/ | `bash scripts/check_one_ggml_type_enum.sh` exits 1 naming 3 definitions; its case table (must-match / must-not-match, incl. `GgufValueType`) passes | -| 2 | `GgmlType` + `TRAITS[43]` in aprender-quant | crates/aprender-quant/ | `cargo test -p aprender-quant --lib ggml_type` — every live id round-trips, 8 removed ids are `Err(Removed)`, `TRAITS` equals the pinned upstream size fixture row by row | -| 3 | compute + core re-export, admission functions | crates/aprender-compute/src/inference/, crates/aprender-core/src/format/gguf/ | `cargo test -p aprender-compute --lib gguf && cargo test -p aprender-core --lib format::gguf` + an admission test per crate: former id set in, everything else refused | -| 4 | serve re-export, `QTYPE_LABELS` removed | crates/aprender-serve/src/gguf/, the #3405 file | `cargo test -p aprender-serve --lib gguf::types` + `bash scripts/check_one_ggml_type_enum.sh` exits **0** | -| 5 | DoD: `pv validate`, mutation (re-add a second enum → guard RED), diff quorum, receipt, PR | docs/audits/ | `make gate` | +| 1 | RED: falsifier guard, upstream size fixture, boundary characterization tests (snapshot taken on unchanged code) | scripts/check_one_ggml_type_enum.sh, contracts/ggml-type-v1.yaml, crates/aprender-quant/tests/, crates/aprender-serve/src/gguf/tests/, crates/aprender-compute/src/inference/, crates/aprender-core/src/format/gguf/tests/ | `bash scripts/check_one_ggml_type_enum.sh` exits 1 naming exactly 3 definitions; its case table passes (must-match: the 3; must-not-match: `GgufValueType`, `GgufValue`, `QuantType`, a commented-out enum); characterization tests GREEN on unchanged code | +| 2 | `GgmlType` + `TRAITS[43]` in aprender-quant | crates/aprender-quant/ | `cargo test -p aprender-quant` (lib AND tests/): every live id round-trips, 8 removed ids are `None` / `Err(Removed)`, `TRAITS` vs the pinned fixture with the exception list exactly `[Q8_1]` | +| 3 | compute + core re-export, admission, `Bf16`→`BF16` | crates/aprender-compute/src/inference/, crates/aprender-core/src/format/ | `cargo test -p aprender-compute --lib -- inference:: && cargo test -p aprender-core --lib -- format::` — includes the Phase 1 characterization tests, unchanged | +| 4 | serve re-export, admission at all 7 boundaries, `QTYPE_LABELS` removed | crates/aprender-serve/src/gguf/, crates/aprender-serve/src/apr/, crates/aprender-serve/src/infer/, crates/aprender-serve/tests/apr_coverage.rs | `cargo test -p aprender-serve --lib -- gguf:: apr:: infer:: && cargo test -p aprender-serve --test apr_coverage` + `bash scripts/check_one_ggml_type_enum.sh` exits **0** + `cargo check --workspace --all-targets` (apr-cli's `tests_trace_dispatch.rs` uses core's re-export) | +| 5 | DoD: `pv validate`, mutation (re-add a second enum → guard RED; change one `ADMITTED` set → characterization RED; set `TRAITS[9].type_size = 40` → fixture RED), diff quorum, receipt, PR | docs/audits/, .github CI selection line (needs the operator's web-UI merge click) | `make gate` | Phases 3 and 4 have disjoint scopes and may run together. The guard is wired into the same PR's CI selection line or it is dark. ## 5. Not in this ticket Retyping `qtype: u32` fields (Table B) — T1/Q2. The 558 bare-integer lines — #3431. `tensor_byte_size` single refusal — #3432. Executing the 4 Table E merges and the 10 merge-candidates — #3423 Phase 2. Widening any crate's admitted id set. + +## 6. Grill record — v1, 2026-09-17, 3 lanes (gemini-3.1-pro-high, gemini-3.8-flash-high, gemini-3.7-flash-high; author fable-5-1): **0/3 PASS** + +Reducer: `agreed=false`, `partial=true` (each lane's `.err` had 2 lines beyond workspace narration). Every finding below was re-executed by the orchestrator before being accepted. + +| finding | lanes | re-run | disposition in v2 | +|---|---|---|---| +| assoc-const alias `Bf16` fails in `match self` on `&self` | 2 (E0308), 3 (lint, glob) ; 1 said it works | rustc: **E0308 reproduced** — lane 1 was wrong | Q1-b: aliases withdrawn, rename 2 files | +| admission at the GGUF parser only; `apr/mod.rs:324` and 5 more boundaries bypass it | 1, 2, 3 | grep: 7 serve boundaries confirmed | Q1-c rewritten; characterization tests | +| `from_id → Result` and `tensor_bytes → Option` break callers | 2, 3 | `apr/mod.rs:325`, `loader_tests_apr.rs:311` confirmed | signatures kept; `try_from_id` / `checked_tensor_bytes` added | +| Q8_1: 36 vs 40 | 2, 3 → 36 ; 1 → 40 | upstream read at the full sha: C `static_assert` = 36, gguf-py = 40 | Q1-d: 36, named exception | +| `KvQuantType` should merge | 1 ; 3 agrees with keep | not re-run (Q2 executes it) | recorded as dissent in Table E | +| Phase 2 `--lib` filter skips `tests/`; Phase 4 `gguf::types` exercises 0 `GgmlQuantType` sites | 2, 3 | — | §4 acceptance commands widened | +| scope_paths miss `serve/src/apr/`, `serve/src/infer/`, core `format/` siblings, the `QTYPE_LABELS` file; Table E paths not repo-relative | 1, 2, 3 | usage footprint measured: core 22 files under `format/`, serve 9 files in 4 dirs + `tests/` | §4 and Table E corrected | +| home crate `aprender-quant`: sound, no cycle | 1, 2, 3 | cargo metadata (§1.1) | unchanged | +| X1 ordering | no lane contradicted it | — | unchanged | + From eeaac2415a9864fe9611ee958bf85f521d7bfee6 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Thu, 17 Sep 2026 15:01:18 +0200 Subject: [PATCH 3/6] =?UTF-8?q?PMAT-3430:=20plan=20v3=20=E2=80=94=20round?= =?UTF-8?q?=202=20was=201/3;=20core=20has=20no=20id=20boundary,=20and=20it?= =?UTF-8?q?s=20byte=5Fsize=20sizes=20Q4=5F1=20at=2018?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All three lanes found the exhaustive match in GgufTensor::byte_size; re-running it showed its Q4_1 arm is 18 bytes per block where upstream is 20. Delegating to the single table fixes that, so it is named as the one intended behaviour delta and pinned by a test that flips. Pmat-Ticket: PMAT-3430 Co-Authored-By: Claude Fable 5.1 --- docs/audits/impl-PMAT-3430-plan.md | 44 ++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/docs/audits/impl-PMAT-3430-plan.md b/docs/audits/impl-PMAT-3430-plan.md index 74dba9ea31..e251f6e386 100644 --- a/docs/audits/impl-PMAT-3430-plan.md +++ b/docs/audits/impl-PMAT-3430-plan.md @@ -1,6 +1,6 @@ # PMAT-3430 plan — PP-QUANT-001 M1: one `GgmlType` + `TRAITS[43]` -Status: **PLAN v2 — v1 was grilled 3/3 FAIL (§6); this revision answers every finding; not yet re-reviewed.** Operator rule (#3421, 2026-09-17): no code until the plan quorum is 3/3 on Q1 and X1. Inputs: Tables S / E / B / G of `docs/audits/impl-PMAT-3427-receipt.md` on draft PR #3447 (`d76b4a417`), measured on `origin/main` @ `7eb81a531`. Everything marked `[V]` below was re-measured for this plan on `origin/main` @ `ee684e94c`; `[U]` is unverified and says so. +Status: **PLAN v3 — v1 grilled 0/3, v2 grilled 1/3 (§6); this revision answers every finding of both rounds; not yet re-reviewed.** Operator rule (#3421, 2026-09-17): no code until the plan quorum is 3/3 on Q1 and X1. Inputs: Tables S / E / B / G of `docs/audits/impl-PMAT-3427-receipt.md` on draft PR #3447 (`d76b4a417`), measured on `origin/main` @ `7eb81a531`. Everything marked `[V]` below was re-measured for this plan on `origin/main` @ `ee684e94c`; `[U]` is unverified and says so. ## 1. What is measured `[V]` @@ -42,24 +42,34 @@ pub struct QuantTraits { pub name: &'static str, pub blck_size: u32, pub type_si pub const TRAITS: [QuantTraits; 43] = [ /* indexed BY ID; the 8 removed ids are rows with family = Removed, sizes 0 */ ]; pub enum GgmlTypeError { Removed { id: u32, name: &'static str }, Unknown { id: u32 } } -impl GgmlType { - pub const fn from_id(id: u32) -> Option; // same signature serve's callers use today; match, never transmute - pub const fn try_from_id(id: u32) -> Result; // names WHY: Removed vs Unknown - pub const fn as_id(self) -> u32; pub const fn traits(self) -> &'static QuantTraits; - pub const fn name(self) -> &'static str; pub const fn blck_size(self) -> usize; pub const fn type_size(self) -> usize; - pub const fn tensor_bytes(self, n_elements: usize) -> usize; // compute's existing signature and rounding, unchanged +impl GgmlType { // THE COMPLETE leaf API. Every name and signature that exists on any of the three enums today is here, unchanged. + // from serve's GgmlQuantType (crates/aprender-serve/src/gguf/types.rs:136-221): + pub const fn from_id(id: u32) -> Option; // match, never transmute; all 35 live ids + pub const fn as_id(self) -> u32; + pub const fn as_byte(self) -> u8; // every id <= 42 + pub const fn as_str(self) -> &'static str; // == TRAITS[id].name; measured equal to serve's 16 strings today ("Q4_K", "IQ2_XXS", "BF16", …) + pub fn from_str_lossy(s: &str) -> Option; // exact upstream name or its all-lowercase form, as today + // from compute's GgmlType (crates/aprender-compute/src/inference/gguf.rs:68-101): + pub fn block_bytes(&self) -> usize; // == TRAITS[id].type_size + pub fn block_size(&self) -> usize; // == TRAITS[id].blck_size + pub fn tensor_bytes(&self, n_elements: usize) -> usize; // compute's rounding-up formula, unchanged + // new: + pub const fn try_from_id(id: u32) -> Result; // names WHY: Removed vs Unknown + pub const fn traits(self) -> &'static QuantTraits; pub const fn checked_tensor_bytes(self, n_elements: usize) -> Option; // None on overflow or n % blck_size != 0; M4 (#3432) adopts it } +impl std::fmt::Display for GgmlType { /* as_str, as serve has today — the only trait impl on any of the three (measured) */ } +// `blck_size` / `type_size` are TRAITS FIELD names only (upstream's spelling); there is no second method spelling. ``` Decisions put to the quorum: - **Q1-a. 35 variants, 43 table rows.** A removed id is not constructible; `try_from_id(4)` is `Err(Removed{..})`, distinct from `Err(Unknown{..})`; `from_id(4)` is `None`. The table stays dense so `TRAITS[id]` is the lookup and a `const` assertion proves `TRAITS[v.as_id()].name == v.name()` for every variant. - **Q1-b. Variant spelling — one spelling, NO aliases.** Keep the tree's `Q4K`-style (all three enums agree on it) and `BF16`. v1 proposed an associated-const alias `Bf16`; that is withdrawn: measured with rustc, `match self { Self::Bf16 => … }` on `&self` is **E0308** (a const pattern gets no default-binding-mode deref, unlike a variant pattern), it needs `#[allow(non_upper_case_globals)]`, and a glob import does not carry it. Instead compute's `GgmlType::Bf16` sites are renamed to `BF16` in Phase 3 (2 files: `crates/aprender-compute/src/inference/gguf.rs`, `…/inference/model.rs`; the `Bf16` in `brick/tracing/quant_type.rs` belongs to a different enum and is not touched). -- **Q1-c. M1 is behaviour-preserving: no crate accepts an id or a name it refused before — enforced at every id→enum and name→enum BOUNDARY, not only at the GGUF parser.** v1 gated the parser only; the grill found boundaries outside it. The measured list (serve): `gguf/dtype.rs:17` and `:54`, `infer/mod.rs:39`, `apr/mod.rs:324` (APR dtype byte), `apr/special_tokens.rs:210`, `apr/dequant.rs:235`, test `gguf/tests/loader_tests_apr.rs:311`. compute: private `from_u32` in `inference/gguf.rs`. core: `format/gguf/reader.rs`. Mechanism: each crate gets ONE admission function (`admitted_from_id(u32) -> Option`, and in serve `admitted_from_name(&str)`) that is `GgmlType::from_id` filtered by a `const ADMITTED: [GgmlType; N]` reproducing the former set exactly (compute 15, core 12, serve 16), and every boundary above calls it instead of `from_id`/`from_str_lossy`. **Proof is a characterization test written in Phase 1, before any change:** for each boundary function, its output for every id 0..=255 (and every name in `TRAITS` plus case variants) is snapshotted on `origin/main`, and the same test must pass unchanged after Phases 3–4. Wildcard arms (`_ =>`; ~22 across 8 files that name these enums, e.g. `crates/aprender-compute/src/inference/model.rs:734` "quantized norms … return zeros") are therefore not widened in practice: they are already reached today by admitted quantized ids, and a non-admitted variant can only arrive by a new literal in new code. Phase 3/4 receipts still list every wildcard site with its disposition. Widening admission is M4 (#3432) / Q2. +- **Q1-c. M1 is behaviour-preserving: no crate accepts an id or a name it refused before — enforced at every id→enum and name→enum BOUNDARY, not only at the GGUF parser.** v1 gated the parser only; the grill found boundaries outside it. The measured list — serve: `gguf/dtype.rs:17` and `:54`, `infer/mod.rs:39`, `apr/mod.rs:324` (APR dtype byte), `apr/special_tokens.rs:210`, `apr/dequant.rs:235`, test `gguf/tests/loader_tests_apr.rs:311`. compute: private `from_u32` in `inference/gguf.rs`. **core has NO boundary** (v2 was wrong): `crates/aprender-core/src/format/gguf/reader.rs:325` keeps `dtype: u32` raw and nothing in core turns an integer or a string into `GgmlType` — core builds the enum from literals only, so core gets no admission function and no `u32` field is retyped. Mechanism: each crate gets ONE admission function (`admitted_from_id(u32) -> Option`, and in serve `admitted_from_name(&str)`) that is `GgmlType::from_id` filtered by a `const ADMITTED: [GgmlType; N]` reproducing the former set exactly (compute 15, serve 16), and every boundary above calls it instead of `from_id`/`from_str_lossy`. **Proof is a characterization test written in Phase 1, before any change:** for each boundary function, its output for every id 0..=255 (and every name in `TRAITS` plus case variants) is snapshotted on `origin/main`, and the same test must pass unchanged after Phases 3–4. Wildcard arms (`_ =>`; ~22 across 8 files that name these enums, e.g. `crates/aprender-compute/src/inference/model.rs:734` "quantized norms … return zeros") are therefore not widened in practice: they are already reached today by admitted quantized ids, and a non-admitted variant can only arrive by a new literal in new code. Phase 3/4 receipts still list every wildcard site with its disposition. **Exhaustive matches (no wildcard) stop compiling — E0004 — and that is the compiler enumerating them.** Rule: such a match is replaced by delegation to the leaf (`TRAITS`-backed method), never patched with a bare `_ =>`. One is known: `GgufTensor::byte_size`, `crates/aprender-core/src/format/gguf/types.rs:361`, becomes `self.dtype.tensor_bytes(elements as usize)`. **That is the ticket's one intended behaviour delta, named here so it is not a surprise:** the hand-written arm computes `Q4_1` at 18 bytes/block; upstream and `TRAITS` say 20 — a latent export-size defect that the single table fixes. Phase 1 pins it with a test asserting 18 on unchanged code, flipped to 20 in Phase 3, and it is filed as its own issue and named in the PR body. Any OTHER exhaustive match the compiler reports whose delegated value differs from its hand-written arm is a stop-the-line finding, not a silent fix. Widening admission is M4 (#3432) / Q2. - **Q1-d. Q8_1 `type_size` = 36. Measured at llama.cpp `3173a56471c1753650cd806694145ffd6dcace67`:** `ggml/src/ggml-common.h:269` is `static_assert(sizeof(block_q8_1) == 2*sizeof(ggml_half) + QK8_1, "wrong q8_1 block size/padding")` = 36 — the assert rules out padding, so `sizeof` IS the on-disk block — while `gguf-py/gguf/constants.py:5678` says `(32, 4 + 4 + 32)` = 40 (the pre-f16 layout). Truth source for `type_size` is the `static_assert`ed C struct; gguf-py is the cross-check. The falsifier becomes: `TRAITS` equals gguf-py `GGML_QUANT_SIZES` for every live id **except a named exception list, today exactly `[Q8_1]`**, each exception carrying both upstream file:line citations in `contracts/ggml-type-v1.yaml`. The test fails if an exception stops being a disagreement (so the list cannot rot). This amends the ticket's falsifier wording; the amendment goes on #3430 with the verdict grid. (Grill: lanes 2, 3 ruled 36; lane 1 ruled 40 on a padding argument the `static_assert` refutes.) - **Q1-e. Re-exports.** compute: `pub use trueno_quant::GgmlType;`. core: same. serve: `pub use trueno_quant::GgmlType as GgmlQuantType;` plus `GgmlType`. serve's `from_id`/`as_id`/`as_byte`/`as_str`/`from_str_lossy`/`Display` and compute's `block_bytes`/`block_size`/`tensor_bytes` move into the leaf with **unchanged names and signatures** (all three lanes: no body depends on a crate-local type; `TruenoError` is used by the parser, not the enum). `from_id` stays `Option` so `apr/mod.rs:325`'s zero-arg `map_or_else` and `loader_tests_apr.rs:311`'s `.is_some()` compile untouched; `block_bytes` stays as a name alongside `type_size`. -- **Q1-f. #3405's private `QTYPE_LABELS` (13 rows, `crates/aprender-serve/src/gguf/qwen3_moe_load.rs:90`)** is replaced by `TRAITS[id].name` in this ticket — it is a fourth table of the same fact. +- **Q1-f. #3405's private `QTYPE_LABELS` (13 rows, `crates/aprender-serve/src/gguf/qwen3_moe_load.rs:90`)** is replaced in this ticket — it is a fourth table of the same fact. `qtype_label(qtype: u32)` (`:107`) takes an arbitrary `u32`, so the replacement is `GgmlType::from_id(qtype).map_or_else(|| format!("qtype {qtype}"), |t| format!("{}({qtype})", t.as_str()))` — never a raw `TRAITS[id]` index, which would panic for id ≥ 43. The 13 existing labels are measured equal to upstream names. Second named delta, diagnostics only: ids the 13-row table lacked (e.g. 9, 15–29) now render their name instead of `qtype N` in that error message. ### Table E — the 24 "keep?" rows resolved @@ -99,8 +109,8 @@ Q1 (this ticket) → T1/T2 (#3433/#3434) → Q2 → Q3. Evidence: 14 of 34 serve |---|---|---|---| | 1 | RED: falsifier guard, upstream size fixture, boundary characterization tests (snapshot taken on unchanged code) | scripts/check_one_ggml_type_enum.sh, contracts/ggml-type-v1.yaml, crates/aprender-quant/tests/, crates/aprender-serve/src/gguf/tests/, crates/aprender-compute/src/inference/, crates/aprender-core/src/format/gguf/tests/ | `bash scripts/check_one_ggml_type_enum.sh` exits 1 naming exactly 3 definitions; its case table passes (must-match: the 3; must-not-match: `GgufValueType`, `GgufValue`, `QuantType`, a commented-out enum); characterization tests GREEN on unchanged code | | 2 | `GgmlType` + `TRAITS[43]` in aprender-quant | crates/aprender-quant/ | `cargo test -p aprender-quant` (lib AND tests/): every live id round-trips, 8 removed ids are `None` / `Err(Removed)`, `TRAITS` vs the pinned fixture with the exception list exactly `[Q8_1]` | -| 3 | compute + core re-export, admission, `Bf16`→`BF16` | crates/aprender-compute/src/inference/, crates/aprender-core/src/format/ | `cargo test -p aprender-compute --lib -- inference:: && cargo test -p aprender-core --lib -- format::` — includes the Phase 1 characterization tests, unchanged | -| 4 | serve re-export, admission at all 7 boundaries, `QTYPE_LABELS` removed | crates/aprender-serve/src/gguf/, crates/aprender-serve/src/apr/, crates/aprender-serve/src/infer/, crates/aprender-serve/tests/apr_coverage.rs | `cargo test -p aprender-serve --lib -- gguf:: apr:: infer:: && cargo test -p aprender-serve --test apr_coverage` + `bash scripts/check_one_ggml_type_enum.sh` exits **0** + `cargo check --workspace --all-targets` (apr-cli's `tests_trace_dispatch.rs` uses core's re-export) | +| 3 | compute + core re-export, compute admission, `Bf16`→`BF16`, `GgufTensor::byte_size` delegates (Q4_1 pin flips 18→20) | crates/aprender-compute/src/inference/, crates/aprender-core/src/format/ | `cargo test -p aprender-compute --lib -- inference:: && cargo test -p aprender-core --lib -- format::` — includes the Phase 1 characterization tests, unchanged | +| 4 | serve re-export, admission at all 7 boundaries, `QTYPE_LABELS` removed via `from_id` (no raw index) | crates/aprender-serve/src/gguf/, crates/aprender-serve/src/apr/, crates/aprender-serve/src/infer/, crates/aprender-serve/tests/apr_coverage.rs | `cargo test -p aprender-serve --lib -- gguf:: apr:: infer:: && cargo test -p aprender-serve --test apr_coverage` + `bash scripts/check_one_ggml_type_enum.sh` exits **0** + `cargo check --workspace --all-targets` (apr-cli's `tests_trace_dispatch.rs` uses core's re-export) | | 5 | DoD: `pv validate`, mutation (re-add a second enum → guard RED; change one `ADMITTED` set → characterization RED; set `TRAITS[9].type_size = 40` → fixture RED), diff quorum, receipt, PR | docs/audits/, .github CI selection line (needs the operator's web-UI merge click) | `make gate` | Phases 3 and 4 have disjoint scopes and may run together. The guard is wired into the same PR's CI selection line or it is dark. @@ -125,3 +135,15 @@ Reducer: `agreed=false`, `partial=true` (each lane's `.err` had 2 lines beyond w | home crate `aprender-quant`: sound, no cycle | 1, 2, 3 | cargo metadata (§1.1) | unchanged | | X1 ordering | no lane contradicted it | — | unchanged | +### Round 2 — v2, 3 lanes, same models: **1/3 PASS** (lane 3 PASS with notes; lanes 1, 2 FAIL) + +Reducer: `agreed=false`, `partial=true` — lane 2 exited 3, `LANE ISOLATION VIOLATED - a shared ref changed (refs/remotes/origin/HEAD, refs/remotes/origin/main)`. repo_root is a linked worktree sharing the main checkout's git dir; another session fetched `origin` during the lane window. Lane 2's own clone came back byte-identical. Its findings were kept because each was re-run below, not because its verdict was counted. + +| finding | lanes | re-run | disposition in v3 | +|---|---|---|---| +| exhaustive match `GgufTensor::byte_size`, core `format/gguf/types.rs:361` → E0004 | 1, 2, 3 | confirmed; **and its `Q4_1` arm is 18 where upstream is 20** (found on the re-run, no lane saw it) | Q1-c: delegation rule + the named Q4_1 delta | +| core `format/gguf/reader.rs` is not a boundary (stores raw `u32`) | 1, 2 ; lane 3 agreed core has none | grep: no `u32 → GgmlType` anywhere in core | Q1-c corrected; core gets no admission function | +| §2 API block omitted `as_byte as_str from_str_lossy block_bytes block_size Display` | 2 ; 3 (`block_size` vs `blck_size`) | only trait impl on the three is serve's `Display` (grep) | §2 block is now the complete API; one method spelling | +| `TRAITS[id]` raw index panics for `qtype ≥ 43` in `qtype_label` | 2 | `qwen3_moe_load.rs:107` takes arbitrary `u32` — confirmed | Q1-f: via `from_id` | +| cargo test filters `inference:: format:: gguf:: apr:: infer::` select tests | 2, 3 verified | — | unchanged | + From 03008e6384009c429951f03ef682e82ff589b961 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Thu, 17 Sep 2026 15:16:04 +0200 Subject: [PATCH 4/6] =?UTF-8?q?PMAT-3430:=20plan=20v4=20=E2=80=94=20round?= =?UTF-8?q?=203=20was=201/3=20on=20detail,=20not=20design;=20the=20Q4=5F1?= =?UTF-8?q?=20fix=20leaves=20M1=20and=20lands=20first?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No lane disputes the architecture any more. Adopted from the dissent: a size fix riding a refactor breaks the refactor's own behaviour-preserving claim, so core byte_size Q4_1 18 to 20 becomes Phase 0b, its own issue and PR. The leaf API block is declared a contract with clippy -D warnings in the acceptance command. Pmat-Ticket: PMAT-3430 Co-Authored-By: Claude Fable 5.1 --- docs/audits/impl-PMAT-3430-plan.md | 39 ++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/docs/audits/impl-PMAT-3430-plan.md b/docs/audits/impl-PMAT-3430-plan.md index e251f6e386..916fe7ad5c 100644 --- a/docs/audits/impl-PMAT-3430-plan.md +++ b/docs/audits/impl-PMAT-3430-plan.md @@ -1,6 +1,6 @@ # PMAT-3430 plan — PP-QUANT-001 M1: one `GgmlType` + `TRAITS[43]` -Status: **PLAN v3 — v1 grilled 0/3, v2 grilled 1/3 (§6); this revision answers every finding of both rounds; not yet re-reviewed.** Operator rule (#3421, 2026-09-17): no code until the plan quorum is 3/3 on Q1 and X1. Inputs: Tables S / E / B / G of `docs/audits/impl-PMAT-3427-receipt.md` on draft PR #3447 (`d76b4a417`), measured on `origin/main` @ `7eb81a531`. Everything marked `[V]` below was re-measured for this plan on `origin/main` @ `ee684e94c`; `[U]` is unverified and says so. +Status: **PLAN v4 — grilled 0/3, 1/3, 1/3 (§6); this revision answers every finding of all three rounds; not yet re-reviewed.** Operator rule (#3421, 2026-09-17): no code until the plan quorum is 3/3 on Q1 and X1. Inputs: Tables S / E / B / G of `docs/audits/impl-PMAT-3427-receipt.md` on draft PR #3447 (`d76b4a417`), measured on `origin/main` @ `7eb81a531`. Everything marked `[V]` below was re-measured for this plan on `origin/main` @ `ee684e94c`; `[U]` is unverified and says so. ## 1. What is measured `[V]` @@ -32,15 +32,20 @@ Intersection (self included): aprender-compute, aprender-contracts-macros, apren ## 2. Design (Q1) +The block below is the API **contract** — names, signatures, semantics — not literal source. The implementation additionally carries what `crates/aprender-quant/Cargo.toml`'s lint table demands (`missing_docs`, `pedantic`): a doc comment on every public item and variant, `#[must_use]` on every value-returning method (serve's enum already does this), backticked identifiers in docs. `cargo clippy -p aprender-quant --lib -- -D warnings` is part of Phase 2's acceptance, so none of that is left to taste. `non_camel_case_types` does not fire on variants (measured by lanes 1–3; the three existing enums carry no allow for it). + ```rust // crates/aprender-quant/src/ggml_type.rs (lib name: trueno_quant) #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum GgmlType { F32 = 0, F16 = 1, Q4_0 = 2, /* … 35 live variants, upstream ids … */ Q2_0 = 42 } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum GgmlFamily { Float, Int, Affine, KQuant, Codebook, Ternary, MicroscaleFp, Removed } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct QuantTraits { pub name: &'static str, pub blck_size: u32, pub type_size: u32, pub family: GgmlFamily } pub const TRAITS: [QuantTraits; 43] = [ /* indexed BY ID; the 8 removed ids are rows with family = Removed, sizes 0 */ ]; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] // + impl Display + impl std::error::Error pub enum GgmlTypeError { Removed { id: u32, name: &'static str }, Unknown { id: u32 } } impl GgmlType { // THE COMPLETE leaf API. Every name and signature that exists on any of the three enums today is here, unchanged. // from serve's GgmlQuantType (crates/aprender-serve/src/gguf/types.rs:136-221): @@ -50,13 +55,13 @@ impl GgmlType { // THE COMPLETE leaf API. Every name and signature that exists pub const fn as_str(self) -> &'static str; // == TRAITS[id].name; measured equal to serve's 16 strings today ("Q4_K", "IQ2_XXS", "BF16", …) pub fn from_str_lossy(s: &str) -> Option; // exact upstream name or its all-lowercase form, as today // from compute's GgmlType (crates/aprender-compute/src/inference/gguf.rs:68-101): - pub fn block_bytes(&self) -> usize; // == TRAITS[id].type_size - pub fn block_size(&self) -> usize; // == TRAITS[id].blck_size - pub fn tensor_bytes(&self, n_elements: usize) -> usize; // compute's rounding-up formula, unchanged + pub const fn block_bytes(&self) -> usize; // == TRAITS[id].type_size; `&self` kept so compute's call sites are untouched + pub const fn block_size(&self) -> usize; // == TRAITS[id].blck_size + pub const fn tensor_bytes(&self, n_elements: usize) -> usize; // compute's rounding-up formula, unchanged // new: pub const fn try_from_id(id: u32) -> Result; // names WHY: Removed vs Unknown pub const fn traits(self) -> &'static QuantTraits; - pub const fn checked_tensor_bytes(self, n_elements: usize) -> Option; // None on overflow or n % blck_size != 0; M4 (#3432) adopts it + pub const fn checked_tensor_bytes(self, n_elements: usize) -> Option; // reads TRAITS directly (const-callable); None on overflow or n % blck_size != 0; M4 (#3432) adopts it } impl std::fmt::Display for GgmlType { /* as_str, as serve has today — the only trait impl on any of the three (measured) */ } // `blck_size` / `type_size` are TRAITS FIELD names only (upstream's spelling); there is no second method spelling. @@ -66,7 +71,7 @@ Decisions put to the quorum: - **Q1-a. 35 variants, 43 table rows.** A removed id is not constructible; `try_from_id(4)` is `Err(Removed{..})`, distinct from `Err(Unknown{..})`; `from_id(4)` is `None`. The table stays dense so `TRAITS[id]` is the lookup and a `const` assertion proves `TRAITS[v.as_id()].name == v.name()` for every variant. - **Q1-b. Variant spelling — one spelling, NO aliases.** Keep the tree's `Q4K`-style (all three enums agree on it) and `BF16`. v1 proposed an associated-const alias `Bf16`; that is withdrawn: measured with rustc, `match self { Self::Bf16 => … }` on `&self` is **E0308** (a const pattern gets no default-binding-mode deref, unlike a variant pattern), it needs `#[allow(non_upper_case_globals)]`, and a glob import does not carry it. Instead compute's `GgmlType::Bf16` sites are renamed to `BF16` in Phase 3 (2 files: `crates/aprender-compute/src/inference/gguf.rs`, `…/inference/model.rs`; the `Bf16` in `brick/tracing/quant_type.rs` belongs to a different enum and is not touched). -- **Q1-c. M1 is behaviour-preserving: no crate accepts an id or a name it refused before — enforced at every id→enum and name→enum BOUNDARY, not only at the GGUF parser.** v1 gated the parser only; the grill found boundaries outside it. The measured list — serve: `gguf/dtype.rs:17` and `:54`, `infer/mod.rs:39`, `apr/mod.rs:324` (APR dtype byte), `apr/special_tokens.rs:210`, `apr/dequant.rs:235`, test `gguf/tests/loader_tests_apr.rs:311`. compute: private `from_u32` in `inference/gguf.rs`. **core has NO boundary** (v2 was wrong): `crates/aprender-core/src/format/gguf/reader.rs:325` keeps `dtype: u32` raw and nothing in core turns an integer or a string into `GgmlType` — core builds the enum from literals only, so core gets no admission function and no `u32` field is retyped. Mechanism: each crate gets ONE admission function (`admitted_from_id(u32) -> Option`, and in serve `admitted_from_name(&str)`) that is `GgmlType::from_id` filtered by a `const ADMITTED: [GgmlType; N]` reproducing the former set exactly (compute 15, serve 16), and every boundary above calls it instead of `from_id`/`from_str_lossy`. **Proof is a characterization test written in Phase 1, before any change:** for each boundary function, its output for every id 0..=255 (and every name in `TRAITS` plus case variants) is snapshotted on `origin/main`, and the same test must pass unchanged after Phases 3–4. Wildcard arms (`_ =>`; ~22 across 8 files that name these enums, e.g. `crates/aprender-compute/src/inference/model.rs:734` "quantized norms … return zeros") are therefore not widened in practice: they are already reached today by admitted quantized ids, and a non-admitted variant can only arrive by a new literal in new code. Phase 3/4 receipts still list every wildcard site with its disposition. **Exhaustive matches (no wildcard) stop compiling — E0004 — and that is the compiler enumerating them.** Rule: such a match is replaced by delegation to the leaf (`TRAITS`-backed method), never patched with a bare `_ =>`. One is known: `GgufTensor::byte_size`, `crates/aprender-core/src/format/gguf/types.rs:361`, becomes `self.dtype.tensor_bytes(elements as usize)`. **That is the ticket's one intended behaviour delta, named here so it is not a surprise:** the hand-written arm computes `Q4_1` at 18 bytes/block; upstream and `TRAITS` say 20 — a latent export-size defect that the single table fixes. Phase 1 pins it with a test asserting 18 on unchanged code, flipped to 20 in Phase 3, and it is filed as its own issue and named in the PR body. Any OTHER exhaustive match the compiler reports whose delegated value differs from its hand-written arm is a stop-the-line finding, not a silent fix. Widening admission is M4 (#3432) / Q2. +- **Q1-c. M1 is behaviour-preserving: no crate accepts an id or a name it refused before — enforced at every id→enum and name→enum BOUNDARY, not only at the GGUF parser.** v1 gated the parser only; the grill found boundaries outside it. The measured list — serve: `gguf/dtype.rs:17` and `:54`, `infer/mod.rs:39`, `apr/mod.rs:324` (APR dtype byte), `apr/special_tokens.rs:210`, `apr/dequant.rs:235`, test `gguf/tests/loader_tests_apr.rs:311`. compute: private `from_u32` in `inference/gguf.rs`. **core has NO boundary** (v2 was wrong): `crates/aprender-core/src/format/gguf/reader.rs:325` keeps `dtype: u32` raw and nothing in core turns an integer or a string into `GgmlType` — core builds the enum from literals only, so core gets no admission function and no `u32` field is retyped. Mechanism: each crate gets ONE admission function (`admitted_from_id(u32) -> Option`, and in serve `admitted_from_name(&str)`) that is `GgmlType::from_id` filtered by a `const ADMITTED: [GgmlType; N]` reproducing the former set exactly (compute 15, serve 16), and every boundary above calls it instead of `from_id`/`from_str_lossy`. **Boundary function names survive:** compute's private `fn from_u32(v: u32) -> Option` stays under that name as a free function in `inference/gguf.rs` and IS compute's admission function (it cannot remain an inherent method of a re-exported type); its one caller changes from `GgmlType::from_u32(x)` to `from_u32(x)`. serve's boundary functions (`gguf/dtype.rs`, `infer/mod.rs:39`, `apr/mod.rs:324`, `apr/special_tokens.rs:210`, `apr/dequant.rs:235`) keep their names and signatures. So the Phase 1 characterization tests call the same symbols before and after; the single permitted edit to a Phase 1 test in Phase 3 is that `GgmlType::from_u32` → `from_u32` path change, named here in advance. **Proof is a characterization test written in Phase 1, before any change,** placed as a `#[cfg(test)]` child module of each boundary's own module (a child module sees its ancestors' private items, so no visibility is widened; the only non-test edit in Phase 1 is the `mod …;` line)**:** for each boundary function, its output for every id 0..=255 (and every name in `TRAITS` plus case variants) is snapshotted on `origin/main`, and the same test must pass unchanged after Phases 3–4. Wildcard arms (`_ =>`; ~22 across 8 files that name these enums, e.g. `crates/aprender-compute/src/inference/model.rs:734` "quantized norms … return zeros") are therefore not widened in practice: they are already reached today by admitted quantized ids, and a non-admitted variant can only arrive by a new literal in new code. Phase 3/4 receipts still list every wildcard site with its disposition. **Exhaustive matches (no wildcard) stop compiling — E0004 — and that is the compiler enumerating them.** Rule: such a match is replaced by delegation to the leaf (`TRAITS`-backed method), never patched with a bare `_ =>`. One is known: `GgufTensor::byte_size`, `crates/aprender-core/src/format/gguf/types.rs:361`, becomes `self.dtype.tensor_bytes(elements as usize)`. The hand-written arm sizes `Q4_1` at 18 bytes/block (`Q4_0 | Q4_1 => … * 18`, `:365`); upstream and `TRAITS` say 20 — a latent export-size defect. **It is NOT fixed inside M1** (round 3, lane 1: a fix riding a refactor breaks M1's own behaviour-preserving claim; adopted). It lands first as its own issue and its own small PR — Phase 0b in §4: split the arm, `Q4_1 => … * 20`, one test. After that, delegation in Phase 3 is value-identical for all 12 of core's variants, and Phase 1 asserts exactly that: `byte_size` for each of the 12, on a block-aligned and a non-aligned element count, snapshotted before and unchanged after. Any OTHER exhaustive match the compiler reports whose delegated value differs from its hand-written arm is a stop-the-line finding, handled the same way — never a silent fix. - **Q1-d. Q8_1 `type_size` = 36. Measured at llama.cpp `3173a56471c1753650cd806694145ffd6dcace67`:** `ggml/src/ggml-common.h:269` is `static_assert(sizeof(block_q8_1) == 2*sizeof(ggml_half) + QK8_1, "wrong q8_1 block size/padding")` = 36 — the assert rules out padding, so `sizeof` IS the on-disk block — while `gguf-py/gguf/constants.py:5678` says `(32, 4 + 4 + 32)` = 40 (the pre-f16 layout). Truth source for `type_size` is the `static_assert`ed C struct; gguf-py is the cross-check. The falsifier becomes: `TRAITS` equals gguf-py `GGML_QUANT_SIZES` for every live id **except a named exception list, today exactly `[Q8_1]`**, each exception carrying both upstream file:line citations in `contracts/ggml-type-v1.yaml`. The test fails if an exception stops being a disagreement (so the list cannot rot). This amends the ticket's falsifier wording; the amendment goes on #3430 with the verdict grid. (Grill: lanes 2, 3 ruled 36; lane 1 ruled 40 on a padding argument the `static_assert` refutes.) - **Q1-e. Re-exports.** compute: `pub use trueno_quant::GgmlType;`. core: same. serve: `pub use trueno_quant::GgmlType as GgmlQuantType;` plus `GgmlType`. serve's `from_id`/`as_id`/`as_byte`/`as_str`/`from_str_lossy`/`Display` and compute's `block_bytes`/`block_size`/`tensor_bytes` move into the leaf with **unchanged names and signatures** (all three lanes: no body depends on a crate-local type; `TruenoError` is used by the parser, not the enum). `from_id` stays `Option` so `apr/mod.rs:325`'s zero-arg `map_or_else` and `loader_tests_apr.rs:311`'s `.is_some()` compile untouched; `block_bytes` stays as a name alongside `type_size`. - **Q1-f. #3405's private `QTYPE_LABELS` (13 rows, `crates/aprender-serve/src/gguf/qwen3_moe_load.rs:90`)** is replaced in this ticket — it is a fourth table of the same fact. `qtype_label(qtype: u32)` (`:107`) takes an arbitrary `u32`, so the replacement is `GgmlType::from_id(qtype).map_or_else(|| format!("qtype {qtype}"), |t| format!("{}({qtype})", t.as_str()))` — never a raw `TRAITS[id]` index, which would panic for id ≥ 43. The 13 existing labels are measured equal to upstream names. Second named delta, diagnostics only: ids the 13-row table lacked (e.g. 9, 15–29) now render their name instead of `qtype N` in that error message. @@ -107,11 +112,12 @@ Q1 (this ticket) → T1/T2 (#3433/#3434) → Q2 → Q3. Evidence: 14 of 34 serve | # | phase | scope_paths | `A_i` | |---|---|---|---| -| 1 | RED: falsifier guard, upstream size fixture, boundary characterization tests (snapshot taken on unchanged code) | scripts/check_one_ggml_type_enum.sh, contracts/ggml-type-v1.yaml, crates/aprender-quant/tests/, crates/aprender-serve/src/gguf/tests/, crates/aprender-compute/src/inference/, crates/aprender-core/src/format/gguf/tests/ | `bash scripts/check_one_ggml_type_enum.sh` exits 1 naming exactly 3 definitions; its case table passes (must-match: the 3; must-not-match: `GgufValueType`, `GgufValue`, `QuantType`, a commented-out enum); characterization tests GREEN on unchanged code | -| 2 | `GgmlType` + `TRAITS[43]` in aprender-quant | crates/aprender-quant/ | `cargo test -p aprender-quant` (lib AND tests/): every live id round-trips, 8 removed ids are `None` / `Err(Removed)`, `TRAITS` vs the pinned fixture with the exception list exactly `[Q8_1]` | -| 3 | compute + core re-export, compute admission, `Bf16`→`BF16`, `GgufTensor::byte_size` delegates (Q4_1 pin flips 18→20) | crates/aprender-compute/src/inference/, crates/aprender-core/src/format/ | `cargo test -p aprender-compute --lib -- inference:: && cargo test -p aprender-core --lib -- format::` — includes the Phase 1 characterization tests, unchanged | +| 0b | **separate PR, lands first:** core `GgufTensor::byte_size` sizes `Q4_1` at 20, not 18 (own issue) | crates/aprender-core/src/format/gguf/ | `cargo test -p aprender-core --lib -- format::gguf` incl. a new `Q4_1` test that is RED before the one-line fix | +| 1 | RED: falsifier guard, upstream size fixture, boundary characterization tests (snapshot taken on unchanged code; `#[cfg(test)]` child modules + their `mod` lines only) | scripts/check_one_ggml_type_enum.sh, contracts/ggml-type-v1.yaml, crates/aprender-quant/tests/, crates/aprender-serve/src/gguf/, crates/aprender-serve/src/apr/, crates/aprender-serve/src/infer/, crates/aprender-compute/src/inference/, crates/aprender-core/src/format/gguf/ | `bash scripts/check_one_ggml_type_enum.sh` exits 1 naming exactly 3 definitions; its case table passes (must-match: the 3; must-not-match: `GgufValueType`, `GgufValue`, `QuantType`, a commented-out enum); characterization tests GREEN on unchanged code | +| 2 | `GgmlType` + `TRAITS[43]` in aprender-quant | crates/aprender-quant/ | `cargo test -p aprender-quant && cargo clippy -p aprender-quant --lib -- -D warnings` (lib AND tests/): every live id round-trips, 8 removed ids are `None` / `Err(Removed)`, `TRAITS` vs the pinned fixture with the exception list exactly `[Q8_1]` | +| 3 | compute + core re-export, compute admission, `Bf16`→`BF16`, `GgufTensor::byte_size` delegates (value-identical for all 12 after Phase 0b) | crates/aprender-compute/src/inference/, crates/aprender-core/src/format/ | `cargo test -p aprender-compute --lib -- inference:: && cargo test -p aprender-core --lib -- format::` — includes the Phase 1 characterization tests, unchanged | | 4 | serve re-export, admission at all 7 boundaries, `QTYPE_LABELS` removed via `from_id` (no raw index) | crates/aprender-serve/src/gguf/, crates/aprender-serve/src/apr/, crates/aprender-serve/src/infer/, crates/aprender-serve/tests/apr_coverage.rs | `cargo test -p aprender-serve --lib -- gguf:: apr:: infer:: && cargo test -p aprender-serve --test apr_coverage` + `bash scripts/check_one_ggml_type_enum.sh` exits **0** + `cargo check --workspace --all-targets` (apr-cli's `tests_trace_dispatch.rs` uses core's re-export) | -| 5 | DoD: `pv validate`, mutation (re-add a second enum → guard RED; change one `ADMITTED` set → characterization RED; set `TRAITS[9].type_size = 40` → fixture RED), diff quorum, receipt, PR | docs/audits/, .github CI selection line (needs the operator's web-UI merge click) | `make gate` | +| 5 | DoD: `pv validate`, mutation (re-add a second enum → guard RED; change one `ADMITTED` set → characterization RED; set `TRAITS[9].type_size = 40` → fixture RED; `TRAITS[3].type_size = 18` → core byte_size characterization RED), diff quorum, receipt, PR | docs/audits/, .github CI selection line (needs the operator's web-UI merge click) | `make gate` | Phases 3 and 4 have disjoint scopes and may run together. The guard is wired into the same PR's CI selection line or it is dark. @@ -147,3 +153,16 @@ Reducer: `agreed=false`, `partial=true` — lane 2 exited 3, `LANE ISOLATION VIO | `TRAITS[id]` raw index panics for `qtype ≥ 43` in `qtype_label` | 2 | `qwen3_moe_load.rs:107` takes arbitrary `u32` — confirmed | Q1-f: via `from_id` | | cargo test filters `inference:: format:: gguf:: apr:: infer::` select tests | 2, 3 verified | — | unchanged | +### Round 3 — v3, 3 lanes, same models: **1/3 PASS** (lane 3 PASS; lanes 1, 2 FAIL). Reducer `partial=false`; no isolation violation this round. Lanes 1 and 2 compiled probe crates inside their sandboxed clones (KEPT, nothing escaped). + +No lane disputed the architecture any more: home crate, 35/43, Q8_1 = 36, boundaries (compute 1, serve 7, core 0), `byte_size` as the only exhaustive match, Table E and X1 were each called sound by all three. What failed is below. + +| finding | lanes | re-run | disposition in v4 | +|---|---|---|---| +| Q4_1 18→20 must not ride the refactor — land it first, separately | 1 blocking ; 2, 3 accepted it in-ticket | the 18 is confirmed (`types.rs:365`) | adopted: Phase 0b, own issue + PR | +| §2 block fails `clippy -D warnings` in aprender-quant (`missing_docs`, `must_use_candidate`, doc backticks) | 1, 2 | lint table read: `missing_docs = warn`, `pedantic = warn` | §2 preamble: block is the contract; clippy is in Phase 2's `A_i` | +| aux types lack derives; `GgmlTypeError` lacks `Display`/`Error` | 2 | — | added | +| `const fn checked_tensor_bytes` cannot call non-const `block_size` | 2 | — | all size methods `const fn`; `&self` kept | +| Phase 1 tests on compute's private `from_u32` break in Phase 3 (E0599) | 2 ; lane 1 said reachable | both are right: reachable from a child module, and the inherent method cannot survive a re-export | Q1-c: `from_u32` survives as a free fn; the one path edit is named in advance | +| Phase 1 scope omits serve `gguf/dtype.rs` | 2 | `gguf/tests/` was too narrow — confirmed | Phase 1 scope widened to the boundary modules' directories | + From 205f71105c0fd7501a7f4ada9e5f4af02b942434 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Thu, 17 Sep 2026 15:38:56 +0200 Subject: [PATCH 5/6] =?UTF-8?q?PMAT-3430:=20receipt=20=E2=80=94=20plan=20q?= =?UTF-8?q?uorum=200/3,=201/3,=201/3,=201/3;=20two=20Q1=20objection=20clas?= =?UTF-8?q?ses=20survived=20three=20rounds,=20so=20no=20v5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lanes never disputed the target or the home crate. They dispute that a source-compatible, behaviour-preserving shim can be enumerated in advance. That is the operator's to rule on; no code was written. Pmat-Ticket: PMAT-3430 Co-Authored-By: Claude Fable 5.1 --- docs/audits/impl-PMAT-3430-receipt.md | 103 ++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 docs/audits/impl-PMAT-3430-receipt.md diff --git a/docs/audits/impl-PMAT-3430-receipt.md b/docs/audits/impl-PMAT-3430-receipt.md new file mode 100644 index 0000000000..0f6901d0ac --- /dev/null +++ b/docs/audits/impl-PMAT-3430-receipt.md @@ -0,0 +1,103 @@ +# IMPL receipt — PMAT-3430 — PP-QUANT-001 M1 (plan phase only) + +Verdict: **PARTIAL(escalate)** — plan quorum is not 3/3 after four rounds; the operator rules. No code was written. No v5. + +## Identity +- ticket PMAT-3430 (#3430), kind=code, branch `PMAT-3430-one-ggml-type`, orchestrator model fable-5-1 admitted by `model-gate.sh` (`orch:fable`, `orch-basis:M>=3` — from main's fragment, landed by #3448; not written by this session). +- First launch of this session was refused at Phase 0 (`kind-gate` exit 2: ticket not filed on main; `model-gate` exit 1 against the then-unlabelled entry). Work started only after the entry carrying the basis existed on `origin/PMAT-3427-roadmap-entries`; the workaround commit that pulled it was dropped by rebase once #3448 merged. `git diff origin/main...HEAD -- docs/roadmaps` is empty. +- Harness findings: `pmat hooks install --strict --force` fails in a linked worktree (`Error: Not a directory (os error 20)`); the edit hook keys `active-ticket` on the session cwd's repo key, not the edited file's, so one was written by hand at the path the hook named; `estimate.sh aprender 5` exits 2 (47 rows, none pooled) so `K̂=24` is the issue's borrowed `[A]` figure entered as `first-run[U]`, the only basis `goal.sh` admits; `receipt-lint.sh` has no `--kind delegate`. + +## Dispatch ledger +| round | description prefix | lane | width | route | agy conversations | delegate turns hit? | +|---|---|---|---|---|---|---| +| v1 | PMAT-3430/ph0.delegate | quorum/grillme | 3 | route=agy-plan w=1.00 basis=absent effort=1[U] | (receipt not emitted — delegate hit 30 turns; lanes + reduce-artifact read from disk) | yes | +| v2 | PMAT-3430/ph0.delegate2 | quorum/grillme | 3 | same | 5588db06…, 909d9ee2…, 99f05b71… | no | +| v3 | PMAT-3430/ph0.delegate3 | quorum/grillme | 3 | same | d0d7bffd…, 9ec33c0a…, 8ba37edd… | no | +| v4 | PMAT-3430/ph0.delegate4 | quorum/grillme | 3 | same | 2efab8bf…, 6efcbd2f…, f42ccf0d… | no | + +Slots: 1 Claude subagent live at any instant (slots=3), 0 resumes, 0 hook denials, 0 stalls. Review lanes wrote rustc probes into their sandboxed clones in v1, v3, v4 (KEPT; nothing reached the checkout). + +``` +PASS transcript-gate: attempted=0 denied=0 stalled=0 running_peak=0 slots=3 — 0 subagents ran in /home/noah/.claude/projects/-home-noah-src-aprender-worktrees-PMAT-3430/a26ca3bb-cffd-4f6e-b6fd-358f489cf451 (rule=pid-file (/run/user/1000/paiml-implement/pid-2717702); vacuous but honest — say so in the receipt) +``` + +## PMAT-3430 plan quorum — history, v1–v4. **Not 3/3. No v5 written; no code written.** + +Plan: `docs/audits/impl-PMAT-3430-plan.md` on branch `PMAT-3430-one-ggml-type` (draft PR linked below). Lanes: agy `grillme`, review-only, none in the author's model family (author `fable-5-1`). Every objection below was re-executed by the orchestrator before it was answered; two lane claims were refuted on re-run (lane 1 v1: "assoc-const alias works in patterns" — rustc E0308; lane 1 v1: "Q8_1 = 40, C struct has padding" — upstream `static_assert` says 36). + +### 1. Grid + +| lane (model) | v1 | v2 | v3 | v4 | +|---|---|---|---|---| +| 1 gemini-3.1-pro-high | FAIL | FAIL | FAIL | FAIL † | +| 2 gemini-3.8-flash-high | FAIL | FAIL † | FAIL | FAIL † | +| 3 gemini-3.7-flash-high | FAIL | PASS | PASS | PASS † | +| **PASS count** | **0/3** | **1/3** | **1/3** | **1/3** | +| reducer | partial | partial | clean | partial | + +† lane exited 3, `LANE ISOLATION VIOLATED — a shared ref changed`: the plan worktree shares a git dir with the main checkout, and other sessions moved refs during the lane window (v2: `refs/remotes/origin/main`; v4: `refs/heads/PMAT-3445-t0-milestone-gate`, `refs/heads/PMAT-3451-ont-6-verdict-lattice`, a merge-queue ref). Each lane's own clone was unaffected, but under the rail those verdicts are unclean — **v3 is the only round with three clean lanes, and it was 1/3.** v1's `partial` was 2 stderr lines per lane beyond workspace narration. + +### 2. Every REJECT / AMEND reason, verbatim from the lane's `findings[].claim`, one tag each + +| round | lane | tag | at | reason (verbatim) | +|---|---|---|---|---| +| v1 | 1 | Q1 | `crates/aprender-compute/src/inference/model.rs:712` | Q1-c: Admission function fails to protect cross-crate passing. If serve admits IQ2XS and passes a TensorInfo to compute, it silently hits the wildcard and widens to returning zeros. | +| v1 | 1 | Q1 | `docs/audits/impl-PMAT-3430-plan.md:58` | Q1-d: gguf-py is authoritative for bytes-on-disk in a GGUF file. The TRAITS falsifier must compare against gguf-py (40), not the C struct sizeof (36). | +| v1 | 1 | Table E resolutions | `crates/aprender-serve/src/paged_kv/mod_compute_prefix.rs:254` | TABLE E: KvQuantType variants (FP32, Q8, Q4) are exactly ggml block formats. It meets the merge rule. | +| v1 | 1 | plan mechanics | `crates/aprender-core/src/format/tensors_tests_workflow.rs:82` | PHASES (Phase 3): Scope path misses core files using GgmlType. | +| v1 | 1 | plan mechanics | `crates/aprender-serve/src/apr/mod.rs:324` | PHASES (Phase 4): Scope path misses serve files using GgmlQuantType. | +| v1 | 2 | Q1 | `docs/audits/impl-PMAT-3430-plan.md:56` | compute's Bf16 becomes an associated const alias pub const Bf16: Self = Self::BF16 inside the quant crate — usable in expressions and patterns — so M1 touches no dispatch arm. | +| v1 | 2 | Q1 | `docs/audits/impl-PMAT-3430-plan.md:57` | M1 is behaviour-preserving: no crate accepts an id it refused before. Going from 12/15/16 to 35 variants would silently widen every match that ends in _ =>. So each crate keeps a thin, named admission function (supported(GgmlType) -> bool, one each in compute, core and serve) that reproduces its former id set exactly, and its parser calls it. | +| v1 | 2 | Q1 | `docs/audits/impl-PMAT-3430-plan.md:46` | pub const fn from_id(id: u32) -> Result; ... serve's as_str/from_str_lossy/as_byte/Display move into the quant crate | +| v1 | 2 | Q1 | `docs/audits/impl-PMAT-3430-plan.md:49` | pub const fn tensor_bytes(self, n_elements: usize) -> Option; // checked: None on overflow or n % blck_size != 0 | +| v1 | 2 | plan mechanics | `docs/audits/impl-PMAT-3430-plan.md:101` | Phase 4 A_i: cargo test -p aprender-serve --lib gguf::types + bash scripts/check_one_ggml_type_enum.sh exits 0 | +| v1 | 2 | plan mechanics | `docs/audits/impl-PMAT-3430-plan.md:101` | Phase 4 scope_paths: crates/aprender-serve/src/gguf/, the #3405 file | +| v1 | 2 | Table E resolutions | `docs/audits/impl-PMAT-3430-plan.md:76` | Table E file paths: apr-cli quantize.rs:27, apr-cli quantize_flag_parity.rs:49, aprender-train quant_type.rs:5, aprender-core converter_types_expectations.rs:149, aprender-cgp profilers/quant.rs:9, aprender-serve paged_kv:254 | +| v1 | 2 | Q1 | `docs/audits/impl-PMAT-3430-plan.md:31` | The ticket's falsifier says 'equals gguf-py for every shared id', which would bake in 40. Phase 2 must read both upstream files at 3173a5647 and the quorum is asked to rule (§2, Q1-d). | +| v1 | 3 | Q1 | `docs/audits/impl-PMAT-3430-plan.md:56` | Associated const alias pub const Bf16 triggers non_upper_case_globals compiler warning/error and does not work with glob imports (use GgmlType::*). | +| v1 | 3 | Q1 | `crates/aprender-serve/src/gguf/tests/loader_tests_apr.rs:311` | GgmlQuantType::from_id changing from Option to Result breaks existing call sites such as .is_some() in loader_tests_apr.rs:311. | +| v1 | 3 | Q1 | `crates/aprender-serve/src/apr/mod.rs:324` | Non-GGUF callers of from_id, specifically TensorEntry::from_binary in apr/mod.rs:324, will silently admit new variants when from_id expands to 35 variants without going through the GGUF parser admission function. | +| v1 | 3 | plan mechanics | `docs/audits/impl-PMAT-3430-plan.md:99` | Phase 2 acceptance command `cargo test -p aprender-quant --lib ggml_type` does not run integration tests in crates/aprender-quant/tests/ (declared in Phase 1 scope_paths). | +| v1 | 3 | plan mechanics | `docs/audits/impl-PMAT-3430-plan.md:101` | Phase 4 acceptance command `cargo test -p aprender-serve --lib gguf::types` runs only 10 unit tests in types.rs and does not execute qwen3_moe_load.rs (#3405 QTYPE_LABELS removal) or loader_tests_apr.rs. | +| v2 | 1 | Q1 | `docs/audits/impl-PMAT-3430-plan.md:59` | The plan claims `core: format/gguf/reader.rs` is an id-to-enum boundary and will get an admission function. | +| v2 | 1 | Q1 | `crates/aprender-core/src/format/gguf/types.rs:361` | The plan fails to account for an existing exhaustive match on `GgmlType` in core that will become non-exhaustive. | +| v2 | 2 | Q1 | `crates/aprender-core/src/format/gguf/types.rs:361` | The plan assumes all matches on GgmlType across the tree have wildcard arms (_ =>) and only accounts for wildcard disposition. In reality, crates/aprender-core/src/format/gguf/types.rs:361 (GgufTensor::byte_size) is an EXHAUSTIVE match over core's 12 variants with NO wildcard arm. Re-exporting trueno_quant::GgmlType (35 variants) causes rustc compilation error E0004 (non-exhaustive patterns). | +| v2 | 2 | Q1 | `docs/audits/impl-PMAT-3430-plan.md:45` | The Section 2 leaf API code definition (lines 45-52) omits existing inherent methods (as_byte, as_str, from_str_lossy, block_bytes, block_size) and trait impl Display, causing existing callers across aprender-serve and aprender-compute to fail compilation if implemented from the code block as written. | +| v2 | 2 | Q1 | `crates/aprender-core/src/format/gguf/reader.rs:325` | Section 2 Q1-c claims 'core: format/gguf/reader.rs' is an id-to-enum boundary where u32 becomes GgmlType and prescribes an admission function admitted_from_id there. In reality, format/gguf/reader.rs and reader_parsing.rs store raw dtype: u32 in GgufTensorMeta and never convert u32 to GgmlType. Core has no id-to-enum boundary. | +| v2 | 2 | Q1 | `crates/aprender-serve/src/gguf/qwen3_moe_load.rs:107` | Section 2 Q1-f states '#3405\'s private QTYPE_LABELS (13 rows, crates/aprender-serve/src/gguf/qwen3_moe_load.rs:90) is replaced by TRAITS[id].name in this ticket'. In qwen3_moe_load.rs:107, qtype_label(qtype: u32) accepts arbitrary u32. Direct array indexing TRAITS[id] will panic with out-of-bounds on any qtype >= 43. | +| v3 | 2 | Q1 | `docs/audits/impl-PMAT-3430-plan.md:45` | Section 2 code block claims to be the complete leaf API, but omits #[must_use] on methods returning values. Under aprender-quant's Cargo.toml lints (pedantic = warn), cargo clippy -p aprender-quant --lib -- -D warnings fails with clippy::must_use_candidate on every method. | +| v3 | 2 | Q1 | `docs/audits/impl-PMAT-3430-plan.md:40` | Auxiliary types GgmlFamily, QuantTraits, and GgmlTypeError in Section 2 lack derive attributes. Without #[derive(Debug, Clone, Copy, PartialEq, Eq)], Phase 2 test assertions like assert_eq!(GgmlType::try_from_id(4), Err(GgmlTypeError::Removed { .. })) fail compilation, and GgmlTypeError lacks Display/Error trait implementations. | +| v3 | 2 | Q1 | `docs/audits/impl-PMAT-3430-plan.md:59` | checked_tensor_bytes is declared as pub const fn, but block_size(&self) and block_bytes(&self) are declared as non-const pub fn. In Rust, a const fn cannot call non-const methods, causing E0015 if checked_tensor_bytes delegates to them. | +| v3 | 2 | plan mechanics | `docs/audits/impl-PMAT-3430-plan.md:69` | Phase 1 characterization tests cannot call compute's private from_u32 and pass unchanged in Phase 3, because in Phase 3 compute re-exports trueno_quant::GgmlType (which lacks from_u32) and replaces from_u32 with admitted_from_id, causing E0599 in Phase 3. | +| v3 | 2 | plan mechanics | `docs/audits/impl-PMAT-3430-plan.md:110` | Phase 1 scope_paths specifies crates/aprender-serve/src/gguf/tests/ and crates/aprender-compute/src/inference/, but compute's from_u32 in gguf.rs and serve's apr_qtype_to_dtype / apr_dtype_to_byte in dtype.rs are private (fn). External test files in those directories cannot call them without editing gguf.rs and dtype.rs to make them pub(crate), which is not explicit in Phase 1 scope. | +| v3 | 1 | Q1 | (summary; this lane's findings carried no fix) | First, fixing the Q4_1 byte_size calculation (18->20) within this refactoring violates the behavior-preserving invariant of M1 and must be landed as a separate PR first. | +| v3 | 1 | Q1 | (summary) | Second, moving the enum to `aprender-quant` without doc comments will fail the crate's existing `#![warn(missing_docs)]` lint, though it does pass `non_camel_case_types`. | +| v4 | 1 | plan mechanics | `crates/aprender-serve/src/apr/mod.rs:324` | serve's boundary functions (gguf/dtype.rs, infer/mod.rs:39, apr/mod.rs:324, apr/special_tokens.rs:210, apr/dequant.rs:235) keep their names and signatures | +| v4 | 1 | plan mechanics | `Makefile:1129` | Phase 1 scope_paths cover the creation and wiring of contracts/ggml-type-v1.yaml | +| v4 | 2 | plan mechanics | `crates/aprender-serve/src/apr/mod.rs:324` | apr/mod.rs:324 is a boundary function that keeps its name and signature and can be called directly by Phase 1 characterization tests | +| v4 | 2 | plan mechanics | `crates/aprender-serve/src/infer/mod.rs:38` | infer/mod.rs:39 is a boundary function that keeps its name and signature | +| v4 | 2 | plan mechanics | `contracts/census.json:5` | Adding contracts/ggml-type-v1.yaml requires regenerating contracts/census.json and synchronizing README.md, but both are omitted from scope_paths | +| v4 | 2 | plan mechanics | `README.md:44` | README.md contract count is checked for exact equality by scripts/readme_sync.sh --check during make contracts, which fails upon adding a new contract unless updated | +| v4 | 2 | plan mechanics | `ci/explicit-test-commands.d:1` | crates/aprender-quant/tests/ integration tests are not executed in CI unless an explicit command fragment is added under ci/explicit-test-commands.d/ | +| v4 | 2 | plan mechanics | `.github/workflows/ci.yml:938` | The plan asserts in Phase 5 and line 122 that the guard check_one_ggml_type_enum.sh must be wired into a CI selection line in .github, but no such selection line exists (ci.yml dispatches via scripts/guard_tree.sh --no-cargo) | + +Counts by tag: Q1 (enum + TRAITS design) 21 · plan mechanics 16 · Table E resolutions 2 · **home crate 0 · X1 ordering 0**. `aprender-quant` as home was called sound by every lane in every round; no lane contradicted X1; Table E drew one flip (`KvQuantType`, lane 1, v1 only) and one path-format note. + +### 3. Objections that repeated across rounds + +No objection came back in the same words — each round's specific was re-run, answered, and not raised again. But three objection **classes** survived two or more rounds, each time with a new instance the previous revision had not found: + +| class | tag | rounds | instances | +|---|---|---|---| +| **A. The §2 leaf API does not match the surface existing call sites use** | Q1 | v1, v2, v3 | v1: `from_id → Result` and `tensor_bytes → Option` break callers · v2: block omits `as_byte as_str from_str_lossy block_bytes block_size Display` · v3: aux types lack derives, `const fn` calls non-const, `must_use`/`missing_docs` under the crate's lint table | +| **B. The list of id→enum / name→enum admission boundaries is wrong** | Q1 | v1, v2, v4 | v1: gated the GGUF parser only, `apr/mod.rs:324` + 5 more bypass it · v2: listed core `reader.rs`, which keeps `u32` raw — core has no boundary · v4: `apr/mod.rs:324` is an inline match inside `TensorEntry::from_binary`, not a callable "boundary function that keeps its name" | +| **C. scope_paths / acceptance commands miss files or cannot fail** | plan mechanics | v1, v3, v4 | v1: Phase 3/4 scopes miss core `format/` siblings, serve `apr/` `infer/`; `gguf::types` filter exercises 0 sites · v3: Phase 1 scope omits serve `gguf/dtype.rs`; tests on private `from_u32` break in Phase 3 · v4: `contracts/census.json`, `README.md:44` (1791→1792), `ci/explicit-test-commands.d/`, Makefile `CONTRACTS` are outside every phase's scope; "CI selection line" does not exist (guards auto-dispatch via `guard_tree.sh --no-cargo`) | + +Read against the operator's rule: **A and B are tagged Q1 and each survived three rounds** — by that rule they are design findings, not wording. What they have in common: the plan keeps trying to make M1 *source-compatible and behaviour-preserving at every existing call site* while replacing three enums with one 35-variant enum, and each round finds one more site where that promise needs another special case (an alias, an `Option`-returning twin, a surviving free function, a per-crate `ADMITTED` set, an inline match that is not a function). The lanes never disputed the target (one enum, `TRAITS[43]`, in `aprender-quant`); they dispute that the compatibility shim can be specified completely in advance. C is the same thing seen from the phase table. + +Found on the orchestrator's re-runs, not by any lane — both stand regardless of the ruling: +- core `GgufTensor::byte_size` (`crates/aprender-core/src/format/gguf/types.rs:365`) sizes `Q4_1` at 18 bytes/block; upstream is 20. Latent export-size defect, to be filed and fixed on its own. +- Q8_1: ggml C `static_assert` = 36, gguf-py `GGML_QUANT_SIZES` = 40 at llama.cpp `3173a56471c1753650cd806694145ffd6dcace67`. The ticket's falsifier ("equals gguf-py for every shared id") cannot be met as worded. + +Decision requested: rule on A/B — e.g. accept a compatibility shim specified by characterization tests rather than by enumeration, or drop source-compatibility from M1 and let the compiler enumerate the sites. Andon stands: 3/3 by 2026-09-18T12:00Z or the ticket is pulled. No code before 3/3. + From b5eb08cece6179c84e44356aecfe92974725573a Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Thu, 17 Sep 2026 15:39:55 +0200 Subject: [PATCH 6/6] =?UTF-8?q?PMAT-3430:=20receipt=20=E2=80=94=20I-3=20me?= =?UTF-8?q?asured=20from=20the=20session=20cwd:=20attempted=3D4=20running?= =?UTF-8?q?=5Fpeak=3D1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run from the worktree the gate reads the wrong project directory and passes on zero subagents. Pmat-Ticket: PMAT-3430 Co-Authored-By: Claude Fable 5.1 --- docs/audits/impl-PMAT-3430-receipt.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/audits/impl-PMAT-3430-receipt.md b/docs/audits/impl-PMAT-3430-receipt.md index 0f6901d0ac..8f89dcfaf0 100644 --- a/docs/audits/impl-PMAT-3430-receipt.md +++ b/docs/audits/impl-PMAT-3430-receipt.md @@ -17,8 +17,9 @@ Verdict: **PARTIAL(escalate)** — plan quorum is not 3/3 after four rounds; the Slots: 1 Claude subagent live at any instant (slots=3), 0 resumes, 0 hook denials, 0 stalls. Review lanes wrote rustc probes into their sandboxed clones in v1, v3, v4 (KEPT; nothing reached the checkout). +I-3 (`transcript-gate.sh`, run from the session cwd; run from the worktree it looks in the wrong project directory and passes vacuously with `attempted=0` — a harness finding): ``` -PASS transcript-gate: attempted=0 denied=0 stalled=0 running_peak=0 slots=3 — 0 subagents ran in /home/noah/.claude/projects/-home-noah-src-aprender-worktrees-PMAT-3430/a26ca3bb-cffd-4f6e-b6fd-358f489cf451 (rule=pid-file (/run/user/1000/paiml-implement/pid-2717702); vacuous but honest — say so in the receipt) +PASS transcript-gate: attempted=4 denied=0 stalled=0 running_peak=1 slots=3 (agent_calls=4 resumes=0 workflow_started=0) ``` ## PMAT-3430 plan quorum — history, v1–v4. **Not 3/3. No v5 written; no code written.**