Skip to content

Gate Execution Economics

A fleet’s cost per landing is a governance property, not a performance detail. The decision set is recorded in the architecture decision record Gate Execution Economics for an N-Party Fleet. This page is the operator-facing summary of that record.

A gate-attempt ledger scan over 2026-08-21 to 2026-09-02 — grouping ledger events by work unit, head commit and lifecycle stage, and taking each group’s wall span — reports 286 runs, a median of 487 s and a p90 of 1326 s, and a distribution that is flat for the whole window: it does not respond to change size, to fleet composition, or to several landed point improvements. A cost that ignores the size of the change is not a testing cost — it is a fixed toll, produced by admission, selection, and reuse decisions that were each made locally and never reconciled with one another.

Never-skippable coverage

No decision may let a change land without a gate result that actually observed that change. Faster is never a reason for less observed.

Fail closed on uncertainty

Any reuse, cache, or detection path that cannot prove its precondition from a complete, content-derived fingerprint executes the full work instead.

Evidence is attributable

Every admission, selection, reuse, fallback, and capability-absence decision is recorded in the gate-attempt ledger with the inputs that produced it.

Alongside them stands a standing negative-control requirement: every reuse or narrowing path ships an executable test that makes a real, behaviour-changing edit inside the path’s claimed-safe region and proves the path misses — that the affected gate still executes and still fails. A performance measurement is never a negative control.

#Decision
D1Weighted admission replaces both host ceilings — the fixed-capacity gate-execution semaphore and the capacity-1 whole-run build-output hold.
D2One workspace-graph test oracle with printed selection evidence; a zero match fails closed instead of running the whole workspace.
D3Evidence reuse across heads and across the done-side rebase, bound to a normalized content fingerprint and an untouched graph neighbourhood.
D4Content-addressed build outputs with atomic activation; isolation is the default, declaration is the contract.
D5Incremental typecheck and lint with fail-closed cache validation bound into the prep fingerprint.
D6Input-hash-bound cached test results count as attestation evidence under five stated conditions, and otherwise the test executes.
D7A neutral remote gate-capacity port: local and remote providers are indistinguishable to the caller, and the default needs no network.
D8The N-party landing-queue protocol, with a coordination-epoch clock and three explicit consensus classes.

Sequencing is binding: D4 before D1; D2 before D3 and D6 (until D2 lands, both reuse paths use the conservative whole-workspace over-approximation); D1 before D7.

D7 is planned for a later phase:

  • D7 (the remote port) has two creation triggers. One is that adding a machine adds capacity. The other is that it becomes a later work unit when the measured p90 over the next 30 recorded gate runs after D1 lands — or the metrics command’s default window, whichever set is larger — is still above the advisory prep latency target. The advisory target is the threshold deliberately, not the phase-4 enforced budget: waiting for a release to fail would make the port permanently unreachable.
  • D4’s general half. Content-addressed outputs, atomic activation and verified reuse are delivered for this workspace’s own CLI output only, and digest-based freshness — bigint stats plus a per-file content digest — has since landed, after this decision’s second trigger fired: a fail-closed case recurred on the integration target itself. What remains unimplemented is declared output roots for arbitrary toolchains, content addressing for them, and opt-in serialization for a declared shared root; that becomes a phase-3 work unit when a workspace declares an output root name-based detection cannot resolve. Until then D1’s conservative key carries the uncertainty.

Graph neighbourhood (D3). For a change set, the neighbourhood is the union of its dependency closure (everything the change can observe) and its importer closure (everything that can observe the change), plus the tests selected from either. Reuse requires that nothing moved inside it — in both directions — and the negative control tests both.

Session scope (D3 versus D6). The same-session restriction applies to lifecycle authority artifacts — checkpoints and receipts — because those decide who may land. A hash-bound test result answers a different question, whether an assertion observed this exact content, so it is admissible whichever session, owner, or machine produced it, provided all five D6 conditions hold. Verifiable provenance is what replaces session identity there, and a remote provider’s cache is admitted on exactly those same five conditions.

wu:prep runs every ratchet suite whose declared scan paths touch the diff, so a landing cannot break main from a rule it never ran. Correct selection is not enough on its own: a suite that reads the whole repository costs the same for a one-file change as for a release, and selecting 30 of 40 suites made a one-file prep take about 85 seconds.

The static ratchet phase therefore hands the diff to the suites it runs. It writes the work unit’s added, changed and deleted paths, plus the commit they are measured against (the merge base with origin/main), to a private scope file, names it in LUMENFLOW_RATCHET_CHANGED_FILES_SCOPE_FILE for the one test process it starts, and removes it afterwards. A suite that reads the scope behaves in one of two ways:

  • An enumerating scan (the shared shipped-source scanner and every guard built on it, the package-manager spawn guard, the path-spelling guard, the state-namespace guards) reads only the changed files. The matchers and exclusions are the suite’s own and do not change; the scanner’s anchors and size floors are still judged against the whole enumeration.
  • A “count must not increase” baseline (the path-literal, file-extension and env-var-literal ratchets) compares the changed files’ contribution with the base’s: the saved baseline moved by what the changed files now add or remove against the same files at the merge base. The baseline is never skipped and never read as zero, and a scoped run never rewrites it.

Three rules keep it safe. Unset, which is the post-land sweep, the release cut and a developer running a suite by hand, every suite scans everything. A scope that is named but unreadable is an error, never an empty scan. A suite whose own file, or a module it imports, changed keeps a full scan, because its matchers may have moved and every file has to be judged again.

The protocol governs landing order for any number of orchestrators in any harness, over LumenFlow’s existing coordination primitives. It introduces no new transport and no lock: exclusivity is a grant with an expiry.

  • The clock is a coordination epoch, not a wall clock. Every party derives the same monotonically increasing epoch, per integration target, from the durable signal log: an epoch closes when a queue-affecting event is recorded by any party other than the current grantee, evaluated against the state immediately before the event applies — so a custodian granting a window to itself still closes an epoch. Grantee heartbeats mark presence within an epoch and never close one, so a grantee cannot keep its own window alive by talking. A heartbeat discharges the obligation for the epoch open at its own recorded position in the log — not the epoch it cites — so every party computing revocation or custody release independently gets the same answer. A cited epoch that disagrees with that position is never a well-formedness failure — detecting it needs the log — but what follows differs by class. For a heartbeat it is a substantive-validity failure and the heartbeat discharges nothing, because a heartbeat’s whole content is its claim to be present in a particular epoch. For every epoch-closing class the position-derived epoch simply governs and the citation is repaired, not refused: emitters predict the epoch they will open, contention can overtake that prediction, and refusing on that basis would reject honest traffic exactly when the queue is busiest.
  • Why a sole party’s window cannot expire. Not because the clock is frozen: a single party’s own readiness signal and self-grant are emitted while it is not yet the grantee, so they do close epochs. It is because once it holds the window, every message it emits is a grantee message, and with no other party to emit a queue-affecting event nothing closes an epoch for as long as the window is held. The clock cannot advance while that party is the one being measured, which is the property that matters.
  • Two tests, and only one drives the clock. Well-formedness is structural — every element of the message class’s minimum, present and syntactically valid, checkable from the message alone — and it is what closes an epoch. Anchoring additionally requires every cited identifier to resolve in shared durable state, and it governs whether a party may act. A well-formed but unanchored message closes its epoch and is refused for action, so the clock never forks on a resolution only some parties can do. Acceptance is a Class A judgement and a separate logged message: it governs queue membership, never the clock.
  • Only some identifiers are resolvable. Anchoring applies to signal ids (a window id is a grant’s own signal id), work-unit ids, initiative ids, lease or party ids, and the authorization id — each names a record the log holds or projects. Commit identifiers are content: syntactically checked, never a bar to anchoring, and compared against a branch tip only under the epoch-boundary divergence rule. Without that carve-out no grant or heartbeat could ever anchor, and no window would ever be granted.
  • Readiness evidence must be shared, and it fails closed. Gate-attempt ledgers are per-worktree, so a bare ledger id means nothing to a peer. A readiness signal therefore publishes the gate-attempt records themselves — id, gate, outcome, declared commit, result digest — into the shared log, and those records are elements of its class minimum. A signal carrying only bare ledger ids is therefore not well-formed: it closes no epoch and cannot be granted against. For a conforming signal, non-anchoring can arise only from its work-unit id or pinned initiative ids, both of which are resolvable.
  • A window closes just before its own closing message applies, so a return or a landing completion is not emitted by “the grantee” in pre-event state and both close an epoch as enumerated, instead of being dead letters.
  • The queue is derived, not stored. Every party computes the same total order: priority first, then the readiness signal’s timestamp as recorded by the coordination store, then work-unit identifier as a deterministic tiebreak. The queue is the set of readiness signals that are unretired and unrejected — acceptance is not a membership precondition, and a custodian may grant to an unaccepted head, so a silent lease holder cannot stall every landing. Entries leave only by logged message: landing completion, rejection, retirement, or a recorded revocation, which retires the signal it answered — without that a revoked entry would have no eviction path and the custodian would re-grant to the same silent party forever. A declared head that no longer matches its lane branch tip is a reason to retire, evaluated at an epoch boundary, never an eviction on unlogged observation.
  • A readiness signal carries its evidence, and that evidence must pass — work unit, declared head, the cheap gates’ gate-attempt records published into the shared log, the pinned affected-initiative set (pinned for the same reason a Class B roster is, so the Class A conjunction cannot drift), and the recorded priority. Each carried record must show a terminal pass of that gate against the declared head: never a skip, never a partial run, never a timeout, never another outcome, never another head — the same shape D6 condition 2 uses. That test is well-formedness, decided from the message alone, so a signal carrying a failing, skipped or wrong-head record closes no epoch and joins no queue rather than sitting at the front of one.
  • One landing window per integration target, granted by the custodian, naming grantee, base commit, expiry epoch, and the readiness signal it answers. The expiry epoch is derived, not chosen: grant_epoch + window_span, where window_span is workspace-declared with a default of 3 and a range of 2 to 10 — never below the revocation threshold, or a window could expire before the evidence to revoke it could exist. It is re-derived from the position-derived grant epoch, so the carried number is repaired along with the citation; honouring a carried expiry after contention overtook the grant’s predicted epoch would shrink the effective span below its own bound and leave two readers disagreeing about whether the window is still open. The cited pair is range-checked as well-formedness only. The window id is the grant’s own signal id, so every later citation of it resolves in the shared log by construction. Custody is a role any lease holder may serve; a custody change inherits an open window rather than voiding it.
  • A window that closes without a landing does not strand its entry. On a return the readiness signal stays queued but is passed over for the next grant — returning is penalty-free, so retiring the courteous case would be perverse. On expiry, which has no message of its own, the custodian records a retirement with reason expired; until then the entry is passed over too. On revocation the signal is retired outright. “The next entry” in all three cases is the queue front re-derived after the closure applies, skipping anything currently passed over — so an ever-heartbeating, never-landing party is retired by expiry rather than holding the front.
  • Well-formed is necessary, not sufficient. A message is also refused when it is substantively invalid — claiming an authority its emitter does not hold (a grant or revocation from a non-custodian), asserting a transition the log does not support (a takeover before the threshold), or arriving out of order (a grant for a target that already has an open window). Like anchoring, this governs action, not the clock.
  • Bootstrap and contest resolve in a fixed order. A target with no custodian is claimed by stating the observed absence; every epoch-closing message cites the epoch it itself opens, so a bootstrap claim that is the target’s first message cites epoch 0. Contests then resolve: validity first — a bootstrap claim whose asserted absence the log contradicts, or a takeover before the threshold, is substantively invalid and never enters the comparison, however late it is; then recency by position-derived epoch, not by the cited number, because position is the objective fact and a citation is only an assertion; then the lowest lease-or-party identifier, reachable only when two parties predicted the same next epoch concurrently and the store has not yet ordered them. The tiebreak is a concurrency rule and never overturns an order the store has established. Later-wins governs a takeover race, and being recorded after something is never itself a ground for refusal — a valid takeover is by definition ordered behind the claim that installed the custodian it replaces, so only the asserted state can invalidate a message.
  • Takeover after two consecutively closed epochs without a heartbeat — from the grantee, its window is revoked; from the custodian, custody is released. The epoch in which a grant or custody claim was made never counts, so every party gets one full epoch of grace before the clock can be used against it.
  • Queueing constrains landing only. It never withholds a claim, never blocks a gate run, and never serializes unrelated lanes.

Each class carries its own minimum, and a message is never refused for failing a different class’s minimum. A readiness signal, its acceptance or rejection, its retirement, a grant, a heartbeat, a landing completion, a custody claim, a custody withdrawal, a Class B proposal, and a Class C action each have their own — so a governance proposal is never refused for lacking a commit id it could not have, and no epoch-closing transition exists without a minimum that makes it checkable. Where a fleet has no lease holders, the identifier a custody claim or an acceptance carries is the party’s own identifier rather than a lease id, so the lease-less clause is executable rather than merely stated.

ClassWho must agreeExamples
AThe lease holder of each affected initiativeGranting, returning, revoking a landing window; accepting a readiness signal; scope narrowing inside its own initiative
BEvery lease holder (unanimity, fail-closed)Changing the protocol itself; freezing the integration target; cross-initiative landing order; fleet-wide gate admission defaults
CThe ownerSkipping or weakening a never-skippable gate; any ownership override; weakening an attestation invariant or removing a negative control

The classes are not disjoint, so precedence is strict: C > B > A. A decision falling in more than one class is governed by the highest, and the lower class’s assent never substitutes — though where the mechanism structurally needs both, both are required. Plainly: lease holders may amend the protocol by Class B unanimity, but any amendment that weakens an attestation invariant, removes a negative control, or narrows a never-skippable gate additionally requires the owner under Class C. Coordination is theirs to change; the invariants are not theirs to vote away.

A landing work unit spanning several initiatives needs Class A assent from each affected lease holder. One with no initiative lease falls to the custodian of its integration target. With no lease holders at all, party count and lease count are treated separately: at one party, its owner is its own Class A authority; with several parties and still no lease, custody opens to any landing party under the same contest rule, until the first party to take an initiative lease supersedes it.

A gate run’s locks and ledger rows belong to the run, not to whatever process started it:

  • WU context comes from the run’s own checkout. A gate run reads its work unit and lane from the git branch of its own working directory. A run against another directory is never attributed to the work unit of the process that started it, so it never appends gate-attempt rows to that work unit’s ledger.
  • A programmatic run can own its locks. The trusted lockRoot option (not a CLI flag) places the run’s admission pool, build-output lock and stale-lock sweep under a directory the caller owns. In-process test runs use it, so they never queue behind the host-wide locks that real lifecycle runs hold.
  • A directory outside every checkout does not take the host-wide lock. A build-producing gate whose directory has no .git or workspace.yaml at or above it (temp directories are never climbed past) refuses with the remedy instead of waiting on the machine-wide build-output lock. A real checkout whose build-output roots cannot be keyed still falls back to the machine-wide lock, as before.

Every vitest run in the repository asks for host admission, so a hand-started test cannot saturate the cores unseen. The request is sized by what the run executes:

  • A focused run takes one unit. vitest run <file> with one to three existing test files takes one admission unit. A run with no file filter, or with more files, keeps the larger scoped weight.
  • It backfills idle capacity. A one-unit request is admitted whenever a unit is idle, even when larger requests such as preps are queued ahead of it. Backfill never starves them: once the oldest ordinary request at the head of any repository’s queue on the host has waited 60 seconds, small requests stop jumping it and released units accumulate for it (queue fairness).
  • The wait is two seconds at most. If no unit is idle within 2 seconds, the run withdraws its place in the queue and proceeds at the lowest CPU priority, so it never takes CPU from an admitted gate. The admission line it prints says which of the two happened.
  • A refused request keeps its place. A request refused at the 30 minute cap keeps its queue position for 10 minutes. A retry of the same command resumes its place instead of queueing behind everyone who arrived meanwhile.
  • Concurrent runs share a cache that cannot be wiped. The vitest module cache directory is named for the install lockfile, so a dependency change never deletes a cache another run is writing.

A lock owner is identified by its pid plus its process start time, never by the pid alone. Windows reissues a freed pid quickly and keeps lock directories in %TEMP% across reboots. If a run is killed with taskkill /F, or the machine loses power, another long-lived process can take over its pid. That process no longer keeps the dead run’s locks alive:

  • Every lock record carries the owner’s start time. This covers gate admission tickets and holders, the build-output lock, the prep-evidence lock, the wu-events and completion locks, the lane lock and the installed-trust ledger. A record whose pid is alive but whose start time is not the owner’s is reclaimed. An owner LumenFlow cannot verify is never reclaimed.
  • Waits are bounded. The build-output lock and the prep-evidence lock stop waiting after 60 minutes and fail with an error. The error names the lock and the pid holding it. For the prep-evidence lock it also names the recovery: once no wu:prep for that work unit is running, delete the named directory.
  • Records written by an older version keep working. A record without a start time is judged by its pid alone, as before.

Capacity follows the machine (amendment, 2026-10-06)

Section titled “Capacity follows the machine (amendment, 2026-10-06)”

The 2026-10-06 amendment decides that gate capacity follows the measured machine instead of a fixed product cap. It is a decision, not yet behaviour:

  • No fixed product maximum. Capacity is the processor and memory quota the host reports, so a larger host admits proportionally more work. gates.execution_lock.concurrency becomes an optional cap that can only lower it.
  • Runs get their need, not the whole ceiling. Two runs whose summed need fits run together; a run that does not fit waits.
  • The operating system shares processor time. Gate processes run at a lowered operating-system priority. LumenFlow guards memory, disk and deadlines, and a guard only slows new starts; it never signals, pauses or ends running work.
  • A lone run is never turned away for memory. It starts with fewer workers. The one refusal is a disk that cannot hold the run, and it names the filesystem.
  • Adding a machine adds capacity, through the D7 port with one operator-declared reference adapter.
  • Platform gaps are named, seven of them, in the amendment itself.

Today’s behaviour stands until each part lands: capacity is still capped at 16 units and a full run still takes the whole ceiling. The statement below tracks what has landed.

Implemented today: weighted gate admission that replaced the fixed capacity-2 semaphore, declaring a run’s weight from its resolved execution plan against a measured processor and memory ceiling and failing closed to the conservative grant whenever measurement is unavailable, with a declared gates.execution_lock.concurrency still able to cap a workspace below that ceiling and never above it (superseded by the 2026-10-09 amendment; deletion planned); a build-output writer lock of capacity 1 with a 60-minute fail-closed wait (the build-output exception, not capacity admission), keyed on the resolved build-output root and falling back to a machine-wide key whenever detection is unresolved or spans several checkouts, now acquired per build-producing gate rather than held around the whole run, so a read-only allowlist gate never acquires it at all; a gate-attempt ledger that records every gate execution; prep evidence bound to a mutable branch head and disabled by default configuration; two test-selection oracles with a whole-workspace zero-match fallback; a hard-coded full-workspace typecheck command alongside a configured affected-only command no gate reads; runner worker counts pinned below the host’s capacity; an advisory-only prep latency budget alongside an opt-in per-prep enforced budget that ships disabled; and a release-time throughput regression ratchet in the pre-release checks that records gate-run cost alongside claim-to-done phase/size-band medians and lifecycle command wall time, refusing only on a claim-to-done or lifecycle command regression against the prior release, never on gate-run cost alone. The release-time regression gate is that ratchet; the per-prep budget is an operational guard a workspace opts into, and D7’s creation trigger depends on neither of them.

Planned, not yet implemented: a single workspace-graph test oracle, supplied as a declared pack-owned capability, with printed selection evidence and no zero-match fallback; evidence reuse across heads and across the done-side rebase, bound to a normalized content fingerprint and an untouched graph neighbourhood in both directions; content-addressed build outputs with atomic activation and declared output roots, of which only this workspace’s own CLI output has landed; incremental typecheck and lint with fail-closed cache validation; input-hash-bound cached test results admitted as attestation evidence under five stated conditions; the gate capacity clause now points to the 2026-10-09 amendment, which removes the gate lock and its admission pool: live memory and disk start guards with a bounded defer budget, operating-system priority classes at exec and slowdown-aware deadlines, under invariants S1-S7 and the lock deletion (Release A in observe mode, then Release B after calibration); the 2026-10-06 capacity clauses it supersedes are listed in its supersession table; and a neutral remote gate-capacity port with one operator-declared reference adapter; and the N-party landing-queue protocol with its coordination-epoch clock and three consensus classes.

  • Gates Reference — the gates that run today and how they are scoped.
  • Trajectory Supervision — the gate-attempt ledger this record extends from productivity to cost.
  • Product Policies — cross-platform parity, documentation parity, and vendor neutrality, all binding on this record.
  • Metrics & Flow — where the throughput measurements come from.