Documentation
¶
Index ¶
- Constants
- Variables
- func AdmissionName(stop []byte) string
- func Admit(stop []byte, cardLine1 string) (ok bool, reason string)
- func AppendLedger(queue string, rows ...LedgerRow) error
- func AppendRule(queue string, r RuleRow) error
- func Beat(in BeatInput) int
- func Cut(in CutInput) int
- func CutKind(in CutKindInput) int
- func CutValidated(in CutValidatedInput) int
- func Fill(in FillInput) int
- func FleetAdd(in FleetAddInput) int
- func FleetJoin(in FleetJoinInput) int
- func FleetMirror(in FleetMirrorInput) int
- func FleetReboot(in FleetRebootInput) int
- func FleetRegistry(in FleetRegistryInput) int
- func FleetSecrets(in FleetSecretsInput) int
- func FleetSleep(in FleetSleepInput) int
- func FleetStandard(in FleetStandardInput) int
- func FleetSuspend(in FleetSuspendInput) int
- func FleetWake(in FleetWakeInput) int
- func Gate(in GateInput) int
- func Harvest(in HarvestInput) int
- func Hygiene(in HygieneInput) int
- func KnownTriageKind(kind string) bool
- func Launch(in LaunchInput) int
- func MagicPacket(mac string) ([]byte, error)
- func Main(name string, args []string, stamp string, out, errs io.Writer) int
- func Manager(in ManagerInput) int
- func NextCardNumber(queue string) (int, error)
- func ParseTimeout(s string) (time.Duration, error)
- func Pool(in PoolInput) int
- func Probe(in ProbeInput) int
- func Progress(in ProgressInput) int
- func ReadFleetBenches(path string) (map[string]FleetBench, error)
- func ReadRunnerServices(queue string) (map[string]RunnerService, error)
- func Reap(in ReapInput) int
- func Run(in RunInput) int
- func Status(in StatusInput) int
- func StatusHTML(in StatusHTMLInput) int
- func StatusLine(in StatusInput) int
- func Sweep(in SweepInput) int
- func Triage(in TriageInput) int
- func Watch(in WatchInput) int
- func Wire(run *RunInput, w *Wiring)
- type BeatInput
- type BenchReading
- type BenchShell
- type CIJob
- type CIRun
- type Candidate
- type Capacity
- type CardLauncher
- type CardRow
- type Config
- type CutInput
- type CutKindInput
- type CutValidatedInput
- type Decider
- type Enqueuer
- type FillInput
- type FleetAddInput
- type FleetBench
- type FleetJoinInput
- type FleetMirrorInput
- type FleetProbeJob
- type FleetProbeRecord
- type FleetReader
- type FleetRebootInput
- type FleetRegistryInput
- type FleetRunner
- type FleetSecretsInput
- type FleetSleepInput
- type FleetStandardInput
- type FleetSuspendInput
- type FleetWakeInput
- type Forge
- type GHEnqueuer
- type GHRunners
- type GHSource
- type GHWork
- type GateInput
- type Gater
- type HarvestInput
- type Harvester
- type HygieneDisk
- type HygieneInput
- type HygieneProcs
- type LaunchInput
- type Launcher
- type LedgerRow
- type LoopCount
- type ManagerInput
- type Notifier
- type OSDisk
- type OSProcs
- type OpenIssue
- type OpenPR
- type PRCheck
- type PRSource
- type PRView
- type Packet
- type PoolInput
- type PoolRow
- type ProbeInput
- type Proc
- type ProcessTable
- type ProgressInput
- type PublishFile
- type Publisher
- type ReapInput
- type Reaper
- type Refiller
- type Rerunner
- type RuleRow
- type RunInput
- type RunSource
- type Runner
- type RunnerRestarter
- type RunnerService
- type RunnerTable
- type SSHRunner
- type SelfReader
- type SelfReading
- type ServiceRestarter
- type StandardCheck
- type State
- type StatusHTMLInput
- type StatusInput
- type SweepInput
- type Sweeper
- type TriageInput
- type Undecided
- type Verdict
- type WatchInput
- type Wiring
- func (w *Wiring) Gate(tick int) (bool, string, error)
- func (w *Wiring) Harvest(tick int) (int, []Undecided, error)
- func (w *Wiring) Launch(tick int) (int, int, error)
- func (w *Wiring) Reap(tick int) (int, int, []Undecided, error)
- func (w *Wiring) Refill(tick int) (int, error)
- func (w *Wiring) Sweep(tick int) (int, error)
- type WiringInput
- type WorkSource
Constants ¶
const ( ConfigFile = "pulse.toml" SeenFile = "pulse.config.seen" )
ConfigFile is the loop's configuration, in the queue directory. SeenFile is the snapshot of the last load, which is how a CONFIG line can say what changed and a DEFAULT line can be printed once rather than every tick.
const ( DefaultSlotsStudio = 8 DefaultSlotsSpace = 8 DefaultSlotsLocal = 2 DefaultHeadroomStudio = 2 DefaultHeadroomSpace = 2 DefaultHeadroomLocal = 1 DefaultTickSeconds = 60 DefaultRefillCadence = 5 DefaultRunnersPerMachine = 8 // DefaultLaunchFiles is the file budget every launched card carries. `nova-swarm batch` // requires --files and refuses to guess one (issue #869): a launch that names none is // refused before a single card starts, so the loop carries the documented number. DefaultLaunchFiles = 40 )
The documented defaults. A missing key takes the value here and says so once.
const ( FleetRootsFile = "PULSE_ROOTS" FleetLabelsFile = "runner-labels.tsv" )
FleetRootsFile and FleetLabelsFile are written by `fleet add` alone: the roots the loop holds and the runner labels the admitted bench brings. Nothing else writes them.
const ( MatchEquals = "equals" // the value is exactly Want MatchContains = "contains" // the value holds Want MatchNonempty = "nonempty" // the value is not empty MatchNotEquals = "not" // the value is anything but Want MatchAtLeast = "at-least" // the value is a number >= Want )
The matchers a check may ask for. They are strings rather than functions so the whole table is data a test (and a reader) can hold against the standard.
const ( HygieneDiagDaysDefault = 2 HygieneDiagMaxBytesDefault = int64(2 * 1024 * 1024 * 1024) )
The runner `_diag` prune's defaults.
Measured on hulk, 2026-09-18: 24 runner directories, 3.5 GB of `_diag`, 18,296 files, and the OLDEST file on the bench two days old. A runner rolls its own diagnostics at a rate nobody chose, so the seven-day window the bench ran with took nothing, ever, and neither would three days.
The two rules divide the job. On a busy bench the window is what bites day by day (a dry run on hulk at --diag-days 1 selects 10,391 files, 1.95 GB); the cap is the backstop for a burst, or for a runner that starts writing faster than anyone watches. 2 GiB per runner over hulk's 24 runners is a 48 GiB ceiling on a 1.8 TB disk, and it takes nothing there today, because no runner directory is over 220 MB. That is what a ceiling is for.
const AdmitShown = 5
AdmitShown is how many refusals a launch prints before it prints the count instead.
const DefaultBase = "dev"
DefaultBase is the base a card that names none is measured and opened against.
const DefaultBranchPrefix = "rowan/"
DefaultBranchPrefix is the branch prefix a bench harvest takes when the caller names none: this line's own branches and nobody else's.
const DefaultCardDeadline = 25 * time.Minute
DefaultCardDeadline is how long a card may run before the reaper collects it: the batch deadline the hand loop passed nova-swarm, 25 minutes.
const DefaultLaunchTokens = "unmetered"
DefaultLaunchTokens is the token budget the launch names. The native runner the bench's shim has always used has no live token accounting -- the deadline is the stop -- and `unmetered` is the word nova-swarm requires a caller to say out loud rather than a number nothing observes.
const DefaultResume = "read this section, run nova-pulse status, act on REDS/STOP/HUMAN first"
DefaultResume is the rule a beat writes when --resume names none: the first thing the next window does with the section it just read.
const DefaultRunnerIdle = 5 * time.Minute
DefaultRunnerIdle is how long a runner may be busy with nothing running before it is stuck. Five minutes: longer than the longest gap between a job finishing and the next starting, far shorter than the forty minutes the jam cost.
const DefaultTick = 10 * time.Second
DefaultTick is the manager's tick (SPEC-PULSE, "Rate and convergence" rule 1).
const FillCap = 30
FillCap is the most cards one bench may take in a tick: fill-loop.sh holds this reserve back so a filling bench never eats the machine its own CI needs.
const FillInterval = 300 * time.Second
FillInterval is how often the loop ticks when --once is absent (fill-loop.sh's sleep 300).
const FirstCard = 1
FirstCard is the number a fresh queue's first card takes. It is 1 and not 0 so a card number is never the empty value of anything.
const FleetProbeRecordFile = "fleet-probe.tsv"
FleetProbeRecordFile is where the fleet-probe read-back is kept under the queue unless the verb is given an explicit --probe path.
const NewRule = "NEW"
NewRule is the third field of a verdict that no row in the table decided.
const PacketMax = 5000
PacketMax is the ceiling on a triage card, in bytes. Five thousand is about 1.2k tokens on the text route: enough for the evidence and the rule table, few enough that the decision is cheaper than the coordinator reading the same lines once.
const RoutesLogFile = "ROUTES.log"
RoutesLogFile is the log rule 8 demands: one ROUTE line per card, beside the card's label and the time, under the queue directory.
const RunnerBusyFile = "runners.busy.tsv"
RunnerBusyFile is where the moment each runner was first seen busy-with-nothing-running is kept, so the rule survives a tick and a restart of the loop.
const RunnerServicesFile = "runner-services.tsv"
RunnerServicesFile maps a runner name to the service that restarts it: name<TAB>host<TAB>kind<TAB>target, kind being systemd (target is the unit) or svc (target is the runner directory holding svc.sh). A host of "-" is this machine.
const SlotDash = "-"
SlotDash is the cards.tsv slot every cut card carries until launch allocates one.
const StateFile = "pulse.state"
StateFile is the loop's counters, in the queue directory beside ConfigFile.
const StatusLineMax = 400
StatusLineMax is the ceiling on the line, in bytes. Four hundred is about a hundred tokens: the price of knowing the whole day, paid once per window.
const StopFile = "STOP"
StopFile is the halt, in the queue directory. Its line 1 is the gate's own record -- MAIN-RED <sha> run=<id> job=<name> test=<name> -- and its line 2 is the admission name admission.go reads.
const StopMark = "MAIN-RED"
StopMark is what makes a STOP the gate's own. A STOP whose first line does not begin with it was written by a person, and no green lifts it.
const TriageRoute = "deepseek-v4-flash"
TriageRoute is the model a triage card is cut for: the cheapest route that can hold a text decision (SPEC-PULSE rule 7's cost table gives the same answer; this is the name the card carries so a hand reading the card knows what it was written for).
Variables ¶
var Benches = []string{"studio", "space", "local"}
Benches are the benches this configuration knows, in the order a line prints them.
var CutKinds = []string{"read", "fix", "replay", "spec", "rebase"}
CutKinds are the kinds this cutter knows, in the order help prints them.
var DefaultIntegrationBranches = []string{"main", "dev"}
DefaultIntegrationBranches is the one list of integration branches (class B, bug 8: dev inherited main's cancellation bug because the branch name was written twice).
var SendWakeOnLAN = func(packet []byte, addr string) error { conn, err := net.Dial("udp4", addr) if err != nil { return err } defer conn.Close() _, err = conn.Write(packet) return err }
SendWakeOnLAN is the real transport: one UDP datagram to the broadcast address. It is a variable so a test replaces it and never opens a socket.
var TriageKinds = []string{"signature", "scope", "docs-only", "nosha", "orphan", "fence", "hold-line"}
TriageKinds are the seven cases that took a coordinator turn each on 2026-09-16 and which a rule row plus a packet decides instead. A kind outside this list is refused: a case nobody named is a case with no rule rows to offer, and a packet with no candidate rows is a packet asking a model to invent policy.
var TriageVerdicts = []string{"ADMIT", "REFUSE", "HOLD", "RETRY", "ESCALATE"}
TriageVerdicts are the words the third field may be. They are the dispositions the loop already has: admit the card, refuse it, hold it for a person, retry it once, or escalate.
Functions ¶
func AdmissionName ¶
AdmissionName is STOP's line 2: the issue or the test the red belongs to, and the one name a card must carry to launch while it stands.
func Admit ¶
Admit says whether one card may launch under the STOP in hand, and why not when it may not. An empty STOP is no state to be in and admits everything. A STOP whose line 2 is an admission name admits the cards whose line 1 carries that name. A STOP with no line 2 -- a person's hold, or a red the gate could not name -- admits nothing.
func AppendLedger ¶
AppendLedger appends rows under the queue's lock: one writer, append only, one take of the lock however many rows a sweep has to mark.
func AppendRule ¶
AppendRule writes one row to <queue>/RULES.tsv marked pending. It is what a NEW verdict costs: the decision is recorded as policy the same hour, and a person reading the table sees which rows have not been read yet.
func Beat ¶
Beat appends one section to the cairn file, commits it when the cairn lives in a git repo (git add and one commit, never a push), and prints the two lines the next window reads: BEAT OK with the cairn's line count, then the restart command.
func Cut ¶
Cut writes one card per pool.tsv candidate from its typed template and returns the exit code: 0 when every candidate was cut, 1 when any candidate was skipped, 2 when the pool, the templates or the routing table could not be read. Routing is the cost table (benches.tsv, or routes.tsv beside it) when it is there; a templates directory with only a models.tsv falls back to the two-line flash/pro table cut wrote before the cost table landed (issue #635), so a spend rule that holds every card to one model still works.
func CutKind ¶
func CutKind(in CutKindInput) int
CutKind writes one card of one kind under the next number and prints one line. It returns 0 when the card was written and 2 when the invocation was refused.
func CutValidated ¶
func CutValidated(in CutValidatedInput) int
CutValidated cuts cards from one input (an issue, a table of rows, or a PR's head branch) through a template with named slots. It writes the cards under --out and their rows to cards.tsv, and nothing else. Before a byte is written it runs five checks in order -- branch, base, STEP 1, row, result -- and stops at the first that fails, exit 2 with one refusal line. Success prints one CUT OK line naming the source.
func Fill ¶
Fill holds the loop: one fillTick per bench set, one FILL line per tick, until killed -- or exactly one tick when --once is set. It returns 0, 1 when every bench failed the tick (a fleet nobody can reach is not a quiet success), or 2 on a refusal that never started.
func FleetAdd ¶
func FleetAdd(in FleetAddInput) int
FleetAdd admits one bench or refuses it. A bench whose probe carries a job that is not green, or a probe with no jobs at all, is refused with the offending runner name and the run id on the line; a bench whose every runner name is green is admitted and its PULSE_ROOTS and runner labels written.
func FleetJoin ¶
func FleetJoin(in FleetJoinInput) int
FleetJoin joins one bench to the tailnet and prints one line: `FLEET <bench> JOINED ip=<addr>`. Exit 0 when the bench joined, 2 when the invocation was refused (no key in the named variable, a tailscale path that is not absolute, a bench the file does not carry, `studio`), 3 when the bench could not be reached or tailscale refused.
func FleetMirror ¶
func FleetMirror(in FleetMirrorInput) int
FleetMirror creates or refreshes one bare mirror on one bench and prints one line: `FLEET <bench> MIRROR <path> created|refreshed head=<sha> size=<n>K`. Exit 0 when the mirror is there, 2 when the invocation was refused, 3 when the bench could not be reached or git failed on it.
func FleetReboot ¶
func FleetReboot(in FleetRebootInput) int
FleetReboot boots each named bench, waits for its runners, and prints one FLEET line per bench. A bench not in the file, and `studio` by name, are refused (exit 2); a bench that never answers is unreachable (exit 3). Refusals are read before any ssh starts.
func FleetRegistry ¶
func FleetRegistry(in FleetRegistryInput) int
FleetRegistry prints one MACHINE line per machine, in file order:
MACHINE hulk ssh=hulk os=linux/x64 roles=bench,runner seat=swarm-hulk cores=64 notes="..."
Exit 0 when the registry read, 2 when it did not or when --role names a role no machine carries (a role nobody carries is far more likely a typo than a fleet fact).
func FleetSecrets ¶
func FleetSecrets(in FleetSecretsInput) int
FleetSecrets runs the seat check on every bench in parallel and prints one FLEET line per bench. It exits 0 when every seat checked OK, 2 when any refused or found no seat, and 3 when any bench was unreachable.
func FleetSleep ¶
func FleetSleep(in FleetSleepInput) int
FleetSleep suspends one idle bench over ssh and prints one line: `FLEET <bench> SUSPENDED`, `FLEET <bench> BUSY <what>` (exit 2, and nothing is suspended), `FLEET <bench> SKIP <what>` under --if-idle, or `FLEET <bench> UNREACHABLE <reason>` (exit 3).
func FleetStandard ¶
func FleetStandard(in FleetStandardInput) int
FleetStandard runs the standard's checks on one bench and prints one `STANDARD <bench> <check> OK|DRIFT ...` line per check and one verdict line: exit 0 when every check is met, 2 when any drifted or the bench was refused, 3 when the bench could not be reached.
func FleetSuspend ¶
func FleetSuspend(in FleetSuspendInput) int
FleetSuspend sleeps each named bench: a bench holding a running card (a lease or a job directory with a live pid under ~/rowan-swarm-root or ~/stella-swarm-root) or a busy runner (a Runner.Worker process) is BUSY and refused (exit 2) unless --force, and is skipped under --if-idle. A bench that cannot be reached is exit 3; every bench prints one FLEET line.
func FleetWake ¶
func FleetWake(in FleetWakeInput) int
FleetWake sends each named bench one Wake-on-LAN magic packet to its mac, then polls ssh until the bench answers, printing `FLEET <name> AWAKE wall=<s>` or `FLEET <name> WAKE TIMEOUT` (exit 3). A bench with no mac is refused (exit 2); every bench prints one FLEET line.
func Gate ¶
Gate reads the branch's latest ci run and writes, holds or lifts the STOP. It prints exactly ONE line: GATE RED (exit 1, the check says NO), GATE HELD or GATE GREEN (exit 0), or GATE REFUSED (exit 2) when the source could not answer -- a dead source is never green.
func Harvest ¶
func Harvest(in HarvestInput) int
Harvest folds one pulse's cards: it pushes and opens a draft PR for every card whose RESULT.md line 1 equals its contract line, sends every abstain (or missing RESULT) to retry.tsv with the harness log's last refusal line, cuts a read card per PR, and then pulses again -- queue first. It never merges.
func Hygiene ¶
func Hygiene(in HygieneInput) int
Hygiene runs one hygiene subcommand and returns its exit code: 0 when it ran, 2 on every refusal. One HYGIENE line is the only stdout a run prints; each deletion is one line in <home>/hygiene.log.
func KnownTriageKind ¶
KnownTriageKind reports whether kind is one of the seven.
func Launch ¶
func Launch(in LaunchInput) int
Launch allocates free slots, admits the cards that fit as one nova-swarm batch in its CARD form (--id --cards --deadline --runner --root --files, the only form that runs a card; the POOL form wants --tokens as well, which no launch flag supplies, issue #630), and queues the rest when --queue is set. The card form still carries the file budget `nova-swarm batch` refuses an admission without (issue #869). It returns the process exit code: 0 when the pulse was admitted, 2 when it could not be.
func MagicPacket ¶
MagicPacket is the Wake-on-LAN packet for one mac: six 0xFF bytes then the six-byte hardware address repeated sixteen times, 102 bytes and nothing else.
func Manager ¶
func Manager(in ManagerInput) int
Manager runs one bounded shift: 0 when it ended by itself, 2 on a refusal that never started.
func NextCardNumber ¶
NextCardNumber takes the next card number under the queue's lock and writes the state file back one higher. Two cutters never see the same number, whatever else is running.
func ParseTimeout ¶
ParseTimeout reads a bound on a child, for the flag layer. A bare number is seconds, because that is what every caller of this tool has typed for a year; anything else is a Go duration, because the progress line prints one and a flag that will not accept what the tool prints is a trap. There is no default here and no zero: a bound of nothing is not a bound.
func Pool ¶
Pool enumerates bounded open work from the declared sources into pool.tsv. It runs no model: every candidate is read through gh, git or the files the sources name. One unreadable source is one refusal and no pool.tsv is written.
func Probe ¶
func Probe(in ProbeInput) int
Probe runs the learned checklist against one card and returns 0 when it passes and 1 when a learned class refuses it; 2 when the history could not be read. A refusal names the class and its remedy so the per-class count stays comparable to the history.
func Progress ¶
func Progress(in ProgressInput) int
Progress prints the PROGRESS line and the ESTIMATE line and returns 0, or 2 when it could not run.
func ReadFleetBenches ¶
func ReadFleetBenches(path string) (map[string]FleetBench, error)
ReadFleetBenches reads the benches file: one bench per line, `name<TAB>ssh target<TAB>home<TAB>mac`. Blank lines and `#` comments are skipped, and a line that is not four fields is a refusal naming the file and the line -- a partially-read fleet is worse than none.
func ReadRunnerServices ¶
func ReadRunnerServices(queue string) (map[string]RunnerService, error)
ReadRunnerServices reads the service map. A map nobody wrote is empty, and every restart against it is then a refusal naming the file.
func Reap ¶
Reap runs one collection over the roots and the queue and prints one REAP line. It returns 0 when it ran, 2 when it could not.
func Run ¶
Run holds the loop for --hours and prints one WIDTH line per tick. It returns 0 when the shift ended by itself and 2 on a refusal that never started.
func Status ¶
func Status(in StatusInput) int
Status prints the eight line kinds and returns 0, or 2 when it could not run.
func StatusHTML ¶
func StatusHTML(in StatusHTMLInput) int
StatusHTML writes the fleet page to --html, appends one seven-column metrics.tsv row beside it, ships both where --publish says, and prints the one STATUS HTML line.
func StatusLine ¶
func StatusLine(in StatusInput) int
StatusLine prints the one line and returns 0, or 2 when it could not run.
func Sweep ¶
func Sweep(in SweepInput) int
Sweep walks every open ledger row once and prints one SWEEP line. It returns 0 when it ran, 2 when it could not.
func Triage ¶
func Triage(in TriageInput) int
Triage cuts one packet to a card file and prints one line.
func Watch ¶
func Watch(in WatchInput) int
Watch waits on the queue, the bus and the job roots and prints one line per change. It starts from the state of its first poll, so a backlog is never printed; a named --until event ends it at the poll that sees it, before the cap, with WATCH OK and exit 0; the cap prints WATCH CAP and exits 3. Every unusable invocation is WATCH REFUSED, exit 2, one remedy, and makes no poll.
Types ¶
type BeatInput ¶
type BeatInput struct {
Queue string // the queue directory: pending, launched, done, failed and the state files
Cairn string // the cairn file this beat is appended to
Title string // the one-line title of this beat
Resume string // the resume rule; empty takes DefaultResume
Now func() time.Time
Stdout io.Writer
Stderr io.Writer
}
BeatInput is everything the beat verb needs, held apart from flag parsing so a test can drive it against a temp queue and a temp git repo.
type BenchReading ¶
type BenchReading struct {
Name string
Live int
Cores int
Load int
FreeGB int
MemGB int
Allowed int
Hygiene int // reaps and deletions on this bench since the window opened
// Down is the bench that did not answer. It is NOT a bench with nothing to do, and the
// difference is what the 2026-09-17 pit stop cost: a row of zeros reads as an idle
// bench, so a fleet nobody could see looked like a fleet with nothing to do. The page
// says DOWN, the metrics row writes a dash for its disk rather than a number the bench
// never gave, and the STATUS HTML line counts it.
Down bool
// Note says WHY, when the bench answered well enough to tell us. A bench whose home is
// not there answers every other question perfectly and then reports no disk, which read
// as "out of disk, allowed 0" on every row of a page that was otherwise right.
Note string
}
BenchReading is one bench's numbers for the page: the live cards counted from running card processes, the capacity the allowed arithmetic folds, and the hygiene actions the same round trip counted.
type BenchShell ¶
BenchShell runs one command on a bench and returns its stdout. The shipped one is ssh; a test's is a fake, so no test of this package opens a connection.
type CIJob ¶
type CIJob struct {
Name string `json:"name"`
Log string `json:"log"`
StartedAt string `json:"startedAt"`
CompletedAt string `json:"completedAt"`
}
CIJob is the failing job of a run: its name, and its log, which is where the failing test and the issue the red belongs to are named. StartedAt/CompletedAt are the job's wall, which the ci-wall rule reads to name the long pole.
type CIRun ¶
type CIRun struct {
ID int64 `json:"databaseId"`
Status string `json:"status"`
Conclusion string `json:"conclusion"`
HeadSHA string `json:"headSha"`
Workflow string `json:"workflowName"`
Event string `json:"event"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
CIRun is one run as `gh run list --json status,conclusion,headSha,databaseId,workflowName,event,createdAt,updatedAt` gives it. The gate reads only the ci workflow's push runs, so it can tell the branch's own run from a certification, workflow_dispatch, schedule or merge_group run at the same sha. CreatedAt/UpdatedAt are the run's wall, which the ci-wall rule (issue #888) reads.
type Candidate ¶
Candidate is one row of bounded work: a source kind, an item id, a card kind, a title and the template to render it from. pool.tsv, queue.tsv and next.tsv all hold candidates in the same five-field shape.
type Capacity ¶
Capacity answers how many cards the named bench can take this tick -- card 9316's formula, the min of core, disk and memory headroom. The real one runs it over ssh; tests inject a fixed number.
type CardLauncher ¶
CardLauncher launches one card that Fill has already moved into --launched. It is the per-card seam of the launch verb (internal/pulse/launch.go): one call, in order, one card. The real one shells flash-native-bench.sh on the bench; tests inject a recorder.
type CardRow ¶
CardRow is one row of cards.tsv: the label the card was admitted under, the slot it holds ("-" until launch allocates one), the model, and the card file's path.
type Config ¶
type Config struct {
Slots map[string]int
// Headroom is a ratio of the bench's cores, so it is a decimal: the spec writes it
// `cores x 1.5` and 2.5 is a value a person writes (issue #869).
Headroom map[string]float64
TickSeconds int
RefillCadence int
IntegrationBranches []string
RunnersPerMachine int
Files int // the --files budget every launched card carries
Tokens string // the --tokens budget: a number, or the word `unmetered`
}
Config is the loop's whole configuration: slots and headroom per bench, the tick, the refill cadence, the integration branches and the runners each machine runs.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig is the documented configuration, for a caller that has not read a file yet.
func LoadConfig ¶
LoadConfig reads <dir>/pulse.toml, takes the documented default for every key it does not carry, and prints ONE CONFIG line naming the keys that changed since the last load, plus one DEFAULT line per key taking a default it has not announced before, bounded at max. A missing file is every default and no refusal: a first tick on a fresh queue still runs.
type CutInput ¶
type CutInput struct {
Pool string // path to pool.tsv: source, id, kind, title, template per line
Templates string // the directory holding read.md fix.md text.md replay.md drift.md tone.md and benches.tsv (or routes.tsv, or a models.tsv fallback)
Out string // the directory the card files go into
Root string // the state root; skipped.tsv and retry.tsv are read and written here
Local string // an optional ollama tag that overrides the flash model for read and text cards
Max int // cap on the cards cut, and on the skipped lines printed; 0 means no cap
Probe bool // run the learned admission checklist before writing each card (#585)
History string // the abstain history the checklist is cut from, when Probe is set
Budget int // the card's byte budget, when Probe is set; 0 means unbounded
Stdout io.Writer
Stderr io.Writer
}
CutInput is everything the cut verb needs, held apart from flag parsing so a test can drive it with a fake templates directory and a fake pool.tsv.
type CutKindInput ¶
type CutKindInput struct {
Kind string
Repo string // owner/name; line 1 names the repo for every kind
PR int // read, rebase
Head string // read
Issue int // fix
Title string // fix, spec, rebase, and the parenthesised title of a read
Branch string // rebase: the branch rebased onto the base
Base string // rebase: the branch it is rebased onto
BodyFile string // fix, spec: the numbered steps this card carries
Prior string // fix: what a prior attempt did, so the worker never repeats it
Names string // replay: the replay names, comma separated
SpecLines string // replay: the spec lines the replays are named at
Out string // the directory the card is written into
Queue string // the queue directory holding the state file and its lock
Stdout io.Writer
Stderr io.Writer
}
CutKindInput is everything `cut --kind` takes. Flag parsing lives in cmd/nova-pulse.
type CutValidatedInput ¶
type CutValidatedInput struct {
Source string // issue, rows or branch-from
Issue string // <owner>/<repo>#<n> when Source is issue
Rows string // the rows.tsv path when Source is rows
BranchFrom string // <owner>/<repo>#<n> when Source is branch-from
Templates string // the directory holding the source's .md template
Out string // the directory the cards go into
Repo string // the clone every git call runs in: `git -C <repo> ...`, never the working directory
Base string // the base branch a source that names none is cut onto; "" is dev
Cards string // where the cards.tsv goes; "" is <out>/cards.tsv, which puts a table in a queue directory
Max int // cap on the cards cut; 0 lifts it
Stdout io.Writer
Stderr io.Writer
}
CutValidatedInput is everything the validated-template cut needs, held apart from flag parsing so a test can drive it with a fixture gh and a fixture git.
type Decider ¶
type Decider interface {
Decide(ctx context.Context, state string, qs map[string]decide.Question) (map[string]decide.Answer, decide.Usage, error)
}
Decider is the typed decision the gate asks about a failing job: one call, a verdict choice and a noul. The shipped one is *decide.Client; a test passes a fake, so no test reaches the network.
type FillInput ¶
type FillInput struct {
Ready string // the queue/ready directory the card-<n>.md are popped from
Launched string // the queue/launched directory they are moved into; its cards are live
Lanes string // the lanes file: <name>\t<path prefixes> per line; empty names no lane
Machines string // the machines registry; a bench whose roles lack `bench` is refused
Session string // the session id stamped into every launched card's marker
Benches []string // the benches to fill, in order
Only []string // glob patterns over a card's filename; empty takes every ready card
Once bool // true runs exactly one tick and returns
Interval time.Duration // how long between ticks; 0 takes FillInterval
Stdout io.Writer
Stderr io.Writer
Now func() time.Time
Sleep func(time.Duration)
Launcher CardLauncher
Capacity Capacity
}
FillInput is the fill verb apart from flag parsing, so a test drives one tick with fake directories and stub seams.
type FleetAddInput ¶
type FleetAddInput struct {
Bench string
Queue string
Roots string
Probe string
Stdout io.Writer
Stderr io.Writer
}
FleetAddInput is `fleet add <bench>`.
type FleetBench ¶
FleetBench is one line of the benches file: name, ssh target, home, mac (mac is "-" when the bench never sleeps). The file is shared with `fleet survey`.
type FleetJoinInput ¶
type FleetJoinInput struct {
Benches string // the fleet file
Machines string // the machines registry; a machine whose roles lack `bench` is refused
Name string // the one bench, which is also its tailnet hostname
SSH string // the ssh program; empty is "ssh"
Tailscale string // the absolute path of tailscale ON THE BENCH
AuthKeyEnv string // the environment variable holding the auth key
Getenv func(string) string
Timeout time.Duration
Runner FleetRunner
Stdout io.Writer
Stderr io.Writer
}
FleetJoinInput is everything `fleet join` needs apart from flag parsing.
type FleetMirrorInput ¶
type FleetMirrorInput struct {
Benches string // the fleet file
Machines string // the machines registry; a machine whose roles lack `bench` is refused
Name string // the one bench
SSH string // the ssh program; empty is "ssh"
Repo string // the https remote to mirror
Path string // the absolute path of the bare mirror on the bench
Timeout time.Duration
Runner FleetRunner
Stdout io.Writer
Stderr io.Writer
}
FleetMirrorInput is everything `fleet mirror` needs apart from flag parsing.
type FleetProbeJob ¶
FleetProbeJob is one fleet-probe job read back by runner_name.
type FleetProbeRecord ¶
type FleetProbeRecord struct {
Run string
Bench string
Jobs []FleetProbeJob
}
FleetProbeRecord is the read-back of one fleet-probe dispatch: the run id and the job of every runner slot.
func ReadFleetProbeRecord ¶
func ReadFleetProbeRecord(path string) (FleetProbeRecord, error)
ReadFleetProbeRecord reads the read-back of the fleet-probe job (ci.yml, #872): `run` and `bench` lines, then one `runner<TAB><name><TAB><conclusion>` line per runner slot. A line that is not one of those three fields is a refusal naming the file and the line, because a partially-read probe would read as a green one.
type FleetReader ¶
type FleetReader func(benches []FleetBench, now time.Time) []BenchReading
FleetReader reads every bench's numbers in file order. The production reader reads them over ssh with ps/pgrep liveness; a test injects a fake returning per-bench process counts and disk/mem, so no test starts ssh.
type FleetRebootInput ¶
type FleetRebootInput struct {
Benches string
Machines string // the machines registry; empty leaves the older refusals as the guard
Names []string
SSH string // the ssh program; empty is "ssh"
Wait time.Duration
Timeout time.Duration
Max int // at most this many FLEET lines; 0 is all
Now func() time.Time
Sleep func(time.Duration)
Stdout io.Writer
Stderr io.Writer
}
FleetRebootInput is everything `fleet reboot` needs: the file, the names, the ssh path, the wait and child bound, and the clock and sleep a test replaces.
type FleetRegistryInput ¶
type FleetRegistryInput struct {
Machines string // the machines file
Role string // list only the machines carrying this role; empty is all of them
Max int // at most this many MACHINE lines; 0 is all
Stdout io.Writer
Stderr io.Writer
}
FleetRegistryInput is everything the verb needs apart from flag parsing.
type FleetRunner ¶
FleetRunner is the one remote step: run a script on a bench and return what it said. The real one is `ssh <target> bash -s` with the script on stdin.
type FleetSecretsInput ¶
type FleetSecretsInput struct {
Benches string
SSH string
Timeout time.Duration
Max int
Stdout io.Writer
Stderr io.Writer
}
FleetSecretsInput is everything the fleet secrets verb needs, apart from flag parsing, so a test can drive it against a benches file and a fake ssh on PATH.
type FleetSleepInput ¶
type FleetSleepInput struct {
Benches string // the fleet file
Machines string // the machines registry; a machine whose roles lack `bench` is refused
Name string // the one bench to put to sleep
SSH string // the ssh program; empty is "ssh"
Force bool // sleep even a busy bench
IfIdle bool // skip a busy bench instead of refusing it
Timeout time.Duration
Max int
Stdout io.Writer
Stderr io.Writer
}
FleetSleepInput is everything `fleet sleep` needs apart from flag parsing.
type FleetStandardInput ¶
type FleetStandardInput struct {
Benches string // the fleet file: name<TAB>ssh target<TAB>home<TAB>mac
Machines string // the machines registry; a machine whose roles lack `bench` is refused
Name string // the one bench to check
SSH string // the ssh program; empty is "ssh"
OS string // "linux" or "darwin"; empty asks the bench with uname -s
Go string // the Go toolchain the fleet is on; empty is go1.26.5
Want string // the nova bins' stamp; empty reports what the bench has
MinFreeGB int // the floor on free space under HOME
Timeout time.Duration
Max int // at most this many lines; 0 is all
Runner FleetRunner
Stdout io.Writer
Stderr io.Writer
}
FleetStandardInput is everything `fleet standard` needs apart from flag parsing.
type FleetSuspendInput ¶
type FleetSuspendInput struct {
Benches string
Machines string // the machines registry; empty leaves the older refusals as the guard
Names []string
SSH string // the ssh program; empty is "ssh"
Force bool // suspend even a busy bench
IfIdle bool // skip a busy bench instead of refusing it
Timeout time.Duration
Max int // at most this many FLEET lines; 0 is all
Stdout io.Writer
Stderr io.Writer
}
FleetSuspendInput is everything `fleet suspend` needs, apart from flag parsing, so a test can drive it against a benches file and a fake ssh on PATH.
type FleetWakeInput ¶
type FleetWakeInput struct {
Benches string
Machines string // the machines registry; empty leaves the older refusals as the guard
Names []string
SSH string // the ssh program; empty is "ssh"
Wait time.Duration
Timeout time.Duration
Max int // at most this many FLEET lines; 0 is all
Now func() time.Time
Sleep func(time.Duration)
Send func(packet []byte, addr string) error
Stdout io.Writer
Stderr io.Writer
}
FleetWakeInput is everything `fleet wake` needs, apart from flag parsing, so a test can drive it against a benches file, a fake ssh on PATH and a sender that captures the packet instead of opening a socket.
type Forge ¶
type Forge interface {
FindPR(repo, branch string) (int, error)
CreatePR(repo, base, branch, title, body string) (int, error)
}
Forge opens pull requests. The shipped one runs gh; a test's records what it was asked for. FindPR answers 0 when the branch has no open PR.
type GHEnqueuer ¶
GHEnqueuer is the real Enqueuer, and it is now a THIN ADAPTER ONTO THE ONE DOOR (internal/merge.Enqueuer).
It used to run `gh pr merge <n> -R <repo> --auto`, once per approval, on the reasoning that the sweep only reached it when every check was green and none pending. That reasoning was wrong in the one way that matters: --auto enqueues nothing. It leaves a STANDING INSTRUCTION on the forge, which fires later, with nobody in the room, against whatever the head has become. On 2026-09-18 twenty-seven open pull requests were carrying one, and four of them walked into the dev merge queue on their own. Glenn locked it the same day: nothing reaches the dev merge queue but a batch.
So this reads the pull request's head and offers it to the one door, which admits a batch's head -- or a head with that head's BATCH OK receipt -- and refuses everything else. A refusal is an error like any other here, and the sweep leaves the row open. There is no path from this type to a merge of any kind.
type GHRunners ¶
GHRunners is the real table: `gh api repos/<o/n>/actions/runners` for the runners and `gh run list --status in_progress` for what is actually running, each bounded.
type GHSource ¶
GHSource is the real PRSource: one bounded `gh pr view` per row, the one JSON call the sweep makes per open approval.
type GateInput ¶
type GateInput struct {
Repo string
Branch string
Queue string // the queue directory STOP lives in
Source RunSource
Stdout io.Writer
Stderr io.Writer
// Decide turns on the gate's typed decision: a failing job is classified behind Floor,
// and a flaky verdict reruns the failed jobs once for the head. A decision below the
// floor is a suggestion, never an authorization, and today's STOP stands.
Decide bool
Floor float64
Decider Decider
}
GateInput is the verb's input, held apart from flag parsing.
type Gater ¶
type Gater interface {
// Gate reports whether the branch is red this tick, the sha or reason that says so,
// and an error when the probe itself could not run (never a verdict).
Gate(tick int) (red bool, why string, err error)
}
Gater is the mechanical gate on the branch this bench merges into: it answers whether that branch is red, and the loop does the rest (STOP, the note, the skip). It is a seam: the gh probe and the admission gates live on the sibling branch rowan/pulse-config-gate-admission. A nil Gater is the STOP file alone.
type HarvestInput ¶
type HarvestInput struct {
ID string
Root string
Sources string
Templates string
MaxBodyBytes int
Max int
Stdout io.Writer
Stderr io.Writer
Now func() time.Time
// Decide turns on the harvest's one typed decision per finished job: before
// any push each done card's RESULT.md is classified behind Floor as fixed,
// already-fixed, no-change, failed or off-branch. Decider is the typed
// decision seam (the shipped *decide.Client; a test's fake). A nil Decider
// leaves the harvest exactly as it was.
Decide bool
Floor float64
Decider Decider
// Bench turns the harvest around: the jobs are read on that bench over the
// BenchShell seam rather than under a local root, and Root names the swarm
// root ON the bench (comma separated for more than one). A bench harvest
// wants no --sources, no --templates and no --id -- which a `cut --rows`
// never produces -- and never relaunches: it folds what is there.
Bench string
// Machines is the machines registry --bench is held against before the first ssh
// (Glenn's lock of 2026-09-18: runner hosts are CI-only). An empty path leaves the
// verb unguarded, which is what a test and a by-hand run want; cmd/nova-pulse names
// the registry on every real invocation.
Machines string
SSH string // the ssh program the shipped shell runs; "" is "ssh"
Clones []string // <owner>/<name>=<dir>, or a bare <dir> for any repo
Session string // only jobs whose RESULT.md names this session
BranchPrefix string // only branches under this prefix; "" is rowan/
Base string // the base of a card that names none; "" is dev
Since time.Duration
Shell BenchShell
Forge Forge
// Launched is the queue directory `fill` moves live cards into. When it is
// named, harvest drains it: a card whose job has finished leaves it for Done
// or Failed, its launched marker goes with it, and its lane is free.
Launched string
Done string
Failed string
}
HarvestInput is everything the harvest verb needs, held apart from command-line parsing so a test can drive it with fake directories and a fake nova-swarm/nova-pulse on PATH.
type Harvester ¶
Harvester folds every card whose job has written a RESULT.md. Seam: the shipped `nova-pulse harvest` verb, driven per pulse id.
type HygieneDisk ¶
HygieneDisk answers the two numbers the run verb's one line needs.
type HygieneInput ¶
type HygieneInput struct {
Verb string // run, reap, delete-job, delete-slot, drop-cache, log
Slot string
Job string
DryRun bool
N int // log: how many trailing lines
Home string // every root, the log and the cache hang under it
Roots []string // default <home>/rowan-swarm-root, <home>/rowan-working/tmp
LogPath string // default <home>/hygiene.log
CachePath string // default <home>/.cache/go-build
Hostname string // default the short local host name
// The runner `_diag` prune's two rules. Zero means the default.
DiagDays int // age window in days, default HygieneDiagDaysDefault
DiagMaxBytes int64 // per-runner-directory cap, default HygieneDiagMaxBytesDefault
Now func() time.Time
Procs HygieneProcs
Disk HygieneDisk
Stdout io.Writer
Stderr io.Writer
}
HygieneInput is the hygiene verb family's input, held apart from flag parsing.
type HygieneProcs ¶
HygieneProcs answers whether any process names a slot in its command line or its working directory. The real one reads /proc; tests drive a fake.
type LaunchInput ¶
type LaunchInput struct {
Cards string // path to cards.tsv: label<TAB>slot<TAB>model<TAB>card
Root string // the pulse root; the swarm pool lives under <root>/pool
Slots int // the ceiling on free slots launch considers
Deadline string // the whole pulse's deadline, whole seconds
Queue bool // true keeps the overflow in queue.tsv instead of refusing
Files int // the --files budget every card in the batch carries; 0 takes the default
Tokens string // the --tokens budget; empty takes the default
QueueDir string // the queue directory STOP lives in; empty falls back to Root (admission.go)
// Routes is the routes.tsv the typed decision reads to pick each card's worker. Empty
// means no routing: the cards group by their own model column, exactly as before.
Routes string
Floor float64 // answers below the floor keep the card's own model as the default worker
KeyEnv string // the environment variable the decision's key comes from
BaseURL string // the decision provider endpoint
Stdout io.Writer
Stderr io.Writer
Now func() time.Time
// Log is where the structured JSON event line goes, beside the stdout line and never
// instead of it. nil writes no JSON line, which is how the tests that predate the
// slice keep their exact stdout and stderr; cmd/nova-pulse passes stderr, which on a
// bench is the systemd journal.
Log io.Writer
// GUID is the source of this run's guid. nil reads the real /proc in production; a
// test injects a fixed one so it reads no /proc.
GUID func() string
}
LaunchInput is everything the launch verb needs, held apart from the command-line parsing so a test can drive it with fake directories and a fake nova-swarm on PATH.
type Launcher ¶
Launcher fills every free slot on every bench. Seam: the shipped `nova-pulse launch`.
type LedgerRow ¶
type LedgerRow struct {
PR int
Head string
Card string
Verdict string // APPROVE, HOLD, STALE
At string // when the verdict was recorded, RFC3339 UTC
EnqueuedAt string // when the merge was enqueued, or "-"
ClosedAt string // when the row was closed, or "-"
}
LedgerRow is one row of the approvals ledger.
func OpenRows ¶
OpenRows folds the log into one row per PR — the last one written — and keeps the rows that are not closed, in PR order.
func ReadLedger ¶
ReadLedger reads every row of <queue>/ledger.tsv in file order.
type LoopCount ¶
LoopCount is one long-running loop the caller named and how many of it are up. The patterns come from --loop and never from a name baked into this tool: a verb carrying `harvest-loop.sh` in its source would freeze the scripts it exists to retire.
type ManagerInput ¶
type ManagerInput struct {
Policy string // the finite policy this shift executes
Queue string // the queue directory: pending, launched, done, failed and the state files
Roots string // comma-separated swarm roots, the benches this shift harvests
Bus string // the nova-bus clone this shift is the single waiter on
As string // the name this shift waits and receipts as
Hours float64
Max int
Stdout io.Writer
Stderr io.Writer
Now func() time.Time
}
ManagerInput is the verb's input, held apart from flag parsing so a test can drive a whole shift against a fake queue and fake gh, git, nova-bus and nova-swarm on PATH.
type Notifier ¶
Notifier is the ONE call a tick may make to a person. The real one shells `nova-bus send`; with no --bus the loop appends the note to <queue>/ESCALATE, which is the same inbox the hand loop wrote to.
type OSDisk ¶
type OSDisk struct{ Home string }
OSDisk is the real disk: df for the free space, du for the build cache. It fails open -- a number it cannot read never turns into a deletion.
type OSProcs ¶
OSProcs is the real process table: one bounded listing, and a signal per overdue pid.
type PRSource ¶
PRSource answers what a pull request looks like right now. The real one runs gh; a test drives a fake, so no test of this package ever reaches the network.
type PRView ¶
type PRView struct {
Number int
State string // OPEN, MERGED, CLOSED
IsDraft bool
Head string
Labels []string
Title string
Checks []PRCheck
// AutoMerge says the forge is carrying a STANDING INSTRUCTION to merge this pull
// request when its checks go green. It is not an enqueue and it is not ours: the sweep
// reads it as a hold, and `nova-merge queue audit` takes it off.
AutoMerge bool
}
PRView is everything the sweep needs to know about one pull request.
type Packet ¶
type Packet struct {
Case string
Ref string
Result []string // the RESULT lines of the card the case is about
Refusal string // the harness's refusal line, one line
Rules []RuleRow
}
Packet is the decision packet: everything the triage route is given.
func BuildPacket ¶
BuildPacket assembles a packet for one case from the queue's rule table.
func (Packet) Card ¶
Card renders the packet as the card the route is handed: line 1 the RESULT contract line (`RESULT <label> sha=<sha12 of everything below>`, SPEC-PULSE "The card"), then the wall, the evidence, the candidate rows, and the one line the verdict must be.
It never exceeds PacketMax bytes. Over the ceiling it drops EVIDENCE first, from the end, and says how many lines it dropped: the rule rows and the verdict shape are the decision and are never the thing cut.
type PoolInput ¶
type PoolInput struct {
Sources string // path to the declared sources TSV (kind, locator, template)
Root string // root directory holding seen.tsv and the default pool.tsv
Out string // path pool.tsv is written to; empty means <root>/pool.tsv
Timeout time.Duration // bound on every gh child, default 120s
Max int // bound on admitted candidates; 0 means no bound
Stdout io.Writer
Stderr io.Writer
}
PoolInput holds everything the pool verb reads, apart from command-line parsing, so a test can drive it with fake gh invocations on PATH.
type PoolRow ¶
type PoolRow = Candidate
PoolRow is a candidate as pool.tsv holds it: pool.tsv, queue.tsv and next.tsv share the Candidate shape, and pool names it for the file it lives in.
type ProbeInput ¶
type ProbeInput struct {
Label string
Card string
History string
Budget int
Stdout io.Writer
Stderr io.Writer
}
ProbeInput is one card under the checklist. History is the abstain history: one class per line, an optional tab-separated count after it. Budget is the card's byte budget; 0 means unbounded. The probe makes no model call and touches no network.
type ProcessTable ¶
ProcessTable is the machine's processes, and the two things the reaper does with them.
type ProgressInput ¶
type ProgressInput struct {
Queue string // the queue directory: pending, launched, done and the POLICY
Roots string // comma-separated swarm roots, the benches whose usage.tsv is read
Day string // YYYY-MM-DD the day window starts at; empty means today (UTC)
Stdout io.Writer
Stderr io.Writer
Now func() time.Time
}
ProgressInput is the progress verb's input, held apart from flag parsing so a test can drive it against fake usage files and a fake gh on PATH.
type PublishFile ¶
PublishFile is one file shipped to where the page is served.
type Publisher ¶
type Publisher func(dest string, files []PublishFile, timeout time.Duration) error
Publisher ships the page and its series to where they are served. Injected: a test that published for real would need a host.
type ReapInput ¶
type ReapInput struct {
Roots string // comma-separated swarm roots
Queue string // the queue directory holding pending, launched and failed
Deadline time.Duration
DryRun bool
Procs ProcessTable
TempGlob string // default "/tmp/*swarmtest*"; a test points it at its own directory
TempRoot string // the directory the temp glob must sit under; empty derives it from the glob's literal prefix
TempAge time.Duration // default 30 minutes
// Rule E2 (runners.go): a runner busy with no in-progress run on Repo for longer than
// RunnerIdle is restarted through its service. With no Repo and no Runners table the
// rule does not run and the count is zero: a bench with no self-hosted runner has no
// jam to clear.
Repo string
Runners RunnerTable
Restarter RunnerRestarter
RunnerIdle time.Duration // default DefaultRunnerIdle (5 minutes)
Now func() time.Time
Stdout io.Writer
Stderr io.Writer
}
ReapInput is the reap verb's input, held apart from flag parsing.
type Reaper ¶
Reaper requeues or fails a card whose slot went quiet. Seam: rowan/pulse-ledger-reap-cut. A card it can neither requeue nor fail by a rule comes back Undecided.
type Refiller ¶
Refiller tops the queue up to its floor from the declared sources. Seam: rowan/pulse-config-gate-admission (the policy's floor and scope live with the config).
type Rerunner ¶
Rerunner is the optional half of a RunSource that can ask the provider to rerun a run's failed jobs. The shipped ghRunSource implements it; a source that cannot rerun leaves the gate's STOP as it is today.
type RuleRow ¶
RuleRow is one row of <queue>/RULES.tsv: the case kind it decides, the condition in the evidence that selects it, the verdict it gives, and its state (`pending` until a person has read it).
func CandidateRules ¶
CandidateRules are the rows that could decide this case: the rows of its own kind, and the rows written for every kind (`*`). Nothing else is put in front of the route, because a row of another kind is a row that can only mislead it.
type RunInput ¶
type RunInput struct {
Queue string // the queue directory: pending, launched, done, failed, STOP, RULES.tsv
Roots string // the benches, comma separated
Repo string // owner/name, the repo the gate watches
Branch string // the branch the gate watches
Hours float64
Tick time.Duration
Once bool
Max int
Bus string // a nova-bus clone; empty sends notes to <queue>/ESCALATE
As string // the name notes are sent to
Stdout io.Writer
Stderr io.Writer
Now func() time.Time
Sleep func(time.Duration)
// GateEvery is how many ticks apart the gate's own probe runs; 0 is gateEvery. It is a
// field and not a constant so a bench with a slow gh and a bench with a fixture can
// each say how often the probe is worth its seconds.
GateEvery int
// Configured, when set, is handed the configuration after every re-read, so the seams
// run on the same values this loop does and pulse.toml has ONE reader per tick.
Configured func(Config)
Gate Gater
Harvest Harvester
Sweep Sweeper
Reap Reaper
Refill Refiller
Launcher Launcher
Notifier Notifier
}
RunInput is the run verb, apart from flag parsing, so a test drives it against fake directories, a fake clock and stub seams -- no network, no model, no sleep.
type RunSource ¶
type RunSource interface {
LatestRun(repo, branch string) ([]CIRun, error)
FailedJob(repo string, runID int64) (CIJob, error)
}
RunSource is where the gate's verdict comes from. The shipped one wraps gh; a test's answers a fixture, so no test in this package reaches the network.
func NewFileRunSource ¶
NewFileRunSource reads a source file: {"runs":[...],"job":{"name":...,"log":...}}, or a bare array of runs.
func NewGHRunSource ¶
NewGHRunSource is the gate's source when no --source file is given.
type Runner ¶
type Runner struct {
Name string `json:"name"`
Status string `json:"status"` // online, offline
Busy bool `json:"busy"`
}
Runner is one self-hosted runner as the runners API reports it.
type RunnerRestarter ¶
RunnerRestarter restarts one runner through its own service.
type RunnerService ¶
type RunnerService struct {
Name string
Host string // "-" or empty is this machine
Kind string // systemd or svc
Target string // the unit, or the runner directory holding svc.sh
}
RunnerService is one row of the service map.
type RunnerTable ¶
type RunnerTable interface {
Runners(repo string) ([]Runner, error)
InProgress(repo string) (int, error)
}
RunnerTable is the two facts the rule needs: which runners are busy, and how many runs are actually in progress on the repo they serve.
type SSHRunner ¶
type SSHRunner struct {
Program string
}
SSHRunner is the real runner. Program is the ssh command from --ssh; empty is `ssh` on PATH.
type SelfReader ¶
type SelfReader func(name string, loops []string, now time.Time) SelfReading
SelfReader reads the host the verb runs on. Injected for the same reason.
type SelfReading ¶
type SelfReading struct {
Name string
CIRunners int
Cores int
Load int
FreeGB int
Orphans int
Loops []LoopCount
Unknown bool // the host could not be read; it renders like a DOWN bench
}
SelfReading is the host running the verb, which is a bench too. The Studio drowned at load 147 on 2026-09-17 and no page showed it, because the page only ever read the machines it ssh'd to.
type ServiceRestarter ¶
ServiceRestarter is the real restarter: per the service map, `systemctl restart <unit>` over ssh for a systemd runner (Space) and `<dir>/svc.sh stop && <dir>/svc.sh start` for a launchd one (the Studio). A runner with no row in the map is a refusal and not a guess: restarting the wrong service is worse than the jam.
func (ServiceRestarter) Restart ¶
func (s ServiceRestarter) Restart(name string) error
type StandardCheck ¶
type StandardCheck struct {
Name string // the token the STANDARD line carries
OS string // "linux", "darwin", or "" for every bench
Probe string // one line of POSIX shell printing the value on stdout
Match string // one of the Match* constants
Want string // what Match compares against
// Root, when set, is the TOOLCHAIN ROOT this check is the standard's own copy of, in the
// slash form internal/swarm/toolchain.go declares it: HOME-relative (`sdk`) or absolute
// (`/opt/homebrew/Cellar/go`). The wall grants exactly these paths, so the standard and
// the wall are ONE LIST checked in both directions, per OS, by the class test in
// internal/ci -- a root the wall grants that no bench provisions is a wall naming a path
// that will not be there, and a root a bench has that the wall never names is the hurt of
// 2026-09-18 returning. The KIND of each grant is the wall's decision and lives with the
// wall, not here: a bench only has to HAVE a root.
Root string
}
StandardCheck is one line of the bench provisioning standard: what it is called, which benches it applies to, the one-line shell that reads the bench, and the answer the standard demands.
func FleetStandardChecks ¶
func FleetStandardChecks(goos, goWant, stamp string, minFreeGB int) []StandardCheck
FleetStandardChecks is the standard itself: the checks for one operating system, in the order they print. goWant is the Go toolchain the fleet is on, stamp is the nova bins' build stamp (empty means "report whatever this bench has"), and minFreeGB is the floor under HOME (launch refuses below 25 GB).
The Linux list is tools/bench-standard.sh's toolchain half: the Go SDK, sbcl, the safe-rm helper every bench script sources, and the nova stamp. The darwin list is fleet/macos/provision-mac-bench.sh: the Go SDK and sbcl under ~/sdk, real git ahead of the Xcode shim, and each runner's .path carrying it (INSTALL-batman.md, 2026-09-18: /usr/bin/git is the shim and the sandbox cannot read /var/db/xcode_select_link).
THE TOOLCHAIN ROOTS (the `toolchain-*` checks, Root set) are this table's half of the one list the sandbox wall grants -- internal/swarm/toolchain.go is the other half, and the class test in internal/ci fails when they drift apart in either direction, per OS. A LINUX root is DEMANDED, because the standard's own installer is what puts it there and a bench missing one kills every Go card on it. A DARWIN root is REPORTED and never drifted on: a Mac's toolchains are installed and on PATH rather than unpacked into a home, so which trees exist is the machine's shape -- a Mac with no .NET is a Mac with no .NET -- while that the bench has a working Go at all is what the `go` check above asserts. The wall skips an absent root either way, so the report is the wall's argv read in advance.
func (StandardCheck) Met ¶
func (c StandardCheck) Met(value string) bool
Met says whether one check's value meets the standard.
type State ¶
State is what a restart must not lose: the next card number (the one cutter's counter), the tick count, the tick the last refill ran on, and how many times the gate has run.
type StatusHTMLInput ¶
type StatusHTMLInput struct {
HTML string // the local file to write
// Machines is the fleet's truth: the machines registry (internal/fleet), seven columns
// and roles. The benches read over ssh are its rows carrying role `bench`, and the page
// says which machines serve the merge group's shards, instead of leaving that to a
// second file nobody updates.
Machines string
// Benches is the retired four-column fleet file: name, ssh target, home, mac. It reads
// for one more release, and every run of it says on stderr what to pass instead.
Benches string
Queue string // the queue whose depth, fill log and REPO the page reads
SSH string // the ssh program; empty is "ssh"
Publish string // host:dir to ship the page and the series to; empty ships nothing
GhConfig string // GH_CONFIG_DIR for the gh children; empty leaves the caller's own
DayStart string // when the merged counter resets, as HH:MMZ; empty is 02:00Z
Branch string // the branch whose merge queue and tip the page shows; empty is dev
Self string // the name of the host running the verb; empty omits the row
Loops []string // label=pattern pairs counted on the self host
Timeout time.Duration
Reader FleetReader
SelfRead SelfReader
Ship Publisher
Now func() time.Time
Stdout io.Writer
Stderr io.Writer
}
StatusHTMLInput is everything `status --html` needs, apart from flag parsing, so a test can drive it against fake directories and injected readers.
type StatusInput ¶
type StatusInput struct {
Queue string // the queue directory: pending, launched, done, failed and the state files
Roots string // comma-separated bench roots, the benches in scope
SlotsStores string // comma-separated bench slot-lease stores to report utilisation for
Batches string // the directory holding the swarm's batch-*.out outputs; empty claims nothing
Day string // YYYY-MM-DD the day window starts at; empty means today (UTC)
Max int
Timeout time.Duration
ExpandingHours int // the sustained window the EXPANDING verdict requires, in whole hours; <= 0 means 2
Stdout io.Writer
Stderr io.Writer
Now func() time.Time
}
StatusInput is everything the status verb needs, apart from flag parsing so a test can drive it against fake directories and a fake gh on PATH.
type SweepInput ¶
type SweepInput struct {
Repo string
Queue string
Source PRSource
Enqueuer Enqueuer
Now func() time.Time
Stdout io.Writer
Stderr io.Writer
}
SweepInput is the sweep verb's input, held apart from flag parsing.
type Sweeper ¶
Sweeper retires what is finished: merged PRs, closed issues, stale job directories. Seam: rowan/pulse-ledger-reap-cut.
type TriageInput ¶
type TriageInput struct {
Case string
Queue string
Out string
Ref string
Evidence string // a file of the RESULT lines and the refusal line; default <queue>/UNDECIDED/<case>.txt
Stdout io.Writer
Stderr io.Writer
// Decide turns on the verb's typed decision: the bounded packet is put to the provider
// as one verdict question behind Floor, and the answer is printed as one advisory line
// beside the verb's own. A decision below the floor is a suggestion, never an
// authorization: the packet is cut and written exactly as it was before the flag.
Decide bool
Floor float64
Decider Decider
}
TriageInput is the triage verb, apart from flag parsing.
type Undecided ¶
Undecided is one thing no rule could decide, and the whole evidence for it. It is what a step hands back when its rule table has no row: the case kind (one of TriageKinds), the thing it is about, the RESULT lines and the harness's refusal line. Nothing else -- a log, a transcript or a report body in here would be the coordinator's turn again.
type Verdict ¶
type Verdict struct {
Case string
Verdict string
Rule string // the condition of the row that decided it, or NEW
}
Verdict is the one line a triage comes back as.
func ParseTriage ¶
ParseTriage reads the one line a triage card demands. Anything else -- a preamble, a second line, a verdict that is not one of the five, a kind that is not one of the seven -- is an error naming what was wrong, because a verdict this tool half-read is a verdict nobody approved.
type WatchInput ¶
type WatchInput struct {
Queue string // the merge queue: entry rows, the MERGED line and removal rows
Bus string // the nova-bus checkout whose addressed notes are watched
Jobs string // the job roots: <root>/<slot>/jobs/<label>/RESULT.md
Until string // the event that ends the watch: pr=<n> merged, note-from=<name>, job=<label> done
Cap time.Duration // the wall it never runs past
Now func() time.Time
Sleep func(time.Duration)
Stdout io.Writer
Stderr io.Writer
}
WatchInput is everything the watch verb needs, held apart from flag parsing so a test can drive it against a fake queue, a fake bus checkout, a fake bench tree and a fake clock.
type Wiring ¶
type Wiring struct {
// contains filtered or unexported fields
}
Wiring is the six seams of RunInput at once: it satisfies Gater, Harvester, Sweeper, Reaper, Refiller and Launcher, so one value fills every seam of a run.
func NewWiring ¶
func NewWiring(in WiringInput) *Wiring
NewWiring fills in the defaults and returns the seams. It makes no call of its own.
func (*Wiring) Gate ¶
Gate runs the gate verb over the branch this bench merges into. The verb owns the STOP file -- it writes the admission name on line 2, which is what admission.go reads while red -- so run.go's own STOP is only ever the fallback for a bench with no gate wired.
func (*Wiring) Harvest ¶
Harvest folds every bench that has cards in flight. A root with no cards.tsv has never launched anything and is skipped rather than refused.
func (*Wiring) Launch ¶
Launch fills every free slot on every bench from queue/pending, oldest card first. The cards it takes are moved to queue/launched before the batch starts, so a card is in exactly one place at every moment -- the stale listing that launched card-892 on both benches at once is what a move, and not a copy, prevents.
The STOP admission is launch.go's: while the gate's STOP stands, only a card whose line 1 names the red launches. That is class C, and it is why the red's own fix card no longer goes out by hand.
func (*Wiring) Reap ¶
Reap collects what the benches leak, and restarts a runner stuck busy with nothing running (rule E2). The counts are the reap verb's own line.
func (*Wiring) Refill ¶
Refill is the rule of bin/refill.sh, in the tool: a READ card for every open non-draft PR in <queue>/WORKSET with no card at its current head, and a FIX card for every WORKSET issue with no open PR naming it and no card pending or launched for it, the prior attempts named so the worker fixes the prompt's failure instead of repeating it (Glenn: fix the prompt, not retry). Every card is cut by `cut --kind`, which is the only numberer.
It runs on the configured cadence and only while the pool is under the floor -- the floor being every slot on every bench, because a queue deeper than the benches is a queue that went stale before it ran. WORKSET is the whole admission: an issue outside it is work nobody asked for (bug: 25 reads refused by mistake at 15:20Z, and the answer was the WORKSET, not a wider net).
type WiringInput ¶
type WiringInput struct {
Queue string
Roots string
Repo string
Branch string
Deadline time.Duration // the batch deadline; 0 is DefaultCardDeadline
Timeout time.Duration // the bound on every child; 0 is childTimeout
Max int
TempGlob string // the swarm test leftovers reap collects; empty is reap's own default
TempRoot string // the directory the temp glob must sit under; empty derives it from the glob's prefix
Now func() time.Time
Config func() Config // the tick's configuration, re-read by run.go each tick
Log io.Writer // where each verb's one line goes; nil is <queue>/pulse.log
Runs RunSource
PRs PRSource
Enqueuer Enqueuer
Procs ProcessTable
Runners RunnerTable
Restarter RunnerRestarter
Work WorkSource
}
WiringInput is everything the wired seams need. Every interface field is optional: nil takes the shipped implementation, and a test puts a fake in its place.
Source Files
¶
- admission.go
- beat.go
- cli.go
- config.go
- convergence.go
- cut.go
- cutkind.go
- fill.go
- fleet.go
- fleetadd.go
- fleetjoin.go
- fleetmirror.go
- fleetregistry.go
- fleetsleep.go
- fleetstandard.go
- fleetverbs.go
- gate.go
- harvest.go
- harvest_decide.go
- harvestbench.go
- hygiene.go
- launch.go
- ledger.go
- manager.go
- number.go
- pool.go
- probe.go
- progress.go
- reap.go
- relaunch.go
- run.go
- runners.go
- state.go
- status.go
- statusbatches.go
- statushtml.go
- statushtmlpage.go
- statushtmlrows.go
- statusindex.go
- statusline.go
- triage.go
- types.go
- validated.go
- watch.go
- wire.go