Commit graph

11 commits

Author SHA1 Message Date
Doxa Maps
cdcdd84cf9 verify:browser-all reads a gate's exit 75 as NOT MEASURED, not as a failure
It counted any non-zero exit as FAIL, so a gate that could not measure (no
browser slot, a server mid-build) read as a broken product, the same bug
upg100 found in check:packets-all once the browser guard made gates refuse
outside the slot. Exit 75 is now NOT MEASURED, shown as such, and a run with
no failures but any unmeasured gate exits 75 itself (INCOMPLETE), never green.
2026-09-28 09:07:29 +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
a5ab04ec75 Measure the basemap, in a real embed, on the token the page ships
"The map renders" had never been measured. Every browser gate here
forces a Mapbox token onto the page from a shared file; that token is
restricted and 403s on the basemap, while the leaf bakes a different
one that works. The gates then counted PINS — which are drawn from our
own GeoJSON and paint with no token at all — so they passed on a
basemap the probe itself had broken.

This gate forces nothing. It serves the leaf on one port and a host
page on another so the iframe is genuinely cross-origin, loads it as a
stranger's browser would, and takes evidence two ways: Mapbox responses
counted by status, and colour variety in a slice of the canvas. The
gospel map passes — 9 tile responses, none denied, and the screenshot
shows coastlines, ocean shading and country labels.

The negative control runs every time, and the check reports PROVED
NOTHING rather than a pass if the control succeeds. That is not
decoration: the first two attempts at the control did nothing at all.
A page-level init script never reaches a cross-origin iframe, and even
at context level it was ignored, because this leaf injects `tk`
straight into the element and only falls back to window.MAP_TOKEN —
so it is immune to the forced-token hazard by construction. The
control had to move to the wire. With the token rewritten there, 6 of
8 tile requests are denied and the basemap vanishes, leaving only the
pins and choropleth: the picture that shows exactly why a pin count
was never evidence of a map.
2026-09-24 19:13:46 +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
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
doxa-maps-88655c70
7e1baac4a1 Hand an inheritor a token that works, and stop claiming one is present without checking
The golden path told a new author to fetch a token from doxa.life/api/maps/token.
Measured: that token is URL-restricted to doxa.life, so a tile request without
that Referer returns 403 and every basemap in their smoke test renders blank
while the pins draw fine. That is exactly the symptom the Driver reported as
loading the pins but not the underlying map, and it is why tokenUrl was emptied
in the build config the day before. The first command in the hand-off doc
reproduced a known bug.

It now reads the token from the build config, which is the source that works:
same tile request, config token 200, endpoint token 403. That is a pk. public
key baked deliberately, protected by URL restrictions rather than secrecy, and
check:public-hygiene allows pk. while failing the build on sk. and tk. -- so it
is safe to read and never a secret to guard. The doc shows the command, not the
key.

The aggregator had the same bug and a worse symptom: it fetched from the same
endpoint and printed "token present", which meant a string had been obtained,
not that it worked. So a run could report a present token while every map gate
measured a blank basemap. It now prefers the config token, falls back to the
endpoint, and validates whichever it gets with one tile request.

Verified in all three states: the config token reports "present and serves
tiles", a fabricated token reports "tiles 401 - basemaps will be blank", and the
old endpoint token reports "tiles 403 - URL-restricted?". The banner reports
what was measured rather than what was fetched.
2026-09-23 04:09:07 +00:00
doxa-maps-88655c70
aa196db1d8 Refuse to serve a half-built tree, so a gate cannot mistake one for a regression
A build removes public/doxa-maps before rebuilding it, so a browser gate running
during one reads an empty tree and reports a confident wrong answer.
verify-shell-scroll said it could not find the dashboard-plus-grid tab twice in
ten minutes while a swarm rebuilt underneath it. That reads exactly like a real
regression and cost three ticks of this flight.

The server now answers 503 for anything under /doxa-maps/ while the manifest is
absent, with a body naming the cause. A 503 rather than a 404 is deliberate: 404
means "this is not here", which a gate may reasonably report as a product defect,
while 503 with a reason cannot be mistaken for a finding. Assets outside the
bundle tree are unaffected.

The check belongs in the server because all nine gates fetch through it. Guarding
one chokepoint means none of them can misread a torn tree and none of them needs
editing, where having every gate take the build lock is nine changes plus a rule
every future gate has to remember.

The aggregator already refused to start against a missing tree, but only at
startup, and a full sweep runs for seventeen minutes. It also blamed a missing
server for a 503, sending the reader to start one that was already running; it
now names the real cause. The gap the server closes is the one that actually bit:
a single gate run directly, at any moment, including mid-sweep.

Verified by hiding the manifest in place: a leaf returns 503 with the explanation
while /assets/ still returns 404, and both return to 200 when it is restored.
2026-09-23 03:52:30 +00:00
doxa-maps-88655c70
b2fc0e4dda Make the shell-scroll gate runnable, and catch orphan gates
Some checks are pending
build-gate / build (push) Waiting to run
verify-shell-scroll.mjs was written and negative-tested, then left with no
package.json entry and no importer, so nothing could ever run it again. It
also defaulted to localhost:8899 -- an ad-hoc port from an earlier session --
so it failed with ERR_CONNECTION_REFUSED for anyone else and read as a broken
gate rather than a missing server. Now check:shell-scroll, defaulting to
BASE_URL or 127.0.0.1:4173 like every other browser gate, and listed in
verify-browser-all's GATES array.

check:no-orphan-gates fails any gate-shaped file in internal/ that nothing can
invoke. The repo carded this once already; it recurs because writing a gate
and registering one are separate acts and only the first feels like work.

Not every unreferenced file is a lost gate, so the rule is not "everything
must be wired": theme-flash-probe is a dated diagnostic for two fixed bugs,
worth keeping as a reproduction recipe. It now carries an @one-shot tag saying
so, which makes the author state which kind of thing they wrote.

Two self-referential defects in the first version of that gate, both from
reading evidence out of documentation:

- it cited theme-flash-probe.mjs in its own header as an example, then grepped
  for importers, found itself, and declared the orphan reachable. The gate
  written to catch orphans made an orphan pass by mentioning it. A gate must
  never be its own witness, so it now excludes its own file.
- it matched @one-shot anywhere in prose, including its own paragraph
  explaining the tag, and reported "1 tagged" when nothing had been tagged.
  The tag must now be a marker at line start.

Negative-tested: removing check:shell-scroll from package.json fails by name.

The scroll fix itself is NOT done. contain:layout and .vtl-host overflow-y are
confirmed in the built bundle, and a real wheel gesture still moves the grid
0px with 2586px mounted and unreachable. The gate is what caught that; the
diff looked correct.
2026-09-22 17:55:43 +00:00
doxa-maps-88655c70
a1005869ce Anchor the filter dropdown in every host, not just standalone
Some checks are pending
build-gate / build (push) Waiting to run
The menu is position:fixed with viewport coordinates. That resolves against the
viewport only while nothing establishes a containing block for fixed
descendants. VerticalTabsLayout -- the cap-5 shell that hosts every plugin as a
tab -- sets contain:layout paint on .vtl-content, inside .vtl-main{left:256px}.

Measured at 1600x900, same component, two hosts:
  standalone research-dashboard ...... dLeft=0   dTop=4    six of six
  inside plugin-studio dashboard tab . dLeft=256 dTop=111  every one

256 is the rail width and 111 the header height. Reported as the menu landing
"in a random spot further down to the right"; it was never random.

An earlier probe measured six triggers at four viewport widths and passed, so
the card was closed while the bug was still there. Every number was accurate.
They were answers about a surface the user never looks at -- widening across
more widths could not have found it, because the variable was the host.

Fix measures the containing block instead of assuming it: a fixed probe at 0,0
inserted in the menu's own parent reports the origin the menu resolves against.
When nothing captures, that origin is 0,0 and the standalone numbers are
unchanged bit-for-bit.

Not fixed by dropping contain from the layout: it is load-bearing there,
stopping a rail collapse from reflowing a heavy mounted map.

check:fixed-anchoring measures both hosts and treats an unmeasured host as a
failure. Negative-tested: reverting the subtraction fails the studio host with
dLeft=256 while standalone still passes -- it fails exactly where the old probe
passed.

Also cards a second symptom of the same cause, not yet fixed: fixed-position
modal scrims written inset:0 stretch to the content pane instead of the
viewport, leaving the rail clickable behind an open modal.
2026-09-22 17:22:23 +00:00
doxa-maps
f132d2e056 Put share verification back in the browser suite
Some checks are pending
build-gate / build (push) Waiting to run
I excluded smoke:share from the aggregate two ticks ago and the reason was wrong.
Every clause of it was true — the script takes required arguments, and its
gallery mode does need the vite dev server — but the conclusion did not follow. I
had tried exactly two invocations, bare and --plugin-gallery, and never --url,
which the script's own header documents first. It runs fine against the static
tree: 8 of 8 pass, each Share button reopening the correct custom element with a
live canvas.

That is the same shape I logged as an anti-pattern last tick, now found in my own
work: a not-applicable verdict is more durable than a wrong bug report, because
it closes the question. It cost the suite its highest-consequence surface for two
ticks. A share URL is the one artefact a partner pastes into an email, and it
fails silently.

It runs as smoke:share-all, carrying its own eight --url flags and --browser
chromium, since Firefox headless has no WebGL on this box and the default engine
stops. The eight leaves are the set derived by grepping for ShareButton, not a
guess. The simple-map case is the discriminating one: it shares to a different
leaf than the one opened, so the deep link is really being followed rather than
an SPA fallback rendering something plausible.

Two shell traps on the way. A VAR=x prefix does not apply to $VAR on the same
line, because the shell expands the argument before the assignment scopes to the
command, so the URLs came out with an empty base; deferring through sh -c fixes
it. And a glob in a block comment closed the comment early.
2026-09-22 10:30:41 +00:00
doxa-maps
d463cb6161 Run every browser gate from one command
Some checks are pending
build-gate / build (push) Waiting to run
All seven browser gates were orphans: each had a script, and not one was invoked
by anything else. They were reachable only by someone who already knew the name
to type, which is exactly the person who does not need the list. This repo has
been bitten by this before — the Gate-1 acceptance test existed, worked, and was
invoked by nothing, so it could never report anything. An orphan gate is worse
than a missing one because it looks like coverage.

The data guards already solved this; verify-all.mjs says it plainly — a guard
nobody runs is documentation. That file has no reference to playwright or to the
built tree. It is the data half. This is the browser half.

Every gate listed here was run before it was listed, following the rule the
earlier audit set: listing a program you have not executed puts unvalidated
entries in the hand-off surface. All six pass, and the aggregate now runs them
end to end: capabilities, embeds, map paint, the Gate-1 chain, accessible names,
and phone navigation.

smoke:share is excluded on purpose and says so. It takes required arguments and
its gallery mode reads a registry only the vite dev server serves, so listing it
would make the suite fail for something that is not a defect.

Preconditions are asserted up front and exit 2, so a missing server reads as a
setup problem rather than six mystery failures.
2026-09-22 09:15:00 +00:00