Commit graph

18 commits

Author SHA1 Message Date
Doxa Maps
7c99d1a06c A gate's tree server dies with its gate: serve-strict --exit-with-parent, passed by every spawning gate
On 2026-09-29, 41 serve-strict processes had outlived the gates that started them: 279 MB resident,
the oldest 5.3 days, spread across eight worktrees. A gate that crashes, is killed, or is refused by the
browser-slot guard's process.exit cannot run its own srv.kill(), and _serve-reaped's handlers cover
signals and normal exit but not a SIGKILLed parent.

serve-strict takes --exit-with-parent: it records the pid that started it and exits within 2 s once
that process is gone. Opt-in, so a server someone deliberately left running is never taken down.
Every internal/ gate and tool that spawns it now passes the flag (14 files, plus _serve-reaped, the
shared helper). Proven: a parent that spawns the server with the flag and exits abruptly (as the
guard does) leaves it alive for a moment and gone within 3 s.

The orphans already running belong to other worktrees' runs and are left to their owners; plugin-local
scripts that spawn serve-strict should pass the flag too.
2026-09-29 14:56:38 +00:00
Doxa Maps
fbc517c94a A browser gate refuses to launch outside the browser slot; check:browser-gates-guarded keeps it that way
Only the browser seat runs browsers, and only inside internal/browser-slot.sh.
That rule was remembered, not enforced: an agent ran a gate it took for a
headless source check and it launched Chromium outside the slot.
internal/lib/playwright-guarded.mjs is now the one way a repo file reaches
playwright: its launch(), launchPersistentContext(), launchServer(), connect()
and connectOverCDP() exit 75 (NOT MEASURED) with a message naming
browser-slot.sh unless DOXA_BROWSER_SLOT is set, and browser-slot.sh exports it
for the command it runs, so a bash -c grandchild inherits it. All 72 files that
imported playwright switch to it (internal/lib/browser.mjs and 64 internal gates;
plugin-designs scripts: doxa-research-map 3, my-upg-100-list 2,
research-map-plus-grid 2, research-dashboard 1): a mechanical import swap.
check:browser-gates-guarded (gates:pre) fails a direct import;
bun run browser-gates lists the 97 scripts that launch a browser. isBrowserGate
moves to internal/lib/browser-gates.mjs so the runner and the check share it.
2026-09-28 08:20:21 +00:00
Doxa Maps
376ecaee25 Prayer as a move at the grain a team is sent to
A country is not where anyone is sent; a state is. Each province now carries the
same two prayer numbers the country sheet does and ends in the same move — the
largest group there with nothing and nobody praying, one tap from its prayer page,
ranked need-first so drilling from country to province never hands the reader a
second answer. Andhra Pradesh: 64 of its 76 groups with a prayer page have nobody
praying, 3.8M people.

Always N of M. doxa.life publishes a page for 1,723 of the roster, so nobody-praying
can be said of the groups that HAVE one and never of the province; both numbers
ship together so no surface can quote the first alone.

The join here is name+country, not language — you pray for a people in a place, and
that is doxa.life's own key. It is the one join in this build where a name match is
correct rather than forbidden, and the slug is always theirs: pray.doxa.life answers
200 for any path, so a constructed one would look valid and be empty.

The doctrine screen now scans the province table too. It is the SIXTH place a URL
lives in this bundle, and a table added after the scan was written is exactly how
the access table went unscreened for weeks — read from disk, so it is checked even
when the province layer was never opened.
2026-09-26 17:54:39 +00:00
Doxa Maps
27bc18a6e1 Name the translator, and let a mobiliser write to them
The credit line said who translated the Scripture and called them "the likeliest
people to ask", then stopped at a name. 838 of the 929 credited groups now reach
that organisation's own homepage from the sheet.

The URL does not come from eBible's homeDomain field, which is still dropped: it
comes from a hand-screened list of 16 organisations, every one fetched on the run
that wrote the table. Where a credit names two, the one it LEADS with wins, so
"Pioneer Bible Translators and The Word for the World International" reaches
Pioneer and not the other way round.

National Bible Societies hold 89+ credits and are deliberately linked to none of
them: several UBS members publish editions carrying the deuterocanonicals, so
whether a Society contact sits inside the 66-book filter is a ruling to be made,
not one to assume. They keep the name without the link, and the count says so.

A contact is screened as somewhere to write to, not as a publisher this map reads
text from — two separate lists, so wycliffe.org appearing as a scripture link is
still a failure. Four organisations answered a datacentre IP with a bot challenge
while serving browsers normally; those are proved by robots.txt and the table
records which method proved which.
2026-09-26 07:43:58 +00:00
doxa-maps-88655c70
d38488d799 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
# Conflicts:
#	internal/check-gospel-rtl-shaping.mjs
#	public/dx-maps/assets/tables/country-languages.json
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/gospel-resources/charts/index.html
#	public/dx-maps/gospel-resources/data-tables/index.html
#	public/dx-maps/gospel-resources/gospel-dashboard/index.html
#	public/dx-maps/gospel-resources/gospel-resources.js
#	public/dx-maps/gospel-resources/resources-list/index.html
#	public/dx-maps/gospel-resources/resources-map/index.html
#	public/dx-maps/manifest.json
2026-09-25 21:38:20 +00:00
doxa-maps-88655c70
ca548b37ba Gospel map: the Deaf groups get their way in on the map, not only in the table
SUN reached the access table and the CSV first, and the map — the surface a
mobiliser actually opens — still showed Deaf groups a panel that explained their
situation honestly and then left them with nothing to do. That is the largest
population on this map with nothing: 93 groups, 17.5 million people, and
structurally so, because a spoken bridge cannot reach a Deaf community and there
was no other kind.

Clicking Deaf Beninese now offers the SUN New Testament, the SUN Bible stories
and the language itself, and says that SUN is already being taught in Benin by
Mission Évangélique des Sourds — a named local Deaf church rather than a
resource with nobody attached to it. It is offered only where the group has
nothing, because a group with Scripture in its own sign language does not need
to learn a second one, and it says on its face that it is a bridge which has to
be learnt and closes no gap in their own language.

Carried in the pin index beside the spoken bridges rather than fetched by the
leaf, so the sheet and the access table cannot disagree about what exists. It is
one small object, not a per-language join, because SUN is one signed language
for all of them.

The doctrine scan reads it too. Those URLs live in a new top-level block that the
per-language loop cannot see, so they were added to the scan in the same change
rather than found missing later, the way the access table was.
2026-09-25 21:00:44 +00:00
doxa-maps-88655c70
da016d8b1d The tree-identity guard can check the DATA a gate judges, not just the bundles
servesThisTree fingerprints manifest.json, which describes BUNDLES. MEASURED 2026-09-25 (gospel):
verify:gospel-map adopted another worktree's server and reported PASS, reading 16,867 links, 93
short of this tree — the manifests were byte-identical because the change under test was a data
asset (doxa-gospel-access.json); a planted jw.org link went undetected.

- servesThisTree(base, repo, { assets }) / requireThisTree(base, repo, label, { assets }): a gate
  declares the tree-relative data files it judges, and their served bytes must equal disk too.
  A mismatch or a missing file is a DIFFERENT build -> requireThisTree exits 75 NOT MEASURED,
  naming the file.
- verify-gospel-map converges onto it (its per-gate copy of the check is gone): the tables are
  declared in its adopt check, so a same-manifest/different-data server is not adopted and its own
  server is started instead; a server you NAMED that is another tree is now NOT MEASURED (75), not
  "SKIP — nothing served" (exit 0).

Negative-tested: a copy of this tree with an identical manifest and one planted link in
doxa-gospel-access.json — without assets the guard says "same build" (the incident); with assets it
refuses, naming the file; requireThisTree exits 75; this tree passes; verify:gospel-map named at the
copy exits 75.
2026-09-25 20:57:39 +00:00
doxa-maps-88655c70
cac041da42 Gospel access: a bridge for the Deaf, and screen the links nobody was screening
Of the 369 Doxa groups with nothing in their language, 94 had no bridge either,
and 93 of those were Deaf — 17.5 million people, the largest untouched number on
the map. That was structural, not bad luck: every bridge here is a spoken
language widely used in the same country, and a spoken bridge is worthless to a
Deaf community, so the build correctly refused to offer one and then had nothing
else to say.

SUN is a signed language built to be learnt across sign languages, with the Old
Testament, New Testament and Bible stories in it. It is carried the way a spoken
bridge is — something to learn, no substitute for Scripture in a group's own
sign language — in its own columns, never folded into the access rung, which
stays the honest "nothing found in their language". 28 countries where it is
already being taught come with the named local Deaf church or organisation
doing the teaching, which is the difference between a resource and a lead.

Only URLs verified by their content ship. sun.bible answers 200 for
/sun/<anything> and /resource/<anything>, so a per-language URL built by pattern
would look valid and could be empty; the fetcher re-measures that catch-all on
every run rather than trusting a note. Hosts left out are listed with the reason,
including three that are reachable by a person but not by a script.

Doctrine was checked harder here, not less. The Deaf are the most heavily
targeted group in this dataset by publishers this build must never carry, and
the thinner the pickings the stronger the pull. SUN is Wycliffe Associates'.

Screening the result exposed the bigger hole. The doctrine scan read the pin
index only, and within it skipped the Words of Life and Scripture Earth fields,
so the access table — the one the page renders and the one exported to CSV for
posters and QR codes — was never screened at all. The least supervised links in
the build were the ones most likely to be printed and handed to a stranger.
Scanning both, and both fully, takes the screened count from 6,497 to 16,960.

The gate also now proves the server holds these tables. It adopted a server from
another worktree on the default port and reported PASS on it: all three
manifests are byte-identical, because a manifest describes bundles and the
change was to a data asset it never mentions. A planted excluded-publisher link
went undetected, which is the worst possible failure for this particular check.
2026-09-25 17:47:00 +00:00
doxa-maps-88655c70
44ef31b2c6 Tooling reads the tree's name from the build config, in one place
The output tree's name has always been configurable (vite.doxa-maps-build-config.json "name"),
but 61 tooling files ignored it and hardcoded 'doxa-maps' (181 literals), so a rename would have
moved the build while the gates kept reading the old folder.

- internal/_tree-name.mjs reads it once (validated as one URL-safe path segment; falls back to the
  historical name) and exports TREE_NAME / TREE_NAME_RE; `node internal/_tree-name.mjs` prints it
  for shell scripts.
- A codemod rewrote 171 literals in internal/ to TREE_NAME; the regex literals, messages with
  backticks, atomic-public-swap.sh (fails loudly if the name cannot be read), build-lock.sh and
  vite.config.js (its duplicate readMfeName is gone) were done by hand.
- The name is unchanged: old and new tooling produce all 1408 files under public/ byte for byte.
  Code lines still naming the tree in internal/ + vite.config.js: 2, both intended (the helper's
  fallback, the private-repo-name pattern in public-hygiene-check).

This renames nothing visible; the eventual rename becomes a one-line config change.
2026-09-25 16:25:22 +00:00
doxa-maps-88655c70
e0fd61eb3e Gospel presentations in 94 languages, from Cru's API rather than its parked domain
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".
2026-09-24 13:51:56 +00:00
doxa-maps-88655c70
fb7ed4566d Browser gates now prove the server is serving THIS checkout
The gospel-resources gate was found adopting whatever answered on the default port,
which on this machine was an unrelated process from a different worktree. It reported
on a build that was not the one on disk: first a PASS, then a mount timeout that read
exactly like a regression in code nobody had touched. "Answers 200" and "is serving
this checkout" are different questions, and only the second one is the gate's job.

That fix was local to one gate. The same default sits under the rest of them, so it is
now one shared helper, internal/_serves-this-tree.mjs, fingerprinting
public/doxa-maps/manifest.json — which generate-tree rewrites on every build and which
names every bundle and profile in the tree. Different bytes means a different build:
another worktree, or a server someone forgot to restart. Both produce the same lie.

Wired into verify-browser-all, which hands ten gates their base URL and would have
passed the wrong tree to all of them, and into check-a11y-button-names. Both now refuse
with a NOT MEASURED exit rather than producing a confident wrong answer. The gospel
gate keeps its behaviour of starting its own server instead.

Verified in both directions: against the foreign 4173 each prints "serves a DIFFERENT
build than public/" and exits 2; against this tree the aggregator proceeds to its ten
gates and the gospel gate passes.

ONE REPORTED NUMBER WAS WRONG BECAUSE OF THIS. The a11y result recorded earlier in this
flight, "810 buttons, 810 with an accessible name", was measured against the foreign
tree. Re-run against this checkout it is 825 buttons, 825 with an accessible name,
still passing across 21 leaf pages. The conclusion held; the evidence had not been
about this build.

Audited the rest of this flight's browser evidence rather than assuming: the 41 probe
scripts in _qa-cycle all target port 5399, which is served by
serve-strict --directory <this worktree>/public, and the manifest served there is
byte-identical to the local one. Those results stand.
2026-09-24 12:54:35 +00:00
doxa-maps-88655c70
77ac7e3436 The two largest gaps on this map were Chinese varieties of a resourced language
Read top-down, the gap list sent you to "Chinese, Jinyu - 60,310,000 people - nothing
found", then "Chinese, Gan - 41,280,000". Both statements are true about those ISO
codes and both are misleading as priorities: ISO 639-3 files them under the
macrolanguage zho beside cmn, Mandarin, which has Scripture, film, audio and training
here. Measured across the whole map: 84 pins covering 140M people sit at "nothing
found" while a sibling variety of their own ISO macrolanguage carries resources.

Those groups now carry a related-variety note, on the pin sheet and inline in the
ranked list, because the list is the thing that gets read top-down. 63 codes have one.

WHAT IT DOES NOT SAY. It is not a claim of mutual intelligibility, and the wording
refuses to imply one - spoken Jinyu and spoken Mandarin are not mutually intelligible;
what they share is a written standard. Only ISO's own relation is asserted, the reader
is told exactly that, and it changes NO tier and closes NO gap. Tiers are byte-identical
at 2901/4767/4711. The chips still read "none found", because that is still true.

The macrolanguage table is fetched from SIL rather than transcribed, and written
through the snapshot guard like every other catalogue.

Also corrected: the gap list header still said "Audio has never been searched for any
of them". That stopped being true when Faith Comes By Hearing was added, and a stale
caveat misleads exactly as much as a stale number. It now says audio HAS been searched
and these groups have none there either, with GRN still named as the one pool nobody
can look in.

And a gate defect that produced three runs of false signal. verify-gospel-map adopted
any server answering 200 on the default port. Port 4173 turned out to be held by an
unrelated python3 process from another worktree, so the gate reported on a build that
was not this one - first a PASS, then a mount timeout that looked exactly like a
regression in code that had nothing to do with it. Answering 200 is not the same
question as serving this tree. It now fingerprints the served bundle against the bytes
in public/ and refuses to adopt a server whose build differs, starting its own instead.
Proven: it now prints "serves a DIFFERENT build than public/ - not adopting it",
starts its own, and passes.

Screenshot read: the ranked list shows "related: Chinese, Mandarin has resources"
under Jinyu, Gan and Min Bei, and correctly nothing under Deaf Chinese or Deaf
Pakistani, which have no macrolanguage sibling.
2026-09-24 12:44:24 +00:00
doxa-maps-88655c70
c4fbc469ac Bible video from FCBH, with the real book scope on every link
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".
2026-09-24 11:47:37 +00:00
doxa-maps-88655c70
a11e19a6b0 Audio Scripture in 2,121 languages: every pin used to say "not searched"
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.
2026-09-24 11:16:30 +00:00
doxa-maps-88655c70
e04509a94d Pashto: 47.6M speakers were told Zume had nothing, while its Pashto site was live
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.
2026-09-24 09:37:56 +00:00
doxa-maps-88655c70
2ed595c0f8 Zume Training: 52 languages of discipleship course, joined to 3,479 pins
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.
2026-09-24 09:31:13 +00:00
doxa-maps-88655c70
ff8a318e51 Let the map gate start its own server, so it will actually run
Registering a gate is not enough to make it run. verify:browser-all is invoked NOWHERE in
this repo; build:unlocked runs only `bun run verify`, and verify-all.mjs skips anything
marked browser:true unless --full is passed. That is precisely how two gates rotted here
before — "both had rotted unnoticed precisely because the build chain has never run them".
A gate whose precondition is "remember to start a server first" has a precondition nobody
remembers.

So the precondition is gone. If nothing answers, the gate spawns serve-strict on an
ephemeral port — ephemeral so two gates running at once cannot collide on 4173 — and shuts
it down afterwards. `bun run verify:gospel-map` now works from a clean shell with no setup.

An explicitly named BASE_URL or argv is honoured and never auto-served: a caller naming a
host means it, and quietly pointing at a different tree would be worse than skipping.
Verified — a dead explicit URL SKIPs and starts nothing.

★ THE FAILURE PATH LEAKED A PROCESS AND THE PASS PATH DID NOT. process.exit(1) inside the
try terminates before the finally block's cleanup runs, so a failing run left its spawned
server alive: measured 0 strays after a pass, 1 after a fail. A gate that leaks only when
it fails leaks exactly when someone is re-running it to see what broke. The exit code is
now set, not thrown, and the process exits after cleanup. Re-measured from a clean
baseline: 0 strays after pass, after fail, and after restore.

One scare worth recording: a stray server DID show up mid-test and it was the orphan from
the pre-fix run, six minutes older than the code being tested. Checking its start time
rather than its existence is what told them apart.
2026-09-24 07:44:26 +00:00
doxa-maps-88655c70
3d3586b5f9 Commit the map check, so the browser path survives this flight
Every browser check written for this bundle lived in _qa-cycle/, which is gitignored: a
dozen probes proving the pins render, the sheet opens with real links and the deep link
filters the table — all of which disappear with the working tree. check:coverage-consistent
guards the DATA and cannot see the map at all. The repo has carded orphaned gates twice;
this is the same loss one step earlier, a gate that was never committed.

It asserts, against the SHIPPED tree through the production-parity server: the element
mounts and exposes its state handle (a stale-SRI leaf silently refuses to execute, which
looks identical to a mount failure); every pin in the source is RENDERED, not merely
loaded; a tap opens a group with a NAMED language; the table deep-link carries that
group's own ISO code; and the gap filter leaves only tier 0.

★ IT TOOK THREE TRIES TO MAKE IT WORTH TRUSTING, and each failure is the point.

FIRST it was FLAKY — 1 run in 3. Clause 4 waited a fixed 900ms after clicking the filter,
but setFilter is applied asynchronously, so it queried mid-transition and still saw tier 1
and 2. It now waits for the map's own idle event. 5 of 5 runs clean. A gate that fails a
third of the time on correct code is a gate that gets switched off.

SECOND its doctrine check had a SAMPLE SIZE OF 1. It validated the two URLs of the one
group it happened to open — 1 of 2,685 codes — so a jw.org link planted anywhere else
passed cleanly. MEASURED: the injection did not fail the gate until the scan covered the
whole index. It now checks all 3,550 links, and fails if the index carries none at all,
because a scan over nothing passes vacuously.

THIRD, adversarially tested end to end: with jw.org injected on code `tel` the gate exits
1 naming the publisher; restored, it exits 0; the file's md5 is unchanged either side.

Registered in package.json and in verify-browser-all's GATES array — check:no-orphan-gates
confirms it, and check:quoted-scripts passes. Without a server it SKIPS loudly with the
command to start one, rather than passing quietly.
2026-09-24 07:31:01 +00:00