mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-27 05:56:00 -04:00
fix(phase-30 S1e): the distinct-code "regression" was a STALE DIGEST — alias lever ungated
The S38 checkpoint gated the phase's best lever ("do NOT scale the alias lever") on
distinct-code falling 89.3 -> 89.2. It never fell.
PROOF (each commit's metric recomputed from its OWN committed tree, 0 unresolved):
commit:1426 TRUE : instr 12394533 distinct 5022306 (77895 uniq)
commit:1426 COMMITTED: instr 12402412 distinct 5029324 (78025 uniq) <- stale
HEAD TRUE == COMMITTED: instr 12405402 distinct 5025082 (77952 uniq)
=> true delta 843->HEAD: instr +10869, distinct +2776 ins / +57 uniq. ALL ROSE.
The 843 digest was generated from a working tree still holding work REVERTED before the
commit landed (+7,879 ins / +130 uniq overstated) and never regenerated, so the next
HONEST digest read as a fall. => THE ALIAS LEVER IS UNGATED (scale it, §61 small batches).
Both recorded leads were wrong (R14): progress.py:423's SIG regex feeds fn-count ONLY
(neither weighted metric sees a C identifier — both derive matched = sig - corpus.stubs),
and "the harvest reverted functions to INCLUDE_ASM" died on one grep (483 removed, 0 added).
The 3-grep proof: identical sigs + unchanged tools/ + zero +INCLUDE_ASM => HEAD's stub set
is a strict subset => both numerators are FORBIDDEN to fall.
THREE INSTRUMENT DEFECTS, all one class (a bare except around a fail-CLOSED oracle):
- progress.py stub_addrs wrapped corpus.stubs in `except Exception: return set()`. An empty
stub set means "could not answer", not "no stubs", so matched = sig - stubs credited EVERY
function. Byte-witnessed: instr 100.00% / distinct 100.00% in a tree with no asm/. Now
propagates.
- cast_call_sites.tu_for + reconcile_tu.tu_for had the identical swallow, falling back to the
default <ov>.c instead of the jr/-O0 split TU — silently reinstating the exact bug
cast_call_sites' own docstring says it exists to fix. A wrong-TU reconcile fails the gate,
and this phase's base rate is ~24k PLUMBING vs 4,917 DIFF, so it presents as a codegen wall.
Now propagate CorpusError; ValueError fallback for curated names preserved; derived-TU path
re-verified (a _jr_ split stub resolves correctly, both tools agree).
NEW GATE (R34 — the byte-gate is a null oracle for DOCUMENTS; check-all stays 140/140 over a
stale digest forever): tools/audit_digest.py + `make audit-digest`, wired into tools-health
after report. Recomputes the three headline metrics from the current tree and fails if the
committed digest disagrees. Compares INTEGERS, not percentages — the +7,879-instruction
staleness printed as "94.4%" on both sides. Negative-control-proven against the stale 843
digest (fails, exit 1) and green on HEAD.
Verified: make report exit 0 (dedup-check 1910 validated / 0 failed, C1 coverage
241216/241216); audit-digest OK; cookbook-index OK (398 sections); metrics unchanged by the
fix (94.40% / 89.18%). No src/ or config/ edits — no bytes touched, nothing banked.
cookbook §140 · decision-log 2026-08-04 · SETUP.md inventory (R21) · R14/R32/R34/R35.
This commit is contained in:
@@ -140,7 +140,7 @@ CC1_SMOKE_FLAGS := -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-lin
|
||||
BINUTILS_WARN_MAJOR := 2
|
||||
BINUTILS_WARN_MINOR := 38
|
||||
|
||||
.PHONY: help check-env extract build check expected clean report sig-refresh sig-overlays sig-resident build-all check-all audit-corpus audit-cdecl audit-binaries audit-text-sources tools-health
|
||||
.PHONY: help check-env extract build check expected clean report sig-refresh sig-overlays sig-resident build-all check-all audit-corpus audit-cdecl audit-binaries audit-text-sources audit-digest tools-health
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
help:
|
||||
@@ -193,6 +193,14 @@ audit-cdecl:
|
||||
audit-binaries:
|
||||
$(VENV_PY) tools/audit_binaries.py
|
||||
|
||||
# P30 S1e: the committed fleet digest must still describe the CURRENT tree. A digest generated from
|
||||
# a working tree that later changed (work reverted before the commit landed) is BYTE-INVISIBLE —
|
||||
# check-all stays 140/140 over it — and the next honest regeneration then reads as a REGRESSION that
|
||||
# never happened. That cost a session-opening false alarm and gated the best-performing lever on a
|
||||
# phantom. R34: the byte-gate is a null oracle for documents, so this is a second one that disagrees.
|
||||
audit-digest:
|
||||
$(VENV_PY) tools/audit_digest.py
|
||||
|
||||
# P30 S28: every tracked C source must be TEXT. A raw NUL inside a char literal (`'<NUL>'` instead
|
||||
# of `'\0'`) COMPILES — the fleet stayed byte-identical — but grep treats the file as BINARY and
|
||||
# reports nothing, silently, so the file vanishes from every grep-based audit and hand-search. The
|
||||
@@ -221,6 +229,9 @@ tools-health:
|
||||
$(MAKE) --no-print-directory audit-binaries
|
||||
$(MAKE) --no-print-directory audit-text-sources
|
||||
$(MAKE) --no-print-directory report BINARY=main
|
||||
# AFTER report (which regenerates the digest), so this asserts the freshly-written digest agrees
|
||||
# with the tree — and, on a tree whose digest was committed stale, says so instead of staying green.
|
||||
$(MAKE) --no-print-directory audit-digest
|
||||
# The cookbook index is DERIVED (R33) and self-asserts its coverage (R32). Stale = agents can't
|
||||
# find documented idioms and re-derive them at full token cost (measured, P30 wave 1).
|
||||
$(VENV_PY) tools/cookbook_index.py --check
|
||||
|
||||
+2
-1
@@ -448,7 +448,7 @@ Modern cpp preprocesses → **vintage cc1** compiles to asm → **maspsx** emula
|
||||
- **`make report` is genuinely fail-closed** — any of its gates failing exits non-zero (verified by a negative control: the same broken gate exits 0 under the old `-c`, non-zero under `-ec`).
|
||||
- **`make check-all` / `extract-all` assert COVERAGE (`pass == N`), not the absence of a failure marker** — the old `fail == 0` form was a *vacuous pass* on an empty pipeline. `check-all`'s `pass=$(grep -c …)` carries `|| true` (grep -c exits 1 on zero matches, which `-e` would otherwise treat as fatal — it would fail check-all exactly when nothing failed).
|
||||
- **`make check-env` opts OUT** (`set +e` at the top of its recipe) — its contract is accumulate-every-failure-and-report, which `-e` would truncate at the first missing tool. It is the **only** intended opt-out; add `set +e` to a recipe only with the same justification.
|
||||
- **`make tools-health`** (new) = regenerate the byte-derived sigs (`sig-overlays` + `sig-resident`) then run `audit-corpus` + `audit-cdecl` + `report`, fail-closed — the deliberate pre-matching ritual the roadmap's standing invariant names. Deliberately NOT a prerequisite of `report`/`build` (audit-cdecl cross-compiles every C declaration through real gcc, ~minutes). `audit-cdecl` ≈ several minutes; `audit-corpus` ≈ 7 s.
|
||||
- **`make tools-health`** (new) = regenerate the byte-derived sigs (`sig-overlays` + `sig-resident`) then run `audit-corpus` + `audit-cdecl` + `audit-binaries` + `report` + `audit-digest`, fail-closed — the deliberate pre-matching ritual the roadmap's standing invariant names. Deliberately NOT a prerequisite of `report`/`build` (audit-cdecl cross-compiles every C declaration through real gcc, ~minutes). `audit-cdecl` ≈ several minutes; `audit-corpus` ≈ 7 s.
|
||||
- **`make sig-resident`** (Phase-27 T10) signs the resident flat blob with `sig_image` (byte-derived) so `make audit-corpus`'s second, independent boundary oracle (R34) now covers the **resident** — probed clean (0 phantom/truncated). `sig-overlays` derives its payload list from `config/overlays.mk` (not a `0.4.dec` glob, which dropped the 4 SC07 index-1 overlays). **main** stays a boundary blind spot — `sig_image` can't sign the PS-X EXE yet (header offset + interleaved islands + one text range); scoped + deferred in `docs/second-oracle.md`. `progress.py --fleet` now reports a separate **MAIN game-code weighted** line (provisional, from a LINKED-excluding Ghidra sig) — the metrics-contract "main in the denominators", honestly un-folded.
|
||||
|
||||
### §6.4 asm-differ + baseline discipline
|
||||
@@ -721,6 +721,7 @@ Every script under `tools/` (plus the two report make-targets), grouped by purpo
|
||||
| | `tools/serve_local.py` | **Serve the fine-tuned model on the GPU** (base+LoRA via Unsloth, `.venv-train`, OpenAI endpoint) — the in-repo replacement for LM Studio. Run: `LD_LIBRARY_PATH=$(ls -d .venv-train/lib/python3.12/site-packages/nvidia/*/lib \| tr '\n' :) .venv-train/bin/python tools/serve_local.py --adapter models/bfm-match-7b-v3 --name bfm-match-7b-v3 --port 1234`. (Prebuilt `llama-cpp-python` CUDA wheels SIGILL on this no-AVX-512 CPU; the Unsloth/torch path is reliable, no build.) |
|
||||
| | `tools/export_pairs.py` / `format_finetune.py` / `train_lora.py` / `eval_lora.py` | The corpus→LoRA pipeline (`.venv-train`): mine (asm↔C) pairs incl. the `engine_core.h` **macro bodies** + `engine_types.h` structs (corpus-v3) → Qwen chat-template + compile-filter → Unsloth QLoRA (3080 Ti) → held-out gate-true eval. Datasets/weights gitignored (`datasets/`, `models/`, `.venv-train/`). |
|
||||
| **The derived oracles** (Phase 26-A tooling audit; R33 before R32 — *the best outcome is a deleted scanner, not a fixed regex*) | `tools/corpus.py` + **`make audit-corpus`** | THE corpus oracle. Derives from the FILESYSTEM (which `.c` files make a binary; the `INCLUDE_ASM` line is self-describing — its first argument *is* the asm subdir) and from the PROVEN INVARIANT (`matched = sig − stubs`, never re-parsed from C). Killed ~10 hand-maintained layout models. `audit-corpus` is a **second oracle that can disagree**: it cross-checks splat's boundaries against `sig_image`'s independent ones (0 phantom + 0 truncated since A4; was 193 unmatchable slices). |
|
||||
| | `tools/audit_digest.py` + **`make audit-digest`** | **(P30 S1e, cookbook §140)** The **scoreboard** oracle: recomputes the three headline metrics from the CURRENT tree and fails if the committed `docs/progress.fleet.md` disagrees. Wired into `tools-health` AFTER `report`. Exists because a digest generated from a working tree that later changed (work reverted before the commit landed) is **byte-invisible** — `check-all` stays 140/140 over it forever (R34: the byte-gate is a null oracle for DOCUMENTS) — and the next honest regeneration then reads as a REGRESSION that never happened. That is exactly what the `commit:1426` digest did: overstated **+7,879 ins / +130 unique fns**, which parked the phase's best lever on a phantom for a session. Compares **integers, not the printed percentages** (the staleness rendered as "94.4%" on both sides). Negative-control-proven against that stale digest. Same task hardened `progress.py stub_addrs`, which wrapped the fail-closed `corpus.stubs` in a bare `except` → empty stub set → `matched = sig − stubs` credited EVERY function: byte-witnessed reporting **instr 100.00% / distinct 100.00%** in a tree with no `asm/`. The identical swallow was fixed in `cast_call_sites.tu_for` + `reconcile_tu.tu_for`, where it silently reconciled drafts against the default `<ov>.c` instead of the jr/-O0 split TU — the very bug `cast_call_sites`' docstring exists to fix. |
|
||||
| | `tools/cdecl.py` + **`make audit-cdecl`** | **THE C-declaration oracle (cookbook §51g).** ONE recursive-descent parser of C's **declarator grammar**, replacing fifteen tools' private regex models — models that disagreed with each other and were, all fifteen, blind to fn-ptr/jump-table decls (`extern void (*D_X[])(void);`), sized arrays (`[4]`), and multi-declarators (where the *whole line* was dropped). Total by construction, not by shape enumeration. **Two statement paths, because the inputs differ:** `tu_statements()` derives a TU's file scope from **`cpp`** (a decl inside a `DEFINE_func_*` macro body declares nothing until invoked — §8c; 54 ms/TU), and `split_statements()` is a **span-preserving** raw split for drafts (which get rewritten). API: `parse` / `scope` / `tu_scope` / `Declarator{name,kind,type,params,pnames,is_proto,is_definition}`. Verified: **2,952,246 depth-0 statements → 2,731,521 declarators, 0 parser defects**; **50,405 distinct declarations round-tripped through the real cross-gcc, 0 rejected**; residue adjudicated NOT-C *by gcc*, not by opinion. **Phase-27 T4 — the canonical draft-typedef strip:** `typedef_names(tu_path)` (the names a TU declares as typedefs, robust `tu_statements`-based so a coverage gap can't crash the byte-gate) + `strip_provided_typedefs(draft, provided)` (drop a draft's self-contained typedefs the target already supplies, splitting multi-typedef lines and covering scalar AND struct typedefs). Replaced **six** copied scalar-name regexes with complementary holes: `harvest_verify` now strips per-TU (unblocks the 39 struct-typedef drafts `_TD` dropped) and **surfaces cc1 stderr** so a `redefinition`/`conflicting types` failure reports as **PLUMBING**, not a byte mismatch (`.run/harvest_failed.classified.txt`); `masked_diff.strip_scalar_typedefs()` (used by `match_one`/`p16_permute`) fixes the multi-typedef-line skip that discarded 42 masked-MATCH drafts over whitespace (`func_8015C030` → `MATCH (23 ins)` unedited). `canon_sig_reconcile`/`eval_lora`/`format_finetune` keep their own copies for now (migrate per-bank, byte-gated — the audit-prescribed cadence). |
|
||||
| | Phase 26-A tool-hygiene close (A9d–A10) | **DELETED** (R33, dead Phase-17 chain): `tools/census_conflict_callees.py` + `tools/derive_canonical_sigs.py` — `reconcile_tu`/`cdecl` answer their question from the build. **`overlay_src_split.py`**: `scan_construct` force_decl latch fixed (no longer swallows a def sharing a line with leading externs) + `hidden_definitions()` R32 coverage oracle wired into `selftest`. **`jr_isolate_all.py` `jr_inventory`**: `banked` DERIVED FROM THE IMAGE (`family_remap.reloc_targets` owns-a-carve) not a gitignored roster (R33) + curated-name via `addr_of` + 1:1 carve-ownership assert. **`family_remap.reloc_targets`**: optional `data=` param (read the image once, pass to N calls). **`backlog.py`**: `BACKLOG_NO_RENDER` env so parallel `gate_stage` workers skip the render race (append is atomic). `reconcile_tu` confirmed live on BOTH banking paths (`gate_stage` + `jtbl_family_bank.recover`→`bank_exemplar`). |
|
||||
|
||||
|
||||
+18
-6
@@ -2,7 +2,7 @@
|
||||
|
||||
> **Generated by `tools/cookbook_index.py` — do not hand-edit** (R33). Regenerate after adding a cookbook section.
|
||||
>
|
||||
> `docs/matching-cookbook.md` is ~716 KB / 393 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses.
|
||||
> `docs/matching-cookbook.md` is ~716 KB / 398 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses.
|
||||
|
||||
**How to use:** name what you SEE in the diff (a stolen delay slot, an extra `la`, a swapped register pair, a `conflicting types` error), find that symptom below, read those sections first. If nothing fits, THEN grind — and add a section when you win.
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
- **§3-The** — attribution primitive (use this before calling anything a scheduling residual) <sub>L6050</sub>
|
||||
- **§3-The** — scheduling rules (refining §135-2 and §135-4) <sub>L8902</sub>
|
||||
|
||||
### register allocation & pins (32)
|
||||
### register allocation & pins (33)
|
||||
|
||||
- **§10** — Closing the regalloc/scheduling hard tail by hand (LZSS, Phase 7 session F — the full close) <sub>L835</sub>
|
||||
- **Residual** — A — commutative `|`/`&`/`+` result lands in the wrong source-operand register <sub>L856</sub>
|
||||
@@ -92,6 +92,7 @@
|
||||
- **§86** — Pinned-exemplar templatability is a PER-FAMILY property, not a per-member rate; and the §42e pin guard is now over-conservative (Phase 29 SESSION-20) <sub>L6582</sub>
|
||||
- **§3-Two** — further notes worth keeping <sub>L8241</sub>
|
||||
- **§137** — REGALLOC-PERM is a TWO-COMPILE ARITHMETIC PROBLEM, not a permuter job <sub>L9334</sub>
|
||||
- **§3-The** — same swallow, twice more, in the integration spine <sub>L9699</sub>
|
||||
|
||||
### CSE / redundancy / rematerialization (2)
|
||||
|
||||
@@ -342,7 +343,7 @@
|
||||
- **§136c** — SIBLING-FIRST is a DERIVATION shortcut, not just a conflict fix (the fastest route in a family wave) <sub>L9059</sub>
|
||||
- **§138** — The propagation lanes: a gate refusal is a DECLARATION, and which lever you owe depends on blast radius <sub>L9405</sub>
|
||||
|
||||
### integration / TU plumbing (35)
|
||||
### integration / TU plumbing (36)
|
||||
|
||||
- **§8c** — Splitting a TU means rebuilding its DECLARATION ENVIRONMENT, not moving text (Phase 26 session 6) <sub>L437</sub>
|
||||
- **§8d** — Templating a body INTO a TU must not CHANGE its declaration environment — demote the carried data externs (Phase 26 session 8, byte-proven on `func_8015AE2C` ×133) <sub>L483</sub>
|
||||
@@ -379,8 +380,9 @@
|
||||
- **§3-The** — integration idioms (these decide whether a byte-correct draft BANKS) <sub>L8797</sub>
|
||||
- **§3-The** — declaration surface (integration, not codegen) <sub>L8931</sub>
|
||||
- **Reconciling** — a gate-refused draft: which way you edit depends on WHERE the TU's decl is <sub>L9555</sub>
|
||||
- **§3-The** — same swallow, twice more, in the integration spine <sub>L9699</sub>
|
||||
|
||||
### build graph, splat & the harness (96)
|
||||
### build graph, splat & the harness (97)
|
||||
|
||||
- **§4** — Flag/toolchain gotchas <sub>L190</sub>
|
||||
- **Build** — mechanism — per-file opt override (splat resegmentation) <sub>L288</sub>
|
||||
@@ -478,8 +480,9 @@
|
||||
- **§134** — again, in a second tool — and the waiter rule corrected <sub>L9516</sub>
|
||||
- **Reconciling** — a gate-refused draft: which way you edit depends on WHERE the TU's decl is <sub>L9555</sub>
|
||||
- **§139** — A GATE THAT GREPS FOR VERDICTS MUST ASSERT 1:1 ACCOUNTING; and a `--src` filter must not survive a carve (P30 S38, wave 6: 10 of 16 drafts vanished) <sub>L9584</sub>
|
||||
- **§140** — A METRIC IS NOT A MEASUREMENT UNTIL IT IS REPRODUCIBLE FROM THE COMMITTED TREE (P30 S1e: a phantom regression that gated the session's best lever) <sub>L9650</sub>
|
||||
|
||||
### process, measurement & doctrine (53)
|
||||
### process, measurement & doctrine (54)
|
||||
|
||||
- **§8e** — The jtbl ALIGNMENT LAW + the pad-spec filter — multi-table .rodata spans (Phase 29, byte-proven; `.run/probe_jtbl/verdict.md`) <sub>L530</sub>
|
||||
- **§3-The** — mechanism: game-code dedup is SOURCE-LEVEL, not an object swap (R-D1, the key lesson) <sub>L926</sub>
|
||||
@@ -534,8 +537,9 @@
|
||||
- **§136h** — CORRECTION: the zero-crack pool does NOT "refill with cheap work" (my error, byte-measured) <sub>L9230</sub>
|
||||
- **§136j** — The failure MIX flips with function size (measured across four bands, one session) <sub>L9292</sub>
|
||||
- **Rank** — the lane by measured concentration, not by class count <sub>L9471</sub>
|
||||
- **§140** — A METRIC IS NOT A MEASUREMENT UNTIL IT IS REPRODUCIBLE FROM THE COMMITTED TREE (P30 S1e: a phantom regression that gated the session's best lever) <sub>L9650</sub>
|
||||
|
||||
### (unbucketed — title matched no symptom vocabulary) (112)
|
||||
### (unbucketed — title matched no symptom vocabulary) (115)
|
||||
|
||||
- **§3-How** — to use this <sub>L30</sub>
|
||||
- **§1** — Idiom catalog (asm pattern → C that produces it) <sub>L39</sub>
|
||||
@@ -649,6 +653,9 @@
|
||||
- **STEP** — 0 of sibling-first: grep `src/` for a distinctive LITERAL from the `.s` <sub>L9536</sub>
|
||||
- **§3-The** — generalisation — three corollaries worth more than the bug <sub>L9616</sub>
|
||||
- **§3-And** — the inverse-lookup trap, same session <sub>L9633</sub>
|
||||
- **§3-The** — three-line proof (do this before diagnosing any metric movement) <sub>L9670</sub>
|
||||
- **§3-The** — two instrument defects it exposed <sub>L9679</sub>
|
||||
- **§3-Two** — wrong mechanisms I chased first, and why they were wrong <sub>L9710</sub>
|
||||
|
||||
|
||||
## All sections, in order
|
||||
@@ -1046,3 +1053,8 @@
|
||||
- **§139** — A GATE THAT GREPS FOR VERDICTS MUST ASSERT 1:1 ACCOUNTING; and a `--src` filter must not survive a carve (P30 S38, wave 6: 10 of 16 drafts vanished) <sub>L9584</sub>
|
||||
- **§3-The** — generalisation — three corollaries worth more than the bug <sub>L9616</sub>
|
||||
- **§3-And** — the inverse-lookup trap, same session <sub>L9633</sub>
|
||||
- **§140** — A METRIC IS NOT A MEASUREMENT UNTIL IT IS REPRODUCIBLE FROM THE COMMITTED TREE (P30 S1e: a phantom regression that gated the session's best lever) <sub>L9650</sub>
|
||||
- **§3-The** — three-line proof (do this before diagnosing any metric movement) <sub>L9670</sub>
|
||||
- **§3-The** — two instrument defects it exposed <sub>L9679</sub>
|
||||
- **§3-The** — same swallow, twice more, in the integration spine <sub>L9699</sub>
|
||||
- **§3-Two** — wrong mechanisms I chased first, and why they were wrong <sub>L9710</sub>
|
||||
|
||||
@@ -2033,3 +2033,64 @@ no releases, and it requires a separate per-game repo to produce a playable resu
|
||||
reference, a risk as a dependency.
|
||||
|
||||
**Verdict: PARKED for Gen3. Do not evaluate again before Gen2 exit.**
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-04 (P30 S1e) — "distinct-code FELL" was a stale digest, not a regression; the alias lever is UNGATED
|
||||
|
||||
**Context + belief.** S38 closed with the def-side asm-label alias cracking a 208-conflict class
|
||||
138/138 — the phase's best-performing lever. Its checkpoint then gated it in bold: *"distinct-code
|
||||
FELL 89.3 → 89.2 — UNEXPLAINED. Do NOT scale the alias lever until it is resolved. The BYTES are
|
||||
proven (R22); the ACCOUNTING is not."* The recorded lead was `progress.py:423`'s `SIG` regex booking
|
||||
`void aF80146A6C(…)` under the alias name — with the honest caveat, written at the time, that a pure
|
||||
naming artifact would move fn-count and distinct-code *together*, and these had diverged.
|
||||
|
||||
**What failed.** Two mechanisms, both mine, both asserted before being derived (R14):
|
||||
1. *The recorded lead.* Real blindness — but it feeds `classify()`, which computes **fn-count only**.
|
||||
Neither weighted metric ever sees a C identifier; they derive from `matched = sig − corpus.stubs`.
|
||||
2. *"The harvest reverted functions to INCLUDE_ASM."* Attractive because that is **byte-neutral**
|
||||
(INCLUDE_ASM pastes the original asm), so R22 would stay 140/140 across genuine coverage loss —
|
||||
the R34 blind spot. Refuted by one grep: **483 stub lines removed, 0 added.**
|
||||
|
||||
**The pivot.** Stop hypothesizing; prove the arithmetic. Identical sigs (both denominators unchanged)
|
||||
+ unchanged `tools/` + zero `INCLUDE_ASM` additions ⇒ HEAD's stub set is a strict subset of the
|
||||
prior commit's ⇒ HEAD's matched set is a superset ⇒ **both numerators are forbidden to fall.** A
|
||||
reported fall is therefore a statement about the *digest*, not the tree.
|
||||
|
||||
**The byte/measurement-grounded why.** Reconstructing each commit's stub set from its own committed
|
||||
tree (0 unresolved symbols):
|
||||
|
||||
| | instr | distinct | unique fns |
|
||||
|---|---|---|---|
|
||||
| `commit:1426` **true** | 12,394,533 | 5,022,306 | 77,895 |
|
||||
| `commit:1426` *as committed* | 12,402,412 | 5,029,324 | **78,025** |
|
||||
| `HEAD` true **= committed** | 12,405,402 | 5,025,082 | 77,952 |
|
||||
|
||||
True delta: **instr +10,869, distinct +2,776 ins / +57 unique fns — everything rose.** The
|
||||
`commit:1426` digest was **committed stale** (generated from a working tree still holding work that was
|
||||
reverted before the commit landed; overstated +7,879 ins / +130 unique fns, never regenerated). The
|
||||
next honest digest was lower than the stale one, so the metric *appeared* to fall.
|
||||
|
||||
**Consequences (what changed).**
|
||||
- **The alias lever is UNGATED** — the blocker was a phantom. It is the phase's cheapest large lever
|
||||
and should be scaled (S4 onward), subject only to the §61 small-batch discipline the two real R22
|
||||
failures taught.
|
||||
- `progress.py stub_addrs` no longer swallows `corpus.stubs`' refusal. The old bare `except` turned a
|
||||
fail-closed oracle into a guess: byte-witnessed reporting **instr 100.00% / distinct 100.00%** in a
|
||||
tree with no `asm/`.
|
||||
- **`make audit-digest` is new** (in `tools-health`, after `report`): recomputes the three headline
|
||||
metrics from the current tree and fails if the committed digest disagrees. Compares **integers, not
|
||||
percentages** — the +7,879-instruction staleness printed as "94.4%" both before and after.
|
||||
- The same swallow was found twice more in the **integration spine** (`cast_call_sites.tu_for`,
|
||||
`reconcile_tu.tu_for`), where it silently reconciled drafts against the default `<ov>.c` instead of
|
||||
the jr/-O0 split TU — the exact bug `cast_call_sites`' own docstring says it exists to fix. Both now
|
||||
propagate. Given the phase's ~24k PLUMBING vs 4,917 DIFF base rate, that class presents as a
|
||||
codegen wall.
|
||||
|
||||
**Hindsight — the better path.** The three greps (denominators / `tools/` diff / `+INCLUDE_ASM`
|
||||
count) cost under a minute and settle the question *before* any hypothesis is formed. The general
|
||||
form, now cookbook §140: **a committed number is a claim about a tree; if it cannot be recomputed
|
||||
from that tree it is not evidence, and it must never gate a lever.** The deeper repeat is that this
|
||||
is the *fourth* consecutive phase where a "wall" resolved to our own instruments — and this time the
|
||||
instrument was the scoreboard itself, which is the one nobody thought to audit because the byte-gate
|
||||
is green over it by construction (R34).
|
||||
|
||||
@@ -9644,3 +9644,83 @@ family up BY.
|
||||
Symptom lines for the index: **"a gate reports fewer verdicts than it had drafts"** · **"a tool
|
||||
reports NO FAMILY for a head another tool just enumerated"** · **"a gate result got worse after a
|
||||
revert"** (→ re-extract, then re-measure).
|
||||
|
||||
---
|
||||
|
||||
## §140 — A METRIC IS NOT A MEASUREMENT UNTIL IT IS REPRODUCIBLE FROM THE COMMITTED TREE (P30 S1e: a phantom regression that gated the session's best lever)
|
||||
|
||||
**The alarm.** S38's checkpoint recorded, in bold: *"distinct-code FELL 89.3 → 89.2 across the last
|
||||
commit — UNEXPLAINED. Do NOT scale the alias lever until it is resolved. The BYTES are proven (R22);
|
||||
the ACCOUNTING is not."* The def-side asm-label alias had just cracked a 208-conflict class 138/138
|
||||
— the best-performing lever of the phase — and it was parked on that one line.
|
||||
|
||||
**The regression never happened.** True values, recomputed from each commit's own tree:
|
||||
|
||||
| | instr | distinct | unique fns |
|
||||
|---|---|---|---|
|
||||
| `commit:1426` **true** | 12,394,533 | 5,022,306 | 77,895 |
|
||||
| `commit:1426` *as committed* | 12,402,412 | 5,029,324 | **78,025** |
|
||||
| `HEAD` true **= committed** | 12,405,402 | 5,025,082 | 77,952 |
|
||||
|
||||
The real delta over that span is **instr +10,869, distinct +2,776 ins / +57 unique fns — everything
|
||||
rose.** The `843` digest was **committed stale**: generated from a working tree that still held work
|
||||
REVERTED before the commit landed (overstating by +7,879 ins / +130 unique fns), and never
|
||||
regenerated. The next honest digest was lower than the stale one, so the metric *appeared* to fall.
|
||||
|
||||
### The three-line proof (do this before diagnosing any metric movement)
|
||||
|
||||
The weighted metrics are `matched = sig − corpus.stubs`. So if, between two commits, (a) the sigs are
|
||||
identical (both **denominators** unchanged is sufficient evidence), (b) `tools/` is unchanged, and
|
||||
(c) `git diff A B -- src/ | grep -c '^+.*INCLUDE_ASM('` is **0** — then HEAD's stub set is a strict
|
||||
subset of A's, HEAD's matched set is a superset, and **both numerators are mathematically forbidden
|
||||
to fall.** A reported fall is then a statement about the *digest*, not the tree. Three greps settle
|
||||
it before a single hypothesis is formed; I burned two wrong mechanisms first (see below).
|
||||
|
||||
### The two instrument defects it exposed
|
||||
|
||||
**(1) `progress.py stub_addrs` swallowed the oracle's refusal.** It wrapped `corpus.stubs` in
|
||||
`except Exception: return set()` — and an empty stub set does not mean "no stubs", it means "the
|
||||
oracle could not answer", after which `matched = sig − stubs` credits **every** function as banked.
|
||||
Byte-witnessed: running the metric in a tree with no `asm/` made `corpus.stubs` raise its correct,
|
||||
coverage-asserted `CorpusError` for all 140 binaries, and the tool reported **instr 100.00% /
|
||||
distinct-code 100.00%** — a complete decomp, out of a swallowed error. `corpus.stubs` is
|
||||
*deliberately* fail-closed ("it refuses to answer rather than guess 'banked'"); a bare `except`
|
||||
around a fail-closed oracle reinstates precisely the guess it refuses to make. **Fixed: propagates.**
|
||||
|
||||
**(2) Nothing ever re-checked a committed digest.** R34, again: the whole-binary byte-gate is a
|
||||
perfect CORRECTNESS oracle and a **null oracle for documents** — a stale digest is byte-irrelevant,
|
||||
so `check-all` stays 140/140 across it forever, and `audit-corpus`/`audit-binaries` assert things
|
||||
about the CODE. **Fixed: `make audit-digest`** (`tools/audit_digest.py`, wired into `tools-health`
|
||||
after `report`) recomputes the three headline metrics from the current tree and fails if the
|
||||
committed digest disagrees. Negative-control-proven against the known-stale `843` digest. Note it
|
||||
compares **integers, not the printed percentages**: the +7,879-instruction staleness rendered as
|
||||
"94.4%" both before and after, so a percentage comparison would have seen nothing.
|
||||
|
||||
### The same swallow, twice more, in the integration spine
|
||||
|
||||
`cast_call_sites.tu_for` and `reconcile_tu.tu_for` had the identical `except Exception: pass` around
|
||||
`corpus.stubs`, falling back to the default `src/<ov>/<ov>.c`. `cast_call_sites`' own docstring, three
|
||||
lines above, says the function exists *because* "the recovery passes were reconciling against a
|
||||
DIFFERENT TRANSLATION UNIT than the one that would compile the code, and the heavy Phase-26 cores
|
||||
live in exactly those jr files" — so the swallow silently reinstated the bug the function was written
|
||||
to fix. A wrong-TU reconcile fails the gate, and this phase's base rate is **~24k PLUMBING vs 4,917
|
||||
DIFF**, so it would present as a codegen wall. Both now propagate `CorpusError` while keeping the
|
||||
`ValueError` fallback for curated (non-`func_ADDR`) names.
|
||||
|
||||
### Two wrong mechanisms I chased first, and why they were wrong
|
||||
|
||||
- **The recorded lead — `progress.py:423`'s `SIG` regex** (it books `void aF80146A6C(…)` under the
|
||||
alias name). Real blindness, but it feeds `classify()`, which computes **fn-count only**. Neither
|
||||
weighted metric ever sees a C identifier. *A lead that names a function must be checked against
|
||||
which metric that function actually feeds.*
|
||||
- **"The harvest reverted functions to INCLUDE_ASM"** — plausible because reverting to `INCLUDE_ASM`
|
||||
is **byte-neutral** (it pastes the original asm), so R22 would stay green over real coverage loss.
|
||||
Refuted in one grep: 483 stub lines removed, **0 added**.
|
||||
|
||||
**Symptom lines for the index:** **"a progress metric fell but the byte-gate is green"** · **"two
|
||||
metrics moved in opposite directions"** · **"a digest disagrees with the tree it describes"** ·
|
||||
**"a coverage oracle reports 100%"** · **"a recovery pass reconciled against the wrong TU"**.
|
||||
|
||||
**The law:** *a committed number is a claim about a tree; if it cannot be recomputed from that tree,
|
||||
it is not evidence — and it must never gate a lever.* (R32/R34/R35; and R14 — I asserted two
|
||||
mechanisms before deriving either.)
|
||||
|
||||
+12
-10
@@ -191,8 +191,9 @@ stub on a named wall/behemoth/queue ledger** — 140/140 byte-identical througho
|
||||
## FLEET — R22 **140 passed / 0 failed of 140**
|
||||
**96.46% fn-count · 94.4% instr-weighted · 89.2% distinct-code** (77,952 uniq) · 0 NON_MATCHING.
|
||||
Session opened 96.28 / 94.1 / 88.7 ⇒ **+37,166 instructions**, ~0 agent tokens after the opening wave.
|
||||
**⚠️ distinct-code FELL 89.3 → 89.2 across the last commit — UNEXPLAINED. See task #11 (S1e); do NOT
|
||||
scale the alias lever until it is resolved. The BYTES are proven (R22); the ACCOUNTING is not.**
|
||||
**✅ RESOLVED S39 (S1e) — the "distinct-code FELL 89.3 → 89.2" alarm was a STALE COMMITTED DIGEST, not
|
||||
a regression. True delta over that span: instr +10,869 · distinct +2,776 ins / +57 uniq — everything
|
||||
ROSE. THE ALIAS LEVER IS UNGATED. Guard added: `make audit-digest` (in `tools-health`). See §140.**
|
||||
|
||||
## 🔑 THE SESSION'S BIGGEST FIND — the def-side asm-label alias is a CLASS lever
|
||||
The dominant sweep blocker was `conflicting types for func_80146A6C` (**208 of ~398** conflicts).
|
||||
@@ -232,16 +233,17 @@ Both passed their per-binary/per-draft gate and FAILED the clean-tree rebuild:
|
||||
| **S2** jr families | DONE — 10 members; 7 families now CLASSIFIED |
|
||||
| **S3** the whale | DONE-PARTIAL — **137/138**; `ov_SC07_010` open; the 61 SC07 -O0 members NOT attempted |
|
||||
| **S1d** the alias class | **138/138 on family 1**; generalised harvest committed w/ the accounting caveat |
|
||||
| **S1e** (NEW #11) · S4 · S5 · S6 · S7 | pending |
|
||||
| **S1e** (#11) | ✅ **RESOLVED — THE REGRESSION NEVER HAPPENED (S39).** The `commit:1426` digest was committed **STALE** (generated from a tree still holding work reverted before the commit landed; overstated **+7,879 ins / +130 uniq**, never regenerated), so the next honest digest read as a fall. True delta 843→HEAD: **instr +10,869 · distinct +2,776 ins / +57 uniq — everything ROSE.** HEAD's digest reproduces EXACTLY. **⇒ THE ALIAS LEVER IS UNGATED — scale it (§61 small batches).** Both recorded leads were wrong (R14): `progress.py:423`'s `SIG` feeds **fn-count only**, and "reverted to INCLUDE_ASM" died on one grep (483 removed, **0 added**). Fixes: `stub_addrs` no longer swallows `corpus.stubs` (the bare `except` byte-witnessed reporting **100.00%/100.00%** in a tree with no `asm/`); **NEW `make audit-digest`** in `tools-health` (integers, not percentages — the staleness printed as "94.4%" both sides), negative-control-proven vs the stale digest; the SAME swallow fixed in `cast_call_sites.tu_for` + `reconcile_tu.tu_for`, where it reconciled against the **wrong TU** (the bug that file's own docstring exists to fix). cookbook **§140** · decision-log 2026-08-04. |
|
||||
| S4 · S5 · S6 · S7 | pending |
|
||||
|
||||
## ▶ RESUME HERE
|
||||
1. **S1e (task #11) FIRST** — resolve the distinct-code drop before scaling the alias lever. Lead:
|
||||
`progress.py:423`'s `SIG` regex records the identifier before the paren, so `void aF80146A6C(...)`
|
||||
is recorded as `aF80146A6C`; the same scanner's docstring documents that exact blindness for K&R
|
||||
defs ("silently erased ~190k banked instructions"). **But that does NOT explain fn-count RISING
|
||||
while distinct-code FELL** — under a pure naming artifact they move together. Fix by resolving a
|
||||
definition through its `__asm__` label; `overlay_src_split.asm_label_aliases` already does this
|
||||
(R33, reuse it).
|
||||
1. ~~**S1e (task #11) FIRST**~~ ✅ **DONE (S39)** — no regression existed; the alias lever is ungated
|
||||
and is the cheapest large lever on the board. Both recorded leads were wrong (R14): the
|
||||
`progress.py:423` `SIG` lead feeds **fn-count only** (neither weighted metric sees a C
|
||||
identifier — they derive from `matched = sig − corpus.stubs`), and the "reverted to INCLUDE_ASM"
|
||||
theory died on one grep (483 removed, 0 added). Still worth doing opportunistically: the `SIG`
|
||||
alias blindness is REAL for fn-count — resolve a def through its `__asm__` label via
|
||||
`overlay_src_split.asm_label_aliases` (R33, reuse it) rather than a new regex.
|
||||
2. **`ov_SC06_030` + `ov_SC07_010`** — both reverted, both re-attemptable (see the failures above).
|
||||
3. **S4** (task #6) + wave 6's 3 still-failing alias drafts (the three LARGEST — size-correlated).
|
||||
4. **S5 the wave** (task #7) — 1,689 h_norm clusters / 326,261 ins at 2.7×. **VERIFY Fable's pool
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
"""tools/audit_digest.py — does the COMMITTED fleet digest still describe the CURRENT tree?
|
||||
|
||||
WHY THIS EXISTS (Phase 30 S1e)
|
||||
===============================
|
||||
`docs/progress.fleet.md` is the project's headline instrument: the burn-down, the roadmap's
|
||||
re-baselines, and every "is this lever paying?" call are read off its three numbers. It is written
|
||||
by `tools/progress.py --fleet` from whatever tree happened to be on disk at that moment — and then
|
||||
COMMITTED, at which point nothing ever checks it again.
|
||||
|
||||
That gap produced a real, costly false alarm. The S38 digest committed at `commit:1426` claimed:
|
||||
|
||||
instr 12402412 distinct 5029324 (78025 uniq)
|
||||
|
||||
The same metric recomputed from `commit:1426`'s own committed tree gives:
|
||||
|
||||
instr 12394533 distinct 5022306 (77895 uniq)
|
||||
|
||||
— overstated by +7,879 instructions and +130 unique functions, because the digest was generated
|
||||
from a working tree that still held banked work which was REVERTED before the commit landed, and
|
||||
was never regenerated afterwards. The next honest digest was therefore LOWER than the stale one, so
|
||||
the metric appeared to FALL (distinct-code 89.3 -> 89.2) while the tree had in fact only gained
|
||||
(true delta over that span: instr +10,869, distinct +2,776 ins / +57 unique fns — everything rose).
|
||||
|
||||
A session then opened with "distinct-code FELL — UNEXPLAINED; do NOT scale the alias lever until it
|
||||
is resolved", i.e. the project's best-performing lever was gated on a phantom regression, and the
|
||||
recorded lead (a name-scanning regex in classify()) pointed at a function that does not feed either
|
||||
weighted metric at all.
|
||||
|
||||
WHY THE EXISTING GATES CANNOT SEE IT (R34)
|
||||
==========================================
|
||||
The whole-binary byte-gate is a perfect CORRECTNESS oracle and a NULL oracle for documents: a stale
|
||||
digest is byte-irrelevant, so `make check-all` stays 140/140 across it forever. `audit-corpus` and
|
||||
`audit-binaries` assert things about the CODE. Nothing asserted that the number we publish about the
|
||||
tree is a number OF that tree. This is that second, disagreeing oracle — and its only job is to
|
||||
disagree when the digest and the tree have drifted apart.
|
||||
|
||||
WHAT IT ASSERTS
|
||||
===============
|
||||
Recompute the three headline metrics from the CURRENT tree and require the committed digest to
|
||||
match. A mismatch is not "the numbers moved" — it is "the published number was never true of this
|
||||
tree", and the fix is always the same: `make report`.
|
||||
|
||||
Exit 0 = the digest describes this tree. Exit 1 = it does not (message says which metric drifted).
|
||||
Usage: tools/audit_digest.py [--fix]
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, os.path.join(REPO, "tools"))
|
||||
|
||||
DIGEST = os.path.join(REPO, "docs/progress.fleet.md")
|
||||
|
||||
# The three headline lines, exactly as progress.fleet() writes them. Parsed as integers (the
|
||||
# printed percentages are rounded to 1dp, so they are far too coarse to catch a 130-function drift —
|
||||
# the +7,879-instruction staleness above still rendered as "94.4%" both before and after).
|
||||
_LINES = {
|
||||
"fn-count": re.compile(r'^FLEET fn-count byte-ident:\s*(\d+)\s*/\s*(\d+)', re.M),
|
||||
"instr": re.compile(r'^FLEET instr-weighted\s*:\s*(\d+)\s*/\s*(\d+)', re.M),
|
||||
"distinct": re.compile(r'^FLEET distinct-code\(uniq\):\s*(\d+)\s*/\s*(\d+)\s*=\s*[\d.]+%\s*\((\d+)/(\d+)', re.M),
|
||||
}
|
||||
|
||||
|
||||
def committed():
|
||||
"""The three metrics as published in docs/progress.fleet.md."""
|
||||
if not os.path.exists(DIGEST):
|
||||
raise SystemExit(f"audit-digest: {os.path.relpath(DIGEST, REPO)} does not exist — run `make report`.")
|
||||
txt = open(DIGEST).read()
|
||||
out = {}
|
||||
for key, rx in _LINES.items():
|
||||
m = rx.search(txt)
|
||||
if not m:
|
||||
# R32: a line we cannot read is a DEFECT, not a skip — a silently-unparsed headline is
|
||||
# exactly the blindness this file exists to remove.
|
||||
raise SystemExit(f"audit-digest: cannot parse the '{key}' line out of "
|
||||
f"{os.path.relpath(DIGEST, REPO)} — the digest format changed and this "
|
||||
f"oracle went blind. Fix the pattern, do not delete the check.")
|
||||
g = [int(x) for x in m.groups()]
|
||||
out[key] = tuple(g)
|
||||
return out
|
||||
|
||||
|
||||
def live():
|
||||
"""The same three metrics, recomputed from the CURRENT tree."""
|
||||
import progress
|
||||
# fn-count comes from the per-binary classification; the weighted pair from the derived stub
|
||||
# oracle. Both are recomputed here exactly as `progress.fleet()` does it.
|
||||
order = [b for b in ("main", "resident") if b in progress.BINARIES] + \
|
||||
sorted(b for b in progress.BINARIES if b not in ("main", "resident"))
|
||||
rows = [progress.report(b, write=False) for b in order]
|
||||
byte = sum(r["byteident"] for r in rows)
|
||||
match = sum(r["matchable"] for r in rows)
|
||||
wm = progress.weighted_metrics()
|
||||
if not wm:
|
||||
raise SystemExit("audit-digest: no .run/sig.*.jsonl — run `make sig-overlays sig-resident`.")
|
||||
return {
|
||||
"fn-count": (byte, match),
|
||||
"instr": (wm["fleet_m"], wm["fleet_t"]),
|
||||
"distinct": (wm["dedup_m"], wm["dedup_t"], wm["dedup_fns"], wm["dedup_total_fns"]),
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
fix = "--fix" in sys.argv
|
||||
have, want = committed(), live()
|
||||
bad = [k for k in want if have.get(k) != want[k]]
|
||||
if not bad:
|
||||
print(f"audit-digest: OK — docs/progress.fleet.md describes the current tree "
|
||||
f"(instr {want['instr'][0]}/{want['instr'][1]}, "
|
||||
f"distinct {want['distinct'][2]}/{want['distinct'][3]} uniq).")
|
||||
return 0
|
||||
print("audit-digest: FAIL — the committed digest does not describe the current tree.")
|
||||
print(" A stale digest is BYTE-INVISIBLE (check-all stays green over it), and a later honest")
|
||||
print(" regeneration then reads as a REGRESSION that never happened. See this file's header.")
|
||||
for k in bad:
|
||||
print(f" {k:9s} committed {have.get(k)} actual {want[k]}")
|
||||
if fix:
|
||||
print(" --fix: regenerating…")
|
||||
import progress
|
||||
progress.fleet()
|
||||
return 0
|
||||
print(" Fix: `make report` (regenerates the digest), then commit it WITH the work it describes.")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -69,11 +69,19 @@ def tu_for(overlay, fn, override=None):
|
||||
if override:
|
||||
return override
|
||||
try:
|
||||
st = _corpus.stubs(overlay).get(int(fn[5:], 16))
|
||||
if st:
|
||||
return os.path.join(REPO, st.path)
|
||||
except Exception:
|
||||
pass
|
||||
addr = int(fn[5:], 16) # a curated (non-func_ADDR) name has no address here
|
||||
except ValueError:
|
||||
return os.path.join(REPO, f'src/{overlay}/{overlay}.c')
|
||||
# CorpusError PROPAGATES (R32/R35, P30 S1e). This was a bare `except Exception: pass`, which on
|
||||
# any oracle failure fell through to the default `<ov>.c` — silently reinstating the exact bug
|
||||
# the docstring above says this function exists to fix (reconciling against the WRONG TU, where
|
||||
# the heavy Phase-26 jr cores live). A wrong-TU reconcile fails the gate, and this phase's base
|
||||
# rate is ~24k PLUMBING vs 4,917 DIFF, so it would read as a codegen wall. Verified safe: at
|
||||
# P30 S1e HEAD, corpus.stubs raises for 0 of 140 binaries — if it fires, the tree really is
|
||||
# inconsistent and reconciling through it is worse than stopping.
|
||||
st = _corpus.stubs(overlay).get(addr)
|
||||
if st:
|
||||
return os.path.join(REPO, st.path)
|
||||
return os.path.join(REPO, f'src/{overlay}/{overlay}.c')
|
||||
|
||||
# A function declaration line (prototype ending in `;`, NOT a definition): optional `extern`,
|
||||
|
||||
+11
-4
@@ -637,10 +637,17 @@ def weighted_metrics():
|
||||
return None
|
||||
|
||||
def stub_addrs(binary):
|
||||
try:
|
||||
return set(corpus.stubs(binary)) # {addr:int -> Stub}; the derived INCLUDE_ASM set (R33)
|
||||
except Exception:
|
||||
return set()
|
||||
# FAIL LOUD (R32/R35). This was `except Exception: return set()` — and an empty stub set does
|
||||
# not mean "no stubs", it means "the oracle could not answer": `matched = sig − stubs` then
|
||||
# credits EVERY function in that binary as banked. Byte-witnessed during P30 S1e: running
|
||||
# this metric in a tree with no `asm/` made corpus.stubs raise its (correct, coverage-asserted)
|
||||
# CorpusError for all 140 binaries, the swallow turned each into an empty set, and the tool
|
||||
# cheerfully reported **instr-weighted 100.00% / distinct-code 100.00%** — a 100%-complete
|
||||
# decomp, from a swallowed error. corpus.stubs is deliberately fail-closed ("it refuses to
|
||||
# answer rather than guess 'banked'"); wrapping it in a bare except reinstated exactly the
|
||||
# guess it refuses to make. A loud failure nobody counts is as invisible as a silent one
|
||||
# (R32) — so here it is neither swallowed nor merely logged: it aborts the metric.
|
||||
return set(corpus.stubs(binary)) # {addr:int -> Stub}; the derived INCLUDE_ASM set (R33)
|
||||
|
||||
fm = ft = 0
|
||||
cls_nins, matched_cls = {}, set()
|
||||
|
||||
@@ -72,11 +72,15 @@ def tu_for(overlay, fn, override=None):
|
||||
if override:
|
||||
return override
|
||||
try:
|
||||
st = corpus.stubs(overlay).get(int(fn[5:], 16))
|
||||
if st:
|
||||
return os.path.join(REPO, st.path)
|
||||
except Exception:
|
||||
pass
|
||||
addr = int(fn[5:], 16) # a curated (non-func_ADDR) name has no address here
|
||||
except ValueError:
|
||||
return os.path.join(REPO, f'src/{overlay}/{overlay}.c')
|
||||
# CorpusError PROPAGATES (R32/R35, P30 S1e) — see the identical note in cast_call_sites.tu_for.
|
||||
# A swallow here silently reconciles the draft against the DEFAULT `<ov>.c` instead of the jr/-O0
|
||||
# split TU that actually compiles it, which is a §51g LAW 10 violation dressed up as a gate refusal.
|
||||
st = corpus.stubs(overlay).get(addr)
|
||||
if st:
|
||||
return os.path.join(REPO, st.path)
|
||||
return os.path.join(REPO, f'src/{overlay}/{overlay}.c')
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user