doxa-maps/internal/check-list-selection.mjs
Doxa Maps 926cc1fe89 Measure the zone gap in the shipped data, not only in the fixture
Every assertion here ran against a fixture. That proves the module behaves and
proves the fixture models a gap; it does not prove the gap still exists in the
data we ship. So the negative control could have stayed green forever while the
bucket quietly became dead code — or while a fifth zone fell out of the region
tree and nobody heard. Evidence that lives somewhere other than the thing it
describes has already stopped being evidence, and looks identical.

Three arms now read the real files. The gap between the region tree's source and
the joinable set is measured, and it is exactly the four this check names:
guizhou, xinjiang, bihar, madhya-pradesh. xinjiang is asserted specifically —
joinable in admin-to-list, absent from zone-regions, so its hundred groups are
reachable only through the bucket.

Two of the arms fail ON PURPOSE rather than going quiet. If every joinable zone
becomes filed, the check refuses to pass and says to re-derive the bucket or
delete it with its component, because an arm guarding nothing is not a pass. If
the gap moves in either direction it prints both sets and names the zone that
changed: one added is groups that just became unreachable, one removed means the
fixture is stale.

Both were falsified before being trusted. Filing all four turns the inert guard
red; dropping gujarat from the region tree turns the drift arm red with gujarat
named. Data restored, clean diff.

This deliberately does not resolve the three disagreeing lists — 33 filed, 12
joinable, 14 grouped, disagreeing in both directions. That is a data-ownership
question, and deciding it here would be the silent join we have refused. The
check only records the difference so it cannot drift unannounced.
2026-09-28 13:56:06 +00:00

130 lines
9.6 KiB
JavaScript

#!/usr/bin/env node
/**
* check-list-selection — what the vertical legend emits, and the zones a region tree forgets. Headless.
*
* ★ WHY THIS IS IN internal/ AND NOT _qa-cycle/. It began as a _qa-cycle probe, which is GITIGNORED
* in this repo (.gitignore:113) — so it ran green on my disk and would NOT have landed with the
* component. studio caught that while staging: "the 13 assertions are not in either commit". I had
* reported them as evidence for code that was about to ship untested, which is the same failure this
* whole file guards against — a green result measured somewhere other than where it matters.
* A probe proves something once. A registered check keeps proving it.
*/
// What the vertical legend emits, and the zones a region tree forgets.
//
// The second half is the one that matters most: four joinable UPG-100 zones are absent from
// zone-regions.json today, one of them the consumer's largest at 100 groups. A region-grouped tree
// drops them with no error and no empty state — just a shorter tree — so this asserts they survive.
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
import { leavesOf, zoneIdsIn, withUnassigned, selectedId, UNASSIGNED_ID } from '../library/components/legends/listSelection.js'
const DATA = join(dirname(fileURLToPath(import.meta.url)), '..', 'plugin-designs', 'my-upg-100-list', 'data')
const readJson = (f) => JSON.parse(readFileSync(join(DATA, f), 'utf8'))
let fails = 0
const ok = (c, m) => { console.log(` ${c ? 'ok ' : 'FAIL'} - ${m}`); if (!c) fails++ }
const TREE = [{
id: 'asia', label: 'Asia', depth: 0,
children: [
{ id: 'z1', label: 'Zone 1', zoneKey: 'Z1' },
{ id: 'sub', label: 'Sub', depth: 1, children: [{ id: 'z2' }, { id: 'z3' }] },
],
}]
// The real gap, named as it appears in the data.
const MISSING = [
'asia-china-guizhou-province',
'asia-china-xinjiang-uyghur-autonomous-region',
'asia-india-bihar',
'asia-india-madhya-pradesh',
]
ok(leavesOf(TREE[0]).map((l) => l.id).join(',') === 'z1,z2,z3', 'leavesOf walks NESTED children, not one level')
ok([...zoneIdsIn(TREE)].sort().join(',') === 'z1,z2,z3', 'zoneIdsIn collects every leaf id in the tree')
// ── the emit contract ───────────────────────────────────────────────────────────────────────────
ok(selectedId({ id: 'z1' }) === 'z1', 'a LEAF emits its zone id')
ok(selectedId({ id: 'asia' }) === 'asia', 'a REGION row emits the region id — the consumer expands it, where the join lives')
ok(selectedId(null) === null, 'and a CLEARED selection emits null, so a filter can be dropped')
ok(typeof selectedId({ id: 'z1' }) === 'string',
'the value is a STRING, so it survives ?zone=, profile-config and a deep link')
// ── never drop a zone ───────────────────────────────────────────────────────────────────────────
const all = ['z1', 'z2', 'z3', ...MISSING]
const withBucket = withUnassigned(TREE, all, 'Unassigned')
ok(withBucket.length === TREE.length + 1, 'zones absent from the tree get an explicit bucket')
const bucket = withBucket[withBucket.length - 1]
ok(bucket.id === UNASSIGNED_ID && bucket.children.length === 4, 'and all four missing zones are in it')
ok(bucket.children.map((c) => c.id).includes('asia-china-xinjiang-uyghur-autonomous-region'),
'including xinjiang — the consumer\'s largest zone at 100 groups, which a region tree loses silently')
ok(bucket.children.every((c) => c.id && c.zoneKey),
'each is a SELECTABLE leaf, not a lump — a reader wants xinjiang, not "the four nobody filed"')
ok(withUnassigned(TREE, ['z1', 'z2', 'z3']).length === TREE.length,
'CONTROL: no bucket appears when nothing is missing')
ok(withUnassigned(TREE, null).length === TREE.length && withUnassigned(TREE, []).length === TREE.length,
'CONTROL: omitting the zone set leaves the tree untouched, so this is additive')
// NEG CONTROL: the drop this exists to prevent must be detectable.
const naive = TREE // what a region-grouped tree gives you, unaugmented
ok(!zoneIdsIn(naive).has('asia-china-xinjiang-uyghur-autonomous-region'),
'NEG CONTROL: the unaugmented tree really does omit xinjiang, so the bucket is not decoration')
// ── a region sweep is NOT a zone sweep (gospel, END-GOS-VLG2) ───────────────────────────────────
// ★ The consequence of the bucket that neither of us should leave implicit: ticking every REGION
// still does not select every ZONE, because the four unfiled zones have no region BY DEFINITION.
// Nothing on screen would say so — the reader just gets a smaller total, which is the exact
// silent-wrong-answer shape the bucket exists to end. So if a "select all" is ever added it must
// sweep ZONES, not regions, or it inherits the bug. Pinned here so the wrong one cannot ship
// quietly; gospel pins the mirror of it on the expansion side.
const regionRows = withBucket.filter((n) => n.id !== UNASSIGNED_ID)
const byRegionSweep = zoneIdsIn(regionRows)
ok(byRegionSweep.size < all.length,
'selecting every REGION reaches fewer zones than exist — a region sweep is not a zone sweep')
ok(!byRegionSweep.has('asia-china-xinjiang-uyghur-autonomous-region'),
'and the 100 groups it misses are xinjiang\'s, so a "select all" over regions must be over zones')
ok(zoneIdsIn(withBucket).size === all.length,
'sweeping the WHOLE tree, bucket included, does reach all of them — what the bucket buys')
// ── a zone with no members stays SELECTABLE (gospel: 16 of 33 resolve today) ────────────────────
// Of the 33 zones, members resolve for 16; 5 await a wagf field and 9 have no selector anywhere.
// Those zones must render as EMPTY, not vanish — same reason as the bucket. A tree that hides what
// it cannot resolve tells the reader the zone does not exist, when the truth is that we cannot
// currently join it. Nothing here filters on member count, and this fails if anything starts to.
const EMPTYZONE = [{ id: 'asia', label: 'Asia', depth: 0, children: [{ id: 'z9', label: 'Zone 9', zoneKey: 'Z9', members: [] }] }]
ok(leavesOf(EMPTYZONE[0]).map((l) => l.id).join(',') === 'z9',
'a zero-member zone is still a LEAF, so a reader can select it and see an empty result')
ok(selectedId(leavesOf(EMPTYZONE[0])[0]) === 'z9', 'and it still emits its own id')
ok(withUnassigned(EMPTYZONE, ['z9']).length === EMPTYZONE.length,
'presence is by ID, not by member count, so an unresolvable zone is not ALSO dumped in the bucket')
// ── the gap is REAL TODAY, not just real in my fixture (gospel, END-GOS-VLG3) ───────────────────
// ★ EVERY ASSERTION ABOVE RUNS AGAINST A FIXTURE. That proves the MODULE behaves, and it proves the
// fixture models a gap — it does not prove the gap still exists in the data we ship. So the negative
// control above could stay green forever while the bucket quietly became dead code, or while a FIFTH
// zone went missing and nobody heard. That is the same failure in a new costume: evidence that lives
// somewhere other than where the thing it describes lives. gospel named it — find out where your
// evidence lives — after catching the identical shape in their own work.
//
// This does NOT resolve the three disagreeing lists (33 in zone-regions, 12 reachable via
// admin-to-list, 14 in groupings, disagreeing in BOTH directions). That is a data-ownership ruling
// for upg100 and the Driver, and picking a winner here would be exactly the silent join we have both
// refused. It only compares the region tree's own source against the joinable set and RECORDS the
// difference, so the disagreement cannot drift without someone being told.
const regionOf = readJson('zone-regions.json') // the region tree's source: zone id -> region
const adminToList = readJson('admin-to-list.json') // admin name -> zone id; its VALUES are joinable
const filed = new Set(Object.keys(regionOf))
const joinable = new Set(Object.values(adminToList).map(String))
const realGap = [...joinable].filter((id) => !filed.has(id)).sort()
ok(realGap.length > 0,
realGap.length > 0
? `the region tree really does omit ${realGap.length} joinable zone(s) TODAY — the bucket is load-bearing in the shipped data, not only in the fixture`
: 'INERT ARM: every joinable zone is now filed under a region, so this check no longer measures anything. That is not a pass. Re-derive the bucket against the current data or DELETE it and its component — do not leave a green arm guarding nothing.')
ok(realGap.join(',') === MISSING.join(','),
realGap.join(',') === MISSING.join(',')
? 'and it is exactly the four this check names, so the fixture above models the shipped data rather than a story about it'
: `THE GAP MOVED — re-derive. shipped now: [${realGap.join(', ')}] but this check asserts: [${MISSING.join(', ')}]. A zone ADDED here is groups that just became unreachable; a zone REMOVED got filed and the fixture is stale. Either way the numbers in the commit body are no longer true.`)
ok(joinable.has('asia-china-xinjiang-uyghur-autonomous-region') && !filed.has('asia-china-xinjiang-uyghur-autonomous-region'),
'xinjiang specifically: joinable in admin-to-list, absent from zone-regions — its 100 groups are reachable ONLY through the bucket')
console.log(fails ? `\nLIST-SELECTION: ${fails} FAIL(s)` : '\nLIST-SELECTION: serialisable ids out, and no joinable zone can be dropped')
process.exit(fails ? 1 : 0)