diff --git a/internal/check-hardcoded-english.mjs b/internal/check-hardcoded-english.mjs index 62108bccc..8026b1325 100644 --- a/internal/check-hardcoded-english.mjs +++ b/internal/check-hardcoded-english.mjs @@ -500,6 +500,15 @@ const STAGE_FIXTURE = { { bundle: 'deep-exp', stage: 'experimental' }, { bundle: 'pair', stage: 'experimental' }, { bundle: 'lonely-exp', stage: 'experimental' }, + // ★ CYCLES ARE REAL IN THIS MANIFEST, not hypothetical: six edges are self-composition via the + // bundle/profile form (country-posters, my-upg-100-list, regions-dashboard all compose + // themselves), and upg100 measured that a naive recursive walk hangs on it TODAY. The header + // claimed termination in prose and never tested it, which is the same gap as an untested + // fallback — so a self-cycle and a mutual cycle are both in the fixture. If the closure ever + // regresses to recursion this test does not fail, it HANGS, which is its own loud signal. + { bundle: 'selfie', stage: 'production', composes: ['selfie/one', 'selfie/two', 'ring-a'] }, + { bundle: 'ring-a', stage: 'experimental', composes: ['ring-b'] }, + { bundle: 'ring-b', stage: 'experimental', composes: ['ring-a'] }, ], } @@ -586,6 +595,8 @@ function selfTest() { ok(eff.includes('pair'), "a composes entry of the form 'bundle/profile' resolves to its BUNDLE") ok(!eff.includes('lonely-exp'), 'CONTROL: an experimental bundle nobody composes stays out of scope') ok(stageReason(STAGE_FIXTURE, 'mid-exp').startsWith('composed by'), 'and the report can say WHY it is in scope') + ok(eff.includes('selfie'), 'a SELF-COMPOSING bundle resolves rather than looping (6 such edges exist in the real manifest)') + ok(eff.includes('ring-a') && eff.includes('ring-b'), 'and a MUTUAL cycle terminates, marking both members') console.log(fails ? `\nSELF-TEST: ${fails} FAIL(s)` : '\nSELF-TEST: the gate can see planted leaks and stays quiet on correct code') return fails }