go-int cross-checked 20 roster languages with "no New Testament found in the pools" against
open-data signals. Thirteen of the twenty had readable text all along — on a record the tier
could not see — and chasing that found one rule written four times and wrong in two of them.
THE BUG. tierOfCode tested `!!c.s`, one field, while this build's own hasReadableScripture
tested four sources. Tier 2 means "Scripture AND film", so a language whose text is on the
Faith Comes By Hearing record (au.m names the media it holds), on Scripture Earth, or in a
Door43 translation was scored 1 — "some resource found" — with its own text and the JESUS
Film both in hand. Measured: 602 codes and 2,001 of 12,379 groups move 1 -> 2. Tier 0 does
not move at all, 1,592 before and after, so nothing about who has NOTHING changes: the gap
lists, the 369 zero-access groups and the bridge-reach table are untouched.
THE SAME BUG DOWNSTREAM, and this is the one a catalyst would have felt: resourceRows typed
an FCBH record "audio, text, video" as Audio Scripture only, so the access table's
byType.Scripture never saw it. 288 people groups were published as "watch or hear, cannot
read" while their own readable Scripture sat behind that link. Access now reads 1,129 "read
and watch" (was 869), 518 "cannot read" (was 806), 85 "read" (was 57), 369 nothing found and
6 stories only — unchanged where it should be, and 2,107 in total either way. Typing the row
by its text would have dropped "can hear", so the access builder now counts a Scripture row
whose scope names audio as hearable too.
ONE OWNER NOW. hasReadableScripture is exported from gospel-resource-rows.js and imported by
the pin builder and by check-gospel-coverage-consistent, whose arm F had its own narrow copy
and therefore reported 2,001 disagreements against the corrected tier — a gate defending the
bug it exists to catch. A rule with four copies has no owner; this one has.
`sx` ships alongside: the readable text with its source named, for the 887 codes whose text is
not at c.s, so a surface can LINK what the tier now counts instead of claiming Scripture and
showing a film. Wiring it into the sheet and the field card is the next commit; this one is
data and rules only, and public/ is deliberately NOT rebuilt because rega is rendering this
worktree.
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.
The same drift has now appeared six times, each spelled differently:
negated fields on a line naming "none", a positive hasAny over a
mapped row, a predicate inside a build script, and — found this tick —
a pair of columns no pass had ever touched. A source-level pattern has
to guess how the next one will be written.
Clause N does not guess. It compares the shipped ANSWER against the
pin tier, so it catches the seventh however it is phrased. Measured
when written: people-group-resources.any_resource_found disagreed with
the tier on 194 rows, and its no_resource_found pair contradicted
ITSELF on 7 more, because neither column was in the refresh at all.
Both now move together, 195 and 202 corrected, and the table agrees
with the tier on all 2,103 judgeable rows.
A code with no pin is SKIPPED, not guessed. The tier lives on pins, so
an unpinned code cannot be judged this way — judging it anyway
reported ajp as a defect on the first run, when the row was right and
the check was wrong. That is the failure this repo keeps meeting, so
it is written into the clause rather than left as a comment.
Also corrected there: the table's own note still told readers GRN
answers 403 to every request and had never been searched.
The sheet's "checked" line is what tells a reader whether "nothing
found" means anything, and it was wrong in both directions. 5,131
codes omitted Scripture Earth, which had in fact been swept for every
one of them. And 110 codes named GRN as checked when the sweep has
never looked at them — GRN is crawled a batch at a time at the delay
their robots.txt asks for, so 2,100 codes are still unread.
The cause was ordering, the same lesson this file already carries for
the dead-link drop: the pass that normalises provenance ran before the
Scripture Earth and GRN fetchers, so the codes those fetchers create
kept the raw constant. A tidying pass that runs before the thing it
tidies reports a confident answer about a room still filling up. It
now runs after every source attaches, and the count is exact — 282
codes name GRN, 282 have been decided, and none that was decided goes
unnamed.
Scripture Earth is claimed unconditionally because that sweep really
did cover every code this map uses: a code absent from their index is
a checked negative. GRN is claimed per code, and the rest carry
"grn (not yet swept)" in the unchecked list instead.
Clause M of the coverage gate now fails if any code names a pool the
sweep never decided for it. Poison is 13/13.
GRN records the gospel as AUDIO for languages with no writing, which is
most of what is left in the gap — and every earlier attempt recorded it
as unreachable. globalrecordings.net sits behind a proof-of-work bot
shield that challenges even robots.txt; that is an access control and
is not worked around. Their own delivery site, 5fish.org, is not
shielded, allows what this reads, and resolves by ISO 639-3 directly.
First 180 gap codes swept: 74 hold gospel audio, a 41% hit rate, and
147 people groups that had nothing now have the gospel in a form they
can receive. Tier 0 falls 2,873 to 2,746.
The host NEVER 404s. A nonsense path returns HTTP 200 and a plausible
geo-located page, so reading 200 as existence would have marked every
code on earth as covered. Existence is read from the page title, and a
negative and a positive control run in band on every run — the fetcher
aborts if a nonsense code ever reads as a real language. Their
robots.txt asks for a ten-second crawl delay, so the sweep is
sequential, capped per run and resumable. Do not add concurrency.
Three sign languages are deliberately left out. GRN's catalogue is
audio; 5fish holding a page titled "Pakistan Sign Language" says they
index the language, not that they have signed video. Attaching it told
20 Deaf groups — 11.5 million people — that gospel audio was available
to them. Unverified, and for that audience useless.
Six places across three surfaces still told the reader GRN answers 403
to everything and had never been searched. That is now false, and a
stale caveat misleads exactly as much as a stale number.
Also here, because adding a source is what exposed it: the charts
profile decided "this group has nothing" a second time, by listing the
resource fields by hand. That list went stale every time a source was
added — 7 languages once, 23 when Scripture Earth landed — so the chart
drew groups as having nothing while the map offered them a working
link. It now reads the tier the pin index already computed, and a new
gate refuses any surface that re-derives it. The gate was checked
against the real pre-fix file, not only a synthetic one.
SIL and its translation partners publish Scripture for small languages
the big catalogues skip, which is exactly the population left in the
gap: 2,901 groups had nothing in eBible, Arclight, Door43 or FCBH.
921 codes now carry a Scripture Earth link and 28 groups that had
nothing at all — 339,550 people — have Scripture in their own language
for the first time on this map. Tier 0 falls 2,901 to 2,873.
Most of this source is not what it appears to be, and the fetcher
throws away both false positives rather than shipping them. Their open
index lists 3,477 language directories, but 764 of the ones we can join
are empty stubs: trusting the listing alone would have handed 716
people groups a link to an empty folder. And a "video" directory holds
JESUSFilm-<iso>.txt metadata, not playable film — 1,588 codes have
nothing else, so video alone never qualifies a code. Only PDF, audio,
Scripture App Builder, study notes and SFM text count.
All 921 URLs verified HTTP 200 before shipping, and the artefacts were
opened by hand first: Baka's is 14.4 MB of application/pdf, Foodo's
40.7 MB, and a nonexistent sibling 404s, so a 200 here means a file.
The coverage gate, the verifier and the links table each had to be
taught the new field. Leaving any one of them ignorant would have been
worse than not adding the source: the gate caught all 28 tier changes
as inconsistent, and the links table filed 921 rows under the "other"
publisher — the fallback that exists to make an unscreened domain
impossible to hide. The sheet's "checked" line now names the pool too,
since it is searched.
Every pool here so far is Scripture, film, Bible video or a course. None is a GOSPEL
PRESENTATION — the short translated thing a person actually opens with someone. Cru
publishes those, in the same organisation that makes the JESUS Film, so the doctrine
question is settled the same way the brief settles it for the film. Only their gospel
tracts are included, from a fixed allow-list; the lessons and survey tools are not.
IT CLOSES NO GAP AND SAYS SO. All 94 of these languages already have something, so this
is depth rather than coverage and moves no tier. Tiers are byte-identical at
2901/4767/4711. It reaches 3,270 pins and 3.97bn people.
godtools.org, the obvious host, IS A PARKED DOMAIN. It answers 200 to everything with a
114-byte body, and rendered, every URL — including /zz/nosuchtool-xyzzy — reads
"godtools.org is parked free, courtesy of GoDaddy.com". Shipping links there would have
sent people to a GoDaddy ad.
The real front-end is knowgod.com, and it does serve per-language tracts: /en/kgp reads
"Knowing God Personally", /sw/kgp reads "Kumjua Mungu Kibinafsi". But ONLY once
rendered. By fetch it is a uniform 3,864-byte shell for every URL valid or not — the
third host in this bundle with that shape, after jesusfilm's old ?languageId= and
live.bible.is.
So existence is proved from Cru's own API and never from the page: a (language, tool)
pair exists exactly when the resource's latest-translations intersect that language's
translations. Checked against the browser before being trusted — en/kgp and sw/kgp both
intersect and both render real localised text, and the bogus pair does not intersect. In
band, the verifier's controls are now 7 negative all non-verified and 7 positive all
verified, with a bogus knowgod pair among the negatives. All 94 URLs verified.
Clause I keeps its closed set: Gospel presentation only on knowgod.com, and knowgod.com
only that type. Poison self-test still 12/12. Screenshot read: German shows six distinct
resource types, including "Gospel presentation - Knowing God Personally".
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.
The previous change left video_date out because the column does not say what the video
IS. Establishing it took measurement, and the first samples were misleading. Three
video-only filesets all rendered ...S2DV pages opening on "Intro & Garden of Eden" with
Drama/Non-Drama controls, which suggested one Old Testament series. Reading
GET /api/bibles/<bible_id>, which returns the exact books array in one call, for all
1,669 video languages says otherwise:
209 27 parts, Matthew to Revelation (the whole New Testament)
116 27 parts, San Mateo to Apocalipsis (same, Spanish editions)
110 12 parts, Intro & Garden of Eden ... (Covenant, Old Testament)
27 Mark 26 Acts 23 Luke
1,006 distinct scopes in all
Generalising from three samples would have described the bulk of it exactly backwards.
Every link therefore ships its own scope rather than a shared description, and the 3
languages whose book list could not be read were dropped: an undescribed video is not
shipped.
It is typed "Bible video", never "Film". Film on this map means the JESUS Film, and
bible.is itself treats the two as different - its pages call a separate Arclight
endpoint, /api/arclight/jesus-film/languages?iso=..., to find it. So Bible video lifts a
group off "nothing found", which is true, and contributes nothing to the Scripture+film
tier. 1,666 languages carry it: 287 as their own link reaching 4,375 pins and 5.76bn
people, and 1,379 merged into the Scripture link whose media list now reads "audio,
text, video" instead of advertising half of what the page holds.
Verification for bible.is switched from a body-size heuristic to their own API. The page
answers 200 for any id, so the first version compared body sizes against a shell
measured at run time. That was a heuristic on a number the site controls, and it stopped
working when their WAF began answering HEAD with 403 and a 520-byte body for real and
bogus alike. The run-time calibration caught exactly that and ABORTED rather than mark
thousands of links verified - which is the whole reason it was measured and not
hard-coded. The API needs no heuristic: /api/bibles/OKEBSN is 200, /api/bibles/ZZZZZZ is
404. It is a tenth of the weight, it describes the same fileset the page renders, and
GET is used because HEAD is what the WAF blocks.
Also fixed: a fetch with no timeout hung the fetcher for thirteen minutes with no
output. A stall looks exactly like slow work, so every fetch in it now carries a
timeout, and the scope lookups run with bounded concurrency and 403 backoff.
6,404 links screened, 6,557 verified, 74 broken and dropped, 5 uncheckable. Screenshot
read: German shows "Bible video - 12 parts, Intro & Garden of Eden to After the Forty
Years / End Credits" as its own chip, distinct from "Film - link checked".
The FCBH integration used only audio_date. Their rows also carry text_date, and it is
unambiguous - they publish Bible text on bible.is. 698 codes carry text where this map
had no text Scripture at all, reaching 2,176 pins and 1.38bn people. Codes go 2,192 ->
2,400 and the gap falls 2,927 -> 2,908.
ONE LINK PER LANGUAGE, NOT ONE PER MEDIUM. A bible.is fileset is a single page carrying
whatever media it has, so two chips pointing at the same URL would be noise. The link
now states what it carries: 1,465 codes audio+text, 677 audio only, 207 text only.
That last number was a live falsehood. Because the builder never copied the media field
across, all 2,349 bible.is rows were typed "Audio Scripture" - including 207 filesets
with no recording at all. A group with text-only Scripture was being sent to look for a
recording that does not exist. Both the chip and the table type now follow the media.
Ranking is by media count, then AUDIO PRESENT, then recency. The middle term is not
decoration: without it a newer text-only fileset outranks an audio-only one on date
alone and the language silently loses its recording, which is the medium an
oral-preference group actually needs. Measured while it was missing: 3 codes lost audio.
video_date is deliberately left out. The column does not say what the video IS - a Luke
film, a sign-language edition, something else - and counting it as "film" beside the
JESUS Film would assert something nobody here has checked. 400 codes with no film today
carry one, so it is worth establishing later, but not by guessing.
Clause I now accepts BOTH Audio Scripture and Scripture on bible.is, because a fileset
really can be either, while keeping the set closed and keeping Audio Scripture wrong on
every other host. Proven by poisoning both directions: Film on bible.is and Audio
Scripture on ebible.org are each caught. Poison self-test 11/11.
Also verified, not assumed: the brief's requirement that a pin or country click filters
the table. iso=deu shows 4 of 5,889, country=IND shows 502 of 5,889. It works; an
earlier reading of mine that suggested otherwise was measuring the wrong thing.
All 6,117 links screened and verified. Screenshot read: Ninigo (ssg) shows "Scripture,
text - 2012 Wycliffe Bible Translators, Inc." where it would have claimed audio.
Every pin on this map has read "Audio - not searched (GRN)" for its whole life, because
Global Recordings Network sits behind Bunny Shield and answers 403 to everything, its
own robots.txt included. Re-probed this tick: still 403. But GRN was never the only
audio pool.
Faith Comes By Hearing needed no key. Their 4.dbt.io API wants one and answers 422,
which is where the earlier note stopped. Their own recordings-database page does not
use that API - it is client-rendered and pulls a STATIC public file from their CDN,
bible_recordings.json, 3,602 rows of { iso, lang_name, bible_name, bible_id, country,
video_date, audio_date, text_date }, already keyed by ISO 639-3. 2,192 codes carry a
dated audio recording; 2,121 survive verification.
Audio lifts a group off tier 0, unlike Zume, because a people group with an audio Bible
in their own language does not have "nothing" - for an oral-preference group it may be
the most useful thing on the list. It does not create tier 2, which stays reserved for
text Scripture together with film. Measured: 533 pins and 59.5M people leave the gap,
which falls 3,460 -> 2,927. Coverage 72.0% -> 76.4%. 8,094 pins now carry audio.
A 200 FROM live.bible.is PROVES NOTHING and this almost shipped wrong. It answers 200
for a fileset that does not exist, returning a fixed-size not-found shell, so the
discriminator is BODY SIZE and HEAD gives content-length for free. The shell size is
measured at run time, never hard-coded: it was 7,961 bytes on one path and 7,860 on
another within the same hour. Both fetcher and verifier abort if a bogus id ever
returns the same size as a known-good one. It caught 73 dead filesets in FCBH's own
data, all dropped.
The first full pass collected 466 HTTP 403s from bible.is - rate limiting, not dead
links; the same URL answered 200 when retried alone. Requests are now paced per host
and 403/429 retried with backoff, which cut those to 48, and a VERIFY_RETRY_UNKNOWN
pass resolved all 48. Final: 5,882 verified, 73 broken, 5 uncheckable.
Three bugs found and fixed on the way:
- The dead-link drop ran BEFORE the audio merge, so 71 dead filesets attached after the
cleanup and shipped anyway while the pass reported "dropped 0". A cleanup that runs
before the thing it cleans is worse than none, because its count measures an empty
room. Moved after every source attaches.
- A VERIFY_LIMIT smoke run overwrote the real table, destroying the broken-link history
the previous change added. A limit run is now a smoke test that writes nothing.
- The browser gate screened 3,768 of 5,889 links because its harvest never learned the
new field.
Added VERIFY_MISSING=1 so adding one resource costs one request, not a quarter-hour
pass over 5,960 URLs.
Charts, list and map were corrected together: all three still asserted audio had never
been searched, and charts deliberately drew it as a hatched "not measured" row. That
bar is now a real count, decided by hasField() from the data itself. The checked-pools
caveat on every group now names fcbh.
Screenshot read: Uab Meto (aoz) - no Scripture, no film, no stories, not in Zume - now
shows a verified audio link and the caveat lists fcbh as checked.
The Door43 fetcher kept only 3-letter codes and dropped 2-letter ones on the grounds
that turning one into ISO 639-3 would be a guess. The caution was right and the
conclusion was wrong: ISO 639-3 publishes the correspondence itself, in the Part1
column of iso-639-3.tab. The file is now fetched rather than transcribed, so it cannot
go stale, and the run aborts if it comes back with fewer than 150 pairs.
Measured cost of the old behaviour: 32 languages spoken by 2,576 pins - Bengali,
Hindi, Spanish, Amharic, Hausa, English and more - carried Scripture and film but no
Door43 resource at all, purely over a spelling. Door43 reach goes from 169 codes and
1,051 pins to 208 codes and 3,627 pins, 3.58bn people. Table rows 226 -> 391.
ONE EXCLUSION, AND IT IS LOAD-BEARING: a tag with an '-x-' private-use subtag is
skipped on this path. Door43 publishes el-x-koine, Koine Greek, whose base 'el' maps
to 'ell' - Modern Greek, which 13 people groups on this map speak. Folding it in would
have handed those 13 groups a Koine Greek text as though it were their own language.
Region and script subtags (es-419, pt-br, sr-latn, ur-deva, zh-hant) are not private
use and are the same language, so they still fold to the base. Verified: ell is absent
from the index.
Fixed a self-erasing bug in the previous change. Dropping a dead link removed it from
the corpus, so the next verification run never saw it, so the next build put it back -
measured, both links came straight back and REAL URL returned to 8,922. The
verification table is now a RECORD, not a snapshot: it carries forward every URL a
previous run proved broken and keeps re-testing them, which is also what lets a link
the publisher later repairs come back on its own. Proven by dropping them and
re-running: "re-testing 2 URL(s) a previous run recorded broken", still broken, build
now stable across two full cycles.
Clause K's 95% coverage floor is gone, because that floor is exactly how this hid: two
missing URLs out of 3,768 is 99.9%. Every shipped URL must now appear in the record.
Poison self-test 11/11.
All 3,768 links re-verified: 3,761 live, 5 uncheckable, 2 broken and dropped.
Also fixed the chip overflow this change caused - a Door43 entry can be "Aligned Bible
(translationCore-Create-BCS) - Door43 - Portions of both Testaments", which ran off the
edge of a 330px sheet unreadable. Long chips wrap now. Screenshot read: Hindi shows
seven verified Door43 resources, all inside the panel, where it read "none found".
The brief's rule is to verify a URL resolves before shipping it. The table that did
that was written in August against a corpus of 426 links, while the map now ships
3,603 - and the UI matched exactly TWO of them. Two silent causes: its eBible keys
were http:// while every shipped URL now passes through httpsify, and its 306 film
keys were the old ?languageId= URLs that were later replaced wholesale by Arclight
shareUrl deep links. Links that HAD been verified displayed as "unchecked", which a
reader cannot tell apart from "nobody looked".
The old note said the film page was unverifiable by fetch, and for the URL shape it
was written against that was right: ?languageId=13562 and ?languageId=99999999 return
byte-identical HTML, so a status check there is a false green. The shareUrl migration
changed that. A path discriminates:
/watch/jesus/german-standard -> 200, 944,516 bytes
/watch/jesus/pashto-yusufzai -> 200, 810,741 bytes (distinct body)
/watch/jesus/definitely-not-xyzzy -> 404
So 2,049 film links became checkable for the first time. The 5 remaining ?languageId=
URLs stay "unknown" with that reason - never verified, never broken.
Every host was probed with a known-good and a known-bad before being trusted, and all
answer HEAD and discriminate: ebible.org, png.bible, alkitab.pw, jesusfilm.org,
arc.gt, git.door43.org, zume.training. HEAD is deliberate - a GET over 2,049 film
pages is ~1.6GB of someone else's bandwidth to learn a status code. Five negative and
five positive controls run in band and the script aborts rather than write a table if
either arm misbehaves.
Result over 3,603 unique URLs in 365s: 3,596 verified, 5 unknown, and 2 genuinely
dead - arc.gt/092vn (Yakpa, 2 pins) and /watch/jesus/4101 (Zhuang Liujiang, 1 pin),
both re-confirmed 404 with a browser UA and a GET. The builder now drops any link
verification proved dead BEFORE tiers are computed, so a code whose only film is a 404
has no film and its tier says so. The gap filter moves 3,457 -> 3,460, which is
exactly those three pins.
Also fixed: the verification fetch hung off openPin's country branch, so a tap landing
before the country index had loaded left the whole session reading "unchecked". It is
now fetched unconditionally.
Lookup coverage goes from 2/3,605 to 3,603/3,603. New gate clause K fails below a 95%
coverage floor or if any shipped URL is recorded broken; poison self-test now 11/11.
Verified in a browser on the exact case that failed - one immediate pin tap - which
now fetches the table and renders "Scripture - link checked" and "Film - link checked";
screenshot read to confirm both chips are green where they were grey.
Last change joined Zume by exact ISO 639-3 and left pus unjoined, which turned into a
false statement on the map: 13 people groups covering 47.6M speakers read
"Discipleship training - not in Zume's 54" while https://zume.training/pus was live
and in Pashto the whole time. Absence of a variety label is not absence of a resource.
Research for something that selects a variety found nothing that does. The publisher's
launch article scopes Pashto as Afghanistan's other official language and a major
language of northwestern Pakistan, especially Khyber Pakhtunkhwa and parts of
Balochistan - territory spanning all three of pbu, pbt and pst rather than selecting
one. The locale pages declare no language metadata at all: no hreflang alternates, no
BCP-47 tag, and og:locale is the site-wide en_US on every locale including this one.
So it ships as an APPROXIMATE match rather than an exact join or a silence: its own
field, its own dashed chip that reads "Zume, in Pashto (variety not stated)", its own
scope text in the table, never counted as an exact join and unable to move a tier. The
gap filter still cuts 12,379 features to 3,457. It is a curated three-code list, not a
runtime name match - an approximate join a reader cannot see and a gate cannot check is
the one that eventually ships a wrong link.
Why this is defensible here and would not be for film: Zume in Pashto is a written
course in one literary standard, where the pbu/pbt/pst split is largely spoken variety.
The JESUS Film is already in this index under separate per-variety dubs
(pashto-yusufzai, pashto-southern) because a dub differs by pronunciation. The medium
decides.
New gate clause J fails if any code carries both a confirmed and an approximate
training link, or if an approximate entry lacks a url, language or note - that is the
corruption that would make the qualified claim render as the confirmed one. Poison
self-test now 10/10. Both gates also harvest the new field, so its URLs are screened
rather than invisible; the browser gate screens 3,605.
Verified in a browser with three controls: deu renders gm-chip ok with a real href, kri
renders gm-chip meh with no href, pbu renders gm-chip approx linking
https://zume.training/pus and carrying "variety not stated". Screenshot read: the
dashed chip is visibly distinct from both.
The brief names zume.training every tick and an earlier pass recorded the list as
unobtainable. That was wrong. robots.txt Disallows /api/ and the homepage switcher
is client-rendered, both true, but robots.txt also DECLARES a sitemap, and the
publisher maintains /articles/zume-languages for exactly this purpose. Neither is
behind a Disallow. The real figure is 62 locales, not the ~40 estimated: 54 serve a
landing page that answers 200.
The two sources disagree in both directions - Dari and Pashto ship sites but are
missing from the article's table, and eight table rows serve no page yet - so
fetch-zume-index.mjs takes the union and then fetches every landing URL. A locale
ships a link only if its own page answered. Naming and linking stay separate facts.
Pashto is deliberately left unjoined: pus is a macrolanguage, the people-group data
carries pbu, pbt and pst, and nothing the publisher gives selects one.
Training is not Scripture and not film, so it sits in its own field and its own type
and moves NO tier. Measured: the 52 joined languages reach 3,479 pins and 4.78bn
people, and none of them are tier 0 - every Zume language already has Scripture or
film. Folding training into the tiers would have cut the gap count without anyone
having translated anything. The gap filter still cuts 12,379 features to 3,457.
Both gates had a hole this exposed. The coverage gate harvested URLs from s, f and d
only, so the 52 new URLs existed in no pin record it could see; and clause I checked
Film, Scripture and Bible Stories by host but let any other type fall through
unchecked. Both now cover training, and a poisoned row typed Scripture on
zume.training is caught. The browser gate screened 3,550 links and silently ignored
the new ones; it now screens all 3,602.
Verified in a browser with a negative control: deu opens with a chip linking
https://zume.training/de, kri opens with "not in Zume's 54" and no href, no page
errors.
70 story sets were typed as Scripture for seven ticks because NOTHING compared a row's
label to where it points. Clause H, added last tick, catches that exact mislabel. It would
not have caught the same mistake made anywhere else.
Clause I asserts the general shape: Film only on jesusfilm.org or arc.gt, Bible Stories
only on git.door43.org and only under that name, Scripture never on a film host. Audited
across all 3,550 rows first — 0 violations, plus 0 duplicate (language, url) pairs — so
this ships as a guard rather than a fix. The 8 languages carrying more than one Scripture
URL were checked and are correct: Cebuano really does have an eBible ULB alongside Door43's
ULB and UDB, which is three translations, not three copies.
THE PASS LINE NOW NAMES EVERY CLAUSE. It listed four of nine, which reads as a narrower
check than it is, and the next reader calibrates their trust on that line rather than on
the source. Nine clauses, nine names, and --poison still fails 9/9 on demand.
The table also states its scope breakdown where a reader meets it — 855 New Testament, 254
Portions of both Testaments, 136 Full Bible, 14 Old Testament, 13 Portions — with the
reason it is counted from each publication's own book list rather than its subject label.
"How much Scripture exists" is the question under every other number in that table and it
was previously visible only by sorting a column.
Verified: gate passes, self-test 9/9, the table renders its new Scope column, and pins,
scope and deep-link probes all pass.
Door43 entries carry their own book list. Each ingredient is categorised bible-ot or
bible-nt, so a Door43 Scripture entry can say how much Scripture it is, exactly as an
eBible one does — and it is already in the response we fetch, so this costs no extra
requests. 35 entries scoped: 15 New Testament, 13 Portions, 4 Full Bible, 3 Portions of
both Testaments.
THE SUBJECT LABEL IS NOT THE SCOPE, which is why it is counted from the data. An entry
filed under "Bible" can be 27 NT books and nothing else. Calling that a Bible because of
its folder name is the same error as calling Open Bible Stories Scripture.
★ AND THAT ERROR WAS ALREADY SHIPPING, 70 ROWS OF IT, BY MY OWN HAND. Tick 15 added owner
disambiguation so two same-named entries could be told apart — "Open Bible Stories
(OBS-TLF)". The type test was `d.s === 'Open Bible Stories'`, an exact match, so every
disambiguated label fell through to Scripture. 70 story sets have been typed as Scripture
for seven ticks: Bhojpuri, Maithili and 68 more told a reader they had a translation when
they have 50 illustrated narratives. Found only because the type counts moved when this
tick changed the test to a prefix.
An exact match against a label that can grow a suffix is the whole defect, and the fix is
not enough on its own — so clause H now asserts that nothing named "Open Bible Stories" is
typed as Scripture, poisoned with the rest. 8/8 clauses fail on demand.
The Door43 chip in the sheet had the same exact-match and now reads "Bible Stories
(OBS-TLF) · Door43 · <scope>". The one `===` left in the fetcher is correct and was
checked rather than blanket-replaced: it compares the RAW subject from the API, which
never carries an owner suffix.
Every Scripture row on the map now has a scope. The 2,278 blanks reconcile exactly to
2,087 film plus 191 Bible Stories — none of which is a portion of the canon.
Verified: gate passes, its self-test fails 8/8 on demand, and the Door43, pins, table,
scope and deep-link probes all pass.
1,237 of the URLs we ship were plain http — all of eBible.org, png.bible and alkitab.pw,
with the host spelled `eBible.org` on six of them. Every one of those hosts serves https
directly (sampled 12/12 at 200) and redirects http there anyway, so the plain link bought
nothing but a redirect hop and a downgraded scheme in a field worker's browser, on a page
that may itself be embedded over https.
NORMALISED WHERE A URL ENTERS THE INDEX, not per fetcher. Doing it in the eBible fetcher
first left 120 links behind — the curated scripture_url values, which fill-never-overwrite
preserves and which therefore never pass through a fetcher at all. One httpsify at every
emit point takes it to 3,548 https and 0 http.
Scheme and HOST only. The path is left exactly as published, because a path is
case-sensitive and "tidying" it would break the link rather than improve it.
★ THE GATE CAUGHT MY OWN DRIFT, ten seconds after I added the clause. Clause G failed —
but as clause E, "1237 table URL(s) exist in no pin record": I had rebuilt the pins and
not the table, so the two surfaces disagreed about every eBible link in the corpus. That
is precisely the class this gate was written for last tick, and it found it on its first
real use rather than three ticks later by hand.
Clause G is poisoned in the self-test alongside the rest: 7/7 clauses now fail on demand.
Verified: 0 plain-http URLs remain, six upgraded links sampled live all answer 200 over
https, the coverage gate passes, and pins, table, deep-link, tooltip and list probes all
still pass.
Three defects this flight were the same defect, and none of them threw. Every page
rendered; only comparing two surfaces caught them:
the country sheet listed 36 groups for Indonesia while the map drew 669 pins, because
the sheet read a curated subset and the map read the pins;
the resource table silently dropped 506 real resources across 377 languages, because
its generator skipped any code no pin speaks — in a table titled "every link";
resources attached to `und` told 14 people groups there was Scripture in a language
the record explicitly does not identify.
A fourth is a matter of time, so the comparison becomes a gate rather than a habit.
SIX CLAUSES, all static, 60ms, no browser and no server: no ISO reserved code carries a
resource; every resourced code has a language name; the table's reach columns match an
independent recount off the pins; every resourced code appears in the table; no table URL
exists that the pin index does not hold; and each pin's tier agrees with its code's URLs.
Each one is a defect that was actually shipped, not a hypothetical.
★ IT REFUSES TO PASS VACUOUSLY. Five of six clauses assert a NEGATIVE, which is exactly
the shape that reads green against empty or mis-keyed input, so an empty corpus is a FAIL
rather than a clean run, and a missing input SKIPS loudly with the command to fix it.
★ AND IT SELF-TESTS. `--poison` injects one defect per clause in memory and requires all
six to fail. Clause D is included deliberately: it is the one that would have caught the
506 dropped resources, and the first version of this self-test left it unproven — the
most valuable clause was the one clause never shown to fail. 6/6 now.
Registered in package.json and in build:unlocked, because a gate nothing runs is not a
gate — this repo has carded that twice. check:no-orphan-gates went from FAIL to PASS on
registration, which is the check confirming itself. The CLI doc quotes the build chain
verbatim, so it was updated to match: package.json is what runs, the doc is the copy.
No product file changed, so nothing is rebuilt. check:quoted-scripts, check:doc-scripts,
check:no-orphan-gates and check:mjs-parse all pass.