From 9ab0d9eb67bc05d9098fac222ce045dbbd0becc8 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:20:19 -0600 Subject: [PATCH] fix(gate_main): refuse a verbatim-asm draft at slate load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I converted 9 main SDK functions from §265 verbatim bodies to INCLUDE_ASM stubs so they could be decompiled, then 'banked' all 9 from stored drafts that were those same verbatim asm blocks. match_one printed closeness 0 nine times and the whole-binary gate went BYTE-IDENTICAL — both truthfully, since a raw asm blob assembles to the bytes it was copied from. Nothing was decompiled. progress.py caught it by not moving: REAL 882, VERBATIM 164, INCLUDE_ASM 37, identical before and after. The banks are reverted. The cookbook's closing paragraph, written last session, describes this exact trap. I read it and hit it anyway ~4 hours later, because the rule was addressed to 'any burst over this class' and I was hand-picking stored drafts, and because 'no byte gate can catch it' reads as unpreventable. The byte CHECK cannot; a slate-load refusal can. draft_prechecks.is_verbatim_asm_draft: a file-scope __asm__ naming the fn via .ent/.globl/label AND no C definition of it. Both spellings of .ent handled (inside a C string it is a backslash-t, not a tab — five censuses of this class disagreed until that was fixed). gate_main refuses such a slate beside its existing INCLUDE_ASM no-op refusal (R43). Census of the draft store: 1,099 of 704,375 .c files are verbatim-asm drafts under ordinary .c names. Negative control: 0 false positives across 45,898 drafts carrying both a C definition and an inline __asm__ (R39). --- docs/matching-cookbook.md | 45 +++++++++++++++++++++++++++++++++++++++ tools/draft_prechecks.py | 38 +++++++++++++++++++++++++++++++++ tools/gate_main.py | 19 +++++++++++++++++ 3 files changed, 102 insertions(+) diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index 7eba66e53..70e85f1ae 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -35133,3 +35133,48 @@ its own source by construction**. The adversarial verifier refuted it on the rul containing `__asm__` or `INCLUDE_ASM` is a no-op that passes for free. Any burst over this class MUST carry that check: the trivially-passing draft is not a hypothetical here, it is the *default* thing to produce, and a byte gate cannot tell the difference. + +#### ADDENDUM to §265 (P31 S76) — THE PROSE GUARD DID NOT HOLD. IT IS A GATE REFUSAL NOW. + +**The paragraph immediately above this one was written in S75 and is correct. I tripped over the +exact trap it describes about four hours later, in the next session, having read it.** That is the +finding worth keeping — not the trap, which was already known, but the fact that knowing it in +prose did not prevent it. + +**What happened.** The 9 remaining `DECOMPILE-NOW` SDK functions in `src/800c3.c` / `src/800c2_2.c` +were converted from §265 verbatim bodies to `INCLUDE_ASM` stubs *specifically so they could be +decompiled*. I then searched the draft store for stored drafts, scored 195 candidates with +`match_one`, and got **closeness 0 on all nine**. Slate, gate, `BANKED 9 main functions — +143dbb89 BYTE-IDENTICAL`, 58 seconds. Every one of those nine "drafts" was the function's own +assembly, emitted by `tools/asm_verbatim.py` into `.c` in the same directories as real drafts. +I had converted verbatim → stub → verbatim. A perfect round trip that decompiled nothing. + +**What caught it: `progress.py` did not move.** REAL 882, VERBATIM 164, INCLUDE_ASM 37 — identical +before and after "banking nine functions". The instrument was right and the claim was wrong +(`check-against-a-known-true-case`: the count you already know is the cheapest oracle you own). + +**Why the prose could not hold.** S75's rule was addressed to a *burst over this class* — "any +burst over this class MUST carry that check". I was not running a burst. I was hand-picking stored +drafts one function at a time, which the rule did not name, and the conditional in a reader's head +("does this apply to me?") is exactly where a prose guard fails. S75 also concluded "a byte gate +cannot tell the difference", which is true and which reads as *unpreventable*. It is not: the byte +CHECK cannot tell, but a **slate-load refusal** can, because the verbatim form is trivially +decidable from the text. + +**The guard, where it can actually fire.** `draft_prechecks.is_verbatim_asm_draft(text, fn)` — a +file-scope `__asm__` naming this fn via `.ent`/`.globl`/label, AND no C definition of it. Both +halves matter: a real draft may carry a small inline `__asm__`, and a verbatim body may omit +`.globl` for a static. Match BOTH spellings — `".ent\tNAME"` inside a C string is a *backslash-t*, +not a tab; five S75/S76 censuses of this class disagreed with each other until both were handled. +`gate_main` now refuses such a slate at load, beside its existing `INCLUDE_ASM` no-op refusal (R43). + +**The scale of the landmine.** A census of the draft store: **1,099 of 704,375 `.c` files are +verbatim-asm drafts**, sitting under ordinary `.c` names in ordinary wave directories. Negative +control: **0 false positives across 45,898** drafts that carry both a real C definition and an +inline `__asm__` (R39). Any future "search the store for a stored draft" pass — and that pass is now +a standard move, since S75 banked 20 functions from drafts already on disk — is drawing from a pool +with 1,099 of these in it. + +**The general law.** *A lesson that is only prose will be re-learned. If a check is decidable, the +knowledge base is where you explain it and the pipeline is where you enforce it* — the same +relationship R32 sets between "assert your coverage" and a scanner that actually does. diff --git a/tools/draft_prechecks.py b/tools/draft_prechecks.py index de851009a..70728d086 100644 --- a/tools/draft_prechecks.py +++ b/tools/draft_prechecks.py @@ -116,3 +116,41 @@ def undefined_data(binary, dest_text, syms, self_name=None, body=""): continue out.append(s) return out + + +def is_verbatim_asm_draft(text, fn): + """True if `text` is the target's own assembly, not a decompile of it (P31 S76). + + THE DEFECT THIS CATCHES, and why no byte gate can. `tools/asm_verbatim.py` emits a §265 + verbatim body -- the function's own .s pasted into a file-scope `__asm__` string. Those files + sit in the same draft directories as real C drafts and are named `.c` like everything else. + Score one with match_one and it prints MATCH, truthfully: a raw asm blob assembles to the bytes + it was copied from. Substitute one and the whole-binary gate goes GREEN, also truthfully. The + function is then reported BANKED while nothing whatsoever has been decompiled. + + S75 hit this once (an agent submitted the verbatim block as its own "decompile"; only an + adversarial reader caught it) and recorded "No byte gate can" catch it. That is true of the + BYTE check and false of a slate-load refusal, which is why this lives here: the verbatim form + is trivially decidable from the text (R43 -- refuse the input, never mishandle it). + + S76 hit it again, from the other direction and at scale: nine functions were converted from + verbatim bodies to stubs precisely so they could be decompiled, then "banked" from stored + drafts that were the same verbatim blocks -- a round trip through the gate that moved + progress.py by exactly zero and read as 9 of 9 MATCH. The instrument was right and the claim + was wrong. + + A draft is verbatim iff a file-scope `__asm__` names this function as a .ent/.globl/label AND + no C definition of it exists. Both halves matter: a real draft may legitimately carry a small + inline `__asm__`, and a verbatim body may omit .globl for a static.""" + if '__asm__' not in text: + return False + f = re.escape(fn) + # The escaped forms are what the emitters write INTO a C string: `.ent\tfoo\n` is backslash-t, + # not a tab. Match both the escaped and the literal spellings -- five S76 censuses of this + # class disagreed with each other until both were handled. + asm_names = re.search(rf'\.(?:ent|globl)(?:\\t|[ \t])+{f}\b', text) or \ + re.search(rf'"\s*{f}:\s*(?:\\n|$)', text) + if not asm_names: + return False + c_def = re.search(rf'^[A-Za-z_][\w \*]*\b{f}\s*\([^;]*\)\s*(?:/\*.*?\*/\s*)?\{{', text, re.M | re.S) + return not c_def diff --git a/tools/gate_main.py b/tools/gate_main.py index 28e9a1b46..9562edf3e 100644 --- a/tools/gate_main.py +++ b/tools/gate_main.py @@ -39,6 +39,7 @@ import hashlib import time, collections, fcntl, functools, glob, json, os, re, subprocess, sys sys.path.insert(0, 'tools') import corpus +import draft_prechecks as DP # stdout is BUFFERED when redirected to a file -- a long run then looks hung with an # empty log (measured: 16 min of silence during a bisect). Always flush. @@ -932,6 +933,24 @@ def main(): 'passes for free: %s' % (len(_noop), ', '.join(_noop))) + # THE SAME NO-OP, WEARING C's CLOTHES (P31 S76). A §265 verbatim body is the function's own + # assembly in a file-scope `__asm__` string; `tools/asm_verbatim.py` writes them as `.c` + # into the same draft directories as real drafts. Substituting one assembles the bytes it was + # copied from, so match_one prints MATCH and this gate goes GREEN -- both truthfully -- while + # nothing has been decompiled. S75 saw it once from an agent and concluded "no byte gate can" + # catch it; that is true of the BYTE check and false of a slate-load refusal. S76 then did it + # at scale: 9 functions converted from verbatim bodies to stubs *so they could be decompiled*, + # then "banked" from stored drafts that were those same verbatim blocks. 9 of 9 MATCH, and + # progress.py moved by exactly zero. Refuse at load, like the INCLUDE_ASM no-op above (R43). + _verb = [e['fn'] for e in slate + if os.path.exists(e.get('draft', '')) + and DP.is_verbatim_asm_draft(open(e['draft'], errors='replace').read(), e['fn'])] + if _verb: + raise SystemExit("gate_main: REFUSED — %d draft(s) are the target's own assembly in a " + "file-scope __asm__ (a §265 verbatim body), not a decompile. They would " + "pass this gate for free and bank nothing: %s" + % (len(_verb), ', '.join(_verb))) + kept, dropped = resolve_conflicts(slate) print(f"slate {len(slate)} -> {len(kept)} compatible, {len(dropped)} dropped for in-TU decl conflict") for d in dropped: