@groundnuty/macf-core 0.2.44 → 0.2.46
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.
- package/dist/discovery.d.ts +59 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +103 -0
- package/dist/discovery.js.map +1 -0
- package/dist/fleet-driver.d.ts +135 -0
- package/dist/fleet-driver.d.ts.map +1 -0
- package/dist/fleet-driver.js +44 -0
- package/dist/fleet-driver.js.map +1 -0
- package/dist/fleet-reconcile.d.ts +180 -0
- package/dist/fleet-reconcile.d.ts.map +1 -0
- package/dist/fleet-reconcile.js +249 -0
- package/dist/fleet-reconcile.js.map +1 -0
- package/dist/fleet-upgrade.d.ts +171 -0
- package/dist/fleet-upgrade.d.ts.map +1 -0
- package/dist/fleet-upgrade.js +177 -0
- package/dist/fleet-upgrade.js.map +1 -0
- package/dist/guest.d.ts +100 -0
- package/dist/guest.d.ts.map +1 -0
- package/dist/guest.js +120 -0
- package/dist/guest.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/norm.d.ts +2 -2
- package/dist/prompt-responses.d.ts +208 -0
- package/dist/prompt-responses.d.ts.map +1 -0
- package/dist/prompt-responses.js +274 -0
- package/dist/prompt-responses.js.map +1 -0
- package/dist/registry/local-client.d.ts.map +1 -1
- package/dist/registry/local-client.js +15 -9
- package/dist/registry/local-client.js.map +1 -1
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +91 -27
- package/dist/registry/registry.js.map +1 -1
- package/dist/registry/types.d.ts +57 -12
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry/types.js.map +1 -1
- package/dist/stall-signatures.d.ts +139 -0
- package/dist/stall-signatures.d.ts.map +1 -0
- package/dist/stall-signatures.js +219 -0
- package/dist/stall-signatures.js.map +1 -0
- package/dist/verify-green.d.ts +62 -0
- package/dist/verify-green.d.ts.map +1 -0
- package/dist/verify-green.js +53 -0
- package/dist/verify-green.js.map +1 -0
- package/package.json +1 -1
package/dist/registry/types.d.ts
CHANGED
|
@@ -25,7 +25,8 @@ export type AgentInfo = z.infer<typeof AgentInfoSchema>;
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function agentInfoEquals(a: AgentInfo | null, b: AgentInfo | null): boolean;
|
|
27
27
|
/**
|
|
28
|
-
* Outcome of a conditional (compare-and-set) register (groundnuty/macf#439
|
|
28
|
+
* Outcome of a conditional (compare-and-set) register (groundnuty/macf#439,
|
|
29
|
+
* reshaped by groundnuty/macf#702 — over-register).
|
|
29
30
|
*
|
|
30
31
|
* `ok: true` — this instance now owns the slot; `current` is the value just
|
|
31
32
|
* written (=== the `info` passed in). Ownership strength is
|
|
@@ -36,11 +37,38 @@ export declare function agentInfoEquals(a: AgentInfo | null, b: AgentInfo | null
|
|
|
36
37
|
* API; see registry.ts `registerConditional`).
|
|
37
38
|
* `ok: false` — a concurrent writer changed the slot between the collision
|
|
38
39
|
* check and this write; `current` is the conflicting value
|
|
39
|
-
* observed (null if the slot was emptied). The caller
|
|
40
|
-
*
|
|
40
|
+
* observed (null if the slot was emptied). The caller decides
|
|
41
|
+
* claim-vs-yield from `reason` (see below) — `ok:false` is NO
|
|
42
|
+
* LONGER an unconditional abort signal (groundnuty/macf#702 fixed
|
|
43
|
+
* the devops 8h-outage bug where it was).
|
|
44
|
+
*
|
|
45
|
+
* `reason` discriminates WHY the CAS resolved the way it did — this is the
|
|
46
|
+
* groundnuty/macf#702 fix. Before #702, `ok:false` was always treated as "lost
|
|
47
|
+
* a race, abort" (`RegisterRaceError`), which mis-fired when the write
|
|
48
|
+
* actually succeeded but the GitHub Variables API's read-after-write lag
|
|
49
|
+
* served a stale GET on the post-write read-back (the exact devops bug:
|
|
50
|
+
* `current == expected` — nothing changed — yet the read-back momentarily
|
|
51
|
+
* still showed the pre-write value, and the old code aborted anyway):
|
|
52
|
+
*
|
|
53
|
+
* - `'claimed'` — `ok:true`. The write landed and (when a read-back
|
|
54
|
+
* exists) is confirmed, OR the pre-write compare
|
|
55
|
+
* found the slot unchanged from `expected` and the
|
|
56
|
+
* write was issued — the CAS ALWAYS claims in this
|
|
57
|
+
* case, never aborts, even if a same-backend
|
|
58
|
+
* read-back lag means confirmation isn't
|
|
59
|
+
* immediately visible (groundnuty/macf#702 §1).
|
|
60
|
+
* - `'lost-to-newer'` — `ok:false`. The write-time re-read (or read-back)
|
|
61
|
+
* observed a DIFFERENT registration than `expected`
|
|
62
|
+
* — a genuine concurrent writer. The caller
|
|
63
|
+
* (server.ts) is responsible for classifying that
|
|
64
|
+
* `current` as newer-live (yield) vs stale/older
|
|
65
|
+
* (retry the takeover) — the registry layer only
|
|
66
|
+
* reports the conflicting value; it doesn't probe
|
|
67
|
+
* liveness (that's `collision.ts`'s job).
|
|
41
68
|
*/
|
|
42
69
|
export interface RegisterResult {
|
|
43
70
|
readonly ok: boolean;
|
|
71
|
+
readonly reason: 'claimed' | 'lost-to-newer';
|
|
44
72
|
readonly current: AgentInfo | null;
|
|
45
73
|
}
|
|
46
74
|
/**
|
|
@@ -98,15 +126,32 @@ export interface HeartbeatResult {
|
|
|
98
126
|
export interface Registry {
|
|
99
127
|
readonly register: (name: string, info: AgentInfo) => Promise<void>;
|
|
100
128
|
/**
|
|
101
|
-
* Compare-and-set register (groundnuty/macf#439
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* atomic under its file lock
|
|
108
|
-
* window with a pre-write compare
|
|
109
|
-
*
|
|
129
|
+
* Compare-and-set register (groundnuty/macf#439; over-register semantics
|
|
130
|
+
* per groundnuty/macf#702): write `info` if the slot still matches
|
|
131
|
+
* `expected` (the value observed during the collision check; `null` =
|
|
132
|
+
* expected-absent). Closes the TOCTOU window between the collision check's
|
|
133
|
+
* read and the registration write — a racing second writer can't silently
|
|
134
|
+
* clobber the first. Backends differ in atomicity: the local registry is
|
|
135
|
+
* fully atomic under its file lock (no read-back needed); the
|
|
136
|
+
* GitHub-Variables backend narrows the window with a pre-write compare and
|
|
137
|
+
* best-effort-confirms via a post-write read-back (no native CAS primitive
|
|
138
|
+
* exists on that API — see registry.ts).
|
|
139
|
+
*
|
|
140
|
+
* **groundnuty/macf#702 (over-register):** `current == expected` — the slot
|
|
141
|
+
* is UNCHANGED, including the case where `expected` is itself a stale/dead
|
|
142
|
+
* entry the caller decided to take over — is ALWAYS a claim (`ok:true,
|
|
143
|
+
* reason:'claimed'`), never a failure. This holds even when a same-backend
|
|
144
|
+
* read-back can't yet observe the write (GitHub Variables API
|
|
145
|
+
* read-after-write lag) — a lagging read-back is a confirmation gap, not a
|
|
146
|
+
* lost race, and must not be reported as one (this was the devops 8h-outage
|
|
147
|
+
* bug: `current==expected==<stale>` incorrectly returned `ok:false`).
|
|
148
|
+
* `ok:false, reason:'lost-to-newer'` fires ONLY when the write-time re-read
|
|
149
|
+
* (or the read-back) observes a value that is a genuinely different
|
|
150
|
+
* registration than the caller's own `info` AND different from `expected`
|
|
151
|
+
* — a real concurrent writer. The registry layer reports the conflicting
|
|
152
|
+
* `current` but does not classify it as live/dead/newer/older — that
|
|
153
|
+
* liveness judgment is the caller's (server.ts + collision.ts), which
|
|
154
|
+
* decides claim-over-stale (retry) vs yield-to-newer-live.
|
|
110
155
|
*/
|
|
111
156
|
readonly registerConditional: (name: string, info: AgentInfo, expected: AgentInfo | null) => Promise<RegisterResult>;
|
|
112
157
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/registry/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,eAAe;;;;;;;;;;iBAiB1B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,GAAG,IAAI,EAAE,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CASjF;AAED
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/registry/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/registry/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,4DAA4D;AAE5D,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACjC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACrC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB;;;;;;;;;OASG;IACH,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACtC,CAAC,CAAC;AAIH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,CAAmB,EAAE,CAAmB;IACtE,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7C,OAAO,CACL,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;QACjB,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;QACjB,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;QACjB,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,WAAW;QAC/B,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,CACxB,CAAC;AACJ,CAAC;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/registry/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,4DAA4D;AAE5D,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACjC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACrC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB;;;;;;;;;OASG;IACH,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACtC,CAAC,CAAC;AAIH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,CAAmB,EAAE,CAAmB;IACtE,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7C,OAAO,CACL,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;QACjB,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;QACjB,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;QACjB,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,WAAW;QAC/B,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,CACxB,CAAC;AACJ,CAAC;AAiMD,iCAAiC;AAEjC,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;IACtB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACvB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,MAAM,CAAC;IAClD,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;IAC1B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACxB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACvB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACxB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACxB,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;IACxB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACxB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC;IAC1C,uBAAuB;IACvB,2BAA2B;IAC3B,wBAAwB;IACxB,yBAAyB;CAC1B,CAAC,CAAC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stall-signature allowlist — config schema, the pure pane-matcher, and the
|
|
3
|
+
* per-action fire-cap logic behind `macf fleet resume` (DR-037 / groundnuty/macf#686,
|
|
4
|
+
* porting `groundnuty/macf-devops-toolkit:fleet/resume.sh` + `stall-signatures.json`).
|
|
5
|
+
*
|
|
6
|
+
* SAFETY-CRITICAL. `macf fleet resume` acts on an IDLE agent — an idle agent is one
|
|
7
|
+
* of three things and only the pane tells them apart (operator, 2026-06-28):
|
|
8
|
+
*
|
|
9
|
+
* - idle-CLEAN (no signature matched) → legitimately idle/done → DO NOTHING.
|
|
10
|
+
* - idle-STALLED (rate-limit / turn-abort) → `action: nudge` — resume the SAME
|
|
11
|
+
* session (a nudge preserves in-progress work; a restart loses it + re-hits a
|
|
12
|
+
* rate-limit). The gentlest recovery.
|
|
13
|
+
* - idle-BLOCKED (permission / trust / → `action: report` — a DURABLE operator
|
|
14
|
+
* skill / memory prompt) alert, NEVER auto-answered (an
|
|
15
|
+
* authorization decision needs a human;
|
|
16
|
+
* DR-033 ceremony-not-authorization).
|
|
17
|
+
*
|
|
18
|
+
* The safety contract is the sibling of DR-033's: **allowlist-only, never a blind
|
|
19
|
+
* nudge.** We act ONLY on an idle agent whose recent pane matches a KNOWN, vetted
|
|
20
|
+
* signature. An unmatched idle pane is legitimately idle/done → never touched (no
|
|
21
|
+
* spam). An unknown-but-blocking prompt falls through to no-match → skip; the
|
|
22
|
+
* operator adds a `report` signature as the TUI evolves (the allowlist is DATA).
|
|
23
|
+
*
|
|
24
|
+
* This module holds the PURE logic — the config the operator curates in
|
|
25
|
+
* `.claude/.macf/stall-signatures.json`, the schema/validator the CLI runs at
|
|
26
|
+
* `macf init/update/rules refresh`, and the frame-matcher the `fleet resume`
|
|
27
|
+
* decision layer drives off. The tested reference is the devops `resume.sh` bash
|
|
28
|
+
* (which reimplements the same match-then-dispatch in bash + jq); the two MUST
|
|
29
|
+
* stay in lockstep.
|
|
30
|
+
*/
|
|
31
|
+
import { z } from 'zod';
|
|
32
|
+
import { MacfError } from './errors.js';
|
|
33
|
+
/** A matched stall's dispatched action: nudge a stalled agent, or report a blocked one. */
|
|
34
|
+
export declare const STALL_ACTIONS: readonly ["nudge", "report"];
|
|
35
|
+
export type StallAction = (typeof STALL_ACTIONS)[number];
|
|
36
|
+
/**
|
|
37
|
+
* Per-action default fire cap when an entry omits `max_fires` — a nudge is a
|
|
38
|
+
* gentle repeatable resume (retry a few times, then escalate), a report is a
|
|
39
|
+
* one-shot durable alert per episode (re-alerting spams the operator). Mirrors
|
|
40
|
+
* the devops `resume.sh` defaults (`nudge`=3, `report`=1).
|
|
41
|
+
*/
|
|
42
|
+
export declare const DEFAULT_NUDGE_MAX_FIRES = 3;
|
|
43
|
+
export declare const DEFAULT_REPORT_MAX_FIRES = 1;
|
|
44
|
+
/** Thrown when the stall-signatures config fails schema / regex validation. */
|
|
45
|
+
export declare class StallSignaturesError extends MacfError {
|
|
46
|
+
constructor(message: string);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A single stall-signature allowlist entry.
|
|
50
|
+
*
|
|
51
|
+
* - `name` — the signature's stable identifier (rendered in the plan / the
|
|
52
|
+
* alert title, so a fire-cap counter can key off it).
|
|
53
|
+
* - `signature` — a regular-expression source matched CASE-INSENSITIVELY against
|
|
54
|
+
* the captured idle pane (the ERE the devops bash greps with `grep -qiE`; the
|
|
55
|
+
* seeds' alternation/`\?`/`\.` forms are JS-RegExp-compatible). Validated for
|
|
56
|
+
* compilability at load — a malformed pattern is a LOUD config error, never a
|
|
57
|
+
* silent non-match.
|
|
58
|
+
* - `action` — `nudge` (resume a stalled agent) or `report` (durable alert for a
|
|
59
|
+
* blocked agent; NEVER auto-answered). Defaults to `nudge`.
|
|
60
|
+
* - `nudge` — the text sent into the TUI for `action: nudge` (default: a generic
|
|
61
|
+
* "please continue").
|
|
62
|
+
* - `report` — the operator-facing summary for `action: report` (default: a
|
|
63
|
+
* generic "blocked on an operator-input prompt").
|
|
64
|
+
* - `max_fires` — per-episode fire cap; omitted → the per-action default.
|
|
65
|
+
* - `comment` — operator documentation (why this signature; caveats). Ignored by
|
|
66
|
+
* the matcher; kept in the schema so the seed's `comment` fields validate.
|
|
67
|
+
*
|
|
68
|
+
* `.strict()` so an operator typo in a field name (e.g. `signatures`) is a loud
|
|
69
|
+
* validation error, not a silently-ignored field.
|
|
70
|
+
*/
|
|
71
|
+
export declare const StallSignatureEntrySchema: z.ZodObject<{
|
|
72
|
+
name: z.ZodString;
|
|
73
|
+
signature: z.ZodString;
|
|
74
|
+
action: z.ZodDefault<z.ZodEnum<{
|
|
75
|
+
nudge: "nudge";
|
|
76
|
+
report: "report";
|
|
77
|
+
}>>;
|
|
78
|
+
nudge: z.ZodOptional<z.ZodString>;
|
|
79
|
+
report: z.ZodOptional<z.ZodString>;
|
|
80
|
+
max_fires: z.ZodOptional<z.ZodNumber>;
|
|
81
|
+
comment: z.ZodOptional<z.ZodString>;
|
|
82
|
+
}, z.core.$strict>;
|
|
83
|
+
export type StallSignatureEntry = z.infer<typeof StallSignatureEntrySchema>;
|
|
84
|
+
/**
|
|
85
|
+
* The whole config: a bare array of entries — the exact shape of the devops
|
|
86
|
+
* `fleet/stall-signatures.json` reference (so the two files are interchangeable).
|
|
87
|
+
*/
|
|
88
|
+
export declare const StallSignaturesConfigSchema: z.ZodArray<z.ZodObject<{
|
|
89
|
+
name: z.ZodString;
|
|
90
|
+
signature: z.ZodString;
|
|
91
|
+
action: z.ZodDefault<z.ZodEnum<{
|
|
92
|
+
nudge: "nudge";
|
|
93
|
+
report: "report";
|
|
94
|
+
}>>;
|
|
95
|
+
nudge: z.ZodOptional<z.ZodString>;
|
|
96
|
+
report: z.ZodOptional<z.ZodString>;
|
|
97
|
+
max_fires: z.ZodOptional<z.ZodNumber>;
|
|
98
|
+
comment: z.ZodOptional<z.ZodString>;
|
|
99
|
+
}, z.core.$strict>>;
|
|
100
|
+
export type StallSignaturesConfig = z.infer<typeof StallSignaturesConfigSchema>;
|
|
101
|
+
/**
|
|
102
|
+
* Validate + regex-check a parsed config. Throws `StallSignaturesError` on a
|
|
103
|
+
* schema-invalid shape OR an uncompilable `signature` (loud — a malformed
|
|
104
|
+
* allowlist must never silently degrade to "matches nothing"). Returns the
|
|
105
|
+
* accepted entries.
|
|
106
|
+
*/
|
|
107
|
+
export declare function loadStallSignatures(raw: unknown): readonly StallSignatureEntry[];
|
|
108
|
+
/**
|
|
109
|
+
* The per-episode fire cap for an entry — its explicit `max_fires`, else the
|
|
110
|
+
* per-action default (nudge 3 / report 1).
|
|
111
|
+
*/
|
|
112
|
+
export declare function resolveMaxFires(entry: StallSignatureEntry): number;
|
|
113
|
+
/** Fire-cap guard: may this stall-signature fire again given its prior fire count? */
|
|
114
|
+
export declare function canFireStall(entry: StallSignatureEntry, firedCount: number): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* First allowlist entry whose `signature` regex matches the captured pane
|
|
117
|
+
* (case-insensitive), or `null` when the pane matches nothing — the idle-CLEAN
|
|
118
|
+
* case the caller treats as "legitimately idle/done, never touched". Pure.
|
|
119
|
+
*
|
|
120
|
+
* A signature that fails to compile is skipped (defensively) rather than
|
|
121
|
+
* throwing here — `loadStallSignatures` is the fail-loud gate; the matcher stays
|
|
122
|
+
* total so a single bad entry never breaks a whole sweep.
|
|
123
|
+
*/
|
|
124
|
+
export declare function matchStallSignature(pane: string, entries: readonly StallSignatureEntry[]): StallSignatureEntry | null;
|
|
125
|
+
/**
|
|
126
|
+
* The canonical seed `macf init` writes when no `stall-signatures.json` exists.
|
|
127
|
+
* Ported verbatim from `groundnuty/macf-devops-toolkit:fleet/stall-signatures.json`
|
|
128
|
+
* (the tested reference). Two `nudge` signatures (transient rate-limit / turn-abort
|
|
129
|
+
* — resume the same session) and three `report` signatures (permission / trust /
|
|
130
|
+
* skill-or-memory prompts — authorization decisions that must NEVER be
|
|
131
|
+
* auto-answered, only surfaced as a durable operator alert).
|
|
132
|
+
*
|
|
133
|
+
* Signature strings are best-effort across Claude Code versions — the allowlist is
|
|
134
|
+
* DATA; an operator tunes it against the real pane as the TUI evolves. An imperfect
|
|
135
|
+
* signature fails SAFE (falls through to no-match → the agent is left untouched,
|
|
136
|
+
* never a wrong action).
|
|
137
|
+
*/
|
|
138
|
+
export declare const STALL_SIGNATURES_SEED: StallSignaturesConfig;
|
|
139
|
+
//# sourceMappingURL=stall-signatures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stall-signatures.d.ts","sourceRoot":"","sources":["../src/stall-signatures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,2FAA2F;AAC3F,eAAO,MAAM,aAAa,8BAA+B,CAAC;AAC1D,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,+EAA+E;AAC/E,qBAAa,oBAAqB,SAAQ,SAAS;gBACrC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;kBAU3B,CAAC;AACZ,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;;GAGG;AACH,eAAO,MAAM,2BAA2B;;;;;;;;;;;mBAAqC,CAAC;AAC9E,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,mBAAmB,EAAE,CAsBhF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CAGlE;AAED,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAEpF;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,mBAAmB,EAAE,GACtC,mBAAmB,GAAG,IAAI,CAW5B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,EAAE,qBA8DnC,CAAC"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stall-signature allowlist — config schema, the pure pane-matcher, and the
|
|
3
|
+
* per-action fire-cap logic behind `macf fleet resume` (DR-037 / groundnuty/macf#686,
|
|
4
|
+
* porting `groundnuty/macf-devops-toolkit:fleet/resume.sh` + `stall-signatures.json`).
|
|
5
|
+
*
|
|
6
|
+
* SAFETY-CRITICAL. `macf fleet resume` acts on an IDLE agent — an idle agent is one
|
|
7
|
+
* of three things and only the pane tells them apart (operator, 2026-06-28):
|
|
8
|
+
*
|
|
9
|
+
* - idle-CLEAN (no signature matched) → legitimately idle/done → DO NOTHING.
|
|
10
|
+
* - idle-STALLED (rate-limit / turn-abort) → `action: nudge` — resume the SAME
|
|
11
|
+
* session (a nudge preserves in-progress work; a restart loses it + re-hits a
|
|
12
|
+
* rate-limit). The gentlest recovery.
|
|
13
|
+
* - idle-BLOCKED (permission / trust / → `action: report` — a DURABLE operator
|
|
14
|
+
* skill / memory prompt) alert, NEVER auto-answered (an
|
|
15
|
+
* authorization decision needs a human;
|
|
16
|
+
* DR-033 ceremony-not-authorization).
|
|
17
|
+
*
|
|
18
|
+
* The safety contract is the sibling of DR-033's: **allowlist-only, never a blind
|
|
19
|
+
* nudge.** We act ONLY on an idle agent whose recent pane matches a KNOWN, vetted
|
|
20
|
+
* signature. An unmatched idle pane is legitimately idle/done → never touched (no
|
|
21
|
+
* spam). An unknown-but-blocking prompt falls through to no-match → skip; the
|
|
22
|
+
* operator adds a `report` signature as the TUI evolves (the allowlist is DATA).
|
|
23
|
+
*
|
|
24
|
+
* This module holds the PURE logic — the config the operator curates in
|
|
25
|
+
* `.claude/.macf/stall-signatures.json`, the schema/validator the CLI runs at
|
|
26
|
+
* `macf init/update/rules refresh`, and the frame-matcher the `fleet resume`
|
|
27
|
+
* decision layer drives off. The tested reference is the devops `resume.sh` bash
|
|
28
|
+
* (which reimplements the same match-then-dispatch in bash + jq); the two MUST
|
|
29
|
+
* stay in lockstep.
|
|
30
|
+
*/
|
|
31
|
+
import { z } from 'zod';
|
|
32
|
+
import { MacfError } from './errors.js';
|
|
33
|
+
/** A matched stall's dispatched action: nudge a stalled agent, or report a blocked one. */
|
|
34
|
+
export const STALL_ACTIONS = ['nudge', 'report'];
|
|
35
|
+
/**
|
|
36
|
+
* Per-action default fire cap when an entry omits `max_fires` — a nudge is a
|
|
37
|
+
* gentle repeatable resume (retry a few times, then escalate), a report is a
|
|
38
|
+
* one-shot durable alert per episode (re-alerting spams the operator). Mirrors
|
|
39
|
+
* the devops `resume.sh` defaults (`nudge`=3, `report`=1).
|
|
40
|
+
*/
|
|
41
|
+
export const DEFAULT_NUDGE_MAX_FIRES = 3;
|
|
42
|
+
export const DEFAULT_REPORT_MAX_FIRES = 1;
|
|
43
|
+
/** Thrown when the stall-signatures config fails schema / regex validation. */
|
|
44
|
+
export class StallSignaturesError extends MacfError {
|
|
45
|
+
constructor(message) {
|
|
46
|
+
super('STALL_SIGNATURES_ERROR', message);
|
|
47
|
+
this.name = 'StallSignaturesError';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A single stall-signature allowlist entry.
|
|
52
|
+
*
|
|
53
|
+
* - `name` — the signature's stable identifier (rendered in the plan / the
|
|
54
|
+
* alert title, so a fire-cap counter can key off it).
|
|
55
|
+
* - `signature` — a regular-expression source matched CASE-INSENSITIVELY against
|
|
56
|
+
* the captured idle pane (the ERE the devops bash greps with `grep -qiE`; the
|
|
57
|
+
* seeds' alternation/`\?`/`\.` forms are JS-RegExp-compatible). Validated for
|
|
58
|
+
* compilability at load — a malformed pattern is a LOUD config error, never a
|
|
59
|
+
* silent non-match.
|
|
60
|
+
* - `action` — `nudge` (resume a stalled agent) or `report` (durable alert for a
|
|
61
|
+
* blocked agent; NEVER auto-answered). Defaults to `nudge`.
|
|
62
|
+
* - `nudge` — the text sent into the TUI for `action: nudge` (default: a generic
|
|
63
|
+
* "please continue").
|
|
64
|
+
* - `report` — the operator-facing summary for `action: report` (default: a
|
|
65
|
+
* generic "blocked on an operator-input prompt").
|
|
66
|
+
* - `max_fires` — per-episode fire cap; omitted → the per-action default.
|
|
67
|
+
* - `comment` — operator documentation (why this signature; caveats). Ignored by
|
|
68
|
+
* the matcher; kept in the schema so the seed's `comment` fields validate.
|
|
69
|
+
*
|
|
70
|
+
* `.strict()` so an operator typo in a field name (e.g. `signatures`) is a loud
|
|
71
|
+
* validation error, not a silently-ignored field.
|
|
72
|
+
*/
|
|
73
|
+
export const StallSignatureEntrySchema = z
|
|
74
|
+
.object({
|
|
75
|
+
name: z.string().min(1),
|
|
76
|
+
signature: z.string().min(1),
|
|
77
|
+
action: z.enum(STALL_ACTIONS).default('nudge'),
|
|
78
|
+
nudge: z.string().min(1).optional(),
|
|
79
|
+
report: z.string().min(1).optional(),
|
|
80
|
+
max_fires: z.number().int().positive().optional(),
|
|
81
|
+
comment: z.string().optional(),
|
|
82
|
+
})
|
|
83
|
+
.strict();
|
|
84
|
+
/**
|
|
85
|
+
* The whole config: a bare array of entries — the exact shape of the devops
|
|
86
|
+
* `fleet/stall-signatures.json` reference (so the two files are interchangeable).
|
|
87
|
+
*/
|
|
88
|
+
export const StallSignaturesConfigSchema = z.array(StallSignatureEntrySchema);
|
|
89
|
+
/**
|
|
90
|
+
* Validate + regex-check a parsed config. Throws `StallSignaturesError` on a
|
|
91
|
+
* schema-invalid shape OR an uncompilable `signature` (loud — a malformed
|
|
92
|
+
* allowlist must never silently degrade to "matches nothing"). Returns the
|
|
93
|
+
* accepted entries.
|
|
94
|
+
*/
|
|
95
|
+
export function loadStallSignatures(raw) {
|
|
96
|
+
const parsed = StallSignaturesConfigSchema.safeParse(raw);
|
|
97
|
+
if (!parsed.success) {
|
|
98
|
+
throw new StallSignaturesError(`stall-signatures config is invalid: ${parsed.error.issues
|
|
99
|
+
.map((i) => `${i.path.join('.') || '<root>'}: ${i.message}`)
|
|
100
|
+
.join('; ')}`);
|
|
101
|
+
}
|
|
102
|
+
for (const entry of parsed.data) {
|
|
103
|
+
try {
|
|
104
|
+
// Validate compilability up front (fail-loud). Case-insensitive to mirror
|
|
105
|
+
// the devops `grep -iE`.
|
|
106
|
+
new RegExp(entry.signature, 'i');
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
110
|
+
throw new StallSignaturesError(`stall-signatures entry "${entry.name}" has an invalid regex signature: ${msg}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return parsed.data;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The per-episode fire cap for an entry — its explicit `max_fires`, else the
|
|
117
|
+
* per-action default (nudge 3 / report 1).
|
|
118
|
+
*/
|
|
119
|
+
export function resolveMaxFires(entry) {
|
|
120
|
+
if (entry.max_fires !== undefined)
|
|
121
|
+
return entry.max_fires;
|
|
122
|
+
return entry.action === 'report' ? DEFAULT_REPORT_MAX_FIRES : DEFAULT_NUDGE_MAX_FIRES;
|
|
123
|
+
}
|
|
124
|
+
/** Fire-cap guard: may this stall-signature fire again given its prior fire count? */
|
|
125
|
+
export function canFireStall(entry, firedCount) {
|
|
126
|
+
return firedCount < resolveMaxFires(entry);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* First allowlist entry whose `signature` regex matches the captured pane
|
|
130
|
+
* (case-insensitive), or `null` when the pane matches nothing — the idle-CLEAN
|
|
131
|
+
* case the caller treats as "legitimately idle/done, never touched". Pure.
|
|
132
|
+
*
|
|
133
|
+
* A signature that fails to compile is skipped (defensively) rather than
|
|
134
|
+
* throwing here — `loadStallSignatures` is the fail-loud gate; the matcher stays
|
|
135
|
+
* total so a single bad entry never breaks a whole sweep.
|
|
136
|
+
*/
|
|
137
|
+
export function matchStallSignature(pane, entries) {
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
let re;
|
|
140
|
+
try {
|
|
141
|
+
re = new RegExp(entry.signature, 'i');
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (re.test(pane))
|
|
147
|
+
return entry;
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The canonical seed `macf init` writes when no `stall-signatures.json` exists.
|
|
153
|
+
* Ported verbatim from `groundnuty/macf-devops-toolkit:fleet/stall-signatures.json`
|
|
154
|
+
* (the tested reference). Two `nudge` signatures (transient rate-limit / turn-abort
|
|
155
|
+
* — resume the same session) and three `report` signatures (permission / trust /
|
|
156
|
+
* skill-or-memory prompts — authorization decisions that must NEVER be
|
|
157
|
+
* auto-answered, only surfaced as a durable operator alert).
|
|
158
|
+
*
|
|
159
|
+
* Signature strings are best-effort across Claude Code versions — the allowlist is
|
|
160
|
+
* DATA; an operator tunes it against the real pane as the TUI evolves. An imperfect
|
|
161
|
+
* signature fails SAFE (falls through to no-match → the agent is left untouched,
|
|
162
|
+
* never a wrong action).
|
|
163
|
+
*/
|
|
164
|
+
export const STALL_SIGNATURES_SEED = [
|
|
165
|
+
{
|
|
166
|
+
name: 'rate-limit',
|
|
167
|
+
signature: '(temporarily limiting requests|Rate limited|API Error.*limiting)',
|
|
168
|
+
action: 'nudge',
|
|
169
|
+
nudge: 'You appear to have stalled on a transient API rate-limit (server-side, not ' +
|
|
170
|
+
'your usage quota). The limit should have cleared — please continue your work ' +
|
|
171
|
+
'where you left off.',
|
|
172
|
+
max_fires: 4,
|
|
173
|
+
comment: 'Anthropic server-side capacity throttle (NOT the account quota). Leaves the ' +
|
|
174
|
+
'agent idle mid-flow. Nudge resumes the SAME session (preserves work); restart ' +
|
|
175
|
+
'would lose it + re-hit the limit. verify-resumed self-corrects a nudge into a ' +
|
|
176
|
+
'still-active throttle (back off + retry).',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'turn-aborted',
|
|
180
|
+
signature: '(Request was aborted|aborted by user|API Error: Connection error)',
|
|
181
|
+
action: 'nudge',
|
|
182
|
+
nudge: 'Your last turn aborted before completing. Please continue your work where you left off.',
|
|
183
|
+
max_fires: 3,
|
|
184
|
+
comment: 'A turn that died on a transient connection/abort error, leaving the agent ' +
|
|
185
|
+
'idle. Same resume-not-restart logic.',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: 'permission-prompt',
|
|
189
|
+
signature: '(Do you want to proceed\\?|Do you want to make this edit\\?|Do you want to create|Yes, and don.t ask again)',
|
|
190
|
+
action: 'report',
|
|
191
|
+
report: 'blocked on a tool-permission prompt ("Do you want to proceed?") — needs the ' +
|
|
192
|
+
'operator to approve/deny. Cannot be auto-answered (an authorization decision, ' +
|
|
193
|
+
'per DR-033 ceremony-not-authorization).',
|
|
194
|
+
max_fires: 1,
|
|
195
|
+
comment: 'Claude Code tool-permission dialog. action=report (NOT nudge/answer): ' +
|
|
196
|
+
'authorization prompts require human judgment, so the correct automation is to ' +
|
|
197
|
+
'make the silent block LOUD, never to answer it. macf-devops-toolkit#132.',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: 'trust-folder-prompt',
|
|
201
|
+
signature: '(Do you trust the files in this folder\\?|trust the files in this)',
|
|
202
|
+
action: 'report',
|
|
203
|
+
report: 'blocked on a folder-trust prompt — needs the operator to confirm trust. ' +
|
|
204
|
+
'Authorization decision; cannot be auto-answered.',
|
|
205
|
+
max_fires: 1,
|
|
206
|
+
comment: "Claude Code 'trust this folder' dialog on a new/changed workspace. action=report. #132.",
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'skill-or-memory-prompt',
|
|
210
|
+
signature: '(Allow .* to run\\?|Save this to memory\\?|Add this to memory\\?|Allow this skill)',
|
|
211
|
+
action: 'report',
|
|
212
|
+
report: 'blocked on a skill-approval or memory-accept prompt — needs the operator\'s ' +
|
|
213
|
+
'decision. Cannot be auto-answered.',
|
|
214
|
+
max_fires: 1,
|
|
215
|
+
comment: 'Skill-approval / memory-accept dialogs. action=report. Signature strings are ' +
|
|
216
|
+
'best-effort across CC versions — the allowlist is DATA, tune as the TUI evolves. #132.',
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
//# sourceMappingURL=stall-signatures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stall-signatures.js","sourceRoot":"","sources":["../src/stall-signatures.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { HealthResponse } from './types.js';
|
|
2
|
+
/** Default poll interval between `probe` attempts (ms). */
|
|
3
|
+
export declare const DEFAULT_VERIFY_GREEN_INTERVAL_MS = 2000;
|
|
4
|
+
/** Inputs to `verifyGreen`. */
|
|
5
|
+
export interface VerifyGreenOptions {
|
|
6
|
+
/** The agent (routing label) to verify — passed straight to `probe`. */
|
|
7
|
+
readonly agent: string;
|
|
8
|
+
/** The version the agent must self-report to be considered green. */
|
|
9
|
+
readonly targetVersion: string;
|
|
10
|
+
/** Total wall-time budget before giving up (ms). */
|
|
11
|
+
readonly timeoutMs: number;
|
|
12
|
+
/** Poll interval between attempts (ms; default `DEFAULT_VERIFY_GREEN_INTERVAL_MS`). */
|
|
13
|
+
readonly intervalMs?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Injectable seams for `verifyGreen` — keeps it a pure function of its inputs
|
|
17
|
+
* (no clock, no network, no timers baked in), so it's unit-testable with a fake
|
|
18
|
+
* probe sequence + a virtual clock.
|
|
19
|
+
*/
|
|
20
|
+
export interface VerifyGreenDeps {
|
|
21
|
+
/**
|
|
22
|
+
* Re-resolve the agent's CURRENT registry endpoint and probe its `/health`.
|
|
23
|
+
* Returns the parsed body, or `null` when unreachable (offline / mid-restart /
|
|
24
|
+
* endpoint not yet re-advertised). MUST re-read the registry each call so a
|
|
25
|
+
* restart-self port churn is picked up (DR-037 Decision 5).
|
|
26
|
+
*/
|
|
27
|
+
readonly probe: (agent: string) => Promise<HealthResponse | null>;
|
|
28
|
+
/** Sleep `ms` between poll attempts (injected so tests don't wait). */
|
|
29
|
+
readonly sleep: (ms: number) => Promise<void>;
|
|
30
|
+
/** Monotonic-ish clock in ms (injected so tests drive the timeout). */
|
|
31
|
+
readonly now: () => number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The terminal outcome of a `verifyGreen` poll.
|
|
35
|
+
*
|
|
36
|
+
* - `ok: true` — the agent self-reported the target version; `version` echoes it.
|
|
37
|
+
* - `ok: false` + `reason: 'wrong-version'` — the agent was REACHABLE within the
|
|
38
|
+
* budget but never at the target (`lastVersion` is the last version seen, or
|
|
39
|
+
* `null` if a reachable body carried no version). The common mid-restart case
|
|
40
|
+
* that never converges (bad pin / stuck old process).
|
|
41
|
+
* - `ok: false` + `reason: 'unreachable'` — the agent was NEVER reachable within
|
|
42
|
+
* the budget (every probe returned `null`). The dead / never-relaunched case.
|
|
43
|
+
*/
|
|
44
|
+
export type VerifyGreenResult = {
|
|
45
|
+
readonly ok: true;
|
|
46
|
+
readonly version: string;
|
|
47
|
+
} | {
|
|
48
|
+
readonly ok: false;
|
|
49
|
+
readonly reason: 'wrong-version' | 'unreachable';
|
|
50
|
+
readonly lastVersion: string | null;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Poll `deps.probe(agent)` until the agent self-reports `targetVersion`
|
|
54
|
+
* (via `compareSemver === 0`) or the `timeoutMs` budget is exhausted.
|
|
55
|
+
*
|
|
56
|
+
* Guarantees at least one probe (even with `timeoutMs === 0`), then checks the
|
|
57
|
+
* elapsed budget BEFORE each `sleep`, so the loop always terminates. The
|
|
58
|
+
* version comparison uses `compareSemver` (macf-core, macf#424) so a leading
|
|
59
|
+
* `v` / normalization is handled consistently with the rest of the fleet.
|
|
60
|
+
*/
|
|
61
|
+
export declare function verifyGreen(opts: VerifyGreenOptions, deps: VerifyGreenDeps): Promise<VerifyGreenResult>;
|
|
62
|
+
//# sourceMappingURL=verify-green.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-green.d.ts","sourceRoot":"","sources":["../src/verify-green.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,2DAA2D;AAC3D,eAAO,MAAM,gCAAgC,OAAO,CAAC;AAErD,+BAA+B;AAC/B,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uFAAuF;IACvF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAClE,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,uEAAuE;IACvE,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC/C;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAAC;IACjD,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC,CAAC;AAEN;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,iBAAiB,CAAC,CAyB5B"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `verifyGreen` — the shared post-restart health-and-version gate (DR-037
|
|
3
|
+
* Decision 5). Both the rolling-upgrade sequencer and the reconcile loop need it:
|
|
4
|
+
* after a `driver.restart(agent)`, confirm the agent came back REACHABLE at the
|
|
5
|
+
* TARGET version before proceeding to the next agent (upgrade) or clearing the
|
|
6
|
+
* heal (reconcile).
|
|
7
|
+
*
|
|
8
|
+
* Pure + runtime-agnostic (macf-core, DR-037 OQ2). It takes an injectable
|
|
9
|
+
* "re-resolve + probe" callback and polls-with-timeout. The **re-resolve is the
|
|
10
|
+
* caller's job, inside `probe`** — `restart-self` relaunches with a NEW port +
|
|
11
|
+
* instance_id (DR-037 Decision 5), so a correct `probe(agent)` MUST re-read the
|
|
12
|
+
* registry for the FRESH endpoint on each call and never reuse the dead
|
|
13
|
+
* pre-restart `host:port` (a silent-fallback trap: a stale endpoint returns
|
|
14
|
+
* `null` forever → a false "not-green"). `verifyGreen` itself only polls that
|
|
15
|
+
* callback + applies the version check via macf-core's `compareSemver`.
|
|
16
|
+
*/
|
|
17
|
+
import { compareSemver } from './semver.js';
|
|
18
|
+
/** Default poll interval between `probe` attempts (ms). */
|
|
19
|
+
export const DEFAULT_VERIFY_GREEN_INTERVAL_MS = 2000;
|
|
20
|
+
/**
|
|
21
|
+
* Poll `deps.probe(agent)` until the agent self-reports `targetVersion`
|
|
22
|
+
* (via `compareSemver === 0`) or the `timeoutMs` budget is exhausted.
|
|
23
|
+
*
|
|
24
|
+
* Guarantees at least one probe (even with `timeoutMs === 0`), then checks the
|
|
25
|
+
* elapsed budget BEFORE each `sleep`, so the loop always terminates. The
|
|
26
|
+
* version comparison uses `compareSemver` (macf-core, macf#424) so a leading
|
|
27
|
+
* `v` / normalization is handled consistently with the rest of the fleet.
|
|
28
|
+
*/
|
|
29
|
+
export async function verifyGreen(opts, deps) {
|
|
30
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_VERIFY_GREEN_INTERVAL_MS;
|
|
31
|
+
const start = deps.now();
|
|
32
|
+
let reachedOnce = false;
|
|
33
|
+
let lastVersion = null;
|
|
34
|
+
for (;;) {
|
|
35
|
+
const health = await deps.probe(opts.agent);
|
|
36
|
+
if (health) {
|
|
37
|
+
reachedOnce = true;
|
|
38
|
+
lastVersion = health.version ?? null;
|
|
39
|
+
if (lastVersion !== null && compareSemver(lastVersion, opts.targetVersion) === 0) {
|
|
40
|
+
return { ok: true, version: lastVersion };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (deps.now() - start >= opts.timeoutMs) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
reason: reachedOnce ? 'wrong-version' : 'unreachable',
|
|
47
|
+
lastVersion,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
await deps.sleep(intervalMs);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=verify-green.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-green.js","sourceRoot":"","sources":["../src/verify-green.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,2DAA2D;AAC3D,MAAM,CAAC,MAAM,gCAAgC,GAAG,IAAI,CAAC;AAoDrD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAwB,EACxB,IAAqB;IAErB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,gCAAgC,CAAC;IACvE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,WAAW,GAAkB,IAAI,CAAC;IAEtC,SAAS,CAAC;QACR,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,IAAI,MAAM,EAAE,CAAC;YACX,WAAW,GAAG,IAAI,CAAC;YACnB,WAAW,GAAG,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC;YACrC,IAAI,WAAW,KAAK,IAAI,IAAI,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;gBACjF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;YAC5C,CAAC;QACH,CAAC;QAED,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACzC,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,aAAa;gBACrD,WAAW;aACZ,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groundnuty/macf-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.46",
|
|
4
4
|
"description": "Shared internals consumed by the MACF CLI and channel-server packages. Not intended for direct external use — will be marked deprecated-internal on first npm publish per DR-022 Amendment A.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|