docs(phase-26a): A4/A5 + the stale-object false-pass hole recorded

This commit is contained in:
Drew T
2026-07-14 10:12:59 -06:00
parent af2f40d153
commit a2a507d079
+8 -1
View File
@@ -74,7 +74,14 @@ The Phase-25 h_seq reframe: the "unique tail" is really per-location families
**`harvest_verify` + `gate_stage` (`commit:0591`) — the byte-gate itself.** It could only see ONE TU: **4.9% → 100%** of ov_SC01_077 reachable; **96.6%** of fleet stubs were unreachable. *Safety argued explicitly:* only the SPLICE LOCATION is derived — the verdict (`make build` + SHA1) is untouched, and since `INCLUDE_ASM` pastes the original asm, a bug can make it **fail to bank but never falsely bank**. Verified by an **identity known-answer test** across three different split TUs → `d19c9580` BYTE-IDENTICAL, tree clean.
**STILL TO DO in A3:** `family_manifest`/`family_hseq` (the matched-set oracle — **1,071 of 2,758 advertised families are already fully matched**, 62% of the endgame plan's byte-weight is phantom) · **DELETE `census_conflict_callees`** (R33 — `reconcile_tu` answers its question from the build) · `exemplar_miner`/`difficulty` (inherited holes) · `jr_isolate_all.jr_inventory` (reads its banked-roster from an **ephemeral gitignored scratch file**). `jtbl_family_bank` (3 bugs found by hand, never audited) · the SELECTION tools `family_hseq`/`family_manifest`/`wave_targets`/`exemplar_miner`/`worklist`/`build_fuel_manifest` · the closeness oracle `masked_diff`/`match_one` that every crack agent trusts · the bank/gate path `gate_stage`/`harvest_verify`/`bank_exemplar` · `dup_report`/`difficulty`/`sig_image`/`canon_draft_decls`/`derive_canonical_sigs`/`census_conflict_callees`. **METHOD (do NOT audit by reading regexes — that is the failure mode that WROTE these bugs):** build a deliberately **over-approximating** candidate detector, run both over the real corpus, compute `gap = candidates − parsed`, and **classify every item in the gap** as a real silent skip or a justified exclusion. Pair each auditor with an **adversarial skeptic** told to refute it (in the first audit several magnitudes were wrong in *both* directions and one whole class was refuted). **R33 before R32.**
- [ ] **A3 — `tools/cdecl.py`: ONE coverage-asserting C-decl parser** `[Max]` *(blocked on A2)* — the same char-class disease (`[\w\s\*]` cannot hold `(`, `,`, `[N]`) is **independently re-implemented in 6+ scanners**; two tools in ONE pipeline already disagree about what a data decl *is*. Patching 6 regexes = 6 chances to diverge again. **R33:** one statement-oriented splitter + a **TOTAL** classifier (scalar/ptr/`[N]`/fn-ptr/fn-ptr-array/multi-declarator/wrapped/trailing-comment) with a built-in coverage assertion, consumed by all of them. Migrate consumers one at a time, each byte-gated. Kills ~15 of the 40 findings at the root.
- [ ] **A4 — Wire `reconcile_tu.py`; retire the fleet-majority oracle** `[Max]` *(blocked on A2)* — `reconcile_decls.canonical_data_map` asks *"what does the FLEET call this symbol"* when C's constraint is **per-TU**: 36.8% of symbols carry ≥2 conflicting spellings, so one fleet-wide answer is **wrong for some TU by construction** — it returns an **actively WRONG** canon for **3,717** symbols and disagrees with the TU's own decl on **21.7%** of pairs (**every one of 678 TUs** has ≥1 broken answer). `reconcile_tu.py` is **written + validated + NOT WIRED** (`commit:0580`). Wire → `bank_exemplar` / `jtbl_family_bank` / `gate_stage`; byte-gate. Unblocks `func_8017A4AC` (287 KB), `func_8013F350`, `func_80131340`.
- [x] **A4 — The `listCdBuffer` corpus defect: 193 unmatchable slices** `[Max]` — **DONE (`commit:0594`). R22 CLEAN-FLEET: `make clean` → extract 136 → build 136 → `check-all` = 136 PASSED, 0 FAILED. `make audit-corpus`: 0 PHANTOM + 0 TRUNCATED (was 193).**
`symbols.us.txt:981` declared `listCdBuffer = 0x80180000` — a *correct* Phase-3 name for **main's** LIST.CD RAM buffer. But that address is **outside main's image** and **inside the overlay slot**, and every overlay stacks that file. **High RAM is reused: an address that is a buffer to main is live CODE to an overlay.** splat therefore cut **97 real functions in half** and **invented 96 phantoms** — 193 slices **unmatchable by construction** (the phantom `listCdBuffer.s` literally begins `lw $ra,0x10($sp)` / `addiu $sp,$sp,0x18` / `jr $ra` — splat cut a function immediately before its **epilogue** and called the epilogue a function).
**It had already contaminated real work:** in `ov_SC03_031` the cut landed where the epilogue was exactly `jr $ra; nop`, so the Phase-26 ×134 sweep **banked the phantom** as `void listCdBuffer(void) {}` — byte-correct, gate-green, and entirely fictitious — while leaving `func_8017FFC4` permanently unmatchable.
**And it was hiding a real function:** in `ov_SC01_084`/`ov_SC02_041`/`ov_SC03_094`/`ov_SC06_008` there IS a genuine function at 0x80180000 (111/35/28/74 ins), reachable **only** via a fn-pointer table and never by `jal` — so splat cannot find it and needs the boundary *declared*. `listCdBuffer` had supplied it **by luck**. Now declared honestly, per-overlay, exactly where R13/R15 says it belongs.
**THE RULE (the mirror of R13/R15, never written down):** *a symbol whose address falls inside ANOTHER binary's vram window must never enter that binary's symbol stack.* Fix: `config/symbols.us.ram.txt`, stacked **only** by main.
- [x] **A5 — The closeness oracle lied on 155 functions** `[Max]` — **DONE (`commit:0594`).** `masked_diff._reloc_kind()` knew 26/HI16/LO16; an over-approximating sweep of every reloc `objdump` emits across all 3,367 build objects found **four** — the missing one, **`R_MIPS_PC16`**, fell through to a **full-word compare** against an *unresolved placeholder*, which can never succeed. **Decisive test** (derived from the invariant, coverage-asserted): `INCLUDE_ASM` pastes the ORIGINAL asm, so every stub MUST score 0 — **2,741 scored: old mask 150 LIES, PC16 masked 4** (the 4 are the separate length-delta defect). A phantom non-zero sends an agent to grind at a wall that isn't there, and the wasted attempt is booked as a *matching* failure → `reserved_walls()` → **a function that was never broken is permanently blacklisted.**
- [x] **NEW (found by cutting the R22 corner): a STALE OBJECT CAN PRODUCE A FALSE PASS** `[Max]` — `.o ← .s` is **not** a dependency make can see (assembly arrives via `INCLUDE_ASM` → `.include` → maspsx/as, *after* cpp; `-MMD` tracks headers only). Re-extract + build incrementally ⇒ make links a **stale object** — and because `INCLUDE_ASM` pastes the ORIGINAL bytes, the stale object still yields the original image, **SHA1 goes GREEN**, and the split just changed is never exercised. **A broken config change can be "verified" by an incremental build.** Live proof: 8 of 136 linked stale objects here and failed **loudly only by luck**. R22/H3 already legislate this and I broke them — but *a rule that needs a human to remember it is not a gate*. `extract` now invalidates the objects that include what it rewrote. **Structural, not advisory.**
- [ ] **A6 — Wire `reconcile_tu.py`; retire the fleet-majority oracle** `[Max]` *(blocked on A2)* — `reconcile_decls.canonical_data_map` asks *"what does the FLEET call this symbol"* when C's constraint is **per-TU**: 36.8% of symbols carry ≥2 conflicting spellings, so one fleet-wide answer is **wrong for some TU by construction** — it returns an **actively WRONG** canon for **3,717** symbols and disagrees with the TU's own decl on **21.7%** of pairs (**every one of 678 TUs** has ≥1 broken answer). `reconcile_tu.py` is **written + validated + NOT WIRED** (`commit:0580`). Wire → `bank_exemplar` / `jtbl_family_bank` / `gate_stage`; byte-gate. Unblocks `func_8017A4AC` (287 KB), `func_8013F350`, `func_80131340`.
- [ ] **A5 — Family engine: 93 phantom exemplars, 1,834 free members** `[Max]` *(blocked on A2)* — `family_remap.extract_unit` globs only `src/<ov>/<ov>*.c`, so it **cannot read a `DEFINE_func_*` body out of `engine_core.h`** → **93 of 218 (43%)** "matched" exemplars are PHANTOM and **1,834** still-stubbed, PURE/IMM-clean, unpinned member templates are **never attempted**. Plus `symbol_map` naming an address-taken **function** `D_<addr>` (3 families × 37 members silently no-op, booked as *byte* failures), `gather_externs`' multi-line externs (9), `family_sweep.stub_map`'s curated-name stubs (3 phantom families). Fix, assert, **re-run the sweep, harvest.**
- [ ] **A6 — `dedup_propagate`: 407 invisible files** `[Max]` *(blocked on A2)* — a hardcoded suffix allowlist predating the jr splits sees **404 of 811** overlay `.c`; the gap holds **36,135 stubs + ~32,000 inline defs — half the corpus** — and it gates *all* of dedup_propagate. ≥266 free ×N banks cannot even be nominated. Fix = **glob, not allowlist** (the next split family would re-open it), landed **together with** `find_site`'s K&R blindness or the newly-visible half's biggest prizes (`func_8015AE2C`, `func_80166994` — both K&R) are still dropped. Re-run `--auto-from`, harvest.
- [ ] **A7 — `build_engine_types`: dead on 81% of its own corpus** `[Max]` *(blocked on A2)* — hard-exits on **573 of 709** type-bearing files (all 134 overlays) because 1,929 **tagged**-struct typedefs trip an overlap guard written when the source had only anonymous ones. `inject_capped_externs` routes every type-bearing body to this tool as the type-heavy tail's **only** sanctioned unblocker — and it cannot run on the corpus that tail lives in. **This is the "3,098 type-heavy tail" and the 9 zero-bank type-using families.** (Fail-LOUD, not silent — it went unfixed because the message read like an edge case rather than an 81% coverage failure.)