instar 1.3.18 → 1.3.20

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 (101) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +104 -6
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/FencedLease.d.ts +126 -0
  5. package/dist/core/FencedLease.d.ts.map +1 -0
  6. package/dist/core/FencedLease.js +189 -0
  7. package/dist/core/FencedLease.js.map +1 -0
  8. package/dist/core/GitLeaseStore.d.ts +46 -0
  9. package/dist/core/GitLeaseStore.d.ts.map +1 -0
  10. package/dist/core/GitLeaseStore.js +79 -0
  11. package/dist/core/GitLeaseStore.js.map +1 -0
  12. package/dist/core/GitSync.d.ts +19 -0
  13. package/dist/core/GitSync.d.ts.map +1 -1
  14. package/dist/core/GitSync.js +77 -0
  15. package/dist/core/GitSync.js.map +1 -1
  16. package/dist/core/HandoffSentinel.d.ts +80 -0
  17. package/dist/core/HandoffSentinel.d.ts.map +1 -0
  18. package/dist/core/HandoffSentinel.js +141 -0
  19. package/dist/core/HandoffSentinel.js.map +1 -0
  20. package/dist/core/LeaseCoordinator.d.ts +141 -0
  21. package/dist/core/LeaseCoordinator.d.ts.map +1 -0
  22. package/dist/core/LeaseCoordinator.js +226 -0
  23. package/dist/core/LeaseCoordinator.js.map +1 -0
  24. package/dist/core/LiveTailBuffer.d.ts +64 -0
  25. package/dist/core/LiveTailBuffer.d.ts.map +1 -0
  26. package/dist/core/LiveTailBuffer.js +122 -0
  27. package/dist/core/LiveTailBuffer.js.map +1 -0
  28. package/dist/core/MultiMachineCoordinator.d.ts +49 -0
  29. package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
  30. package/dist/core/MultiMachineCoordinator.js +136 -0
  31. package/dist/core/MultiMachineCoordinator.js.map +1 -1
  32. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  33. package/dist/core/PostUpdateMigrator.js +24 -0
  34. package/dist/core/PostUpdateMigrator.js.map +1 -1
  35. package/dist/core/RegistrySyncDebouncer.d.ts +77 -0
  36. package/dist/core/RegistrySyncDebouncer.d.ts.map +1 -0
  37. package/dist/core/RegistrySyncDebouncer.js +131 -0
  38. package/dist/core/RegistrySyncDebouncer.js.map +1 -0
  39. package/dist/core/liveTailRedaction.d.ts +36 -0
  40. package/dist/core/liveTailRedaction.d.ts.map +1 -0
  41. package/dist/core/liveTailRedaction.js +57 -0
  42. package/dist/core/liveTailRedaction.js.map +1 -0
  43. package/dist/core/registryReplayGuard.d.ts +59 -0
  44. package/dist/core/registryReplayGuard.d.ts.map +1 -0
  45. package/dist/core/registryReplayGuard.js +86 -0
  46. package/dist/core/registryReplayGuard.js.map +1 -0
  47. package/dist/core/seamlessnessConfig.d.ts +79 -0
  48. package/dist/core/seamlessnessConfig.d.ts.map +1 -0
  49. package/dist/core/seamlessnessConfig.js +131 -0
  50. package/dist/core/seamlessnessConfig.js.map +1 -0
  51. package/dist/core/types.d.ts +117 -0
  52. package/dist/core/types.d.ts.map +1 -1
  53. package/dist/core/types.js.map +1 -1
  54. package/dist/core/wireRegistrySync.d.ts +32 -0
  55. package/dist/core/wireRegistrySync.d.ts.map +1 -0
  56. package/dist/core/wireRegistrySync.js +31 -0
  57. package/dist/core/wireRegistrySync.js.map +1 -0
  58. package/dist/feedback-factory/dispatch/dispatch.d.ts +47 -0
  59. package/dist/feedback-factory/dispatch/dispatch.d.ts.map +1 -0
  60. package/dist/feedback-factory/dispatch/dispatch.js +72 -0
  61. package/dist/feedback-factory/dispatch/dispatch.js.map +1 -0
  62. package/dist/feedback-factory/processor/reopen.d.ts +39 -0
  63. package/dist/feedback-factory/processor/reopen.d.ts.map +1 -0
  64. package/dist/feedback-factory/processor/reopen.js +41 -0
  65. package/dist/feedback-factory/processor/reopen.js.map +1 -0
  66. package/dist/messaging/FencedOutbox.d.ts +42 -0
  67. package/dist/messaging/FencedOutbox.d.ts.map +1 -0
  68. package/dist/messaging/FencedOutbox.js +60 -0
  69. package/dist/messaging/FencedOutbox.js.map +1 -0
  70. package/dist/messaging/MessageProcessingLedger.d.ts +105 -0
  71. package/dist/messaging/MessageProcessingLedger.d.ts.map +1 -0
  72. package/dist/messaging/MessageProcessingLedger.js +206 -0
  73. package/dist/messaging/MessageProcessingLedger.js.map +1 -0
  74. package/dist/messaging/TelegramAdapter.d.ts +18 -1
  75. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  76. package/dist/messaging/TelegramAdapter.js +50 -0
  77. package/dist/messaging/TelegramAdapter.js.map +1 -1
  78. package/dist/scaffold/templates.d.ts.map +1 -1
  79. package/dist/scaffold/templates.js +7 -0
  80. package/dist/scaffold/templates.js.map +1 -1
  81. package/dist/server/AgentServer.d.ts.map +1 -1
  82. package/dist/server/AgentServer.js +1 -0
  83. package/dist/server/AgentServer.js.map +1 -1
  84. package/dist/server/routes.d.ts +2 -0
  85. package/dist/server/routes.d.ts.map +1 -1
  86. package/dist/server/routes.js +6 -0
  87. package/dist/server/routes.js.map +1 -1
  88. package/package.json +1 -1
  89. package/src/data/builtin-manifest.json +64 -64
  90. package/src/scaffold/templates.ts +7 -0
  91. package/upgrades/1.3.19.md +23 -0
  92. package/upgrades/1.3.20.md +23 -0
  93. package/upgrades/side-effects/PR-BODY-cross-machine-seamlessness.md +63 -0
  94. package/upgrades/side-effects/cross-machine-seamlessness-g1-lease.md +71 -0
  95. package/upgrades/side-effects/cross-machine-seamlessness-g2-foundation.md +75 -0
  96. package/upgrades/side-effects/cross-machine-seamlessness-g3-telegram-contract.md +36 -0
  97. package/upgrades/side-effects/cross-machine-seamlessness-g3a-ledger-handoff.md +56 -0
  98. package/upgrades/side-effects/cross-machine-seamlessness-g3bc-livetail-outbox.md +51 -0
  99. package/upgrades/side-effects/cross-machine-seamlessness-p4-awareness-observability.md +54 -0
  100. package/upgrades/side-effects/feedback-factory-dispatch.md +31 -0
  101. package/upgrades/side-effects/feedback-factory-reopen.md +31 -0
@@ -0,0 +1,226 @@
1
+ /**
2
+ * LeaseCoordinator — drives the FencedLease over the durable (git) and fast
3
+ * (tunnel) media, and owns the lifecycle of acquisition / renewal / fencing /
4
+ * escalation. Spec §6 + §8 G1.
5
+ *
6
+ * Correctness substrate is GIT: acquisition is a compare-and-swap implemented
7
+ * as write-then-push-or-reject-reread plus epoch monotonicity (git has no
8
+ * native CAS). The TUNNEL is an optional low-latency accelerator: it can raise
9
+ * the observed epoch (speeding acquisition) but can NEVER lower it below the
10
+ * git-committed floor, and a replayed/below-floor tunnel message is dropped by
11
+ * FencedLease.acceptTunnelLease. If the tunnel is unavailable the system
12
+ * degrades to git-only — correct, just bounded by git cadence — which is why
13
+ * Phase-0 pairing worked over git alone.
14
+ *
15
+ * Renewal requires the tunnel medium when one is configured: a holder that
16
+ * cannot renew for > leaseTtlMs MUST self-suspend ingress regardless of its
17
+ * local clock (closes the tunnel-down / git-up split-authority window).
18
+ *
19
+ * The store/transport are injected so the dangerous CAS-contention and
20
+ * self-suspend logic are unit-testable with in-memory fakes.
21
+ */
22
+ export class LeaseCoordinator {
23
+ d;
24
+ fl;
25
+ nonceCounter = 0;
26
+ lastRenewOkAt;
27
+ lastObservedEpoch = 0;
28
+ suspended = false;
29
+ /**
30
+ * The freshest lease THIS machine has signed (acquisition or renewal). It is
31
+ * the authoritative low-latency copy of our own holding — we broadcast it
32
+ * over the tunnel, but git is only updated coarsely (on epoch change), so a
33
+ * renewal's new expiry lives here, not in git. Folded into effectiveView only
34
+ * while it is not superseded by a higher epoch.
35
+ */
36
+ selfIssued = null;
37
+ constructor(deps) {
38
+ this.d = deps;
39
+ this.fl = deps.lease;
40
+ this.lastRenewOkAt = this.now();
41
+ }
42
+ now() {
43
+ return (this.d.now ?? Date.now)();
44
+ }
45
+ log(m) {
46
+ this.d.logger?.(`[lease] ${m}`);
47
+ }
48
+ nextNonce() {
49
+ return ++this.nonceCounter;
50
+ }
51
+ get selfMachineId() {
52
+ return this.fl.selfMachineId;
53
+ }
54
+ get isSuspended() {
55
+ return this.suspended;
56
+ }
57
+ /**
58
+ * Compute the current effective epoch = max(tunnel-observed accepted, git).
59
+ * A tunnel lease is folded in only if acceptTunnelLease passes (valid sig,
60
+ * ≥ git floor, fresh nonce).
61
+ */
62
+ effectiveView() {
63
+ const git = this.d.store.read();
64
+ let bestLease = git.lease;
65
+ let epoch = git.epoch;
66
+ if (this.d.tunnel) {
67
+ const obs = this.d.tunnel.observed();
68
+ if (obs.lease) {
69
+ const decision = this.fl.acceptTunnelLease(obs.lease, git.epoch, obs.lastNonceByHolder);
70
+ if (decision.accept && obs.lease.epoch > epoch) {
71
+ bestLease = obs.lease;
72
+ epoch = obs.lease.epoch;
73
+ }
74
+ }
75
+ }
76
+ // Fold in our own freshest self-issued lease (a renewal's new expiry lives
77
+ // here, not in coarse git). Only while not superseded by a higher epoch.
78
+ if (this.selfIssued && this.selfIssued.holder === this.selfMachineId && this.selfIssued.epoch >= epoch) {
79
+ bestLease = this.selfIssued;
80
+ epoch = this.selfIssued.epoch;
81
+ }
82
+ return { lease: bestLease, epoch, gitEpoch: git.epoch };
83
+ }
84
+ /** Does THIS machine currently hold a valid lease at the effective epoch? */
85
+ holdsLease() {
86
+ if (this.suspended)
87
+ return false;
88
+ const view = this.effectiveView();
89
+ return this.fl.holdsValidLease(view.lease, view.epoch, this.now());
90
+ }
91
+ /** The current effective epoch (for stamping writes/sends). */
92
+ currentEpoch() {
93
+ return this.effectiveView().epoch;
94
+ }
95
+ currentHolder() {
96
+ return this.effectiveView().lease?.holder ?? null;
97
+ }
98
+ /**
99
+ * Attempt to acquire (or self-renew) the lease if eligible. Returns true if
100
+ * THIS machine holds the lease afterward. Implements the bounded-retry CAS
101
+ * with livelock backoff.
102
+ */
103
+ async acquireIfEligible() {
104
+ if (this.suspended) {
105
+ // A suspended holder may resume only by re-acquiring cleanly below.
106
+ this.suspended = false;
107
+ }
108
+ const dead = this.d.presumedDeadHolders();
109
+ let retries = 0;
110
+ // eslint-disable-next-line no-constant-condition
111
+ while (true) {
112
+ const view = this.effectiveView();
113
+ // Already hold it at the current epoch → just renew.
114
+ if (view.lease && view.lease.holder === this.selfMachineId && !this.fl.isExpired(view.lease, this.now())) {
115
+ return this.renew();
116
+ }
117
+ const decision = this.fl.canAcquire(view.lease, dead, this.now());
118
+ if (!decision.can) {
119
+ this.log(`acquire skipped: ${decision.reason}`);
120
+ return false;
121
+ }
122
+ const candidate = this.fl.buildAcquisition(view.lease, this.now(), this.nextNonce());
123
+ const res = this.d.store.casWrite(candidate);
124
+ if (res.ok) {
125
+ this.selfIssued = candidate;
126
+ await this.broadcast(candidate);
127
+ this.lastRenewOkAt = this.now();
128
+ this.emitEpoch(candidate.epoch);
129
+ this.log(`acquired lease at epoch ${candidate.epoch}`);
130
+ return true;
131
+ }
132
+ // CAS lost — someone advanced. Re-evaluate against the observed state.
133
+ const observedEpoch = res.observed.epoch;
134
+ if (observedEpoch >= candidate.epoch) {
135
+ this.log(`CAS lost to epoch ${observedEpoch} (our candidate ${candidate.epoch}) — yielding`);
136
+ this.emitEpoch(observedEpoch);
137
+ // If the winner is a presumed-dead/expired holder we'll retry; else stop.
138
+ if (!this.fl.canAcquire(res.observed.lease, dead, this.now()).can)
139
+ return false;
140
+ }
141
+ retries++;
142
+ if (this.fl.shouldBackoffAfterContention(retries, res.observed.lease?.holder ?? '')) {
143
+ this.log(`livelock backoff after ${retries} retries — yielding for ${this.fl.backoffMs}ms`);
144
+ return false;
145
+ }
146
+ }
147
+ }
148
+ /**
149
+ * Renew the held lease: re-sign with a fresh expiry, broadcast over the
150
+ * tunnel, and (coarsely, via the store on epoch change) keep git current.
151
+ * Returns false (and self-suspends) if the tunnel medium is configured but
152
+ * has been unreachable for longer than the lease TTL.
153
+ */
154
+ async renew() {
155
+ const view = this.effectiveView();
156
+ if (!view.lease || view.lease.holder !== this.selfMachineId)
157
+ return false;
158
+ // Re-sign with a fresh expiry (same epoch — renewal never advances it).
159
+ const renewed = this.fl.signLease(view.epoch, view.lease.acquiredAt, new Date(this.now() + this.fl.ttlMs).toISOString(), this.nextNonce());
160
+ // Medium-agnostic renewal requirement: the renewal must be CONFIRMED over a
161
+ // shared medium — the tunnel (reachable broadcast) when configured, else a
162
+ // durable git refresh. A holder that cannot confirm over ANY medium for
163
+ // > leaseTtlMs MUST self-suspend, rather than extend its lease locally
164
+ // forever (which is exactly the partitioned-old-awake split-brain).
165
+ let confirmed;
166
+ if (this.d.tunnel) {
167
+ confirmed = await this.d.tunnel.broadcast(renewed).catch(() => false);
168
+ }
169
+ else {
170
+ confirmed = this.d.store.refresh(renewed);
171
+ }
172
+ if (confirmed) {
173
+ this.selfIssued = renewed;
174
+ this.lastRenewOkAt = this.now();
175
+ return true;
176
+ }
177
+ if (this.now() - this.lastRenewOkAt > this.fl.ttlMs) {
178
+ this.suspended = true;
179
+ this.d.onSelfSuspend?.(`could not confirm lease over ${this.d.tunnel ? 'tunnel' : 'git'} for > leaseTtlMs (${this.fl.ttlMs}ms) — lease lapsed`);
180
+ this.log('self-suspended: renewal-confirmation lapse');
181
+ return false;
182
+ }
183
+ // Within grace: keep serving on the EXISTING (soon-to-expire) lease — do
184
+ // NOT extend selfIssued's expiry, so it lapses if we never reconfirm.
185
+ return true;
186
+ }
187
+ async broadcast(lease) {
188
+ if (!this.d.tunnel)
189
+ return;
190
+ try {
191
+ const ok = await this.d.tunnel.broadcast(lease);
192
+ if (ok)
193
+ this.lastRenewOkAt = this.now();
194
+ }
195
+ catch (err) {
196
+ this.log(`broadcast failed: ${err instanceof Error ? err.message : String(err)}`);
197
+ }
198
+ }
199
+ emitEpoch(epoch) {
200
+ if (epoch !== this.lastObservedEpoch) {
201
+ this.lastObservedEpoch = epoch;
202
+ this.d.onEpochAdvance?.(epoch);
203
+ }
204
+ }
205
+ /**
206
+ * Detection (signal only): does the synced state show contention the lease
207
+ * cannot resolve (e.g. a presumed-dead holder we cannot demote because no
208
+ * shared medium can advance the epoch)? Escalates ONCE per partition episode.
209
+ */
210
+ checkForUnresolvableSplit(partitionEpisodeId) {
211
+ const view = this.effectiveView();
212
+ if (!view.lease)
213
+ return;
214
+ const dead = this.d.presumedDeadHolders();
215
+ const holderDead = dead.has(view.lease.holder);
216
+ const cannotAdvance = this.d.tunnel ? !this.d.tunnel.isReachable() : false;
217
+ if (holderDead && cannotAdvance && view.lease.holder !== this.selfMachineId) {
218
+ this.d.onEscalate?.({
219
+ partitionEpisodeId,
220
+ holder: view.lease.holder,
221
+ reason: 'presumed-dead holder cannot be demoted — no shared medium to advance the epoch',
222
+ });
223
+ }
224
+ }
225
+ }
226
+ //# sourceMappingURL=LeaseCoordinator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeaseCoordinator.js","sourceRoot":"","sources":["../../src/core/LeaseCoordinator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * LiveTailBuffer — the standby-side persisted live-tail with sequence-dedup
3
+ * (spec §8 G3b). The holder pushes monotonic-sequence flushes of the live
4
+ * conversation tail; the standby applies a flush ONLY if its sequence is
5
+ * lastAppliedSeq + 1, coalescing/holding out-of-order flushes and dropping
6
+ * duplicates — so an at-least-once tunnel redelivery cannot double-append and
7
+ * corrupt the persisted context window (which would make a post-failover reply
8
+ * misrepresent history, violating acceptance criterion 3).
9
+ *
10
+ * Out-of-order flushes are held only until liveTailOutOfOrderTimeoutMs; if the
11
+ * gap is never filled (sender died mid-sequence) the buffer DECLARES THE GAP
12
+ * UNFILLABLE, discards the held flushes, and proceeds with the last contiguous
13
+ * sequence as its resumable tail — bounding the holdout buffer and never
14
+ * wedging the standby behind a gap the at-least-once channel will re-present.
15
+ *
16
+ * Bounded by liveTailMaxBytesPerTopic (drop-oldest). Pure logic — the encrypted
17
+ * transport feeds applyFlush(); persistence is the caller's (an injected sink).
18
+ */
19
+ export interface LiveTailFlush {
20
+ seq: number;
21
+ topic: string;
22
+ /** Already-redacted, already-decrypted tail content for this flush. */
23
+ content: string;
24
+ /** Bytes (for the cap); defaults to content length. */
25
+ bytes?: number;
26
+ receivedAtMs?: number;
27
+ }
28
+ export interface LiveTailBufferConfig {
29
+ outOfOrderTimeoutMs: number;
30
+ maxBytesPerTopic: number;
31
+ now?: () => number;
32
+ logger?: (msg: string) => void;
33
+ }
34
+ export interface ApplyResult {
35
+ applied: boolean;
36
+ reason: 'applied' | 'duplicate' | 'held-out-of-order' | 'gap-discarded-then-applied';
37
+ }
38
+ export declare class LiveTailBuffer {
39
+ private readonly cfg;
40
+ private topics;
41
+ constructor(cfg: LiveTailBufferConfig);
42
+ private now;
43
+ private log;
44
+ private topic;
45
+ /**
46
+ * Apply a flush. Returns whether it was applied (or held/dropped). Enforces
47
+ * exactly-once contiguous application: dup dropped, out-of-order held, gap
48
+ * timed-out and discarded.
49
+ */
50
+ applyFlush(flush: LiveTailFlush): ApplyResult;
51
+ /** Discard held flushes whose gap has exceeded the out-of-order timeout. */
52
+ private evictTimedOutGaps;
53
+ /** A periodic tick the caller invokes to time out gaps even without new flushes. */
54
+ tick(topic: string): void;
55
+ private commit;
56
+ /** The resumable tail for a topic (the contiguous applied content). */
57
+ getTail(topic: string): {
58
+ lastAppliedSeq: number;
59
+ content: string;
60
+ };
61
+ getLastAppliedSeq(topic: string): number;
62
+ heldCount(topic: string): number;
63
+ }
64
+ //# sourceMappingURL=LiveTailBuffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiveTailBuffer.d.ts","sourceRoot":"","sources":["../../src/core/LiveTailBuffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AASD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,mBAAmB,GAAG,4BAA4B,CAAC;CACtF;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAuB;IAC3C,OAAO,CAAC,MAAM,CAAiC;gBAEnC,GAAG,EAAE,oBAAoB;IAIrC,OAAO,CAAC,GAAG;IAGX,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,KAAK;IASb;;;;OAIG;IACH,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,WAAW;IAwC7C,4EAA4E;IAC5E,OAAO,CAAC,iBAAiB;IAWzB,oFAAoF;IACpF,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKzB,OAAO,CAAC,MAAM;IAYd,uEAAuE;IACvE,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAMnE,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAIxC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAGjC"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * LiveTailBuffer — the standby-side persisted live-tail with sequence-dedup
3
+ * (spec §8 G3b). The holder pushes monotonic-sequence flushes of the live
4
+ * conversation tail; the standby applies a flush ONLY if its sequence is
5
+ * lastAppliedSeq + 1, coalescing/holding out-of-order flushes and dropping
6
+ * duplicates — so an at-least-once tunnel redelivery cannot double-append and
7
+ * corrupt the persisted context window (which would make a post-failover reply
8
+ * misrepresent history, violating acceptance criterion 3).
9
+ *
10
+ * Out-of-order flushes are held only until liveTailOutOfOrderTimeoutMs; if the
11
+ * gap is never filled (sender died mid-sequence) the buffer DECLARES THE GAP
12
+ * UNFILLABLE, discards the held flushes, and proceeds with the last contiguous
13
+ * sequence as its resumable tail — bounding the holdout buffer and never
14
+ * wedging the standby behind a gap the at-least-once channel will re-present.
15
+ *
16
+ * Bounded by liveTailMaxBytesPerTopic (drop-oldest). Pure logic — the encrypted
17
+ * transport feeds applyFlush(); persistence is the caller's (an injected sink).
18
+ */
19
+ export class LiveTailBuffer {
20
+ cfg;
21
+ topics = new Map();
22
+ constructor(cfg) {
23
+ this.cfg = cfg;
24
+ }
25
+ now() {
26
+ return (this.cfg.now ?? Date.now)();
27
+ }
28
+ log(m) {
29
+ this.cfg.logger?.(`[live-tail] ${m}`);
30
+ }
31
+ topic(id) {
32
+ let t = this.topics.get(id);
33
+ if (!t) {
34
+ t = { lastAppliedSeq: 0, applied: [], held: new Map(), bytes: 0 };
35
+ this.topics.set(id, t);
36
+ }
37
+ return t;
38
+ }
39
+ /**
40
+ * Apply a flush. Returns whether it was applied (or held/dropped). Enforces
41
+ * exactly-once contiguous application: dup dropped, out-of-order held, gap
42
+ * timed-out and discarded.
43
+ */
44
+ applyFlush(flush) {
45
+ const t = this.topic(flush.topic);
46
+ const seq = flush.seq;
47
+ if (seq <= t.lastAppliedSeq) {
48
+ return { applied: false, reason: 'duplicate' };
49
+ }
50
+ if (seq === t.lastAppliedSeq + 1) {
51
+ this.commit(t, flush);
52
+ // Drain any held flushes that are now contiguous.
53
+ let next = t.lastAppliedSeq + 1;
54
+ while (t.held.has(next)) {
55
+ const h = t.held.get(next);
56
+ t.held.delete(next);
57
+ this.commit(t, h);
58
+ next = t.lastAppliedSeq + 1;
59
+ }
60
+ return { applied: true, reason: 'applied' };
61
+ }
62
+ // seq > lastAppliedSeq + 1 → there's a gap. Check whether the earliest held
63
+ // gap has timed out; if so, declare unfillable, discard held, and treat this
64
+ // flush's predecessor chain as the new contiguous baseline.
65
+ const evicted = this.evictTimedOutGaps(t);
66
+ if (evicted) {
67
+ // After discarding the unfillable gap, accept this flush as the new
68
+ // baseline (we proceed with the last contiguous sequence == this one).
69
+ t.lastAppliedSeq = seq - 1;
70
+ this.commit(t, flush);
71
+ return { applied: true, reason: 'gap-discarded-then-applied' };
72
+ }
73
+ // Hold it for the out-of-order window.
74
+ if (!t.held.has(seq)) {
75
+ t.held.set(seq, { ...flush, heldAtMs: this.now() });
76
+ }
77
+ return { applied: false, reason: 'held-out-of-order' };
78
+ }
79
+ /** Discard held flushes whose gap has exceeded the out-of-order timeout. */
80
+ evictTimedOutGaps(t) {
81
+ if (t.held.size === 0)
82
+ return false;
83
+ const oldestHeldAt = Math.min(...[...t.held.values()].map((h) => h.heldAtMs));
84
+ if (this.now() - oldestHeldAt > this.cfg.outOfOrderTimeoutMs) {
85
+ this.log(`gap unfillable after ${this.cfg.outOfOrderTimeoutMs}ms — discarding ${t.held.size} held flush(es)`);
86
+ t.held.clear();
87
+ return true;
88
+ }
89
+ return false;
90
+ }
91
+ /** A periodic tick the caller invokes to time out gaps even without new flushes. */
92
+ tick(topic) {
93
+ const t = this.topics.get(topic);
94
+ if (t)
95
+ this.evictTimedOutGaps(t);
96
+ }
97
+ commit(t, flush) {
98
+ const bytes = flush.bytes ?? Buffer.byteLength(flush.content);
99
+ t.applied.push({ ...flush });
100
+ t.bytes += bytes;
101
+ t.lastAppliedSeq = flush.seq;
102
+ // Enforce the per-topic byte cap (drop-oldest).
103
+ while (t.bytes > this.cfg.maxBytesPerTopic && t.applied.length > 1) {
104
+ const dropped = t.applied.shift();
105
+ t.bytes -= dropped.bytes ?? Buffer.byteLength(dropped.content);
106
+ }
107
+ }
108
+ /** The resumable tail for a topic (the contiguous applied content). */
109
+ getTail(topic) {
110
+ const t = this.topics.get(topic);
111
+ if (!t)
112
+ return { lastAppliedSeq: 0, content: '' };
113
+ return { lastAppliedSeq: t.lastAppliedSeq, content: t.applied.map((f) => f.content).join('') };
114
+ }
115
+ getLastAppliedSeq(topic) {
116
+ return this.topics.get(topic)?.lastAppliedSeq ?? 0;
117
+ }
118
+ heldCount(topic) {
119
+ return this.topics.get(topic)?.held.size ?? 0;
120
+ }
121
+ }
122
+ //# sourceMappingURL=LiveTailBuffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiveTailBuffer.js","sourceRoot":"","sources":["../../src/core/LiveTailBuffer.ts"],"names":[],"mappings":"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"}
@@ -19,7 +19,20 @@ import { HeartbeatManager } from './HeartbeatManager.js';
19
19
  import { SecurityLog } from './SecurityLog.js';
20
20
  import { NonceStore } from './NonceStore.js';
21
21
  import type { StateManager } from './StateManager.js';
22
+ import type { LeaseCoordinator } from './LeaseCoordinator.js';
22
23
  import type { MachineRole, MachineIdentity, MultiMachineConfig, CoordinationMode } from './types.js';
24
+ /** Observability shape for /health.multiMachine.syncStatus (spec §11). */
25
+ export interface MultiMachineSyncStatus {
26
+ enabled: boolean;
27
+ role: MachineRole;
28
+ leaseHolder: string | null;
29
+ leaseEpoch: number;
30
+ holdsLease: boolean;
31
+ /** 'clear' | 'contested' (more than one awake machine in the registry) | 'self-suspended'. */
32
+ splitBrainState: 'clear' | 'contested' | 'self-suspended';
33
+ protocolVersion: number;
34
+ awakeMachineCount: number;
35
+ }
23
36
  export interface CoordinatorConfig {
24
37
  /** State directory (.instar) */
25
38
  stateDir: string;
@@ -51,6 +64,14 @@ export declare class MultiMachineCoordinator extends EventEmitter {
51
64
  /** Integrated-Being v1 — tracks whether we've already emitted the
52
65
  * per-machine-ledger warning this boot. Spec §Multi-machine. */
53
66
  private integratedBeingWarningEmitted;
67
+ /**
68
+ * Cross-Machine Seamlessness §6 — the fenced lease is the authority for
69
+ * "awake". When attached, holdsLease() gates the hot path (the structural
70
+ * demotion the Phase-0 split-brain was missing). Null on single-machine /
71
+ * non-git meshes, where the heartbeat path remains the authority.
72
+ */
73
+ private leaseCoordinator;
74
+ private leaseTicking;
54
75
  constructor(state: StateManager, config: CoordinatorConfig);
55
76
  /** Whether multi-machine is enabled (has identity). */
56
77
  get enabled(): boolean;
@@ -99,6 +120,34 @@ export declare class MultiMachineCoordinator extends EventEmitter {
99
120
  * Returns true if this machine should NOT process messages.
100
121
  */
101
122
  shouldSkipProcessing(): boolean;
123
+ /**
124
+ * Attach the LeaseCoordinator (built once gitSync exists in server boot).
125
+ * From here the lease is the authority for awake/standby. Wires the lease's
126
+ * epoch-advance to a coordinator `leaseEpochChange` event so the registry
127
+ * sync debouncer pushes the new epoch durably.
128
+ */
129
+ attachLeaseCoordinator(lc: LeaseCoordinator): void;
130
+ /** Whether this machine structurally holds the lease (false if none attached). */
131
+ holdsLease(): boolean;
132
+ /**
133
+ * Observability snapshot for /health.multiMachine.syncStatus (spec §11).
134
+ * Always returns valid fields (never null/throws) — this is the Phase-1
135
+ * "feature is alive" surface. On a single-machine install it reports the
136
+ * trivially-held lease.
137
+ */
138
+ getSyncStatus(): MultiMachineSyncStatus;
139
+ /**
140
+ * Initialize the lease-based role at boot: attempt acquisition if eligible,
141
+ * then reconcile _role + StateManager read-only to whether we hold the lease.
142
+ */
143
+ initializeLease(): Promise<void>;
144
+ /**
145
+ * Drive the lease on each monitor tick: renew if we hold it, else attempt
146
+ * failover acquisition. Reconciles role afterward. Fire-and-forget safe.
147
+ */
148
+ private tickLease;
149
+ /** Bring _role + read-only into line with whether we hold the lease. */
150
+ private reconcileRoleToLease;
102
151
  /**
103
152
  * Start periodic heartbeat writes (awake machine only).
104
153
  */
@@ -1 +1 @@
1
- {"version":3,"file":"MultiMachineCoordinator.d.ts","sourceRoot":"","sources":["../../src/core/MultiMachineCoordinator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAUrG,MAAM,WAAW,iBAAiB;IAChC,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,wDAAwD;IACxD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,yDAAyD;IACzD,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,0CAA0C;IAC1C,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,6BAA6B;IAC7B,UAAU,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,KAAK,IAAI,CAAC;CAC1D;AAID,qBAAa,uBAAwB,SAAQ,YAAY;IACvD,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,mBAAmB,CAA+C;IAC1E,OAAO,CAAC,mBAAmB,CAA+C;IAC1E;qEACiE;IACjE,OAAO,CAAC,6BAA6B,CAAkB;gBAE3C,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,iBAAiB;IAc1D,uDAAuD;IACvD,IAAI,OAAO,IAAI,OAAO,CAA0B;IAEhD,mCAAmC;IACnC,IAAI,IAAI,IAAI,WAAW,CAAuB;IAE9C,yDAAyD;IACzD,IAAI,QAAQ,IAAI,eAAe,GAAG,IAAI,CAA2B;IAEjE,2DAA2D;IAC3D,IAAI,OAAO,IAAI,OAAO,CAAmC;IAEzD,0DAA0D;IAC1D,IAAI,gBAAgB,IAAI,gBAAgB,CAEvC;IAED,kDAAkD;IAClD,IAAI,QAAQ;;;;;MAOX;IAID;;;OAGG;IACH,KAAK,IAAI,WAAW;IAuGpB;;OAEG;IACH;;;;OAIG;IACH,OAAO,CAAC,sCAAsC;IAiB9C,IAAI,IAAI,IAAI;IAcZ;;;OAGG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IA8BpC;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAgCrC;;;OAGG;IACH,oBAAoB,IAAI,OAAO;IAY/B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA4B5B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAc7B;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAoB7B"}
1
+ {"version":3,"file":"MultiMachineCoordinator.d.ts","sourceRoot":"","sources":["../../src/core/MultiMachineCoordinator.ts"],"names":[],"mappings":"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"}