fix(phase-29): the gate manufactured 3 false CC1-FAIL verdicts — carve-refusal, tree hygiene, R32 (§97)

A 15-draft harvest_verify batch reported CC1-FAIL=4 and `final SHA None`. Three of the four were the
HARNESS, not the compiler. Checked the tree FIRST (the MISMATCH is a tree alarm, not a result),
reverted to the committed baseline rather than reasoning about a half-applied state, rebuilt ->
d19c9580 BYTE-IDENTICAL. No banked result was ever at risk: the byte-gate cannot manufacture a match,
but it CAN manufacture a verdict — and verdicts are what the backlog and roadmap are built from.

ORDERING PROVED THE CASCADE (R14): items 1-11 are real (9 PLUMBING, 2 DIFF), all before item 12 —
jtbl_carve REFUSING func_8013B83C (§59(3) non-contiguous same-subseg table). Items 13-16 are four
CC1-FAILs on the SAME ov_SC01_077_o0.o = one refused carve counted four times.

THREE DEFECTS FIXED:
1. `_ok` was computed and IGNORED — a refused carve was spliced and built anyway into a guaranteed
   Error 33, filed as CC1-FAIL. Now a named CARVE-REFUSED class, skipped (one build cheaper).
2. attempt() never restored on failure, so the tree was dirty BETWEEN drafts — and _jtbl_snapshot()
   snapshots the tree AS IT FINDS IT, so a later carve captured an earlier FAILED draft's splice and
   its undo faithfully RE-APPLIED it, after the final _write(baseline). That is the entire
   `final SHA None` mechanism. Invariant restored: the tree is at baseline except while a draft is
   under test (atomic AND bisect branches).
3. The recovery's own `make extract` rc was unchecked (_sh does not raise — §93's sibling). Now loud.
Plus an R32 assertion on the cleanup: at 0 verified a non-empty git status is residue, not a result;
it names the files and the recovery command. It fired correctly on its first real run.

MEASURED RECOVERY (same drafts, clean tree): func_8013B83C -> CARVE-REFUSED; func_801789AC ->
PLUMBING (actionable); func_8017C974 -> DIFF (corroborates its agent's global_alloc spill diagnosis);
func_80140958 -> CC1-FAIL (genuinely its own). final SHA None -> d19c9580; tracked diff empty.

BLAST RADIUS OF §96, HONESTLY: the reconcile_tu span fix unblocked func_80176218 (banked, swept
133/137) and no other draft in the batch. 7 of the 9 PLUMBING are `conflicting types for <the
function itself>` = the DEF-side self-decl axis conform_decls owns — the next lever, now a measured
target list rather than a guess. cookbook §97.
This commit is contained in:
Drew T
2026-07-27 17:17:54 -06:00
parent ea1d6587d6
commit 97cd2739b5
3 changed files with 142 additions and 1 deletions
+47
View File
@@ -7041,3 +7041,50 @@ different "next conflict".
> needed both: 4 data symbols conformed, then 2 callees (`func_80177AD4` `void (int, unsigned int)`,
> `func_80178298` `(u32*, u8*, short, short)`) decl-conformed + call-site-cast to the draft's
> intended widths.
## §97 — The gate's own tree hygiene: a refused carve, an unchecked recovery, and a snapshot that captured a dirty tree (Phase 29 SESSION-22)
A 15-draft `harvest_verify --chunk 1` batch reported `CC1-FAIL=4` and ended `final SHA None`. Three
of those four were manufactured by the harness. The chain, in order:
1. **`_ok` was computed and ignored.** `_jtbl_prep_one` returns `(ok, snapshot)`; the caller tested
only `snapshot`. When `jtbl_carve` REFUSES a table (§59(3): a non-contiguous same-subseg carve),
`ok` is False and `snapshot` is None — so the draft was spliced and built **without its carve**,
which cannot link. The resulting `Error 33` was filed as **CC1-FAIL**, a codegen-flavoured verdict
for a pure plumbing wall. Now classified `CARVE-REFUSED` and skipped (also one build cheaper).
2. **`attempt()` does not restore on failure.** It writes the candidate render and relies on the
NEXT attempt's render to overwrite it — so the tree is dirty *between* drafts.
3. **`_jtbl_snapshot()` snapshots the tree as it finds it.** Combined with (2), a later draft's carve
captured an EARLIER FAILED DRAFT'S SPLICE, and its undo then faithfully **re-applied** it — after
the final `_write(baseline)` had already run. A run that verified nothing therefore ended with a
body spliced on disk and unable to rebuild the binary, which reads exactly like a byte regression
and is not one.
4. **The recovery's own `make extract` was unchecked.** `_sh` does not raise (§93's sibling), so a
failed re-extract inside `_jtbl_restore` would report nothing — a recovery that silently did not
recover, strictly worse than not attempting one.
**The invariant, one line:** *the gate's tree is at `baseline` except while a specific draft is under
test.* Restore after every failed attempt (atomic branch AND bisect branch), and the snapshot can
never capture someone else's failure.
**Plus an R32 coverage assertion on the cleanup itself:** at 0 verified, a non-empty
`git status --porcelain src/<bin> config` is residue, not a result — print the files and the recovery
command rather than making the operator go looking.
**Measured recovery of the false verdicts** (same 4 drafts, same drafts dir, clean tree):
| draft | contaminated | true |
|---|---|---|
| `func_8013B83C` | CC1-FAIL | **CARVE-REFUSED** (§59(3) wall) |
| `func_801789AC` | CC1-FAIL | **PLUMBING** — `conflicting types for func_801789AC` (actionable) |
| `func_8017C974` | CC1-FAIL | **DIFF** — corroborates its agent's `global_alloc` spill diagnosis |
| `func_80140958` | CC1-FAIL | CC1-FAIL (genuinely its own, on a different object) |
`final SHA` went `None` → `d19c9580` BYTE-IDENTICAL; tracked diff empty.
> **The law (R34/R35 again, pointed at the gate itself):** the whole-binary byte-gate is a perfect
> oracle for *did this draft match* and a NULL oracle for *what state did I leave behind*. It cannot
> manufacture a match — no banked result in this session was affected — but it can manufacture a
> **verdict**, and verdicts are what the backlog and the roadmap are built from. A failure class is
> evidence about the compiler only once the harness is proven not to be the cause (§53's carve law,
> generalized from "sweep with the right tool" to "gate from a clean tree").
+51
View File
@@ -5805,3 +5805,54 @@ Each reverted its byte-neutral self-decl edit cleanly ("no dead diff left behind
honest. Per **§59** a sweep failure is a per-sibling INTEGRATION signal, not a codegen verdict:
the next step is to read ONE sibling's real gate result (COMPILE-fail vs byte-DIFF) before
concluding anything about the class.
## ⚠️ T14 — the 15-draft batch banked 0, and 3 of its 4 "CC1-FAIL" verdicts were the HARNESS (§97)
Ran the reconcile+cast ladder over all 24 SESSION-21 drafts to measure the §96 fix's **blast radius**
(a confirmed mechanism proves nothing about consequence). 15 had live stubs, across 9 TUs — the
reconcile was re-run with the TU **derived per draft** (the first pass had forced one `--src-file`,
correct only for the 801734BC TU), and `cast_call_sites` was run grouped by TU.
**Prepared:** 4 drafts reconciled / 10 data symbols; 3 drafts cast-recovered / 8 callees.
**Gated: 0 verified / 15 failed, `final SHA None (*** MISMATCH ***)`.**
### The MISMATCH was a tree-state alarm, not a matching result — checked FIRST
4 source files were left modified at 0 verified. Reverted to the committed baseline rather than
reasoning about a half-applied state (the SESSION-21 lesson), then rebuilt: **`d19c9580`
BYTE-IDENTICAL**. No banked result was ever at risk — every bank this session passed the whole-binary
gate AND a clean-tree R22.
### Ordering proved the counts were a cascade (R14)
Items 1–11 are real verdicts (**9 PLUMBING, 2 DIFF**), all recorded BEFORE item 12 — the
`jtbl_carve` REFUSAL of `func_8013B83C` (§59(3) non-contiguous same-subseg table). Items 13–16 are
four CC1-FAILs **on the same `ov_SC01_077_o0.o`**. That is ONE refused carve counted four times,
three of them against drafts never actually diagnosed.
### Three harness defects, each independently justified (cookbook §97)
1. **`_ok` was computed and ignored** — a refused carve was built anyway into a guaranteed `Error 33`
and filed as CC1-FAIL, a codegen-flavoured verdict for pure plumbing. Now a named
**`CARVE-REFUSED`** class, skipped (and one build cheaper).
2. **`attempt()` never restored on failure**, so the tree was dirty BETWEEN drafts — and
`_jtbl_snapshot()` snapshots the tree as it finds it, so a later carve captured an EARLIER FAILED
DRAFT'S SPLICE and its undo faithfully **re-applied** it, after the final `_write(baseline)`.
That is the whole `final SHA None` mechanism. Fixed with the invariant: *the tree is at `baseline`
except while a draft is under test* (both the atomic and bisect branches).
3. **The recovery's own `make extract` return code was unchecked** (`_sh` does not raise — §93's
sibling, and the same class already logged in Task 14 stage 1). Now loud.
4. Plus an **R32 assertion on the cleanup**: at 0 verified, a non-empty `git status --porcelain` is
residue, not a result — it names the files and the recovery command. **It fired correctly on its
first real run.**
### Measured recovery of the false verdicts (same drafts, clean tree)
`func_8013B83C` CC1-FAIL → **CARVE-REFUSED** · `func_801789AC` CC1-FAIL → **PLUMBING**
(`conflicting types for func_801789AC` — actionable) · `func_8017C974` CC1-FAIL → **DIFF**
(corroborating its agent's `global_alloc` spill-choice diagnosis) · `func_80140958` CC1-FAIL →
CC1-FAIL (genuinely its own, on a different object).
**`final SHA None` → `d19c9580` BYTE-IDENTICAL; tracked diff empty.** 3 of 4 corrected.
### The honest blast-radius answer for §96
The `reconcile_tu` span fix unblocked **`func_80176218` (measured, banked, swept 133/137)** and did
**not** by itself unblock any of the other 14 — their blockers are a different axis. **7 of the 9
PLUMBING are `conflicting types for <the function itself>`**, i.e. the DEF-side self-decl axis that
`conform_decls` owns (the path that banked `func_80179B74` and `func_8015B950` at 137/137 in
SESSION-21). That is the next lever, and it is now a measured target list rather than a guess.
+44 -1
View File
@@ -272,7 +272,13 @@ def _jtbl_restore(snap):
os.remove(rf)
except OSError:
pass
_sh(['make', '--no-print-directory', 'extract', 'BINARY=%s' % a.binary])
# R32/§93: CHECK the recovery's own exit status. `_sh` does not raise, so a failed re-extract here
# left the generated build inputs describing the abandoned isolation while config/ read clean —
# a recovery that silently did not recover, which is strictly worse than not attempting one.
if _sh(['make', '--no-print-directory', 'extract', 'BINARY=%s' % a.binary]).returncode:
print(' [jtbl] !! RESTORE INCOMPLETE: re-extract FAILED after undoing the carve — the tree '
'is NOT back at baseline; stop and `git checkout -- src/ config/` before trusting any '
'later verdict in this run')
_reload_corpus()
@@ -435,6 +441,22 @@ while i < len(items):
_jsnap = None
if len(chunk) == 1:
_ok, _jsnap = _jtbl_prep_one(chunk[0])
if not _ok:
# THE CARVE WAS REFUSED — do NOT build this draft (Phase 29 SESSION-22, byte-witnessed).
# `_ok` was computed and ignored here, so a table-bearing draft whose carve jtbl_carve
# REFUSED (§59(3): a non-contiguous same-subseg table) was spliced and built anyway. It
# cannot link without its carve, so the build fails with `Error 33` — and that failure was
# then recorded as **CC1-FAIL**, i.e. a codegen-flavoured verdict, for what is purely a
# carve-plumbing wall. Worse, the tail of the batch inherited the broken object: one
# refused carve produced FOUR CC1-FAILs on the same `ov_SC01_077_o0.o`, three of them
# against drafts that were never even diagnosed, and the run ended unable to rebuild the
# binary at all (`final SHA None`). Skip it with a NAMED class instead: honest, cheaper by
# one build, and it leaves the tree where the next draft can be judged on its own merits.
fn = chunk[0]
failed.append((fn, 'CARVE-REFUSED'))
print(' - %s (%s) [CARVE-REFUSED: jtbl_carve declined the table; §59(3) plumbing, '
'NOT a codegen verdict]' % (fn, drafts[fn]['conf']))
continue
if _jsnap is not None: # a jtbl carve happened -> reconcile the draft vs the CARVED TU
_jtbl_reconcile(chunk[0])
if attempt(chunk):
@@ -443,6 +465,15 @@ while i < len(items):
elif len(chunk) == 1:
if _jsnap is not None:
_jtbl_restore(_jsnap) # stranded-carve + truncated-TU undo (R32/§61)
# AND PUT THE SPLICE BACK (Phase 29 SESSION-22). `attempt()` writes the candidate render and
# does not restore on failure — it relied on the NEXT attempt's render to overwrite it. That
# left the tree dirty BETWEEN drafts, and `_jtbl_snapshot()` snapshots the tree AS IT FINDS
# IT: a later draft's carve therefore captured an earlier FAILED draft's splice, and its undo
# faithfully RE-APPLIED it — after the final `_write(baseline)` had already run. Net effect: a
# run that verified nothing still ended with a spliced body on disk and could not rebuild the
# binary (`final SHA None`), which reads exactly like a byte regression and is not one.
# The invariant is one line: the tree is at `baseline` except while a draft is under test.
_write(baseline)
# ATOMIC CHUNK — do NOT bisect (Phase-28 T6). The old code fell into the loop below and
# re-ran attempt([fn]) on the SAME single element against the SAME baseline: a byte-identical
# DUPLICATE build. classify_fail reads _last_sha/_last_err, which the failed attempt(chunk)
@@ -462,6 +493,7 @@ while i < len(items):
klass = classify_fail(_last_sha) # DIFF (real codegen) vs PLUMBING (recoverable) vs CC1-FAIL
failed.append((fn, klass))
print(' - %s (%s) [%s]' % (fn, drafts[fn]['conf'], klass))
_write(baseline) # same invariant as the atomic branch above
# restore the accumulated verified state and confirm the binary is byte-identical
_write(baseline)
@@ -477,6 +509,17 @@ if _klass:
print(' failed by class:', ' '.join('%s=%d' % (k, n) for k, n in sorted(_klass.items())))
print('VERIFIED:', ' '.join(verified) or '(none)')
print('FAILED :', ' '.join(fn for fn, _ in failed) or '(none)')
# R32 — assert the gate LEFT THE TREE where it found it (plus whatever it banked). `_write(baseline)`
# above restores the files render() manages; a carve/isolation can touch files it does not. At 0
# verified the tracked diff must be EMPTY, and any residue is a failed draft still spliced in — which
# the next run would silently gate on top of. Name the files; do not make the operator go looking.
_dirty = [l[3:] for l in _sh(['git', 'status', '--porcelain', '--', 'src/%s' % a.binary,
'config']).stdout.splitlines()]
if _dirty and not verified:
print(' !! TREE NOT CLEAN at 0 verified — residue from a failed draft/carve, NOT a result:')
for q in _dirty[:12]:
print(' %s' % q)
print(' recover with: git checkout -- src/%s config' % a.binary)
open(a.verified_out, 'w').write('\n'.join(verified) + '\n')
# failed_out stays NAMES-only (backward-compatible for existing consumers); the class goes to a sidecar
open(a.failed_out, 'w').write('\n'.join(fn for fn, _ in failed) + '\n')