The host has one instrument lock
The coordinator’s HEAVY-SLOT decision (PLAN.md, 2026-10-04; superseding the HEAVY-SLOT-ADHOC row), carried out as ONE waiting lock in LAND-OC-FIX rounds 5 to 12. It closes L-2026-10-04 (two heavy runs sharing the cores measured nothing about either tree).
- One lock, one primitive. A heavy run — a full or multi-file suite, a
coverage run, a perf profile, a load run, a workspace validate or merge —
holds the host user’s ONE instrument lock
(
testExecution.instrumentLock.cacheRelativeLockPathunder.cacheof the password database’s home:$HOMEand$XDG_CACHE_HOMEnever move it; never in a checkout, never in the keep-out floor). It is built on the one lock primitive (engine/src/filesystem/file-lock.ts): a record naming the holder by pid AND start time, read without following a link, without blocking and bounded; a holder counts only while it, or a process it adopted, is live by that identity AS THIS USER (pid 1, or another user’s process, holds nothing). - One definition of heavy, from config (round 13). A run of
testExecution.instrumentLock.minimumTestFilestest files or more is heavy; fewer is a light, targeted run that takes nothing. A WHOLE INSTRUMENT is heavy by kind, as config declares it (wholeInstruments: every Playwright run, whatever config, project or filter it names; the coverage run; the perf profile; a slot run; a flake reproduction): its door passeswholeInstrumentTestFileCount, and a kind config does not name is refused. The vitest door counts the files vitest itself selects (its own CLI parser and specification search), avitest listincluded: no verb is exempt (a setup step that lists the suite,web-module-cache, is declaredholdsInstrumentLock: the checkout runner takes the lock before it starts, its wait counting toward no timeout, once per tree, and a read-only create skips it, so a create waits behind a validate and never fails for it). The engine runner and the web runner count their own arguments (namedTestFileCount); a web run that miscounts itself light is still caught by the vitest door, which re-runs it inside the supervising door. Known limit: the guard that every door reaches the lock is a hand list of source patterns over named files (engine/test/prevention/test-run-lock.test.ts), not a search for every spawn of a runner (L-3, owned by the TASKS row LOCK-DOOR-CENSUS). Amendment (round 4): the claim above is the VITEST DOOR’s, and the door does not run forvitest list. Measured:vitest list --filesOnly --jsonnever invokes the global setup, so the listing process takes no lock. A raw listing is therefore exempt in fact, whatever this bullet says, and this amendment does not pretend otherwise: the shard path now useslistinscripts/test-shard-manifest.mjs, which records a content fingerprint of the working tree before and after the listing;verifyrecomputes it and refuses a raw array or a listing from another tree. The listing STILL takes no instrument lock — that is a stated gap, not a silent one — but it is bound to one tree state, which is what a population claim needs. - A second heavy run WAITS, saying who holds the lock. The wait is asynchronous and honours SIGTERM, SIGINT and SIGHUP: a signal ends it with a typed exit (128 + the signal’s number), and nothing is taken.
- One supervising door holds it for others.
runInsideInstrumentLock(harness slot run, the coverage run, the perf profile,test:e2e,test:all) takes the lock, runs the command under the test-process supervisor and ADOPTS it, so the hold lives until the command’s process tree is gone, even when the door itself is killed. An in-process holder (the vitest and Playwright global setups, the coverage run’s and the perf profile’s inner process) only BORROWS a hold it runs inside, through the one door, which refuses it outside a live hold. ONE marker says “inside the door” (HARNESS_INSIDE_INSTRUMENT_LOCK, set by the supervising door): it chooses the inner path, it never grants the lock. Outside a hold, the vitest door RE-RUNS its own command inside the supervising door (as the coverage run and the perf profile do) and leaves with its code; the Playwright door is refused (instrument_lock_unsupervised) and namesnpm run test:e2e. - A measurement is stamped. A run inside a live hold, by the owner’s
acquisition token, is stamped
held; anything else isunslotted, and an unslotted measurement is refused, never judged (UR-24-BUDGETS, D-17). The stamp’s limit: a same-user process that can read the record can copy its token. - Fairness is open. A waiter polls, so a holder whose next run starts at once can win every hand-over (INSTRUMENT-LOCK-FAIRNESS): its ordering lives inside the one primitive, never a second mechanism beside it.
Amended by NO-LOAD-BLOCK (2026-10-15)
NO-LOAD-BLOCK changes this
decision’s rule that a second heavy run waits. The amended rule: nothing
waits on, or fails because of, host CPU load. Once every judged measurement is
load-independent (CPU work, not wall time: Q-TX2,
UI-CPU), the one instrument lock
gives way to concurrent runs under per-run caps; landings are never queued
behind lab runs. Until that lands, the lock described above is still what the
code does: heavy runs take it and wait for it, and this section is the
decided change, not a built one (NO-LOAD-BLOCK is pending, route TEST-10X).