Every reference to the Driver in internal/ is reworded the same way as in
library/: the reason stays, the speaker and the quote go, an open decision
reads "provisional, pending a product decision", and agent names and decision-
sheet rows are dropped. Where a gate printed the word, its message now names
the reviewer or the ruling instead.
Three spots were code, not comments, and changed with their callers:
- the element census's allowlist field `driver` is now `ruling`, in the gate
and both presets (same validation: match, ruling, date and card required);
- check:i18n-never-translated's DRIVER-GATED marker is DECISION-PENDING, in
the entries and in the regex that counts them (its output is otherwise
byte-identical);
- the package.json description of the census says "ruling".
public-hygiene-check keeps its two patterns for the word: they are what keep
it out of the served folder.
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.
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.
Two defects, both reproduced before the fix and re-measured after.
1. It assigned BASE for its lock path. A variable the caller exported
stays exported in bash, so every gate run through the slot saw
BASE=/tmp/doxa-maps-browser-gate.lock. Seven gates read
process.env.BASE; check:upg-map-data-painted printed "SKIP — nothing
served at /tmp/doxa-maps-browser-gate.lock/…" and exited 0. Renamed
to SLOT_BASE. Proof: with BASE=/x set, a child printed the lock path
before and /x after.
2. With every slot busy, waiters queued on slot 1 alone, so a slot that
freed was never taken and two slots behaved as one queue. It now
polls every slot with flock -n every 2 s until the deadline and
still exits 75 on timeout. Proof, in a private slot dir with slot 1
held 12 s and slot 2 freed at ~3 s: a caller waited 11.5 s before and
4.3 s after. A free slot is still taken at once (66 ms).
check-upg-map-data-painted: its unreachable-server SKIP exited 0 and
its wrong-tree refusal exited 2. Both now exit 75, the repo's "not
measured" code. Its existing tree-identity check is what makes the
:4173 default safe, since another agent's tree there is refused.
"Answers 200" and "is serving this build" are different questions, and my two
browser gates only ever asked the first. Point either at a stale server and it
measures the OLD build and reports PASS, with nothing in the output naming which
tree it saw.
★ THAT IS NOT HYPOTHETICAL — I DID IT FOR SEVENTEEN TICKS. The published site was
many merges behind while I ran these gates against it and reported the results
as live verification. They were confident measurements of a tree that was not
mine. The country-posters defect they eventually caught was only visible once
the publish made live and local agree; before that, a stale copy would have
passed either way and I would not have known the difference.
bundler-2 found this class first (BUG-BROWSER-GATES-CAN-MEASURE-ANOTHER-WORKTREES-TREE:
port 4173 on this box was held by a different worktree, and a gate reported three
confident runs about code nobody had changed) and wrote _serves-this-tree.mjs,
which hashes the served manifest.json against the one on disk. Eight gates use
it; mine did not. This adds it to the two of mine that drive a browser.
⚠ NOT MEASURED, NOT SKIP, and the distinction carries the meaning. A SKIP reads
as "nothing to check here". This is "I could not attribute what I saw", which is
a different and louder fact. Exit 2, so a caller can tell it from a pass (0) and
from a real failure (1).
A useful side effect: these gates now refuse to measure live whenever the
publish is stale, turning the silent failure mode I lived with into a loud one
at the point of use.
VERIFIED BOTH DIRECTIONS on both gates: against this checkout they measure and
pass as before (15 width x locale combinations; 195 polys · 27/27 zones · 2107
pins on both leaves); against a host serving a different build they print NOT
MEASURED and exit 2 instead of passing.
The other 26 browser gates that lack this belong to bundler-2's chain, which
owns them — reported, not converted.
The Driver reported "the polygons for the map and map colors and pins are not
showing up" and no gate could answer it. verify-map-paint asserts a live GL
context, zero console errors and that Mapbox emitted attribution -- every one
of which is true of a perfectly-painted basemap with none of our data on it.
"The engine booted and tiles are loading" and "my data is on it" are different
properties, and only the first was measured.
The profile already counts this, at the moment it adds the layers, and prints
it: upg-100-list.vue:1849 writes "N polys · R/T zones · P pins" into
.upg-status. Three numbers printed by the product and asserted on by nothing.
This gate reads them.
★ THE TWO OBVIOUS APPROACHES ARE BOTH WRONG, and I shipped one before measuring.
Counting DOM .mapboxgl-marker elements returns 0 on a HEALTHY map -- mapbox
draws these pins as GL geometry inside the canvas, so there is no element to
count; I carried "markers: 0" across three probes as proof of breakage, and it
was proof of a wrong selector. Sampling the canvas returns an empty buffer,
because a WebGL drawing buffer is cleared after compositing: 0 of 13362 sampled
pixels had any alpha, on a map visibly full of colour. The product's own count
is authoritative in a way a proxy is not.
It asserts the zone RATIO, not just presence. "27/27 zones" and "1/27 zones"
both contain polygons and only one of them is the product, so the threshold is
what separates "some data arrived" from "the data arrived". Floors rather than
exact counts, well below the real values, so a data refresh does not fail the
gate and teach people to edit the expectation instead of reading it.
MUTATION-TESTED BOTH WAYS: raise the three thresholds above the real values and
each assertion fires independently with its own message (exit 1); restore them
and it passes (exit 0). Blocking the boundary geojson also fails it, via the
unparsed-status branch.
MEASURED, local AND live, both leaves: 195 polys · 27/27 zones · 2107 pins.
The Driver's symptom does not reproduce in that form on either tree.