The lever existed but nothing downstream applied it. Proof it mattered: a wave
agent this session diagnosed its own blocker as "§378 THE SELF-CALLER CAST, a
TU-level fix (cast_self_callers.py) that requires editing src/, which I'm not
permitted to touch" — the knowledge propagated, the automation did not.
* recover_integration.py: NEW "self-cast" stage (tier=binary), so the driver can
run the whole chain as --stages arity,self-cast. The docstring states WHY the
order is not arbitrary: self-cast answers the error that "arity" CREATES.
* residual_rules_b.py: both decl-conflict tiers now prescribe the full chain
instead of "route to integration / budget for banking", and
NOCOMPILE-UNDECLARED-FIXED now says outright NOT to gate the autodecl arm (it
is a second conflicting declaration in the real TU).
* wave-playbook §4b: replaced the stale two-step recipe with the three-step
chain, the one-driver form, the callee variant, and the MANDATORY
--undo-journal.
* SETUP.md: full inventory row (R21) — it had zero mentions.
Not wired, deliberately: gate_stage's ladder rewrites DRAFTS via _xform, while
this edits the TU; a src-side edit inside the automatic gate needs
revert-on-failure, which recover_integration already owns.
Still open: a draft_prechecks rule to catch the self-decl conflict statically,
before a build is spent. The new stage's plumbing is verified (CLI + candidate
selection); its functional end-to-end run is NOT — gate12 held the tree.
The lock I added earlier today guards a real hazard (the driver mutates the shared tree and is not
parallel-safe), but I scoped it to the whole tool instead of the mutating path. --probe-only execs
blocker_probe, which compiles in its own scratch dir and touches nothing — excluding it buys no
safety and costs a free diagnostic.
Measured cost: a t7b drafting agent (func_801832E0) tried the probe TWICE, was refused both times by
a concurrent sweep holding the lock, and submitted with its blocker unconfirmed — exactly the $0
diagnostic the pack tells agents to run first.
Control: with the lock held, --probe-only now returns rc 0 and its verdict table; the mutating path
still returns rc 1 REFUSED.
Three properties compose into tree corruption under concurrency:
(a) assert_write_set measures a GLOBAL git status, so a concurrent run's writes read as THIS
run's blast-radius violation and abort it;
(b) an abort does NOT restore the stage edits already on disk;
(c) gate_stage's commit is a deliberately broad 'git add -u src/' — and it must be, since
propagation touches many overlays and a narrower filename glob once DROPPED four R22-verified
banks — so a concurrent --commit sweeps the aborted run's half-applied edits into its commit.
Measured today: xargs -P 4 over 33 binaries put 696 broken lines of ov_MAIN_012 into md_MAIN_026's
+1 bank commit; check-all went 212/213 and the wave bank was blocked behind it (R59).
Narrowing the gate's git add was the WRONG fix (it would restore defect (c)'s predecessor). Instead
the driver enforces its own contract: flock on .run/recover/.driver.lock, refuse loudly (R43).
Control: with the lock held -> rc 1 REFUSED; lock free -> rc 0 and the probe runs normally.
os.execv'd tools/blocker_probe.py with --drafts <run_dir>/drafts while that directory was still
created further down, so every non---draft-dir probe died with FileNotFoundError. Only --draft-dir
worked, because stage_drafts() had already populated the dir. Staging now happens first.
Control: the --draft-dir path returns the same verdict as before the move (ov_SC06_029
func_80185214 -> DIFF 52/52 ins, identical to the pre-edit run). --funcs now works: 10 backlog
candidates classified in one pass (2 real-TU MATCH, 3 conflicting-types, 2 too-few-arguments,
1 parse error).
Worth recording (R40): my own probe loop grepped for result rows and swallowed the traceback, so the
crash read as 'no blockers found' — a silently narrowed scope in the harness, not the tool.
recover_integration's macro-externs stage rewrote a draft's callee extern to the FLEET macro's
signature and then gated only the rewrite. func_ADDR names are per-address, not per-function, so
another overlay's 'extern void func_8017C338(void)' replaced this overlay's correct 4-arg decl and
manufactured the CC1-FAIL it reported as the draft's failure. The untouched draft banks
byte-identical (ov_SC03_012:func_8017BEBC, 246 ins, banked in the previous commit).
reconcile_and_gate(draft_rewrite=) now gates raw (pass 1a) then rewrites only what raw refused
(pass 1b), records the winning variant per fn, and re-gates that variant in pass 2.
harvest_verify.classify_fail kept the 'note:' half of a benign warning pair and labelled a built
draft CC1-FAIL with it; notes now drop with their warnings. Negative-controlled over 5 diagnostic
shapes — only warning+note-only changed (to the honest no-diagnostic label). R39/R57/R32.
Cookbook 920 -> 921 sections, index green.
- tools/plumbing_groups.py: derives the honest still-open pool from the classified
ledgers (R38) — '1,217 PLUMBING' collapsed to 237 (SELF 109 / CALLEE 48 / OTHER
48 / DATA 32)
- recover_integration: PER-GROUP ISOLATION (git-checkout binary TUs between groups
— one TU-stage edit was poisoning every other group's whole-binary gate with a
phantom shared error; per-group banked_from_source capture) + new stages
'macro-externs' (§121 draft-tier, via family_sweep.macro_def_sig_map, R33) and
'tu-scope' (§103 STU binary-tier, the sweep-only lever)
- the probe (ov_SC03_107): raw 0/14 -> root-caused (poisoning + stale seed
symbols; rtu_match MATCHes them — blind to reloc names, R34) -> symfix-first
-> 9/14 BANKED (64%)
- sweep finding (Law 3): the no-draft majority (ov_SC02_037 44/44, most of
ov_MAIN_012) had verdicts from transient sweep remaps never persisted — family-
lane fuel, not recovery fuel; the stored-draft class is consumed
- cookbook §173 (symfix-first / per-group isolation / verdicts-without-drafts);
index 518 green; R22 clean fleet 213/213; phase total 17 banked @ 0 agent tokens
- THE FREE TEST (cookbook §66): reverted func_801778A8's bank to its INCLUDE_ASM stub (stub state
rebuilds BYTE-IDENTICAL 7ca772be — a faithful revert proves itself; needs `make extract` first,
the R22 corollary) and re-banked it THROUGH recover_integration.py --commit --r22.
pass1 1/1 -> exact restore -> pass2 1/1 -> commit commit:0928 -> R22 140/140 -> report.json.
Bank confirmed from SOURCE (stub gone), never the report (§55b trap 4). EQUIVALENCE: git diff vs
the pre-revert commit = ONE blank line (mine) -> the driver reproduced SESSION-16's state exactly.
- DEFECT 1 (SAFETY, found by reading before firing): PROPAGATION is a fleet-tier write
(dedup_propagate --auto-from -> src/shared/engine_core.h + up to 138 overlay .c) that was both
UNDECLARED and the DEFAULT, so --max-tier binary still permitted the widest write in the toolchain.
assert_write_set cannot catch it (it runs before the gate; under --commit git status is clean).
FIXED up front: propagate now requires --max-tier fleet AND --r22, and is REFUSED after a
demacroize stage (those banks are x1 by construction; --auto-from would re-macroize and undo them).
Both refusals negative-control-tested, exit 1. The "standing hazard" is now a refusal.
- DEFECT 2 (METRIC): gate_stage scraped the fleet % via a progress.py label that no longer exists ->
fp=None -> 50 gate commits recorded "fleet None%". Now reads FLEET instr-weighted (legacy fallback
+ loud stderr warning if neither matches); parses 79.6.
- STALE DIGEST (R14): docs/progress.fleet.md at HEAD disagreed with HEAD's own source by 45 in the
dedup-shared column — generated during the §65g local_type trial whose edits were then reverted.
Regenerated (reproduced identically in-gate + standalone); headline %s unaffected.
- cookbook §66/§66a/§66b distilled in-session (R30); SETUP.md gains the missing recover_integration
row (R21 debt). tools-health OK: corpus 0/0, cdecl green, audit-binaries 140 citizens, lint OK,
dedup-check 1879/0. Fleet unchanged 79.6% instr / 67.7% distinct / 88.86% fn-count.
Extended, not replaced: it already owned exact snapshot/restore, split-aware grouping and the two-pass
gate-all -> restore -> re-stage-winners protocol. A new driver would be a 7th snapshot impl (R33).
- --draft-dir (repeatable): consume a WAVE dir instead of the backlog (unreliable closeness,
overlay-specific drafts). Strict ^func_[0-9A-Fa-f]{8}\.c$ filter -- the wave dirs carry scratch
(_b.c, try2.c, scratch/) and run_gate globs *.c blindly.
- --run-id: all scratch under .run/recover/<id>/, and run-local verified_out/failed_out passed into
run_gate -- closes §55b trap 4 (the accumulating .run/harvest_verified.txt phantom bank), which the
cookbook still lists as "still armed".
- --stages with the new demacroize stage; --max-tier; --r22; --probe-only; --report.
- TIERS ENFORCED not documented: stages declare T0/T1/T2, the driver MEASURES the write set
(git status before/after) and ABORTS if a stage writes outside its blast radius (§61d). A fleet-tier
stage is refused without --max-tier fleet AND --r22. Both refusals negative-control-tested.
- stub_map now derives from corpus.stubs (R33, coverage-asserting) instead of a private regex that
could silently return a short map; banked_from_source() is the sole bank oracle for reporting.
- End-to-end on the remaining 22: excluded 26 already-banked by name, demacroize ran on 12, banked 0,
restored exactly (src/ clean), 14/14 prior banks intact. A clean negative -- it does not manufacture
banks. Those 22 need normalize_self_decls / draft type-uniquify wired next.
--auto silently skipped the 263-stub ov_SC01_077_after cohort: the drift-check hardcoded
the main asm subdir and gate_stage was never passed src/asm/src_file. Now stub_map() reads
each stub asm subdir from its INCLUDE_ASM line, the drift-check uses the per-fn subdir, and
reconcile_and_gate groups targets by split file, gating each via run_gate(src=,asm=,src_file=).
fix_arity_callers/dedup_propagate already split-aware; harvest_verify self-filters per split.
The integration-recovery tool for leaf-MATCH-but-whole-binary-gate-rejected fns ("declaration/TU
plumbing" — the dominant residual gate_stage's canon/cast/sig_unify pipeline doesn't reach).
- tools/recover_integration.py (NEW): batch recovery — gather leaf-MATCH candidates (--auto from
the backlog, drift-checked R14; or --funcs/--from-file) → no-proto their conflicting caller decls
→ gate_stage (byte-gate + log). 2-PASS snapshot/restore: pass 1 finds the bankable set, pass 2
re-banks ONLY winners from the clean snapshot (so non-banks are never corrupted).
- tools/fix_arity_callers.py: extended with --binary — scan+rewrite the overlay's OWN inline caller
decls (src/<bin>/<bin>*.c), not just engine_core.h. That was THE gap: a conflicting caller extern
is often inline in the overlay src (e.g. func_8016E778's `extern void f(void)` vs def `f(int)`),
which fix_arity_callers never saw -> the fn stayed unbanked.
- VALIDATED: banked 13 leaf-MATCH fns (func_8014F74C/801542A4/8015BE94/8015F380/80160F00/801653B8/
80166244/8016E778/801732C4/8017331C/80173374/80174554/801745AC), CLEAN-verified together
(ov_SC01_077 d19c9580). The banks themselves are reverted here (they re-bank via the tool and
will land ×134 once propagation-recovery lands — cleaner than committing ×1).
- R14 lesson (clean-verify caught it, R22): fix_arity_callers --revert is LOSSY for --any-proto
(()->(void), not back to the original args) -> corrupted non-banks; fixed with the 2-pass snapshot.
- REMAINING T6 (×134 propagation-recovery, 3 diagnosed blockers): (1) dedup_propagate find_site
misses INDENTED inline defs (Phase-15 class); (2) overlay-local-type lift; (3) auto-reconcile the
straggler's conflicting caller externs (the flagship func_80132784 / ov_SC02_005 class, done by
hand — needs automating). See CURRENT_PHASE.