Point 4 (READ THE WORD) again prints Mark 5:1-20 in full, the texts the
packet shipped before a80f4fd6e (the public-domain versions on file for
en, es, fr, de, ru and ar; the English WEB text elsewhere, as before).
Verse 19 is in bold: point4_passage_bold holds it verbatim from the same
text, so the bold is a substring match and never a second rendering of
scripture. THE HEAVENLY FATHER'S HEART follows the passage.
The passage at a readable size (24px) and the twelve points at theirs do
not share one sheet, so point 4's reading has its own page right after the
points ("4. READ THE WORD"), and the points page gains more space between
points. Both sheets fit in all 11 locales.
The gates count the new sheet (packet-render +3, poster-prints 5 sheets,
a1-structure "... points · word") and assert the passage in full again,
verse 19 in bold and the seven readings. packet-strings.json is re-pinned,
and the five bundles that compile the pin are rebuilt (country-posters,
doxa-research-map, my-upg-100-list, regions-dashboard, upg-neighbor),
with the tree and SRI regenerated.
The pair (a80f4fd6e) changed library/scripts/posters/packet-strings.json
(point 4's reading list) without re-running the pin script, so the served
copy (assets/lib/packet-strings.json) no longer matched
PACKET_STRINGS_SHA384. The print then refused it and fell back to the
bundled English block: every non-English packet printed in English.
check:poster-render-pinned has failed on development since.
Re-pinned (library/packet/poster-render.integrity.js, packet-strings only;
the generator and places pins are unchanged), and rebuilt the five bundles
that compile the pin in: country-posters, doxa-research-map,
my-upg-100-list, regions-dashboard, upg-neighbor. The tree and SRI
regenerated. check:poster-render-pinned: FAIL (2) on 7744a8418, PASS here.
The SPARSE series line rounds its total to tens -- 173 reads as ~170, 28 as ~30
-- because a series that large is read as a size, not counted. Below ten the
rounding breaks: China is the only 3x4 country sheet, so its tier has one member,
and Math.round(1 / 10) * 10 = 0 printed "1 of ~0 maps".
Under ten the line now states the exact count, "map 1 of 1" -- the same text
the dense layout already prints for China, so the move to SPARSE is not a
regression for it on the print.
PROVED: China rendered SPARSE reads "— map 1 of 1" with 0 occurrences of "~0";
and Sudan rendered after this change is BYTE-IDENTICAL to Sudan rendered before
it, so no series of ten or more changes -- the 173 country sheets and the
UPG-100 zones are unaffected.
Not decided here: China's thumbnail says 174 of 174 and its print 1 of 1. That
is per-tier against whole-series numbering, the pre-existing R-CHN-SERIES-TIER,
left for a product decision after the demo.
poster-render.js is sha384-pinned; re-pinned so check:poster-render-pinned
accepts it.
The rule lives in library/packet/posterSheets.js so the browser and the Node
generator cannot disagree, which puts it in every bundle that generates a country
sheet. landing-preconditions step 4 named country-map, country-posters and
regions-dashboard; each built with BUNDLE, then tree, then fix:sri.
No source of country-posters changed -- only its compiled bundle catches up with
the library it already imported.
The page reused the life calendar's grid and counted its shaded boxes in
weeks ("0.61% · 32 weeks"), under a week axis and a year axis, with a lede
calling it "one human life in weeks". On this page a box is an equal share
of the list's PEOPLE, so the same 5,200 boxes are now drawn with no week or
year axis, and the legend states each side in people: how many follow
Jesus and how many do not yet, each with its share (the two percentages
sum to 100 as printed), plus how many people one box stands for. Numbers
are formatted in the reader's locale.
The labels become "Followers of Jesus" / "Not yet following Jesus" and the
lede drops the weeks, in all eleven languages; the served strings file is
re-pinned. check:evangelical-share asserted the weeks figure; it now
asserts the people totals, the bare percentage and the absence of weeks
and axes, with the life page (which keeps its axes) as the negative
control. check:packet-variants found the grid by an exact class string
and now matches the class prefix.
A single-file build inlines every import, dynamic ones included, so
packet-strings.json (~115 KB minified: every language's prayer points,
captions and headings) rode in every bundle that can print a packet, list
or card deck. With the wall's print messages translated, country-posters
crossed the 1.5 MB ceiling (1,502,226 bytes).
The table is now served at <assets>/lib/packet-strings.json and fetched at
the first print by library/packet/packetStrings.js, the same move as the
sheet generator and ne-places.json:
- Pinned like them: PACKET_STRINGS_SHA384 in poster-render.integrity.js
(pin-poster-render.mjs); the text is hashed before it is parsed, and an
unmatched table is not used. These strings are laid into the print
document, so an unverified copy never reaches it.
- English is bundled as the fallback (a named import of the JSON, so only
the en block ships): with no asset base, on file://, on a network error or
an integrity mismatch the print comes out in English and the console says
why. A print never fails for want of this file.
- generate-tree copies it into the pool AND the served allowlist; verified
by regenerating the tree (the served copy is byte-identical to source).
- check:poster-render-pinned, two-sided: the pin matches source and served
copy, the genuine copy loads all 11 languages, a tampered copy falls back
to English (mutation-tested: disabling the hash check fails it), and an
unserved one falls back rather than failing.
- sha384 is one shared helper (library/packet/sha384.js).
Sizes: country-posters 1,378,463 (was 1,484,053), my-upg-100-list 1,143,456
(was 1,243,509), regions-dashboard 973,802 (was 1,079,448).
The served generator was run by importing its VERIFIED text from a blob: URL, which fails closed on a
partner page whose Content-Security-Policy forbids blob: scripts: safe, but no generated sheet there
(dx-shepherd's FIX-POSTER-RENDER-LOAD-WITHOUT-BLOB-FOR-STRICT-CSP).
The tree now also serves a CLASSIC-SCRIPT copy, lib/poster-render.global.js: the same file under a
mechanical transform (strict mode kept; the one export becomes window.DoxaPosterRender), written by
generate-tree with the function the pin is computed from (globalVariant, pin-poster-render.mjs) and on
its served allowlist — produced by regenerating the tree, not copied in by hand. posterSheets keeps the
proven path first; only when the blob import itself is refused does it load the classic copy with
<script integrity=sha384-…>, so the browser verifies it natively. An integrity FAILURE never falls back:
it throws before any import is tried.
check:poster-render-pinned adds: the classic copy matches its pin and the served copy, and — run as a
real classic script — draws the module's sheet byte for byte. The <script> path under a strict policy
needs a browser (rega). country-posters 1,473,964 bytes (ceiling 1,500,000); verify-bundles-shipped
PASS; gates:pre 74/74.
Following the generator's pin (studio's follow-up): the Natural Earth table the sheets fetch at print
time (boundaries/ne-places.json) is data, not code, but pinned the same way — its sha384 sits beside the
generator's in poster-render.integrity.js (bun run pin:poster-render writes both), and posterSheets hashes
the fetched text before parsing it. A table that is not the pinned one is not used: the sheet draws no
city dots and says so in the console, rather than plotting places it cannot vouch for.
check:poster-render-pinned, two-sided for the table as for the generator: the pin matches the source
and the served copy; a FORGED table (every name replaced) yields exactly the sheet drawn with no places;
the genuine table IS used (its sheet differs) — the probe's pins now span Chad, because a single pin
framed too small an area for any place to fall inside it and the check could see nothing.
check:poster-browser-equiv still byte-identical; gates:pre green.
Code loaded at print time executes on partner pages, so it is pinned like every embed. The bundle
carries the generator's sha384 (library/packet/poster-render.integrity.js, written by
`bun run pin:poster-render`); posterSheets fetches <assets>/lib/poster-render.js as TEXT, hashes it
with WebCrypto, and on a match imports the verified text from a blob: URL — never the network URL a
second time. A mismatch refuses to run, with a message naming the URL and both hashes; the print falls
back to what it can do without the generator.
The page base is captured when posterSheets evaluates, and every URL it resolves (the generator, the
boundaries, the places) resolves against it — not the bundle's location, and not a URL a single-page
host changed after load.
check:poster-render-pinned (gates:pre), two-sided: the pin equals the sha384 of the source and of the
served copy (a stale pin names the command — proven by an unpinned edit); a TAMPERED copy is refused
and its planted side effect never runs; the genuine copy loads and draws. check:poster-browser-equiv
still passes byte-identical through the pinned path. country-posters 1,472,734 bytes (ceiling
1,500,000), my-upg-100-list 1,232,508, both carrying the pin; verify-bundles-shipped PASS; gates:pre 74/74.