Clause L: a verification record that stops being re-run now fails the gate
Eleven clauses checked that the surfaces AGREE with each other. None checked that any
of it was still true. "Link checked" is a claim about the present tense, and a table
that quietly stops being re-run keeps making that claim on evidence that could be a
year old while every consistency clause still passes. A dead link is the failure a
field worker actually hits, so this is not bookkeeping.
Clause L fails when a shipped URL carries no check date at all, or when the oldest
shipped link was last checked more than 45 days ago, and the message names the exact
command to fix it. Re-running is cheap and resumable now, so the failure is an
instruction rather than an obstacle.
Proven against real data, not only the poison harness: deleting the check date from
one shipped URL is caught ("1 shipped URL(s) carry no check date"), and backdating one
by 120 days is caught ("last checked 120 days ago, past the 45-day limit"). Both
restore clean. Poison self-test now 12/12.
Also checked and deliberately NOT changed: the Arclight harvest keeps a language only
when counts.featureFilm >= 1, and it was worth asking whether that discards coverage.
Of 2,330 catalogue languages, 2,309 already have a feature film. Only 15 carry just a
shortFilm or a series, and exactly one of those - nmg Kwasio, 2 pins, 60k people - is
spoken by a group here with nothing else. Widening the filter would mean typing a
short film as "Film" beside the JESUS Film for a single language. The filter stays.
The registered description still claimed "6/6 clauses must fail"; corrected to 12.
This commit is contained in:
parent
270fae1a02
commit
f129294609
3 changed files with 56 additions and 2 deletions
|
|
@ -53,6 +53,7 @@ import { fileURLToPath } from 'node:url'
|
|||
|
||||
const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const POISON = process.argv.includes('--poison')
|
||||
let poisonStale = false
|
||||
|
||||
const read = (p) => JSON.parse(readFileSync(resolve(REPO, p), 'utf8'))
|
||||
let pins, table, verif
|
||||
|
|
@ -88,6 +89,7 @@ if (POISON) {
|
|||
rows = [[...rows[0].slice(0, 6), 2], ...rows.slice(1)] // F (forced tier 2)
|
||||
codes.zzj = { l: 'Both at once', z: 'https://zume.training/en', za: { u: 'https://zume.training/pus', l: 'Pashto', n: 'x' } } // J
|
||||
codes.zzk = { l: 'Ships a known-dead link', f: 'https://jesusfilm.org/watch/jesus/4101' } // K
|
||||
poisonStale = true // L
|
||||
}
|
||||
|
||||
const SPECIAL = (c) => c === 'und' || c === 'mul' || c === 'mis' || c === 'zxx' || (c >= 'qaa' && c <= 'qtz')
|
||||
|
|
@ -214,13 +216,54 @@ if (verif && verif.links) {
|
|||
if (dead.length) add('K', `${dead.length} shipped URL(s) are recorded BROKEN and must not ship (e.g. ${dead[0]})`)
|
||||
}
|
||||
|
||||
// L — the verification record must be FRESH, and every shipped URL must carry a stamp.
|
||||
// "Link checked" is a claim about the present tense. A table that silently stops being
|
||||
// re-run keeps making that claim on evidence that may be a year old, and every clause
|
||||
// above would still pass: they check that the record is CONSISTENT, never that it is
|
||||
// current. A dead link is exactly the failure a field worker hits, so the check that the
|
||||
// record is recent is not bookkeeping.
|
||||
// Re-running is cheap and resumable now (VERIFY_MAX_AGE_H skips what is already fresh),
|
||||
// so this failing is an instruction, not an obstacle.
|
||||
if (verif && verif.links) {
|
||||
const V = verif.links
|
||||
const MAX_AGE_DAYS = Number(process.env.VERIFY_MAX_AGE_DAYS || 45)
|
||||
const shipped2 = new Set()
|
||||
for (const v of Object.values(codes)) {
|
||||
if (v.s) shipped2.add(v.s)
|
||||
if (v.f) shipped2.add(v.f)
|
||||
if (v.z) shipped2.add(v.z)
|
||||
if (v.za && v.za.u) shipped2.add(v.za.u)
|
||||
if (v.au && v.au.u) shipped2.add(v.au.u)
|
||||
if (v.bv && v.bv.u) shipped2.add(v.bv.u)
|
||||
for (const d of v.d || []) if (d.u) shipped2.add(d.u)
|
||||
}
|
||||
const unstamped = [...shipped2].filter((u) => V[u] && !V[u].at)
|
||||
if (unstamped.length) {
|
||||
add('L', `${unstamped.length} shipped URL(s) carry no check date (e.g. ${unstamped[0]}) — rerun scripts/verify-gospel-links.mjs`)
|
||||
}
|
||||
let oldest = Infinity
|
||||
let oldestUrl = ''
|
||||
for (const u of shipped2) {
|
||||
const at = V[u] && V[u].at ? Date.parse(V[u].at) : NaN
|
||||
if (!Number.isFinite(at)) continue
|
||||
if (at < oldest) { oldest = at; oldestUrl = u }
|
||||
}
|
||||
const nowMs = poisonStale ? Date.now() + (MAX_AGE_DAYS + 10) * 86400000 : Date.now()
|
||||
if (Number.isFinite(oldest)) {
|
||||
const days = (nowMs - oldest) / 86400000
|
||||
if (days > MAX_AGE_DAYS) {
|
||||
add('L', `the oldest shipped link was last checked ${days.toFixed(0)} days ago (${oldestUrl}), past the ${MAX_AGE_DAYS}-day limit — rerun VERIFY_MAX_AGE_H=24 node scripts/verify-gospel-links.mjs`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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']
|
||||
const want = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L']
|
||||
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) {
|
||||
|
|
@ -245,3 +288,4 @@ console.log(' E no table URL the pins do not hold F pin tiers match their c
|
|||
console.log(' G no plain-http URL H no story set typed as Scripture')
|
||||
console.log(' I row type agrees with its host J approximate never reads as confirmed')
|
||||
console.log(' K every shipped link is verified, and none known-broken ships')
|
||||
console.log(' L the verification record is current, not merely consistent')
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@
|
|||
"check:backlog-status": "node internal/check-backlog-status-enum.mjs",
|
||||
"check:mjs-parse": "node internal/check-mjs-parses.mjs",
|
||||
"check:snapshot-guard": "node internal/check-snapshot-guard.mjs # every gospel-resources catalogue fetcher must write through scripts/_snapshot-guard.mjs, which refuses a write that loses more than a tenth of the previous snapshot. Written after a rate-limited FCBH run silently replaced 1,666 video scopes with 1,103. Static, sub-second. Self-test: --poison injects a raw write and the check must fail on it.",
|
||||
"check:coverage-consistent": "node internal/check-gospel-coverage-consistent.mjs # the gospel-resources surfaces must agree: no resource on an ISO RESERVED code (und/mul/mis/zxx), every resourced code NAMED, table reach recounted off the pins, no invented URL, tiers reconcile. Static, sub-second. Self-test: --poison, 6/6 clauses must fail. Written after the same drift defect was found by hand three times.",
|
||||
"check:coverage-consistent": "node internal/check-gospel-coverage-consistent.mjs # the gospel-resources surfaces must agree: no resource on an ISO RESERVED code (und/mul/mis/zxx), every resourced code NAMED, table reach recounted off the pins, no invented URL, tiers reconcile. Static, sub-second. Self-test: --poison, all 12 clauses (A-L) must fail. Clause L is the one that is not about consistency: it fails when the link-verification record goes stale, because 'link checked' is a claim about the present tense. Written after the same drift defect was found by hand three times.",
|
||||
"check:synced-css:self-test": "node internal/check-synced-css-copies.mjs --self-test",
|
||||
"serve:permissive": "python3 -m http.server 4173 --bind 127.0.0.1 --directory public # LOOPBACK ONLY. This was --bind 0.0.0.0, which published the built tree on every interface of whatever machine ran a gate; measured on the build VPS it answered HTTP 200 on the box's PUBLIC IP while a browser gate was running. Every consumer (verify-browser-all, check:a11y-button-names, check:nav-reachable, smoke:share-all) already defaults to 127.0.0.1, so nothing needed the wider bind. Override with BASE_URL/BASE if you deliberately want to reach it from another host.",
|
||||
"check:doc-scripts": "node internal/check-doc-quotes-scripts.mjs",
|
||||
|
|
|
|||
|
|
@ -130,6 +130,16 @@ Doctrine: straight Scripture audio and text, which the brief names as acceptable
|
|||
Societies, Biblica, Wycliffe and Covenant. The name screen is a guard for future data, not a fix for
|
||||
anything present — 0 of 2,769 distinct translation names match it.
|
||||
|
||||
## Arclight beyond featureFilm — checked, and there is almost nothing there
|
||||
The Arclight harvest keeps a language only when `counts.featureFilm >= 1`, and it was worth
|
||||
asking whether that filter was discarding real coverage. It is not. Of 2,330 languages in
|
||||
the catalogue, **2,309 already have a feature film**. Languages carrying only a `shortFilm`
|
||||
or a `series` number 15, and of those exactly **one** — `nmg` Kwasio, 2 pins, 60k people —
|
||||
is spoken by a group on this map that has nothing else.
|
||||
|
||||
So the filter was already near-complete, and widening it would mean typing a short film as
|
||||
"Film" beside the JESUS Film for a single language. Left as it is.
|
||||
|
||||
## Sign language — the open catalogues are EXHAUSTED, and that is the finding
|
||||
136 of the 150 sign languages on this map have no resource at all, across 234 people groups.
|
||||
Searched this tick rather than assumed:
|
||||
|
|
|
|||
Loading…
Reference in a new issue