Set an alarm on the zone/region id collision, which data currently hides

gospel found a latent ambiguity in the emit contract while wiring, and checked
it instead of building past it. selectedId emits one bare string with no kind,
and region ids and zone ids share a namespace. Re-measured here rather than
taken on report: 33 zone ids, 7 region ids, and the intersection is exactly
europe, whose region is itself. So the string "europe" cannot tell a consumer
whether the Europe region row was clicked, meaning every European zone, or the
Europe zone leaf, meaning that one.

It is not live today, and only for a reason that is a coincidence rather than a
design: region europe contains exactly one zone, so both readings give the same
set. It goes live the day a second European zone is filed, and then it fails the
quiet way this whole file exists to catch — a smaller result, no error, no empty
state.

So this is an alarm, not a fix. The consumer builds the tree, so only the
consumer knows which rows are regions, and gospel is namespacing theirs. Two
arms: the colliding set is pinned and says re-derive if it changes, and each
colliding id's region must still contain exactly one zone. The second fails with
the instruction rather than the symptom, naming the zones and telling whoever
hits it to namespace their region rows.

Both falsified before trusting them: filing a second European zone fires the
ambiguity arm with both zones named, and adding a second colliding id fires the
drift arm with both ids listed. Data restored, clean diff.

The same note is on selectedId, because the next consumer to build a tree from
this zone data inherits the collision, and the doc as written invited it.
This commit is contained in:
Doxa Maps 2026-09-28 14:24:17 +00:00
parent 53d3862627
commit 493bbf45b8
2 changed files with 43 additions and 0 deletions

View file

@ -126,5 +126,32 @@ ok(realGap.join(',') === MISSING.join(','),
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')
// ── the id ambiguity that is masked by a data coincidence (gospel, END-GOS-VLGC) ────────────────
// ★ REGION IDS AND ZONE IDS SHARE ONE NAMESPACE. selectedId emits one bare string with no kind, and
// the two sets OVERLAP in the shipped data. gospel found it while wiring and I re-measured it here
// rather than taking the report: 33 zone ids, 7 region ids, intersection exactly {europe}, whose
// region is itself. So `europe` cannot tell a consumer whether the Europe REGION row was clicked
// (expand to every European zone) or the Europe ZONE leaf (filter to that one).
//
// It is not live today ONLY because region `europe` contains exactly one zone, so both readings give
// the same set. That is a coincidence in the data, not a property of the design, and it is the same
// shape as the inert arm above: something green because reality currently happens to agree with it.
// The arm below is therefore the alarm, not the fix — the consumer builds the tree, so only the
// consumer can namespace its region rows, and gospel is doing that.
const zonesAll = new Set(Object.keys(regionOf))
const regionsAll = new Set(Object.values(regionOf).map(String))
const collide = [...zonesAll].filter((id) => regionsAll.has(id)).sort()
ok(collide.join(',') === 'europe',
collide.join(',') === 'europe'
? 'region ids and zone ids share one namespace and collide on exactly {europe} — measured, not assumed'
: `THE COLLIDING IDS CHANGED — re-derive. shipped now: [${collide.join(', ') || 'none'}], this check asserts: [europe]. Every id in both sets is one a consumer cannot disambiguate from the emitted string alone.`)
for (const id of collide) {
const members = Object.keys(regionOf).filter((z) => String(regionOf[z]) === id)
ok(members.length === 1,
members.length === 1
? `and region '${id}' still contains exactly one zone, so both readings of '${id}' give the same set — the ambiguity is MASKED, not absent`
: `THE '${id}' AMBIGUITY IS NOW LIVE: region '${id}' contains ${members.length} zones (${members.join(', ')}), so emitting the bare id '${id}' means either that whole region or the single zone of the same name, and the two now differ. Nothing on screen would say which — the reader just gets a smaller set. The consumer that BUILDS the tree must namespace its region rows (region:${id}) and leave leaves bare; this component cannot know which rows are regions.`)
}
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)

View file

@ -64,6 +64,22 @@ export function withUnassigned(nodes, allZoneIds, label = 'Unassigned') {
* the union of its zones). The consumer expands a region id to its zones using the zone data it
* already holds — which is also where the diacritic-folding join lives, so there is no second copy
* of it here to drift.
*
* ★ REGION IDS AND ZONE IDS SHARE ONE NAMESPACE, SO NAMESPACE YOUR REGION ROWS (e.g. `region:europe`,
* leaves left bare). One bare string on one channel carries no kind, and the two id sets overlap in
* the shipped data: measured against zone-regions.json there are 33 zone ids and 7 region ids, and
* the intersection is exactly {europe}, whose region is itself. So `europe` alone cannot tell a
* consumer whether the Europe REGION was clicked (expand to every European zone) or the Europe ZONE
* leaf (filter to that one).
*
* It is not live TODAY only because region `europe` currently contains exactly one zone, so both
* readings produce the same set — the bug is masked by a data coincidence, not absent. It goes live
* the day a second European zone is filed, and then it fails the quiet way: a smaller result, no
* error, no empty state. check:list-selection FAILS ON PURPOSE at that moment.
*
* This component cannot fix it: the CONSUMER builds the tree, so only the consumer knows which rows
* are regions. Found by gospel-plugin while wiring, who is namespacing their region rows; written
* here because the next consumer to build a tree from the same zone data inherits the same collision.
*/
export function selectedId(node) {
return node && node.id != null ? String(node.id) : null