/** * ⟨q-1c95f7d4⟩ PHASE 5.4 TASK 5 — THE EXTERNAL TICK, OVER THE FLEET. * * `stall_check` infers. Its liveness axis is pid-existence (and for a local `tmux-push` * seat nothing writes heartbeats at all), its activity axis is VCS commits, and between * them a seat that is THINKING and a seat that is WEDGED are the same reading. herdr * observes the pane from outside the process and answers `idle | working | blocked`, which * is a signal about the SEAT rather than about its branch. * * ⛔ EVIDENCE, NEVER A VERDICT (5.2). Nothing here decides a lane's state: the board says * what work exists, the existing axes say whether it moved, and the tick says whether the * seat is moving. A `working` tick does NOT cancel a stalled branch — a seat can be busy on * the wrong thing — and this module therefore never removes a hit and never marks a row * healthy. It adds evidence and it credits COVERAGE, which is the one thing a real * observation is allowed to do. * * ⛔ A SEAT WITH NO TICK SOURCE PRODUCES NO ENTRY, AND THAT IS THE POINT (5.3). tmux seats * are not given an invented tick: they keep exactly the unmeasurable story they had. A * HERDR seat whose signal cannot be read produces an entry that says so, so `0 of N * readable` reads BLIND and never CALM. */ import type { Transport, TransportMarker, TickState } from "../transports/types.js"; import { activeTransport } from "../transports/index.js"; export type SeatTick = | { agentId: string; transport: string; readable: true; state: TickState; source: string } | { agentId: string; transport: string; readable: false; why: string }; export type FleetTick = { /** One entry per seat whose transport HAS an external observer. Never a tmux seat. */ seats: SeatTick[]; /** Seats whose transport has no tick source at all — named, never silently skipped. */ withoutSource: string[]; readable: number; states: Partial>; /** Said in the answer itself: a fleet with a tick source and nothing readable is BLIND. */ note: string; }; export const NO_SOURCE = "this seat's transport has no external observer to ask — its coverage is unchanged by the tick"; /** * Read the tick for every live marker. `transport` is injected by tests; in the server it * is whatever the configured transport resolved to, so a fleet running tmux asks nothing * and gets an empty, explicitly-blind answer rather than a fabricated one. */ export async function readFleetTick( markers: Iterable, transport: Transport | null | undefined = activeTransport(), ): Promise { const seats: SeatTick[] = []; const withoutSource: string[] = []; const states: Partial> = {}; for (const marker of markers) { // The ACTIVE transport is the only thing that can answer, and only for its own kind: // a tmux-push server holding a herdr marker (the 3.4 disagreement state) must not // claim a reading it cannot take. if (!transport || typeof transport.readTick !== "function" || marker.transport !== transport.kind) { withoutSource.push(marker.agentId); continue; } let reading; try { reading = await transport.readTick(marker); } catch (e) { reading = { readable: false as const, why: `the transport threw while reading the tick: ${(e as Error).message}` }; } if (reading.readable) { seats.push({ agentId: marker.agentId, transport: marker.transport, readable: true, state: reading.state, source: reading.source }); states[reading.state] = (states[reading.state] ?? 0) + 1; } else { seats.push({ agentId: marker.agentId, transport: marker.transport, readable: false, why: reading.why }); } } const readable = seats.filter((s) => s.readable).length; return { seats, withoutSource, readable, states, note: seats.length === 0 ? `no seat on this bus has an external tick source (${withoutSource.length} seat(s) on a transport that cannot be asked) — the tick measured NOTHING, which is not the same as a calm fleet` : readable === 0 ? `${seats.length} seat(s) have a tick source and NONE answered — this fleet is BLIND on the tick axis, not quiet` : `${readable} of ${seats.length} seat(s) with a tick source answered`, }; } /** Index by agentId for the per-row lookup in `stall_check`. */ export const tickByAgent = (tick: FleetTick): Map => new Map(tick.seats.map((s) => [s.agentId, s])); /** * ⟨q-1c95f7d4⟩ 5.2 — WHAT A READING MEANS FOR A SCORED LANE, as one function so the rule * is testable without a board, a repo or a registry. * * `blocked` is the reading this task was filed for: herdr's blocked means the seat is * WAITING ON A PERSON, and an in-flight lane whose seat waits on a person while the fleet * is away is unattended work that no VCS or heartbeat axis can see. It is reported as a * hit. `working` and `idle` are coverage and nothing else — explicitly NOT a clean bill, * because the branch axes answer a different question and keep their own verdicts. */ export function tickVerdict(seat: SeatTick | undefined): { measured: boolean; hit: boolean; why: string } | null { if (!seat) return null; if (!seat.readable) return { measured: false, hit: false, why: seat.why }; if (seat.state === "blocked") return { measured: true, hit: true, why: `${seat.source} reports the seat BLOCKED — waiting on a person, which no branch or heartbeat axis can see. Evidence about the SEAT, not a verdict about the lane: the row's own axes still say whether the work moved.`, }; return { measured: true, hit: false, why: `${seat.source} reports the seat ${seat.state} — coverage, not a clean bill: a seat can be busy on the wrong thing, so the branch axes keep their own verdicts.`, }; }