check:studio-rail — a production profile must DECLARE its name

The rail showed tiles nobody had named while this gate stayed green. The obvious
fix is a no-op, and writing it first is how that came out.

THE NO-OP: testing the rail's label for null/empty can never fail, because
rowsFromManifest falls back to humanize(id) (plugin-studio.vue:421,429). A
profile that declares nothing still arrives as "Doxa Simple Map" -- not empty,
and not equal to the bare id either, so the existing bare-id rule misses it too.
humanize() defeats BOTH rules. That is why country-posters could ship a tile
nobody had named.

So the property is the DECLARATION, read from the manifest, not the rendered
label: does this profile's <profile>.meta.json carry a "label"? A humanized id is
a reasonable DEFAULT and a bad NAME -- "Doxa Simple Map" tells a reader nothing
the folder did not already say.

PRODUCTION-STAGE ONLY, deliberately: 15 profiles declare no label today and 14
are experimental -- work in progress nobody has named yet, which is not a defect.
A gate that is red for a condition the repo accepts gets ignored.

Also declares the two labels my own plugin was missing: upg-100-list becomes
"UPG-100 — Zoned Map" and poster-wall "UPG-100 — Poster Wall".

⚠ THIS GATE IS RED ON MAIN TODAY, 8 violations across 4 plugins and 4 owners
(cities-internet 2, doxa-simple-map 1, gospel-resources 3, my-upg-100-list 2).
It must NOT land before those labels do, or it blocks every other agent's
landing for a defect that is not theirs. The two meta files here fix my share;
they reach the manifest only through the full build, which is the build owner's.

internal/ is the build owner's under rule 5B -- source sha to studio, sequenced
after the other three owners declare their labels.
This commit is contained in:
Doxa Maps 2026-09-28 07:37:16 +00:00
parent a02458a171
commit 112f4cee77
3 changed files with 34 additions and 0 deletions

View file

@ -66,6 +66,32 @@ try {
for (const b of byBundle.keys()) if (!seen.has(b)) fail(`bundle ${b} is missing from the rail`)
// A row is NAMED, never shown as a bare id (the Driver: Gospel x Internet read just "board").
for (const r of ctx.rows) if (r.label === r.profile || r.label === r.bundle) fail(`${r.key} shows a bare id ("${r.label}") — give its <profile>.meta.json a "label"`)
// ★ AND A PRODUCTION PROFILE MUST DECLARE ITS NAME, not inherit one.
//
// ⚠ THE OBVIOUS VERSION OF THIS RULE IS A NO-OP, and writing it first is how I found out. Testing
// the RAIL's label for null/empty can never fail: rowsFromManifest falls back to humanize(id)
// (plugin-studio.vue:421,429), so a profile that declares NOTHING still arrives here as
// "Doxa Simple Map" — not empty, and not equal to the bare id either, so the bare-id rule above
// misses it too. humanize() defeats BOTH rules, which is why country-posters could ship a tile
// nobody had named while this gate stayed green.
//
// So the property is the DECLARATION, read from the manifest, not the rendered label: does this
// profile's <profile>.meta.json carry a "label"? A humanized id is a reasonable DEFAULT and a bad
// NAME — "Doxa Simple Map" tells a reader nothing the folder did not already say.
//
// ⚠ PRODUCTION-STAGE ONLY, deliberately. 15 profiles declare no label today and 14 are
// 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`)
}
}
}
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

View file

@ -0,0 +1,4 @@
{
"_comment": "The printable half of UPG-100: a wall of prayer-card posters, one per 100-list entry, with the three print paths. Declared so the Studio rail names it rather than falling back to a humanized id (check:studio-rail).",
"label": "UPG-100 — Poster Wall"
}

View file

@ -0,0 +1,4 @@
{
"_comment": "The map half of UPG-100: the zoned list of unengaged people groups, with the semantic-tree legend and the packet/poster exports. Declared so the Studio rail names it rather than falling back to a humanized id (check:studio-rail).",
"label": "UPG-100 — Zoned Map"
}