# Infrastructure
The machines the generation loop runs on, and how work reaches them. The loop itself is
in `docs/generation-loop.md`; measured rates are in `docs/rig-capacity.md`. Hardware is
named by role and class, never by hostname, address or account — this repository is
assumed public. Real names live in the gitignored `bench/*.local.json` and the local rig
notes.
## In brief
- **The loop.** One engine function per spec algorithm (for example the CSS 2.1 width
calculation), written by a model, checked against cached browser results.
- **Sampling tier.** GPU cards running mid-sized models write N versions of a function
from the spec text, the engine's Rust interfaces and the currently failing cases. Many
cheap tries beat one careful one, because the browsers say which tries are right.
- **Capacity tier.** When every version fails, a larger model gets the spec, the best
failing versions and the exact per-box geometry differences.
- **Verification tier.** CPU servers compile candidates in one batch and run each inside
the engine over the cached corpus, under a millisecond per page. The same tier builds
that cache by running the three browsers once per page, and runs conformance suites.
- **Outcomes.** Keep the passing version, sample again with the new failures, or
escalate. What is kept is ordinary Rust with a provenance record; no model runs in the
shipped engine.
Environment variables are named `WEBENGINE_*`. Older records use `NEZE_*`
(`docs/rename-to-bez.md`), `NEV_*` (`docs/rename-to-neze.md`) or `GENERA_*`
(`docs/rename-to-nev.md`); nothing reads any of them any more.
## Detail
### The full cycle
```mermaid
flowchart TD
browsers["Chromium, Firefox, WebKit
once per page (CPU)"] --> cache["cached ground truth
three-engine vote"]
spec["spec text
one algorithm"] --> sample["SAMPLE (GPU pool)
N versions of one function"]
sample --> verify["VERIFY (CPU)
dedupe, batch compile,
run over the corpus"]
cache --> verify
verify -- "all pass" --> accept["commit Rust source
+ provenance record"]
verify -- "some fail" --> sample
verify -- "all fail repeatedly" --> escalate["ESCALATE
larger model + geometry diffs"]
escalate --> verify
```
Nothing is accepted on a model's word: escalated output goes through the same check.
Writing candidates is the binding constraint; verification has a large margin over it.
### What comes out, and how the engine uses it
The cycle outputs **ordinary Rust source**, committed next to the hand-written code it
plugs into, with spec steps as comments and a **provenance record** (`roadmap.md` →
"Provenance is a first-class artifact"). The engine compiles it like any other code; the
GPU and CPU fleets exist only at build time.
**Provenance drives maintenance.** When a spec section or a definition it uses changes,
the provenance index finds exactly the functions to regenerate (`roadmap.md` → "A
specification changes"); a browser update re-dumps the ground truth instead. The same
index is the bill of materials for a configurable build (`roadmap.md` → "Phase 4").
## Measured baseline
One oracle process, three engines, 235 documents including browser startup: **17.9 s**,
about **13 documents per second per engine**, using about two cores. Scaled to a few
hundred cores that would be about 3,000 documents per second, a ten-million-case corpus in
about an hour — but per-core throughput on the contended 16-core development box fell from
1.48 to 0.48 docs/s/core between 1 and 16 workers (`docs/rig-capacity.md` → "Oracle
corpus building"), so the figure on dedicated hardware is still to be measured. GPU-side
figures: `docs/rig-capacity.md` → "GPU tier".
## Three tiers
**Sampling tier — high memory bandwidth GPUs.** Bulk best-of-N generation,
throughput-bound. Decode speed tracks *active* parameters, so a mixture-of-experts model
with a small active fraction gives several times the candidates of a dense model. Ampere
cards have no fp8, so quantization is 4-bit weight-only.
**Capacity tier — large unified-memory accelerators.** About a third of the bandwidth,
several times the memory: for the few hard calls and any teacher model. About 100B total
with ~12B active is the realistic ceiling.
**Verification tier — many CPU cores.** Corpus building, candidate compilation and the
inner loop. It must never spawn a browser per case.
## As discovered
Every host advertises itself over mDNS and answers an HTTP inventory; a collector
aggregates it. What that shows, by role:
- **The model gateway** is a LiteLLM instance fronting one alias per model pool. The
aliases, their pools, how many requests each takes at once, and which are retired are
declared in `bench/folknet.json` (the two GLM-5.3 Flash aliases, without backends since
2026-09-22, were removed from the gateway on 2026-09-25); whether each one answers
right now is in each lane's snapshot, which a probe refreshes every 5 minutes and
`bench/folknet-status.sh [--lane N]` prints (status, how long it has held it, last
answer, snapshot age). Design: `docs/folknet-inventory.md`. Virtual keys carry per-key
allowlists, so a new alias must be granted to the caller's key. Each pool's HAProxy
reports liveness, not readiness: a `/v1/models` answer does not prove a completion
will return.
- **Four ARM unified-memory nodes (DGX Spark class)** — `aarch64`, one GB10 GPU, 20
cores, 121 GB RAM, 3.7 TB NVMe each. They served GLM-5.3 Flash as two independent
two-node pairs until 2026-09-22. Qwen3.8-Flash-Next did not run under any vLLM build on
the rig (the architecture was too new for the rig's EXL3 fork, and upstream has no EXL3
backend), so it serves through the checkpoint's own engine (TabbyAPI) instead, one
replica per node, about 75 GB plus ~45 GB of page cache each. Two of the four now serve
DeepSeek-V4.1-Flash. Full account: `docs/rig-capacity.md` → "Attempted —
Qwen3.8-Flash-Next".
- **The eight-card host** — eight 24 GB consumer cards on a 64-core EPYC with ~500 GB
RAM, bare Linux, six serving containers using every card (four behind `-fast`, two
behind `-long`). The closest thing to the sampling tier as designed. No spare GPU.
- **The verification tier** — ten Proxmox 9.x nodes (EPYC 7742, 7B13, 7K62 and 7452; 32
to 64 physical cores, 125 GB to ~500 GB RAM, 1–2 TB NVMe), shared with tenant VMs;
five pass thirteen consumer cards through to tenant machines. A VM sized for browser
builds sits on the GLM-pool proxy host (open: its core count and whether it should be
running). Free capacity is host by host.
- **A downloader host** pulls checkpoint shards in parallel across the site's four WAN
uplinks onto the NAS, verifies them and publishes a pinned revision for nodes to sync.
- **Access.** Root with the jump host's key works on every physical host; the ARM nodes
use their own admin key. The gateway's own guest refuses, and is not a compute host.
## Where each piece runs
Every role is a guest or a container; nothing is installed on a Proxmox node's own OS.
```mermaid
flowchart LR
dev["development box
16 cores, shared"] -- "ssh via jump host" --> lanes["2 build lanes
(LXC, 96 threads each)"]
dev -- "port forward" --> gw["model gateway
(LiteLLM)"]
lanes --> gw
gw --> fast["eight-card host
-fast, -long"]
gw --> vm["four-card VM
-fast-2"]
gw --> fn["2 ARM nodes
flash-next"]
gw --> ds["2 ARM nodes
deepseek pair"]
lanes --> helpers["compile helpers
(sccache-dist)"]
```
- **Development box.** No GPU, no rig role; holds the port forwards to the gateway. Each
forward is an enabled systemd user unit (`rig-gateway-tunnel`, `rig-forge-tunnel`,
`rig-lightwall-tunnel`, `rig-tunnel`, `webengine-job-events-tunnel`) with `Restart=always`
every 10 s; user lingering is on, so the units start at boot with no login.
- **Jump host.** A VM on one node; the only route in. Nothing of this project on it.
- **Build lanes.** One unprivileged LXC on each of two nodes (`bench/lane-create-lxc.sh`,
`bench/lane-provision.sh`), started at node boot. A gate running when a lane stops
fails.
- **Compile helpers and scheduler.** Four more containers plus builders on the two lanes;
scheduler, redis cache and a loopback CONNECT proxy on one lane (`bench/lane-dist.sh`).
- **Eight-card host.** Docker containers under a templated unit
`folknet-qwen38-vllm-worker@`, behind that host's own HAProxy on port 4000.
Not edited by this project.
- **ARM nodes, split two ways since 2026-09-24.**
- Two serve `qwen3.8-flash-next`, one systemd unit and container each on port 5000
(`bench/sparks/`), behind the proxy host's HAProxy (least connections, eight in flight
per replica) — so the alias takes **16 in flight, not 32**; a batch at concurrency 32
queues the rest. Either replica can stop alone.
- Two, joined by a direct cable, serve `deepseek-v4.1-flash` as one tensor-parallel
pair: one oneshot unit on the head node
(`bench/sparks/dsv41/folknet-dsv41-exl3-pair.service`) starts both containers and
returns once the API is healthy (about 7.5 minutes cold); stop and start only through
that unit, never one node alone. HAProxy `maxconn 2`.
- Units are stopped and started through a privileged container that chroots into the
host, because the admin account has no sudo. GLM units are disabled on all four
(since 2026-09-29; before that `folknet-glm53-vllm` was still enabled everywhere).
- **Four-card VM (`qwen3.8-27b-fast-2`).** Four 3090s borrowed from a stopped tenant VM on
the same node (only one of the two may run; `bench/gpu-vm/`). Four units
`folknet-qwen38-vllm-worker@fast-{0..3}` on ports 8101–8104, the eight-card host's
`-fast` recipe, HAProxy `maxconn 4` per worker (16 in flight). A separate alias because
the `-fast` load balancer lives on the eight-card host.
**Open.** Which host carries the four-card VM behind `qwen3.8-27b` (the measured Qwen
server), and how it is started and stopped; whether anything restarts the eight-card
host's HAProxy or re-pulls its image; and the gateway config
is generated from a site file outside the rig, which needs every hand-added alias too.
## Sharing the hardware
The Proxmox nodes are shared with other tenants. Three rules:
- **Nothing of this project goes on a node's own OS**, so a guest can be stopped and its
hardware handed back without touching the node.
- **GPU work runs in a VM with passthrough, never an LXC** (an LXC would need the vendor
driver on the node). CPU work runs in an unprivileged LXC, or in docker on non-Proxmox
hosts.
- **Stopping a project guest frees what it held**, and a lane competes with tenants as an
equal under contention. Nothing checks this; it is how the rig is laid out.
Limits today: the ARM replicas' HAProxy queues a request at most 30 minutes; power
budgets are modelled (`bench/capacity.py`) but not enforced.
**Not built yet:** a per-role start/stop/drain wrapper; a drain (a replica's stop gives
in-flight replies only 60 s); taking a backend out of HAProxy before stopping it; a narrow
sudo rule for the ARM nodes' admin account.
## Build lane
Two unprivileged containers (96 logical CPUs — three quarters of a 64-core, 128-thread
node — 96 GB, 200 GB disk), each a build lane and oracle host: the Rust toolchain, Node
22, the repository cloned from the rig's forge ("Where the repository is stored" below),
Playwright's three browsers, and a PulseAudio null sink so a realtime `AudioContext` runs. The scripts are the record and are
re-runnable: `bench/lane-create-lxc.sh`, `bench/lane-provision.sh`, `bench/lane-nas.sh`.
When folknet is down, the development box can serve as a lane with its own forge and Kimi
as the model: `docs/local-lane.md`.
**Running a gate there.**
```mermaid
sequenceDiagram
participant W as worktree (dev box)
participant L as lane
participant F as rig forge
W->>L: ssh: git bundle of unpushed commits + diff + untracked files
L->>F: fetch the lane's mirror (only if needed, or older than 10 min)
L->>L: lane-apply.sh: check out exact commit, apply diff, run command
L-->>W: streamed output (and --pull directories)
```
`bench/remote-lane.sh ` does this. Each lane root keeps one bare
mirror (`/.git-mirror`) with one git worktree per track; it fetches only when the
bundle cannot supply the commit or the last fetch is older than `WEBENGINE_LANE_FETCH_MAX_AGE`
(600 s), because the public forge the mirrors first fetched from rate-limits. The lane
address comes from `WEBENGINE_LANE_SSH` or `bench/lane.local.json`; the mirror URL from `WEBENGINE_LANE_GIT_URL`, that file, or the
worktree's `origin`. Examples:
```
bench/remote-lane.sh . cargo test --workspace
bench/remote-lane.sh --pull corpus/absolute-ground-truth . \
cargo run --release -p generation --bin absolute_ground_truth_cache
```
Why a bundle rather than rsync: uploads from the development box to the lane run at about
18.6 KB/s (downloads 435 KB/s), so rsyncing a worktree took ~315 s of a ~424 s run.
**Where the repository is stored.** Two forges, with different jobs:
| forge | role | holds |
|---|---|---|
| the rig's Forgejo | intermediate storage, on the rig network | everything the lanes need: `main`, job branches `job/` and their results `result/` (`tasks/job-submit-land.md`) |
| tangled | persistent storage, public | `main`, the `spec//` landing tags, and every polled job run's result as the tag `result//` (`jobs/scripts/poll-and-land.sh` `keep_result`) |
The Forgejo repository is a regular repository, not a mirror of tangled. The lanes clone
and fetch from it: each lane root's `.git-mirror` and the lane user's clone fetch from
`http://:@//.git`, and
`bench/lane.local.json` `git_url` (or `WEBENGINE_LANE_GIT_URL`) names the same URL. The user and
token come from the primary checkout's `.env` (`WEBENGINE_FORGE_USER`, `WEBENGINE_FORGE_TOKEN`). The development box reaches Forgejo
through a local port forward, as the remote `rig`
(`http://:@127.0.0.1://.git`), which
the job scripts use by default (`JOBS_FORGE_REMOTE`). `origin` fetches from tangled and has
push URLs for both forges, so `git push origin main` updates both; job and result branches
go to `rig` only and are deleted there after a landing. Each of those URLs carries the
token: they live in `.git/config` and the gitignored files, never in a committed file, a
log line or a message.
**Outside sources.** Every outside git repository is fetched from a pull mirror on the
rig's Forgejo, never from GitHub or another upstream, so a lane or the development box
neither meets GitHub's rate limits nor depends on it being up. Each mirror is a Forgejo
pull mirror (all history and branches, so any pinned commit stays fetchable) owned by
the same owner as the project's own Forgejo repository, and synced from upstream every
8 hours, the interval the forge's other mirrors use:
| source | pinned in | mirror repository | size on the forge |
|---|---|---|---|
| `https://github.com/web-platform-tests/wpt.git` | `oracle/suites/suites.json` `wpt` | `/web-platform-tests.wpt` | 3.0 GiB |
| `https://github.com/KhronosGroup/WebGL.git` | `oracle/suites/suites.json` `webgl` | `/KhronosGroup.WebGL` | 43 MiB |
| `https://github.com/gpuweb/cts.git` | `webgpu-cts/cts.json` | `/gpuweb.cts` | 623 MiB |
The pin files keep the upstream URL as the source's identity; `oracle/suites/mirror.mjs`
`resolveSource()` turns it into the mirror's URL, `//..git` for
upstream `…//.git`. The forge and owner come from the first of
`WEBENGINE_GIT_MIRRORS` (the owner's URL, `http:///`), the
checkout's `rig` remote (the development box), `WEBENGINE_LANE_GIT_URL`, and the
checkout's `origin` remote when its URL carries credentials (a lane's worktree of its
`.git-mirror`). The mirror URL never carries credentials: the user and token (from that
URL, else `WEBENGINE_FORGE_USER` / `WEBENGINE_FORGE_TOKEN` in the environment or the
primary checkout's `.env`) reach git only as an `http./.extraHeader` in the
environment of the one git command, so a checkout's `.git/config` on the share holds the
address and nothing else. Every fetch prints which mirror it used.
`WEBENGINE_UPSTREAM_DIRECT=1` fetches from the upstream URL instead (a source whose
mirror is missing, or the forge down) and says so on every fetch; with neither a forge
setting nor the override, a fetch fails naming both.
Callers: `oracle/suites/fetch.mjs` (and through it `bench/nas-refresh.sh suites`),
`bench/nas-refresh.sh webgpu-cts`, `oracle/suites/fetch-checklayout.sh`, and `crates/wpt-votes/src/features.rs` (its WPT tree
cache). Shell and Rust callers run git through the script:
`node oracle/suites/mirror.mjs git `, an argument `{url}` standing
for the mirror URL; `mirror.mjs url ` prints the URL. `fetch.mjs` points a
checkout's `origin` at the mirror before anything that reaches the network (a fetch, or a
new sparse path whose blobs a blobless checkout fetches), so a checkout made from
upstream is switched in place, never cloned again; a checkout already at its pin needs no
forge settings. `oracle/suites/mirror.test.mjs` covers the resolution;
`oracle/suites/fetch.test.mjs` and `bench/nas-refresh-test.sh` fetch through a local
directory standing in for the forge.
Adding an outside source: create its mirror before the first script that reads it lands,
with the forge's migrate API and a token holding the repository scope —
`POST /api/v1/repos/migrate` with `clone_addr` the upstream URL, `repo_owner` the
owner above, `repo_name` from `node oracle/suites/mirror.mjs name `,
`mirror: true`, `mirror_interval: "8h0m0s"`, `service: "git"` (WPT took about 7
minutes; the call returns when the first sync is done). A pin moved to a commit newer than the mirror's last sync needs
`POST /api/v1/repos///mirror-sync` first.
npm packages and crates are plain files on the NAS share, filled only by the scheduled
refresh (`bench/nas-refresh.sh`, below), and every install and build reads only those
files: nothing contacts npmjs or crates.io at build time, and nothing falls back to them.
A package or crate missing from the share fails the install or build, naming it.
- npm: the share's `npm-cache/`, filled by `bench/nas-refresh.sh npm`.
`bench/oracle-install.sh` runs `npm ci --offline` with it as npm's cache; the share is
`WEBENGINE_LANE_SHARED` on a lane or in a job and `$HOME/lane-shared` otherwise.
`bench/lane-apply.sh` installs with `--offline` against it as well. npm's `integrity`
hashes are checked on every install from the cache. The `web-features` data
(`data.json`, which the web-features map and the feature loop read for each feature's
keys and specification URLs) is the installed package's file,
`oracle/node_modules/web-features/data.json`, pinned in `oracle/package-lock.json`;
`crates/wpt-votes/src/features.rs` `WebFeatures::load` reads it, checks the installed
version against the pin, and fails naming `bench/oracle-install.sh` when the package is
missing. It does not fetch from a CDN. `browser-compat-data` is read the same way:
`crates/content-analyser/src/bcd.rs` `Bcd::load` reads
`oracle/node_modules/@mdn/browser-compat-data/data.json`.
- Crates: the share's `crates/`, a vendored directory source (one `-/` per
crate) filled by `bench/nas-refresh.sh crates` from every crate `Cargo.lock` and
`bench/Cargo.lock` name (`cargo vendor --locked --versioned-dirs`, with its own
`CARGO_HOME` holding no source replacement). Crates are only added, never removed. Each
machine's own `config.toml` in its Cargo home replaces crates.io with that directory:
`bench/lane-dist.sh` with `DIST_ROLE=registries` writes the block. On a lane it reads
`LANE_SHARED_DIR` (default `/srv/lane-shared`) and writes the lane user's
`~/.cargo/config.toml`. The development box's user builds other projects whose crates are
not on the share, so its block goes into a Cargo home used only for this repository,
`$HOME/.local/share/webengine/cargo-home`, from `$HOME/lane-shared`;
`bench/devbox-cargo-home.sh` installs it (it runs the registries role with
`LANE_CARGO_HOME`). A cargo wrapper (`$HOME/.local/share/webengine/bin/cargo`, linked from
`$HOME/.local/bin/cargo`, which comes before `~/.cargo/bin` on `PATH`) sets `CARGO_HOME` to
it when cargo runs inside a checkout of this repository (a directory above holding both
`Cargo.lock` and `jobs/src/manifest.rs`) and `CARGO_HOME` is unset; elsewhere it changes
nothing. Dagu job steps (`bench/dagu/*.yaml`) put the wrapper's directory before
`~/.cargo/bin`; `bench/devbox-cargo-home-test.sh` covers the wrapper and that ordering.
`~/.cargo/config.toml` settings (sccache) still apply, because cargo reads the
`.cargo/config.toml` of every directory above where it runs. Not caught: `cargo
--manifest-path` or `-C` from outside a checkout. Cargo takes no `[source]` replacement
from environment variables, so the setting lives in each machine's Cargo home, not in the
repository. Each vendored crate carries a `.cargo-checksum.json`; cargo checks its
package checksum against `Cargo.lock` (a mismatch stops the build) and each file against
its recorded hash. `bench/lane-dist-registries-test.sh` covers the role. `experiments/` lockfiles are not
vendored; building them needs a Cargo home without the replacement.
The development box cannot mount the NAS, so `$HOME/lane-shared` holds its own copy of
both, refreshed the same way (`WEBENGINE_LANE_SHARED=$HOME/lane-shared
bench/nas-refresh.sh crates npm`, or the scheduled refresh on the box's own lane entry).
Other outside downloads are not yet mirrored: Playwright's browsers, specification sections (`fetch_specs`, over HTTP),
wpt.fyi run listings and raw results (`crates/wpt-votes` `runs.rs`, `build_table.rs`;
`oracle/suites/compare-wptfyi.mjs`), and release binaries from
GitHub for Dagu (`bench/dagu-lane.sh`) and sccache (`bench/lane-dist.sh`), plus the
toolchain installers `bench/lane-provision.sh` runs.
The wpt.fyi readers stay direct and are not mirrored: only by-hand reports use them, and
nothing in the feature pipeline does (`jobs/scripts/feature-triage.sh` and
`derive_feature_file` read the WPT mirror, and the browser vote comes from the rig's own
browser runs), while a copy on the share would cost about 2.4 GB per WPT revision.
**The track gate runs only on the build lane.** `tracks.json` → `verify` runs
`bench/verify.sh`, which hands itself to `bench/on-lane.sh --lane-only` and, once there,
runs `cargo test --workspace`, the job event store's tests (`jobs/events/*.test.mjs` under
`node --test`, then `bench/job-events-lane-test.sh`), the page selector's tests
(`oracle/suites/feature-pages.test.mjs` under `node --test`, on committed fixture pages
with no browser), and then `bench/generated-scores.sh`. The other `*.test.mjs` and
`bench/*-test.sh` files are run by hand, not by the gate. With no lane
configured or none answering (`ssh -o BatchMode=yes -o ConnectTimeout=5` fails) the gate
exits 3 and runs nothing on the development box; a lane that answers but fails the tests or
a score is a failed gate — no local retry. Measured before the scores were added: a new
worktree's first test run 106 s (124 s with a fresh mirror), a warm rerun 22 s, local
fallback 78 s. A clean `cargo test --workspace` compiles in 61 s on a lane and 57 s on the
development box — the build is bound by the dependency chain through Stylo, not by cores;
the lane's value is that it takes load off the shared box and runs several gates at once.
**Unattended landings never fall back to the development box.** `bench/on-lane.sh`
without `--lane-only` runs its command locally when no lane answers, and `bench/land.sh`
runs each `landChecks` command that way, and `verify` directly. Setting
`WEBENGINE_LANE_STRICT=1` in `bench/land.sh`'s environment (the scheduled
`jobs/scripts/poll-and-land.sh` gets it from `tracks.json`'s `specWatch.pollEnv`) makes
`verify` go through `bench/on-lane.sh --lane-only` too and every check `--lane-only`, so
with no lane answering the landing is refused with "verify failed on the tip (exit 3: no
build lane answered ...)" and nothing of the gate runs here. Unset, as in interactive
use, a check keeps its local fallback. `bench/verify.sh` itself is lane-only either way;
the one thing that would run it on the development box is `WEBENGINE_ON_LANE` set there by hand.
**`bench/on-lane.sh` probes the lane up to 3 times before calling it unreachable.** The probe
(`bench/lane-probe.sh`, 10 s connect timeout, 2 s between tries) fails now and then through the
jump host on a healthy lane; `WEBENGINE_LANE_PROBE_TRIES` changes the count, and each retry
prints one line to stderr.
**Already on a lane, `bench/on-lane.sh` runs the command in place.** With `WEBENGINE_ON_LANE`
set, `bench/on-lane.sh` (and `bench/remote-lane.sh`) runs its command in the named
worktree on the machine it is on, `--lane-only` or not, reads no lane settings, and
ignores `--pull`, since the outputs are already where they belong; the command's exit
status is the run's. This is the same check `bench/verify.sh` makes, so a spec's
`bench/on-lane.sh --lane-only "$PWD" ...` works unchanged inside a Dagu job, whose clone
has no `.env` and no `bench/lane.local.json` and whose writer gets `WEBENGINE_ON_LANE=1` from
`jobs/scripts/pi-session.sh`. As with `bench/verify.sh`, a `WEBENGINE_ON_LANE` set by hand on the
development box would run the command there. `bench/on-lane-test.sh` covers both sides.
The cost: with both lanes down nothing lands until one answers. `bench/land-test.sh`
scenario `s1` covers both settings.
**The gate scores every generated function against its own corpus.** A committed file under
`crates/layout/src/generated/` can be wrong while every test passes, because the tests
exercise in-tree reference rules, not the generated function. `bench/generated-scores.sh`
clears and rebuilds each corpus with its `*_ground_truth_cache` binary (the browser oracle
runs on the lane), then `generated_score_check` scores each generated function through its
own runner and fails it against its floor. The floor is two numbers in
`crates/layout/src/generated/.floor.json` beside the function's provenance record:
`cases_passed`, how many cases it passes, and `cases_run`, how many cases its corpus holds
(only cases three engines agreed on). It fails when the passing count drops, when the corpus
shrinks even though the passing count holds, and when a function passes no case at all (a
lane missing a browser leaves fewer than three engines to vote, so every case drops; a floor
of zero cannot hide a function that scores nothing). The provenance record describes the
model batch that admitted the function and is never rewritten; the floor is never below its
`verification.cases_passed` when the record names `verification.scoring` as `browser-measured
inputs` (how the runner scores now, `docs/generation-loop.md` -> "Scoring a function:
browser-measured inputs"); a record with no such field was scored another way and binds
nothing, so its floor is the floor file alone. A function with no floor file yet is held to the
record alone.
That is where a function a feature job admits starts: the job's `admit` stage
(`jobs/scripts/admit.py` `store_main`) writes the provenance record and no floor file, since
`bench/land.sh` refuses a branch that touches one, and the first floor comes from the raise
after the landing (below). A
placeholder record, such as the hand-written `table` one (0 recorded), therefore floors at
its real score once a floor file exists.
**Floors rise by themselves, after a landing.** `bench/after-land.sh` ends with
`bench/generated-floors-update.sh`, which runs `bench/generated-scores.sh --print-raises` on
the build lane, writes each floor that the fresh score is above (a function with no floor
file gets one), and commits only those files as one commit on `main`, the way the map and
dashboard commits are made. It refuses on uncommitted changes, writes nothing when the lane
does not answer or a score fails, never lowers a number, and a failure is one line and does
not change the landing. Raising happens after the landing rather than in the gate because
the gate runs on the lane's copy, where a commit cannot reach the track, and because a track
never edits a floor: `bench/land.sh` refuses a diff that touches any `*.floor.json`, so an
unattended track cannot lower one. The gate scores with `--print-raises` too, and a gate
that passes on a checkout with nothing uncommitted or untracked keeps its raise lines keyed
by the git tree it scored (`bench/generated-raises.sh`, in the repository's shared git
directory). A landing is a fast-forward to exactly that tree, so the raise after it takes
those lines instead of scoring again, as long as `main` has changed since only in
`README.md` and under `docs/` (the dashboard and map commits that run first). A gate on a
dirty checkout, or a landing whose tree no passing gate scored, still costs a second
scoring run on the lane. Either way there is a window between a landing that improves a
function and the raise in which the old floor holds. A floor comes down only by a person's
reviewed edit of its file.
**One place to add a generated function.** A function is discovered from its
`.provenance.json`; its runner and the cache binary that writes its corpus are one
entry in `FUNCTIONS` in `crates/generation/src/bin/generated_score_check.rs`, and a
provenance record with no entry fails the gate with that instruction. `bench/generated-scores.sh`
takes the caches to rebuild from `generated_score_check --list-caches`, so nothing under
`bench/` changes and a track adding a function can land unattended.
`bench/generated-scores-test.sh` exercises the scripts, including the raise, without a
browser. `bench/score-generated-oracle-test.sh` runs `jobs/scripts/score-generated.sh`,
what a feature job's `admit` stage scores with, against the real browser oracle on the
box-generation train pages (generated, no network; about two minutes), on the development box
only. It and `bench/feature-job-box-test.sh` (`docs/local-lane.md` → "Before a folknet
send") run in the job-system gate, `bench/e2e-gate.sh`, on every landing that changes the
job system, `crates/generation/`, `crates/provenance/`, the harness's case modules or a
`specs/*.job.json`, and fail it when either fails (`docs/local-lane.md` → "Scenario jobs").
**Model batches run only on a lane.** `bench/absolute-mixed-batch.sh` and
`bench/overflow-mixed-run.sh` are started from the development box like any script
(`WEBENGINE_MODELS=qwen3.8-27b-fast:32:32 bench/absolute-mixed-batch.sh`) and re-run themselves
on the lane through `bench/on-lane.sh --lane-only` (`bench/batch-lane.sh`): the `WEBENGINE_*`
settings go along, credentials and lane settings excepted, and the batch's results come
back with `--pull`. With no lane configured or none answering the script exits 3 and runs
nothing locally; `bench/lane-apply.sh` exports `WEBENGINE_ON_LANE=1`, which is how a script
knows it is already there. Each run also records its phase timings and how many of the
lane's CPUs each phase kept busy (`bench/cpu-sample.sh`, `bench/cpu-phases.py`;
`docs/rig-capacity.md` → "A real batch on a build lane").
**A stray directory at a worktree's lane path breaks the gate.** `bench/lane-apply.sh`
tests only for `//.git`; a leftover directory without it makes `git
worktree add` fail, and the gate cannot pass until it is removed by hand (check `git -C
worktree list` first). Not yet fixed in the script.
**Cleanup.** Every worktree run on a lane leaves its copy (`/`, `target/`
and all, 3–12 GB) and `/.payload-` there; left alone they filled a lane's
disk. `bench/lane-sweep.sh` removes, on every lane in `bench/lane.local.json` `lanes[]`,
the copies whose track has no worktree in the primary checkout (`--dry-run` lists them
with sizes). It keeps the primary's own copy, any copy with a process running in it (a
spec writer runs inside its copy), and directories that are not worktrees of the lane's
mirror. Untracked data under a copy's `corpus/` (links excepted: they point into the
NAS already) goes to the NAS first, as one tar file at
`archive/corpus//