check:studio-rail — scope the label rule by PROFILE stage, not bundle stage

regions-plugin challenged the rule and was right. A BUNDLE reads
`stage: production` when ANY ONE of its profiles is production, so scoping the
loop by b.stage drags every experimental sibling in with it.

Measured: ALL SEVEN violations it reported were false. cities-internet is a
production bundle whose connectivity-list and data-tables are EXPERIMENTAL;
gospel-resources likewise for charts / data-tables / resources-list; and
my-upg-100-list's own poster-wall and upg-100-list are experimental too -- only
upg-100-mfe is production, and it has carried a label all along. With p.stage the
gate reports 0.

THE SAME TRAP A THIRD TIME. I moved off stageOf (bundle-keyed) after catching it
once, and moved to b.stage -- the same mistake wearing a different field name.
The profile's own stage is the only one that answers "is this row production".

THE SELF-TEST HAD A HOLE THAT MADE THE BUG INVISIBLE: every fixture bundle had
exactly one profile, so bundle-stage and profile-stage always agreed and scoping
by the wrong one could not be detected. A fixture where the two CANNOT disagree
cannot test which one you read. Adds a mixed bundle whose production profile must
FAIL and whose experimental profile must PASS; 6/6.

The two meta files in be398ce8f stay. They were a fix for a non-problem -- both
profiles are experimental -- but a named row is better than a humanized id, and
they cost nothing.
This commit is contained in:
Doxa Maps 2026-09-28 07:52:50 +00:00
parent 114df6f6c4
commit 1223fa7b45

View file

@ -25,8 +25,17 @@ import { TREE_NAME } from './_tree-name.mjs'
function undeclaredLabels(manifest) {
const out = []
for (const b of manifest.bundles || []) {
if (b.stage !== 'production') continue
for (const p of b.profiles || []) {
// ⚠ PROFILE STAGE, NOT BUNDLE STAGE — the same trap, a third time. A BUNDLE reads
// `stage: production` when ANY ONE of its profiles is production, so scoping the loop by
// b.stage drags every experimental sibling in with it. Measured: cities-internet is a
// production bundle whose connectivity-list and data-tables are EXPERIMENTAL, and
// gospel-resources likewise for charts / data-tables / resources-list. That was 5 false
// violations out of 7 (regions-plugin caught it, 2026-09-28).
// I had already moved off stageOf (bundle-keyed) once; moving to b.stage was the SAME
// mistake wearing a different field name. The profile's own stage is the only one that
// answers "is this row production".
if (p.stage !== 'production') continue
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`)
@ -42,11 +51,20 @@ function undeclaredLabels(manifest) {
// 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' }] },
{ bundle: 'planted-prod', stage: 'production', profiles: [{ profile: 'nameless', stage: 'production', label: null }] },
{ bundle: 'planted-exp', stage: 'experimental', profiles: [{ profile: 'nameless', stage: 'experimental', label: null }] },
{ bundle: 'planted-ok', stage: 'production', profiles: [{ profile: 'named', stage: 'production', 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: ' ' }] },
{ bundle: 'planted-blank', stage: 'production', profiles: [{ profile: 'blank', stage: 'production', label: ' ' }] },
// ★ THE CASE THE FIRST SELF-TEST MISSED, and the bug it would have caught. A BUNDLE reads
// production when ANY profile is, so a mixed bundle must flag ONLY its production profile.
// The earlier fixture gave every bundle one profile, so bundle-stage and profile-stage agreed
// and scoping by the wrong one was invisible. A fixture where the two CANNOT disagree cannot
// test which one you read.
{ bundle: 'planted-mixed', stage: 'production', profiles: [
{ profile: 'prod-nameless', stage: 'production', label: null },
{ profile: 'exp-nameless', stage: 'experimental', label: null },
] },
] }
const hits = undeclaredLabels(fixture)
const cases = [
@ -54,6 +72,8 @@ if (process.argv.includes('--self-test')) {
['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/'))],
['mixed bundle: its PRODUCTION profile FAILS', hits.some((h) => h.startsWith('planted-mixed/prod-nameless'))],
['mixed bundle: its EXPERIMENTAL profile PASSES', !hits.some((h) => h.startsWith('planted-mixed/exp-nameless'))],
]
let ok = 0
for (const [name, pass] of cases) { console.log(` ${pass ? '✓' : '✗'} ${name}`); if (pass) ok++ }