doxa-maps/documentation
doxa-maps-88655c70 0e0b142719 A half-finished catalogue fetch can no longer overwrite a good one
The FCBH fetcher read per-fileset video scope for 1,666 languages and that file was
committed. A later run, while live.bible.is was rate-limiting, produced 1,103 and
overwrote the good file with the degraded one. Nothing failed, nothing warned, and the
artefact silently lost a third of its content. It was caught only because git status
happened to be read, and the fix was a git checkout.

That is the third time this shape has appeared here:
  - a VERIFY_LIMIT smoke run replaced the full verification table with 30 rows;
  - dropping a dead link removed it from the corpus, so the next run forgot it was
    dead and the build put it straight back;
  - this one.
Every snapshot in this bundle is built from a network read, and a network read has a bad
day. The dangerous case is never the run that throws - that leaves the old file alone -
it is the run that half-succeeds and writes anyway.

scripts/_snapshot-guard.mjs now stands in front of every catalogue write. A snapshot may
grow freely and may shrink a little, but losing more than a tenth of the previous run is
treated as a failed fetch: the existing file is left untouched and the script exits 1.
It is deliberately not an equality check, because catalogues do genuinely lose entries
when a publisher retires something, and a gate nobody can satisfy gets switched off.
SNAPSHOT_FORCE=1 is the one loud way past it.

Proven by replaying the exact failure: feeding it the 1,103-entry version refuses the
write, names the real numbers (563 fewer, 13.8%, below the 90% floor) and leaves the
file byte-identical to the good one. SNAPSHOT_FORCE=1 writes it. An equal write passes.

All six fetchers are wired: ebible, arclight, arclight-share-urls, door43, zume, fcbh.
Re-running zume, door43 and ebible for real passes the guard at 52, 208 and 1,237 codes
and produces byte-identical data, so the guard perturbs nothing.

The one exception is the checkpoint write in the resumable share-urls fetcher, which
starts from the existing file as a cache and can only grow. It must now declare itself
with a CHECKPOINT-WRITE-OK marker, so the exemption is a statement rather than an
oversight.

New gate check:snapshot-guard fails if any fetch-*.mjs writes OUT directly without that
marker, or never calls writeSnapshot at all - a guard that is easy to bypass is a guard
that will be bypassed. It refuses to run against an empty file list rather than passing
vacuously, and --poison injects a raw write and requires the check to catch it. Chained
into build:unlocked and its doc copy kept in sync.
2026-09-24 11:59:48 +00:00
..
architecture Document why a gate cannot import plugin source, and stop teaching public binds 2026-09-22 17:40:11 +00:00
assets consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +00:00
contributing consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +00:00
readme-images Say which population the studio's count describes 2026-09-23 10:49:06 +00:00
screenshots consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +00:00
CONTRIBUTING.md consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +00:00
develop-a-plugin-and-see-it-live.md Point the golden-path doc at the self-test of the golden path 2026-09-23 10:19:40 +00:00
explanation.md consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +00:00
how-to.md Land the swarm's work: no iframes anywhere, a tree that keeps its shape, deeper facets 2026-09-22 14:51:52 +00:00
README-embed-example.md consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +00:00
README-longform.md Say which embed targets we actually verified, and which we only expect to work 2026-09-23 05:33:51 +00:00
README-SECTION-3-cli-commands.md A half-finished catalogue fetch can no longer overwrite a good one 2026-09-24 11:59:48 +00:00
README.md consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +00:00
reference-capability-exemplars.md Give nested ProfileLoader children the profile-config they require 2026-09-22 09:37:35 +00:00
reference-library-index.md Regenerate the library index, which had fallen a day behind 2026-09-23 07:16:13 +00:00
reference.md Fix the same broken token command in reference.md, and say only what was measured 2026-09-23 04:13:45 +00:00
slide-deck-outline.md consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +00:00
tutorial.md consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +00:00
why.md consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +00:00

documentation/ — pick your path

        "what is this?"          "teach me"        "how do I …?"       "look it up"      "all the detail"
             │                       │                   │                  │                  │
         why.md  ──────────►  tutorial.md  ────►  how-to.md  ────►  reference.md  ───►  architecture/
        (2 min, 1 diagram)   (first map)         (task recipes)     (tables)           (contracts)
  • why.md — the problem and the "map islands" idea. Start here.
  • tutorial.md — build your first map, no jargon.
  • how-to.md — recipes: embed, iframe, SPA, colors, CDN, mobile, tokens.
  • reference.md — config fields, build scripts, the profile-config contract.
  • explanation.md — the model in 15 lines.
  • architecture/ — the deep end: full contracts, the per-file catalog.
  • README-embed-example.md — the drop-in README used when this bundler is embedded inside a host repo.

Nothing on the simple path is longer than a few minutes; everything complex lives in architecture/, linked from where you'd need it.