fix(phase-31): wire the S56 findings into the tools, SETUP.md and memory

Audit after "did you update tooling for those findings" found three gaps
beyond the four committed fixes:

  - R21: docs/SETUP.md had no record of any of it. Adds the "four flow traps"
    table — each trap, its measured cost, and where it is now caught.
  - family_sweep --only had no coverage assertion, so the 3-vs-50 mis-scope
    could recur silently. It now resolves member addrs to their family, always
    prints the coverage line, and refuses when it resolves to zero families.
  - pregate_check modelled the banking driver's typedef strip but never checked
    the consequence. Adds [DROP-RISK] §203 USE-BEFORE-TYPEDEF. R39 NC: flags
    the known-bad splice, 0 false positives on the post-hoist file and all 7
    other wave-Z TUs (first draft read a typedef named in its own comment).

Also corrects §203 and the phase log: the text that banked was the RENAMED
variant, because gate_stage's backlog.save_draft() overwrote the original on
the failed attempt. Bytes are correct (R22 213/213); the claim "draft
byte-unchanged" was not.
This commit is contained in:
Drew T
2026-08-18 15:32:32 -06:00
parent 34b8f772b4
commit e9ac4db9ba
8 changed files with 213 additions and 54 deletions
+29
View File
@@ -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/<bin>/nonmatchings/<bin>/`** — 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 '<symbol>'`, 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.
+28 -28
View File
@@ -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) |
+4 -4
View File
@@ -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)
+20 -20
View File
@@ -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) | |
+10 -1
View File
@@ -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
+18
View File
@@ -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.**
+34 -1
View File
@@ -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"))]
+70
View File
@@ -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 '<symbol>'` -- 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