Commit Graph

33 Commits

Author SHA1 Message Date
Drew T 11f79c1b0a docs(phase-32): T3 checkpoint — the 31 agents' FULL final reports saved to .run/P32/t3/reports/ (tools/agent_reports.py, NEW) + tools/transcript_dump.py (NEW, the dead-session reader); SETUP rows, playbook addendum-2, 🛑 block §3/§6 point at them 2026-09-05 10:11:43 -06:00
Drew T 9480c979a0 docs(phase-32): T3 CHECKPOINT — the launching coordinator overflowed after bank 9; 22 agent verdicts recovered (20 MATCH / 9 NEAR / 2 FAIL of 31), 11 swept Opus deliverables restored + re-verified, cookbook §500 harvest, playbook §S80 addendum-2, agent_drafts_restore.py
- verdict ledger .run/P32/t3/verdicts.jsonl rebuilt from the 31 T3 transcripts (agent_verdicts.py); every unbanked draft
  re-verified with rtu_match in its real TU: 10 MATCH awaiting the gate (main func_80015B6C 120 + func_8002FDE8 73;
  md_SC03_054 func_801EF6D8 604 + six jtbls; md_SC03_053 func_801EF734 44 + func_801EF7E4 72; md_MAIN_007
  func_800CF148/2BC/EEFC/EF94/068) + func_800CF3B0 leaf-exact behind the TU's void/3-arg decl; 9 NEAR at exact length
  (2/6/15/17/27/35/46/49/137), each with its class and inert-lever list
- R48 incident: one agent's `find .run/P32/t3/opus -maxdepth 1 -type f ! -name <mine> -exec mv {} _scratch/` swept 11
  sibling deliverables (two MATCHes among them); found in _scratch/, restored to the contract paths, byte-verified;
  tools/agent_drafts_restore.py (NEW: transcript replay) as the fallback; .gitignore allowlist for .run/P32/** so the
  drafts, ledger and census files are committed (R20)
- harvest (R16/R30): cookbook §500 (10 banked closers, 10 MATCH closers, 9 NEAR classes, two NEW mechanisms — the
  pinned-base-vs-pseudo-address alias basin and #line-equalised ASM_OPERANDS for cross_jump — and the wave-process
  defects); wave-playbook §S80 addendum-2 (per-function work dirs, JSON-only final message, the 20-agent cap, the
  recovery tools); accelerators P32 T3; decision-log P32 S82 (R31); SETUP tooling row (R21); cookbook-index
  regenerated; .run/P32/t3/BRIEF.md output contract amended for the 17 queued launches
- CURRENT_PHASE: T3 row IN PROGRESS, Log entry, 🛑 SESSION CHECKPOINT (census 44 stubs / 5,313 ins with every row's
  state and draft path, the 9-step resume order, the dead session's read-only T4 pre-read); harness task list rebuilt
- no src/ or config/ change in this commit; no fleet R22 has run since the 10 T3 banks — the resume order starts with one
2026-09-05 09:36:43 -06:00
Drew T 77df1092e2 docs(phase-31): post-close bank — accelerators.md S80 (five: loop tools must prove they iterated; the byte gate is a null oracle for 'is this C?'; a pointer-only callee's extern is unconstrained; banked-but-not-merged printed as success; Agent-tool drafters outlive the session) + wave-playbook S80 (parallel_gate's exit-2 guard and recovery route in the gate section; the one-agent-per-function shape + agent_verdicts addendum) 2026-09-04 22:36:55 -06:00
Drew T 46097c2339 feat(permuter_sweep): hand a wave's NEARs to the permuter, and correct §479 a second time
THE GAP: a drafting agent is briefed to STOP at a plateaued permuter-class
residual — right, since an agent grinding a register permutation burns tokens
for nothing — so every SCHEDULE-REORDER/DELAY-SLOT/REGALLOC-PERM residual lands
unattempted while the local permuter costs no tokens. In S77 the hand-off
happened only when I remembered.

THE CORRECTION THIS TOOL FORCED. §479 v2 claimed the predictor of a permuter win
was 'prior-attempt history: all 3 winners were drafts nobody had worked'.
Building the selector on that claim refuted it immediately: journal_notes
reports prior attempts for ALL EIGHT known runs, winners included (2, 3, 3).
What I had eyeballed was the DRAFT HEADER narrative, a different corpus — the
winners came from a recovery pile whose files carry no header journal. That is
provenance, not evidence.

So the tool selects on the two NECESSARY conditions only (small residual, a
match_one class the permuter can search), prints prior-attempt counts as
information, and puts the unvalidated filter behind --skip-ground, off by
default so it cannot silently discard good work (R39).

AND A BUG IN THE NEW TOOL, caught by cross-checking against known-true numbers:
wave_results globbed journals across EVERY session and did last-write-wins on a
bare function name, so an older wave's row won and carried its stale
draft_path — the sweep reported func_8002AC98 at closeness 73 and func_80015608
at 65 while both drafts measure 1 and 3. R48 inside a brand-new tool. Journals
are now read newest-last and rows are kept only when the draft lives under this
wave's directory. After the fix all seven cross-checkable residuals agree with
what the agents independently reported (9, 8, 7, 3, 3, 1, 1).

§479 now states the honest position: ~3 in 8 at <=4, no validated predictor, and
a note that a yield table is evidence while a story about why is a hypothesis
needing its own negative control before it goes in the cookbook.
2026-09-03 21:09:59 -06:00
Drew T ec258ff75a feat(recover_route): route a gate DROP to the tool that applies, and wire it into gate_main
gate_main printed ONE recovery chain for every dropped draft, and it was the
SELF chain (fix_arity_callers --any-proto + cast_self_callers) regardless of
what the clashing symbol actually was. Two of the three classes are not that
chain:

  CALLEE — §378 does not transfer; cast_self_callers reads the return type off
           the draft and cannot cast a callee, so --any-proto runs unprotected
           over every call site. S69 measured 60 decls no-protoed, binary RED.
  DATA   — neither tool in the printed chain touches a data extern at all.

Measured cost of the wrong route THIS session: func_8006252C was dropped on a
clash with itself; following the shape of the printed chain I reached for
scope_demote_drafts first, which aliased D_80078D08 through __asm__ and BROKE
the build. The real blocker was one --sync-decls away. Three tools, wrong
order, one destructive — because the report named a chain instead of a route.

A route is an ORDERED LADDER, not a prediction: for a DATA clash the choice
between adopting the TU's spelling and demoting to block scope depends on
whether the draft can live with the TU's type, which no classifier can know.
The byte gate remains the sole arbiter (G3/P9). Refusals come first (R43/R61a):
a verbatim draft and a NEAR are not declaration problems.

NEGATIVE CONTROL (R39): all 7 S77 drops whose winning tool was already known
route correctly — 2 SELF (cast_self_callers), 1 CALLEE (sync_tu_decls via a
definition header), 4 DATA — and the DATA ladder's order matches which rung
actually won in each case (sync for D_80072978, demote for D_80072960 and
D_80074818). Verbatim draft refused; real-C draft not refused.

Playbook §4b and SETUP updated in the same change.
2026-09-03 21:05:50 -06:00
Drew T f9f446449e feat(claude_wave_packs): wire neighbor_ref into every pack, and resolve its names to the source spelling
playbook §2b has called neighbor_ref the biggest measured cost lever in the
wave since S68 (~20x token swing) and documented it as a MANUAL per-card
command wired into nothing — so it ran for approximately zero cards. Packs now
carry an ALREADY-MATCHED NEIGHBOURS block, same additive never-fail contract as
the past-attempt notes. First run: 30/30 targets had a matched neighbour.

It also shipped with a defect that would have silently un-done it:
neighbor_ref reports the SYMBOL-TABLE name, and for an unnamed function that is
Ghidra's FUN_8003a0e4 — which appears nowhere in src/*.c, where the function is
func_8003A0E4. An agent sent to read FUN_8003a0e4 finds nothing and concludes
there is no neighbour. _src_name resolves against the destination TU's own text,
falls back to the address, and shows the symbol-table spelling in parentheses.
Measured: 150 of 150 neighbour names needed resolving; 0 primary names remain
Ghidra-style. Checked against known-true cases first (resolves FUN_8003a0e4,
leaves func_8003A0E4 alone, leaves an unknown name untouched).

R61(b): the pack was asserting a name true of the symbol table and false of the
world the agent works in.
2026-09-03 20:08:27 -06:00
Drew T e0229af908 docs: restore §462/§463, record S76 tooling in SETUP, add the gate-triage step to the playbook
Three gaps found by auditing instead of asserting.

§462 and §463 were MISSING from the cookbook although their commits are
ancestors of HEAD and added 37 and 34 lines. Same silent loss as §464, which
I caught only because I happened to re-check the three sections I had just
written. Both restored from their own commits; all of §460-§476 now verified
present one by one.

SETUP.md had no record of either new tool (R21). Added gate_main_parallel and
sync_tu_decls, plus the oracle corrections a reader needs in order to
re-judge older verdicts: the REORDER_TUS routing in match_one/rtu_match, the
draw_waves --main no-op, the verbatim-draft refusals at three points, and the
§179-C conversion guard.

The playbook had nothing on what to do when a gate banks far less than it
staged — which is exactly what happened this session. Added the triage step:
probe first (CC1-FAIL 16 / DIFF 18 / MATCH 6 on main's 40), sync declarations
for the plumbing class, hand self_decl_tu to cast_self_callers, and expect a
cascade because every bank changes the declaration environment for the drafts
that follow it.
2026-09-03 17:41:43 -06:00
Drew T c982efada3 docs(playbook): record the S76 draw and oracle fixes as procedure
The playbook IS the procedure, so the five instrument fixes have to land in
it or the next session repeats them: --main drawing zero main functions,
the ledger reporting an empty frontier, the reorder-island oracle
manufacturing a §188 wall, and verbatim-asm drafts refused at three points.

Each entry carries the check to run rather than the fix that was made — the
'main: N stub(s) reached the pool' line, the ledger NOTE, and the rule that a
draw disagreeing with corpus.stubs is the thing that is wrong.
2026-09-03 14:49:29 -06:00
Drew T eb0329b3a4 docs: the five agent-authored tool changes that shipped with a commit message and no docs
I answered Drew's yes/no honestly — NO — and this closes it. Every gap had the same shape: a tool
change that came from a SUBAGENT arrived as a report, I merged the code and wrote it up in the
commit message, and a commit message is not the knowledge base. The six changes I made myself were
documented inline; these five were not.

SETUP.md tooling ledger:
  * `ld_interleave` — the row still said "interleave linker inputs" and predated BOTH --order
    (S72, main's 7-piece island) and --pre (S74, the resident's leading-rodata header).
  * `harvest_verify` — the typedef strip-set is computed SCOPED (`above=fn`) now, and why.
  * `jtbl_rodata_pads` — a new row for the three S74 measurement corrections, each of which ACCUSES
    THE CARVE when it fires, plus why the trailing-.align one stayed latent (zero_gap self-corrects
    an undershoot when the next item is an anchor, and a C jump table has no anchor).
  * NEW row `jtbl_carve` — the `covered` / `covered-tpad` verdicts.
  * NEW row `jr_isolate_all` — `_region_emit_start` and the empty-closing-region skip.

Cookbook:
  * §440 — a carve piece binds to a SUBSEG, not a function, so §8b's "non-adjacent => ISOLATE" is
    over-strict: EXTEND the carve across still-stubbed material instead. Four byte-proven
    corollaries (migrated tables self-align by SPAN-RELATIVE offset; JTBL_PADS counts cc1 tables
    only so a mixed span's spec grows as siblings bank; the zero-word rule is invalid across a
    migrated boundary; a covered table at 4-mod-8 gains 4 bytes when it banks). Plus the resident's
    rodata->text->data->rodata->data layout and why it needed --pre.
  * §441 — three more instrument defects that each produced a confident, precise, WRONG verdict
    about a correct draft, with the habit they share: when a gate rejects a body you have
    byte-verified standalone, the first suspect is the gate.

Playbook: new step 2a-0 — the same-address lead is size-filtered now; read the `⚠ IGNORE` line, and
regenerate any pack built before S74 rather than trusting a bare address lead.
2026-09-02 19:53:56 -06:00
Drew T e9220d2d8a fix(pgate): a carve left asm/ stale, and the refusal that reported it named nothing
TWO DEFECTS, ONE INCIDENT. ov_SC03_105's own SUCCESSFUL gate committed an isolation's new TUs
(src/ov_SC03_105/ov_SC03_105_jr_801813BC.c, _jr_80181C84.c) whose `INCLUDE_ASM` lines name .s files
that do not exist until a re-extract. corpus.stubs then refused — correctly, "the tree and the
source disagree" — so the NEXT gate on that binary died before doing any work, and a matched body
(func_801818E8) sat unbankable behind it.

1. THE REASON NOW TRAVELS WITH THE REFUSAL. stubs_of() returned a bare None and the caller printed
   "corpus refused in worktree": true, and naming nothing. It took a hand-built worktree to see that
   corpus had said exactly what was wrong all along. It now returns the message and the result JSON
   carries it. Verified against a TRUE reproduction (delete one .s in a scratch worktree):
   verdict REFUSED + "1 stub(s) have NO .s on disk ... src/...:4214: asm/.../func_8017F018.s".

2. THE MERGE STEP REPAIRS WHAT IT BROKE. For every binary whose carve created a new source file,
   assert corpus.stubs is satisfiable in the MAIN tree; if not, `make extract BINARY=<b>` and
   re-assert; if it is STILL unreadable, say so loudly rather than leaving a tree no tool can read
   (R32/R43). This is the R22 corollary — a config change needs a make extract, not just a make
   check — firing inside a tool's own commit.

Repaired the live instance by hand first: rm -rf asm/ov_SC03_105 + extract + build -j + check ->
BYTE-IDENTICAL d305ff6d, corpus readable again, and func_801818E8 then banked (commit:3718).

Cookbook §436-D; wave-playbook §6 carries the hand-gating version of the same warning.
2026-09-02 19:10:57 -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 c43008a48b docs(playbook): seven stale spots — the copy-paste command was the worst
* Step-1's draw command still passed a superseded .run snapshot through the UN-AUDITED
  --exclude flag. Running it verbatim bypassed the freshness prerequisite built this
  session. Now --exclude-file config/wave_exclude.txt with a fresh --ledger.
* Both exclude populations were wrong: '96 jtbl functions build_carve refuses' is 16
  across 4 overlays (split_indicator derives it), and the seven .run/S6*_walls.txt
  ledgers are superseded by the WALL entries pinned in the canonical list.
* Entry count said 19; it is 26. Replaced with 'trust exclude_audit, never a number
  written here' — a count in prose goes stale the moment anything is added.
* Model routing still had a Sonnet band Drew abolished, and no mention that Fable is
  exhausted account-wide (three agents died on the limit in S73 at ~133k tokens each).
* Section 1c's census was pre-session: 25 of 59 main jtbl functions, 'every one now
  drawable', and SaveLoadRoutine as the flagship drawable example. It is 2 of 36, and
  those two are the §434 frame pair, excluded from draws.
* The gate step listed three gate_main verdicts; there are four, and the missing TABLE
  REJECT is the DOMINANT residual on main's switch functions (§433).
* Triage still named jr_isolate_all as the usual CARVE unblock; it does not yet produce
  an assemblable object, and §431 is the cheaper route.
2026-09-02 17:16:36 -06:00
Drew T 4f66709ade chore(exclude): promote the wave exclude list to config/wave_exclude.txt
There were NINE session-snapshot copies under .run/ and no way to tell which was
current — the accumulation smell behind the whole staleness problem. This is the one,
it is tracked, and it is regenerated rather than hand-edited.

.run/ is gitignored scratch, which is the wrong home for it: CARVE-BLOCKED entries are
derived and vanish when the subseg is split, but WALL entries are CURATED and cannot be
re-derived — that is precisely why the file needs to be tracked.
2026-09-02 14:39:09 -06:00
Drew T 983df054f2 feat(draw): audit the exclude list as a PREREQUISITE — a stale one is refused
An exclude list records what the TOOLING could not do, then gets treated as a property of
the FUNCTIONS. Nothing re-examined it, so every tool fix left behind a population that is
now tractable and still marked impossible — invisible, because the draw filters it out
before anything measures it.

MEASURED one day after .run/S71_exclude.txt was written: 88 of its 107 entries were
stale — 28 already banked, 14 linked PsyQ symbols that were never targets, and 46 whose
blocker had since been fixed. Those 46 are 12,750 instructions of open, drawable work
including main:SaveLoadRoutine (1,165), the largest function left in main.

* tools/exclude_audit.py (NEW) — classifies each entry by its CURRENT blocker
  (BANKED / LINKED / RE-PROBE / CARVE-BLOCKED / WALL), regenerates keeping only the
  still-valid classes, and --assert-fresh exits 3 on staleness.
* draw_waves --exclude-file — runs that audit and REFUSES to draw on a stale list, naming
  the counts and the regenerate command. --exclude-stale-ok still draws but prints what it
  ignores: skipping is possible, never silent. Also fixes the old --exclude parsing, which
  could not survive a '#' comment.
* .run/S72_exclude.txt — the regenerated list: 19 entries (16 CARVE-BLOCKED + 3 WALL),
  each carrying its reason, down from 107.

Verified in all three directions: stale refuses rc=1, fresh proceeds rc=0, override
proceeds and announces. The parser's own report-don't-drop design caught a bug I
introduced in it (comma-splitting before comment-stripping).
2026-09-02 14:38:39 -06:00
Drew T 8e8521da22 fix+docs: make every consumer aware of main's new TU layout (R36)
The split created two new TUs and a shared header; four consumers still described main's
game code as one file:

* tools/reconcile_slate.py — HARDCODED open('src/800.c'), so after the split it saw a
  THIRD of main's typedefs while reporting success (silently-narrowed scope, R32).
  Measured: 133 visible before the fix, 187 after, 0 lost. Now globs
  corpus.src_files('main') + src/800_shared.h, so a future split is already handled.
* docs/wave-playbook.md 1c — still said spans B/C/D were NOT drawable and that drawing
  one is an R45 violation. That is now false and would have STOPPED a future session
  from drawing the very targets this work unlocked.
* cookbook §426 — its 'the remaining spans need src/800.c split' paragraph now records
  that it was done the same session, and points at §431 for the method.
* config/dedup.us.yaml + src/shared/clearTbl40.h — both said dedup group I0 is
  instantiated 'at both sites in src/800.c'; both sites are above 0x80035270 and are now
  in src/800_c.c.

Byte-neutral: dedup.us.yaml parses, gate_main --assert-baseline BYTE-IDENTICAL.
SETUP.md gains a row describing the layout and the rule it implies: never hardcode
src/800.c, glob corpus.src_files('main').
2026-09-02 13:48:00 -06:00
Drew T 7bfc543d76 docs(playbook): 5b — gate the directory, never the verdict list
A wave verdict is a claim about what the AGENTS returned, not what is on disk.
claude_wave_draft.js has no filesystem access, so its .catch() reports NO-DRAFT for
any agent that dies — including one that already wrote a finished body.

S72: a Fable agent on main/func_80024448 spent 133k tokens / 17 tool calls / 10 min,
wrote its draft, then died on 'You've reached your Fable limit'. The workflow returned
NO-DRAFT with an empty draft_path; match_one on that same file returns
{status: match, closeness: 0, nins: 362}. A complete byte-perfect body reported as no
work at all.
2026-09-02 12:13:56 -06:00
Drew T e94bb20833 docs: bank the S72 main carve finding — cookbook §426/§427, playbook 1c, SETUP, decision log
§426 — main's switch functions were never a codegen wall: one .rodata carve had been
missing since Phase 7, so a drafted switch double-emitted its jump table (+28/+52/+76/+84
image growth, 238 symbols shifted, first mover jtbl_80072A4C every time). Includes the
derived name-address overlay that names a layout shift with no reference build, the span
table, and why spans B-D need src/800.c split at the original TU boundaries the spans reveal.

§427 — a hash is a correctness oracle with zero diagnostic content; preserve the red
artifact before anything rebuilds over it, attribute per byte, and negative-control the
localizer in both directions.

playbook 1c — which main jtbl functions are drawable (R45), and the gate note: main is not
in the parallel lane, read the BODY/PLUMBING/MIXED verdict before recording a main reject.
2026-09-02 12:05:01 -06:00
Drew T 6c904ebd0c docs: close the S71 documentation gaps - SETUP tools table, playbook steps, decision log, gate ledger
Audit found real gaps rather than assumed coverage:
* SETUP.md (R21) had NONE of the five tools written this session. Added a table for
  journal_notes / launch_check / gate_triage / restage_matching / weave_sweep, each with
  when you need it, plus the two gating rules now enforced in code (parallel_gate refuses
  main; gate_main refuses a no-op draft and counts banks from the source).
* wave-playbook: launch_check as step 4c (payloads go stale while gates run - 3 of 27
  wave-2 targets were already banked) and gate_triage as step 6b with the measured
  blocker census.
* decision-log (R31) held only the §406 pivot. Added the two strategic entries this
  session actually turned on: gating main with a tool documented as unable to gate it
  (false PASS, caught only by R22), and the drafting pool running dry while the lever
  was an exclude list nobody re-probed after a tool fix.
* CURRENT_PHASE: the per-gate ledger for all 14 cycles plus the carve/rebase/main gates.
* Two memories: gate-main-only-with-gate-main, reprobe-exclude-lists-after-tool-fixes.
2026-09-02 11:19:49 -06:00
Drew T 02e1b3a7e6 feat(waves): every pack now carries that function's own PAST-ATTEMPT history
tools/journal_notes.py mines the agent journals per (binary, fn) and appends a
PAST ATTEMPTS section to the pack; claude_wave_packs.py calls it automatically, so
it is the default rather than a step to remember. Idempotent, and R48-safe (a note
stamped with a different binary is never served — §238 homonyms).

Measured before adopting (S71 wave 1, 50 one-agent workflows over the 210-function
real frontier where every target had already refused an earlier wave):
  * 38/39 MATCH at closeness 0 (97.4%) vs S70's 124/131 (94.7%) on an EASIER pool
  * 29/39 agents cite a prior attempt as what they used
  * 4/39 banked by RECOVERING a body that already matched, from a path a note named
  * 11/39 matched on the first compile

The two costs it removes are re-testing a measured-inert lever (§406 lists twelve,
§407 fifteen, §410 four — each paid for by an agent and never seen again) and
re-deriving a body that already exists on disk.

Also: jr_isolate_all places file-local `static` definitions with the region that uses
them instead of refusing the whole file. A `static inline` helper (§82.1) has no
address by construction, which is not a defect; the R32 guard was refusing these and
blocking the isolate on 4 of the 6 overlays whose CARVE-REFUSED functions it is the
named remedy for. Two regions using one static is still a hard refusal (duplicating a
used static is a byte change, R43).

docs: cookbook §411, wave-playbook step 3b, accelerators entry.
2026-09-02 01:11:52 -06:00
Drew T f33c455747 docs(playbook): fix the packs invocation — the old form hid SYS.md from every drafting agent
claude_wave_packs writes out_dir/SYS.md + out_dir/packs/<fn>.md, so out_dir is the
WAVE dir. The playbook documented  plus an mv to undo the
resulting packs/packs nesting — which also put SYS.md at <wave>/packs/SYS.md while
claude_wave_draft.js tells every agent to read <wave>/SYS.md.

Net effect: the laws file did not exist where any agent looked, in every wave run
this way, and the brief silently degraded to the pack alone. Two S70 agents said so
verbatim; the rest never noticed. Passing the wave dir fixes it and removes the mv.
2026-09-01 22:55:19 -06:00
Drew T 9d01e382cc docs: §397 + playbook §2a-3 — re-run the twin scan after every exemplar bank
Measured the expensive way. A reach-6 cluster showed open-open, so seed_ref
correctly reported 'no banked twin' for all six. I cracked the exemplar (203k
tokens, five new levers) and then drafted four siblings at ~60k each — including
one that had already burned 257k plateauing at permuter-class NEAR.

They were EXACT clones. The agents' own diffs said so: 'label-stripped .s diff vs
the twin is EMPTY', 'an EXACT clone (asm diff = labels only)'. The moment the
exemplar banked, seed_ref returned it as a banked twin for every sibling, and
family_remap + the §378 chain banks them for ~0 tokens.

The law: a bank CHANGES THE TWIN GRAPH. The twin oracle answers 'is there a
BANKED body like this?', so its verdict for every sibling is stale the instant the
exemplar lands. crack-wave-sweep-map-regen applied one level down — the family map
is not the only stale artifact, and the twin oracle is the one the cards read.

Also: never draft two members of one cluster in parallel; if either cracks the
other is free.
2026-09-01 14:37:18 -06:00
Drew T f8e522ea0d docs(cookbook): §378b — the four decl-blocker variants, and the two places §378 does NOT apply
Correcting my own guidance from earlier today. §378 gave the self-caller chain;
three more variants appeared within hours and two of them BREAK the chain.

Variant 3 (NEW, byte-proven ov_SC04_018/func_8017F35C, banked): conflicting
RETURN type on a decl that is ALREADY no-proto, where the symbol is
ADDRESS-TAKEN rather than called. --any-proto has nothing to relax and
cast_self_callers has no call site to cast; --sync-decls ALONE fixes it, and is
safe precisely because an address-taken site has no arguments to convert.

Variant 4 (REFUTATION of what I wrote in the playbook this morning): "run the
same chain on the callee the diagnostic names" is wrong at scale. Applied to
func_8012AD44 in ov_SC07_000 it no-protoed 60 caller decls and the binary went
RED (265b24bb vs 9dbe4241); reverted via journal. The self case is safe because
step 2 casts the call sites so the decl change cannot alter argument conversion;
for a callee, cast_self_callers correctly refuses and the decl change runs
unprotected. It banked main/func_80021D38 only because that callee had ONE decl,
not sixty.

Rule added: never --any-proto a symbol whose call sites you are not also casting;
count the sites first. The chain is a DECISION TABLE, not a sequence to run
blindly.
2026-09-01 13:34:21 -06:00
Drew T f11bf13b4f feat(seed_ref): the CONTAINED tier + docs for the twin ladder (§390/§391, accelerator #18)
tools/seed_ref.py gains --contained/--contained-control: an open stub that is a
banked body plus or minus WHOLE BLOCKS — the class edit distance ranks badly.
Branch-offset masking was required (unmasked offsets veto exactly the target
pairs) and a min-side-25 floor (89% of raw hits were prologue/epilogue vacuity).
Ranks by (substitutions+regions, cover), not by d. Controls: planted-deletion
positive 60/60, random-pair base rate 0/397, R32 population 346/346, and a
post-refactor --near regression reproducing the stored slice exactly.

Banked on first use: ov_SC01_077/func_80184D50 = banked ov_SC03_007/func_8018283C
minus its trailing `&= 0x7FFFFFFF;` — MATCH, closeness 0, 98/98.

* cookbook §390: minimum distance is not minimum work (rank by effort; a deletion
  is free, a substitution is thought), the lookalike filter r = d/min(nins) ~ 0.3
  (17 of 30 "cousins" were boilerplate coincidence), and the three fleet-wide
  nulls that close the scanner question — 0 new / 9 / 2. Spend integration
  effort, not scanner effort.
* cookbook §391: a byte-aligned struct copies in FOUR instructions (lwl/lwr/swl/
  swr), a word-aligned one in TWO. Never invent an aggregate type to make a draft
  compile — an invented word-aligned Blk8 lost exactly 8 ins across two copies and
  read as a believable "near, closeness 70" codegen residual.
* accelerators #18: a claim derived from BYTES is not a claim verified by a
  COMPILER. Every similarity/correctness claim must name the tier it reached
  (stream containment / compiled standalone / whole-binary gate / clean fleet);
  a report that says "verified" without one invites the strongest reading.
  Non-reproduction is a finding — say so rather than assuming your own setup.
* playbook §2a-2: the twin ladder (exact -> RELOC-ONLY -> CONTAINED -> cousin ->
  cold), take the cheapest tier available, widen only when the tier above is empty.
* SETUP inventory row; generic-decomp-package: rank by work, and stop building
  scanners once the well is dry.
2026-09-01 13:06:27 -06:00
Drew T 881dd5a37c docs: the NEAR-TWIN BAND — §389, accelerator #17, SETUP, playbook §2a, generic-decomp-package
The exact-hash twin tier found 22 of 352 reachable open stubs (6%). The
edit-distance band added by `seed_ref --near` finds 75 of 352 (21%) — 3.4x — on a
corpus we believed fully mined. 31 of the new rows were PURE reloc-only twins of
already-banked bodies; 8 banked the same day at ~0 agent tokens, one 94-ins
exemplar serving five open copies.

* cookbook §389: the h_norm hole (norm_stream drops its pending lui-hi on an
  intervening R-type, so indexed-global reloc twins hash differently and vanish
  from seed_ref/twin_sweep/dedup/family-maps at once). Do NOT fix h_norm — every
  stored calibration keys on it; the near tier reads through it.
* accelerators #17: the generalisable law. A similarity hash built for DEDUP
  under-matches by design, which is correct for dedup and silently lossy as a
  FRONTIER join — the two questions want opposite error directions, and the
  frontier failure looks exactly like "this function is unique".
* generic-decomp-package §2b: build the near band at the same time as the exact
  tier, with the three verifications. It pays from the first bank for a new
  project, where we paid a session to recover the debt.
* SETUP inventory row + playbook §2a (run it before believing any "no twin"
  verdict; never send a RELOC-ONLY row to a drafting agent).
2026-09-01 12:20:59 -06:00
Drew T 9fc27960e9 feat(integration): teach the rest of the toolkit about §378 (self-caller cast)
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.
2026-09-01 11:03:06 -06:00
Drew T 1dd15eda32 feat(triage): the triage ladder — built, wired, and acceptance-green
tools/triage_ladder.py — the zero-token pre-agent pass, split PRE (target-side:
BANKED/WALL-332/PARKED, no build) from POST (residual_rules_b, needs a draft).
--escalate refuses a walled or banked target; --acceptance is the R39/R32 harness.
Refuses on a non-quiescent tree: a merging gate makes the stub oracle wrong in
both directions (measured, ov_SC01_004:func_8017EB30).

Acceptance, on the whole corpus: false-skip 0/1367 open stubs, recall 426/426
matched, wall tier fires on exactly the 10 enumerated walls (0 extra, 0 missing).
The first wall control asked for evidence that CANNOT exist — it scanned banked
functions' .s, which splat never writes — and printed '0 scanned / 0 tripped',
indistinguishable from a pass. The R32 empty-denominator assertion caught it on
its first run; replaced with a two-sided sweep over all open stubs.

tools/cast_self_callers.py — the §378 lever + --sync-decls for the narrow-param
case C89 forbids no-proto from reaching (§378a).

Wiring: wave_args drops walled/parked targets at draw time via pre_classify (one
implementation, R33); escalate_fable.js refuses any target without triage:'DRAFT'.

Tool fixes found by measurement:
* fix_arity_callers was blind to main entirely (globbed src/main/main*.c; main is
  src/*.c) — reported success over an empty file set through three gates. Now
  refuses when --binary selects no files.
* parallel_gate records each worker's 'failed by class' line (was truncated out of
  the 200-char tail); gater_lane retries in-tree ONLY on the diagnostic-free
  blind-worktree signature — S69 ran 22 serial retries against real cc1 errors.

docs: cookbook §376/§377/§378 (index 1033), SETUP.md, wave-playbook §4b.
2026-08-31 23:53:50 -06:00
Drew T 5c317dbe5f docs: wire the S68 tools into SETUP.md (R21) and the playbook (when to use them)
A tool nobody knows about is invisible work. Audit found neighbor_ref (built an
hour ago), residual_rules, lane_inflight and r22_verify in NEITHER doc, and
wall_sweep in the playbook but not the inventory.

SETUP.md gains a tooling-inventory row for all five with what each is FOR.

wave-playbook gains §2b: run neighbor_ref for EVERY card, placed right after the
seed_ref step because it answers the weaker and far more common question ('which
matched function should this agent READ?') that seed_ref structurally cannot. It
carries the measurement that justifies it -- a ~20x token swing on that single
variable -- and the failure it prevents: func_8017BEBC's card said 'no banked twin'
while a matched 755-instruction near-twin sat 3,700 lines up IN ITS OWN FILE.

Also states the two honest limits: an opt-level mismatch is PENALISED not merely
ranked low (§116 -- an -O2 example misleads an -O0 target), and a neighbour is a
worked example to READ, never a body to copy (§168 law 1, cousin-remap 0/26).
2026-08-31 22:25:43 -06:00
Drew T d8b7fb4e49 docs(playbook): §1b — the §332 walls are now ENUMERATED, wire the sweep into the draw
tools/wall_sweep.py --emit-exclude feeds draw_waves --exclude directly. 10
functions / 1,027 instructions over 1,378 open-stub .s files, against §332's
'6 fleet-wide' with two named.

Recorded what it caught immediately: main/func_8005D734 was already escalated to
Fable at closeness 8 when the sweep listed it, and its site is exactly the residual
that agent described -- stopped. Filtering the live queue dropped two more before
they were drafted (func_8005D9C4 133 ins, func_8005F450 159 ins).

The §188 epilogue half is still NOT built and is now named as such rather than left
implied: its detector exists inside oracle_reorder.py and has never been run as a
sweep.
2026-08-31 20:02:56 -06:00
Drew T 0e50fbc84f docs(playbook): §1b — the walls ledger is always incomplete, and each gap costs an agent run
A wall nobody has met yet is invisible to the draw filter, so new ones are found by
PAYING an agent to hit one. Twice in S68 on main: func_8005E228 (a full run, then
banked the §265 verbatim-asm way) and func_8005F0C8 (289k tokens to reach closeness
36 with the residual confirmed as §188's epilogue by oracle_reorder.py).

Neither is a model failure. An agent handed a wall returns a NEAR with an
unexplainable tail, which looks exactly like a hard function -- and an escalation
cannot beat the toolchain, so escalating one is guaranteed waste.

The fix is named rather than left as folklore: run the §188 epilogue-shape detector
over every open stub AT DRAW TIME. It already exists inside oracle_reorder.py and
has never been run as a sweep. Until then, treat 'NEAR with an epilogue-shaped
tail' as a walls candidate and check it with the oracle BEFORE escalating.
2026-08-31 19:54:36 -06:00
Drew T 93ea53217e docs(playbook): a carve writes THREE outputs — merge all three, splice overlays.mk per block, gate jtbl with --r22 2026-08-31 15:17:27 -06:00
Drew T 5bcb322283 docs: gating is fully parallel — no serial lane; jtbl unlocked via isolate_asm; launch detached with setsid 2026-08-31 14:55:39 -06:00
Drew T 022d50325a docs(playbook): the pgrep bracket is NOT enough when launch and wait share a shell
Measured a SECOND time in S67, and the first fix was incomplete. A waiter using the bracketed
pattern still matched itself and spun 1h35m, because the same shell command had LAUNCHED the job —
so its own command line carried the unbracketed 'gate_stage.py --binary ov_SC07_007' from the nohup
half. The regex gate_[s]tage.py does not match the literal bracketed text, but it happily matches
the plain text sitting earlier on the same line.

Rule is now: launch and wait in SEPARATE shell invocations, or better, wait on a completion MARKER
the job writes to its own log rather than on process liveness.
2026-08-31 13:51:48 -06:00
Drew T 00812fc62f docs(p31 s67): wave-playbook (the CURRENT pipeline) + seed_ref fix + harvest §333-§338
THE DOC GAP, and it cost tokens this session. `docs/automation-runbook.md` was titled "the
autonomous campaign, as it actually runs" while documenting the RETIRED OpenRouter/ox-alpha system
whose lanes are all deliberately DEAD. The current Claude-wave pipeline existed only as two dense
tooling-inventory rows in SETUP.md — reference, not procedure. Three of this session's costliest
mistakes were procedural and a playbook prevents each:
  * hand-typed a refill target -> invented func_80184F60 (2nd instruction of a matched function), 58k
  * hand-rolled a serial gate loop when parallel_gate existed -> ~1h for what took 103s
  * re-derived a function banked verbatim in ~20 overlays -> 102k

NEW docs/wave-playbook.md — start to finish, each guard paired with the MEASUREMENT that produced it
(that pairing is the part a generic decomp guide cannot have, and the seed of the future template).
automation-runbook.md retitled HISTORICAL with a pointer; SETUP.md §6.9 links the playbook.

NEW tools/seed_ref.py — the cross-TU banked twin, joined on corpus signature hashes (no atlas knn,
~2s fleet-wide), wired into t5_cards.py. FLEET: 87 open stubs have a banked twin; 41 of them sit in
twin_sweep's refusal ledger, invisible to BOTH tools at once. Documents twin_sweep's two holes:
load_sigs covers 141/213 binaries (main, resident, all md_MAIN_* absent), and one curated symbol
name silently disables an entire binary via a bare `except Exception: pass`.
Schema note: seed_ref's binary/fn are the EXEMPLAR's, because api_agent greps src/{binary} for {fn};
naming them after the target would send every agent grepping for itself — caught pre-ship.

HARVEST §333-§338 from the s67o2_1/pool_1 waves:
 §333 frame size is set by DECLARED aggregates, not used ones — an unreferenced trailing local is a
      dial (3 instances; one worth 30 of 32 residual rows)
 §334 a reload spill slot rounds to BIGGEST_ALIGNMENT for align AND size: one 4-byte pseudo grew a
      frame by 16 (82->53)
 §335 `extern u16 A[]` at a variable subscript allocates ~8B/access of dead stack temps that inflate
      the frame with ZERO extra instructions — invisible in a body diff (141->20)
 §336 the §5a barrier goes at the BOTTOM of the twin; find_cross_jump walks BACKWARD
 §337 the CC1-ONLY blocker class: blocker_probe's static oracle says "none" and cc1 still fails
 §338 _sltiu_bounds misreads a non-switch sltiu as a bounds check, over-spanning the table

gate_wave.py now STREAMS both lanes (R55) — it captured output and printed at the end, leaving a
zero-byte log indistinguishable from a hang.
2026-08-31 13:48:56 -06:00