Skip to content

Commit fe4afc0

Browse files
committed
Blind-spot fix #1 (classifier): structural-literal divergences GATE, never conceded as a "fuzz frontier limit"
The generative scope≡role net FOUND the self-close `/` bug all along — `node test/generative.ts html` printed it — but `isGated` classified a `#24` structural-literal→content divergence found via the FUZZ strategy as "a standing flat-TM frontier limit → report-only", so it never failed CI. That heuristic baked a PROVEN-limit assumption (the [[feedback-tm-impossible-proof]] anti-pattern) into the test suite: it conceded a fixable bug by assumption. Every structural-literal divergence found this session (value-position, flow-in-key, self-close, mixed-case raw-text, non-first-item compact) was a fixable depth/structure bug, closed by deriving a region — never a frontier limit. So gating is now by KIND (the bug class), not discovery STRATEGY: `isGated` gates every `#23` / `#comment` / structural-literal divergence on every strategy including fuzz. A genuinely-proven TextMate limit, if ever established, would be excluded by an explicit fingerprint allowlist, not a blanket strategy concession. Green today (0 such divergences after the 3 fixes); it now turns a FUTURE structural mis-paint into a hard failure that forces a fix. gap-ledger-selftest's `probeDivergence` found its test divergence via `!isGated` (the old report-only set) — decoupled to find the self-close by token, matching the real ledger (gap-ledger.ts classifies by the parse5 oracle, not isGated). Added a classifier-lock assertion (the four real-bug kinds all gate). 26/26 gates green.
1 parent 6de9adf commit fe4afc0

3 files changed

Lines changed: 26 additions & 10 deletions

File tree

test/gap-ledger-selftest.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ ok(dropProven, 'drop case: at least one real parser-over-accept is parser-accept
113113
// and the dual: the oracle-VALID minimal repro is KEPT (not dropped) — the keep/drop split is real.
114114
ok(classifyKeeps('<A A=""/>'), 'keep/drop split: the oracle-VALID `<A A="">`-shape repro is KEPT (not dropped)');
115115

116+
// ── (C) the CLASSIFIER lock: a structural-literal / anchored-marker / comment divergence GATES (the
117+
// generative scope≡role gate fails on it), NEVER conceded as a report-only "fuzz frontier limit". This
118+
// is the blind-spot fix that turns a self-close like the one above into a HARD failure that forces a fix,
119+
// instead of a tracked known-gap. Gating is by KIND (the bug class), not by discovery STRATEGY.
120+
ok(isGated({ kind: '#24 structural-literal→content' }), 'classifier lock: structural-literal→content GATES (not report-only)');
121+
ok(isGated({ kind: 'structural-literal→name' }), 'classifier lock: structural-literal→name GATES');
122+
ok(isGated({ kind: '#23 anchored-marker misfire' }), 'classifier lock: #23 anchored-marker GATES');
123+
ok(isGated({ kind: '#comment uncolored' }), 'classifier lock: #comment GATES');
124+
116125
// ── (A) determinism of the rendered artifact: two builds byte-identical ──
117126
console.log('\n determinism (two full ledger builds)…');
118127
const run = () => execFileSync('node', ['test/gap-ledger.ts'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 64 * 1024 * 1024 });
@@ -128,6 +137,9 @@ function probeDivergence(p: Probe, text: string): { target: string } | null {
128137
let toks; try { toks = tmTokenize(p.tm, text); } catch { return null; }
129138
const leaves = leafRoles(p.grammar, cst, p.roleOf);
130139
const vs = collectViolations({ input: text, strategy: 'fuzz', cst, toks, leaves, anchored: p.anchored });
131-
const v = vs.find((x) => !isGated(x));
140+
// The self-close `/` divergence (a structural-literal→content #24). Found by TOKEN, not by `!isGated`:
141+
// the real ledger (gap-ledger.ts) classifies by the parse5 oracle, NOT the gate's isGated predicate, and
142+
// a structural-literal divergence now GATES (see the isGated lock below) rather than being report-only.
143+
const v = vs.find((x) => x.text === '/');
132144
return v ? { target: sig(v) } : null;
133145
}

test/generative-detect.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,15 @@ export function collectCommentViolations(args: { grammar: CstGrammar; input: str
231231

232232
// What GATES vs what is a report-only DISCOVERY (generative.ts's exact predicate):
233233
// • an ANCHORED-MARKER misfire (#23) ALWAYS gates.
234-
// • a STRUCTURAL-LITERAL→content divergence (#24) gates on the STRUCTURED strategies, but is
235-
// report-only on FUZZ inputs (a standing flat-TM frontier limit). The gap ledger lists the
236-
// DISCOVERED ones (the !isGated set) — the floor-blind divergences a corpus metric is blind to.
237-
// • a COMMENT-uncolored divergence ALWAYS gates: a comment (parser-dropped, highlighter-scoped
238-
// `comment.*` by construction) painted as a NON-comment is unambiguous — there is no legitimate
239-
// "frontier limit" where an injected comment is not a comment (its position is chosen safe + it
240-
// round-trips). On today's correct grammars this finds 0; it CATCHES a future scope regression.
241-
export const isGated = (v: { kind: string; strategy: string }): boolean => v.kind.startsWith('#23') || v.kind.startsWith('#comment') || !v.strategy.startsWith('fuzz');
234+
// • a STRUCTURAL-LITERAL divergence (#24 →content, →name) ALWAYS gates, on EVERY strategy
235+
// INCLUDING fuzz. The prior "fuzz #24 = a standing flat-TM frontier limit → report-only"
236+
// concession was DISPROVEN: every such divergence found (value-position, flow-in-key, self-close,
237+
// mixed-case raw-text, non-first-item compact) was a FIXABLE depth/structure bug, closed by
238+
// deriving the region — never a frontier limit. A structural literal (a punctuation / keyword the
239+
// parser assigns by construction) painted as content/name is a real bug regardless of HOW it was
240+
// discovered, so gate it — don't concede it. (A genuinely-proven TextMate limit, if one is ever
241+
// established, is excluded by an explicit fingerprint allowlist, never a blanket strategy.)
242+
// • a COMMENT-uncolored divergence ALWAYS gates (same reasoning — no legitimate "frontier limit"
243+
// where an injected comment is not a comment). On today's correct grammars this finds 0; it
244+
// CATCHES a future scope regression. Gating is by KIND (the bug class), NOT discovery STRATEGY.
245+
export const isGated = (v: { kind: string }): boolean => v.kind.startsWith('#23') || v.kind.startsWith('#comment') || v.kind.includes('structural-literal');

test/generative.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ async function runLang(cfg: LangCfg): Promise<{ name: string; ok: boolean; viola
182182
// regressions of a known-fixed shape, and #25 is the testing harness, not a fix for every limit.
183183
const gated = violations.filter(isGated);
184184
const discovered = violations.filter((v) => !isGated(v));
185-
console.log(` scope≡role: ${checkedTokens} declared-scope tokens checked · ${gated.length} gated inconsistenc${gated.length === 1 ? 'y' : 'ies'} · ${discovered.length} discovered (fuzz frontier-limit, report-only)`);
185+
console.log(` scope≡role: ${checkedTokens} declared-scope tokens checked · ${gated.length} gated inconsistenc${gated.length === 1 ? 'y' : 'ies'} · ${discovered.length} report-only (allowlisted proven-limits — 0 by default; structural divergences GATE)`);
186186
// comment-witness coverage: how many injected comment spans were graded (was structurally 0 — a comment
187187
// is a skip token, dropped by the parser, so it never reached the leaf-walking scope≡role check).
188188
console.log(` comment witnesses: ${checkedComments} comment span${checkedComments === 1 ? '' : 's'} graded (highlighter must paint COMMENT) · ${violations.filter((v) => v.kind.startsWith('#comment')).length} uncolored`);

0 commit comments

Comments
 (0)