diff --git a/docs/SETUP.md b/docs/SETUP.md index 674a3428b..8d0f3b180 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -842,3 +842,32 @@ min 0.009 / p50 0.08 / p90 0.26 / max 0.53, and hand-checking below the threshol co-occurrence ("delay slot" matching every section that says "delay slot"). Emitting those is worse than emitting nothing: an empty list costs a reader nothing, a plausible-but-wrong section costs a read. At 0.15 it reports 14 confident candidates out of 67 rather than 51 mostly-noise ones. + +### Crack-wave toolchain — the four flow traps, and where each is now caught (P31 S56) + +Every one of these was a capability that was **silently off**: the tool ran, exited zero, and +reported a true number about a scope narrower than the caller believed. None was found by the +byte-gate, because the byte-gate is a perfect correctness oracle and a null coverage oracle (R34). +Each now asserts its own coverage (R32) or cannot be mis-called (R33). + +| trap | what it cost | where it is caught NOW | +|---|---|---| +| **`wave_snapshot.py` assumed `asm//nonmatchings//`** — right only for single-TU binaries; every split-TU overlay lives under its own TU stem. Found 9 of 75 wave-Z targets. Its R32 assertion fired correctly, so the snapshot step got hand-rolled instead — **and the S46 validity gate living inside it came off the path for waves T–Z.** | 6 waves ran with no phantom-target check | `wave_snapshot` now honors the card's `sub` (then the old convention, then a single-hit glob). NC: 9/9 legacy targets byte-identical, 66/66 split-TU recovered. | +| **The S46 validity gate was only reachable through the snapshot step.** Its own comment says "a gate that is a separate command is a gate someone forgets" — and then a path bug forgot it. | S46 measured ~29 phantom targets × 3 tiers = 87 wasted agents, 9.7M tokens | Wired into **`build_wave_atlas`**, where cards are BORN, with an `--allow-invalid` escape. `validate_targets._key` also accepts the card spelling `fn` (it knew only `name`/`n`, so every card-shaped dict silently read MALFORMED). | +| **`family_sweep --hseq --only` is keyed on the FAMILY EXEMPLAR address**, but the natural thing to pass after a wave is the addresses you just banked — which are family *members*. Strictly keyed, that selects almost nothing and reports success. | wave Z: 15 addrs → 2 families → **3 banked**. Re-derived through membership: 21 families / 196 open members → **50 banked**. A 17× miss that looked like work. | `--only` now resolves member addrs to their family, **always prints the coverage line** (`N addrs -> M families, K unresolved`), and REFUSES when it resolves to zero families. | +| **`decl_prior._ASM_SYM`'s leading `\b` bound to the whole alternation**, demanding a word boundary before `%` — impossible in a `.s`, where that position follows a space. The `%hi/%lo` arm had **never fired**: the card's promised GLOBAL-TYPE row was 0 of 1,210 across four waves. | four waves of cards missing every data-symbol prior | Fixed (cookbook §204-E). NC over 75 wave-Z targets: `jal` 306 → 306 **zero regressions**, data **0 → 299** symbols recovered. | + +**A fifth, in the banking driver rather than the wave tools:** when two slate-mates share a typedef, +`harvest_verify.strip_provided_typedefs` drops the duplicate from both drafts and the one surviving +definition sits wherever its owner splices — so if the *other* function is earlier in ADDRESS order, +its externs reference a type the file has not defined yet (`parse error before ''`, which +reads like a codegen residual). `pregate_check` modelled the driver faithfully but never checked the +consequence; it now reports `[DROP-RISK] §203 USE-BEFORE-TYPEDEF` with the fix (hoist to the top of +the TU — never rename in one draft, which gives one symbol two types and just moves the failure). +NC: flags the known-bad pre-hoist splice, 0 false positives on the post-hoist file and on all 7 other +wave-Z TUs (the first draft of the check read a typedef named in its own *comment* as a use — it now +searches a comment-blanked copy with offsets preserved). + +**The standing lesson for this flow:** after any wave step that reports a count, ask what +denominator that count is a fraction of. `3 banked` and `50 banked` were the same tool, same tree, +same day — the only difference was whether the scope was asserted. diff --git a/docs/family-cousins.md b/docs/family-cousins.md index ad4d66907..e8c61819b 100644 --- a/docs/family-cousins.md +++ b/docs/family-cousins.md @@ -1,6 +1,6 @@ # P30 S49 — cousin-unit survey (similarity tier over the open frontier) -> Generated by `tools/family_cousins.py` at HEAD `commit:2541` from `.run/family_hseq.json` + the binary sigs (R32-checked against an independent stub recount — a stale map fails loud). Compare digests only at the same HEAD. +> Generated by `tools/family_cousins.py` at HEAD `commit:2562` from `.run/family_hseq.json` + the binary sigs (R32-checked against an independent stub recount — a stale map fails loud). Compare digests only at the same HEAD. > > **A unit is a CANDIDATE grouping** — same-source cousins split by skeleton drift (insertion/deletion; the li-expansion class). Cousins need a per-member seeded CRACK, never a `family_sweep` remap. The whole-binary byte-gate stays the sole arbiter. @@ -8,11 +8,11 @@ | category | units | open fns | open ins | lever | |---|--:|--:|--:|---| -| A-prop | 442 | 2099 | 94202 | family_sweep (propagation) | -| seeded | 360 | 888 | 33691 | seeded crack — edit a >=0.85-similar MATCHED body | -| cousin-multi | 1170 | 4015 | 162666 | 1 crack seeds the unit's other members | -| cold | 2878 | 2878 | 183157 | full-price crack (the honest unique tail) | -| **total** | **4850** | **9880** | **473716** | | +| A-prop | 438 | 2053 | 92382 | family_sweep (propagation) | +| seeded | 356 | 882 | 33412 | seeded crack — edit a >=0.85-similar MATCHED body | +| cousin-multi | 1163 | 3995 | 160575 | 1 crack seeds the unit's other members | +| cold | 2823 | 2823 | 178507 | full-price crack (the honest unique tail) | +| **total** | **4780** | **9753** | **464876** | | ## Top units by open instructions (whole-cluster weight) @@ -36,25 +36,25 @@ | 16 | A-prop | 1035 | 11 | 5 | 1.0 | `ov_SC06_018:0x80187320` | `ov_SC02_011:0x80188250` (94 ins ×4) | | 17 | A-prop | 987 | 3 | 1 | 1.0 | `ov_SC01_000:0x8013c414` | `ov_SC01_077:0x8013c414` (329 ins ×3 jr) | | 18 | A-prop | 979 | 18 | 11 | 1.0 | `ov_SC06_024:0x8017d474` | `ov_SC06_022:0x8017d6e0` (59 ins ×4) | -| 19 | A-prop | 964 | 12 | 3 | 1.0 | `md_SC03_073:0x801efc94` | `md_SC03_079:0x801efb94` (87 ins ×4) | -| 20 | A-prop | 914 | 8 | 4 | 1.0 | `ov_SC02_017:0x8017f92c` | `ov_SC02_017:0x8017f92c` (115 ins ×4) | -| 21 | A-prop | 908 | 19 | 6 | 1.0 | `ov_SC02_041:0x80182ccc` | `ov_SC02_041:0x80182ccc` (45 ins ×9) | -| 22 | A-prop | 889 | 7 | 1 | 1.0 | `ov_SC01_000:0x80145cec` | `ov_SC01_077:0x80145cec` (127 ins ×7) | -| 23 | A-prop | 872 | 4 | 1 | 1.0 | `ov_MAIN_012:0x8017cf3c` | `ov_MAIN_012:0x8017cf3c` (218 ins ×4 jr) | -| 24 | A-prop | 816 | 3 | 1 | 1.0 | `ov_SC01_000:0x8013b83c` | `ov_SC01_077:0x8013b83c` (272 ins ×3 jr) | -| 25 | cousin-multi | 810 | 10 | 2 | · | · | `ov_SC01_005:0x8017e108` (84 ins ×5) | -| 26 | A-prop | 798 | 3 | 1 | 1.0 | `ov_SC01_000:0x80130d48` | `ov_SC01_077:0x80130d48` (266 ins ×3) | -| 27 | cousin-multi | 784 | 4 | 1 | · | · | `ov_SC03_108:0x8017ffd0` (196 ins ×4 jr) | -| 28 | cousin-multi | 767 | 15 | 7 | 0.839 | · | `ov_SC03_028:0x8018966c` (63 ins ×4) | -| 29 | seeded | 752 | 14 | 10 | 0.921 | `ov_SC02_027:0x8017f6d4` | `ov_SC02_027:0x8017f858` (48 ins ×3) | -| 30 | A-prop | 684 | 6 | 5 | 1.0 | `md_SC03_077:0x801ef9c0` | `ov_SC02_000:0x80181e70` (122 ins ×2) | -| 31 | cousin-multi | 680 | 8 | 2 | 0.7 | · | `md_SC03_077:0x801efb58` (87 ins ×4 jr) | -| 32 | cousin-multi | 668 | 3 | 3 | · | · | `ov_SC02_017:0x80186770` (230 ins ×1) | -| 33 | cousin-multi | 658 | 14 | 5 | · | · | `ov_SC04_018:0x80181124` (52 ins ×4) | -| 34 | cold | 657 | 1 | 1 | · | · | `md_MAIN_003:0x800d12d0` (657 ins ×1) | -| 35 | A-prop | 656 | 29 | 10 | 1.0 | `ov_SC03_001:0x8017f0ec` | `ov_SC03_001:0x8017f0ec` (20 ins ×13) | -| 36 | A-prop | 655 | 16 | 6 | 1.0 | `ov_SC02_005:0x8017df78` | `ov_SC02_005:0x8017df78` (39 ins ×10) | -| 37 | A-prop | 647 | 16 | 6 | 1.0 | `ov_SC01_074:0x8017dba0` | `ov_SC01_077:0x8017d840` (39 ins ×9) | -| 38 | A-prop | 642 | 16 | 5 | 1.0 | `ov_SC01_000:0x8017d024` | `ov_SC01_000:0x8017d024` (38 ins ×8) | -| 39 | A-prop | 630 | 42 | 1 | 1.0 | `md_SC03_073:0x801ef5f4` | `ov_SC01_077:0x80130d0c` (15 ins ×42) | -| 40 | A-prop | 609 | 3 | 1 | 1.0 | `ov_SC01_004:0x8017e804` | `ov_SC01_004:0x8017e804` (203 ins ×3) | +| 19 | A-prop | 914 | 8 | 4 | 1.0 | `ov_SC02_017:0x8017f92c` | `ov_SC02_017:0x8017f92c` (115 ins ×4) | +| 20 | A-prop | 908 | 19 | 6 | 1.0 | `ov_SC02_041:0x80182ccc` | `ov_SC02_041:0x80182ccc` (45 ins ×9) | +| 21 | A-prop | 889 | 7 | 1 | 1.0 | `ov_SC01_000:0x80145cec` | `ov_SC01_077:0x80145cec` (127 ins ×7) | +| 22 | A-prop | 872 | 4 | 1 | 1.0 | `ov_MAIN_012:0x8017cf3c` | `ov_MAIN_012:0x8017cf3c` (218 ins ×4 jr) | +| 23 | A-prop | 816 | 3 | 1 | 1.0 | `ov_SC01_000:0x8013b83c` | `ov_SC01_077:0x8013b83c` (272 ins ×3 jr) | +| 24 | cousin-multi | 810 | 10 | 2 | · | · | `ov_SC01_005:0x8017e108` (84 ins ×5) | +| 25 | A-prop | 798 | 3 | 1 | 1.0 | `ov_SC01_000:0x80130d48` | `ov_SC01_077:0x80130d48` (266 ins ×3) | +| 26 | cousin-multi | 784 | 4 | 1 | · | · | `ov_SC03_108:0x8017ffd0` (196 ins ×4 jr) | +| 27 | cousin-multi | 767 | 15 | 7 | 0.839 | · | `ov_SC03_028:0x8018966c` (63 ins ×4) | +| 28 | seeded | 752 | 14 | 10 | 0.921 | `ov_SC02_027:0x8017f6d4` | `ov_SC02_027:0x8017f858` (48 ins ×3) | +| 29 | cousin-multi | 680 | 8 | 2 | 0.7 | · | `md_SC03_077:0x801efb58` (87 ins ×4 jr) | +| 30 | cousin-multi | 668 | 3 | 3 | · | · | `ov_SC02_017:0x80186770` (230 ins ×1) | +| 31 | cousin-multi | 658 | 14 | 5 | · | · | `ov_SC04_018:0x80181124` (52 ins ×4) | +| 32 | cold | 657 | 1 | 1 | · | · | `md_MAIN_003:0x800d12d0` (657 ins ×1) | +| 33 | A-prop | 656 | 29 | 10 | 1.0 | `ov_SC03_001:0x8017f0ec` | `ov_SC03_001:0x8017f0ec` (20 ins ×13) | +| 34 | A-prop | 656 | 8 | 2 | 1.0 | `md_SC03_073:0x801efc94` | `md_SC03_079:0x801efb94` (87 ins ×4) | +| 35 | A-prop | 655 | 16 | 6 | 1.0 | `ov_SC02_005:0x8017df78` | `ov_SC02_005:0x8017df78` (39 ins ×10) | +| 36 | A-prop | 647 | 16 | 6 | 1.0 | `ov_SC01_074:0x8017dba0` | `ov_SC01_077:0x8017d840` (39 ins ×9) | +| 37 | A-prop | 642 | 16 | 5 | 1.0 | `ov_SC01_000:0x8017d024` | `ov_SC01_000:0x8017d024` (38 ins ×8) | +| 38 | A-prop | 630 | 42 | 1 | 1.0 | `md_SC03_073:0x801ef5f4` | `ov_SC01_077:0x80130d0c` (15 ins ×42) | +| 39 | A-prop | 609 | 3 | 1 | 1.0 | `ov_SC01_004:0x8017e804` | `ov_SC01_004:0x8017e804` (203 ins ×3) | +| 40 | cousin-multi | 602 | 2 | 2 | · | · | `ov_SC01_001:0x8017fee0` (305 ins ×1) | diff --git a/docs/family-hseq.md b/docs/family-hseq.md index 1c3db4af8..47048cd06 100644 --- a/docs/family-hseq.md +++ b/docs/family-hseq.md @@ -5,13 +5,13 @@ > Generated by `tools/family_hseq.py` from the 212 binary sigs + per-binary src stubs. Ranked by TEMPLATABLE byte-weight (PURE+IMM members × nins × 4). The byte-gate is the arbiter. > -> **Scope (212 binaries): 141 location overlays + 70 md_* modules + the resident · MAIN IS EXCLUDED** · generated at HEAD `commit:2559` · stub set derived from `corpus.stubs` — the same oracle `progress.py` counts, so same-tree overlay totals agree by construction; compare digests only at the same HEAD. +> **Scope (212 binaries): 141 location overlays + 70 md_* modules + the resident · MAIN IS EXCLUDED** · generated at HEAD `commit:2562` · stub set derived from `corpus.stubs` — the same oracle `progress.py` counts, so same-tree overlay totals agree by construction; compare digests only at the same HEAD. -**Fleet (overlays):** 97.3% fn / 96.5% instr / 92.8% distinct-code matched. Unmatched: 9,806 instances / 467,271 ins (8,442 distinct classes). +**Fleet (overlays):** 97.3% fn / 96.5% instr / 92.8% distinct-code matched. Unmatched: 9,753 instances / 464,876 ins (8,404 distinct classes). -**Tail cross-check (Phase-25 close):** 6,452 tail fns / 307,802 ins → 367 h_seq families ≥2, **42 substantial (nins≥80) / 23,013 ins**. +**Tail cross-check (Phase-25 close):** 6,436 tail fns / 307,262 ins → 365 h_seq families ≥2, **41 substantial (nins≥80) / 22,755 ins**. -**Full frontier (all unmatched by h_seq):** 1817 target families (≥2 members or a matched sibling) + 3958 singletons (Step-D residue). Substantial: **168 families / 65,255 templatable ins**, 70 with a matched sibling (zero-crack). Substantial member classes: 403 PURE · 17 IMM · 6 STRUCT-excluded. +**Full frontier (all unmatched by h_seq):** 1806 target families (≥2 members or a matched sibling) + 3958 singletons (Step-D residue). Substantial: **167 families / 64,997 templatable ins**, 69 with a matched sibling (zero-crack). Substantial member classes: 400 PURE · 17 IMM · 6 STRUCT-excluded. ## Top substantial families (by templatable byte-weight) diff --git a/docs/frontier-atlas.md b/docs/frontier-atlas.md index 495591658..a5579bf08 100644 --- a/docs/frontier-atlas.md +++ b/docs/frontier-atlas.md @@ -1,40 +1,40 @@ # The Frontier Atlas (P31 T5) -> Generated at HEAD `commit:2541` by `tools/atlas.py` — REGENERATE, never edit. Groups are ADVISORY (the byte-gate is the arbiter). Calibration: warm merge at norm-ratio ≥ 0.7, kNN floor 0.55. +> Generated at HEAD `commit:2562` by `tools/atlas.py` — REGENERATE, never edit. Groups are ADVISORY (the byte-gate is the arbiter). Calibration: warm merge at norm-ratio ≥ 0.7, kNN floor 0.55. -**Scope:** 10632 open instances / 6406 skeletons across 213 binaries (main: 752 — atlas-layer join (family maps stay non-main)). -**Tiers:** T1.5 h_seqn merges 2 · warm merges 868 (from 17018 ratio calls) · seed sweep: 4586 skeletons carry a ≥0.55 seed from the 3587-skeleton matched pool. +**Scope:** 10505 open instances / 6331 skeletons across 213 binaries (main: 752 — atlas-layer join (family maps stay non-main)). +**Tiers:** T1.5 h_seqn merges 2 · warm merges 866 (from 16565 ratio calls) · seed sweep: 4556 skeletons carry a ≥0.55 seed from the 3657-skeleton matched pool. ## Groups by category | cat | groups | instances | ins | |---|--:|--:|--:| -| A-prop | 441 | 2131 | 94475 | -| cold | 1971 | 1971 | 155174 | -| cousin-multi | 1364 | 5163 | 199906 | +| A-prop | 437 | 2085 | 92655 | +| cold | 1920 | 1920 | 150803 | +| cousin-multi | 1355 | 5139 | 197536 | | main-only | 314 | 314 | 27589 | -| seeded | 360 | 890 | 33715 | +| seeded | 356 | 884 | 33436 | | tiny | 97 | 163 | 827 | ## Groups by lever (confidence) | lever | groups | instances | ins | |---|--:|--:|--:| -| head-crack[default] | 933 | 3069 | 109454 | -| UNKNOWN[none] | 1585 | 1585 | 101713 | +| head-crack[default] | 924 | 3045 | 107088 | +| UNKNOWN[none] | 1536 | 1536 | 97537 | | extend-tell[tell] | 582 | 1042 | 78266 | | jtbl-carve[tell] | 191 | 286 | 46068 | -| redraft[measured] | 236 | 973 | 37467 | -| family-sweep[default] | 230 | 891 | 32253 | -| integration[measured] | 47 | 483 | 20618 | -| seeded-crack[default] | 272 | 591 | 19205 | -| len-vein[measured] | 146 | 659 | 13375 | -| plumbing[ledger] | 39 | 188 | 9526 | +| redraft[measured] | 234 | 971 | 37272 | +| family-sweep[default] | 226 | 866 | 30820 | +| integration[measured] | 46 | 481 | 20473 | +| seeded-crack[default] | 268 | 585 | 18926 | +| len-vein[measured] | 146 | 653 | 13186 | | swaprepeat-tell[tell] | 70 | 119 | 9316 | +| plumbing[ledger] | 39 | 173 | 9301 | | o0-lane[tell] | 31 | 69 | 6564 | | cc1[measured] | 12 | 104 | 6382 | -| cc1[ledger] | 16 | 70 | 5489 | -| needs-autopsy[measured] | 52 | 263 | 5090 | +| cc1[ledger] | 17 | 71 | 5643 | +| needs-autopsy[measured] | 52 | 264 | 5104 | | frame-172[measured] | 8 | 32 | 4078 | | needs-autopsy[ledger] | 10 | 34 | 4036 | | near-crack[measured] | 10 | 63 | 1829 | @@ -46,7 +46,7 @@ | gid | cat | lever | inst | ins | exemplar | seed | |---|---|---|--:|--:|---|---| -| 527622c1c114 | cousin-multi | integration[measured] | 257 | 5926 | ov_SC01_084:func_80180CBC (31) | 0.72 | +| 152063fd6de6 | cousin-multi | integration[measured] | 256 | 5908 | ov_SC01_084:func_80180CBC (31) | 0.72 | | e8bf2260391b | cousin-multi | frame-172[measured] | 16 | 3936 | ov_SC01_077:func_8017C294 (246) | | | b0e41839e641 | cousin-multi | redraft[measured] | 111 | 2848 | main:func_8001FB8C (31) | 0.72 | | 4a254010e122 | A-prop | needs-autopsy[ledger] | 4 | 2444 | ov_SC03_001:func_801898E4 (611) | 1.00 | @@ -69,7 +69,6 @@ | 93cd03e11398 | A-prop | cc1[ledger] | 11 | 1035 | ov_SC06_032:func_801820C4 (113) | 1.00 | | 4109ef56e262 | A-prop | jtbl-carve[tell] | 3 | 987 | ov_MAIN_012:func_8013C414 (329) | 1.00 | | 435e948b661b | A-prop | extend-tell[tell] | 18 | 979 | ov_SC03_100:func_8017EC84 (67) | 0.84 | -| 75f80de517ba | A-prop | family-sweep[default] | 12 | 964 | md_SC03_079:func_801EFB94 (87) | 0.94 | | 4f680d0642d8 | A-prop | extend-tell[tell] | 8 | 914 | ov_SC02_011:func_80182B88 (118) | 0.99 | | dd647efa7db9 | cousin-multi | extend-tell[tell] | 8 | 912 | main:func_8001E378 (188) | 0.59 | | 9ec470fd06a4 | A-prop | family-sweep[default] | 19 | 908 | ov_SC02_026:func_80182318 (52) | 0.85 | @@ -85,4 +84,5 @@ | 334bc03beb1f | cousin-multi | integration[measured] | 68 | 760 | md_SC07_003:func_801A38A8 (15) | 0.90 | | 89269e496a2d | cousin-multi | extend-tell[tell] | 23 | 760 | ov_SC03_092:func_8017E6A0 (45) | 0.75 | | ab1569f4386c | seeded | redraft[measured] | 14 | 752 | ov_SC03_096:func_8017D130 (66) | 0.83 | -| 6f8c3c72cc0f | A-prop | len-vein[measured] | 6 | 684 | ov_SC02_000:func_80181E70 (122) | 0.96 | +| b1da4f0f7f14 | cousin-multi | jtbl-carve[tell] | 8 | 680 | md_SC03_077:func_801EFB58 (87) | 0.68 | +| bb7d0989abd9 | main-only | UNKNOWN[none] | 1 | 670 | main:func_800226C0 (670) | | diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index f6fddff3b..28c50711e 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -21480,7 +21480,16 @@ stripper delete the draft's copy as designed: typedef struct { u8 f0; u8 f1; u8 f2; u8 f3; } Quad4_800CCB14; ``` -Banked on the next gate (commit `commit:2558`), draft byte-unchanged from the wave's MATCH. +Banked on the next gate (commit `commit:2558`). + +**Correction, recorded because it bit the writer of this section (P31 S56).** The text that banked +is the RENAMED variant, not the original draft — `gate_stage` calls `backlog.save_draft()` on +failure, so the failed rename attempt OVERWROTE `.run/backlog_drafts/func_800CC310.c`, and copying +"the original" back copied the rename. The BYTES are correct (whole-binary gate + R22 213/213), but +the TU now carries two names for one 4-byte shape, and a symbol-rewriting transform mangled prose +*inside a comment* (`not (*(Quad4_800CCAD0 *)&D_800CCB14)`). Two lessons: **a backlog draft path is +not a stable original** — snapshot the text you mean to re-gate; and **a transform that rewrites +symbols must skip comments** (H5). **The general law.** *A type shared by two functions in one TU belongs at the TOP of that TU, not beside whichever of them happened to bank first.* `pregate_check` already hoists for main's diff --git a/phase-ends/CURRENT_PHASE.md b/phase-ends/CURRENT_PHASE.md index 996567fb1..3ed9b72eb 100644 --- a/phase-ends/CURRENT_PHASE.md +++ b/phase-ends/CURRENT_PHASE.md @@ -347,6 +347,10 @@ R22 clean-fleet **213/213** after every banked batch · tools-health green · 0 `func_800CC310`'s duplicate, and `func_800CC310` splices EARLIER in address order — survivor below its uses. First fix (rename to dodge the stripper) was WRONG and the gate caught it: one symbol, two types. Hoisting the typedef to the top of the TU banked it (`commit:2558`). Cookbook **§203**. + **Correction (same session):** the text that banked is the RENAMED variant — `gate_stage`'s + `backlog.save_draft()` overwrote the original draft on the failed attempt, so "copying the + original back" copied the rename. Bytes are correct (R22 213/213); the TU carries two names for + one shape and a transform mangled a COMMENT. A backlog draft path is not a stable original. `func_8018280C` (ov_SC05_001) — the reconciler's IMMOVABLE §183.3 DEF-SIDE-RETURN wall (asm proves `s32`, TU declares `void` at three sites with live callers). The **§200 alias applied to a DEFINITION** — `s32 aF8018280C(s32) __asm__("func_8018280C")` — banked first try (`commit:2559`), @@ -381,6 +385,20 @@ R22 clean-fleet **213/213** after every banked batch · tools-health green · 0 bound to the whole alternation, demanding a word boundary before `%` — impossible in a .s — so the `%hi/%lo` arm had NEVER fired (0 of 1,210 over four waves). NC over the 75 wave-Z targets: **jal 306 -> 306, zero regressions; data 0 -> 299 symbols recovered.** + - **Flow hardening after Drew asked whether the findings were actually wired in (S56).** The audit + found three gaps beyond the four fixes already committed: (a) **R21 violation — `docs/SETUP.md` + was not updated**; now carries a "Crack-wave toolchain — the four flow traps" table naming each + trap, its measured cost and where it is caught. (b) **`family_sweep --only` had no coverage + assertion**, so my 3-vs-50 mis-scope could recur silently; it now resolves member addrs to their + family, ALWAYS prints `--only: N addr(s) -> M family(ies) (... K unresolved)`, and REFUSES on zero + (replaying the exact wave-Z call now prints `15 -> 3, 12 unresolved` instead of quietly sweeping + nothing). (c) **`pregate_check` modelled the driver's typedef strip but never checked the + consequence**; it now reports `[DROP-RISK] §203 USE-BEFORE-TYPEDEF` — R39 bidirectional NC: flags + the known-bad pre-hoist splice, 0 false positives on the post-hoist file and on all 7 other + wave-Z TUs (its first draft read a typedef named in its own COMMENT as a use, so it searches a + comment-blanked copy with offsets preserved). Memories: `crack-wave-sweep-map-regen` rewritten + with the exemplar-vs-member keying, and a new `silently-narrowed-tool-scope` recording all four + instances as one defect class. - Outstanding: `func_8018675C` (ov_SC02_005, closeness 6) — its agent ran ~35 variants and attributed the residual to cse.c:5278's unconditional constant-second swap for symbol-valued pointer bases (retires "reorder the addends" as a lever). **Permuter fuel, not a hand lever.** diff --git a/tools/family_sweep.py b/tools/family_sweep.py index 3eb14fea5..3de6bb7d3 100644 --- a/tools/family_sweep.py +++ b/tools/family_sweep.py @@ -558,7 +558,40 @@ def hseq_sweep(a): if bands: fams = [f for f in fams if f["band"] in bands] if only is not None: - fams = [f for f in fams if int(f["exemplar"]["addr"], 16) in only] + # ---- R32/R33 (P31 S56): --only IS KEYED ON THE FAMILY'S EXEMPLAR ADDRESS, and the caller + # almost never has those. The natural thing to pass after a crack wave is the addresses you + # just BANKED -- which are family MEMBERS. Keyed strictly, that silently selects almost + # nothing: wave Z passed 15 banked addrs, matched 2 families, swept 4 candidates and banked + # 3, reporting success. Re-derived through member lookup it was 21 families / 196 open + # members / 50 banked. A 17x difference, invisible, because the tool answered exactly what + # was asked. So: resolve member addrs to their family too, and ALWAYS report the coverage. + by_ex = {int(f["exemplar"]["addr"], 16): f for f in fams} + member_of = {} + for f in fams: + for m in list(f.get("members", [])) + list(f.get("matched_members", [])): + member_of.setdefault(int(m[1], 16), f) + picked, via_member, unresolved = {}, 0, [] + for addr in only: + f = by_ex.get(addr) + if f is None: + f = member_of.get(addr) + if f is not None: + via_member += 1 + if f is None: + unresolved.append(addr) + else: + picked[id(f)] = f + print(f"[hseq] --only: {len(only)} addr(s) -> {len(picked)} family(ies) " + f"({len(only) - via_member - len(unresolved)} matched an exemplar directly, " + f"{via_member} resolved via family MEMBERSHIP, {len(unresolved)} unresolved)") + if unresolved: + print("[hseq] UNRESOLVED (not an exemplar and not a member of any sweepable family): " + + ", ".join(f"0x{x:08x}" for x in sorted(unresolved)[:12])) + if not picked: + sys.exit("[hseq] REFUSING: --only resolved to ZERO families. Passing addresses that are " + "not exemplars and not members yields a silent no-op that reads as a clean 0. " + "Check the addrs against .run/family_hseq.json, or drop --only.") + fams = list(picked.values()) if a.reconcile_raw: # only families with a raw crack to reconcile fams = [f for f in fams if os.path.exists( os.path.join(REPO, a.reconcile_raw, f"func_{int(f['exemplar']['addr'], 16):08X}.c"))] diff --git a/tools/pregate_check.py b/tools/pregate_check.py index 3cf77e2f3..93543be14 100644 --- a/tools/pregate_check.py +++ b/tools/pregate_check.py @@ -238,6 +238,54 @@ def check_text(path, text): return findings +def _typedef_use_before_def(text): + defs = {} + for st in cdecl.split_statements(text): + if not re.match(r'\s*typedef\b', st.text): + continue + try: + ds = cdecl.parse(st.text) + except Exception: # noqa: BLE001 - a parse miss must not sink the check + continue + for d in ds: + if getattr(d, 'storage', None) == 'typedef' and d.name and d.name not in defs: + defs[d.name] = st.start + # Search for the first use in a COMMENT-BLANKED copy, offsets preserved. The first draft of + # this check searched the raw text and flagged a typedef named in its own explanatory comment + # (R39 negative control: 1 false positive on md_MAIN_034 AFTER it banked). A checker that + # refuses a good slate over prose is worse than no checker -- it discards work silently. + blanked = _blank_comments(text) + bad = [] + for name, pos in sorted(defs.items(), key=lambda kv: kv[1]): + m = re.search(r'\b%s\b' % re.escape(name), blanked) + if m and m.start() < pos: + bad.append((name, m.start(), pos)) + return bad + + +def _blank_comments(text): + """Replace /*...*/ and //... with spaces, preserving every byte offset.""" + out = list(text) + i, n = 0, len(text) + while i < n: + if text.startswith('/*', i): + j = text.find('*/', i + 2) + j = n if j < 0 else j + 2 + for k in range(i, j): + if out[k] != '\n': + out[k] = ' ' + i = j + elif text.startswith('//', i): + j = text.find('\n', i) + j = n if j < 0 else j + for k in range(i, j): + out[k] = ' ' + i = j + else: + i += 1 + return ''.join(out) + + def main(): ap = argparse.ArgumentParser() ap.add_argument('slate') @@ -258,6 +306,28 @@ def main(): _n, texts = gm.substitute(kept, write=False, transform=_driver_transform) + # ---- §203: A DEDUPED TYPEDEF MUST PRECEDE EVERY SPLICE POINT (P31 S56). + # The transform above is faithful to the driver, and that is exactly the hazard: when two + # slate-mates share a type, harvest_verify strips the duplicate from BOTH drafts, and the one + # surviving definition sits wherever its owner splices. If the OTHER function is earlier in + # ADDRESS order, its externs reference a type the file has not defined yet, and the whole batch + # loses that draft to `parse error before ''` -- a PLUMBING failure that reads like a + # codegen residual. Measured: wave Z's md_MAIN_034 group banked 6 of 7 exactly this way; the + # drop cost a gate cycle, three oracles and a wrong first fix to diagnose (cookbook §203). + # Cheap to detect here, because we already hold the post-transform text. + order_defects = [] + for _tu, _text in (texts.items() if isinstance(texts, dict) else texts): + for name, use_at, def_at in _typedef_use_before_def(_text): + line = _text.count('\n', 0, use_at) + 1 + dline = _text.count('\n', 0, def_at) + 1 + order_defects.append((_tu, name, line, dline)) + for tu, name, uline, dline in order_defects: + print(f'[DROP-RISK] §203 USE-BEFORE-TYPEDEF: {tu}: `{name}` is used at line {uline} but ' + f'defined at line {dline}. Two slate-mates share this type and the earlier-addressed ' + f'one lost its copy to strip_provided_typedefs. FIX: hoist the typedef to the top of ' + f'the TU (above every splice point); do NOT rename it in one draft -- that gives one ' + f'symbol two types and the failure just moves.') + # R32 COVERAGE ASSERTION (P31 S54). Until `gate_main` learned per-binary stub maps, an OVERLAY # slate resolved to zero stubs and this tool printed "checking 0 substituted file(s) ... clean" # -- a green light from a checker that had examined nothing, on exactly the slates (overlay