From 2a9198fa3ca593753d26b0ad7ec48686c218973f Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Tue, 22 Sep 2026 22:44:10 +0000 Subject: [PATCH] Assert the shipped heartbeat ramp, not just the panel's fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check:heartbeat proved the PANEL FALLBACK was intact and called that "default ramp unchanged". It is not the same property. The running app resolves the ramp with dashboard.config.js's map.* layered on top of that fallback, and the gate never read the real config: assertion F checked only that heartbeatThreshold was DECLARED, never what it was SET TO. Measured consequence, reproduced before fixing: sed -i 's/heartbeatThreshold: 150,/heartbeatThreshold: 999,/' \ plugin-designs/research-dashboard/dashboard.config.js node internal/check-heartbeat-threshold.mjs # exit 0 The shipped map could silently change its ramp while the gate reported it unchanged. Now exits 1, naming the key and its value. Adds assertion A2: resolve the ramp the way the running app does — panel defaults with the shipped map.* applied — and require it to equal the historical 5/20/60/150/Infinity with its labels. A (fallback) and A2 (shipped) share one comparison helper so they cannot drift. A2 also rejects a shipped heartbeatMode other than 'fixed': a data-derived ramp is right for an unknown dataset and wrong for this one, where 150 is deliberate. Changing the shipped ramp is now a deliberate act that must update HISTORICAL in the same commit. dashboard.config.js is read by brace-matching its `map:` literal rather than import(): it pulls in @map/* aliases and a JSON module, so importing it would need Vite's resolver — a dependency that buys nothing for a bag of scalars. The self-test now runs three cases, each required to fire the assertion that exists for it, and poisons in memory so a crash cannot leave the tree edited: [literal] resolver replaced by the old array -> must fire B [shipped-default] shipped heartbeatThreshold changed -> must fire A2 [shipped-mode] shipped heartbeatMode flipped to quantile -> must fire A2 The header previously claimed the gate was "negative-tested" on the strength of the first case alone. True of that case, misleading overall, so it now states per-case coverage and what is NOT covered (colour contrast, and whether the map paints at all). Testing that the FEATURE works — a configured threshold moves the edge — is not testing that the REGRESSION is caught, and only the second protects the requirement that today's map must not change. --- internal/check-heartbeat-threshold.mjs | 267 +++++++++++++++++++------ package.json | 4 +- 2 files changed, 208 insertions(+), 63 deletions(-) diff --git a/internal/check-heartbeat-threshold.mjs b/internal/check-heartbeat-threshold.mjs index d8c029f42..ab811a63e 100644 --- a/internal/check-heartbeat-threshold.mjs +++ b/internal/check-heartbeat-threshold.mjs @@ -25,34 +25,58 @@ * today's rendering. And it false-NEGATIVES: a re-hard-coding that used 200, or that hard-coded * only the three lower edges, would sail straight through. Substring presence is not the property. * - * The property is: DOES CHANGING THE CONFIG CHANGE THE MAP? So this gate EXECUTES the resolver - * extracted from the live SFC and asserts the bins actually MOVE when the config moves. A literal - * cannot pass that, whatever numbers it uses. + * The property is TWO-SIDED, and getting only one side is how this gate was wrong once already: + * 1. DOES CHANGING THE CONFIG CHANGE THE MAP? (configurability — assertion B) + * 2. DOES THE SHIPPED CONFIG STILL RENDER TODAY'S MAP? (no regression — assertion A2) + * So this gate EXECUTES the resolver extracted from the live SFC, against BOTH synthetic configs + * and the REAL dashboard.config.js. A literal fails (1); an edited shipped default fails (2). * * ASSERTIONS - * A. DEFAULT IS UNCHANGED — with NO config, the bin assignment over the committed fixture is - * EXACTLY the historical ramp (edges 5/20/60/150/Infinity, labels included). This is the - * non-negotiable half: configurability must not have cost us today's map. - * B. THE THRESHOLD IS LIVE — setting heartbeatThreshold to 40 and to 500 must MOVE the top edge - * to exactly that number and must RE-BIN the fixture's countries. This is the assertion a - * re-hard-coded literal fails. - * C. THE LEGEND FOLLOWS THE EDGES — the top label must read "+". A moved edge with a - * stale '150+' legend is confidently wrong, which is worse than no legend. - * D. NO DEAD BINS — every bin must be reachable (`colorFor` tests `count < upTo`, so an edge of - * 1 creates a band no count can enter: the ramp paints 4 of its 5 colours while the legend - * advertises 5). Found live during this card at threshold 40, and again in quantile mode. - * E. EDGES ARE STRICTLY ASCENDING — a non-monotonic ramp lets an earlier bin shadow a later one. - * F. THE CONFIG DECLARES IT — dashboard.config.js must carry map.heartbeatThreshold, so the - * knob has a documented home rather than living only as a defaulted inject. + * A. THE PANEL'S FALLBACK IS INTACT — with NO config at all, the resolver yields EXACTLY the + * historical ramp (edges 5/20/60/150/Infinity, labels included). + * A2. THE SHIPPED CONFIGURATION RENDERS THAT SAME RAMP — resolved the way the RUNNING APP does, + * with dashboard.config.js's map.* layered on top of the panel defaults. ★ A2 IS THE + * ASSERTION THAT PROTECTS "TODAY'S MAP MUST NOT CHANGE", AND IT WAS MISSING AT FIRST: A + * alone proves only the FALLBACK, which is strictly weaker, and F below proves only that the + * key is DECLARED, not what it is SET TO. Measured consequence — editing the shipped config + * to heartbeatThreshold:999 changed the real map while this gate printed "default ramp + * unchanged" and exited 0. Changing the shipped ramp is now a deliberate act that must also + * update HISTORICAL below, in the same commit. + * B. THE THRESHOLD IS LIVE — setting heartbeatThreshold to 40 and to 500 must MOVE the top edge + * to exactly that number and must RE-BIN the fixture's countries. This is the assertion a + * re-hard-coded literal fails. + * C. THE LEGEND FOLLOWS THE EDGES — the top label must CONTAIN "+" and name the + * heartbeat zone. A moved edge with a stale '150+' legend is confidently wrong, which is + * worse than no legend. Matched by property, not equality — see the note on HISTORICAL. + * D. NO DEAD BINS — every bin must be reachable (`colorFor` tests `count < upTo`, so an edge of + * 1 creates a band no count can enter: the ramp paints 4 of its 5 colours while the legend + * advertises 5). Found live during this card at threshold 40, and again in quantile mode. + * E. EDGES ARE STRICTLY ASCENDING — a non-monotonic ramp lets an earlier bin shadow a later one. + * F. THE CONFIG DECLARES IT — dashboard.config.js must carry map.heartbeatThreshold. PRESENCE + * ONLY; the VALUE is A2's job. These are separate on purpose, because conflating them is + * exactly how the 999 regression shipped past this gate. * * ── DATA ──────────────────────────────────────────────────────────────────────────────────── * internal/fixtures/facet-one-field-rows.json — 114 REAL rows (59 countries). Not synthetic: this * repo deleted a synthetic fallback precisely because it produced confident wrong answers. * - * ── NEGATIVE TEST ─────────────────────────────────────────────────────────────────────────── - * `bun run check:heartbeat:self-test` re-hard-codes the ramp IN MEMORY (the exact regression: the - * resolver replaced by the old literal array) and requires THIS gate to REJECT it. A gate that - * cannot fail is worse than no gate — this repo has shipped one. + * ── NEGATIVE TESTS, AND EXACTLY WHAT THEY COVER ───────────────────────────────────────────── + * `bun run check:heartbeat:self-test` runs THREE cases, each requiring the assertion that exists + * for it to fire. Stated precisely so the next reader does not over-trust this gate: + * + * [literal] the resolver is replaced by the old hard-coded array → must fire B + * [shipped-default] dashboard.config.js's heartbeatThreshold is changed → must fire A2 + * [shipped-mode] dashboard.config.js flips heartbeatMode to 'quantile' → must fire A2 + * + * ★ THE FIRST VERSION OF THIS GATE SHIPPED WITH [literal] ONLY, and its header said it had been + * "negative-tested" — true of that case, misleading overall. Testing that THE FEATURE WORKS (a + * configured threshold moves the edge) is NOT testing that THE REGRESSION IS CAUGHT (the default + * silently changing), and only the second protects the Driver. One negative test per regression + * claimed. A gate that cannot fail is worse than no gate — this repo has shipped one. + * + * NOT COVERED, deliberately: this gate reasons about BIN ASSIGNMENT and LABELS only. It says + * nothing about the colours themselves (contrast against the pin is argued and measured in + * DashboardMapPanel.vue) and nothing about whether the map actually PAINTS (see verify:map-paint). */ import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' @@ -105,6 +129,40 @@ function makeResolver(mapCfg, srcOverride) { return fn({ map: mapCfg }) } +// ── the SHIPPED map config ────────────────────────────────────────────────────────────────── +// Read as SOURCE TEXT, not `import()`. dashboard.config.js imports '@map/…' aliases and a JSON +// module, so importing it from a bare node gate would need Vite's resolver — a dependency that +// would make this gate fragile for no benefit. The `map:` literal is a self-contained bag of +// scalars, so brace-matching and evaluating just that block reads the real shipped values with +// nothing else to resolve. +// Set by the self-test to simulate an edited dashboard.config.js WITHOUT touching the file — a +// self-test that mutated the repo could leave the tree poisoned if it crashed mid-run. +let SHIPPED_CONFIG_OVERRIDE = null +function shippedMapConfig() { + if (SHIPPED_CONFIG_OVERRIDE) return SHIPPED_CONFIG_OVERRIDE + const src = readFileSync(CONFIG, 'utf8') + const at = src.search(/^\s{2}map:\s*\{/m) + if (at < 0) { + fail('A2. Could not find the top-level `map: {` block in dashboard.config.js.\n' + + ' This gate must read the SHIPPED map config; if the block moved, update this gate.') + return null + } + let depth = 0 + const start = src.indexOf('{', at) + let i = start + for (; i < src.length; i++) { + if (src[i] === '{') depth++ + else if (src[i] === '}') { depth--; if (!depth) { i++; break } } + } + try { + // eslint-disable-next-line no-eval + return eval('(' + src.slice(start, i) + ')') + } catch (e) { + fail(`A2. The \`map\` block in dashboard.config.js did not evaluate: ${e.message}`) + return null + } +} + // ── the fixture, binned ───────────────────────────────────────────────────────────────────── function countryCounts() { const rows = JSON.parse(readFileSync(FIXTURE, 'utf8')) @@ -134,22 +192,55 @@ function run(srcOverride) { const vals = [...counts.values()] const resolve0 = (cfg) => makeResolver(cfg, srcOverride)(vals) - // A. DEFAULT UNCHANGED - const def = resolve0({}) - HISTORICAL.forEach((exp, i) => { - const got = def[i] - if (!got || got.upTo !== exp.upTo) { - fail(`A. DEFAULT RAMP MOVED at bin ${i}: expected upTo=${exp.upTo}, got ${got ? got.upTo : '(missing)'}.\n` + - ' With no config supplied the dashboard must render EXACTLY as it always has.') - } else if (exp.l === null) { - if (!topLabelOk(got.l, 150)) { - fail(`A. DEFAULT TOP LABEL is "${got.l}" — it must contain "150+" and name the heartbeat zone.`) + // Compare a resolved ramp against the historical one. Shared by A and A2 so the two can never + // drift apart — the whole defect A2 fixes was one path being asserted and the other not. + const expectHistorical = (bins, tag, why) => { + HISTORICAL.forEach((exp, i) => { + const got = bins[i] + if (!got || got.upTo !== exp.upTo) { + fail(`${tag}. RAMP MOVED at bin ${i}: expected upTo=${exp.upTo}, got ${got ? got.upTo : '(missing)'}.\n ${why}`) + } else if (exp.l === null) { + if (!topLabelOk(got.l, 150)) { + fail(`${tag}. TOP LABEL is "${got.l}" — it must contain "150+" and name the heartbeat zone.\n ${why}`) + } + } else if (got.l !== exp.l) { + fail(`${tag}. LEGEND LABEL MOVED at bin ${i}: expected "${exp.l}", got "${got.l}".\n ${why}`) + } + }) + if (bins.length !== HISTORICAL.length) fail(`${tag}. RAMP has ${bins.length} bins, expected ${HISTORICAL.length}.\n ${why}`) + } + + // A. THE PANEL'S FALLBACK IS INTACT — with NO config supplied at all. + const def = resolve0({}) + expectHistorical(def, 'A', 'With no config supplied the panel must fall back to EXACTLY the historical ramp.') + + // ── A2. THE SHIPPED CONFIGURATION RENDERS THE HISTORICAL RAMP ─────────────────────────────── + // ★ THIS IS THE ASSERTION THAT PROTECTS THE DRIVER, AND IT WAS MISSING. A alone proves the + // PANEL'S FALLBACK is intact — a strictly weaker property than "the map still looks the same", + // because the running app resolves with dashboard.config.js's map.* layered ON TOP of that + // fallback. Assertion F only checked that heartbeatThreshold was DECLARED, never what it was + // SET TO. So editing the shipped config to 999 changed the real map while this gate printed + // "default ramp unchanged" and exited 0. Measured, reproduced, and the reason A2 exists. + // + // A2 resolves the ramp THE WAY THE RUNNING APP DOES and requires it to equal the historical one. + // Changing the shipped default is now a deliberate act that must also update HISTORICAL here. + if (srcOverride === undefined) { + const shipped = shippedMapConfig() + if (shipped) { + expectHistorical( + makeResolver(shipped, srcOverride)(vals), 'A2', + 'This is the ramp dashboard.config.js ACTUALLY SHIPS (map.heartbeatThreshold=' + + `${shipped.heartbeatThreshold}, binThresholds=${JSON.stringify(shipped.binThresholds)}, ` + + `mode=${shipped.heartbeatMode}). Today\'s map must not change. If this move is intended, ` + + 'update HISTORICAL in this gate in the same commit.', + ) + if (shipped.heartbeatMode && shipped.heartbeatMode !== 'fixed') { + fail(`A2. dashboard.config.js ships heartbeatMode='${shipped.heartbeatMode}'.\n` + + " 'fixed' is the shipped default on purpose: a data-derived ramp changes per dataset,\n" + + ' which is right for an unknown dataset and wrong for this one, where 150 is deliberate.') } - } else if (got.l !== exp.l) { - fail(`A. DEFAULT LEGEND LABEL MOVED at bin ${i}: expected "${exp.l}", got "${got.l}".`) } - }) - if (def.length !== HISTORICAL.length) fail(`A. DEFAULT RAMP has ${def.length} bins, expected ${HISTORICAL.length}.`) + } const baseline = assignment(def, counts) @@ -183,7 +274,10 @@ function run(srcOverride) { }) } - // F. THE CONFIG DECLARES IT + // F. THE CONFIG DECLARES IT. + // NOTE this is a PRESENCE check only, and presence is NOT value — A2 is what asserts the value. + // Keeping them separate (and saying so) is deliberate: F alone once let heartbeatThreshold:999 + // ship while this gate printed "default ramp unchanged". const cfgSrc = readFileSync(CONFIG, 'utf8') if (!/heartbeatThreshold\s*:/.test(cfgSrc)) { fail('F. dashboard.config.js does not declare map.heartbeatThreshold.\n' + @@ -192,32 +286,82 @@ function run(srcOverride) { return errors.slice() } -// ── NEGATIVE TEST ─────────────────────────────────────────────────────────────────────────── -// Re-hard-code the ramp exactly as it was before this card, and require the gate to REJECT it. +// ── NEGATIVE TESTS ────────────────────────────────────────────────────────────────────────── +// ★ TWO CASES, BECAUSE THERE ARE TWO DISTINCT REGRESSIONS AND THEY ARE NOT THE SAME ASSERTION. +// This gate originally shipped with case 1 only, and its header claimed it was "negative-tested". +// That was true of case 1 and MISLEADING overall: case 2 — the one that actually protects the +// Driver's "today's map must not change" requirement — was unasserted, and a one-line edit to +// dashboard.config.js changed the shipped ramp while the gate printed PASS. Verified by +// reproducing it, not by reasoning about it. +// +// The general shape, worth more than either case: testing that THE FEATURE WORKS (a configured +// threshold moves the edge) is not testing that THE REGRESSION IS CAUGHT (the default silently +// changing). A gate needs a negative test per regression it claims to prevent. +const CASES = [ + { + id: 'literal', + what: 'the resolver is replaced by a re-hard-coded literal ramp (configurability removed)', + mustFire: 'B', + apply: () => ({ + srcOverride: ` + const BIN_COLORS = ['#f8e0da', '#fccec4', '#febbad', '#ffa797', '#ff9380'] + function resolveBins() { + return [ + { upTo: 5, c: BIN_COLORS[0], l: '1–4' }, { upTo: 20, c: BIN_COLORS[1], l: '5–19' }, + { upTo: 60, c: BIN_COLORS[2], l: '20–59' }, { upTo: 150, c: BIN_COLORS[3], l: '60–149' }, + { upTo: Infinity, c: BIN_COLORS[4], l: '150+' }, + ] + } + `, + }), + }, + { + id: 'shipped-default', + what: "dashboard.config.js's shipped map.heartbeatThreshold is changed (today's map moves)", + mustFire: 'A2', + // Simulates `sed -i 's/heartbeatThreshold: 150,/heartbeatThreshold: 999,/'` WITHOUT editing + // the file, so a crash mid-self-test cannot leave the repo poisoned. + apply: () => { + SHIPPED_CONFIG_OVERRIDE = { mapStyle: 'paper', heartbeatThreshold: 999, binThresholds: [5, 20, 60], heartbeatMode: 'fixed', heartbeatQuantile: 0.1 } + return {} + }, + }, + { + id: 'shipped-mode', + what: "dashboard.config.js flips heartbeatMode to 'quantile' (ramp becomes data-derived)", + mustFire: 'A2', + apply: () => { + SHIPPED_CONFIG_OVERRIDE = { mapStyle: 'paper', heartbeatThreshold: 150, binThresholds: [5, 20, 60], heartbeatMode: 'quantile', heartbeatQuantile: 0.1 } + return {} + }, + }, +] + if (SELF_TEST) { - const POISON = ` - const BIN_COLORS = ['#f8e0da', '#fccec4', '#febbad', '#ffa797', '#ff9380'] - function resolveBins() { - return [ - { upTo: 5, c: BIN_COLORS[0], l: '1–4' }, { upTo: 20, c: BIN_COLORS[1], l: '5–19' }, - { upTo: 60, c: BIN_COLORS[2], l: '20–59' }, { upTo: 150, c: BIN_COLORS[3], l: '60–149' }, - { upTo: Infinity, c: BIN_COLORS[4], l: '150+' }, - ] + let bad = 0 + for (const c of CASES) { + SHIPPED_CONFIG_OVERRIDE = null + const { srcOverride } = c.apply() + const got = run(srcOverride) + SHIPPED_CONFIG_OVERRIDE = null + + if (!got.length) { + console.error(`SELF-TEST FAILED [${c.id}]: the gate ACCEPTED ${c.what}.\n` + + ' A gate that cannot fail is worse than no gate.') + bad++ + continue } - ` - const got = run(POISON) - if (!got.length) { - console.error('SELF-TEST FAILED: the gate ACCEPTED a re-hard-coded ramp. This gate cannot fail, so it is worse than no gate.') - process.exit(1) + if (!got.some((e) => e.startsWith(c.mustFire + '.'))) { + console.error(`SELF-TEST FAILED [${c.id}]: rejected, but NOT via assertion ${c.mustFire}, which is the one\n` + + ` that exists for this regression. Rejections seen:\n ` + got.map((e) => e.split('\n')[0]).join('\n ')) + bad++ + continue + } + console.log(` ✓ [${c.id}] REJECTED via ${c.mustFire} — ${c.what}`) + got.forEach((e) => console.log(' · ' + e.split('\n')[0])) } - const sawB = got.some((e) => e.startsWith('B.')) - if (!sawB) { - console.error('SELF-TEST FAILED: the gate rejected the poison, but not via assertion B (the threshold-is-live check).\n' + - 'Rejections seen:\n ' + got.join('\n ')) - process.exit(1) - } - console.log(`check:heartbeat SELF-TEST PASS — a re-hard-coded ramp is REJECTED (${got.length} assertion(s) fired, including B).`) - got.forEach((e) => console.log(' · ' + e.split('\n')[0])) + if (bad) { console.error(`\ncheck:heartbeat SELF-TEST FAILED — ${bad} of ${CASES.length} case(s) not caught.`); process.exit(1) } + console.log(`\ncheck:heartbeat SELF-TEST PASS — all ${CASES.length} regression case(s) rejected by their own assertion.`) process.exit(0) } @@ -227,5 +371,6 @@ if (found.length) { found.forEach((e) => console.error(' ✗ ' + e + '\n')) process.exit(1) } -console.log('check:heartbeat PASS — default ramp unchanged (5/20/60/150/∞ over 59 real countries); ' + - 'heartbeatThreshold 40 and 500 both move the edge, relabel the legend, and re-bin the fixture; no dead or non-monotonic bins.') +console.log('check:heartbeat PASS — the SHIPPED dashboard.config.js resolves to the historical ramp ' + + '(5/20/60/150/∞ over 59 real countries) and so does the panel fallback; heartbeatThreshold 40 and 500 ' + + 'both move the edge, relabel the legend, and re-bin the fixture; no dead or non-monotonic bins.') diff --git a/package.json b/package.json index f94a28f97..c5f0c0add 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,8 @@ "check:facet-one-field:self-test": "node internal/check-facet-one-field.mjs --self-test", "check:facet-changes-map": "node internal/check-facet-changes-map.mjs # a facet selection must CHANGE THE DOTS — and the dots must SAY what they mean. Asserts (1) the rd-bubbles feature set changes by count AND identity, (2) every surviving dot satisfies the selected level, (3) while a prayer filter is active circle-color is a DATA-DRIVEN EXPRESSION reading peoplePraying, not a flat literal. Assertion 3 is the one that matters: the prayer filter ALREADY narrowed 2107->689 correctly while the Driver reported it broken, because every dot painted the same orange. Browser gate (needs `bun run dev` on 5173, or BASE_URL/PROBE_PATH at a served tree), so NOT in build:unlocked. run the `check:facet-changes-map:self-test` script for the NEGATIVE test.", "check:facet-changes-map:self-test": "node internal/check-facet-changes-map.mjs --self-test # reverts the fix IN THE LIVE PAGE and requires the gate to FAIL. SELF_TEST_MODE=colour (default, isolates the colour assertion — the reported bug) | props | both | frozen (blocks the source update, isolating the set assertion). All four modes verified caught.", - "check:heartbeat": "node internal/check-heartbeat-threshold.mjs # the HEARTBEAT ZONE threshold (the choropleth's top bin) must come from config, not a literal. The old 5/20/60/150 array was a DATASET assumption (~2000 people groups) wearing the costume of a constant: on a 200-group dataset every country falls in the bottom bin and the map renders uniformly pale — correct code, no crash, no error, meaningless map, invisible to every other gate. EXECUTES the resolver from the live SFC and asserts the bins MOVE when the config moves, because a grep for '150' both false-positives (it is the legitimate default) and false-negatives (a re-hard-coding at 200 sails through). run check:heartbeat:self-test for the NEGATIVE test.", - "check:heartbeat:self-test": "node internal/check-heartbeat-threshold.mjs --self-test # re-hard-codes the ramp IN MEMORY and requires check:heartbeat to REJECT it (asserting specifically that assertion B, threshold-is-live, fires). A gate that cannot fail is worse than no gate.", + "check:heartbeat": "node internal/check-heartbeat-threshold.mjs # the HEARTBEAT ZONE threshold (the choropleth's top bin) must come from config AND the shipped config must still render today's ramp. Two sides: (B) a configured threshold must MOVE the edge — catches re-hard-coding; (A2) dashboard.config.js's own map.* must still resolve to 5/20/60/150/Infinity — catches the shipped default silently changing. A2 was MISSING at first and heartbeatThreshold:999 shipped past this gate printing 'default ramp unchanged'. A grep for '150' cannot do either job: it false-positives on the legitimate default and false-negatives on a re-hard-coding at 200. run check:heartbeat:self-test for the NEGATIVE tests (3 cases).", + "check:heartbeat:self-test": "node internal/check-heartbeat-threshold.mjs --self-test # THREE regression cases, each required to fire ITS OWN assertion: [literal] resolver replaced by the old hard-coded array -> B; [shipped-default] dashboard.config.js heartbeatThreshold changed -> A2; [shipped-mode] heartbeatMode flipped to quantile -> A2. Poisons IN MEMORY, never the file. Testing that the FEATURE works is not testing that the REGRESSION is caught — this gate shipped with only the first and missed the second.", "build:unlocked": "bun run check:template-literal && bun run check:mjs-parse && bun run check:sfc-parse && bun run clean && bun run token && for d in plugin-designs/*/; do BUNDLE=$(basename \"$d\") bun x vite build || exit 1; done && bun run tree && bun run check:drop && bun run check:parse && bun run verify && bun run check:sri && bun run check:public-hygiene && bun run check:label-joins && bun run check:facet-cascade && bun run check:facet-one-field && bun run check:heartbeat && bun run check:kpi-unit && bun run check:composite-reg && bun run check:descriptions && bun run check:no-abs-paths && bun run check:no-codename && bun run check:dep-names && bun run check:composite-srcs && bun run check:embed-targets && bun run check:nested-token && bun run gallery", "check:template-literal": "node internal/template-literal-check.mjs", "verify:map-paint": "node internal/verify-map-paint.mjs",