@groundnuty/macf-channel-server 0.2.47 → 0.2.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/collision.d.ts +28 -3
  2. package/dist/collision.d.ts.map +1 -1
  3. package/dist/collision.js +78 -18
  4. package/dist/collision.js.map +1 -1
  5. package/dist/delivery/driver.d.ts +45 -0
  6. package/dist/delivery/driver.d.ts.map +1 -0
  7. package/dist/delivery/driver.js +34 -0
  8. package/dist/delivery/driver.js.map +1 -0
  9. package/dist/delivery/in-memory-store.d.ts +24 -0
  10. package/dist/delivery/in-memory-store.d.ts.map +1 -0
  11. package/dist/delivery/in-memory-store.js +60 -0
  12. package/dist/delivery/in-memory-store.js.map +1 -0
  13. package/dist/delivery/inbox-drain-driver.d.ts +150 -0
  14. package/dist/delivery/inbox-drain-driver.d.ts.map +1 -0
  15. package/dist/delivery/inbox-drain-driver.js +109 -0
  16. package/dist/delivery/inbox-drain-driver.js.map +1 -0
  17. package/dist/delivery/inbox-ticker.d.ts +45 -0
  18. package/dist/delivery/inbox-ticker.d.ts.map +1 -0
  19. package/dist/delivery/inbox-ticker.js +68 -0
  20. package/dist/delivery/inbox-ticker.js.map +1 -0
  21. package/dist/delivery/inbox.d.ts +57 -0
  22. package/dist/delivery/inbox.d.ts.map +1 -0
  23. package/dist/delivery/inbox.js +19 -0
  24. package/dist/delivery/inbox.js.map +1 -0
  25. package/dist/delivery/message-id.d.ts +10 -0
  26. package/dist/delivery/message-id.d.ts.map +1 -0
  27. package/dist/delivery/message-id.js +33 -0
  28. package/dist/delivery/message-id.js.map +1 -0
  29. package/dist/delivery/outbox-ticker.d.ts +50 -0
  30. package/dist/delivery/outbox-ticker.d.ts.map +1 -0
  31. package/dist/delivery/outbox-ticker.js +49 -0
  32. package/dist/delivery/outbox-ticker.js.map +1 -0
  33. package/dist/delivery/outbox.d.ts +105 -0
  34. package/dist/delivery/outbox.d.ts.map +1 -0
  35. package/dist/delivery/outbox.js +103 -0
  36. package/dist/delivery/outbox.js.map +1 -0
  37. package/dist/https.d.ts +24 -0
  38. package/dist/https.d.ts.map +1 -1
  39. package/dist/https.js +70 -4
  40. package/dist/https.js.map +1 -1
  41. package/dist/notify-peer.d.ts +70 -0
  42. package/dist/notify-peer.d.ts.map +1 -1
  43. package/dist/notify-peer.js +142 -29
  44. package/dist/notify-peer.js.map +1 -1
  45. package/dist/server.js +100 -2
  46. package/dist/server.js.map +1 -1
  47. package/dist/shutdown.d.ts +18 -0
  48. package/dist/shutdown.d.ts.map +1 -1
  49. package/dist/shutdown.js +18 -1
  50. package/dist/shutdown.js.map +1 -1
  51. package/package.json +2 -2
@@ -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
- * (null when the peer answered but carried no `version` field — a
39
- * pre-groundnuty/macf#424 instance — or was unreachable/unparseable). */
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
@@ -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;;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,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,CA4G1B"}
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; version is
75
- * the `version` field parsed from the JSON body (null if absent/unparseable).
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. A peer
116
- // that answers 2xx but carries no `version` field (or an unparseable
117
- // body) is treated as version=null (a pre-#424 instance).
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 stays null
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 unchanged #424 version quadrant below.
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
- // Decide takeover-vs-abort against a LIVE peer (the #424 quadrant).
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 (versionTakeoverDisabled) {
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
  });
@@ -1 +1 @@
1
- {"version":3,"file":"collision.js","sourceRoot":"","sources":["../src/collision.ts"],"names":[],"mappings":"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"}
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"}