Hold the gap table to the split it publishes

Last tick the map and the exported gap table bucketed the same 1,845
groups differently — the table tested sign language first, the map
tested private-use first, so 47 Deaf groups landed in different
buckets and the Deaf share read 136 in one place and 183 in the other.
Both were internally consistent and told different stories, which is
worse than one being plainly wrong: a reader comparing them cannot
tell which to believe. I fixed it by hand, which fixes today only.

Clause O holds three things a reader depends on: every tier-0 group
appears in the table exactly once, every row carries a kind, and a
sign language is ALWAYS labelled as one rather than as an unindexable
code — because being Deaf is the actionable fact, and every
private-use code in this gap belongs to a sign language anyway.

Checked against the real defect, not a synthetic one: putting the
precedence back the wrong way round makes it fail with "47
sign-language row(s) are not labelled as sign languages (e.g. qcd,
qiq, qns) — the Deaf share of the gap will read low". Poison is 15/15.
This commit is contained in:
doxa-maps-88655c70 2026-09-25 00:24:29 +00:00
parent 2046f2febd
commit 9d1a0acd9e

View file

@ -55,6 +55,7 @@ const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const POISON = process.argv.includes('--poison')
let poisonStale = false
let poisonTable = false
let poisonGapKind = false
const read = (p) => JSON.parse(readFileSync(resolve(REPO, p), 'utf8'))
let pins, table, verif
@ -93,6 +94,7 @@ if (POISON) {
poisonStale = true // L
codes.zzm = { l: 'Claims a pool nobody swept for it', ck: 'ebible, grn', s: 'https://ebible.org/zzm/' } // M
poisonTable = true // N
poisonGapKind = true // O
}
const SPECIAL = (c) => c === 'und' || c === 'mul' || c === 'mis' || c === 'zxx' || (c >= 'qaa' && c <= 'qtz')
@ -336,13 +338,45 @@ if (verif && verif.links) {
}
}
/* ── O: every gap row is classified, and the classification means what it says ───────────
"1,845 groups have nothing" is four situations wearing one number, and the map and the
exported table each state the split. They did not agree: the table tested sign language
first, the map tested private-use first, so 47 Deaf groups landed in different buckets and
the Deaf share read 136 in one place and 183 in the other. Both were internally consistent
and they told different stories, which is worse than one of them simply being wrong.
This holds the table to three things a reader depends on: every tier-0 group appears
exactly once, every row carries a kind, and a sign language is ALWAYS labelled as one —
never as an unindexable code — because being Deaf is the actionable fact and every
private-use code in this gap belongs to a sign language anyway. */
{
let t
try { t = read('data/tables/gospel-gaps.json') } catch { t = null }
if (t) {
const tier0 = rows.filter((r) => r[6] === 0).length
const gapRows = (t.rows || []).length
if (tier0 !== gapRows) {
add('O', `the gap table has ${gapRows} row(s) but ${tier0} pin(s) are tier 0 — one of them is stale`)
}
const unclassified = (t.rows || []).filter((r) => (poisonGapKind ? true : !r.gap_kind)).length
if (unclassified) add('O', `${unclassified} gap row(s) carry no gap_kind`)
const mislabelled = (t.rows || []).filter((r) => {
const c = codes[r.iso639_3]
return c && c.sg === 1 && !/sign language/.test(r.gap_kind || '')
})
if (mislabelled.length) {
add('O', `${mislabelled.length} sign-language row(s) are not labelled as sign languages (e.g. ${mislabelled.slice(0, 3).map((r) => r.iso639_3).join(', ')}) — the Deaf share of the gap will read low`)
}
}
}
const clauses = new Set(fails.map((f) => f.slice(3, 4)))
if (POISON) {
// D is included deliberately: it is the clause that would have caught the 506
// resources dropped from a table titled "every link", and the most valuable clause is
// the worst one to leave unproven. The injected codes above (und, zzq) are absent from
// the table, so they poison D as well as A and B.
const want = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N']
const want = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O']
const caught = want.filter((c) => clauses.has(c))
console.log(`COVERAGE-CONSISTENT --poison: ${caught.length}/${want.length} clause(s) caught their injected defect (${caught.join(',')})`)
if (caught.length !== want.length) {
@ -370,3 +404,4 @@ console.log(' K every shipped link is verified, and none known-broken ships')
console.log(' L the verification record is current, not merely consistent')
console.log(' M a pool named as checked was really searched for that code')
console.log(' N a table\'s has-anything column agrees with the pin tier')
console.log(' O every gap row is classified, and a sign language says so')