Commit Graph

3 Commits

Author SHA1 Message Date
Drew T 8b2bbff831 fix(phase-32): T2c (1) — split_indicator's population is derived from the yamls (was a stale stored 213-name list; tools-health said "213 OK of 213" over 218 binaries); R32 denominator assertion 2026-09-05 00:08:44 -06:00
Drew T 95c7b7fe0f fix(tools): two tools read a source of truth describing a different world (+ hard-gate the third)
Three independent split agents hit both defects in one session, on the tools that CERTIFY and UNDO
the work they were doing. Each is fixed, negative-controlled against the exact failing case, wired
into its siblings, and documented in the same change (cookbook §436).

1. split_indicator attributed a jump table by the STUB'S DIRECTORY PATH. `make extract` does not
   prune a re-homed subseg's `nonmatchings/<old>/` dir, so after a correct, byte-green §431 split
   both the old and new dirs hold the moved stub — and the tool printed NEEDS SPLIT for a split that
   was already correct. owners() now derives the owner from the CONFIG by address (R33), exactly as
   jtbl_carve.func_subseg already does for the identical §8b hazard, and NAMES any leftover stub in
   a `note:` line. Notes now print on an OK verdict too: hiding one behind `st != OK` is the same
   defect in the other direction — a true verdict about a narrower world than the reader believes.
   PROVEN by planting a stale stub for func_80182A00 under its old subseg: OK + the note, where the
   old code would have seen one subseg owning two spans. --self-test still PASSes both directions.

2. jtbl_carve --revert did `git checkout --` on the WHOLE splat yaml. The carve owns only the
   trailing data/.rodata region; the `c` pieces are source configuration it never writes. The blunt
   form cannot tell "carve state I just added" from "the §431 split someone added to the same
   uncommitted file", so --revert after a carve PROBE silently un-split the overlay — each agent
   recovered only because they had backed the yaml up by hand. It now splices back only its own
   region (parse_config gained an optional `lines=` so the SAME region derivation runs over the
   committed text — one derivation, two callers), refuses loudly if the committed region carves onto
   a subseg the current config no longer defines, and reports how many uncommitted `c` pieces it
   preserved. PROVEN in the ov_SC01_084 worktree: carve → revert → the uncommitted split survived
   ("PRESERVED 30 uncommitted `c` piece(s)"), carve lines gone, diff back to the 6 split lines.

   SIBLING: jtbl_family_bank.revert carried the same blunt checkout for the isolation's code pieces.
   It now keeps whatever pre-dated the attempt (the `keep_regions` signal it already trusts for
   src/) and NAMES anything it drops — an isolation region and a §431 split piece are both
   `<ov>_jr_<addr>`, so no name test can tell them apart and only that signal can.

3. NOT A DEFECT, and recorded as such: a speculative carve fails the build with `jtbl_rodata_pads:
   consumed 3 rodata jump table(s) but 9 pad spec(s) given`. That is R43 working — the pad spec is a
   CONSEQUENCE of banking, not a prediction of it — and it reproduces identically on the pristine
   unsplit config, so it is never evidence about a split.

make tools-health: split_indicator is a HARD GATE now, as its own comment promised it would become
once the last violation was split. 213 OK of 213; a new one fails the build instead of being echoed
past.

Cookbook §435 (an overlay TU split is near-free — 0/3,074, 1/2,679, 2/3,254 names crossed, because
the §8b carried decl layer re-emits externs per region so only typedefs can cross; and the gap test
between two rodata runs is "is this word a valid code address", not "is it zero") + §436 (the two
defects and the shape they share). Playbook + SETUP.md carry the emptied CARVE-BLOCKED class.
2026-09-02 18:11:48 -06:00
Drew T b173d88676 feat(split_indicator): detect subsegs that MUST be split before their switch fns can bank
A code object contributes exactly ONE contiguous .rodata run, so a subseg owning raw
jump tables in >=2 non-adjacent island spans makes every switch function outside the one
carveable span unbankable at any effort. main sat in that state from Phase 7 to Phase 31
and eleven functions were written off as 'PROVEN gate-rejects' because of it. The
evidence is derivable from the raw image on day one; nothing was comparing it.

FIRST FLEET RUN: 209/213 OK, 4 overlays flagged — ov_SC01_084, ov_SC02_005, ov_SC02_011,
ov_SC03_105 — holding 16 open functions / 3,613 instructions (18% of the non-main
frontier). All 16 were already in the S71 exclude list, i.e. recorded as if unmatchable
rather than as 'needs a subseg split'. 3.7s fleet-wide.

Self-test covers all three directions: fires on main's pre-S72 island (fed
synthetically, because the real tree no longer holds that state), stays silent on main
today, and does not over-fire on a one-span subseg. Linked-library subsegs are excluded
on principle — their code comes from a .a so cc1 emits no table for them; without that
filter main reports NEEDS SPLIT on libgs6, which the self-test caught.

Wired into make tools-health. accelerators #20 gains the when-to-split rule: split where
the BUILD forces a boundary (decidable at 0% matched), at the span-owner boundaries and
nowhere else, never on TU archaeology.
2026-09-02 14:03:48 -06:00