@groundnuty/macf-core 0.2.43 → 0.2.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/discovery.d.ts +59 -0
  2. package/dist/discovery.d.ts.map +1 -0
  3. package/dist/discovery.js +103 -0
  4. package/dist/discovery.js.map +1 -0
  5. package/dist/fleet-driver.d.ts +135 -0
  6. package/dist/fleet-driver.d.ts.map +1 -0
  7. package/dist/fleet-driver.js +44 -0
  8. package/dist/fleet-driver.js.map +1 -0
  9. package/dist/fleet-reconcile.d.ts +180 -0
  10. package/dist/fleet-reconcile.d.ts.map +1 -0
  11. package/dist/fleet-reconcile.js +249 -0
  12. package/dist/fleet-reconcile.js.map +1 -0
  13. package/dist/fleet-upgrade.d.ts +171 -0
  14. package/dist/fleet-upgrade.d.ts.map +1 -0
  15. package/dist/fleet-upgrade.js +177 -0
  16. package/dist/fleet-upgrade.js.map +1 -0
  17. package/dist/guest.d.ts +100 -0
  18. package/dist/guest.d.ts.map +1 -0
  19. package/dist/guest.js +120 -0
  20. package/dist/guest.js.map +1 -0
  21. package/dist/index.d.ts +8 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +8 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/norm.d.ts +2 -2
  26. package/dist/prompt-responses.d.ts +208 -0
  27. package/dist/prompt-responses.d.ts.map +1 -0
  28. package/dist/prompt-responses.js +274 -0
  29. package/dist/prompt-responses.js.map +1 -0
  30. package/dist/stall-signatures.d.ts +139 -0
  31. package/dist/stall-signatures.d.ts.map +1 -0
  32. package/dist/stall-signatures.js +219 -0
  33. package/dist/stall-signatures.js.map +1 -0
  34. package/dist/types.d.ts +2 -0
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/types.js +8 -4
  37. package/dist/types.js.map +1 -1
  38. package/dist/verify-green.d.ts +62 -0
  39. package/dist/verify-green.d.ts.map +1 -0
  40. package/dist/verify-green.js +53 -0
  41. package/dist/verify-green.js.map +1 -0
  42. package/package.json +1 -1
@@ -0,0 +1,249 @@
1
+ /** The zero state for an agent with no persisted history. */
2
+ export const EMPTY_RECONCILE_STATE = {
3
+ deafSweeps: 0,
4
+ restartAttempts: 0,
5
+ backoffUntil: 0,
6
+ alertOpen: false,
7
+ lastExit: null,
8
+ paused: false,
9
+ };
10
+ /** Sensible defaults; a caller overrides selectively via `resolveConfig`. */
11
+ export const DEFAULT_RECONCILE_CONFIG = {
12
+ execute: false,
13
+ allowRestart: false,
14
+ escalateAfterSweeps: 1,
15
+ backoffBaseMs: 60_000,
16
+ backoffMaxMs: 3_600_000,
17
+ stuckMax: 3,
18
+ launchStaggerMs: 15_000,
19
+ injectMessage: '⚠️ You appear OFF-CHANNELS (watchdog): your channel is down / not accepting. ' +
20
+ 'Investigate, clear any stale registry entry, and request a relaunch.',
21
+ };
22
+ /** Merge partial overrides onto the defaults. */
23
+ export function resolveReconcileConfig(overrides) {
24
+ return { ...DEFAULT_RECONCILE_CONFIG, ...(overrides ?? {}) };
25
+ }
26
+ // --- pure helpers -----------------------------------------------------------
27
+ /**
28
+ * How the probe sees a desired agent (three-valued, mirroring the reference's
29
+ * empty/`true`/`false` `reachable` field):
30
+ * - `absent` — not in the registry roster at all (not running / never registered)
31
+ * - `deaf` — in the roster but `/health` did NOT answer (registered, channel down)
32
+ * - `reachable` — in the roster + `/health` answered (mTLS 2xx)
33
+ */
34
+ export function classifyReachability(state, agent) {
35
+ const found = state.agents.find((a) => a.name === agent);
36
+ if (!found)
37
+ return 'absent';
38
+ return found.online ? 'reachable' : 'deaf';
39
+ }
40
+ /**
41
+ * The EXPONENTIAL restart-backoff window after `attempts` consecutive restarts:
42
+ * `base * 2^(attempts-1)`, clamped to `maxMs`. `attempts <= 0` → no window (0).
43
+ */
44
+ export function nextBackoffMs(attempts, config) {
45
+ if (attempts <= 0)
46
+ return 0;
47
+ const raw = config.backoffBaseMs * 2 ** (attempts - 1);
48
+ return Math.min(raw, config.backoffMaxMs);
49
+ }
50
+ /**
51
+ * Minimal parser for the fixed `desired-agents.yaml` shape (no yaml dep — the
52
+ * cron-light awk parse's TS twin). Pairs each `- agent: <x>` with the FOLLOWING
53
+ * `workspace: <y>`; strips inline `# comments` + surrounding quotes; ignores the
54
+ * `agents:` header, blank lines, and standalone comments.
55
+ */
56
+ export function parseDesiredAgents(text) {
57
+ const out = [];
58
+ let pendingAgent = null;
59
+ for (const line of text.split(/\r?\n/)) {
60
+ const agentMatch = line.match(/^\s*-\s*agent:\s*(.+)$/);
61
+ if (agentMatch) {
62
+ pendingAgent = cleanScalar(agentMatch[1]);
63
+ continue;
64
+ }
65
+ const wsMatch = line.match(/^\s*workspace:\s*(.+)$/);
66
+ if (wsMatch && pendingAgent) {
67
+ out.push({ agent: pendingAgent, workspace: cleanScalar(wsMatch[1]) });
68
+ pendingAgent = null;
69
+ }
70
+ }
71
+ return out;
72
+ }
73
+ /** Strip a trailing `# comment`, trim, and drop one pair of surrounding quotes. */
74
+ function cleanScalar(raw) {
75
+ const noComment = raw.replace(/\s+#.*$/, '').trim();
76
+ return noComment.replace(/^["']|["']$/g, '');
77
+ }
78
+ // --- report formatting ------------------------------------------------------
79
+ function headerRows() {
80
+ return [
81
+ `${'AGENT'.padEnd(16)} ${'DECISION'.padEnd(10)} DETAIL`,
82
+ `${'-----'.padEnd(16)} ${'--------'.padEnd(10)} ------`,
83
+ ];
84
+ }
85
+ function reportRow(row) {
86
+ return `${row.agent.padEnd(16)} ${row.decision.padEnd(10)} ${row.detail}`;
87
+ }
88
+ // --- the orchestrator -------------------------------------------------------
89
+ /**
90
+ * Run one reconcile sweep over the `desired` set. Probes ACTUAL state, decides +
91
+ * (on `execute`) acts per agent through the injected driver + seams, and returns
92
+ * the report + exit code. Dry-run (the default) prints the report and touches
93
+ * nothing (no driver mutations, no state writes, no heartbeat).
94
+ */
95
+ export async function reconcileFleet(desired, deps, config) {
96
+ let fleet;
97
+ try {
98
+ fleet = await deps.driver.probe();
99
+ }
100
+ catch (err) {
101
+ deps.log(`FATAL: fleet probe failed — ${err instanceof Error ? err.message : String(err)}`);
102
+ return { rc: 2, rows: [] };
103
+ }
104
+ for (const h of headerRows())
105
+ deps.log(h);
106
+ const rows = [];
107
+ let rc = 0;
108
+ let launchCount = 0;
109
+ for (const d of desired) {
110
+ const state = deps.store.read(d.agent);
111
+ const row = await reconcileAgent(d, state, fleet, deps, config, launchCount);
112
+ deps.log(reportRow(row.row));
113
+ rows.push(row.row);
114
+ if (row.row.decision === 'LAUNCH')
115
+ launchCount += 1;
116
+ if (row.acted)
117
+ rc = 1;
118
+ }
119
+ deps.log('');
120
+ const mode = config.execute ? 'EXECUTE' : 'dry-run';
121
+ const restart = config.allowRestart ? 'on' : 'off';
122
+ if (config.execute) {
123
+ await deps.heartbeat({ rc, at: deps.now(), restartEnabled: config.allowRestart });
124
+ }
125
+ deps.log(rc === 0
126
+ ? `reconcile [${mode}, restart:${restart}]: all desired agents OK / down — no action.`
127
+ : `reconcile [${mode}, restart:${restart}]: action(s) taken or needed above.`);
128
+ return { rc, rows };
129
+ }
130
+ /** Per-agent reconcile: classify → decide → (execute) act. Returns the row + whether it counts as an action. */
131
+ async function reconcileAgent(d, state, fleet, deps, config, launchCount) {
132
+ const agent = d.agent;
133
+ // desired-down (SKIP): explicit paused sentinel.
134
+ if (state.paused) {
135
+ return { row: mkRow(agent, 'SKIP', 'paused (desired-down; explicit) — not resurrected'), acted: false };
136
+ }
137
+ const actual = classifyReachability(fleet, agent);
138
+ if (actual === 'absent') {
139
+ // exit-code intent: not running + last-exit==0 (operator typed /exit) → desired-down.
140
+ if (state.lastExit === 0) {
141
+ return {
142
+ row: mkRow(agent, 'SKIP', 'not running + last-exit==0 (operator /exit) — desired-down, not restarted'),
143
+ acted: false,
144
+ };
145
+ }
146
+ await doLaunch(d, deps, config, launchCount);
147
+ return {
148
+ row: mkRow(agent, 'LAUNCH', `not running, last-exit!=0/absent → cold-start (ws: ${d.workspace})`),
149
+ acted: true,
150
+ };
151
+ }
152
+ if (actual === 'reachable') {
153
+ if (config.execute)
154
+ deps.store.reset(agent);
155
+ return { row: mkRow(agent, 'OK', 'reachable + accepting'), acted: false };
156
+ }
157
+ // deaf → HEAL ladder.
158
+ await healAgent(d, state, deps, config);
159
+ return { row: mkRow(agent, 'HEAL', 'registered but channel DOWN (deaf) → ladder'), acted: true };
160
+ }
161
+ /** Cold-start a desired-but-down agent, STAGGERED so N dead agents don't thundering-herd. */
162
+ async function doLaunch(d, deps, config, launchCount) {
163
+ if (!config.execute)
164
+ return;
165
+ // Space every launch after the first by the stagger window.
166
+ if (launchCount > 0)
167
+ await deps.sleep(config.launchStaggerMs);
168
+ await deps.driver.launch(d.agent);
169
+ }
170
+ /**
171
+ * The tiered HEAL ladder for one deaf agent (cross-sweep). Sweep 1 → Tier-1
172
+ * inject; sweep 2+ → escalate under the aliveness-gate + restart-backoff before
173
+ * any Tier-2 restart, with Tier-3 alert on every escalation path.
174
+ */
175
+ async function healAgent(d, state, deps, config) {
176
+ const agent = d.agent;
177
+ const n = state.deafSweeps + 1;
178
+ if (config.execute)
179
+ deps.store.write(agent, { ...state, deafSweeps: n });
180
+ if (n <= config.escalateAfterSweeps) {
181
+ deps.log(` [sweep ${n}] first deaf sweep → Tier-1 (gated inject)`);
182
+ if (config.execute)
183
+ await deps.driver.inject(agent, config.injectMessage);
184
+ return;
185
+ }
186
+ deps.log(` [sweep ${n}] still deaf after ${n - 1} prior sweep(s) → escalate (Tier-1 did not recover)`);
187
+ // Aliveness-gate — NEVER restart a busy agent (the #642 channel-dead/TUI-alive mode).
188
+ // Only probed on execute (dry-run constructs the plan without touching the driver).
189
+ if (config.execute && (await deps.driver.isBusy(agent))) {
190
+ deps.log(` [ABORT] ${agent} pane ACTIVE — agent ALIVE despite channel-down (#642). NOT restarting a working agent.`);
191
+ await raiseAlert(agent, `deaf ${n} sweeps but pane active (#642) — human check`, deps, config);
192
+ return;
193
+ }
194
+ // Restart-backoff gate — NEVER restart-storm a repeatedly-failing agent.
195
+ if (deps.now() < state.backoffUntil) {
196
+ deps.log(` [BACKOFF] ${agent} restart suppressed (attempt ${state.restartAttempts}, window not elapsed)`);
197
+ if (state.restartAttempts >= config.stuckMax) {
198
+ deps.log(` [STUCK] ${agent} stuck in restart-backoff after ${state.restartAttempts} attempts → Tier-3`);
199
+ await raiseAlert(agent, `stuck in restart-backoff after ${state.restartAttempts} restarts`, deps, config);
200
+ }
201
+ return;
202
+ }
203
+ await escalateRestart(d, state, n, deps, config);
204
+ }
205
+ /** The Tier-2/Tier-3 tail once the aliveness + backoff gates pass. */
206
+ async function escalateRestart(d, state, n, deps, config) {
207
+ const agent = d.agent;
208
+ // Restarts exhausted (>= stuckMax) but still deaf → escalate, do NOT restart again.
209
+ if (state.restartAttempts >= config.stuckMax) {
210
+ deps.log(` [STUCK] ${agent} ${state.restartAttempts} restarts did not recover → Tier-3 (not restarting again)`);
211
+ await raiseAlert(agent, `stuck: ${state.restartAttempts} restarts did not recover`, deps, config);
212
+ return;
213
+ }
214
+ // Tier-2 graceful-restart — held behind operator sign-off.
215
+ if (!config.allowRestart) {
216
+ deps.log(` [held] Tier-2 graceful-restart of ${agent} SUPPRESSED (no --allow-restart; operator sign-off required)`);
217
+ await raiseAlert(agent, `deaf ${n} sweeps; Tier-2 held (no --allow-restart)`, deps, config);
218
+ return;
219
+ }
220
+ deps.log(` Tier-2 graceful-restart ${agent} (attempt ${state.restartAttempts + 1})`);
221
+ if (config.execute) {
222
+ await deps.driver.restart(agent);
223
+ const attempts = state.restartAttempts + 1;
224
+ const fresh = deps.store.read(agent);
225
+ deps.store.write(agent, {
226
+ ...fresh,
227
+ restartAttempts: attempts,
228
+ backoffUntil: deps.now() + nextBackoffMs(attempts, config),
229
+ });
230
+ }
231
+ await raiseAlert(agent, `restarted after ${n} deaf sweeps`, deps, config);
232
+ }
233
+ /** Tier-3 alert (a `gh issue create`), dedup'd via the persisted `alertOpen` sentinel. */
234
+ async function raiseAlert(agent, reason, deps, config) {
235
+ if (deps.store.read(agent).alertOpen) {
236
+ deps.log(` [skip] Tier-3 alert for ${agent} already open (dedup)`);
237
+ return;
238
+ }
239
+ deps.log(` Tier-3 alert for ${agent} (${reason})`);
240
+ if (!config.execute)
241
+ return;
242
+ await deps.alert(agent, reason);
243
+ const fresh = deps.store.read(agent);
244
+ deps.store.write(agent, { ...fresh, alertOpen: true });
245
+ }
246
+ function mkRow(agent, decision, detail) {
247
+ return { agent, decision, detail };
248
+ }
249
+ //# sourceMappingURL=fleet-reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fleet-reconcile.js","sourceRoot":"","sources":["../src/fleet-reconcile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,171 @@
1
+ import type { FleetDriver, FleetState } from './fleet-driver.js';
2
+ import type { WorkspaceRecord } from './discovery.js';
3
+ import type { VerifyGreenOptions, VerifyGreenResult } from './verify-green.js';
4
+ /**
5
+ * How a discovered fleet member classifies against the target version. Pure,
6
+ * version-only (the busy-gate is an EXECUTE-time driver decision, not a plan
7
+ * disposition):
8
+ * - `behind` — reachable + running a version `< target` ⇒ a roll candidate.
9
+ * - `at-target` — reachable + running `>= target` ⇒ skip (already current).
10
+ * - `offline` — no comparable running version (unreachable OR a `/health` body
11
+ * with no version) ⇒ skip + report (let reconcile heal it first;
12
+ * a bad-release HALT must never be attributed to a down agent).
13
+ */
14
+ export type UpgradeDisposition = 'behind' | 'at-target' | 'offline';
15
+ /** One member's version-classification against the target (pure planner output). */
16
+ export interface AgentUpgradePlan {
17
+ /** The agent's routing label (registry key). */
18
+ readonly agent: string;
19
+ /** The fleet (registry identifier) this member belongs to. */
20
+ readonly fleet: string;
21
+ /** The agent's live self-reported `/health.version`, or `null` when offline/version-less. */
22
+ readonly runningVersion: string | null;
23
+ /** The workspace's on-disk version pin (legible even when the agent is dead). */
24
+ readonly pinnedVersion: string | null;
25
+ /** The version-only disposition (see `UpgradeDisposition`). */
26
+ readonly disposition: UpgradeDisposition;
27
+ }
28
+ /**
29
+ * The EXECUTE-path outcome of a single agent in the roll:
30
+ * - `upgraded` — rolled + came up green on the target.
31
+ * - `busy-skipped` — busy-gated (or `--wait` timed out still busy); NOT a
32
+ * failure, so the roll CONTINUES to the next agent.
33
+ * - `halted` — did not verify-green within the budget; the roll STOPS here.
34
+ */
35
+ export type RollOutcome = 'upgraded' | 'busy-skipped' | 'halted';
36
+ /** The per-agent EXECUTE result. `detail` carries the green version or halt reason. */
37
+ export interface AgentRollResult {
38
+ readonly agent: string;
39
+ readonly outcome: RollOutcome;
40
+ readonly detail?: string;
41
+ }
42
+ /** The result of rolling ONE fleet (the EXECUTE path). */
43
+ export interface FleetRollResult {
44
+ readonly results: readonly AgentRollResult[];
45
+ /** True when a member failed verify-green and the roll stopped early. */
46
+ readonly halted: boolean;
47
+ /** Count of agents rolled + verified green. */
48
+ readonly upgraded: number;
49
+ /** Count of agents skipped because they were busy. */
50
+ readonly busySkipped: number;
51
+ }
52
+ /** Progress events for CLI rendering (the RESULT objects are what tests assert on). */
53
+ export type UpgradeEvent = {
54
+ readonly kind: 'fleet-start';
55
+ readonly fleet: string;
56
+ readonly behind: number;
57
+ readonly total: number;
58
+ } | {
59
+ readonly kind: 'fleet-skipped';
60
+ readonly fleet: string;
61
+ readonly reason: string;
62
+ } | {
63
+ readonly kind: 'roll-start';
64
+ readonly agent: string;
65
+ readonly from: string | null;
66
+ readonly to: string;
67
+ } | {
68
+ readonly kind: 'busy-skip';
69
+ readonly agent: string;
70
+ readonly waited: boolean;
71
+ } | {
72
+ readonly kind: 'upgraded';
73
+ readonly agent: string;
74
+ readonly version: string;
75
+ } | {
76
+ readonly kind: 'halt';
77
+ readonly agent: string;
78
+ readonly reason: string;
79
+ readonly lastVersion: string | null;
80
+ };
81
+ /**
82
+ * Classify each discovered fleet member against the target version. PURE — no
83
+ * driver, no clock, no network. The single source of the behind/at-target/offline
84
+ * decision, unit-tested in isolation.
85
+ */
86
+ export declare function planFleetUpgrade(members: readonly WorkspaceRecord[], state: FleetState, targetVersion: string): readonly AgentUpgradePlan[];
87
+ /** Tunables for a single-fleet roll. */
88
+ export interface RollFleetOptions {
89
+ /** The version each rolled agent must self-report to be considered green. */
90
+ readonly targetVersion: string;
91
+ /** Per-agent verify-green wall-time budget (ms). */
92
+ readonly verifyTimeoutMs: number;
93
+ /** `--wait`: on a busy agent, poll for idle instead of skipping. */
94
+ readonly wait?: boolean;
95
+ /** Total budget to wait for a busy agent to go idle when `wait` is set (ms). */
96
+ readonly waitTimeoutMs?: number;
97
+ /** Re-poll interval while waiting for idle (ms). */
98
+ readonly waitPollMs?: number;
99
+ }
100
+ /** Injected side-effect seams for the roll (all fakeable in tests). */
101
+ export interface RollFleetDeps {
102
+ /** The fleet driver whose verbs the sequencer calls. */
103
+ readonly driver: FleetDriver;
104
+ /**
105
+ * The post-restart verify-green runner. Injected whole (not built here) so the
106
+ * command wires its RE-RESOLVING probe (DR-037 Decision 5 — re-read the registry
107
+ * each poll for the fresh restart-self port) and tests supply a fake outcome.
108
+ */
109
+ readonly verifyGreen: (opts: VerifyGreenOptions) => Promise<VerifyGreenResult>;
110
+ /** Sleep `ms` (used only by the `--wait` busy re-poll; injected so tests don't wait). */
111
+ readonly sleep: (ms: number) => Promise<void>;
112
+ /** Monotonic clock in ms (drives the `--wait` budget; injected in tests). */
113
+ readonly now: () => number;
114
+ /** Optional progress sink for CLI rendering. */
115
+ readonly onEvent?: (ev: UpgradeEvent) => void;
116
+ }
117
+ /** Default total `--wait` budget for a busy agent to go idle (ms). */
118
+ export declare const DEFAULT_WAIT_TIMEOUT_MS = 300000;
119
+ /** Default `--wait` re-poll interval (ms). */
120
+ export declare const DEFAULT_WAIT_POLL_MS = 5000;
121
+ /**
122
+ * Roll ONE fleet: for every `behind` plan (in order) run the busy-gate → upgrade
123
+ * → restart → verify-green cycle, HALTING the roll on the first agent that fails
124
+ * to come up green. Non-`behind` plans are ignored (planned skips). Busy agents
125
+ * are skipped + reported (never a halt); with `wait`, they are re-polled for idle
126
+ * first. The verb order + HALT semantics mirror `fleet/upgrade.sh`'s `roll_one`.
127
+ */
128
+ export declare function rollFleet(plans: readonly AgentUpgradePlan[], opts: RollFleetOptions, deps: RollFleetDeps): Promise<FleetRollResult>;
129
+ /** Options for the multi-fleet orchestrator. */
130
+ export interface UpgradeFleetsOptions extends RollFleetOptions {
131
+ /** `false` = dry-run: probe + plan + report, NO driver mutations (the default). */
132
+ readonly execute: boolean;
133
+ }
134
+ /** Injected seams for the multi-fleet orchestrator. */
135
+ export interface UpgradeFleetsDeps {
136
+ /**
137
+ * Resolve the driver for a fleet (registry identifier). Production binds
138
+ * `createVmDriverFromConfig` at a representative workspace; tests return a fake.
139
+ * `null` ⇒ the fleet cannot be reached (skip + report, NOT a halt).
140
+ */
141
+ readonly resolveDriver: (fleet: string) => Promise<FleetDriver | null>;
142
+ readonly verifyGreen: (opts: VerifyGreenOptions) => Promise<VerifyGreenResult>;
143
+ readonly sleep: (ms: number) => Promise<void>;
144
+ readonly now: () => number;
145
+ readonly onEvent?: (ev: UpgradeEvent) => void;
146
+ }
147
+ /** One fleet's slice of the run report. */
148
+ export interface FleetPlanReport {
149
+ readonly fleet: string;
150
+ readonly plans: readonly AgentUpgradePlan[];
151
+ /** Present only in EXECUTE mode. */
152
+ readonly rolled?: FleetRollResult;
153
+ /** Present when the fleet was skipped whole (e.g. driver unresolved). */
154
+ readonly skipped?: string;
155
+ }
156
+ /** The full run report across all selected fleets. */
157
+ export interface FleetUpgradeReport {
158
+ readonly target: string;
159
+ readonly fleets: readonly FleetPlanReport[];
160
+ /** True when a fleet HALTED (and later fleets were consequently not started). */
161
+ readonly halted: boolean;
162
+ }
163
+ /**
164
+ * Orchestrate a rolling upgrade across the ORDERED `fleets` (registry
165
+ * identifiers), fleet-by-fleet. Each fleet is probed + planned; in EXECUTE mode
166
+ * it is then rolled, and a HALT in one fleet STOPS the whole run — later fleets
167
+ * are never started (a bad release cannot cascade across fleets). An unresolvable
168
+ * fleet is skipped + reported and does NOT halt the run.
169
+ */
170
+ export declare function upgradeFleets(fleets: readonly string[], opts: UpgradeFleetsOptions, deps: UpgradeFleetsDeps): Promise<FleetUpgradeReport>;
171
+ //# sourceMappingURL=fleet-upgrade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fleet-upgrade.d.ts","sourceRoot":"","sources":["../src/fleet-upgrade.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * The rolling fleet-upgrade DECISION layer (DR-037 / macf#682 Phase 2) — the
3
+ * runtime-agnostic sequencer that promotes the devops `fleet/upgrade.sh`
4
+ * reference impl onto the framework substrate.
5
+ *
6
+ * It is the pure decision half of the DR-037 decision/driver split (OQ2 — pure
7
+ * logic lives in macf-core; the per-runtime driver bodies live in `packages/macf`
8
+ * or a future K8s package). It NEVER touches `/proc`, tmux, `capture-pane`, or
9
+ * kubectl — it drives entirely off the injected `FleetDriver` verbs
10
+ * (`probe`/`discoverWorkspaces`/`isBusy`/`upgrade`/`restart`) plus an injected
11
+ * `verifyGreen` runner, so the SAME sequencer rolls a VM tmux fleet, a macOS
12
+ * host, and a far-future K8s deployment by swapping only the driver.
13
+ *
14
+ * The state machine (per fleet, one agent at a time):
15
+ *
16
+ * probe() + discoverWorkspaces()
17
+ * → for each member whose RUNNING version < target (compareSemver):
18
+ * busy-gate (driver.isBusy → busy ⇒ SKIP+REPORT, or `--wait` for idle)
19
+ * → driver.upgrade(agent)
20
+ * → driver.restart(agent)
21
+ * → verifyGreen({agent, target}) // re-resolves the fresh endpoint
22
+ * → green ⇒ next agent
23
+ * → NOT green within the budget ⇒ HALT the roll (a bad release
24
+ * stalls at agent 1 and CANNOT brick the fleet)
25
+ *
26
+ * Multi-fleet (`--fleet a,b,c`) rolls fleet-by-fleet: each fleet's roll must
27
+ * finish without a HALT before the next fleet starts (a bad release in fleet-1
28
+ * stops fleet-2 from ever being touched).
29
+ *
30
+ * DRY-RUN is the default at the command boundary: `execute: false` computes +
31
+ * reports the PLAN (probe + classify) and performs NO driver mutations.
32
+ */
33
+ import { compareSemver } from './semver.js';
34
+ /**
35
+ * Classify each discovered fleet member against the target version. PURE — no
36
+ * driver, no clock, no network. The single source of the behind/at-target/offline
37
+ * decision, unit-tested in isolation.
38
+ */
39
+ export function planFleetUpgrade(members, state, targetVersion) {
40
+ const byName = new Map(state.agents.map((a) => [a.name, a]));
41
+ return members.map((m) => {
42
+ const live = byName.get(m.agent);
43
+ const runningVersion = live?.online ? (live.version ?? null) : null;
44
+ let disposition;
45
+ if (runningVersion === null) {
46
+ disposition = 'offline';
47
+ }
48
+ else if (compareSemver(runningVersion, targetVersion) >= 0) {
49
+ disposition = 'at-target';
50
+ }
51
+ else {
52
+ disposition = 'behind';
53
+ }
54
+ return {
55
+ agent: m.agent,
56
+ fleet: m.registry,
57
+ runningVersion,
58
+ pinnedVersion: m.versionPin,
59
+ disposition,
60
+ };
61
+ });
62
+ }
63
+ /** Default total `--wait` budget for a busy agent to go idle (ms). */
64
+ export const DEFAULT_WAIT_TIMEOUT_MS = 300_000;
65
+ /** Default `--wait` re-poll interval (ms). */
66
+ export const DEFAULT_WAIT_POLL_MS = 5_000;
67
+ /**
68
+ * Poll `driver.isBusy(agent)` until it reports idle or the budget is exhausted.
69
+ * Returns `true` if the agent became idle (safe to roll), `false` if it stayed
70
+ * busy for the whole budget. Called only after an initial busy reading, so it
71
+ * sleeps-then-checks and always terminates on the injected clock.
72
+ */
73
+ async function waitForIdle(agent, opts, deps) {
74
+ const budget = opts.waitTimeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
75
+ const poll = opts.waitPollMs ?? DEFAULT_WAIT_POLL_MS;
76
+ const start = deps.now();
77
+ while (deps.now() - start < budget) {
78
+ await deps.sleep(poll);
79
+ if (!(await deps.driver.isBusy(agent)))
80
+ return true;
81
+ }
82
+ return false;
83
+ }
84
+ /**
85
+ * Roll ONE fleet: for every `behind` plan (in order) run the busy-gate → upgrade
86
+ * → restart → verify-green cycle, HALTING the roll on the first agent that fails
87
+ * to come up green. Non-`behind` plans are ignored (planned skips). Busy agents
88
+ * are skipped + reported (never a halt); with `wait`, they are re-polled for idle
89
+ * first. The verb order + HALT semantics mirror `fleet/upgrade.sh`'s `roll_one`.
90
+ */
91
+ export async function rollFleet(plans, opts, deps) {
92
+ const results = [];
93
+ let upgraded = 0;
94
+ let busySkipped = 0;
95
+ for (const plan of plans) {
96
+ if (plan.disposition !== 'behind')
97
+ continue;
98
+ const agent = plan.agent;
99
+ let busy = await deps.driver.isBusy(agent);
100
+ let waited = false;
101
+ if (busy && opts.wait) {
102
+ waited = true;
103
+ busy = !(await waitForIdle(agent, opts, deps));
104
+ }
105
+ if (busy) {
106
+ busySkipped += 1;
107
+ results.push({ agent, outcome: 'busy-skipped', detail: waited ? 'still busy after --wait' : 'busy' });
108
+ deps.onEvent?.({ kind: 'busy-skip', agent, waited });
109
+ continue;
110
+ }
111
+ deps.onEvent?.({ kind: 'roll-start', agent, from: plan.runningVersion, to: opts.targetVersion });
112
+ await deps.driver.upgrade(agent);
113
+ await deps.driver.restart(agent);
114
+ const green = await deps.verifyGreen({
115
+ agent,
116
+ targetVersion: opts.targetVersion,
117
+ timeoutMs: opts.verifyTimeoutMs,
118
+ });
119
+ if (green.ok) {
120
+ upgraded += 1;
121
+ results.push({ agent, outcome: 'upgraded', detail: green.version });
122
+ deps.onEvent?.({ kind: 'upgraded', agent, version: green.version });
123
+ continue;
124
+ }
125
+ const reason = green.reason;
126
+ results.push({
127
+ agent,
128
+ outcome: 'halted',
129
+ detail: `verify-green ${reason} (last=${green.lastVersion ?? 'down'})`,
130
+ });
131
+ deps.onEvent?.({ kind: 'halt', agent, reason, lastVersion: green.lastVersion });
132
+ return { results, halted: true, upgraded, busySkipped };
133
+ }
134
+ return { results, halted: false, upgraded, busySkipped };
135
+ }
136
+ /**
137
+ * Orchestrate a rolling upgrade across the ORDERED `fleets` (registry
138
+ * identifiers), fleet-by-fleet. Each fleet is probed + planned; in EXECUTE mode
139
+ * it is then rolled, and a HALT in one fleet STOPS the whole run — later fleets
140
+ * are never started (a bad release cannot cascade across fleets). An unresolvable
141
+ * fleet is skipped + reported and does NOT halt the run.
142
+ */
143
+ export async function upgradeFleets(fleets, opts, deps) {
144
+ const reports = [];
145
+ let halted = false;
146
+ for (const fleet of fleets) {
147
+ const driver = await deps.resolveDriver(fleet);
148
+ if (!driver) {
149
+ reports.push({ fleet, plans: [], skipped: 'driver-unresolved' });
150
+ deps.onEvent?.({ kind: 'fleet-skipped', fleet, reason: 'driver-unresolved' });
151
+ continue;
152
+ }
153
+ const members = driver.discoverWorkspaces().filter((r) => r.registry === fleet);
154
+ const state = await driver.probe();
155
+ const plans = planFleetUpgrade(members, state, opts.targetVersion);
156
+ const behind = plans.filter((p) => p.disposition === 'behind').length;
157
+ deps.onEvent?.({ kind: 'fleet-start', fleet, behind, total: plans.length });
158
+ if (!opts.execute) {
159
+ reports.push({ fleet, plans });
160
+ continue;
161
+ }
162
+ const rolled = await rollFleet(plans, opts, {
163
+ driver,
164
+ verifyGreen: deps.verifyGreen,
165
+ sleep: deps.sleep,
166
+ now: deps.now,
167
+ onEvent: deps.onEvent,
168
+ });
169
+ reports.push({ fleet, plans, rolled });
170
+ if (rolled.halted) {
171
+ halted = true;
172
+ break; // do NOT start the next fleet — a bad release stops the whole run
173
+ }
174
+ }
175
+ return { target: opts.targetVersion, fleets: reports, halted };
176
+ }
177
+ //# sourceMappingURL=fleet-upgrade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fleet-upgrade.js","sourceRoot":"","sources":["../src/fleet-upgrade.ts"],"names":[],"mappings":"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"}