6 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fbc517c94a |
A browser gate refuses to launch outside the browser slot; check:browser-gates-guarded keeps it that way
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. |
||
|
|
9acd4f8865 |
Queue the browser gates, and let them serve their own tree
Two problems, one change. LOAD. Seven agents each running a wave put 7+ chrome-headless renders on one box; measured today it reached load 47 with 8GB of swap, and a browser gate that is swapping measures the swap, not the product. The wave runner now holds browser steps while 1-min load is at or above LOAD_MAX (12) and takes a coarse flock before each one, so they queue instead of dogpiling. Thresholds, cadence and the 20-minute cap are bundler-3's wait-for-quiet.sh verbatim, and BROWSER_GATE_LOCK selects the rendezvous, so every agent lands on one lock whichever entry point they use. Static gates are untouched: in gates:post that is 38 free, 4 queued. The load wait is taken ONCE per wave, not once per gate. Per-step it multiplied the same hold by the number of browser gates — an hour of a wave doing nothing, which is how a load fix becomes a reason to stop running gates. The flock is what serializes; the wait only decides when to join the queue. Browser steps are identified from the gate FILE (a playwright import), not a hand-kept list that goes stale the first time someone adds a gate — the one missing from such a list is exactly the one that rejoins the dogpile. `verify` is caught too: it imports no playwright itself but spawns three gates that render, and it already marks them browser:true, so that declaration is honoured rather than duplicated. COVERAGE. Yesterday's guard made check:provider-switch and check:packet-browser SKIP in every wave, because their default ports (4173, 4898) belong to other worktrees on this machine. Skipping was honest but a gate that skips every time is a gate nobody runs, so on a mismatch they now start a short-lived server over THIS checkout and measure that instead — the point was never the port, it was being able to prove which build answered. MEASURED: a served tree whose manifest differs by one key but has all the same pages -> "serves a DIFFERENT build", then "serving this checkout on :28384 instead", then a real verdict (16 -> 5 mapbox requests). packet-browser likewise, once it re-navigates to the new origin — a dynamic import across origins is refused by a plain static server, which cost one failed run to learn. gates:post 40/42, the only skips being the two that genuinely have no input (packets-all, zone-census); it was 4 before this. The lock path default is product-named. The shared file several shells export carries an internal code name, and check:no-codename refuses to let a repo we hand over carry one — it caught this commit's first draft. |
||
|
|
6602cd1399 |
Browser gates: prove the server is THIS build before believing it
Five gates took their measurement from whatever answered a default localhost port. On this machine that port belongs to whichever worktree started first: :4173 is wt-packet's server, :4898 is its repo root. Measured today — an hour of probing reported on a build that was not on disk here, and the mistake was only caught because a marker string was in the file and absent from the response. "Answers 200" and "is serving this checkout" are different questions, and only the second one licenses a verdict. _serves-this-tree.mjs already asked it (it exists because the gospel-resources gate hit this and spent three runs reporting on the wrong tree) but only three callers used it. Wired into the five that measure a served page and had no such check: check-provider-switch request tally per host — the wrong tree's tally check-maplibre-layer-drops silent layer drops — invisible by nature check-filter-width-stable width divergence — would accuse untouched CSS check-facet-changes-map data flow on facet change check-packet-browser imports the renderer FROM the server Only a genuine MISMATCH skips. "No manifest to compare" is a different precondition (an unbuilt checkout) that each gate already speaks for, and turning that into a skip would silence these on the builds they should run on. A mismatch exits 0, like the unreachable case, because it is a precondition and not a product finding — but it prints which, so nobody reads it as a pass. CONTROLS, each fired, per gate: correct server -> PASS with a real number (provider-switch 19 -> 5 mapbox requests, drops 17/17, filter-width identical, facet 2107 -> 1209 dots, packet-browser headings match); a served tree whose manifest differs by one key -> SKIP naming "serves a DIFFERENT build"; no server -> the pre-existing unreachable SKIP, unchanged. One correction found by running it rather than by reading it: the first wiring passed the LEAF PAGE URL where the helper wants the ORIGIN, so it looked for the manifest under the page's own path and skipped every run for the wrong reason. node --check cannot see that; only running both controls can. KNOWN COST, not hidden: in a wave with no server of this tree running, check:provider-switch and check:packet-browser now SKIP where they used to report a green about another worktree. That is the honest reading, not a regression, and the runner prints "evaluated nothing" for them. Next increment is to let these gates serve this checkout themselves so they measure again. |
||
|
|
7a649b6845 |
packet browser gate: verify the packet a reader actually exports
Some checks are pending
build-gate / build (push) Waiting to run
The legend's Export > Prayer packet action is the path a reader clicks. The
build-time file is the one nobody clicks. The reader's packet was the less
verified of the two.
check:packet-render gained six assertion families over recent ticks — the 12
prayer points, the wheel's 12 LABELS rather than just its 12 segments, page 1's
geography, the print layer, the image census, the build stamp. Each was found by
mutation and each catches a real "looks fine, is empty" shape. None existed
here.
It also printed "Mark 5 in full : undefined" beside a PASS for as long as it has
existed: the measurement is r.mark5Len and the print read r.mark5, which is not
a key on r. The assertion always read the right key, so the requirement was
protected — but undefined next to a PASS reads as a gate that does not know what
it is doing. That is the inverse of this tree's usual failure, where a number is
printed and never asserted on.
A mistake caught before it became a false report. The new geography assertion
went red immediately with "no map svg on page 1", which would have read as a
serious product defect. It was the harness: this gate called buildPacket with an
EMPTY mapSvg, while the real export path fetches posters/<zone>.html and lifts
its <svg>. The shortcut was not just skipping an assertion — it meant the gate
never exercised mapCss() in a browser at all, which is the one thing it exists
to prove. It now fetches the map the way the product does and reports 13
geography paths, the same number check:packet-render gives for laos. ISO3 and
ZONE are also different strings ("lao" vs "laos.html"), so even a naive fetch
would have missed; PACKET_ZONE names it.
Mutation-tested: blanking the wheel labels gives 0/12 labelled and fails; hiding
content at print time gives 0 embedded fonts and fails.
i18n verified on the browser path for the first time. A partial locale with 3 of
12 points renders 12/12 points — the per-key merge holds here too — with the
points heading translated, the wheel heading falling back to English, Mark 5 at
2220 chars, and nothing blank.
Also made wave-safe: without a server it threw an unhandled navigation error and
dumped a stack trace, which is why it could never join a wave. It now SKIPs
cleanly naming its precondition. gates:post 35 -> 36, README re-synced. Verified
both ways — 36/36 with a server, and "0/1 passed, 1 SKIPPED" without one, which
is visible only because of the runner change in the previous commit.
|
||
|
|
3519ca3934 |
check:packet-browser asserts its headings, and stops reading them in English
This gate printed nine metrics, folded six into `const bad = a || b || ...` — so a
failure said "FAIL" without naming which — and left three unchecked: page size and
the two headings, which are the very strings its own header claims it covers ("the
gate reports which headings came back translated"). Reports, not asserts.
★ IT LOOKED LIKE IT HAD FOUND A PRODUCT BUG, AND HAD NOT. Under ?lang=es it printed
an EMPTY wheel heading; under hi it blanked BOTH — while the catalog holds "Rueda de
Oración" and "प्रार्थना चक्र". That reads exactly like a broken i18n path, and it
PASSED anyway, because nothing asserted the values.
The cause was the harness. wheelTitle was innerText.match(/^.*Wheel.*$/m) and
pointsTitle /Prayer Points|Puntos/ — neither can match Hindi, and /Wheel/ cannot
match Spanish. The product was correct in every locale all along: verified after the
fix in en/es/hi/ar/zh, RTL and Devanagari included. Had I reported that as a defect
I would have been wrong.
mark5 carried the same fault: innerText.includes('Gadarenes'), an English proper
noun that goes RED the moment point4_passage is translated. Now asserted by
.p4-passage length — the same fix already applied in check-packet-render.
Headings are now compared to the CATALOG entry for the packet's language, with
per-key fallback to English exactly as the renderer resolves it. packet-render.js
gains class="points-title"/"wheel-title" as TEST HOOKS: both were inline-styled with
no selector, which is what forced the English greps in the first place.
MUTATION-TESTED: a renderer forced to ignore the language FAILS with both headings
named and the expected values shown; a renderer emitting an empty heading FAILS
("a blank heading is worse than an English one").
★ ONE MUTATION CORRECTLY PASSED, and it marks a real limit: reverting es
wheel_title to English IN THE CATALOG passes, because rendered and expected then
legitimately agree. This gate cannot tell a translated catalog from an untranslated
one — that is check:packet-strings-parity's job, and it does assert it. Two gates,
two questions.
Partial translation re-verified through the BROWSER path: a 1-of-21-key Swahili
block renders "Mambo 12 ya Maombi" with the wheel heading falling back to "Prayer
Wheel" — translated where present, English where not, neither blank.
That makes four gates with one root cause (tabs-localized, packet-render,
deck-click, and this one twice). Any new gate matching on user-visible text should
resolve it from the catalog.
|
||
|
|
097ab02458 |
test(packet): gate the packet the READER gets, not the one Node writes
Some checks are pending
build-gate / build (push) Waiting to run
check:packet-render verifies a packet FILE from the build-time generator. This
verifies the BROWSER path — the legend's Export > Prayer packet action, which
imports library/packet/packet-render.js, fetches live records and prints
in-place.
Both callers now share that one renderer, and this gate is what proves the
shared module actually runs in a tab. A module can be perfectly correct under
Node and still fail in a browser: a bare process.env reference, a Node-only
import, a JSON import assertion. Node passing says nothing about that.
It drives the real thing: the renderer over HTTP, the app's own normalizer
(the API returns { posts, total } — hand-rolling that extraction is how a probe
ends up testing something the product never does), live records, then
page.pdf() and a PDF page count.
Measured on the browser path: 8 sheets -> 8 pages at 612x792pt, 95 people
cards, 12 wheel segments, Mark 5:1-20 in full, exactly 2 metacamp.org
attributions, 0 page errors.
I18N is asserted here because it is a product requirement, not a nicety: pass a
language and the gate reports which headings came back translated. Verified with
a partial Spanish block — "12 Puntos de Oración" and "Rueda de Oración" rendered
while Mark 5 and both attributions correctly fell back to English. Fallback is
PER KEY, so a half-finished translation renders its finished keys and never
blanks. The stub block was removed after verifying; shipping it would imply a
Spanish translation that does not exist.
Parameterised rather than pinned: PACKET_ORIGIN / PACKET_API / PACKET_ISO3, and
the language as argv. Needs a static server over the repo root.
|