`--sync-decls` copied the draft's parameter list verbatim into the TU. A draft
names types that are not in scope where the declaration sits, and both forms
of that broke the COMMITTED baseline build in one apply:
src/800.c:2631 extern void func_80015760(Obj_80015760 *obj, s32 *ot);
-> the type is draft-local; the TU has never heard of it
src/800c3.c:866 s32 func_8005E3AC(Ctx *s, s32 size);
-> `Ctx' is typedef'd at line 941, 75 lines BELOW the decl
src/800c3.c:866: parse error before `*'
src/800.c:2631: parse error before `*'
Caught by gate_main's BASELINE RED check with no draft substituted, so the
failure was attributed to the plumbing and not to seven innocent drafts.
THE FALLBACK FOLLOWS THE TOOL'S OWN DOCTRINE. Once the call sites are cast, the
declaration emits no code; it only has to be COMPATIBLE with the definition and
PARSE. `<ret> fn();` satisfies both without naming a type, and C89 6.5.4.3
makes it compatible with a prototyped definition exactly when no parameter is
affected by the default argument promotions. So the draft's own spelling is
still preferred — it is the byte-proven behaviour and it keeps the declaration
informative — and the no-proto form is used ONLY where that spelling cannot
parse at that line. Where it cannot parse AND a narrow parameter forbids
no-proto, the tool refuses loudly and names the type (R43).
NEGATIVE CONTROL (R39) over all 40 main recovery drafts: 68 edits before and
after, 65 byte-identical. The three that changed are exactly the declarations
naming an out-of-scope type — Obj_80015760, Ctx, and Slot54/Rec14 — and no
already-correct declaration is churned.
BASELINE PROOF: with all 14 plumbing edits applied and NO draft substituted,
main builds 143dbb89f34491258bbc27810d0a12ec8b43a8dd — byte-identical. The
casts move zero bytes, as the §20 fold predicts.
`return func_X(a0, a1);` has the exact shape of a forward declaration —
leading identifier, name, parenthesised argument list, `;` — so every
permissive "<type> <fn>(...);" regex in this tool read that CALL as a
DECLARATION. One misclassification, three consumers, two opposite failures:
* `is_declaration` -> `cast_sites` SKIPPED the call site, leaving the
caller's bytes exposed to the synced (narrowed) prototype.
* `sync_decls` -> REWROTE the whole statement into a declaration,
silently deleting the function's `return`.
* `DEF_RE` -> read the same line as "the definition itself", and
in `draft_signature` could have handed back ret="return".
Witnessed on a dry run before anything touched src/:
src/800.c:713
- return func_80013154(a0, a1, a2);
+ s32 func_80013154(s16 x, s16 y, s16 step);
One shared `_kw_prefixed()` guard, called from all three sites (R33 — the
guard lives in one place, never duplicated into three regexes).
BLAST RADIUS: 524 `return func_X(...);` lines across 482 files fleet-wide.
AUDIT: no past journal records a keyword-prefixed `before`, so no committed
source was corrupted by this.
NEGATIVE CONTROL (R39), old vs new over all 40 main recovery drafts:
68 edits each, 67 byte-identical, zero false positives. The single
difference is the defect itself — the corrupting declaration-rewrite
replaced by the correct cast:
- return func_80013154(a0, a1, a2);
+ return ((s32 (*)())func_80013154)(a0, a1, a2);
Both tools restored with `text.replace(after, before, 1)` -- the FIRST occurrence.
--any-proto (and sync-decls) collapse DISTINCT declarations of one function to the
SAME `after` text, so occurrence N received entry N's `before` in JOURNAL order,
not file order: the originals land on the wrong occurrences and the file is
corrupted while the tool prints full success.
Byte-witnessed twice in S70:
* fix_arity_callers: "restored 382, kept 0, missing 0" left the FLEET-SHARED
src/shared/engine_core.h with 97 insertions / 97 deletions (func_8012A828
rotated between three declaration sites).
* cast_self_callers: "reverted 10 edit(s)" left src/800.c with the two decls of
func_80031988 swapped.
Both were caught only by `git diff` AFTER the success line (R40: the tool's own
report is not evidence).
The occurrence->original mapping is NOT recoverable from either journal format, so
the undo now REFUSES (rc=2) when one (file, after) group maps back to differing
`before` texts, naming the file and telling the caller to git checkout it (R43:
refuse, never mishandle). Journals additionally record per-file sha_before, and a
clean undo hash-verifies its own result and reports HASH-MISMATCH loudly. Old
list-form journals are still read.
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.