From 510f3d30d4378dea7c3e15f15ef26313bf7acf32 Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:58:39 +0000 Subject: [PATCH] The backlog-status enum was too narrow, not the nine cards wrong MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This gate keeps the AGGREGATE honest — a prose status makes every open-count wrong while each card still READS correctly, so the error hides in the total. That matters to this loop directly: every tick starts by reading status counts to pick a card. MEASURED across 633 cards: EIGHT distinct status values are in use and EVERY ONE IS A SINGLE WORD. No prose remains anywhere in the field — which is exactly what the original 130-card migration achieved. The nine cards this gate was failing carried legitimate TERMINAL states the enum simply predated: superseded (5), deferred (2), decided, verified-already-true. ★ REWRITING THEM TO "done" WOULD HAVE DESTROYED REAL DISTINCTIONS to satisfy a list. A SUPERSEDED card was replaced by another; a DEFERRED one was consciously postponed; neither was finished. Losing that information to make a checker green is the opposite of what this gate is for. OPEN vs CLOSED is the property that matters, and all four additions are CLOSED: open = todo + doing + in-progress (66), closed = done + these four (507), blocked (60). MUTATION-TESTED BOTH WAYS, so widening did not weaken it: a prose status ("done — reconciled against main, see notes") still FAILS by name — the original defect — AND an unagreed one-word status ("wontfix") ALSO fails, so the set stays CLOSED rather than becoming "any single word". Wired into gates:post (32 -> 33) after confirming it reads only backlog JSON. The doc that quotes gates:post is re-transcribed in the same commit. Build: 7/7 pre, 23/23 bundles, 33/33 post, exit 0. --- .../README-SECTION-3-cli-commands.md | 2 +- internal/check-backlog-status-enum.mjs | 23 +++++++++++++++++-- package.json | 2 +- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/documentation/README-SECTION-3-cli-commands.md b/documentation/README-SECTION-3-cli-commands.md index 7d6fdea0e..6545d3bd3 100644 --- a/documentation/README-SECTION-3-cli-commands.md +++ b/documentation/README-SECTION-3-cli-commands.md @@ -49,7 +49,7 @@ The scripts these commands are grounded in (from this branch's `package.json`): "build": "bash internal/build-lock.sh", "build:unlocked": "bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post && bun run gallery", "gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:wp-embed check:doc-scripts", -"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags", +"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status", "build:bundles": "node internal/run-parallel.mjs --bundles", // the 22 vite builds, 4 at a time "preview": "bun x vite preview", "serve": "node internal/serve-strict.mjs", // 404s a directory, like production diff --git a/internal/check-backlog-status-enum.mjs b/internal/check-backlog-status-enum.mjs index 192606dac..a74d50fec 100644 --- a/internal/check-backlog-status-enum.mjs +++ b/internal/check-backlog-status-enum.mjs @@ -27,7 +27,10 @@ * to the enum; 130/130 preserved, 0 lost. The explanation was never the problem — putting it in the * field that tooling reads was. * - * WHAT IT ASSERTS: every card's `status` is one of todo / doing / in-progress / blocked / done. + * WHAT IT ASSERTS: every card's `status` is ONE WORD from a closed set — the five workflow + * states plus four terminal ones (superseded / deferred / decided / verified-already-true). + * The point is not the particular words: it is that `status` stays machine-readable so the + * open-count is right. Prose belongs in fields.status_detail. * * It deliberately does NOT require `status_detail` to exist. Most cards need no explanation, and * demanding one would turn a real check into paperwork that gets satisfied with filler. @@ -41,7 +44,23 @@ import { fileURLToPath } from 'node:url' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const QA = join(ROOT, '../_qa-cycle') const PHASES = ['01-ideation-research', '02-builder-cycle', '03-deployment-cycle'] -const ALLOWED = new Set(['todo', 'doing', 'in-progress', 'blocked', 'done']) +// ★ THE ENUM WAS TOO NARROW, not the cards wrong. MEASURED 2026-09-24 across 633 cards: +// EIGHT distinct status values are in use and EVERY ONE IS A SINGLE WORD — there is no +// prose left anywhere in the field. The nine this gate was failing carried legitimate +// TERMINAL states the original enum simply predated: superseded (5), deferred (2), +// decided, verified-already-true. +// +// This gate exists to keep the AGGREGATE honest — "a prose status makes every open-count +// wrong while each card still READS correctly". A one-word terminal state serves that +// purpose perfectly; it is exactly what the 130-card migration was FOR. Rewriting nine +// accurate statuses to 'done' would have destroyed real distinctions (a SUPERSEDED card +// was replaced by another; a DEFERRED one was consciously postponed; neither was +// finished) to satisfy a list — losing information to make a checker green. +// +// OPEN vs CLOSED is the property that matters, and all four additions are CLOSED: +// open = todo + doing + in-progress (66), closed = done + these four (507), blocked (60). +const TERMINAL_EXTRA = ['superseded', 'deferred', 'decided', 'verified-already-true'] +const ALLOWED = new Set(['todo', 'doing', 'in-progress', 'blocked', 'done', ...TERMINAL_EXTRA]) if (!existsSync(QA)) { // _qa-cycle lives outside every repo the CI clones, so absence is EXPECTED there. Say so loudly diff --git a/package.json b/package.json index d504df72d..46f6928ab 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,7 @@ "check:doc-scripts:self-test": "node internal/check-doc-quotes-scripts.mjs --self-test", "check:legend-source": "node internal/check-legend-matches-source.mjs # the numbers the maps SHOW must match the numbers the live SOURCE has. Every other gate checks INTERNAL consistency — a page can agree with itself perfectly while its legend has stopped describing reality. Offline SKIPs (exit 0); only a reachable endpoint that disagrees fails.", "gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:wp-embed check:doc-scripts", - "gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags", + "gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status", "build:bundles": "node internal/run-parallel.mjs --bundles", "check:maplibre-drops": "node internal/check-maplibre-layer-drops.mjs", "check:tabs-localized": "node internal/check-tabs-localized.mjs # a chosen locale must CHANGE the rendered tab text. check:i18n-parity compares catalogs to each OTHER, so it cannot tell a shipped translation from a dead one; tabs.json is reached only via a DYNAMIC key (SemanticTreeLegend.vue:40), invisible to any grep. Renders it and reads the result.",