documentation: the browser guard, and ask the repo rather than grepping

Follow-up to the doc landing in 49, which was written before the guard existed.

Adds the guard: every browser gate reaches Playwright through
internal/lib/playwright-guarded.mjs and refuses unless browser-slot.sh exported
DOXA_BROWSER_SLOT; an unslotted run exits 75 with the command to use, quoted
verbatim. And says the slot is a COUNTING SEMAPHORE, not a mutex, with the reason
from its own header -- a single lock queued 10-19 deep for hours because the real
constraint is memory, ~300-500MB per chrome, not exclusivity.

Replaces the grep advice with `bun run browser-gates`. The grep still works and
is fine for a quick look, but it is a heuristic over file text: my own count from
grepping internal/check-*.mjs said 87 browser gates; the repo's own detector says
97, because browser gates also live in plugin-designs/*/scripts/. Corrected. The
authoritative list cannot drift from what is enforced, because
check:browser-gates-guarded uses the same detector.

One line the guard's header earns and this doc had not stated: being able to
launch is not the same as it being yours to launch. The guard stops the accident;
it does not hand a newcomer the seat. Testing whether the guard refuses is itself
a browser launch.

Does NOT claim path-into-node_modules detection: studio has that in landing 50,
and it is not in this tree, so the doc does not describe it yet.
This commit is contained in:
Doxa Maps 2026-09-28 08:45:40 +00:00
parent a9dfe29200
commit f2e2509167

View file

@ -1,6 +1,6 @@
# Which gates need a browser
There are **238** checks in this repo. **148 run anywhere. 87 need a real browser.** If you are not
There are **238** checks in this repo. **97 need a real browser**; the rest run anywhere. If you are not
the person who owns the browser seat, that second group is not yours to run — and knowing which is
which saves you from a whole class of wrong answer.
@ -12,14 +12,16 @@ which saves you from a whole class of wrong answer.
## Telling them apart
Open the gate's file and look for one import:
**Ask the repo, don't grep:**
```bash
grep -l 'playwright\|chromium' internal/check-*.mjs
bun run browser-gates # lists every script that launches a browser
```
If it imports `playwright` or launches `chromium`, it needs a browser. That is the whole test — the
name does not tell you. `check:packet-render` sounds like a file check and drives a browser;
That is the authoritative answer — the same detector `check:browser-gates-guarded` uses, so it
cannot drift from what is enforced. A grep for `playwright|chromium` also works today and is fine
for a quick look, but it is a heuristic: it finds files that *mention* the word, and it would miss a
file that reached the package by path. The name tells you nothing either. `check:packet-render` sounds like a file check and drives a browser;
`check:composites-mount` sounds like it needs one and does not (it mounts every composite through
Vite's SSR loader, no browser at all).
@ -27,8 +29,8 @@ Vite's SSR loader, no browser at all).
| | Count |
|---|---:|
| headless — run these yourself | **148** |
| **browser — not yours unless you own the seat** | **87** |
| headless — run these yourself | the remainder |
| **browser — not yours unless you own the seat** | **97** |
| neither (shells out to `bun`/`php`) | 3 — `check:mobile`, `check:wp-embed`, `smoke:playground` |
The browser group is, broadly: everything that renders a map (`check:map-paints`,
@ -60,6 +62,38 @@ and confusing them is the most common way to report something false:
---
## The guard: you cannot launch a browser by accident any more
The rule above used to be remembered. It is now **enforced**. Every browser gate reaches Playwright
through `internal/lib/playwright-guarded.mjs`, which refuses to launch unless
`internal/browser-slot.sh` has exported `DOXA_BROWSER_SLOT`:
```bash
internal/browser-slot.sh bun run check:packet-render # the only way to run one
```
Run a browser gate outside the slot and it does not fail — **it exits 75 and tells you the command**:
```
this gate launches a browser. Browsers run only on the browser seat, inside the slot:
internal/browser-slot.sh bun run <script> (it exports DOXA_BROWSER_SLOT)
NOT MEASURED (exit 75).
```
`check:browser-gates-guarded` fails any repo file that reaches Playwright directly instead of
through the guard.
**The slot is a counting semaphore, not a mutex.** A single lock made every agent's browser proof
queue behind one chrome — measured 2026-09-25, the queue sat 10–19 deep for hours. The real
constraint is memory (~300–500 MB per headless chrome), not exclusivity, so the slot admits as many
browsers as the box can hold and refuses when free memory is below the floor. A slot that is free
while the box is out of memory is an invitation to be OOM-killed mid-measurement, which reads as a
flaky gate rather than as what it is.
> **If you are not the browser seat, none of this is your invitation.** The guard stops the accident;
> it does not make the run yours to take. Send the command to whoever owns the seat. Testing whether
> the guard refuses *is itself a browser launch* — that one has been learned the hard way.
## ★ Exit 75 means NOT MEASURED. It is not a pass and it is not a failure.
This is the one that bites hardest, because it looks like success.