doxa-maps/internal/check-translator-contacts.mjs
Doxa Maps ca98c1fcf9
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Update from source 26f952c9
2026-09-26 08:52:57 +00:00

82 lines
4.6 KiB
JavaScript

/**
* check-translator-contacts.mjs — the credit line now LINKS the translating organisation, and
* this is the gate on that link. Static, sub-second, no browser.
*
* WHAT IT WOULD CATCH. A contact URL is a link this map stands behind, so it has to clear the
* same bar as a resource link and one more besides:
* 1. Every cr.u in the pin index is on an organisation the DOCTRINE SCREEN lists. The screen's
* pattern is read out of internal/verify-gospel-map.mjs, never re-typed here — two copies of
* an allowlist drift, and the copy that drifts is the one nobody is looking at.
* 2. Every cr.u was actually FETCHED: it must appear in translator-org-links.json as `verified`.
* An org the verifier could not reach must stay a name.
* 3. No contact host is also serving a RESOURCE. wycliffe.org is a fine place to write to and
* is not a publisher this map reads text from; if one ever appears as a scripture or film
* link, that is a new publisher arriving sideways.
* 4. The leaf RENDERS it as a link. The data can carry 838 URLs while the template shows none.
* Self-test: `--poison` injects a JW contact, an unverified org, and a leaf with the anchor
* removed, and every arm must fail.
*/
import { readFileSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const HERE = dirname(fileURLToPath(import.meta.url))
const REPO = resolve(HERE, '..')
const POISON = process.argv.includes('--poison')
const idx = JSON.parse(readFileSync(resolve(REPO, 'data/tables/people-group-pins.json'), 'utf8'))
const orgs = JSON.parse(readFileSync(resolve(REPO, 'data/tables/translator-org-links.json'), 'utf8')).orgs || {}
const gate = readFileSync(resolve(REPO, 'internal/verify-gospel-map.mjs'), 'utf8')
let leaf = readFileSync(resolve(REPO, 'plugin-designs/gospel-resources/resources-map.vue'), 'utf8')
/* The screen, lifted from the gate rather than restated. */
const m = /^const ALLOWED_CONTACT = (\/.+\/i)$/m.exec(gate)
if (!m) { console.error('TRANSLATOR-CONTACTS: verify-gospel-map.mjs no longer declares ALLOWED_CONTACT — refusing to invent one.'); process.exit(1) }
const CONTACT = new RegExp(m[1].slice(1, -2), 'i')
const RESOURCE = /^const ALLOWED = (\/.+\/i)$/m.exec(gate)
const ALLOWED = new RegExp(RESOURCE[1].slice(1, -2), 'i')
const codes = { ...(idx.codes || {}) }
if (POISON) {
codes.__p1 = { cr: { o: 'Watchtower', u: 'https://www.jw.org/' } }
codes.__p2 = { cr: { o: 'Some Org', u: 'https://this-was-never-fetched.example/' } }
codes.__p3 = { s: 'https://www.wycliffe.org/', cr: { o: 'x' } }
leaf = leaf.replace(/class="gm-org"/g, 'class="gm-plain"')
}
const verified = new Set(Object.values(orgs).filter((o) => o.state === 'verified').map((o) => o.url))
const fails = []
let contacts = 0
const contactHosts = new Set()
for (const [c, v] of Object.entries(codes)) {
const u = v.cr && v.cr.u
if (u) {
contacts++
contactHosts.add(new URL(u).host.replace(/^www\./, ''))
if (!CONTACT.test(u)) fails.push(`${c}: contact ${u} is not on the screened organisation list`)
if (!verified.has(u)) fails.push(`${c}: contact ${u} is not recorded as verified in translator-org-links.json`)
}
}
for (const [c, v] of Object.entries(codes)) {
for (const u of [v.s, v.f, v.z, v.au && v.au.u, v.bv && v.bv.u, v.ga && v.ga.u, v.se && (v.se.u || v.se)]) {
if (!u) continue
if (CONTACT.test(u) && !ALLOWED.test(u)) fails.push(`${c}: ${u} is a CONTACT host serving a resource link`)
}
}
if (!contacts) fails.push('no contact URL in the index at all — this check would pass vacuously')
if (!/class="gm-org"/.test(leaf)) fails.push('resources-map.vue renders no .gm-org anchor — the data carries contacts the sheet never shows')
if (!/credit\.u/.test(leaf)) fails.push('resources-map.vue never reads credit.u')
if (POISON) {
const want = ['not on the screened organisation list', 'not recorded as verified', 'CONTACT host serving a resource link', 'renders no .gm-org anchor']
const missed = want.filter((w) => !fails.some((f) => f.includes(w)))
if (missed.length) { console.error(`TRANSLATOR-CONTACTS --poison: these arms did NOT fire: ${missed.join(' | ')}`); process.exit(1) }
console.log(`TRANSLATOR-CONTACTS --poison: PASS — all 4 arms fired (${fails.length} failure(s)).`)
process.exit(0)
}
if (fails.length) {
console.error(`TRANSLATOR-CONTACTS: FAIL — ${fails.length} problem(s)`)
for (const f of fails.slice(0, 12)) console.error(` ${f}`)
process.exit(1)
}
console.log(`TRANSLATOR-CONTACTS: PASS — ${contacts} credit(s) link an organisation, across ${contactHosts.size} screened host(s); each one verified by fetch and rendered as a link.`)