instar 1.3.43 → 1.3.44

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 (99) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +292 -3
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/HandoffReceiver.d.ts +56 -0
  5. package/dist/core/HandoffReceiver.d.ts.map +1 -0
  6. package/dist/core/HandoffReceiver.js +103 -0
  7. package/dist/core/HandoffReceiver.js.map +1 -0
  8. package/dist/core/HandoffWireTransport.d.ts +61 -0
  9. package/dist/core/HandoffWireTransport.d.ts.map +1 -0
  10. package/dist/core/HandoffWireTransport.js +114 -0
  11. package/dist/core/HandoffWireTransport.js.map +1 -0
  12. package/dist/core/HttpLeaseTransport.d.ts +68 -0
  13. package/dist/core/HttpLeaseTransport.d.ts.map +1 -0
  14. package/dist/core/HttpLeaseTransport.js +97 -0
  15. package/dist/core/HttpLeaseTransport.js.map +1 -0
  16. package/dist/core/HttpLiveTailTransport.d.ts +88 -0
  17. package/dist/core/HttpLiveTailTransport.d.ts.map +1 -0
  18. package/dist/core/HttpLiveTailTransport.js +95 -0
  19. package/dist/core/HttpLiveTailTransport.js.map +1 -0
  20. package/dist/core/LeaseCoordinator.d.ts +14 -0
  21. package/dist/core/LeaseCoordinator.d.ts.map +1 -1
  22. package/dist/core/LeaseCoordinator.js +38 -0
  23. package/dist/core/LeaseCoordinator.js.map +1 -1
  24. package/dist/core/LiveTailSource.d.ts +65 -0
  25. package/dist/core/LiveTailSource.d.ts.map +1 -0
  26. package/dist/core/LiveTailSource.js +87 -0
  27. package/dist/core/LiveTailSource.js.map +1 -0
  28. package/dist/core/MultiMachineCoordinator.d.ts +14 -0
  29. package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
  30. package/dist/core/MultiMachineCoordinator.js +22 -0
  31. package/dist/core/MultiMachineCoordinator.js.map +1 -1
  32. package/dist/core/ReplyMarkerTransport.d.ts +58 -0
  33. package/dist/core/ReplyMarkerTransport.d.ts.map +1 -0
  34. package/dist/core/ReplyMarkerTransport.js +66 -0
  35. package/dist/core/ReplyMarkerTransport.js.map +1 -0
  36. package/dist/core/handoffReceiverWiring.d.ts +57 -0
  37. package/dist/core/handoffReceiverWiring.d.ts.map +1 -0
  38. package/dist/core/handoffReceiverWiring.js +65 -0
  39. package/dist/core/handoffReceiverWiring.js.map +1 -0
  40. package/dist/core/handoffSentinelBootWiring.d.ts +53 -0
  41. package/dist/core/handoffSentinelBootWiring.d.ts.map +1 -0
  42. package/dist/core/handoffSentinelBootWiring.js +52 -0
  43. package/dist/core/handoffSentinelBootWiring.js.map +1 -0
  44. package/dist/core/handoffSentinelWiring.d.ts +60 -0
  45. package/dist/core/handoffSentinelWiring.d.ts.map +1 -0
  46. package/dist/core/handoffSentinelWiring.js +59 -0
  47. package/dist/core/handoffSentinelWiring.js.map +1 -0
  48. package/dist/core/seamlessnessConfig.d.ts +8 -0
  49. package/dist/core/seamlessnessConfig.d.ts.map +1 -1
  50. package/dist/core/seamlessnessConfig.js +1 -0
  51. package/dist/core/seamlessnessConfig.js.map +1 -1
  52. package/dist/core/types.d.ts +6 -0
  53. package/dist/core/types.d.ts.map +1 -1
  54. package/dist/core/types.js.map +1 -1
  55. package/dist/messaging/TelegramAdapter.d.ts +6 -0
  56. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  57. package/dist/messaging/TelegramAdapter.js +8 -0
  58. package/dist/messaging/TelegramAdapter.js.map +1 -1
  59. package/dist/messaging/ingressDedup.d.ts +55 -0
  60. package/dist/messaging/ingressDedup.d.ts.map +1 -0
  61. package/dist/messaging/ingressDedup.js +69 -0
  62. package/dist/messaging/ingressDedup.js.map +1 -0
  63. package/dist/messaging/stuckMessageRecovery.d.ts +46 -0
  64. package/dist/messaging/stuckMessageRecovery.d.ts.map +1 -0
  65. package/dist/messaging/stuckMessageRecovery.js +52 -0
  66. package/dist/messaging/stuckMessageRecovery.js.map +1 -0
  67. package/dist/server/AgentServer.d.ts +64 -0
  68. package/dist/server/AgentServer.d.ts.map +1 -1
  69. package/dist/server/AgentServer.js +24 -0
  70. package/dist/server/AgentServer.js.map +1 -1
  71. package/dist/server/handoffInitiateRoutes.d.ts +40 -0
  72. package/dist/server/handoffInitiateRoutes.d.ts.map +1 -0
  73. package/dist/server/handoffInitiateRoutes.js +57 -0
  74. package/dist/server/handoffInitiateRoutes.js.map +1 -0
  75. package/dist/server/machineRoutes.d.ts +47 -0
  76. package/dist/server/machineRoutes.d.ts.map +1 -1
  77. package/dist/server/machineRoutes.js +138 -0
  78. package/dist/server/machineRoutes.js.map +1 -1
  79. package/dist/server/routes.d.ts +20 -0
  80. package/dist/server/routes.d.ts.map +1 -1
  81. package/dist/server/routes.js +65 -0
  82. package/dist/server/routes.js.map +1 -1
  83. package/package.json +1 -1
  84. package/src/data/builtin-manifest.json +49 -49
  85. package/upgrades/1.3.44.md +72 -0
  86. package/upgrades/side-effects/cross-machine-seamlessness-consent-acquire.md +52 -0
  87. package/upgrades/side-effects/cross-machine-seamlessness-exactly-once-ingress.md +73 -0
  88. package/upgrades/side-effects/cross-machine-seamlessness-handoff-ack-yield.md +82 -0
  89. package/upgrades/side-effects/cross-machine-seamlessness-handoff-begin.md +48 -0
  90. package/upgrades/side-effects/cross-machine-seamlessness-handoff-boot-wiring.md +68 -0
  91. package/upgrades/side-effects/cross-machine-seamlessness-handoff-receiver-wiring.md +52 -0
  92. package/upgrades/side-effects/cross-machine-seamlessness-handoff-receiver.md +46 -0
  93. package/upgrades/side-effects/cross-machine-seamlessness-handoff-sentinel-wiring.md +47 -0
  94. package/upgrades/side-effects/cross-machine-seamlessness-lease-wire.md +46 -0
  95. package/upgrades/side-effects/cross-machine-seamlessness-livetail-source.md +50 -0
  96. package/upgrades/side-effects/cross-machine-seamlessness-livetail-streaming.md +50 -0
  97. package/upgrades/side-effects/cross-machine-seamlessness-livetail-wire.md +65 -0
  98. package/upgrades/side-effects/cross-machine-seamlessness-noloss-recovery.md +61 -0
  99. package/upgrades/side-effects/cross-machine-seamlessness-reply-marker.md +65 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * ReplyMarkerTransport — propagates the `reply_committed` marker from the lease
3
+ * holder to the standby peer(s) over the authenticated machine channel (spec §8
4
+ * G3a, "dual-medium sent-marker"). The cross-machine half of exactly-once.
5
+ *
6
+ * When the holder commits a reply, it broadcasts a small marker
7
+ * {dedupeKey, replyIdempotencyKey, epoch, topic} to each standby. The standby
8
+ * applies it via MessageProcessingLedger.applyRemoteReplyMarker, so AFTER a
9
+ * handoff/failover the newly-awake machine already knows that event was answered
10
+ * — a provider redelivery of the same inbound is then dropped by the dedup gate,
11
+ * closing the cross-machine double-reply window the lease alone can't (a
12
+ * redelivery arriving at the new holder for a message the OLD holder answered).
13
+ *
14
+ * The marker carries NO conversation content (just the dedupeKey + the
15
+ * deterministic idempotency key + the fencing epoch), so unlike the live-tail it
16
+ * is NOT encrypted — it rides the signed channel for authentication only. The
17
+ * tunnel is the low-latency medium; git-committed ledger state is the slower
18
+ * belt-and-suspenders (the residual is the documented Two-Generals floor).
19
+ *
20
+ * Single-machine safe: no peers → broadcast is a reachable no-op. fetch/clock
21
+ * injected for testability.
22
+ */
23
+ import { signRequest } from '../server/machineAuth.js';
24
+ export class ReplyMarkerTransport {
25
+ d;
26
+ constructor(deps) {
27
+ this.d = deps;
28
+ }
29
+ log(m) {
30
+ this.d.logger?.(`[reply-marker] ${m}`);
31
+ }
32
+ /**
33
+ * POST the marker to every standby peer over the signed channel. Resolves true
34
+ * if at least one peer accepted (a live medium carried it); false if none was
35
+ * reachable. No peers → reachable no-op (true). Never throws — propagation is
36
+ * best-effort; the provider's at-least-once redelivery + the dedup gate are the
37
+ * backstop if a marker is lost.
38
+ */
39
+ async broadcast(marker) {
40
+ const peers = this.d.peers();
41
+ if (peers.length === 0)
42
+ return true;
43
+ const fetchImpl = this.d.fetchImpl ?? fetch;
44
+ let anyOk = false;
45
+ await Promise.all(peers.map(async (peer) => {
46
+ try {
47
+ const body = { marker };
48
+ const headers = signRequest(this.d.selfMachineId, this.d.signingKeyPem, body, this.d.nextSequence());
49
+ const res = await fetchImpl(`${peer.url.replace(/\/$/, '')}/api/message-marker`, {
50
+ method: 'POST',
51
+ headers: { 'Content-Type': 'application/json', ...headers },
52
+ body: JSON.stringify(body),
53
+ });
54
+ if (res && res.ok)
55
+ anyOk = true;
56
+ else
57
+ this.log(`peer ${peer.machineId} rejected marker ${marker.dedupeKey} (status ${res?.status})`);
58
+ }
59
+ catch (err) {
60
+ this.log(`broadcast to ${peer.machineId} failed: ${err instanceof Error ? err.message : String(err)}`);
61
+ }
62
+ }));
63
+ return anyOk;
64
+ }
65
+ }
66
+ //# sourceMappingURL=ReplyMarkerTransport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReplyMarkerTransport.js","sourceRoot":"","sources":["../../src/core/ReplyMarkerTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AA2BvD,MAAM,OAAO,oBAAoB;IACd,CAAC,CAA2B;IAE7C,YAAY,IAA8B;QACxC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAChB,CAAC;IAEO,GAAG,CAAC,CAAS;QACnB,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,SAAS,CAAC,MAAmB;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACpC,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,IAAI,KAAK,CAAC;QAC5C,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,MAAM,OAAO,CAAC,GAAG,CACf,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YACvB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,EAAE,MAAM,EAAE,CAAC;gBACxB,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC;gBACrG,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,qBAAqB,EAAE;oBAC/E,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,OAAO,EAAE;oBAC3D,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;iBAC3B,CAAC,CAAC;gBACH,IAAI,GAAG,IAAK,GAAgB,CAAC,EAAE;oBAAE,KAAK,GAAG,IAAI,CAAC;;oBACzC,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,SAAS,oBAAoB,MAAM,CAAC,SAAS,YAAa,GAAgB,EAAE,MAAM,GAAG,CAAC,CAAC;YACpH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,GAAG,CAAC,gBAAgB,IAAI,CAAC,SAAS,YAAY,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACzG,CAAC;QACH,CAAC,CAAC,CACH,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * handoffReceiverWiring — the incoming-machine binding for the planned handoff
3
+ * (spec §8 G3d/G3e). Extracted from server.ts so the wiring is a tested unit
4
+ * (Testing Integrity Standard: DI components get wiring-integrity tests) rather
5
+ * than inline closures in the boot path.
6
+ *
7
+ * It composes a HandoffReceiver with ops bound to the live components:
8
+ * - begin route → onBegin(manifest, from): stores the outgoing's flush manifest
9
+ * and drives receiver.onBeginHandoff (build + send the caught-up ack).
10
+ * - buildAck: echoes the manifest's tailSeq + ingressPosition and RECOMPUTES the
11
+ * thread-history hash from THIS machine's own synced state — it matches the
12
+ * outgoing's only if the live-tail kept us caught up. Never trusts the
13
+ * manifest's own hash blindly.
14
+ * - yield route → yieldHandler(): receiver.onYield → acquireLeaseOnConsent. The
15
+ * lease CAS is attempted ONLY here, never on the ack (closes the two-holders
16
+ * race, spec §8 G3e).
17
+ *
18
+ * The hash is computed with the SAME canonical formatting LiveTailSource uses for
19
+ * the tail content, so both machines hash identical bytes for the same history.
20
+ */
21
+ import { HandoffReceiver } from './HandoffReceiver.js';
22
+ import type { HandoffAck } from './HandoffSentinel.js';
23
+ import type { IngressPosition } from './types.js';
24
+ /** A thread entry as LiveTailSource formats it (timestamp + text). */
25
+ export interface ThreadEntry {
26
+ timestamp: string;
27
+ text: string;
28
+ }
29
+ /**
30
+ * Canonical thread-history hash — identical on both machines for the same
31
+ * history. Exported so the OUTGOING flush (HandoffSentinel) hashes the same way.
32
+ */
33
+ export declare function hashTopicHistory(getTopicHistory: (topic: number, limit: number) => ThreadEntry[], topic: number | undefined): string;
34
+ export interface HandoffBeginManifest {
35
+ tailSeq: number;
36
+ ingressPosition: IngressPosition;
37
+ threadHistoryHash: string;
38
+ topic?: number;
39
+ }
40
+ export interface HandoffReceiverWiringDeps {
41
+ /** Push the caught-up ack to the outgoing machine (HandoffWireTransport.sendAck). */
42
+ sendAck: (ack: HandoffAck) => Promise<boolean>;
43
+ /** Acquire the lease on the verified yield (coordinator.acquireLeaseOnConsent). */
44
+ acquireLeaseOnConsent: (fromMachineId: string) => Promise<boolean>;
45
+ /** Thread history for the hash (telegram.getTopicHistory). */
46
+ getTopicHistory: (topic: number, limit: number) => ThreadEntry[];
47
+ logger?: (msg: string) => void;
48
+ }
49
+ export interface HandoffReceiverWiring {
50
+ receiver: HandoffReceiver;
51
+ /** Bind to the /api/handoff/begin route (AgentServer onHandoffBegin). */
52
+ onBegin: (manifest: unknown, fromMachineId: string) => void;
53
+ /** Register on the HandoffWireTransport so a yield drives the lease CAS. */
54
+ yieldHandler: () => void;
55
+ }
56
+ export declare function createHandoffReceiverWiring(deps: HandoffReceiverWiringDeps): HandoffReceiverWiring;
57
+ //# sourceMappingURL=handoffReceiverWiring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoffReceiverWiring.d.ts","sourceRoot":"","sources":["../../src/core/handoffReceiverWiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,sEAAsE;AACtE,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,WAAW,EAAE,EAChE,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,MAAM,CAOR;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,eAAe,CAAC;IACjC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB;IACxC,qFAAqF;IACrF,OAAO,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,mFAAmF;IACnF,qBAAqB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnE,8DAA8D;IAC9D,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,WAAW,EAAE,CAAC;IACjE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,eAAe,CAAC;IAC1B,yEAAyE;IACzE,OAAO,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,4EAA4E;IAC5E,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,yBAAyB,GAAG,qBAAqB,CAiClG"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * handoffReceiverWiring — the incoming-machine binding for the planned handoff
3
+ * (spec §8 G3d/G3e). Extracted from server.ts so the wiring is a tested unit
4
+ * (Testing Integrity Standard: DI components get wiring-integrity tests) rather
5
+ * than inline closures in the boot path.
6
+ *
7
+ * It composes a HandoffReceiver with ops bound to the live components:
8
+ * - begin route → onBegin(manifest, from): stores the outgoing's flush manifest
9
+ * and drives receiver.onBeginHandoff (build + send the caught-up ack).
10
+ * - buildAck: echoes the manifest's tailSeq + ingressPosition and RECOMPUTES the
11
+ * thread-history hash from THIS machine's own synced state — it matches the
12
+ * outgoing's only if the live-tail kept us caught up. Never trusts the
13
+ * manifest's own hash blindly.
14
+ * - yield route → yieldHandler(): receiver.onYield → acquireLeaseOnConsent. The
15
+ * lease CAS is attempted ONLY here, never on the ack (closes the two-holders
16
+ * race, spec §8 G3e).
17
+ *
18
+ * The hash is computed with the SAME canonical formatting LiveTailSource uses for
19
+ * the tail content, so both machines hash identical bytes for the same history.
20
+ */
21
+ import { createHash } from 'node:crypto';
22
+ import { HandoffReceiver } from './HandoffReceiver.js';
23
+ /**
24
+ * Canonical thread-history hash — identical on both machines for the same
25
+ * history. Exported so the OUTGOING flush (HandoffSentinel) hashes the same way.
26
+ */
27
+ export function hashTopicHistory(getTopicHistory, topic) {
28
+ const h = createHash('sha256');
29
+ if (topic != null && Number.isFinite(Number(topic))) {
30
+ const entries = getTopicHistory(Number(topic), 500);
31
+ h.update(entries.map((e) => `[${e.timestamp}] ${e.text}`).join('\n') + (entries.length ? '\n' : ''));
32
+ }
33
+ return h.digest('hex');
34
+ }
35
+ export function createHandoffReceiverWiring(deps) {
36
+ let pendingManifest = null;
37
+ let pendingFrom = null;
38
+ const receiver = new HandoffReceiver({
39
+ buildAck: async () => {
40
+ const m = pendingManifest;
41
+ if (!m)
42
+ throw new Error('no pending handoff manifest');
43
+ return {
44
+ tailSeq: m.tailSeq,
45
+ ingressPosition: m.ingressPosition,
46
+ threadHistoryHash: hashTopicHistory(deps.getTopicHistory, m.topic),
47
+ };
48
+ },
49
+ sendAck: (ack) => deps.sendAck(ack),
50
+ acquireOnYield: () => deps.acquireLeaseOnConsent(pendingFrom ?? ''),
51
+ }, {
52
+ logger: deps.logger,
53
+ onTerminal: (s, detail) => deps.logger?.(`[handoff-recv] ${s}: ${detail}`),
54
+ });
55
+ return {
56
+ receiver,
57
+ onBegin: (manifest, from) => {
58
+ pendingManifest = manifest;
59
+ pendingFrom = from;
60
+ void receiver.onBeginHandoff();
61
+ },
62
+ yieldHandler: () => { void receiver.onYield(); },
63
+ };
64
+ }
65
+ //# sourceMappingURL=handoffReceiverWiring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoffReceiverWiring.js","sourceRoot":"","sources":["../../src/core/handoffReceiverWiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAUvD;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,eAAgE,EAChE,KAAyB;IAEzB,MAAM,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,KAAK,IAAI,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QACpD,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;QACpD,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvG,CAAC;IACD,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzB,CAAC;AA2BD,MAAM,UAAU,2BAA2B,CAAC,IAA+B;IACzE,IAAI,eAAe,GAAgC,IAAI,CAAC;IACxD,IAAI,WAAW,GAAkB,IAAI,CAAC;IAEtC,MAAM,QAAQ,GAAG,IAAI,eAAe,CAClC;QACE,QAAQ,EAAE,KAAK,IAAyB,EAAE;YACxC,MAAM,CAAC,GAAG,eAAe,CAAC;YAC1B,IAAI,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;YACvD,OAAO;gBACL,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,eAAe,EAAE,CAAC,CAAC,eAAe;gBAClC,iBAAiB,EAAE,gBAAgB,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC;aACnE,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;QACnC,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,qBAAqB,CAAC,WAAW,IAAI,EAAE,CAAC;KACpE,EACD;QACE,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,kBAAkB,CAAC,KAAK,MAAM,EAAE,CAAC;KAC3E,CACF,CAAC;IAEF,OAAO;QACL,QAAQ;QACR,OAAO,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE;YAC1B,eAAe,GAAG,QAAgC,CAAC;YACnD,WAAW,GAAG,IAAI,CAAC;YACnB,KAAK,QAAQ,CAAC,cAAc,EAAE,CAAC;QACjC,CAAC;QACD,YAAY,EAAE,GAAG,EAAE,GAAG,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;KACjD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * handoffSentinelBootWiring — binds the outgoing planned-handoff sentinel to the
3
+ * LIVE server components (Telegram adapter, coordinator, live-tail source, wire
4
+ * transport). Extracted from server.ts boot so the glue — active-topic selection
5
+ * and dependency binding — is a tested unit rather than inline boot code. This
6
+ * mirrors handoffReceiverWiring (Testing Integrity Standard: DI components get
7
+ * wiring-integrity tests; spec §8 G3e / §10).
8
+ *
9
+ * server.ts calls this once inside the live-tail block (where liveTailSource is
10
+ * in scope) and exposes the returned `initiate` behind POST /handoff/initiate +
11
+ * `sentinel.inProgress` as the race guard.
12
+ */
13
+ import { type HandoffSentinelWiring } from './handoffSentinelWiring.js';
14
+ import type { HandoffAck } from './HandoffSentinel.js';
15
+ import type { IngressPosition } from './types.js';
16
+ import type { ThreadEntry } from './handoffReceiverWiring.js';
17
+ /** The slice of the Telegram adapter the boot wiring needs. */
18
+ export interface SentinelBootTelegram {
19
+ getIngressPosition: () => IngressPosition;
20
+ getTopicHistory: (topic: number, limit: number) => ThreadEntry[];
21
+ getKnownTopicIds: () => number[];
22
+ }
23
+ /** The slice of the coordinator the boot wiring needs. */
24
+ export interface SentinelBootCoordinator {
25
+ demoteToStandby: (reason: string) => void;
26
+ }
27
+ /** The slice of the live-tail source the boot wiring drives before flushing. */
28
+ export interface SentinelBootLiveTail {
29
+ pushTick: () => Promise<unknown>;
30
+ }
31
+ /** The slice of the wire transport the boot wiring uses (outgoing side). */
32
+ export interface SentinelBootWire {
33
+ sendBegin: (manifest: unknown) => Promise<boolean>;
34
+ awaitAck: (timeoutMs: number) => Promise<HandoffAck | null>;
35
+ sendYield: () => Promise<boolean>;
36
+ }
37
+ export interface HandoffSentinelBootDeps {
38
+ telegram: SentinelBootTelegram;
39
+ coordinator: SentinelBootCoordinator;
40
+ liveTailSource: SentinelBootLiveTail;
41
+ wire: SentinelBootWire;
42
+ handoffAckTimeoutMs: number;
43
+ minHandoffIntervalMs: number;
44
+ logger?: (msg: string) => void;
45
+ }
46
+ /**
47
+ * Pick the topic to fence the handoff on: the most-recently-active one.
48
+ * getTopicHistory returns entries in chronological order capped at the last N,
49
+ * so the final entry is the newest. Falls back to the first known topic id.
50
+ */
51
+ export declare function pickActiveTopic(getKnownTopicIds: () => number[], getTopicHistory: (topic: number, limit: number) => ThreadEntry[]): number | undefined;
52
+ export declare function createHandoffSentinelBootWiring(deps: HandoffSentinelBootDeps): HandoffSentinelWiring;
53
+ //# sourceMappingURL=handoffSentinelBootWiring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoffSentinelBootWiring.d.ts","sourceRoot":"","sources":["../../src/core/handoffSentinelBootWiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAA+B,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACrG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,+DAA+D;AAC/D,MAAM,WAAW,oBAAoB;IACnC,kBAAkB,EAAE,MAAM,eAAe,CAAC;IAC1C,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,WAAW,EAAE,CAAC;IACjE,gBAAgB,EAAE,MAAM,MAAM,EAAE,CAAC;CAClC;AACD,0DAA0D;AAC1D,MAAM,WAAW,uBAAuB;IACtC,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AACD,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CAClC;AACD,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC5D,SAAS,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,WAAW,EAAE,uBAAuB,CAAC;IACrC,cAAc,EAAE,oBAAoB,CAAC;IACrC,IAAI,EAAE,gBAAgB,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,gBAAgB,EAAE,MAAM,MAAM,EAAE,EAChC,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,WAAW,EAAE,GAC/D,MAAM,GAAG,SAAS,CAcpB;AAED,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,uBAAuB,GAAG,qBAAqB,CAkBpG"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * handoffSentinelBootWiring — binds the outgoing planned-handoff sentinel to the
3
+ * LIVE server components (Telegram adapter, coordinator, live-tail source, wire
4
+ * transport). Extracted from server.ts boot so the glue — active-topic selection
5
+ * and dependency binding — is a tested unit rather than inline boot code. This
6
+ * mirrors handoffReceiverWiring (Testing Integrity Standard: DI components get
7
+ * wiring-integrity tests; spec §8 G3e / §10).
8
+ *
9
+ * server.ts calls this once inside the live-tail block (where liveTailSource is
10
+ * in scope) and exposes the returned `initiate` behind POST /handoff/initiate +
11
+ * `sentinel.inProgress` as the race guard.
12
+ */
13
+ import { createHandoffSentinelWiring } from './handoffSentinelWiring.js';
14
+ /**
15
+ * Pick the topic to fence the handoff on: the most-recently-active one.
16
+ * getTopicHistory returns entries in chronological order capped at the last N,
17
+ * so the final entry is the newest. Falls back to the first known topic id.
18
+ */
19
+ export function pickActiveTopic(getKnownTopicIds, getTopicHistory) {
20
+ const ids = getKnownTopicIds();
21
+ let best;
22
+ let bestTs = -Infinity;
23
+ for (const id of ids) {
24
+ const hist = getTopicHistory(id, 50);
25
+ const last = hist[hist.length - 1];
26
+ const ts = last ? Date.parse(last.timestamp) : NaN;
27
+ if (Number.isFinite(ts) && ts > bestTs) {
28
+ bestTs = ts;
29
+ best = id;
30
+ }
31
+ }
32
+ return best ?? ids[0];
33
+ }
34
+ export function createHandoffSentinelBootWiring(deps) {
35
+ // Closures preserve `this` for the adapter methods.
36
+ const getKnownTopicIds = () => deps.telegram.getKnownTopicIds();
37
+ const getTopicHistory = (topic, limit) => deps.telegram.getTopicHistory(topic, limit);
38
+ return createHandoffSentinelWiring({
39
+ pushTick: async () => { await deps.liveTailSource.pushTick(); },
40
+ getIngressPosition: () => deps.telegram.getIngressPosition(),
41
+ getTopicHistory,
42
+ activeTopic: () => pickActiveTopic(getKnownTopicIds, getTopicHistory),
43
+ postBegin: (manifest) => deps.wire.sendBegin(manifest),
44
+ awaitAck: (timeoutMs) => deps.wire.awaitAck(timeoutMs),
45
+ sendYield: () => deps.wire.sendYield(),
46
+ demoteSelf: () => deps.coordinator.demoteToStandby('planned handoff: yielded to peer'),
47
+ handoffAckTimeoutMs: deps.handoffAckTimeoutMs,
48
+ minHandoffIntervalMs: deps.minHandoffIntervalMs,
49
+ logger: deps.logger,
50
+ });
51
+ }
52
+ //# sourceMappingURL=handoffSentinelBootWiring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoffSentinelBootWiring.js","sourceRoot":"","sources":["../../src/core/handoffSentinelBootWiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,2BAA2B,EAA8B,MAAM,4BAA4B,CAAC;AAoCrG;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,gBAAgC,EAChC,eAAgE;IAEhE,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,IAAI,IAAwB,CAAC;IAC7B,IAAI,MAAM,GAAG,CAAC,QAAQ,CAAC;IACvB,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,eAAe,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACnC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACnD,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC;YACvC,MAAM,GAAG,EAAE,CAAC;YACZ,IAAI,GAAG,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IACD,OAAO,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,IAA6B;IAC3E,oDAAoD;IACpD,MAAM,gBAAgB,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,EAAE,CAAC;IAChE,MAAM,eAAe,GAAG,CAAC,KAAa,EAAE,KAAa,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAEtG,OAAO,2BAA2B,CAAC;QACjC,QAAQ,EAAE,KAAK,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC/D,kBAAkB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,kBAAkB,EAAE;QAC5D,eAAe;QACf,WAAW,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,gBAAgB,EAAE,eAAe,CAAC;QACrE,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;QACtD,QAAQ,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;QACtD,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;QACtC,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,kCAAkC,CAAC;QACtF,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;QAC7C,oBAAoB,EAAE,IAAI,CAAC,oBAAoB;QAC/C,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * handoffSentinelWiring — the outgoing-machine binding for the planned handoff
3
+ * (spec §8 G3e). Counterpart to handoffReceiverWiring; extracted from server.ts
4
+ * so the lease-yield orchestration is a tested unit, not inline boot code.
5
+ *
6
+ * Composes a HandoffSentinel whose ops are bound to the live components:
7
+ * - flush(): drive the live-tail so the standby is current, then POST the begin
8
+ * manifest to the peer. The manifest's threadHistoryHash is computed with the
9
+ * SAME canonical hash the incoming uses (handoffReceiverWiring.hashTopicHistory),
10
+ * so the incoming's recomputed echo can verify.
11
+ * - awaitAck(): the HandoffWireTransport's pending-ack promise.
12
+ * - validate(): Tier-1 readiness. v1 is deterministic (the sentinel's own
13
+ * ackMatches echo-verification is the substantive gate); a Haiku-class
14
+ * validator is the spec's upgrade path (tracked).
15
+ * - sendYield(): the explicit yield — the ONLY trigger for the incoming CAS.
16
+ * - demoteSelf(): step down to standby after the yield.
17
+ *
18
+ * The CRITICAL invariant (spec §8 G3e) lives in HandoffSentinel.initiate: the
19
+ * outgoing NEVER yields unless the ack is verified AND validation passes; on any
20
+ * failure it aborts and stays awake. This wiring only supplies the ops.
21
+ */
22
+ import { HandoffSentinel } from './HandoffSentinel.js';
23
+ import type { FlushManifest, HandoffAck, HandoffOutcome } from './HandoffSentinel.js';
24
+ import type { IngressPosition } from './types.js';
25
+ import { type ThreadEntry } from './handoffReceiverWiring.js';
26
+ export interface HandoffSentinelWiringDeps {
27
+ /** Push the live tail so the standby's buffer is current before we flush. */
28
+ pushTick: () => Promise<void>;
29
+ /** This machine's current ingress position (telegram.getIngressPosition). */
30
+ getIngressPosition: () => IngressPosition;
31
+ /** Thread history for the hash (telegram.getTopicHistory) — same fn the receiver uses. */
32
+ getTopicHistory: (topic: number, limit: number) => ThreadEntry[];
33
+ /** The conversation topic to fence on (most-recently-active). undefined → empty hash. */
34
+ activeTopic: () => number | undefined;
35
+ /** Best-effort live-tail sequence echoed in the manifest (hash is the real check). */
36
+ lastTailSeq?: () => number;
37
+ /** POST the begin manifest to the peer (HandoffWireTransport.sendBegin). */
38
+ postBegin: (manifest: FlushManifest & {
39
+ topic?: number;
40
+ }) => Promise<boolean>;
41
+ /** Await the incoming's ack (HandoffWireTransport.awaitAck). */
42
+ awaitAck: (timeoutMs: number) => Promise<HandoffAck | null>;
43
+ /** Send the explicit yield (HandoffWireTransport.sendYield). */
44
+ sendYield: () => Promise<boolean>;
45
+ /** Demote self to standby (coordinator.demoteToStandby). */
46
+ demoteSelf: () => void | Promise<void>;
47
+ /** Optional Tier-1 validator; default → deterministic pass (ackMatches is the gate). */
48
+ validate?: (ack: HandoffAck, manifest: FlushManifest) => Promise<boolean>;
49
+ handoffAckTimeoutMs: number;
50
+ minHandoffIntervalMs: number;
51
+ logger?: (msg: string) => void;
52
+ onTerminal?: (outcome: HandoffOutcome, detail: string) => void;
53
+ }
54
+ export interface HandoffSentinelWiring {
55
+ sentinel: HandoffSentinel;
56
+ /** Run a planned handoff to completion (operator/test trigger). */
57
+ initiate: () => Promise<HandoffOutcome>;
58
+ }
59
+ export declare function createHandoffSentinelWiring(deps: HandoffSentinelWiringDeps): HandoffSentinelWiring;
60
+ //# sourceMappingURL=handoffSentinelWiring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoffSentinelWiring.d.ts","sourceRoot":"","sources":["../../src/core/handoffSentinelWiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEhF,MAAM,WAAW,yBAAyB;IACxC,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,6EAA6E;IAC7E,kBAAkB,EAAE,MAAM,eAAe,CAAC;IAC1C,0FAA0F;IAC1F,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,WAAW,EAAE,CAAC;IACjE,yFAAyF;IACzF,WAAW,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACtC,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,MAAM,CAAC;IAC3B,4EAA4E;IAC5E,SAAS,EAAE,CAAC,QAAQ,EAAE,aAAa,GAAG;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,gEAAgE;IAChE,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC5D,gEAAgE;IAChE,SAAS,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAClC,4DAA4D;IAC5D,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,wFAAwF;IACxF,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAChE;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,eAAe,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;CACzC;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,yBAAyB,GAAG,qBAAqB,CAoClG"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * handoffSentinelWiring — the outgoing-machine binding for the planned handoff
3
+ * (spec §8 G3e). Counterpart to handoffReceiverWiring; extracted from server.ts
4
+ * so the lease-yield orchestration is a tested unit, not inline boot code.
5
+ *
6
+ * Composes a HandoffSentinel whose ops are bound to the live components:
7
+ * - flush(): drive the live-tail so the standby is current, then POST the begin
8
+ * manifest to the peer. The manifest's threadHistoryHash is computed with the
9
+ * SAME canonical hash the incoming uses (handoffReceiverWiring.hashTopicHistory),
10
+ * so the incoming's recomputed echo can verify.
11
+ * - awaitAck(): the HandoffWireTransport's pending-ack promise.
12
+ * - validate(): Tier-1 readiness. v1 is deterministic (the sentinel's own
13
+ * ackMatches echo-verification is the substantive gate); a Haiku-class
14
+ * validator is the spec's upgrade path (tracked).
15
+ * - sendYield(): the explicit yield — the ONLY trigger for the incoming CAS.
16
+ * - demoteSelf(): step down to standby after the yield.
17
+ *
18
+ * The CRITICAL invariant (spec §8 G3e) lives in HandoffSentinel.initiate: the
19
+ * outgoing NEVER yields unless the ack is verified AND validation passes; on any
20
+ * failure it aborts and stays awake. This wiring only supplies the ops.
21
+ */
22
+ import { HandoffSentinel } from './HandoffSentinel.js';
23
+ import { hashTopicHistory } from './handoffReceiverWiring.js';
24
+ export function createHandoffSentinelWiring(deps) {
25
+ const sentinel = new HandoffSentinel({
26
+ flush: async () => {
27
+ await deps.pushTick();
28
+ const topic = deps.activeTopic();
29
+ const manifest = {
30
+ tailSeq: deps.lastTailSeq ? deps.lastTailSeq() : 0,
31
+ ingressPosition: deps.getIngressPosition(),
32
+ threadHistoryHash: hashTopicHistory(deps.getTopicHistory, topic),
33
+ topic,
34
+ };
35
+ const posted = await deps.postBegin(manifest);
36
+ if (!posted)
37
+ throw new Error('begin POST to peer failed (no reachable peer or rejected)');
38
+ return manifest;
39
+ },
40
+ awaitAck: (timeoutMs) => deps.awaitAck(timeoutMs),
41
+ validate: deps.validate ?? (async () => true),
42
+ sendYield: async () => {
43
+ const ok = await deps.sendYield();
44
+ if (!ok)
45
+ throw new Error('yield POST to peer failed');
46
+ },
47
+ demoteSelf: async () => { await deps.demoteSelf(); },
48
+ }, {
49
+ handoffAckTimeoutMs: deps.handoffAckTimeoutMs,
50
+ minHandoffIntervalMs: deps.minHandoffIntervalMs,
51
+ logger: deps.logger,
52
+ onTerminal: deps.onTerminal,
53
+ });
54
+ return {
55
+ sentinel,
56
+ initiate: () => sentinel.initiate(),
57
+ };
58
+ }
59
+ //# sourceMappingURL=handoffSentinelWiring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoffSentinelWiring.js","sourceRoot":"","sources":["../../src/core/handoffSentinelWiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,EAAE,gBAAgB,EAAoB,MAAM,4BAA4B,CAAC;AAmChF,MAAM,UAAU,2BAA2B,CAAC,IAA+B;IACzE,MAAM,QAAQ,GAAG,IAAI,eAAe,CAClC;QACE,KAAK,EAAE,KAAK,IAA4B,EAAE;YACxC,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YACjC,MAAM,QAAQ,GAAuC;gBACnD,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;gBAClD,eAAe,EAAE,IAAI,CAAC,kBAAkB,EAAE;gBAC1C,iBAAiB,EAAE,gBAAgB,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,CAAC;gBAChE,KAAK;aACN,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YAC9C,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;YAC1F,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,QAAQ,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;QACjD,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC;QAC7C,SAAS,EAAE,KAAK,IAAI,EAAE;YACpB,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACxD,CAAC;QACD,UAAU,EAAE,KAAK,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;KACrD,EACD;QACE,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;QAC7C,oBAAoB,EAAE,IAAI,CAAC,oBAAoB;QAC/C,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CACF,CAAC;IAEF,OAAO;QACL,QAAQ;QACR,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE;KACpC,CAAC;AACJ,CAAC"}
@@ -34,6 +34,14 @@ export interface ResolvedSeamlessnessConfig {
34
34
  splitBrainEscalationCooldownMs: number;
35
35
  handoffBar: 'near-instant' | 'relaxed';
36
36
  maxProcessingMs: number;
37
+ /**
38
+ * Exactly-once ingress (spec §8 G3a). When true, the inbound forward path
39
+ * gates each message through the MessageProcessingLedger (dedup redeliveries)
40
+ * and the outbound reply path commits reply_committed. DEFAULT false — ships
41
+ * dark; flip ON only after a live test-as-self confirms no false-drops, since
42
+ * this is the most critical path (every user message).
43
+ */
44
+ exactlyOnceIngress: boolean;
37
45
  protocolVersion: number;
38
46
  }
39
47
  export declare class SeamlessnessConfigError extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"seamlessnessConfig.d.ts","sourceRoot":"","sources":["../../src/core/seamlessnessConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAE/C,gEAAgE;AAChE,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,QAAQ,GAAG,KAAK,CAAC;IACpC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2BAA2B,EAAE,MAAM,CAAC;IACpC,wBAAwB,EAAE,MAAM,CAAC;IACjC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,8BAA8B,EAAE,MAAM,CAAC;IACvC,UAAU,EAAE,cAAc,GAAG,SAAS,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAG1E;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,0BAA0B,CA+B7F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,8BAA8B,CAAC,CAAC,EAAE,0BAA0B,GAAG,MAAM,EAAE,CAqCtF;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,0BAA0B,CAUhG"}
1
+ {"version":3,"file":"seamlessnessConfig.d.ts","sourceRoot":"","sources":["../../src/core/seamlessnessConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAE/C,gEAAgE;AAChE,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,QAAQ,GAAG,KAAK,CAAC;IACpC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2BAA2B,EAAE,MAAM,CAAC;IACpC,wBAAwB,EAAE,MAAM,CAAC;IACjC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,8BAA8B,EAAE,MAAM,CAAC;IACvC,UAAU,EAAE,cAAc,GAAG,SAAS,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAG1E;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,0BAA0B,CAgC7F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,8BAA8B,CAAC,CAAC,EAAE,0BAA0B,GAAG,MAAM,EAAE,CAqCtF;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,0BAA0B,CAUhG"}
@@ -64,6 +64,7 @@ export function resolveSeamlessnessConfig(mm) {
64
64
  splitBrainEscalationCooldownMs: mm?.splitBrainEscalationCooldownMs ?? 5 * 60_000,
65
65
  handoffBar: mm?.handoffBar ?? 'near-instant',
66
66
  maxProcessingMs: mm?.maxProcessingMs ?? 5 * 60_000,
67
+ exactlyOnceIngress: mm?.exactlyOnceIngress ?? false,
67
68
  protocolVersion: mm?.protocolVersion ?? SEAMLESSNESS_PROTOCOL_VERSION,
68
69
  };
69
70
  }
@@ -1 +1 @@
1
- {"version":3,"file":"seamlessnessConfig.js","sourceRoot":"","sources":["../../src/core/seamlessnessConfig.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"seamlessnessConfig.js","sourceRoot":"","sources":["../../src/core/seamlessnessConfig.ts"],"names":[],"mappings":"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"}
@@ -1468,6 +1468,12 @@ export interface MultiMachineConfig {
1468
1468
  handoffBar?: 'near-instant' | 'relaxed';
1469
1469
  /** A message-ledger entry left 'processing' past this is re-runnable by the new holder. Default 5min. */
1470
1470
  maxProcessingMs?: number;
1471
+ /**
1472
+ * Exactly-once ingress gate (spec §8 G3a). DEFAULT false — when true, inbound
1473
+ * Telegram forwards are deduped via the MessageProcessingLedger and replies
1474
+ * commit reply_committed. Ships dark; flip ON only after live test-as-self.
1475
+ */
1476
+ exactlyOnceIngress?: boolean;
1471
1477
  /**
1472
1478
  * Mesh protocol version override. Normally left unset (the build constant
1473
1479
  * SEAMLESSNESS_PROTOCOL_VERSION applies); present so migrations/tests can pin it.