# Golden CI operating protocol

<!-- [doc->REQ-GOLDEN-CI-LANE] -->

Builder pull requests run `.github/workflows/ci.yml`: lint, unit tests, and requirement traceability only. The gater assembles every milestone PR into one `golden/<milestone>` merge chain. `.github/workflows/golden.yml` runs the full cross-platform suite on that branch. Main advances by fast-forward only to the exact green golden SHA. An urgent fix is a golden batch of one.

## Dispatch and concurrency

Pushing the assembled `golden/**` branch starts the full suite, including both coordinated two-host jobs. `workflow_dispatch` is the explicit rerun/manual avenue and must target the same candidate ref; its `twohost` input defaults on and is an explicit opt-out only when isolating other golden evidence. The two-host jobs wait for the suite as a rendezvous barrier but run after any non-cancelled suite result, so an unrelated red cannot turn seam evidence into a dependency skip. Verify two-host evidence from the job list and conclusions—an absent or skipped job is silence, not a pass. Golden runs never cancel in progress. Thin runs may cancel superseded work on the same non-main ref; main evidence is exempt from cancellation.

## Red protocol

1. Record the exact candidate SHA, platform, job, and failing test id. Do not rebuild or amend the candidate.
2. Diagnose the named failure against that batch first. Fix a deterministic product or test defect on its owning builder branch, rebuild the golden chain, and run the new SHA.
3. A failure may use the flake avenue only when `.github/ci/flake-registry.json` has an active entry matching the exact test id and platform. Every entry must contain a prior green same-SHA rerun, its run URL/id, and a concrete retirement condition. `.github/ci/flake-registry.py` validates these fields mechanically.
4. A registry match permits exactly one `workflow_dispatch` rerun of the unchanged SHA. Green clears that named failure for the batch. Red again blocks the batch and immediately invalidates the flake assumption; diagnose or fix it.
5. An unregistered failure gets no automatic rerun. If diagnosis cannot name the failing PR or shared interaction, bisect the assembled merge chain as the last resort.
6. Preserve both run URLs in the gate record. Never treat a green run of rebuilt bytes as evidence that the original red was a flake.

Registry entries are test-infrastructure code. Review additions and removals like workflow changes. Retire an entry as soon as its condition is met; a registry is not a permanent ignore list.

## Shared-runner quiet-window predicate

<!-- [doc->REQ-GOLDEN-CI-LANE] -->

A shared box is quiet only when **both** conditions hold:

1. zero non-terminal CI runs; and
2. no local `cargo`, `rustc`, or `cargo-nextest` process alive on the box.

A GitHub-only drain check reads clean while another agent is mid-local-build. That is the contention class that muddied the g6 evidence twice. These axes are proxies for contention, not a definition of quiet; a proxy returning “quiet” is not itself a measurement of quiet.

### Queue status is not occupancy

`gh run list` status is not an occupancy probe. A run can report `queued` while its job is already executing on the box; this was observed on #121/#122 with `Runner.Worker` and `cargo-nextest` live and a suite mid-flight. Therefore `queued` never means “not started,” and an idle-looking queue is not evidence of a deaf runner.

The authoritative occupancy signal is the process table. `Runner.Worker` means a job is running on this box. `cargo-nextest` means a suite is running, which is the seedmap-starvation collision.

### Attribute process load before counting it

The two predicate axes are not independent: a CI job manifests in the local process table as `cargo` and `rustc`. Their presence does not imply a second occupant. Walk each PID's parent chain before counting it as new load:

- a chain rooted at `Runner.Worker.exe`, `Runner.Listener.exe`, or `RunnerService.exe` is the CI axis already counted;
- a chain rooted at a user shell is a genuine second occupant, so hold.

Owner queries alone are insufficient. The runner service account can return empty owner and command-line fields; use the parent chain. Report attributed load, not bare process presence: “live local cargo” and “box free of a second occupant” can both be true.

### Post-restart daemon churn is a known blind spot

The fleet daemon's own resume storm—reconnects, pulses, and disk I/O for minutes after restart—is real load that neither predicate axis sees. This is an existing 2026-07-27 g6 finding with an existing mitigation, not a new discovery: a timed-window run adjacent to a daemon restart records the fleet-`spt` CPU-seconds delta sampled before and after, so contention is measured rather than inferred. Recording a rediscovery as a discovery loses the mitigation someone already paid for.

The preserved instruments are `.github/ci/g6-postbounce.ps1`, for the immediate post-restart sample, and `.github/ci/g6-curve.ps1`, for the private-daemon age/CPU dose-response curve. Both require caller-supplied scratch and fixture-worktree paths; neither embeds an expired agent-session scratchpad. The curve's throwaway `SPT_HOME`, home-hashed IPC socket, `daemon run`, and PID-scoped bounce are deliberate safety boundaries: `daemon start` or `daemon stop` may reach the fleet service and are prohibited in this rig.

This is a second reason never to restart a wedged daemon merely to tidy the box before a run: that trades a known abnormality for an unmeasured one.

## Rig disposal

Disposal is a rig step and runs **last**: sweep survivors, remove the owned target, then report free bytes. Never perform a multi-gigabyte deletion under a live CI job. Worktree and target bloat are a CI failure mode, not untidiness; `.github/workflows/golden.yml` enforces a 32 GiB free-space floor.

A survivor sweep is scoped by executable path under the rig's own throwaway target, never machine-wide by process name. Concretely, require `Path.StartsWith($TARGET)` before reaping. Installed fleet binaries under `AppData\Local\spt-core\bin` are then unreachable by construction, so another agent's live repro survives the battery and disposal without relying on memory.

Before classifying an unregistered file as junk, read it and identify its producer and purpose. Stray files are where unowned diagnostic instruments accumulate; path shape alone is not evidence of disposability. Preserve or promote paid-for instruments before removing the containing worktree.
