@groundnuty/macf-channel-server 0.2.46 → 0.2.48
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 +28 -3
- package/dist/collision.d.ts.map +1 -1
- package/dist/collision.js +78 -18
- package/dist/collision.js.map +1 -1
- package/dist/delivery/driver.d.ts +45 -0
- package/dist/delivery/driver.d.ts.map +1 -0
- package/dist/delivery/driver.js +34 -0
- package/dist/delivery/driver.js.map +1 -0
- package/dist/delivery/in-memory-store.d.ts +24 -0
- package/dist/delivery/in-memory-store.d.ts.map +1 -0
- package/dist/delivery/in-memory-store.js +60 -0
- package/dist/delivery/in-memory-store.js.map +1 -0
- package/dist/delivery/inbox-drain-driver.d.ts +150 -0
- package/dist/delivery/inbox-drain-driver.d.ts.map +1 -0
- package/dist/delivery/inbox-drain-driver.js +109 -0
- package/dist/delivery/inbox-drain-driver.js.map +1 -0
- package/dist/delivery/inbox-ticker.d.ts +45 -0
- package/dist/delivery/inbox-ticker.d.ts.map +1 -0
- package/dist/delivery/inbox-ticker.js +68 -0
- package/dist/delivery/inbox-ticker.js.map +1 -0
- package/dist/delivery/inbox.d.ts +57 -0
- package/dist/delivery/inbox.d.ts.map +1 -0
- package/dist/delivery/inbox.js +19 -0
- package/dist/delivery/inbox.js.map +1 -0
- package/dist/delivery/message-id.d.ts +10 -0
- package/dist/delivery/message-id.d.ts.map +1 -0
- package/dist/delivery/message-id.js +33 -0
- package/dist/delivery/message-id.js.map +1 -0
- package/dist/delivery/outbox-ticker.d.ts +50 -0
- package/dist/delivery/outbox-ticker.d.ts.map +1 -0
- package/dist/delivery/outbox-ticker.js +49 -0
- package/dist/delivery/outbox-ticker.js.map +1 -0
- package/dist/delivery/outbox.d.ts +105 -0
- package/dist/delivery/outbox.d.ts.map +1 -0
- package/dist/delivery/outbox.js +103 -0
- package/dist/delivery/outbox.js.map +1 -0
- package/dist/https.d.ts +24 -0
- package/dist/https.d.ts.map +1 -1
- package/dist/https.js +70 -4
- package/dist/https.js.map +1 -1
- package/dist/notify-peer.d.ts +70 -0
- package/dist/notify-peer.d.ts.map +1 -1
- package/dist/notify-peer.js +142 -29
- package/dist/notify-peer.js.map +1 -1
- package/dist/server.js +100 -2
- package/dist/server.js.map +1 -1
- package/dist/shutdown.d.ts +18 -0
- package/dist/shutdown.d.ts.map +1 -1
- package/dist/shutdown.js +18 -1
- package/dist/shutdown.js.map +1 -1
- package/package.json +2 -2
package/dist/collision.d.ts
CHANGED
|
@@ -34,12 +34,18 @@ export declare class RegisterRaceError extends MacfError {
|
|
|
34
34
|
* forever.
|
|
35
35
|
*/
|
|
36
36
|
export declare const MAX_REGISTER_RETRIES = 5;
|
|
37
|
-
/** Result of a collision /health ping: liveness + the advertised version
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
/** Result of a collision /health ping: liveness + the advertised version +
|
|
38
|
+
* the RESPONDER's OWN `instance_id` (groundnuty/macf#733). `version` is null
|
|
39
|
+
* when the peer answered but carried no `version` field (a pre-#424
|
|
40
|
+
* instance) or was unreachable/unparseable. `instanceId` is null under the
|
|
41
|
+
* same conditions PLUS the pre-#733 case where a live peer's `/health` body
|
|
42
|
+
* simply predates the `instance_id` self-report — the identity check
|
|
43
|
+
* (`classifyPeer`) treats a null `instanceId` as "can't compare" and falls
|
|
44
|
+
* through to the unchanged #424 version quadrant (back-compat). */
|
|
40
45
|
export interface HealthPingResult {
|
|
41
46
|
readonly alive: boolean;
|
|
42
47
|
readonly version: string | null;
|
|
48
|
+
readonly instanceId: string | null;
|
|
43
49
|
}
|
|
44
50
|
export type CollisionResult = {
|
|
45
51
|
readonly action: 'register';
|
|
@@ -56,6 +62,25 @@ export type CollisionResult = {
|
|
|
56
62
|
* Returns the action to take: register (fresh), takeover (the slot is held by a
|
|
57
63
|
* dead OR alive-but-older instance), or abort (a same/newer live peer holds it).
|
|
58
64
|
*
|
|
65
|
+
* Identity-aware liveness (groundnuty/macf#733): "something answers 2xx on the
|
|
66
|
+
* advertised port" is NOT the same claim as "the REGISTERED owner is alive" —
|
|
67
|
+
* a lingering socket from the just-exited owner, or an unrelated stray/orphan
|
|
68
|
+
* process, can answer 2xx on the port the registry recorded, and the #424
|
|
69
|
+
* quadrant below would then read that as a same-version live peer and ABORT,
|
|
70
|
+
* stranding the slot indefinitely (the bug that repeatedly bricked devops on
|
|
71
|
+
* 0.2.47). The `/health` responder now self-reports its OWN `instance_id`
|
|
72
|
+
* (`health.ts`), so `classifyPeer` can tell "the registered owner is
|
|
73
|
+
* confirmed alive" apart from "the registered owner is gone; something else
|
|
74
|
+
* answers." When the responder's `instance_id` is present AND differs from
|
|
75
|
+
* the registry slot's `instance_id`, the registered owner's liveness is
|
|
76
|
+
* UNCONFIRMED — takeover, bypassing the #424 quadrant entirely (this is an
|
|
77
|
+
* identity question, not a version question). When the responder's
|
|
78
|
+
* `instance_id` matches the slot (the common, correct case) OR the responder
|
|
79
|
+
* doesn't self-report one (a pre-#733 peer — back-compat), the unchanged
|
|
80
|
+
* #424 version quadrant below decides. See `classifyPeer`'s body for the
|
|
81
|
+
* concurrent-newer-instance-race analysis (why this is safe to do without
|
|
82
|
+
* its own displacement logic).
|
|
83
|
+
*
|
|
59
84
|
* Version-aware takeover (groundnuty/macf#424): a same-version alive peer is
|
|
60
85
|
* still protected (alive → abort), but an alive instance running an *older*
|
|
61
86
|
* version no longer permanently squats the slot — a newer incoming displaces
|
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,EAAwD,MAAM,uBAAuB,CAAC;AAKxG,qBAAa,cAAe,SAAQ,SAAS;gBAC/B,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAQrD;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,iBAAkB,SAAQ,SAAS;gBAClC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,IAAI;CAYpD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AA2BtC
|
|
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;;;;;;;;;;;;;GAaG;AACH,qBAAa,iBAAkB,SAAQ,SAAS;gBAClC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,IAAI;CAYpD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AA2BtC;;;;;;;oEAOoE;AACpE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAmHD,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;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,CAS1B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,EACnB,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,CA+I1B"}
|
package/dist/collision.js
CHANGED
|
@@ -71,8 +71,9 @@ const HEALTH_CONFIRM_DELAY_MS = 300;
|
|
|
71
71
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
72
72
|
/**
|
|
73
73
|
* Ping an agent's /health endpoint via mTLS.
|
|
74
|
-
* Returns `{ alive, version }`: alive iff the agent responds 2xx;
|
|
75
|
-
*
|
|
74
|
+
* Returns `{ alive, version, instanceId }`: alive iff the agent responds 2xx;
|
|
75
|
+
* version + instanceId are the `version` / `instance_id` fields parsed from
|
|
76
|
+
* the JSON body (null if absent/unparseable).
|
|
76
77
|
*/
|
|
77
78
|
function pingHealth(host, port, caCertPath, agentCertPath, agentKeyPath, timeoutMs = HEALTH_PING_TIMEOUT_MS) {
|
|
78
79
|
// readFileSync on missing/unreadable cert files throws ENOENT/EACCES
|
|
@@ -92,7 +93,7 @@ function pingHealth(host, port, caCertPath, agentCertPath, agentKeyPath, timeout
|
|
|
92
93
|
key = readFileSync(agentKeyPath);
|
|
93
94
|
}
|
|
94
95
|
catch {
|
|
95
|
-
return Promise.resolve({ alive: false, version: null });
|
|
96
|
+
return Promise.resolve({ alive: false, version: null, instanceId: null });
|
|
96
97
|
}
|
|
97
98
|
return new Promise((resolve) => {
|
|
98
99
|
const req = request({
|
|
@@ -109,32 +110,37 @@ function pingHealth(host, port, caCertPath, agentCertPath, agentKeyPath, timeout
|
|
|
109
110
|
const alive = res.statusCode !== undefined && res.statusCode >= 200 && res.statusCode < 300;
|
|
110
111
|
if (!alive) {
|
|
111
112
|
res.resume(); // drain
|
|
112
|
-
resolve({ alive: false, version: null });
|
|
113
|
+
resolve({ alive: false, version: null, instanceId: null });
|
|
113
114
|
return;
|
|
114
115
|
}
|
|
115
|
-
// Alive — read the body to extract the advertised version
|
|
116
|
-
//
|
|
117
|
-
//
|
|
116
|
+
// Alive — read the body to extract the advertised version + the
|
|
117
|
+
// responder's OWN instance_id (groundnuty/macf#733). A peer that
|
|
118
|
+
// answers 2xx but carries no `version` / `instance_id` field (or an
|
|
119
|
+
// unparseable body) is treated as version=null / instanceId=null (a
|
|
120
|
+
// pre-#424 / pre-#733 instance).
|
|
118
121
|
const chunks = [];
|
|
119
122
|
res.on('data', (c) => chunks.push(c));
|
|
120
123
|
res.on('end', () => {
|
|
121
124
|
let version = null;
|
|
125
|
+
let instanceId = null;
|
|
122
126
|
try {
|
|
123
127
|
const body = JSON.parse(Buffer.concat(chunks).toString('utf-8'));
|
|
124
128
|
if (typeof body.version === 'string')
|
|
125
129
|
version = body.version;
|
|
130
|
+
if (typeof body.instance_id === 'string')
|
|
131
|
+
instanceId = body.instance_id;
|
|
126
132
|
}
|
|
127
133
|
catch {
|
|
128
|
-
// alive but unparseable → version
|
|
134
|
+
// alive but unparseable → version/instanceId stay null
|
|
129
135
|
}
|
|
130
|
-
resolve({ alive: true, version });
|
|
136
|
+
resolve({ alive: true, version, instanceId });
|
|
131
137
|
});
|
|
132
|
-
res.on('error', () => resolve({ alive: true, version: null }));
|
|
138
|
+
res.on('error', () => resolve({ alive: true, version: null, instanceId: null }));
|
|
133
139
|
});
|
|
134
|
-
req.on('error', () => resolve({ alive: false, version: null }));
|
|
140
|
+
req.on('error', () => resolve({ alive: false, version: null, instanceId: null }));
|
|
135
141
|
req.on('timeout', () => {
|
|
136
142
|
req.destroy();
|
|
137
|
-
resolve({ alive: false, version: null });
|
|
143
|
+
resolve({ alive: false, version: null, instanceId: null });
|
|
138
144
|
});
|
|
139
145
|
req.end();
|
|
140
146
|
});
|
|
@@ -149,13 +155,13 @@ function pingHealth(host, port, caCertPath, agentCertPath, agentKeyPath, timeout
|
|
|
149
155
|
* version is that of the final (still-answering) ping, feeding the #424 quadrant.
|
|
150
156
|
*/
|
|
151
157
|
async function confirmLiveness(host, port, caCertPath, agentCertPath, agentKeyPath) {
|
|
152
|
-
let result = { alive: false, version: null };
|
|
158
|
+
let result = { alive: false, version: null, instanceId: null };
|
|
153
159
|
for (let attempt = 0; attempt < HEALTH_CONFIRM_ATTEMPTS; attempt += 1) {
|
|
154
160
|
if (attempt > 0)
|
|
155
161
|
await sleep(HEALTH_CONFIRM_DELAY_MS);
|
|
156
162
|
result = await pingHealth(host, port, caCertPath, agentCertPath, agentKeyPath);
|
|
157
163
|
if (!result.alive)
|
|
158
|
-
return { alive: false, version: null };
|
|
164
|
+
return { alive: false, version: null, instanceId: null };
|
|
159
165
|
}
|
|
160
166
|
return result;
|
|
161
167
|
}
|
|
@@ -165,6 +171,25 @@ async function confirmLiveness(host, port, caCertPath, agentCertPath, agentKeyPa
|
|
|
165
171
|
* Returns the action to take: register (fresh), takeover (the slot is held by a
|
|
166
172
|
* dead OR alive-but-older instance), or abort (a same/newer live peer holds it).
|
|
167
173
|
*
|
|
174
|
+
* Identity-aware liveness (groundnuty/macf#733): "something answers 2xx on the
|
|
175
|
+
* advertised port" is NOT the same claim as "the REGISTERED owner is alive" —
|
|
176
|
+
* a lingering socket from the just-exited owner, or an unrelated stray/orphan
|
|
177
|
+
* process, can answer 2xx on the port the registry recorded, and the #424
|
|
178
|
+
* quadrant below would then read that as a same-version live peer and ABORT,
|
|
179
|
+
* stranding the slot indefinitely (the bug that repeatedly bricked devops on
|
|
180
|
+
* 0.2.47). The `/health` responder now self-reports its OWN `instance_id`
|
|
181
|
+
* (`health.ts`), so `classifyPeer` can tell "the registered owner is
|
|
182
|
+
* confirmed alive" apart from "the registered owner is gone; something else
|
|
183
|
+
* answers." When the responder's `instance_id` is present AND differs from
|
|
184
|
+
* the registry slot's `instance_id`, the registered owner's liveness is
|
|
185
|
+
* UNCONFIRMED — takeover, bypassing the #424 quadrant entirely (this is an
|
|
186
|
+
* identity question, not a version question). When the responder's
|
|
187
|
+
* `instance_id` matches the slot (the common, correct case) OR the responder
|
|
188
|
+
* doesn't self-report one (a pre-#733 peer — back-compat), the unchanged
|
|
189
|
+
* #424 version quadrant below decides. See `classifyPeer`'s body for the
|
|
190
|
+
* concurrent-newer-instance-race analysis (why this is safe to do without
|
|
191
|
+
* its own displacement logic).
|
|
192
|
+
*
|
|
168
193
|
* Version-aware takeover (groundnuty/macf#424): a same-version alive peer is
|
|
169
194
|
* still protected (alive → abort), but an alive instance running an *older*
|
|
170
195
|
* version no longer permanently squats the slot — a newer incoming displaces
|
|
@@ -232,15 +257,48 @@ export async function classifyPeer(name, existing, certPaths, incomingVersion, l
|
|
|
232
257
|
// Liveness is re-confirmed across HEALTH_CONFIRM_ATTEMPTS pings to defeat the
|
|
233
258
|
// just-killed-port race (groundnuty/macf#553): a single momentary 2xx from a
|
|
234
259
|
// dying prior instance must NOT count as alive. A confirmed-live peer then
|
|
235
|
-
// flows into the
|
|
236
|
-
const { alive, version: existingVersion } = await confirmLiveness(existing.host, existing.port, certPaths.caCertPath, certPaths.agentCertPath, certPaths.agentKeyPath);
|
|
260
|
+
// flows into the identity check + the #424 version quadrant below.
|
|
261
|
+
const { alive, version: existingVersion, instanceId: responderInstanceId } = await confirmLiveness(existing.host, existing.port, certPaths.caCertPath, certPaths.agentCertPath, certPaths.agentKeyPath);
|
|
237
262
|
if (alive) {
|
|
238
263
|
const versionTakeoverDisabled = process.env['MACF_NO_VERSION_TAKEOVER'] === '1';
|
|
239
264
|
const incomingVersioned = VERSION_PATTERN.test(incomingVersion);
|
|
240
|
-
//
|
|
265
|
+
// groundnuty/macf#733: the /health responder answered 2xx on every
|
|
266
|
+
// re-confirm ping (so `alive` is true), but that alone doesn't prove the
|
|
267
|
+
// REGISTERED owner (`existing.instance_id`) is who's answering. When the
|
|
268
|
+
// responder self-reports its OWN instance_id and it does NOT match the
|
|
269
|
+
// slot, the registered owner is confirmed GONE — something else (a
|
|
270
|
+
// lingering just-exited socket, an unrelated orphan) is squatting the
|
|
271
|
+
// port. `responderInstanceId === null` means the responder predates the
|
|
272
|
+
// #733 self-report (or the body was unparseable) — we can't compare, so
|
|
273
|
+
// fall through to the unchanged #424 version quadrant (back-compat).
|
|
274
|
+
//
|
|
275
|
+
// Concurrent-newer-instance-race safety: this identity check can, in a
|
|
276
|
+
// narrow TOCTOU window, mis-takeover against a genuinely newer live peer
|
|
277
|
+
// that raced us between our `registry.get()` read and this /health ping
|
|
278
|
+
// (e.g. the peer's own server is already up+serving at the recorded
|
|
279
|
+
// host:port under a fresh instance_id, but its registry write hasn't
|
|
280
|
+
// landed yet). This is bounded, not a permanent stranding: the caller's
|
|
281
|
+
// over-register loop (register-with-takeover.ts, macf#702) re-classifies
|
|
282
|
+
// against the FRESH registry value at CAS write time — if that peer is
|
|
283
|
+
// genuinely live + same-or-newer, our own registerConditional loses the
|
|
284
|
+
// race and we yield (RegisterRaceError); if the peer instead loses the
|
|
285
|
+
// slot to us first, its own registry heartbeat (DR-031 onDisplaced,
|
|
286
|
+
// macf#568) detects the takeover on its own next beat and stands down
|
|
287
|
+
// gracefully. Either direction self-corrects within one heartbeat
|
|
288
|
+
// interval — no additional displacement logic is added here.
|
|
289
|
+
const identityMismatch = responderInstanceId !== null && responderInstanceId !== existing.instance_id;
|
|
290
|
+
// Decide takeover-vs-abort against a LIVE peer (the identity check, then
|
|
291
|
+
// the #424 quadrant).
|
|
241
292
|
let takeover;
|
|
242
293
|
let basis;
|
|
243
|
-
if (
|
|
294
|
+
if (identityMismatch) {
|
|
295
|
+
// The registered owner isn't the one answering — takeover regardless of
|
|
296
|
+
// version policy (this is an identity question, not a version one; it
|
|
297
|
+
// intentionally bypasses versionTakeoverDisabled + incomingVersioned).
|
|
298
|
+
takeover = true;
|
|
299
|
+
basis = 'takeover_stale_owner_gone';
|
|
300
|
+
}
|
|
301
|
+
else if (versionTakeoverDisabled) {
|
|
244
302
|
takeover = false;
|
|
245
303
|
basis = 'abort_version_takeover_disabled';
|
|
246
304
|
}
|
|
@@ -275,6 +333,8 @@ export async function classifyPeer(name, existing, certPaths, incomingVersion, l
|
|
|
275
333
|
agent: name,
|
|
276
334
|
incoming_version: incomingVersion,
|
|
277
335
|
existing_version: existingVersion,
|
|
336
|
+
existing_instance_id: existing.instance_id,
|
|
337
|
+
responder_instance_id: responderInstanceId,
|
|
278
338
|
host: existing.host,
|
|
279
339
|
port: existing.port,
|
|
280
340
|
});
|
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,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAExG,yEAAyE;AACzE,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAE5C,MAAM,OAAO,cAAe,SAAQ,SAAS;IAC3C,YAAY,IAAY,EAAE,IAAY,EAAE,IAAY;QAClD,KAAK,CACH,iBAAiB,EACjB,UAAU,IAAI,2BAA2B,IAAI,IAAI,IAAI,IAAI;YACzD,kDAAkD,CACnD,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,iBAAkB,SAAQ,SAAS;IAC9C,YAAY,IAAY,EAAE,OAAyB;QACjD,KAAK,CACH,qBAAqB,EACrB,OAAO,KAAK,IAAI;YACd,CAAC,CAAC,UAAU,IAAI,uDAAuD;gBACrE,6EAA6E;YAC/E,CAAC,CAAC,UAAU,IAAI,sDAAsD;gBACpE,QAAQ,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,cAAc,OAAO,CAAC,WAAW,KAAK;gBAC1E,8CAA8C,CACnD,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAEtC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAEpC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,uBAAuB,GAAG,CAAC,CAAC;AAClC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAEpC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAC1C,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"collision.js","sourceRoot":"","sources":["../src/collision.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DR-038 Decision 6 — the pluggable store-DRIVER seam. `outbox.ts` / `inbox.ts`
|
|
3
|
+
* (the runtime-agnostic effectively-once logic) depend only on the
|
|
4
|
+
* `OutboxStore` / `InboxStore` interfaces (hoisted to `@groundnuty/macf-core`,
|
|
5
|
+
* `delivery/store.ts` — macf#741); THIS module is the one place `server.ts`
|
|
6
|
+
* decides which concrete driver backs those interfaces.
|
|
7
|
+
*
|
|
8
|
+
* Today there is exactly one driver: the in-memory reference (`in-memory-store.ts`).
|
|
9
|
+
* That is a PLACEHOLDER, not the DR-038 durability guarantee — see the loud
|
|
10
|
+
* warning in `createDefaultDeliveryStores` below. The devops-owned DR-008 VM
|
|
11
|
+
* disk-spool driver (and, later, a K8s PVC/broker driver) is expected to ship
|
|
12
|
+
* a sibling factory with the exact same `DeliveryStores` return shape; wiring
|
|
13
|
+
* it in is then a ONE-LINE swap of which factory `server.ts` calls — no change
|
|
14
|
+
* to `outbox.ts`, `inbox.ts`, the notify-peer sender wiring, or the `/notify` /
|
|
15
|
+
* `message/send` receiver wiring, all of which only ever see the `OutboxStore`
|
|
16
|
+
* / `InboxStore` interface.
|
|
17
|
+
*/
|
|
18
|
+
import type { Logger, OutboxStore, InboxStore } from '@groundnuty/macf-core';
|
|
19
|
+
export interface DeliveryStores {
|
|
20
|
+
readonly outboxStore: OutboxStore;
|
|
21
|
+
readonly inboxStore: InboxStore;
|
|
22
|
+
}
|
|
23
|
+
/** A driver factory — the swap point for a future DR-008 disk/K8s driver. */
|
|
24
|
+
export type DeliveryStoreFactory = (logger?: Logger) => DeliveryStores;
|
|
25
|
+
/**
|
|
26
|
+
* The reference in-memory driver — `server.ts`'s DEFAULT until a durable
|
|
27
|
+
* driver lands.
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ LOUD, DELIBERATE FLAG: this is NOT durable across a process crash. Every
|
|
30
|
+
* queued outbox entry and every undrained inbox entry lives only in this
|
|
31
|
+
* process's heap — a crash (the exact failure mode DR-038 exists to fix, per
|
|
32
|
+
* its "Live-validated, not hypothetical" motivation: the devops 8h outage +
|
|
33
|
+
* code-agent's #702 channel-server death) loses the lot, silently, with no
|
|
34
|
+
* on-disk trace. The WIRING in this slice (persist-then-send, persist-then-
|
|
35
|
+
* ACK, retry/backoff/TTL, dead-letter) is fully correct and exercised end-to-
|
|
36
|
+
* end by this slice's tests — what's missing is durability of the persist
|
|
37
|
+
* step itself, which requires swapping this factory for the devops-owned
|
|
38
|
+
* DR-008 disk-spool driver (or a K8s PVC/broker driver) once it lands. Until
|
|
39
|
+
* then, effectively-once degrades to "effectively-once for the lifetime of
|
|
40
|
+
* one process" — still strictly better than today's fire-and-forget (no
|
|
41
|
+
* retry at all across even a single-attempt transient failure), but NOT the
|
|
42
|
+
* full restart-survival guarantee DR-038 Decision 4 describes.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createDefaultDeliveryStores(logger?: Logger): DeliveryStores;
|
|
45
|
+
//# sourceMappingURL=driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/delivery/driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAG7E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CACjC;AAED,6EAA6E;AAC7E,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,cAAc,CAAC;AAEvE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,cAAc,CAa3E"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createInMemoryOutboxStore, createInMemoryInboxStore } from './in-memory-store.js';
|
|
2
|
+
/**
|
|
3
|
+
* The reference in-memory driver — `server.ts`'s DEFAULT until a durable
|
|
4
|
+
* driver lands.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ LOUD, DELIBERATE FLAG: this is NOT durable across a process crash. Every
|
|
7
|
+
* queued outbox entry and every undrained inbox entry lives only in this
|
|
8
|
+
* process's heap — a crash (the exact failure mode DR-038 exists to fix, per
|
|
9
|
+
* its "Live-validated, not hypothetical" motivation: the devops 8h outage +
|
|
10
|
+
* code-agent's #702 channel-server death) loses the lot, silently, with no
|
|
11
|
+
* on-disk trace. The WIRING in this slice (persist-then-send, persist-then-
|
|
12
|
+
* ACK, retry/backoff/TTL, dead-letter) is fully correct and exercised end-to-
|
|
13
|
+
* end by this slice's tests — what's missing is durability of the persist
|
|
14
|
+
* step itself, which requires swapping this factory for the devops-owned
|
|
15
|
+
* DR-008 disk-spool driver (or a K8s PVC/broker driver) once it lands. Until
|
|
16
|
+
* then, effectively-once degrades to "effectively-once for the lifetime of
|
|
17
|
+
* one process" — still strictly better than today's fire-and-forget (no
|
|
18
|
+
* retry at all across even a single-attempt transient failure), but NOT the
|
|
19
|
+
* full restart-survival guarantee DR-038 Decision 4 describes.
|
|
20
|
+
*/
|
|
21
|
+
export function createDefaultDeliveryStores(logger) {
|
|
22
|
+
logger?.warn('delivery_store_in_memory_placeholder', {
|
|
23
|
+
detail: 'DR-038 effectively-once delivery is wired against the IN-MEMORY reference ' +
|
|
24
|
+
'store driver — a process crash loses every queued outbox entry and every ' +
|
|
25
|
+
'undrained inbox entry (the exact failure DR-038 exists to fix). The retry/' +
|
|
26
|
+
'dedup LOGIC is correct; durability across a crash requires the DR-008 ' +
|
|
27
|
+
'disk-spool driver (devops-owned), not yet wired in. See delivery/driver.ts.',
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
outboxStore: createInMemoryOutboxStore(),
|
|
31
|
+
inboxStore: createInMemoryInboxStore(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../src/delivery/driver.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAU3F;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAe;IACzD,MAAM,EAAE,IAAI,CAAC,sCAAsC,EAAE;QACnD,MAAM,EACJ,4EAA4E;YAC5E,2EAA2E;YAC3E,4EAA4E;YAC5E,wEAAwE;YACxE,6EAA6E;KAChF,CAAC,CAAC;IACH,OAAO;QACL,WAAW,EAAE,yBAAyB,EAAE;QACxC,UAAU,EAAE,wBAAwB,EAAE;KACvC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference `OutboxStore` / `InboxStore` drivers backed by an in-process
|
|
3
|
+
* `Map`. NOT the real driver — the real drivers (VM disk-spool, K8s
|
|
4
|
+
* PVC/broker) are devops-owned per DR-008. This is:
|
|
5
|
+
*
|
|
6
|
+
* 1. what the effectively-once logic (`outbox.ts` / `inbox.ts`) is
|
|
7
|
+
* exercised against in this slice's tests, and
|
|
8
|
+
* 2. the reference shape a disk-backed driver mirrors — the exact same
|
|
9
|
+
* dedup-atomicity / persist-then-X ordering invariants apply, just
|
|
10
|
+
* against `fs` instead of a `Map`.
|
|
11
|
+
*
|
|
12
|
+
* Every method returns a fresh shallow copy of stored entries (never a live
|
|
13
|
+
* reference into the internal `Map`) so a caller mutating a returned object
|
|
14
|
+
* can't silently corrupt store state behind the mutate-only API surface
|
|
15
|
+
* (`enqueue` / `reschedule` / `markAcked` / `deadLetter` / `persist` /
|
|
16
|
+
* `markProcessed`) — consistent with this repo's "immutable interfaces"
|
|
17
|
+
* convention (CLAUDE.md § Conventions).
|
|
18
|
+
*/
|
|
19
|
+
import type { OutboxStore, InboxStore } from '@groundnuty/macf-core';
|
|
20
|
+
/** Create a fresh in-memory `OutboxStore`. Each call is an independent store. */
|
|
21
|
+
export declare function createInMemoryOutboxStore(): OutboxStore;
|
|
22
|
+
/** Create a fresh in-memory `InboxStore`. Each call is an independent store. */
|
|
23
|
+
export declare function createInMemoryInboxStore(): InboxStore;
|
|
24
|
+
//# sourceMappingURL=in-memory-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-memory-store.d.ts","sourceRoot":"","sources":["../../src/delivery/in-memory-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAe,UAAU,EAAc,MAAM,uBAAuB,CAAC;AAE9F,iFAAiF;AACjF,wBAAgB,yBAAyB,IAAI,WAAW,CA0BvD;AAED,gFAAgF;AAChF,wBAAgB,wBAAwB,IAAI,UAAU,CAoCrD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Create a fresh in-memory `OutboxStore`. Each call is an independent store. */
|
|
2
|
+
export function createInMemoryOutboxStore() {
|
|
3
|
+
const entries = new Map();
|
|
4
|
+
return {
|
|
5
|
+
async enqueue(entry) {
|
|
6
|
+
entries.set(entry.id, { ...entry });
|
|
7
|
+
},
|
|
8
|
+
async listPending() {
|
|
9
|
+
return Array.from(entries.values()).map((e) => ({ ...e }));
|
|
10
|
+
},
|
|
11
|
+
async reschedule(id, nextAttemptAt, attemptCount) {
|
|
12
|
+
const existing = entries.get(id);
|
|
13
|
+
if (existing === undefined)
|
|
14
|
+
return; // already acked/dead-lettered elsewhere — no-op
|
|
15
|
+
entries.set(id, { ...existing, nextAttemptAt, attemptCount });
|
|
16
|
+
},
|
|
17
|
+
async markAcked(id) {
|
|
18
|
+
entries.delete(id);
|
|
19
|
+
},
|
|
20
|
+
async deadLetter(id) {
|
|
21
|
+
entries.delete(id);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Create a fresh in-memory `InboxStore`. Each call is an independent store. */
|
|
26
|
+
export function createInMemoryInboxStore() {
|
|
27
|
+
const entries = new Map();
|
|
28
|
+
return {
|
|
29
|
+
async persist(entry) {
|
|
30
|
+
// Atomic dedup-and-persist: a single synchronous check + set against
|
|
31
|
+
// the Map, with no `await` between them — no TOCTOU window is
|
|
32
|
+
// reachable in-process. A disk-backed driver must preserve this same
|
|
33
|
+
// atomicity (e.g. an exclusive-create file write) rather than a
|
|
34
|
+
// separate exists-check + write.
|
|
35
|
+
if (entries.has(entry.id))
|
|
36
|
+
return false;
|
|
37
|
+
entries.set(entry.id, { ...entry });
|
|
38
|
+
return true;
|
|
39
|
+
},
|
|
40
|
+
async has(id) {
|
|
41
|
+
return entries.has(id);
|
|
42
|
+
},
|
|
43
|
+
async get(id) {
|
|
44
|
+
const existing = entries.get(id);
|
|
45
|
+
return existing === undefined ? undefined : { ...existing };
|
|
46
|
+
},
|
|
47
|
+
async listUndrained() {
|
|
48
|
+
return Array.from(entries.values())
|
|
49
|
+
.filter((e) => !e.processed)
|
|
50
|
+
.map((e) => ({ ...e }));
|
|
51
|
+
},
|
|
52
|
+
async markProcessed(id) {
|
|
53
|
+
const existing = entries.get(id);
|
|
54
|
+
if (existing === undefined)
|
|
55
|
+
return; // unknown id — no-op
|
|
56
|
+
entries.set(id, { ...existing, processed: true });
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=in-memory-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-memory-store.js","sourceRoot":"","sources":["../../src/delivery/in-memory-store.ts"],"names":[],"mappings":"AAoBA,iFAAiF;AACjF,MAAM,UAAU,yBAAyB;IACvC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE/C,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,KAAkB;YAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QACtC,CAAC;QAED,KAAK,CAAC,WAAW;YACf,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7D,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,EAAU,EAAE,aAAqB,EAAE,YAAoB;YACtE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACjC,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,CAAC,gDAAgD;YACpF,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC,CAAC;QAChE,CAAC;QAED,KAAK,CAAC,SAAS,CAAC,EAAU;YACxB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,EAAU;YACzB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,wBAAwB;IACtC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;IAE9C,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,KAAiB;YAC7B,qEAAqE;YACrE,8DAA8D;YAC9D,qEAAqE;YACrE,gEAAgE;YAChE,iCAAiC;YACjC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBAAE,OAAO,KAAK,CAAC;YACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;YACpC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,EAAU;YAClB,OAAO,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzB,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,EAAU;YAClB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACjC,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC9D,CAAC;QAED,KAAK,CAAC,aAAa;YACjB,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;iBAChC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;iBAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5B,CAAC;QAED,KAAK,CAAC,aAAa,CAAC,EAAU;YAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACjC,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,CAAC,qBAAqB;YACzD,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DR-038 Decision 5 follow-on (groundnuty/macf#744) — the live, in-process
|
|
3
|
+
* RE-FIRE logic for the receiver-side inbox's orphan class: an entry that
|
|
4
|
+
* `inbox.accept()` persisted (`wasNew: true`) but whose `onNotify` call threw
|
|
5
|
+
* BEFORE `inbox.markProcessed()` ran (see `https.ts`'s on-receipt wiring
|
|
6
|
+
* around the `/notify` + `message/send` recv edges). The sender's own retry
|
|
7
|
+
* dedups the same message-id (Decision 1) and never re-fires `onNotify` for
|
|
8
|
+
* it, so absent this driver the entry sits `processed:false` for the rest of
|
|
9
|
+
* the channel-server's process lifetime — recoverable only by the SEPARATE,
|
|
10
|
+
* short-lived plugin SessionStart `startup_check` drain (Decision 5's
|
|
11
|
+
* "on-startup" trigger), which runs against a FRESH, empty in-memory store
|
|
12
|
+
* and therefore can never see THIS process's own orphans (only a future
|
|
13
|
+
* process's, and only once a durable store driver — DR-008 — lands).
|
|
14
|
+
*
|
|
15
|
+
* `inbox-ticker.ts` drives this function periodically over the channel-
|
|
16
|
+
* server's OWN lifetime, so a transient `onNotify` failure (the common case
|
|
17
|
+
* — a momentarily-unavailable mcp_push / tmux-wake) self-recovers within the
|
|
18
|
+
* SAME process, well before any restart or plugin-drain would ever see it.
|
|
19
|
+
*
|
|
20
|
+
* Kept as a standalone module — not folded into `inbox.ts` — so `inbox.ts`
|
|
21
|
+
* stays the primitive-only persist/dedup/drain-read module. Mirrors the
|
|
22
|
+
* `outbox.ts` / `outbox-ticker.ts` split: `outbox.ts` doesn't know about
|
|
23
|
+
* ticking, `outbox-ticker.ts` doesn't know about persistence. Here,
|
|
24
|
+
* `inbox.ts` doesn't know about re-firing `onNotify` or attempt-bookkeeping;
|
|
25
|
+
* `inbox-ticker.ts` doesn't know about any of THIS module's drive logic.
|
|
26
|
+
*
|
|
27
|
+
* Four contract constraints (science-blessed, groundnuty/macf#744):
|
|
28
|
+
*
|
|
29
|
+
* 1. Grace-before-drain — an entry younger than `graceMs` is left alone;
|
|
30
|
+
* it may still be genuinely mid-flight in the ORIGINAL
|
|
31
|
+
* accept()→onNotify()→markProcessed() sequence, not yet an orphan.
|
|
32
|
+
* 2. `markProcessed` ONLY on a successful re-fire — never mark-then-fire.
|
|
33
|
+
* Marking before a confirmed onNotify success would reintroduce the
|
|
34
|
+
* persist-then-ACK silent-drop shape, inverted (silent-fallback-
|
|
35
|
+
* hazards.md). A failed re-fire leaves the entry exactly as undrained
|
|
36
|
+
* as it was, for a later tick (or eventual give-up, #3) to see.
|
|
37
|
+
* 3. Bounded push-retries — `maxAttempts` OR `ttlMs` (whichever comes
|
|
38
|
+
* first) caps how many times a persistently-failing entry gets
|
|
39
|
+
* re-fired. Attempt counts are tracked in a `Map` keyed by entry id,
|
|
40
|
+
* driver-locally — NEVER added to the frozen `InboxEntry` /
|
|
41
|
+
* `InboxStore` `@groundnuty/macf-core` schema (devops's disk-spool
|
|
42
|
+
* driver depends on that exact contract). Once given up, the entry is
|
|
43
|
+
* left `processed:false` forever — there is no delete/dead-letter
|
|
44
|
+
* primitive on `InboxStore` — this driver simply stops re-firing it.
|
|
45
|
+
* The FUTURE plugin startup-drain's surface-as-text path (Decision 5's
|
|
46
|
+
* other trigger) is the intended eventual consumer of a given-up
|
|
47
|
+
* entry, once it too can see this process's store (post-DR-008).
|
|
48
|
+
* Forward-note (DR-008, science #747 review): the attempt `Map` is
|
|
49
|
+
* process-local, so once a DURABLE store survives restarts a given-up
|
|
50
|
+
* entry gets a FRESH `maxAttempts` budget on every process restart (the
|
|
51
|
+
* Map resets while the entry persists) until the surface-as-text path
|
|
52
|
+
* consumes it. The durable-driver integration should define how a
|
|
53
|
+
* given-up entry is FINALLY resolved (dead-letter, or surfaced-once) so
|
|
54
|
+
* it can't be re-attempted indefinitely across restart generations.
|
|
55
|
+
* Moot today: the in-memory store loses the entry on restart too.
|
|
56
|
+
* 4. Drainer ownership / no double-surface — this driver ONLY re-fires
|
|
57
|
+
* `onNotify` + marks-on-success, or gives up silently. It NEVER
|
|
58
|
+
* surfaces anything as text itself; that split keeps it composable
|
|
59
|
+
* with the plugin's (future) startup-drain without either one
|
|
60
|
+
* duplicating the other's job.
|
|
61
|
+
*/
|
|
62
|
+
import type { Logger, NotifyPayload } from '@groundnuty/macf-core';
|
|
63
|
+
import type { Inbox } from './inbox.js';
|
|
64
|
+
/**
|
|
65
|
+
* Grace window before an undrained entry is considered a genuine orphan
|
|
66
|
+
* (constraint 1). Deliberately >= the ticker's own tick interval — the
|
|
67
|
+
* accept()→onNotify()→markProcessed() sequence on the ORIGINAL receive path
|
|
68
|
+
* completes in well under a second in practice, so an interval comfortably
|
|
69
|
+
* larger than one tick cycle can never mistake a mid-flight entry for an
|
|
70
|
+
* orphan.
|
|
71
|
+
*/
|
|
72
|
+
export declare const DEFAULT_INBOX_DRAIN_GRACE_MS = 30000;
|
|
73
|
+
/**
|
|
74
|
+
* Attempt cap (constraint 3). Small + finite — unlike the outbox's
|
|
75
|
+
* wall-clock-only retry budget (Decision 4 explicitly rejects an attempt
|
|
76
|
+
* count there, because restart gaps make attempt-count meaningless across
|
|
77
|
+
* process lifetimes), THIS driver's retries are only ever in-process (a
|
|
78
|
+
* fresh process gets a fresh, empty attempts `Map` + — today — a fresh,
|
|
79
|
+
* empty in-memory inbox), so a small attempt cap is the right complementary
|
|
80
|
+
* bound to the TTL below: it stops hammering a persistently-broken
|
|
81
|
+
* `onNotify` path quickly, without waiting a full day to give up.
|
|
82
|
+
*/
|
|
83
|
+
export declare const DEFAULT_INBOX_MAX_ATTEMPTS = 5;
|
|
84
|
+
/**
|
|
85
|
+
* Wall-clock TTL (constraint 3), mirroring the outbox's `DEFAULT_TTL_MS`
|
|
86
|
+
* (`outbox.ts`) — same 24h budget, same rationale (an 8h-class outage fits
|
|
87
|
+
* comfortably; anything longer legitimately downgrades to the give-up path).
|
|
88
|
+
*/
|
|
89
|
+
export declare const DEFAULT_INBOX_TTL_MS: number;
|
|
90
|
+
/** Per-tick counters, mainly for test assertions + operator diagnostics. */
|
|
91
|
+
export interface InboxDriveSummary {
|
|
92
|
+
/** Entries whose `onNotify` was actually invoked this tick. */
|
|
93
|
+
readonly attempted: number;
|
|
94
|
+
/** Entries successfully re-fired + marked processed this tick. */
|
|
95
|
+
readonly drained: number;
|
|
96
|
+
/** `onNotify` invocations that threw this tick (entry stays undrained). */
|
|
97
|
+
readonly failed: number;
|
|
98
|
+
/**
|
|
99
|
+
* Entries in the exhausted (attempts-cap OR TTL) state as of this tick —
|
|
100
|
+
* counted whether they crossed the threshold just now or were already
|
|
101
|
+
* past it. There is no store-side "remove" primitive to stop re-counting
|
|
102
|
+
* them; skipping the re-fire (constraint 3/4), not the counting, is what
|
|
103
|
+
* matters.
|
|
104
|
+
*/
|
|
105
|
+
readonly gaveUp: number;
|
|
106
|
+
}
|
|
107
|
+
export interface InboxDriveDeps {
|
|
108
|
+
readonly inbox: Inbox;
|
|
109
|
+
/**
|
|
110
|
+
* The SAME callback passed to `createHttpsServer` — re-invoked verbatim
|
|
111
|
+
* for an orphaned entry. The cast from `InboxEntry.payload` (`unknown` at
|
|
112
|
+
* the store layer by design — see `@groundnuty/macf-core`'s
|
|
113
|
+
* `delivery/store.ts`) to `NotifyPayload` is sound because BOTH
|
|
114
|
+
* `inbox.accept()` call sites in `https.ts` persist a NotifyPayload:
|
|
115
|
+
* - the legacy `/notify` edge persists the `NotifyPayloadSchema`-
|
|
116
|
+
* validated `result.data`;
|
|
117
|
+
* - the A2A `message/send` edge persists `a2aMessageToNotifyPayload(msg)`
|
|
118
|
+
* — the CONVERTED payload, NOT the raw `Message` (this uniform-persist
|
|
119
|
+
* is the #744 correctness fix; persisting the raw `Message` would make
|
|
120
|
+
* an A2A orphan re-fire a malformed NotifyPayload with every field
|
|
121
|
+
* undefined).
|
|
122
|
+
* Nothing else ever calls `accept()`, so `entry.payload` is invariably a
|
|
123
|
+
* NotifyPayload by the time this driver re-fires it.
|
|
124
|
+
*/
|
|
125
|
+
readonly onNotify: (payload: NotifyPayload) => Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* Per-entry-id attempt counter, owned + persisted ACROSS calls by the
|
|
128
|
+
* CALLER (`inbox-ticker.ts` in production; a test file directly) — kept
|
|
129
|
+
* entirely out of the store (constraint 3: never touches the frozen
|
|
130
|
+
* `InboxEntry` schema). Pass the SAME `Map` on every `driveInboxOnce` call
|
|
131
|
+
* for the cap to actually accumulate across ticks.
|
|
132
|
+
*/
|
|
133
|
+
readonly attempts: Map<string, number>;
|
|
134
|
+
readonly logger?: Logger;
|
|
135
|
+
/** Injectable epoch-ms clock (testing). Default `Date.now`. */
|
|
136
|
+
readonly now?: () => number;
|
|
137
|
+
readonly graceMs?: number;
|
|
138
|
+
readonly maxAttempts?: number;
|
|
139
|
+
readonly ttlMs?: number;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Drive one orphan-drain tick: for every undrained inbox entry, either skip
|
|
143
|
+
* it (too young — constraint 1; or already exhausted — constraint 3), give
|
|
144
|
+
* it up (TTL exceeded — constraint 3), or re-fire `onNotify` (success →
|
|
145
|
+
* `markProcessed`, constraint 2; failure → bump the attempt counter,
|
|
146
|
+
* constraint 3). Callable directly by tests with an explicit `now` + a
|
|
147
|
+
* fresh `attempts` Map, or wrapped in `inbox-ticker.ts`'s periodic timer.
|
|
148
|
+
*/
|
|
149
|
+
export declare function driveInboxOnce(deps: InboxDriveDeps): Promise<InboxDriveSummary>;
|
|
150
|
+
//# sourceMappingURL=inbox-drain-driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox-drain-driver.d.ts","sourceRoot":"","sources":["../../src/delivery/inbox-drain-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,QAAsB,CAAC;AAExD,4EAA4E;AAC5E,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgFrF"}
|