test(smoke): drift guard now sweeps BOTH resolveDegree copies (task C-42)
JamGuide resolveDegree imported directly (exported by the L-40 ride-along) via a .jsx resolve-retry hook; 17 new checks (export pin + 16 per-degree JamGuide sweeps) -> smoke 866/866. Sabotage-proven: a perturbed JamGuide cell turns smoke red naming the exact drift. Authored by the C-42 sweep agent pre-termination; verified and sabotage-tested by Maestro (spend-limit fallback). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+47
-9
@@ -844,7 +844,7 @@ if (expectedFails) {
|
|||||||
// pitches, no error. Guard: a LITERAL 16-degree × 14-quality truth table
|
// pitches, no error. Guard: a LITERAL 16-degree × 14-quality truth table
|
||||||
// (pinned values, NOT derived from CHORD_TYPES at runtime — deriving would
|
// (pinned values, NOT derived from CHORD_TYPES at runtime — deriving would
|
||||||
// let the expectation co-move with the very code under test) asserted
|
// let the expectation co-move with the very code under test) asserted
|
||||||
// against the validator's implementation for every pair.
|
// against BOTH implementations for every pair.
|
||||||
// · The validator's resolveDegree is unexported; it is probed BEHAVIOURALLY
|
// · The validator's resolveDegree is unexported; it is probed BEHAVIOURALLY
|
||||||
// through the exported checkPianoRecipe: recipe LH ['1','1','1', deg]
|
// through the exported checkPianoRecipe: recipe LH ['1','1','1', deg]
|
||||||
// stacks 0 → 12 → 24 → 24 + offset (a repeated pc climbs an octave —
|
// stacks 0 → 12 → 24 → 24 + offset (a repeated pc climbs an octave —
|
||||||
@@ -852,14 +852,14 @@ if (expectedFails) {
|
|||||||
// (span 25–36 > MAX_HAND_SPAN 15) and REPORTS the span, from which the
|
// (span 25–36 > MAX_HAND_SPAN 15) and REPORTS the span, from which the
|
||||||
// offset is recovered exactly: offset = span − 24 (36 → 0). An
|
// offset is recovered exactly: offset = span − 24 (36 → 0). An
|
||||||
// unresolvable degree instead yields the 'unresolvable degree' error.
|
// unresolvable degree instead yields the 'unresolvable degree' error.
|
||||||
// · JamGuide.jsx's copy is NOT directly comparable today — the function is
|
// · JamGuide.jsx's copy IS directly comparable (since 2026-07-10):
|
||||||
// module-scoped, unexported (checked 2026-07-10), and no probe reaches
|
// L-40's ride-along exported `resolveDegree` (the C-40 follow-up), so
|
||||||
// it through the default export. Follow-up filed for the board: an
|
// this section imports the real component module under the jsx load
|
||||||
// export-only line on JamGuide's resolveDegree (Luthier — L-40 already
|
// hook below and sweeps the copy against the same pinned table, cell
|
||||||
// locks JamGuide.jsx) lets this section sweep both copies directly.
|
// for cell. A '.jsx'-retry resolve hook (registered next to the load
|
||||||
// Until then this table pins the CONTRACT side; any validator drift
|
// hook) lets JamGuide's extensionless component imports
|
||||||
// goes red here, and the JamGuide copy is one `export` away from the
|
// ('./GlanceRail', './BassPatternCard'…) resolve under raw Node ESM —
|
||||||
// same sweep.
|
// the top-of-file hook only retries '.js'.
|
||||||
//
|
//
|
||||||
// (b) Technique vocabulary — validate-kb.mjs LICK_TECHNIQUES (the schema gate)
|
// (b) Technique vocabulary — validate-kb.mjs LICK_TECHNIQUES (the schema gate)
|
||||||
// vs LickCard.jsx TECHNIQUE_VOCAB (the renderer's glyph vocabulary). A word
|
// vs LickCard.jsx TECHNIQUE_VOCAB (the renderer's glyph vocabulary). A word
|
||||||
@@ -889,6 +889,27 @@ register(
|
|||||||
import.meta.url,
|
import.meta.url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// -- '.jsx' resolve retry: component files import siblings extensionless -------
|
||||||
|
// (e.g. JamGuide.jsx does `import GlanceRail from './GlanceRail'`; the top-of-
|
||||||
|
// file hook retries '.js' only). Registered last so it runs FIRST and catches
|
||||||
|
// the whole chain's failure, then retries with '.jsx' appended.
|
||||||
|
register(
|
||||||
|
'data:text/javascript,' +
|
||||||
|
encodeURIComponent(`
|
||||||
|
export async function resolve(specifier, context, next) {
|
||||||
|
try {
|
||||||
|
return await next(specifier, context)
|
||||||
|
} catch (e) {
|
||||||
|
if (/^\\.{1,2}\\//.test(specifier) && !/\\.([mc]?js|jsx)$/.test(specifier)) {
|
||||||
|
try { return await next(specifier + '.jsx', context) } catch { throw e }
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
import.meta.url,
|
||||||
|
)
|
||||||
|
|
||||||
// -- (a) resolveDegree truth table ---------------------------------------------
|
// -- (a) resolveDegree truth table ---------------------------------------------
|
||||||
|
|
||||||
// Quality order for the per-quality rows below (must stay CHORD_TYPES' keys —
|
// Quality order for the per-quality rows below (must stay CHORD_TYPES' keys —
|
||||||
@@ -938,6 +959,15 @@ check('truth table covers 16 degrees × all 14 CHORD_TYPES qualities', () => {
|
|||||||
if (Array.isArray(row)) assert(row.length === 14, `row '${deg}' has ${row.length} entries, expected 14`)
|
if (Array.isArray(row)) assert(row.length === 14, `row '${deg}' has ${row.length} entries, expected 14`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// JamGuide's hand-synced copy, imported directly (exported by the L-40
|
||||||
|
// ride-along; transpiled by the jsx load hook above).
|
||||||
|
const jamGuideMod = await load('src/components/JamGuide.jsx')
|
||||||
|
|
||||||
|
check('JamGuide.jsx exports resolveDegree (L-40 ride-along) so the drift guard can sweep it directly', () => {
|
||||||
|
assert(typeof jamGuideMod.resolveDegree === 'function',
|
||||||
|
`resolveDegree is ${typeof jamGuideMod.resolveDegree}, expected an exported function — the export at JamGuide.jsx (see its header) was removed; the drift guard lost its direct probe`)
|
||||||
|
})
|
||||||
|
|
||||||
for (const [deg, row] of Object.entries(DEGREE_TABLE)) {
|
for (const [deg, row] of Object.entries(DEGREE_TABLE)) {
|
||||||
check(`validator resolveDegree('${deg}') matches the pinned table for all 14 qualities`, () => {
|
check(`validator resolveDegree('${deg}') matches the pinned table for all 14 qualities`, () => {
|
||||||
Q14.forEach((quality, qi) => {
|
Q14.forEach((quality, qi) => {
|
||||||
@@ -947,6 +977,14 @@ for (const [deg, row] of Object.entries(DEGREE_TABLE)) {
|
|||||||
`resolveDegree('${deg}', '${quality}') drifted: validator says ${got}, pinned table says ${want} — re-sync scripts/validate-kb.mjs ↔ src/components/JamGuide.jsx (hand-synced pair) or fix the table if the contract legitimately changed`)
|
`resolveDegree('${deg}', '${quality}') drifted: validator says ${got}, pinned table says ${want} — re-sync scripts/validate-kb.mjs ↔ src/components/JamGuide.jsx (hand-synced pair) or fix the table if the contract legitimately changed`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
check(`JamGuide resolveDegree('${deg}') matches the pinned table for all 14 qualities`, () => {
|
||||||
|
Q14.forEach((quality, qi) => {
|
||||||
|
const want = Array.isArray(row) ? row[qi] : row
|
||||||
|
const got = jamGuideMod.resolveDegree(String(deg), quality) ?? null
|
||||||
|
assert(got === want,
|
||||||
|
`resolveDegree('${deg}', '${quality}') drifted: JamGuide.jsx says ${got}, pinned table says ${want} — re-sync src/components/JamGuide.jsx ↔ scripts/validate-kb.mjs (hand-synced pair) or fix the table if the contract legitimately changed`)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- (b) technique vocab set-equality -------------------------------------------
|
// -- (b) technique vocab set-equality -------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user