UNVERIFIED: written, not yet run end to end; the handoff stopped it here.
The rebuild runs in the working tree, so it can read what no commit carries: an
untracked locale a glob picks up, a VITE_* key from an untracked root .env that
the code reads (apiBaseUrl.js reads VITE_API_BASE_URL), a build env var such as
DOXA_QA. Bytes built from those prove nothing about freshness, and the landing
precondition caches a PASS per tree id. Build env vars are now scrubbed from each
rebuild; vite.config.js writes down every file a build read when DOXA_BUILD_INPUTS
is set (only this gate sets it); a bundle whose rebuild read an untracked file or
such a key is NOT MEASURED (exit 75), naming the files.
Measured before the stop: an untracked .env.local with an unreferenced VITE_ key
leaves all-people-groups byte-identical (cf536c63), so only keys the code reads
are held against a bundle.
The gate hashed each component's entire emitted CSS, so any unrelated style change
in these four files (Regions giving three :host rules position:relative) failed it,
and could pass only by re-baselining a guard that exists to prove something else.
It now hashes what library/styles/portedGlobals.js emits, found in the executed CSS:
each cyrillicHeading and strongMedium rule with the selectors in front of it, and
each scrollbar-gutter declaration with its rule's selector.
Re-baselined on the tree the old gate passed on. Proven both ways: re-scoping
GoalCard's strong rule, changing the module's Cyrillic letter-spacing and dropping
a file's ported rules each fail it; --write refuses to baseline an empty block.
Ported from rega's snipgate and mbxcheck probes (card GATE-EVERY-PUBLISHED-SNIPPET-DRAWS-A-MAP). Two scripts so the keyless arm never waits on a token: check:snippets-draw pastes each production embed.script and requires a MapLibre map to draw (a hooked instance with its style loaded via a setter trap on the engine global, a sized canvas, tiles 2xx, no failure note, 0 page errors); check:snippets-draw:mapbox does the same over embed.scriptMapbox with only the token filled from MAP_TOKEN, NOT MEASURED without one and never printing it. Composites that open on a list (regions-mfe, upg-100-mfe) are pressed to their map view under the settle rule first, since a hidden pane still draws a canvas. The browser slot is checked before any server starts, and the tree server is spawned --exit-with-parent. Registered and declared on demand until rega's first run; it joins verify-browser-all after.
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.
verify-browser-all's rule is that every gate listed there was run before it was
listed, so this waited for a real run rather than being registered on the
strength of the code reading correctly.
Measured, on the browser seat: the positive arm PASSES 9/9, and the mutation arm
-- the maplibre selector inside bottomControlInset broken to a name that matches
nothing -- FAILS on cases 1 and 5, the two whose pages carry only a maplibre
attribution. Cases 2, 3, 6 and 7 pass under the mutation by design: they assert
the inset is 0, so a selector that stops matching cannot make them fail. They
guard the opposite error, reserving space that is not there.
Registered in all three places: the script, its //scripts description, and
verify-browser-all's list. Deliberately NOT in gates:pre -- every assertion rests
on getBoundingClientRect, so it needs a browser and exits 75 NOT MEASURED
without a slot, which is never a pass.
check:popup-pan-controls proves the ARITHMETIC: given a band height, the pan
clears it. Nothing proves the band height is real. If bottomControlInset returns
0 -- a selector typo, an engine renaming its control classes, a control moving
outside the container -- then panPlanFor is handed bottomInset 0, behaves exactly
as it did before the fix, and check:popup-pan-controls still passes every case
because its arithmetic is still correct. The bug would be back with both gates
green. That is the failure this guards.
Seven cases against a real DOM, running the SHIPPED bottomControlInset read out
of the SFC (it is not exported), never a re-typed copy: a visible attribution
reserves a band EQUAL to what the DOM independently says, not merely non-zero,
which a stub returning 1 would satisfy; no controls reserves exactly 0; a
zero-sized control reserves nothing, since counting it would reserve the whole
map and refuse every popup; the taller of two corners wins, because the pin hint
is bottom-left while the attribution is bottom-right; a wrapped attribution
reserves more than one line, which is what a hard-coded constant gets wrong at
390; a control outside the container is ignored; a null container returns 0
rather than throwing.
It needs a real browser: every assertion rests on getBoundingClientRect, and no
DOM library here computes layout, so a headless stand-in would return zeros and
pass vacuously.
NOT REGISTERED. Browsers are rega's seat, so this has not been run, and
verify-browser-all's rule is that every gate listed there was run before it was
listed. It is registered only after rega's PASS.
Its population was a hand list of 19 names, and it had drifted: the PRODUCTION country-map was never on
it, so its LIST-FETCH (the header's list prop never applied) passed unseen, and big-picture fetched the
list correctly with nothing to catch a regression. The population is now every plugin-designs/<b>/
with an index.js, minus an EXCLUDED map with a reason per entry (empty today), so a new bundle is
measured the day it lands: 28 bundles instead of 19.
Two fail-open paths now fail. `--only <name>` naming no measured bundle printed "PASS: 0 bundles"; it
exits 2 and lists what is measured. A listed bundle whose folder moved was skipped by an existsSync;
the population is derived from the folders, and an EXCLUDED entry naming no bundle is a stale decision
and also exits 2.
Measured over the whole tree, only two bypasses surfaced. country-map's LIST-FETCH is fixed by regions'
commit just before this one (same round, as it must be: this alone goes red). demo-map's LIST-DSM gets a
narrow ALLOW with its reason: it is scaffolding that demonstrates the raw DataSourceManager path, off
both reader shelves, and its other props are still measured. PASS: 28 bundles, 9 self-tested rules.
The file walk read internal/ only, and the chain arm counts package.json NAMES, so a plugin-local
check-*.mjs that nobody registered was invisible to both: on 2026-09-29 six were run by nothing
(dx-shepherd's census; exp's four have since been registered). Every tracked gate-shaped file under
plugin-designs/ or library/ must now be run by PATH from a script or another file's code, carry
@one-shot, or sit in the shrink-only `files` baseline for its owner.
Two rules keep it honest. Comments are stripped before searching, since a file mentioned in prose is
not run by it. And a basename counts only where no other gate file shares it:
library/scripts/research/verify-map-paint.mjs (199 lines, run by nothing) shares its name with
internal/verify-map-paint.mjs (342 lines, what verify:map-paint runs), so a name match let one file's
registration vouch for the other. Self-tested on planted cases (unregistered, comment-only, shared
basename). On the real tree it finds 4, now baselined for their owners:
library/scripts/research/verify-map-paint.mjs likely a stale copy
plugin-designs/doxa-research-map/scripts/check-reduced-motion-jumps.mjs
plugin-designs/gospel-resources/scripts/verify-translator-org-links.mjs a data tool? @one-shot or register
plugin-designs/research-map-plus-grid/scripts/check-ui-strings-have-keys.mjs
The chain arm's writer now keeps every key of the shared baseline file.
ApproxPinNote moved from useOptionalT to useOptionalI18n (which wraps it) so its formatList names follow the sentence's own locale. The per-site register still asserted the old helper and failed. The row now names useOptionalI18n, as SemanticTreeLegend, PosterWall and all-people-groups already do, and still asserts the site passes its own APPROX_FALLBACK.
The popup auto-pan had no gate. Its founding bug was invisible to the obvious
one: measured at 390 on a 366x732 map, the pan fitted the popup inside the
container, the cut really was zero, and a real click on Full Profile still did
nothing, because the top element at the button's centre was
div.maplibregl-ctrl-attrib-inner on one pin and span.pin-hint-pill on the other.
Two pins that looked fixed and were not.
So this asserts the property that matters -- where the popup ENDS UP relative to
the band that takes the click -- rather than whether it was clipped.
It runs the SHIPPED panPlanFor, read out of the SFC with new Function because it
is not exported, the same way check:optional-t-equivalence reaches useOptionalT.
A re-typed copy of the arithmetic would pass forever while the component drifted,
which is the one failure a geometry gate must not have. POPUP_OFFSET is read from
the source too rather than assumed.
Each case states the resulting geometry independently instead of re-deriving the
expected number from the same formula: a self-consistent oracle cannot see an
error that shifts both sides, which is how a 12px offset hid here once already.
The band is measured at runtime, never hard-coded, so the rule is swept across
0-120px rather than pinned to one number -- 120 is far beyond anything observed
and is there to prove the rule, not the value. Also swept across every width the
component renders at, since below 768px the container is 2x width.
Mutation-tested three ways, each failing the case written for it: ignoring
bottomInset (the pre-fix behaviour) fails 15 cases; reserving only half the band
fails; dropping the Math.max clamp fails exactly the negative-inset case.
24 cases, registered in all three places, verified by re-reading the parsed file.
PocketBase's link is /_/#/pbinstall/<token> (two l's). The forward rule matched \bpbinstal\b, which cannot match 'pbinstall', so the terminal showed 'Launch the URL below' with no URL below it (repo, testing live with PocketBase 0.40.4). The rule is now pbinstall?, tested on the line as PocketBase prints it rather than on a guessed spelling: the instruction, the URL and the CLI alternative are shown, the banner and dashboard URL stay quiet.
check:approx-pin-note server-renders ApproxPinNote in Node, and it crashed: with no locale argument
and no runtime locale, formatList fell through to detectLocale(), which reads `document`. The page
lookup is now a guarded last resort. ApproxPinNote also passes its sentence's own locale (the
host's vue-i18n when installed, else '' so the runtime/page chain applies), so a host with its own
i18n can never get an Arabic sentence with a list in another language.
The gate now expects the new English ("Koraga and Maila", the list grammar this change exists for)
and adds the Arabic case: two names joined with و, never an English comma inside Arabic prose.
Poisoned by putting join(', ') back, both of those cases fail; restored, it passes.
With the backend's output going to .pb/logs, PocketBase's one-time 'create your first superuser' link and its command-line alternative went only to the log, so the HOWTO's admin screen at /api/pb/_/ was unusable on a fresh .pb without knowing to look there (repo found it testing the change live). Those lines are a step a person must take, so they are forwarded like errors and 'address already in use'. The banner and URLs stay quiet. Tested against the regex read from the source.
PocketBase prints about 230 lines of startup SQL, and the dev plugin forwarded every line to the
terminal, so Vite's "Local:" address scrolled out of view right after it appeared. The fourth fresh
reader of the README saw it happen.
Each dev backend's output now goes whole to .pb/logs/<name>.log (gitignored), and the terminal gets
only the lines a person must act on: errors, warnings, panics, and "address already in use", the
line the README and HOWTO tell a reader to watch for before setting DX_PB_PORT. The started line
names the log file. Tested against representative lines (in use, WARN, panic shown; SQL and the
banner quiet). The live check rides the next fresh-reader run.
Two production maps paint the doxa-regions layers directly -- 35
setPaintProperty('regions-fill'|'regions-border') calls between them, 10 in one
file and 25 in the other. Twenty-one pass their value through a one-line helper
that wraps it in the same ['case', ['has','iso_3166_1_alpha_3'], v, v]
expression, and each map defined its OWN copy of that helper while already
passing it into the library composable that paints with it. The library owned
the paint logic and not the expression, so the two copies could be edited apart
and the same "idle" would render differently on the two maps, with nothing
failing. No existing check covers this: the nearest one reads a different
component entirely.
The gate asserts the idle paint is single-sourced: the library exports the
helper, neither map keeps a local definition, and neither inlines the
expression. It deliberately does NOT require all 35 writes to route through the
library. The raw multi-line writes are highlight states and outline resets that
differ by design and by map; demanding uniformity there would make this red on
the day it was written, which is how a gate earns being ignored. It also does
not assert how OFTEN each map repaints -- one repaints the idle state more
than the other and has a dynamic call the other lacks. That is a real
difference in when, and flattening it is how a map goes blank. This checks that
the maps agree on what idle looks like, never on how often they say it.
Two-sided. Poisoning it un-exports the library helper and re-inlines a local
copy in a consumer; three assertions flip from ok to FAIL, each catching its
own planted regression, and the run exits 1.
Registered but NOT chained yet: it is legitimately red until the second map
adopts the library helper, and chaining a red gate would break the shared run
for everyone else. It joins the chain with that change.
Building it found a third copy nobody had counted, under a different name, and
two bugs in the gate's own first draft: a block-comment stripper that ate the
declarations it was meant to find (so every single-source assertion passed
while both files plainly defined the helper), and an exemption for lines
mentioning the helper, which hid a one-line definition that mentions it. Both
were caught by the rule that a gate must fail when poisoned.
check:poster-browser-equiv is chained in gates:pre and exited 75 NOT MEASURED
every run. Its input, out/_records.json, is gitignored and written only by
generate.mjs against a live API, so on CI and on any box without credentials the
gate never looked at anything. The runner reported that honestly rather than as
a pass, but an identical NOT MEASURED every run is indistinguishable from an
unwired gate -- and this is the gate that proves a sheet generated in the browser
is byte-identical to the one Node writes.
An equivalence check needs the SAME input on both sides, not fresh input, so a
committed fixture loses nothing it was measuring. The fixture is the 124 public
records the two arms actually consume: Nepal's 34 for the country arm, and the
90 slugs the Gujarat zone selects for the zone arm. Same fields the served maps
show; no other data.
Verified equivalent to the full 2,106-record cache: identical zone pins in the
same order, identical Nepal set, count 90, pop 3,164,770, series {2209, 28, 1}.
Record ORDER is load-bearing -- it drives draw and label order -- so the fixture
keeps the cache's order. A first cut sorted it: every aggregate matched and the
pin SET matched, while the order differed. Sorting a fixture would have quietly
changed the sheet it produces.
generate.mjs gains RECORDS_FILE so a caller can name the offline input instead of
only the API-written cache. It still fails closed without an API when no offline
file is given. The gate passes it the very file the browser arm read, so both
sides are fed the same records by construction.
The gate now says which input it measured, because the full cache widens coverage
to all 175 countries where it exists and the fixture is the floor everywhere else.
Two-sided, on this tree: with the fixture it PASSES byte-identical on both arms
(834,174 and 921,522 bytes). Dropping the province boundaries at
posterSheets.js:206 -- the defect this gate's header records, where a country
beside India or China printed without them -- makes the country arm 251,576
against Node's 921,522 and the gate exits 1. Restored, it passes again.
research-map-plus-grid and regions-mfe draw a map through a child element, so their isMap is false,
and their published snippets had no engine preload and no scriptMapbox. A pasted composite still
draws on Mapbox given provider "mapbox" and a token (measured), so this is the snippet's
convenience, not a broken map.
generate-tree now decides after every profile's isMap is known: a composite (nested-elements-mfe)
whose OWN composes reach a map profile gets the map form of the default snippet and the Mapbox
variant. It reads the profile's own composes, never the bundle's hoisted list, which the bundle's
plain leaves (charts, data-tables, the poster wall) share while drawing nothing. The gallery shell
is left out until its passing of provider to its tabs is measured. Regenerated: exactly those two
profiles' embed changes, in both manifests; nothing internal leaks into the output.
check:snippet-engine asserts the same rule on the built manifest (21 map snippets, 19 maps + 2
composites). Its self-test adds a composite stripped of its variant, which fails. Against the
manifest as it stood before this change, the widened gate fails on exactly these two composites;
on the regenerated one it passes.
Every owner's sweep has landed. The two remaining lines were public-hygiene-check.mjs's pattern list, which must spell the role word in order to catch it on the way out, as this gate's own file must. It is exempt by name in a one-file list with its reason, not by any wider rule. The baseline is now empty: from here, every line that names the role is a new reference and fails.
A poison run answered 404 to posters/<id>.html for the browser only, and the gate still PASSED.
It counted the .html REQUEST, and its failure pattern (not available|failed|blank|error) never
matched what the wall then shows, "poster not found (404)"; the HUD reader did not even collect
that text. So it could not fail on the one path it exists for.
It now requires the .html to ANSWER 200 and a print frame to actually OPEN (counted at creation
with the appendChild hook the degrade gate already uses, since the frame removes itself after
printing). "not found" is read as the failure it is, in the HUD reader, the poll and the verdict.
Its output reports both outcomes. The browser runs are rega's: the positive arm must still pass,
and the 404'd source must now fail.
check:coverage-consistent:chained sets the link-age threshold out of reach. The proof that this narrows its scope and does not disarm it is the poison arm: its clock is computed RELATIVE to the threshold, so all 15 clauses (A-O, L included) still fail when poisoned at any setting (regions measured it at 99999). That proof was an orphan; it now runs on every landing, like the other gates' self-tests. 1.6 s.
check:dxlife walked the whole root-dx-life umbrella, so git worktrees of doxa-life (wt-rg-*) and
a dangling one (research-map-clean, whose .git points at a path that no longer exists) read as
+4,524 "added" files and held it red while the clones were untouched. It now skips any directory
whose .git is a FILE beginning `gitdir:` (keyed on that file, not on `git worktree list`, which
cannot see the dangling one) and .cache with the other generated dirs, and compares the baseline
only within that scope. Result against the 2026-09-20 baseline: 2,844 clone files, +0 -0 ~0. So the
law has held since that date, and the baseline stays as it is: re-taking it would reset the evidence.
Nothing is deleted; research-map-clean may hold the only copy of its work. With no clone on the
machine it reports NOT MEASURED (75) instead of exit 2, so chaining it cannot fail other machines.
check:coverage-consistent fails when the oldest verified link is over 45 days old: its verdict
depends on the calendar, and chained it would turn gates:pre red on 2026-11-08 with no change
(regions measured the margin). Every other clause depends on code and data and is worth running
on every landing, so gates:pre runs check:coverage-consistent:chained (the age knob set out of
reach). The plain script is declared on demand as the freshness run, with that reason. Its poison
self-test still proves every clause can fail.
gates:pre 94/0/2 of 96. The orphan baseline shrinks to 105; 7 checks are declared on demand.
From regions' triage of the headless orphans, every candidate re-run on this round's tip before
it was chained (all exit 0; the slowest, no-conflict-markers, 8.2 s):
gates:pre click-sheet coverage-consistent dashboard-filter-a11y filter-multivalue hover-channel
kit-options-reachable layout-key-namespaced legend-mirrors map-unmount-race
no-conflict-markers series-isolation snapshot-guard stale-sweep-claim
surfaces-read-the-tier translator-contacts (source readers)
gates:post embed-base link-coverage no-public-binds shared-reached tree-weight (built tree)
stale-sweep-claim reports SKIP while 1 of 1184 gap codes is undecided: it measures once the pool is
fully read, and the wave shows it as not measured until then, never as a pass.
Declared on demand instead: catalog-currency and published (they read the published host),
composite (minutes of rebuilds; landing's fresh step does this per landing), hardcoded-english
(report-only while its findings are triaged; its self-test is chained), and shot-fonts (it checks
the SCREENSHOT MACHINE's fonts, so chained it would fail a contributor's gates for their fonts).
Two proposals were declined: check:settled stays on demand (it fails inside any build by design,
and passing standalone is how it was wrongly chained before), and shot-fonts as above.
The orphan baseline shrinks 131 -> 106. gates:pre 93/0/2 of 95, gates:post 91/0/16 of 107.
check:no-orphan-gates only REPORTED scripts outside the chains, and the list grew to 87 main checks.
That day two of them hid real defects: check:i18n-never-translated, while a dead key identical in all
11 catalogues sat on development for hours, and check:auto-engine, which asserted a retired engine
rule that nothing ran. The report also matched by substring, so check:hardcoded-english counted as
run because its :self-test is in gates:pre.
The arm now decides. Every check:/verify:/smoke: script must be:
reached by exact name from gates:pre, gates:post, the browser suite's GATES or the build,
following what those scripts `bun run`;
on demand in internal/fixtures/on-demand-checks.json WITH its reason (check:settled, whose
reason this gate already documented: it fails inside any build by design);
baselined in internal/fixtures/orphan-checks-baseline.json, today's 131 orphans, for their
owners to chain, declare or delete. That list only shrinks (--shrink).
A new unchained script fails by name. A self-test runs first on every invocation. Proven two-sided on
the real tree: a planted unchained script exits 1 naming it; swapping exact matching for the old
substring test makes the self-test refuse; restored, it passes.
A browser given a 390px viewport still lays the page out at the 980px desktop
fallback unless the document declares <meta name="viewport">. Measured in
Chromium at 390x844 with isMobile:true:
without the meta: window.innerWidth = 980 (body 964px)
with the meta: window.innerWidth = 390 (body 374px)
Nothing warns. The page renders, the probe reads its numbers, and the check
goes green having measured a viewport no phone has. A full set of mobile runs
was taken this way before the layout width was ever compared to the requested
one, so the conclusions drawn from them described the wrong viewport.
Three gates already read window.innerWidth and none compares it to the width it
asked for, so all three would pass at 980 while reporting 390. This asserts the
property that makes their numbers mean what they say.
Checked on the built artifact rather than the generator template: a template
containing the right string proves nothing about the HTML that shipped. No
server needed.
width=device-width is required specifically, not just the tag: a meta carrying
only initial-scale still lays out at 980, so matching the tag name alone would
leave the gate unable to fail on its own founding bug.
The 28 sheets under assets/posters/ are exempt by their stated shape - a fixed
paper canvas (.stage{width:1500px;height:2000px}) has no mobile layout to get
wrong. Keyed on that property, not the directory, so a poster keeps its
exemption if it moves and an app page in that folder does not inherit one.
Mutation-tested against the real tree: stripping the meta from a shipped page
fails it, and replacing it with an initial-scale-only tag fails it. 8 self-tests
cover both, plus the exemption and its inverse.
check:gospel-whiteboard does not cover the adoption in the previous commit. I
poisoned GiMap into whiteboard:true — the exact regression that blanks the
production geographic map — and it still exited 0. It asserts chart geometry
(projection, radii, the bounded gap) and imports no component, so a call-site
option is invisible to it. Its pass was never evidence for this change.
check:board-map-options asserts what actually regresses: every
createWhiteboardMap call states `whiteboard` explicitly, and a geographic caller
(whiteboard:false) also states `boundaries`. A caller that inherits defaults
gets the whiteboard's, which are the inverse of a real map's.
Two-sided, because a check that cannot fail is not evidence: poisoned, it exits
1 naming GiMap.vue; restored, 0. Both negative controls run in the gate itself —
a call omitting the option must be judged non-compliant, and a compliant call
must not trip it, or the gate would just ban the helper.
Registered in all three places: script, gates:pre, //scripts. gates:pre is 77.
Lines swept on development since the capitals form joined the gate: research-dashboard, people-groups-grid, my-upg-100-list, regions-dashboard and others. --shrink drops only what is gone; nothing is added.
The suite lists a gate only after it has been run, so these waited for one. On 5b05f019e, in the
browser slot:
check:auto-engine PASS. no token -> MapLibre, 0 Mapbox requests; a host token alone ->
MapLibre, 0 Mapbox requests; ?provider=mapbox + the token -> Mapbox,
carrying it
check:engine-contract PASS. 2 providers x 3 load states: the engine asked for, or a visible
failure, never a substitute
Both entries record those results. check:engine-contract cannot demonstrate a failure at runtime
without the defect rebuilt into a bundle; its falsifiable arm is the source gate's self-test, so
check:no-silent-engine:self-test now runs in gates:post beside the gate it proves. It was chained
nowhere.
Its case 2 expected a host token alone to draw Mapbox, the rule until 2026-09-28. It was in no
chain, so it never ran against the change that made that false, and nothing else measured the case
the change is about. The cases now:
1 no token: MapLibre, zero Mapbox requests, painted, no prompt (unchanged)
2 a host token alone: still MapLibre, painted, zero Mapbox requests
3 ?provider=mapbox with that token: Mapbox, carrying that token
It joins gates:post beside check:leaf-engine-per-provider, together with check:engine-contract
(gospel's resources-map uses the engine it was asked for, or fails visibly), which was also in no
chain. Both are browser gates: NOT MEASURED outside the browser slot, measured in it.
Since 2026-09-28 resolveMapProvider returns MapLibre for 'auto' whether or not a token is present;
only an explicit provider selects Mapbox. Four comments still said "Mapbox when a token is
available": useMapProvider.js's own header (and its note on the `token` argument),
useMapInstance.js at the provider option, check-provider-switch.mjs's contract note, and the
PROVIDER note that generate-tree writes into every leaf page.
The leaf page's note now says what the page does: it finds a token first only when one is on offer,
because place search uses one on either engine, and the element decides the engine. The 64 leaf
pages are regenerated and differ only in that note. The three JS files change in comments only
(compared minified, without renaming).
The README says a plugin folder holds "an index.html for previewing it", and a fresh reader
followed that to /plugin-designs/<plugin>/index.html under `bun run dev`. leafRewrite reads any
<a>/<b>/index.html as <bundle>/<profile>, so the request reached the leaf route as bundle
"plugin-designs" and answered only {"error":"unknown bundle"}, which pointed nowhere.
The 404 now carries a hint: for a folder that is a plugin, its own page
(/__leaf/all-people-groups/all-people-groups, taken from the same registry the gallery uses);
otherwise the /__leaf/<plugin>/<profile> shape and the manifest that lists every plugin. The
status and the error stay as they were. The README now names the page as well.
studio's judgement on the nineteen REGION_OF findings: the English values are
identities, not reader text. Node ids derive from them, and every surface
that shows one translates it at render — upg-100-list.vue:440 and
poster-wall.vue:129 both read REGION_KEY[english] and call t(`poster.${…}`).
The exemption is conditional, and the condition is the whole rule. It holds
only for a name that IS a key of REGION_KEY. A region added to REGION_OF
without a matching key falls through the `|| english` arm and renders English
in all eleven locales, which is the defect upg100 already shipped once with
Americas and Europe. So the gate keeps working as the table grows rather than
blessing the file.
Deliberately not "object values" and not "legend-tree.js", as studio asked:
either would exempt the next table nobody has checked.
The parser had no newline requirement in the end, and finding out why is the
part worth keeping. The first version anchored on a closing brace at the
start of a line, so it never matched a REGION_KEY written on ONE line —
regionKeyNames returned null, the rule exited early, and BOTH two-sided cases
passed while the exemption never ran once. Poisoning the membership test
produced no failure, which is what exposed it. With the anchor gone the
poison fails, so the conditional is now proven load-bearing.
.js findings 41 -> 34.
territory-counts.js is `{ 'Andhra Pradesh': 81, 'Assam': 15, … }`, and those
keys are matched against props.shapeName off the map data. Translating one
breaks the join silently: the count goes missing and nothing errors. Same
inversion as a lookup table whose values are i18n keys, except the values are
numbers, so there is no key to resolve and that rule cannot vouch for it.
Judged on the whole object, not one property. A single numeric value proves
nothing — a map of sentences with one count in it would exempt the sentences.
Every value must be a plain non-key literal, there must be at least six of
them, and the key itself must read as a name: anything with sentence
punctuation stays loud whatever its value is.
Two refinements found by running it rather than reasoning about it. The word
limit is eight, not six, because "Dādra and Nagar Haveli and Damān and Diu"
is one real union territory and the longest key in a 46-entry table; the
punctuation test is what separates a name from a sentence, not the length.
And a bare key is now tried against every area, because legend-tree's
REGION_KEY maps a region to 'region_middle_east' and the call site reads
t(`poster.${…}`) — a suffix table is exempt on the same terms as a dotted
one: the key it names must resolve.
Four two-sided cases, and one of them exists because poisoning found the
first three did not exercise the whole rule. Removing the all-values-are-data
check left every test passing, because the rule only ever exempts KEYS and
those fixtures caught their prose as VALUES. The case that bites is a table
whose keys are names and whose values are prose: a label table, not a join
table, where the keys must still count.
With the exemption in, .js files are scanned. 220 .js files were invisible
against 142 .vue. The .js findings fall from 67 to 41.
A wall whose host passes `sheet` now generates its poster first, from the served
lib/poster-render.js, and never requests posters/ at all. The two gates for the stored-file path
predate that, and neither measured anything since:
- check:poster-print-fallback blocked only the .webp, so the generator printed and the gate saw
0 .webp and 0 .html requests: a FAIL on two served trees that said nothing about the fallback.
It now aborts lib/poster-render.js and its classic twin as well, so rasterising posters/<id>.html
is the one way through, which is what a reader gets when the generator cannot load.
- check:poster-degrades looked for a tree serving neither stored file and SKIPPED with exit 0 on
any other. The served tree ships the sources now, so it skipped every time and the skip read as
a pass. It now forces the worst case in the browser (every posters/*.webp and *.html answers 404,
the generator is aborted) and asserts the honest refusal on any tree. No wall url is NOT MEASURED.
The fallback itself is live code, not dead: it is the path whenever the generator is absent or fails.
The gate matched the role word only as `Driver`. Lines written in capitals, such as `DRIVER: "…"`,
`THE DRIVER'S …` or a `…-DRIVER-REPORTED` card id, never registered: 42 of them sat in shipped text
while the owners' sweeps, measured by this gate, read as nearly finished. research-dashboard alone
carries 24, most of them verbatim quotes.
The pattern is now `\b(?:Driver|DRIVER)\b`. A GPU or device driver, WebDriver and DRIVER_MERGE still
do not match, and the self-test now plants the capitals form as well. Three of the 42 are reworded in
this round. The other 39 join the baseline in a one-time re-measure, the only time it has grown:
15 of the 21 baselined lines remain, the 6 that repo's docs sweep removed are gone, and every added
key is a capitals-only line. It holds 54 lines in 20 files and only shrinks from here. Two of the
additions are code terms rather than citations (a "CAT-1 DRIVER CONTRACT", a "BATCH-UNION DRIVER");
their owners can reword them in their sweeps.
RegionCountryMap's note on the projection override said who ruled it out, in capitals the reference
baseline's pattern never matched; it now says what was decided and why the globe stays. The
mobile-gating header and a people-groups-grid census entry carried internal card ids that named their
source; the ids are shortened to what they describe.
RegionCountryMap.vue and check-mobile-gating.mjs change in comments only: script, template and styles
are identical before and after, compared without comments and without renaming. The census card field
is informational; nothing matches on it.
`throw new Error(`head ${n} tail` + detail)` puts a BinaryExpression between
the literal and the NewExpression, so the parent-only check saw the `+` and
not the throw, and reported a thrown message as a reader-visible leak.
Found while measuring the .js scan on legend-tree.js:392, and my first
report of it was wrong in a way worth recording: I told dx-shepherd the
developer-site exemption "is not firing on the .js path". It fires fine.
The gap is concatenation, and a .vue file concatenating inside a throw hits
it identically — the .js scan only surfaced a shape that is rarer in
templates. Three .vue findings cleared when this landed, which is the proof
it was never .js-specific: 92 -> 89.
Only `+` chains are walked. A string built with + is still the same argument
to the same call; any other expression type between the two is a different
construct and should be judged on its own site.
Two-sided: a thrown message built by concatenation goes quiet, and a
concatenated sentence that is NOT at a developer site still counts.
The .js scan itself is NOT in this commit. It needs the data-map exemption
first, or it hands two owners ~60 findings that are lookup keys they have
already classified.
(cherry picked from commit f96381481b)
The shell's comments carried 23 of the reference baseline's lines, one more written in capitals that
the baseline's pattern never saw, and four internal card ids. Each is reworded to keep the engineering
reason: the 404s, the grey pane, the scroll trap, the device rule and the curated order all stay
explained. Quotes became the rule they stated.
Comments only. The script (esbuild, whitespace and syntax only, no renaming), the template (compiled
without comments) and the styles (comments removed) are identical before and after. The baseline
shrinks by the 23 lines that are gone.
exp's last experimental seam. A print page assembled by concatenation —
'<html lang="' + locale + '" dir="' + dir + '">' — leaves the quasi '" dir="',
which is the seam between two attributes rather than between two CSS
declarations, so the seam shape added for exp does not cover it.
The cause was smaller than the symptom: dir and lang were the only standard
attributes missing from the HTML-fragment list that already holds class,
href, src, alt, loading, width, height, style, id, target and rel. They are
attribute names, not text a reader sees, and every RTL-aware print page
writes both.
Asserted on the shape rather than through a fixture, for the reason the CSS
seam commit records: this gate judges a split template literal as one string,
so no .vue fixture can produce a seam, and a fixture-based test passes
whether or not the rule exists. Removing dir|lang fails the assertion.
prayer-walk-planner.vue now has ZERO findings. The same shape in
library/packet/packet-render.js:1460 is not reachable yet — the scan walks
.vue only — and this rule will cover it when .js coverage lands.
exp found that splitting `width:${w}px;height:${h}px` leaves the quasi
"px;height:" — a seam between two declarations, carrying the tail of one
value and the head of the next property. It has a colon but no value and no
brace, so every CSS shape here missed it. Their regex is used as written.
The semicolon is what keeps prose out, and it is load-bearing: "12 people:"
has the colon but no declaration before it, and "Pools checked; nothing
found:" has both a semicolon and a colon and still counts, because the
segments either side are not property:value pairs.
Tested on the SHAPE, not through a .vue fixture, and the first version was
wrong in a way worth recording. A seam only exists as a separate string if a
quasi is judged alone, and this gate deliberately judges a split template
literal as one string — so no fixture can produce one. The fixture version I
wrote first passed with the rule DISABLED, which is exactly the vacuous green
this file exists to refuse. Asserting at the shape fails when the rule is
removed.
Measured before adding it: exp's four upg-neighbor findings and the CSS half
of prayer-walk-planner's are already gone, fixed by the two CSS shapes that
landed after their measurement. The only seam left in either bundle is an
HTML attribute one (`" dir="`), which this does not cover. So the rule
guards against a recurrence and against callers that do judge quasis
separately, rather than clearing a live count.
gospel verified the upg100 finding independently and refined the mechanism,
which changes what the rule has to look for. legendOpts re-reads t() on every
call — its own comment says the function shape is deliberate — so a reader
skimming for "a translator outside a function" would clear it. The freeze is
one level out: the result is assigned into a ref at two call sites and no
locale watcher ever re-fills them. The labels are computed freshly and then
frozen by where they are put.
So the rule now asks for a translator anywhere in the function plus a
returned structure plus a stored result, rather than requiring the call to
sit inside the returned literal. The old form happened to catch upg100's case
only because one property calls t() inline; a version building its strings
above the return would have slipped through, and that is the shape that will
recur because it looks correct at the call site.
Loosening it immediately flagged correct code in this owner's own plugin:
useDashboardShell builds its segment list with `get l() { return tStatic(…) }`
and says why in a comment — a resolved string would freeze the first locale
seen. A getter defers the read to whoever touches the property, so storing
the object is safe. Same escape hatch the provide() rule uses, for the same
reason: what matters is when the read happens.
Both new cases are in the table, and both sides of the real finding are now
proven against real code rather than fixtures: development's upg-100-list.vue
FAILS and upg100's fixed version at d533db379 PASSES, which matches the result
they got running this gate against their own slot head. Self-test 26/26.
gospel's third shape, and the one no per-string check can see. GiCombos
provides its facet config as a literal: provide('dashboardConfig', { facets:
{ access_level: { label: T('…') } } }). T() runs once at setup, the object
reaches the consumer as a plain value, and a live language switch leaves
every facet name in the boot language.
All ten call sites pass a real key and every key resolves in all eleven
locales, so "is it keyed" and "does the key exist" both answer yes. What is
wrong is WHEN the key was read. A lifetime is invisible to a check that
looks at one string at a time.
A getter or a computed inside the provide rescues it, because the read is
deferred to the consumer — which is how gospel fixed it in ecea53212.
Both sides are real rather than synthetic: the defect is still live on
development (ecea53212 is unlanded), so the gate catches it in the tree
today, and the fixed version with `get facets()` passes. Self-test 24/24.
gospel reviewed the shapes I taught this gate an hour ago and caught a real
bug in it: deciding whether a call site sits inside a reactive wrapper by
scanning the preceding 260 characters. Their GiDrawer.vue opens its
computed( at line 35 with the call sites at 95-100, and a lookback window
reported six false positives on working code — they nearly "fixed" code that
was right.
Measured on the same shape here: 2,600 characters between the watch( and the
call, ten times the window. A window cannot answer a containment question.
The check now walks backwards counting bracket depth, so it finds the wrapper
that actually encloses the offset however far above it sits, and cannot be
fooled by a closed wrapper that merely precedes it.
Two cases added, and the first is proven non-vacuous: reverting to the
lookback fails it (21/22), restoring brace depth passes (22/22). The second
is gospel's model.js:46 — a doc comment quoting a translator call, which must
not read as code.
This is the second correction to these rules in an hour. The first draft
flagged five production files by asking whether a function body merely
mentions t(); this one flagged correct code by asking what sits nearby
instead of what encloses. Both were found by someone else reading the rule
against their own plugin, which is the only reason either was caught before
landing.
The existing rule treats "inside a function" as safe, because a function
re-runs. That is true of the function and false of the label when nothing
calls the function again after the locale changes. upg100 hit both shapes
for real in their UPG-100 i18n batch.
A tree built once from t() and stored: opts() returns labels, onMounted
assigns the built tree to a ref, and no call site sits inside a watcher. The
translator call is shielded by the function, so the walk reports it clean
while the labels stay in the boot language.
A computed whose only locale source is getRuntimeLocale() or detectLocale().
Those are plain getters that notify nobody, so the computed has no reactive
dependency on the language and resolves once. It is the existing tStatic
rule generalised: the getter can be reached through any helper, not only
through a translator.
The watcher does not have to be watch(locale, …). upg100's own fix watches
() => packetLang(), which reads packetLocale.value — a real reactive source
under a local name. A signal looking for the literal `locale` would have
flagged the fix as the defect, which is the worst thing a gate can do to
someone who just did the work.
The first draft flagged five PRODUCTION files, none of them a label tree:
fetchPhotos, relabelSheet, popupHtml and two more — large functions that
call a translator once for an error string and return something unrelated.
"The body mentions t()" is not the shape. The rule now requires the
translator call to sit inside a RETURNED object or array literal, which is
what "builds a structure of labels" actually looks like.
Six cases added to the gate's own table, each bad shape paired with the good
one it must stay quiet on, plus that false positive. Self-test 20/20.
rega measured dashboard-plus-grid's own leaf at 390px: the page carried
"mobile": "desktop-only" in its profile-config, and the dashboard, the grid
and a MapLibre canvas mounted anyway. The rule lived in ProfileLoader, and
this composite defines its element without ProfileLoader (it bakes its
children in), so nothing on that page read the flag.
- library/composables/useScreenGate.js holds the rule (narrow screen, not
inside the Gallery, dismissed for the session); ProfileLoader uses it,
unchanged in behaviour.
- dashboard-plus-grid calls it: on a narrow screen the desktop-only message
replaces the element, and the dashboard and grid never mount.
- check:mobile-gating (g): every flagged profile's element must mount
through ProfileLoader (an import in its index.js, not a mention in a
comment) or call useScreenGate itself. The pre-fix composite fails it.
Every map profile's copy-paste snippet began with a preload of
api.mapbox.com's mapbox-gl.js, while its own profile-config said provider
"maplibre", and since 2026-09-28 only an explicit provider "mapbox" selects
Mapbox. So an unedited paste downloaded the Mapbox engine on every view,
never used it, and made a Mapbox request from a keyless page. Its only
guard was a stale comment asking the partner to delete the line.
- embed.script now preloads MapLibre's script and stylesheet, pinned the
way useMapInstance loads them so the preloads are reused, and its note
points a host who wants Mapbox at the variant.
- embed.scriptMapbox is that variant: the Mapbox preloads and the same
element with "provider":"mapbox".
- check:snippet-engine (gates:post, with a self-test) reads the built
manifest: no default snippet names api.mapbox.com, every map snippet
preloads MapLibre, and every Mapbox variant preloads Mapbox and sets its
provider. Planted: the old snippet and a provider-less variant both fail.
i18n-areas kept an area whenever its name showed up as any string literal
in a bundle's source graph, to cover keys assembled at runtime. But
'gospel', 'religion', 'regions', 'detail', 'poster' and others are also
ordinary ids (a colour strategy, a data source, a field, a mode), so every
bundle importing those modules carried catalogues it never shows, in all
11 locales: 80 area inclusions across 26 bundles, and gospel's catalogue
alone pushed country-posters 9.5 KB over the ceiling.
A bare name now counts only where it really builds a key: a namespace
helper (useOptionalT, useOptionalI18n, qualify) or a translator reading a
whole area (tm('regions') in useRegionsI18n is the one in use). Key
prefixes ('area.' / `area.${x}`) count as before. A bundle that assembles a
key with a leading `${…}` keeps the old generous rule, since its area
cannot be read from the source.
Since a9550858e the gate called _fileURLToPath, which it never imported
(only fileURLToPath is), so it died with a ReferenceError at load and
measured nothing — the UPG-100 legend's info popovers in es/ar/zh
included. And with no leaf URL it exited 0 as SKIP, which the wave runner
reads as a pass; it now exits 75 (NOT MEASURED), the convention the file
itself states. Swept every gate for the same undefined path aliases:
none.