check:studio-rail — a fixture self-test for the declared-label rule
The rule is extracted as a pure undeclaredLabels(manifest) so it can be tested
over a fixture without standing up Vite or needing a built tree, and runs before
that setup so `--self-test` works on a fresh clone.
Four planted cases, and the SECOND is the one that matters -- a rule that fails
everything is as useless as one that fails nothing:
production + no label must FAIL
experimental + no label must PASS (the stage scope is the design)
production + a real label must PASS
production + whitespace-only must FAIL (`label: " "` satisfies a null
check and still renders blank)
Wired as check:studio-rail:self-test. Verified the extraction changed no
behaviour: the live gate still reports the same 8 violations it did before.
It exists because the first version of this rule PASSED on a manifest that
contained the very defect it was written for -- humanize(id) meant the rendered
label was never empty, so nothing could fail.
This commit is contained in:
parent
112f4cee77
commit
114df6f6c4
2 changed files with 47 additions and 9 deletions
|
|
@ -18,6 +18,51 @@ import { join, resolve, dirname } from 'node:path'
|
|||
import { fileURLToPath } from 'node:url'
|
||||
import { TREE_NAME } from './_tree-name.mjs'
|
||||
|
||||
/**
|
||||
* Every PRODUCTION profile that declares no "label". Pure — takes a manifest, returns messages — so
|
||||
* the self-test below can run it over a fixture without standing up Vite or a built tree.
|
||||
*/
|
||||
function undeclaredLabels(manifest) {
|
||||
const out = []
|
||||
for (const b of manifest.bundles || []) {
|
||||
if (b.stage !== 'production') continue
|
||||
for (const p of b.profiles || []) {
|
||||
const declared = p.label
|
||||
if (declared === null || declared === undefined || !String(declared).trim()) {
|
||||
out.push(`${b.bundle}/${p.profile} is PRODUCTION but declares no "label" in its <profile>.meta.json — the rail falls back to a humanized id, which names nothing`)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ── SELF-TEST ────────────────────────────────────────────────────────────────
|
||||
// ★ A PLANTED VIOLATION, because the first version of this rule PASSED on a manifest that contained
|
||||
// the very defect it was written for. Two cases, and the SECOND is the one that matters: a rule that
|
||||
// fails everything is as useless as one that fails nothing.
|
||||
if (process.argv.includes('--self-test')) {
|
||||
const fixture = { bundles: [
|
||||
{ bundle: 'planted-prod', stage: 'production', profiles: [{ profile: 'nameless', label: null }] },
|
||||
{ bundle: 'planted-exp', stage: 'experimental', profiles: [{ profile: 'nameless', label: null }] },
|
||||
{ bundle: 'planted-ok', stage: 'production', profiles: [{ profile: 'named', label: 'A Real Name' }] },
|
||||
// An EMPTY STRING is nameless too — `label: ""` would satisfy a null check and render blank.
|
||||
{ bundle: 'planted-blank', stage: 'production', profiles: [{ profile: 'blank', label: ' ' }] },
|
||||
] }
|
||||
const hits = undeclaredLabels(fixture)
|
||||
const cases = [
|
||||
['production + no label FAILS', hits.some((h) => h.startsWith('planted-prod/'))],
|
||||
['experimental + no label PASSES', !hits.some((h) => h.startsWith('planted-exp/'))],
|
||||
['production + a real label PASSES', !hits.some((h) => h.startsWith('planted-ok/'))],
|
||||
['production + whitespace-only FAILS', hits.some((h) => h.startsWith('planted-blank/'))],
|
||||
]
|
||||
let ok = 0
|
||||
for (const [name, pass] of cases) { console.log(` ${pass ? '✓' : '✗'} ${name}`); if (pass) ok++ }
|
||||
console.log(ok === cases.length
|
||||
? `STUDIO-RAIL SELF-TEST: PASS — ${ok}/${cases.length}`
|
||||
: `STUDIO-RAIL SELF-TEST: FAIL — ${ok}/${cases.length}`)
|
||||
process.exit(ok === cases.length ? 0 : 1)
|
||||
}
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const MANIFEST = join(ROOT, 'public', TREE_NAME, 'manifest.json')
|
||||
if (!existsSync(MANIFEST)) { console.log('STUDIO-RAIL: SKIP — no built manifest'); process.exit(0) }
|
||||
|
|
@ -83,15 +128,7 @@ try {
|
|||
// experimental — work in progress nobody has named yet, which is not a defect. Failing those
|
||||
// would make this gate red for a condition the repo accepts, and a gate that is red for an
|
||||
// accepted condition gets ignored. A profile earns the obligation when it reaches production.
|
||||
for (const b of manifest.bundles) {
|
||||
if (b.stage !== 'production') continue
|
||||
for (const p of b.profiles || []) {
|
||||
const declared = p.label
|
||||
if (declared === null || declared === undefined || !String(declared).trim()) {
|
||||
fail(`${b.bundle}/${p.profile} is PRODUCTION but declares no "label" in its <profile>.meta.json — the rail falls back to a humanized id, which names nothing`)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const m of undeclaredLabels(manifest)) fail(m)
|
||||
const canon = secs.flatMap((s) => s.nodes.filter((n) => n.canonical))
|
||||
for (const r of ctx.rows) { const c = canon.filter((n) => n.target === r.key).length; if (c !== 1) fail(`${r.key} has ${c} canonical nodes (want 1)`) }
|
||||
const nProd = [...stageOf.values()].filter((v) => v === 'production').length
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@
|
|||
"check:engine-contract:poison": "node internal/check-gospel-engine-contract.mjs --poison",
|
||||
"check:embed-contract:poison": "node internal/check-gospel-embed-contract.mjs --poison",
|
||||
"check:map-attribution": "node internal/check-map-attribution.mjs",
|
||||
"check:studio-rail:self-test": "node internal/check-studio-rail.mjs --self-test",
|
||||
"check:studio-rail": "node internal/check-studio-rail.mjs",
|
||||
"check:translator-contacts": "node internal/check-translator-contacts.mjs",
|
||||
"check:translator-contacts:poison": "node internal/check-translator-contacts.mjs --poison",
|
||||
|
|
|
|||
Loading…
Reference in a new issue