Developer preview. Not production-ready, not audited, not for sensitive code. Read the limits

Record
INSTRUMENT-LOCK
Status
superseded by NO-LOAD-BLOCK
Built
built
Decided
2026-10-08 by the coordinator

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.cacheRelativeLockPath under .cache of the password database’s home: $HOME and $XDG_CACHE_HOME never 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.minimumTestFiles test 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 passes wholeInstrumentTestFileCount, 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), a vitest list included: no verb is exempt. (Checkout setup once had a heavy step, the web module cache warm-up that listed the suite, admitted by the checkout runner under the lock. It was removed on 2026-10-29 with the mechanism that admitted it: an optimisation judged by a 600 s wall ceiling with a timed retry, it refused flake reproductions on a loaded host, #652.) 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 for vitest list. Measured: vitest list --filesOnly --json never 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 uses list in scripts/test-shard-manifest.mjs, which records a content fingerprint of the working tree before and after the listing; verify recomputes 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 names npm run test:e2e.
  • A measurement is stamped. A run inside a live hold, by the owner’s acquisition token, is stamped held; anything else is unslotted, 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.

Built, and this decision superseded (2026-10-22). No heavy run waits for another. src/process/instrument-lock.ts admits every heavy run AT ONCE and records it in a run registry beside the configured path (shown, never waited on); the admitting process moves into a cgroup v2 scope of the run’s own (testExecution.instrumentLock.perRun: memory ceiling and CPU weight) and under the harness’s CPU-time limit (src/process/subprocess-bounds.ts), so everything the run starts is born under both. A host that cannot make the scope refuses the run (per_run_caps_unavailable); there is no uncapped mode. The wait, its poll interval and its signal handling are deleted. What stays of this decision: the registry’s place and the one definition of heavy; the supervising door, adoption and borrowing; the stamp, until the measurement half retires it (audit A-04, A-07). Exclusivity survives only for the merge-ref update and single-writer database files.

Search the documentation

Type to search the curated record.

↑↓ move Enter open Esc close