1.9 KiB
§105 — A gate's revert must survive an EXCEPTION, not just a failure (Phase 29 T53, jtbl_family_bank)
jtbl_family_bank.bank() was carefully revert-on-fail: every return path restored the overlay, and
the stage loop even caught a stage that raises while producing a candidate. Nothing covered the
stages never being reached. A wrong exemplar made remap_hseq raise after the carve had rewritten
config/splat.<ov>.yaml + overlays.mk and jr_isolate had created a region file — the exception
propagated out, the revert never ran, and the tree was left with:
- a rewritten carve config (recoverable by
git checkout), and - an untracked region file, which
git checkout -- src/does not remove.
In a 132-member sweep that residue rides silently into the next member's build. Found by being bitten by it while testing something else.
The fix is a wrapper, not a bigger try inside: snapshot the region set, call the real body,
and on ANY exception revert and return a per-member exception status. Loud (it appears in the
tally), non-fatal (the sweep continues), and the tree is provably clean afterwards.
def bank(...):
keep = region_files(to_ov)
try:
return _bank(...)
except Exception as e:
revert(to_ov, keep_regions=keep)
return "exception", repr(e)[:140]
Negative-control proven: the same crashing invocation now reports {'exception': 2} and leaves
git status -- config/ src/ at 0.
The law: "revert on failure" is not the same property as "revert on every exit". Enumerate the exits — success, gate-fail, refusal, and the throw — and put the restore where all four pass through it. Same family as §97 (the gate's own tree hygiene), and the reason it matters more in a sweep than in a one-off: a one-off's residue is visible in the next
git status; a sweep's residue is consumed by the next iteration first.