@groundnuty/macf-channel-server 0.2.37 → 0.2.39
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/collision.d.ts +10 -1
- package/dist/collision.d.ts.map +1 -1
- package/dist/collision.js +89 -3
- package/dist/collision.js.map +1 -1
- package/dist/comms-ledger.d.ts +77 -0
- package/dist/comms-ledger.d.ts.map +1 -1
- package/dist/comms-ledger.js +133 -1
- package/dist/comms-ledger.js.map +1 -1
- package/dist/health.d.ts +106 -2
- package/dist/health.d.ts.map +1 -1
- package/dist/health.js +240 -2
- package/dist/health.js.map +1 -1
- package/dist/https.d.ts +31 -0
- package/dist/https.d.ts.map +1 -1
- package/dist/https.js +54 -1
- package/dist/https.js.map +1 -1
- package/dist/notify-peer.d.ts.map +1 -1
- package/dist/notify-peer.js +20 -5
- package/dist/notify-peer.js.map +1 -1
- package/dist/registry-heartbeat.d.ts +53 -0
- package/dist/registry-heartbeat.d.ts.map +1 -0
- package/dist/registry-heartbeat.js +102 -0
- package/dist/registry-heartbeat.js.map +1 -0
- package/dist/server.js +58 -10
- package/dist/server.js.map +1 -1
- package/dist/shutdown.d.ts +23 -1
- package/dist/shutdown.d.ts.map +1 -1
- package/dist/shutdown.js +61 -3
- package/dist/shutdown.js.map +1 -1
- package/package.json +2 -2
package/dist/collision.d.ts
CHANGED
|
@@ -45,7 +45,8 @@ export type CollisionResult = {
|
|
|
45
45
|
* squatter that motivated this), so a versioned incoming takes it over.
|
|
46
46
|
*
|
|
47
47
|
* Quadrant (incoming × existing-alive), assuming the existing peer answers
|
|
48
|
-
* `/health` (dead
|
|
48
|
+
* `/health` on every re-confirm ping (dead, OR alive-then-dead flicker per the
|
|
49
|
+
* just-killed-port race groundnuty/macf#553 → takeover regardless):
|
|
49
50
|
*
|
|
50
51
|
* incoming versioned, existing unversioned → takeover (takeover_unversioned_existing)
|
|
51
52
|
* incoming versioned, existing older → takeover (takeover_newer_version)
|
|
@@ -63,6 +64,14 @@ export type CollisionResult = {
|
|
|
63
64
|
* over the existing ping→decide→register sequence — two racing newer instances
|
|
64
65
|
* degrade to the same race the current `variable_exists` check already has.
|
|
65
66
|
*
|
|
67
|
+
* Complementary staleness signal (DR-031, groundnuty/macf#568): when the quadrant
|
|
68
|
+
* above would ABORT against a /health-answering peer but that peer's registry
|
|
69
|
+
* `last_heartbeat` has aged past the TTL, the entry is taken over anyway — the
|
|
70
|
+
* stale heartbeat is a SECOND, version-independent liveness signal that resolves
|
|
71
|
+
* the just-killed-port ambiguity (#553) toward "dead". It is purely additive: it
|
|
72
|
+
* only ever turns a would-abort into a takeover, never the reverse, and a fresh or
|
|
73
|
+
* absent heartbeat leaves the quadrant's decision exactly as-is.
|
|
74
|
+
*
|
|
66
75
|
* @param incomingVersion this instance's channel-server version (PACKAGE_VERSION).
|
|
67
76
|
*/
|
|
68
77
|
export declare function checkCollision(name: string, registry: Registry, certPaths: {
|
package/dist/collision.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collision.d.ts","sourceRoot":"","sources":["../src/collision.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"collision.d.ts","sourceRoot":"","sources":["../src/collision.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAwD,MAAM,uBAAuB,CAAC;AAKxG,qBAAa,cAAe,SAAQ,SAAS;gBAC/B,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAQrD;AAED;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,SAAS;gBAClC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,IAAI;CAYpD;AA2BD;;0EAE0E;AAC1E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AA2GD,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAC/B;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,GAC7D;IAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE;IACT,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B,EACD,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,CAAC,CAmH1B"}
|
package/dist/collision.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { request } from 'node:https';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
|
-
import { MacfError, compareSemver } from '@groundnuty/macf-core';
|
|
3
|
+
import { MacfError, compareSemver, isStaleEntry, DEFAULT_REGISTRY_TTL_MS } from '@groundnuty/macf-core';
|
|
4
4
|
/** Matches a parseable `x.y.z` (optional leading `v`) version string. */
|
|
5
5
|
const VERSION_PATTERN = /^v?\d+\.\d+\.\d+$/;
|
|
6
6
|
export class CollisionError extends MacfError {
|
|
@@ -30,6 +30,26 @@ export class RegisterRaceError extends MacfError {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
const HEALTH_PING_TIMEOUT_MS = 5000;
|
|
33
|
+
/**
|
|
34
|
+
* Liveness re-confirmation against the just-killed-port race (groundnuty/macf#553).
|
|
35
|
+
*
|
|
36
|
+
* A killed channel server's listening socket can momentarily still accept a
|
|
37
|
+
* TCP/TLS connection and answer ONE `/health` 2xx during the narrow window
|
|
38
|
+
* before the OS finishes releasing the port. Trusting that single 2xx
|
|
39
|
+
* mis-classifies a just-killed prior instance as `alive`, and the #424 version
|
|
40
|
+
* quadrant then aborts the restart against a doomed peer — stranding the slot
|
|
41
|
+
* until a manual registry delete.
|
|
42
|
+
*
|
|
43
|
+
* To be robust we require HEALTH_CONFIRM_ATTEMPTS *consecutive* 2xx answers
|
|
44
|
+
* (HEALTH_CONFIRM_DELAY_MS apart). Any failed attempt short-circuits to dead →
|
|
45
|
+
* the caller takes over the slot. A genuinely-live peer answers every attempt →
|
|
46
|
+
* still alive → still aborts (no groundnuty/macf#424 regression). Bounded: at
|
|
47
|
+
* most HEALTH_CONFIRM_ATTEMPTS pings and (HEALTH_CONFIRM_ATTEMPTS - 1) delays of
|
|
48
|
+
* added startup latency, paid only when the first ping is alive.
|
|
49
|
+
*/
|
|
50
|
+
const HEALTH_CONFIRM_ATTEMPTS = 2;
|
|
51
|
+
const HEALTH_CONFIRM_DELAY_MS = 300;
|
|
52
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
33
53
|
/**
|
|
34
54
|
* Ping an agent's /health endpoint via mTLS.
|
|
35
55
|
* Returns `{ alive, version }`: alive iff the agent responds 2xx; version is
|
|
@@ -100,6 +120,26 @@ function pingHealth(host, port, caCertPath, agentCertPath, agentKeyPath, timeout
|
|
|
100
120
|
req.end();
|
|
101
121
|
});
|
|
102
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Classify the existing peer's liveness, robust to the just-killed-port race
|
|
125
|
+
* (groundnuty/macf#553). Pings `/health` up to HEALTH_CONFIRM_ATTEMPTS times,
|
|
126
|
+
* waiting HEALTH_CONFIRM_DELAY_MS between attempts. The FIRST non-alive answer
|
|
127
|
+
* short-circuits to dead (`{ alive:false, version:null }`), so a flickering
|
|
128
|
+
* just-killed peer is treated as dead → the caller takes over the slot. Only a
|
|
129
|
+
* peer that answers 2xx on EVERY attempt is classified alive; the returned
|
|
130
|
+
* version is that of the final (still-answering) ping, feeding the #424 quadrant.
|
|
131
|
+
*/
|
|
132
|
+
async function confirmLiveness(host, port, caCertPath, agentCertPath, agentKeyPath) {
|
|
133
|
+
let result = { alive: false, version: null };
|
|
134
|
+
for (let attempt = 0; attempt < HEALTH_CONFIRM_ATTEMPTS; attempt += 1) {
|
|
135
|
+
if (attempt > 0)
|
|
136
|
+
await sleep(HEALTH_CONFIRM_DELAY_MS);
|
|
137
|
+
result = await pingHealth(host, port, caCertPath, agentCertPath, agentKeyPath);
|
|
138
|
+
if (!result.alive)
|
|
139
|
+
return { alive: false, version: null };
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
103
143
|
/**
|
|
104
144
|
* Check if an agent is already registered and alive.
|
|
105
145
|
*
|
|
@@ -114,7 +154,8 @@ function pingHealth(host, port, caCertPath, agentCertPath, agentKeyPath, timeout
|
|
|
114
154
|
* squatter that motivated this), so a versioned incoming takes it over.
|
|
115
155
|
*
|
|
116
156
|
* Quadrant (incoming × existing-alive), assuming the existing peer answers
|
|
117
|
-
* `/health` (dead
|
|
157
|
+
* `/health` on every re-confirm ping (dead, OR alive-then-dead flicker per the
|
|
158
|
+
* just-killed-port race groundnuty/macf#553 → takeover regardless):
|
|
118
159
|
*
|
|
119
160
|
* incoming versioned, existing unversioned → takeover (takeover_unversioned_existing)
|
|
120
161
|
* incoming versioned, existing older → takeover (takeover_newer_version)
|
|
@@ -132,6 +173,14 @@ function pingHealth(host, port, caCertPath, agentCertPath, agentKeyPath, timeout
|
|
|
132
173
|
* over the existing ping→decide→register sequence — two racing newer instances
|
|
133
174
|
* degrade to the same race the current `variable_exists` check already has.
|
|
134
175
|
*
|
|
176
|
+
* Complementary staleness signal (DR-031, groundnuty/macf#568): when the quadrant
|
|
177
|
+
* above would ABORT against a /health-answering peer but that peer's registry
|
|
178
|
+
* `last_heartbeat` has aged past the TTL, the entry is taken over anyway — the
|
|
179
|
+
* stale heartbeat is a SECOND, version-independent liveness signal that resolves
|
|
180
|
+
* the just-killed-port ambiguity (#553) toward "dead". It is purely additive: it
|
|
181
|
+
* only ever turns a would-abort into a takeover, never the reverse, and a fresh or
|
|
182
|
+
* absent heartbeat leaves the quadrant's decision exactly as-is.
|
|
183
|
+
*
|
|
135
184
|
* @param incomingVersion this instance's channel-server version (PACKAGE_VERSION).
|
|
136
185
|
*/
|
|
137
186
|
export async function checkCollision(name, registry, certPaths, incomingVersion, logger) {
|
|
@@ -147,7 +196,11 @@ export async function checkCollision(name, registry, certPaths, incomingVersion,
|
|
|
147
196
|
port: existing.port,
|
|
148
197
|
instance_id: existing.instance_id,
|
|
149
198
|
});
|
|
150
|
-
|
|
199
|
+
// Liveness is re-confirmed across HEALTH_CONFIRM_ATTEMPTS pings to defeat the
|
|
200
|
+
// just-killed-port race (groundnuty/macf#553): a single momentary 2xx from a
|
|
201
|
+
// dying prior instance must NOT count as alive. A confirmed-live peer then
|
|
202
|
+
// flows into the unchanged #424 version quadrant below.
|
|
203
|
+
const { alive, version: existingVersion } = await confirmLiveness(existing.host, existing.port, certPaths.caCertPath, certPaths.agentCertPath, certPaths.agentKeyPath);
|
|
151
204
|
if (alive) {
|
|
152
205
|
const versionTakeoverDisabled = process.env['MACF_NO_VERSION_TAKEOVER'] === '1';
|
|
153
206
|
const incomingVersioned = VERSION_PATTERN.test(incomingVersion);
|
|
@@ -194,6 +247,39 @@ export async function checkCollision(name, registry, certPaths, incomingVersion,
|
|
|
194
247
|
});
|
|
195
248
|
if (takeover)
|
|
196
249
|
return { action: 'takeover', previous: existing };
|
|
250
|
+
// DR-031 (groundnuty/macf#568) — COMPLEMENTARY staleness override. PURELY
|
|
251
|
+
// ADDITIVE and ONE-DIRECTIONAL: it can only turn a would-ABORT into a
|
|
252
|
+
// takeover, never the reverse. The #424 version quadrant + #553 confirmLiveness
|
|
253
|
+
// above are byte-for-byte unchanged; we only reach here when that quadrant
|
|
254
|
+
// already decided to ABORT against a peer that answered /health on EVERY
|
|
255
|
+
// confirm ping. The registry heartbeat (#589) re-stamps `last_heartbeat` on a
|
|
256
|
+
// coarse cadence, so an entry whose heartbeat aged past the TTL is almost
|
|
257
|
+
// certainly a DEAD instance whose freed/recycled port merely still answers (the
|
|
258
|
+
// #553 just-killed-port race, or a zombie squatting the port) — the stale
|
|
259
|
+
// signal resolves that ambiguity toward "dead → take over", exactly as the
|
|
260
|
+
// dead-`/health` path below already does (which likewise ignores the version
|
|
261
|
+
// policy). Safety: this can ONLY add a takeover, never block one — a FRESH
|
|
262
|
+
// heartbeat leaves the abort exactly as the quadrant decided, and an ABSENT or
|
|
263
|
+
// unparseable `last_heartbeat` is NEVER stale (`isStaleEntry` returns false), so
|
|
264
|
+
// pre-DR-031 entries and heartbeat-disabled-but-live instances take the
|
|
265
|
+
// unchanged path. A genuinely-live instance keeps its heartbeat fresh (it beats
|
|
266
|
+
// every cadence) and registration itself writes no heartbeat — so the only way
|
|
267
|
+
// to be stale here is to have beaten once and then stopped (the ungraceful
|
|
268
|
+
// death this backstops). The TTL slack (3× the 5-min cadence) prevents a
|
|
269
|
+
// healthy-but-briefly-unlucky instance from being judged stale.
|
|
270
|
+
if (isStaleEntry(existing, DEFAULT_REGISTRY_TTL_MS, Date.now())) {
|
|
271
|
+
logger.warn('collision_check', {
|
|
272
|
+
result: 'takeover_stale_heartbeat',
|
|
273
|
+
agent: name,
|
|
274
|
+
prior_basis: basis,
|
|
275
|
+
incoming_version: incomingVersion,
|
|
276
|
+
existing_version: existingVersion,
|
|
277
|
+
last_heartbeat: existing.last_heartbeat ?? null,
|
|
278
|
+
host: existing.host,
|
|
279
|
+
port: existing.port,
|
|
280
|
+
});
|
|
281
|
+
return { action: 'takeover', previous: existing };
|
|
282
|
+
}
|
|
197
283
|
return { action: 'abort', existing };
|
|
198
284
|
}
|
|
199
285
|
logger.info('collision_check', {
|
package/dist/collision.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collision.js","sourceRoot":"","sources":["../src/collision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"collision.js","sourceRoot":"","sources":["../src/collision.ts"],"names":[],"mappings":"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"}
|
package/dist/comms-ledger.d.ts
CHANGED
|
@@ -184,6 +184,83 @@ export declare function backfillProcessed(edges: readonly CommsLedgerEdge[], rec
|
|
|
184
184
|
* read, not the authoritative write.
|
|
185
185
|
*/
|
|
186
186
|
export declare function mergeLedgers(contents: readonly string[]): CommsLedgerEdge[];
|
|
187
|
+
/** Receipt-sink filename (sibling of `channel.log`); the local turn-receipt log. */
|
|
188
|
+
export declare const RECEIPT_SINK_FILENAME = "processed-receipts.jsonl";
|
|
189
|
+
/**
|
|
190
|
+
* Derive the receipt-sink path from the channel-server's `logPath` (`MACF_LOG_PATH`),
|
|
191
|
+
* as a sibling file in the same `.macf/logs/` directory — parallel to
|
|
192
|
+
* `ledgerPathFromLog`. `undefined` when no log path is configured (then
|
|
193
|
+
* `last_processed` is null: there is no sink to read). This is the SAME path the
|
|
194
|
+
* `emit-turn-receipt.sh` hook writes to (`$(dirname MACF_LOG_PATH)/<filename>`).
|
|
195
|
+
*/
|
|
196
|
+
export declare function receiptSinkPathFromLog(logPath: string | undefined): string | undefined;
|
|
197
|
+
/**
|
|
198
|
+
* One local turn-receipt line, written by `emit-turn-receipt.sh` (one per routed
|
|
199
|
+
* marker the agent processed in a turn). The compact wire form is exactly
|
|
200
|
+
* `{"ts":<epoch-ms>,"run_id":"<run_id>","agent":"<agent>"}`.
|
|
201
|
+
*/
|
|
202
|
+
export declare const ProcessedReceiptSchema: z.ZodObject<{
|
|
203
|
+
ts: z.ZodNumber;
|
|
204
|
+
run_id: z.ZodString;
|
|
205
|
+
agent: z.ZodString;
|
|
206
|
+
}, z.core.$strip>;
|
|
207
|
+
export type ProcessedReceipt = z.infer<typeof ProcessedReceiptSchema>;
|
|
208
|
+
/** The `/health` `last_processed` self-report shape (DR-030 passive-Processed tier). */
|
|
209
|
+
export interface LastProcessedReport {
|
|
210
|
+
/** ISO-8601 timestamp of the most-recent turn-receipt (null if none/unparseable). */
|
|
211
|
+
readonly at: string | null;
|
|
212
|
+
/**
|
|
213
|
+
* GitHub anchor (owner/repo#N). Always `null` for the receipt sink — a turn
|
|
214
|
+
* receipt carries no github_anchor. The field stays in the shape for back-compat
|
|
215
|
+
* with the schema (and a future ledger-join view that could populate it).
|
|
216
|
+
*/
|
|
217
|
+
readonly anchor: string | null;
|
|
218
|
+
/** now − ts, clamped ≥ 0; null when `at` is absent or `ts` is non-finite. */
|
|
219
|
+
readonly age_ms: number | null;
|
|
220
|
+
/**
|
|
221
|
+
* The receipt's `run_id` — THE `--inject` CONTRACT. An injector routes a prompt
|
|
222
|
+
* carrying a known `run_id` in its `[macf-route:<run_id>:<agent>]` marker, lets
|
|
223
|
+
* the agent process it in a turn, then reads `/health` and matches this
|
|
224
|
+
* `correlation_token` to confirm ITS OWN message was processed.
|
|
225
|
+
*/
|
|
226
|
+
readonly correlation_token: string | null;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Parse a receipt-sink file's text into receipts, tolerantly: blank + malformed
|
|
230
|
+
* lines (incl. a torn final line mid-append) are skipped, never fatal — a corrupt
|
|
231
|
+
* line must not blind the read. Same posture as `mergeLedgers`.
|
|
232
|
+
*/
|
|
233
|
+
export declare function parseReceipts(content: string): ProcessedReceipt[];
|
|
234
|
+
/**
|
|
235
|
+
* Select the most-recent receipt (max `ts`), or null when there are none.
|
|
236
|
+
* Single-pass max-`ts` (not a sort) — cheap on the infrequent `/health` path. On
|
|
237
|
+
* equal `ts`, the first-seen wins (a benign tie). Max-`ts` (rather than just the
|
|
238
|
+
* last line) is robust to out-of-order lines under clock skew.
|
|
239
|
+
*/
|
|
240
|
+
export declare function selectLastReceipt(receipts: readonly ProcessedReceipt[]): ProcessedReceipt | null;
|
|
241
|
+
/**
|
|
242
|
+
* Map a selected receipt (or null) into the `last_processed` `/health` shape.
|
|
243
|
+
* `at` ← ISO-8601 of the receipt's epoch-ms `ts`; `correlation_token` ← `run_id`
|
|
244
|
+
* (the `--inject` contract); `anchor` ← null (the sink carries no github_anchor);
|
|
245
|
+
* `age_ms` ← now − ts, clamped ≥ 0. A non-finite `ts` yields `at: null` + `age_ms: null`.
|
|
246
|
+
*/
|
|
247
|
+
export declare function mapReceiptToLastProcessed(receipt: ProcessedReceipt | null, now: number): LastProcessedReport | null;
|
|
248
|
+
/**
|
|
249
|
+
* Read the agent's most-recent turn-receipt from the receipt sink and map it to
|
|
250
|
+
* the `/health` `last_processed` self-report. NEVER throws — an unset/missing/
|
|
251
|
+
* unreadable/garbage sink (or any read/parse error) degrades to `null`, so it can
|
|
252
|
+
* never break `/health`. Inject `readReceipts` in tests to skip the file entirely.
|
|
253
|
+
*
|
|
254
|
+
* Perf: reads the whole jsonl on each call. A `/health` ping is infrequent and the
|
|
255
|
+
* sink is operator-rotated like the ledger (see `ROTATION_POLICY`), so a bounded
|
|
256
|
+
* full read is fine; a tail-scan optimisation is possible if it ever grows unbounded.
|
|
257
|
+
*/
|
|
258
|
+
export declare function readLastProcessed(opts: {
|
|
259
|
+
readonly logPath?: string | undefined;
|
|
260
|
+
readonly receiptSinkPath?: string | undefined;
|
|
261
|
+
readonly now: number;
|
|
262
|
+
readonly readReceipts?: () => readonly ProcessedReceipt[];
|
|
263
|
+
}): LastProcessedReport | null;
|
|
187
264
|
/**
|
|
188
265
|
* Rotation/retention policy (DR-025 §"Costs", "no silent caps"):
|
|
189
266
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comms-ledger.d.ts","sourceRoot":"","sources":["../src/comms-ledger.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;EAU3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,kBAAkB;;;EAAkC,CAAC;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,oBAAoB;;;EAA2B,CAAC;AAC7D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAahC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,2DAA2D;AAC3D,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,GAAG,GAAE,MAA2B,GAC/B,MAAM,CAUR;AAED,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGjF;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;OASG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;IACrD,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AASD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,oDAAoD;IACpD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C,GAAG,WAAW,CAkBd;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,eAAe,EAAE,EACjC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EAChC,KAAK,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,MAAM,GACvC,eAAe,EAAE,CAInB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,eAAe,EAAE,CAW3E;
|
|
1
|
+
{"version":3,"file":"comms-ledger.d.ts","sourceRoot":"","sources":["../src/comms-ledger.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;EAU3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,kBAAkB;;;EAAkC,CAAC;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,oBAAoB;;;EAA2B,CAAC;AAC7D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAahC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,2DAA2D;AAC3D,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,GAAG,GAAE,MAA2B,GAC/B,MAAM,CAUR;AAED,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGjF;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;OASG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;IACrD,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AASD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,oDAAoD;IACpD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C,GAAG,WAAW,CAkBd;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,eAAe,EAAE,EACjC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EAChC,KAAK,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,MAAM,GACvC,eAAe,EAAE,CAInB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,eAAe,EAAE,CAW3E;AAmCD,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,6BAA6B,CAAC;AAEhE;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGtF;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB;;;;iBAOjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,wFAAwF;AACxF,MAAM,WAAW,mBAAmB;IAClC,qFAAqF;IACrF,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAejE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GACpC,gBAAgB,GAAG,IAAI,CAMzB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gBAAgB,GAAG,IAAI,EAChC,GAAG,EAAE,MAAM,GACV,mBAAmB,GAAG,IAAI,CAS5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,SAAS,gBAAgB,EAAE,CAAC;CAC3D,GAAG,mBAAmB,GAAG,IAAI,CAc7B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAG,4CAAqD,CAAC"}
|
package/dist/comms-ledger.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
/**
|
|
@@ -210,6 +210,138 @@ function safeJson(line) {
|
|
|
210
210
|
return '{}';
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
+
// --- DR-030 phase-1 (groundnuty/macf#568): last_processed via the RECEIPT SINK ---
|
|
214
|
+
//
|
|
215
|
+
// The `/health` `last_processed` field is the agent's passive-Processed
|
|
216
|
+
// self-report: "I processed a routed coordination message at time T", proven
|
|
217
|
+
// LOCALLY with no synthetic injection (DR-030 §3 "Passive-Processed tier").
|
|
218
|
+
//
|
|
219
|
+
// THE LOCAL "PROCESSED" SOURCE IS THE RECEIPT SINK, not the comms-ledger.
|
|
220
|
+
// When the router injects a prompt it appends a `[macf-route:<run_id>:<agent>]`
|
|
221
|
+
// marker (macf-actions#45); the `emit-turn-receipt.sh` UserPromptSubmit hook
|
|
222
|
+
// fires when that prompt is submitted AS A TURN and — alongside the OTLP
|
|
223
|
+
// `turn_processed` span (which feeds Tempo + the #444 reconciler) — appends one
|
|
224
|
+
// receipt line per marker to `processed-receipts.jsonl` (a sibling of
|
|
225
|
+
// `channel.log`, see `receiptSinkPathFromLog`). THAT local append is the proof a
|
|
226
|
+
// turn happened, and it is what this reader surfaces — making the passive tier a
|
|
227
|
+
// real LOCAL self-report rather than a Tempo-only one (the keystone of #568).
|
|
228
|
+
//
|
|
229
|
+
// Why NOT the comms-ledger's `processed` field: every edge site writes
|
|
230
|
+
// `processed: null` — the delivery≠turn join is a DERIVED view (`backfillProcessed`
|
|
231
|
+
// / the #444 reconciler), never written back to the append-only ledger (DR-025).
|
|
232
|
+
// So a `recv` + `processed:true` edge never exists on disk and a ledger scan is
|
|
233
|
+
// always null. The ledger stays the authoritative edge record; this sink is the
|
|
234
|
+
// separate, purpose-built local "I took a turn" proof.
|
|
235
|
+
/** Receipt-sink filename (sibling of `channel.log`); the local turn-receipt log. */
|
|
236
|
+
export const RECEIPT_SINK_FILENAME = 'processed-receipts.jsonl';
|
|
237
|
+
/**
|
|
238
|
+
* Derive the receipt-sink path from the channel-server's `logPath` (`MACF_LOG_PATH`),
|
|
239
|
+
* as a sibling file in the same `.macf/logs/` directory — parallel to
|
|
240
|
+
* `ledgerPathFromLog`. `undefined` when no log path is configured (then
|
|
241
|
+
* `last_processed` is null: there is no sink to read). This is the SAME path the
|
|
242
|
+
* `emit-turn-receipt.sh` hook writes to (`$(dirname MACF_LOG_PATH)/<filename>`).
|
|
243
|
+
*/
|
|
244
|
+
export function receiptSinkPathFromLog(logPath) {
|
|
245
|
+
if (!logPath)
|
|
246
|
+
return undefined;
|
|
247
|
+
return join(dirname(logPath), RECEIPT_SINK_FILENAME);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* One local turn-receipt line, written by `emit-turn-receipt.sh` (one per routed
|
|
251
|
+
* marker the agent processed in a turn). The compact wire form is exactly
|
|
252
|
+
* `{"ts":<epoch-ms>,"run_id":"<run_id>","agent":"<agent>"}`.
|
|
253
|
+
*/
|
|
254
|
+
export const ProcessedReceiptSchema = z.object({
|
|
255
|
+
/** Epoch milliseconds at which the routed prompt was submitted as a turn. */
|
|
256
|
+
ts: z.number(),
|
|
257
|
+
/** The router run id from the `[macf-route:<run_id>:<agent>]` marker. */
|
|
258
|
+
run_id: z.string().min(1),
|
|
259
|
+
/** The kebab agent name from the marker. */
|
|
260
|
+
agent: z.string().min(1),
|
|
261
|
+
});
|
|
262
|
+
/**
|
|
263
|
+
* Parse a receipt-sink file's text into receipts, tolerantly: blank + malformed
|
|
264
|
+
* lines (incl. a torn final line mid-append) are skipped, never fatal — a corrupt
|
|
265
|
+
* line must not blind the read. Same posture as `mergeLedgers`.
|
|
266
|
+
*/
|
|
267
|
+
export function parseReceipts(content) {
|
|
268
|
+
const out = [];
|
|
269
|
+
for (const raw of content.split('\n')) {
|
|
270
|
+
const line = raw.trim();
|
|
271
|
+
if (!line)
|
|
272
|
+
continue;
|
|
273
|
+
let json;
|
|
274
|
+
try {
|
|
275
|
+
json = JSON.parse(line);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const parsed = ProcessedReceiptSchema.safeParse(json);
|
|
281
|
+
if (parsed.success)
|
|
282
|
+
out.push(parsed.data);
|
|
283
|
+
}
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Select the most-recent receipt (max `ts`), or null when there are none.
|
|
288
|
+
* Single-pass max-`ts` (not a sort) — cheap on the infrequent `/health` path. On
|
|
289
|
+
* equal `ts`, the first-seen wins (a benign tie). Max-`ts` (rather than just the
|
|
290
|
+
* last line) is robust to out-of-order lines under clock skew.
|
|
291
|
+
*/
|
|
292
|
+
export function selectLastReceipt(receipts) {
|
|
293
|
+
let best = null;
|
|
294
|
+
for (const r of receipts) {
|
|
295
|
+
if (best === null || r.ts > best.ts)
|
|
296
|
+
best = r;
|
|
297
|
+
}
|
|
298
|
+
return best;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Map a selected receipt (or null) into the `last_processed` `/health` shape.
|
|
302
|
+
* `at` ← ISO-8601 of the receipt's epoch-ms `ts`; `correlation_token` ← `run_id`
|
|
303
|
+
* (the `--inject` contract); `anchor` ← null (the sink carries no github_anchor);
|
|
304
|
+
* `age_ms` ← now − ts, clamped ≥ 0. A non-finite `ts` yields `at: null` + `age_ms: null`.
|
|
305
|
+
*/
|
|
306
|
+
export function mapReceiptToLastProcessed(receipt, now) {
|
|
307
|
+
if (receipt === null)
|
|
308
|
+
return null;
|
|
309
|
+
const finite = Number.isFinite(receipt.ts);
|
|
310
|
+
return {
|
|
311
|
+
at: finite ? new Date(receipt.ts).toISOString() : null,
|
|
312
|
+
anchor: null,
|
|
313
|
+
age_ms: finite ? Math.max(0, Math.floor(now - receipt.ts)) : null,
|
|
314
|
+
correlation_token: receipt.run_id,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Read the agent's most-recent turn-receipt from the receipt sink and map it to
|
|
319
|
+
* the `/health` `last_processed` self-report. NEVER throws — an unset/missing/
|
|
320
|
+
* unreadable/garbage sink (or any read/parse error) degrades to `null`, so it can
|
|
321
|
+
* never break `/health`. Inject `readReceipts` in tests to skip the file entirely.
|
|
322
|
+
*
|
|
323
|
+
* Perf: reads the whole jsonl on each call. A `/health` ping is infrequent and the
|
|
324
|
+
* sink is operator-rotated like the ledger (see `ROTATION_POLICY`), so a bounded
|
|
325
|
+
* full read is fine; a tail-scan optimisation is possible if it ever grows unbounded.
|
|
326
|
+
*/
|
|
327
|
+
export function readLastProcessed(opts) {
|
|
328
|
+
try {
|
|
329
|
+
let receipts;
|
|
330
|
+
if (opts.readReceipts) {
|
|
331
|
+
receipts = opts.readReceipts();
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
const path = opts.receiptSinkPath ?? receiptSinkPathFromLog(opts.logPath);
|
|
335
|
+
if (!path || !existsSync(path))
|
|
336
|
+
return null;
|
|
337
|
+
receipts = parseReceipts(readFileSync(path, 'utf8'));
|
|
338
|
+
}
|
|
339
|
+
return mapReceiptToLastProcessed(selectLastReceipt(receipts), opts.now);
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
213
345
|
/**
|
|
214
346
|
* Rotation/retention policy (DR-025 §"Costs", "no silent caps"):
|
|
215
347
|
*
|
package/dist/comms-ledger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comms-ledger.js","sourceRoot":"","sources":["../src/comms-ledger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"comms-ledger.js","sourceRoot":"","sources":["../src/comms-ledger.ts"],"names":[],"mappings":"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"}
|
package/dist/health.d.ts
CHANGED
|
@@ -1,3 +1,107 @@
|
|
|
1
|
-
import type { HealthState } from '@groundnuty/macf-core';
|
|
2
|
-
|
|
1
|
+
import type { HealthResponse, HealthState } from '@groundnuty/macf-core';
|
|
2
|
+
import { type ProcessedReceipt } from './comms-ledger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Hard version contract for the `/health` body SHAPE (DR-006 watchdog request,
|
|
5
|
+
* macf-devops-toolkit#115). A consumer asserts `health_schema_version === <known>`
|
|
6
|
+
* and refuses an unknown, so it fails LOUD on ANY breaking change to the
|
|
7
|
+
* `/health` shape — including a same-name SEMANTIC change (e.g. `last_processed.age_ms`
|
|
8
|
+
* meaning, an `at` rename) that additive-optional + presence-checks silently miss.
|
|
9
|
+
* BUMP on any breaking change to the `/health` shape; additive-optional fields do NOT
|
|
10
|
+
* bump it. Distinct from the package `version` (which bumps on every cs release).
|
|
11
|
+
*/
|
|
12
|
+
export declare const HEALTH_SCHEMA_VERSION = 1;
|
|
13
|
+
/** The DR-030 `/health` `state` object shape (non-null), derived from the schema. */
|
|
14
|
+
type TurnStateReport = NonNullable<HealthResponse['state']>;
|
|
15
|
+
/**
|
|
16
|
+
* Parse a leaf-cert PEM and return its `notAfter` as an ISO-8601 string, or
|
|
17
|
+
* `null` if the PEM can't be parsed. DR-030 §4 per-agent `cert_expiry`
|
|
18
|
+
* self-report (an expired leaf = silent off-channels; thresholds — warn <30d
|
|
19
|
+
* / crit <7d — are the consumer's call). Uses `node:crypto` (zero-dep):
|
|
20
|
+
* `@peculiar/x509` is only needed to *create* X.509, not to read one.
|
|
21
|
+
*/
|
|
22
|
+
export declare function leafCertExpiry(pem: string): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Map a parsed turn-state marker into the `/health` `state` object, computing
|
|
25
|
+
* `elapsed_ms` against `now` (so it stays fresh through a long, e.g. 20-minute,
|
|
26
|
+
* tool wait). Lenient by construction: the marker is written by an external hook,
|
|
27
|
+
* so any missing/wrong-typed field degrades to `null` rather than throwing, and a
|
|
28
|
+
* marker missing the required `status`/`turn_number` shape maps to `state: null`.
|
|
29
|
+
*
|
|
30
|
+
* elapsed_ms = status === 'busy' ? max(0, now − started_at) : 0
|
|
31
|
+
*/
|
|
32
|
+
export declare function mapTurnStateMarker(raw: unknown, now: number): TurnStateReport | null;
|
|
33
|
+
/**
|
|
34
|
+
* Compute the static parts of the `otel` self-report from `process.env`.
|
|
35
|
+
*
|
|
36
|
+
* `endpoint_is_canonical` is **env-driven by design** — we deliberately do NOT
|
|
37
|
+
* hardcode the monitoring-VM host in channel-server code (it would drift the day
|
|
38
|
+
* the fleet's canonical endpoint moves). Instead "canonical" means "the live
|
|
39
|
+
* `OTEL_EXPORTER_OTLP_ENDPOINT` equals `MACF_OTEL_ENDPOINT`", the same env that
|
|
40
|
+
* `macf init`/`update` bake as the canonical default. So: false when no endpoint
|
|
41
|
+
* is set, false when `MACF_OTEL_ENDPOINT` is unset (nothing to be canonical
|
|
42
|
+
* against), false when they differ, true only when they're set + equal.
|
|
43
|
+
*/
|
|
44
|
+
export declare function computeOtelEndpointInfo(env: NodeJS.ProcessEnv): {
|
|
45
|
+
readonly endpoint: string | null;
|
|
46
|
+
readonly endpoint_is_canonical: boolean;
|
|
47
|
+
};
|
|
48
|
+
/** Parse `host`/`port` from an OTLP endpoint URL; `null` if unparseable. */
|
|
49
|
+
export declare function parseEndpointHostPort(endpoint: string): {
|
|
50
|
+
readonly host: string;
|
|
51
|
+
readonly port: number;
|
|
52
|
+
} | null;
|
|
53
|
+
export interface OtelReachabilityProbe {
|
|
54
|
+
/** The cached reachability result (default `false` until the first probe completes). */
|
|
55
|
+
readonly get: () => boolean;
|
|
56
|
+
/** Run a single probe now and update the cache (awaitable; used by tests). */
|
|
57
|
+
readonly runOnce: () => Promise<void>;
|
|
58
|
+
/** Kick off an immediate probe + start the periodic interval (no-op if no endpoint). */
|
|
59
|
+
readonly start: () => void;
|
|
60
|
+
/** Clear the periodic interval (idempotent). */
|
|
61
|
+
readonly stop: () => void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A cached, periodic OTLP-reachability probe. `getHealth()` is synchronous and
|
|
65
|
+
* can't await a TCP connect, so reachability is computed out-of-band on an
|
|
66
|
+
* interval and read from a cached boolean. The interval is `unref()`'d so it
|
|
67
|
+
* never keeps the process alive, and is also clearable via `stop()` on shutdown.
|
|
68
|
+
* The probe is injectable for testing (no real network in unit tests).
|
|
69
|
+
*/
|
|
70
|
+
export declare function createOtelReachabilityProbe(endpoint: string | null, opts?: {
|
|
71
|
+
readonly intervalMs?: number;
|
|
72
|
+
readonly timeoutMs?: number;
|
|
73
|
+
readonly probe?: (host: string, port: number, timeoutMs: number) => Promise<boolean>;
|
|
74
|
+
}): OtelReachabilityProbe;
|
|
75
|
+
/** Optional self-report inputs for the DR-030 mesh-layer `/health` extension. */
|
|
76
|
+
export interface HealthStateOpts {
|
|
77
|
+
/** The registration instance id (registry/health staleness disambiguator). */
|
|
78
|
+
readonly instanceId?: string;
|
|
79
|
+
/** Path to the agent's leaf cert, for the `cert_expiry` self-report. */
|
|
80
|
+
readonly certPath?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Path to the turn-state marker file. Defaults to
|
|
83
|
+
* `${MACF_WORKSPACE_DIR}/.macf/turn-state.json`; injectable for testing.
|
|
84
|
+
*/
|
|
85
|
+
readonly turnStatePath?: string;
|
|
86
|
+
/** Injectable OTLP-reachability probe (testing — avoids real network). */
|
|
87
|
+
readonly otelProbe?: (host: string, port: number, timeoutMs: number) => Promise<boolean>;
|
|
88
|
+
/** Override the OTLP-reachability probe interval (testing); default 30s. */
|
|
89
|
+
readonly otelProbeIntervalMs?: number;
|
|
90
|
+
/**
|
|
91
|
+
* Channel-server log path (`MACF_LOG_PATH`); the turn-receipt sink lives at a
|
|
92
|
+
* sibling `processed-receipts.jsonl` in the same directory. Source for the
|
|
93
|
+
* DR-030 `last_processed` passive-receipt self-report (the server passes
|
|
94
|
+
* `config.logPath`). When unset (and no `processedReader`), `last_processed`
|
|
95
|
+
* is null — no sink to read.
|
|
96
|
+
*/
|
|
97
|
+
readonly logPath?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Injectable turn-receipt reader for `last_processed` (testing — avoids a real
|
|
100
|
+
* on-disk receipt sink). Takes precedence over `logPath`-derived file reads
|
|
101
|
+
* when set.
|
|
102
|
+
*/
|
|
103
|
+
readonly processedReader?: () => readonly ProcessedReceipt[];
|
|
104
|
+
}
|
|
105
|
+
export declare function createHealthState(agentName: string, agentType: string, opts?: HealthStateOpts): HealthState;
|
|
106
|
+
export {};
|
|
3
107
|
//# sourceMappingURL=health.d.ts.map
|
package/dist/health.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE7E;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,qFAAqF;AACrF,KAAK,eAAe,GAAG,WAAW,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAU5D;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMzD;AAuCD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CA6BpF;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAA;CAAE,CAO/E;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GACf;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAWzD;AAuBD,MAAM,WAAW,qBAAqB;IACpC,wFAAwF;IACxF,QAAQ,CAAC,GAAG,EAAE,MAAM,OAAO,CAAC;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,wFAAwF;IACxF,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAC3B,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,IAAI,GAAE;IACJ,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACjF,GACL,qBAAqB,CAwCvB;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzF,4EAA4E;IAC5E,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,SAAS,gBAAgB,EAAE,CAAC;CAC9D;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,eAAoB,GACzB,WAAW,CAsEb"}
|