From ce6db47ef75bd25fdc5acfb4eac67e7c9275e9ac Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 09:03:03 +0000 Subject: [PATCH] fix(devx): build tenant-audit-census self-test's unenforced-prose fixture from the page's own number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `⭐ a stale unenforced prose number is NOT a finding` / `an unenforced prose claim reworded off the page IS a finding` self-test cases built their `.replace()` search string from the LIVE census (`census.declaredObjects`). The gate itself declares this figure unenforced -- value free, sentence required -- so once the page's stated number and the live tree disagree, that search string stops matching the page, `.replace()` becomes a no-op, and the second case fails for the PAGE rather than for the classifier it exists to pin. Build the fixture from the page's own stated number instead (read with the same `UNENFORCED_PROSE_COUNTS[0]` pattern the classifier itself uses), so the pair of cases test the classifier's behaviour regardless of whether the page and the live census currently agree. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU --- scripts/check-tenant-audit-census.mjs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/scripts/check-tenant-audit-census.mjs b/scripts/check-tenant-audit-census.mjs index 6eaa604ecb..7789eb3791 100644 --- a/scripts/check-tenant-audit-census.mjs +++ b/scripts/check-tenant-audit-census.mjs @@ -837,13 +837,30 @@ export function selfTest() { check(page.replace(UNENFORCED_MEASURED_AT.pattern, 'Measured on 2001-01-01 at `0000000ab`.'), counts) .length === 0); - // The unenforced PROSE figure: value free, sentence required. + // The unenforced PROSE figure: value free, sentence required. ⛔ The fixture is + // built from the PAGE's OWN stated number -- read with the exact same pattern + // the classifier uses (`UNENFORCED_PROSE_COUNTS[0]`) -- never from the live + // `census.declaredObjects`. That figure is declared unenforced precisely so it + // may drift from the tree without being a finding; a fixture pinned to the + // LIVE count made `.replace()` a silent no-op the moment page and tree + // disagreed, so this case failed for the PAGE rather than for the classifier + // it exists to pin (#17437). + const declaredObjectsRow = UNENFORCED_PROSE_COUNTS[0]; + const declaredObjectsMatch = declaredObjectsRow.pattern.exec(page); + if (!declaredObjectsMatch) { + throw new Error( + `self-test fixture: the page no longer states "${declaredObjectsRow.name}" in the shape ` + + `${declaredObjectsRow.pattern} looks for -- fix the page, or this pair of cases can no ` + + 'longer build its fixture.', + ); + } + const declaredObjectsSentence = declaredObjectsMatch[0]; t('⭐ a stale unenforced prose number is NOT a finding', - check(page.replace(`Across ${census.declaredObjects} declared objects`, 'Across 4 declared objects')) + check(page.replace(declaredObjectsSentence, 'Across 4 declared objects')) .length === 0, - check(page.replace(`Across ${census.declaredObjects} declared objects`, 'Across 4 declared objects')).join(' | ')); + check(page.replace(declaredObjectsSentence, 'Across 4 declared objects')).join(' | ')); t('an unenforced prose claim reworded off the page IS a finding', - check(page.replace(`Across ${census.declaredObjects} declared objects`, 'Across the declared objects')) + check(page.replace(declaredObjectsSentence, 'Across the declared objects')) .some((p) => p.startsWith('[unenforced-prose-missing]'))); // ── refusals ───────────────────────────────────────────────────────────────