instar 1.3.521 → 1.3.523

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 (54) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +100 -1
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +16 -1
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/MeshRpc.d.ts +5 -0
  8. package/dist/core/MeshRpc.d.ts.map +1 -1
  9. package/dist/core/MeshRpc.js +8 -0
  10. package/dist/core/MeshRpc.js.map +1 -1
  11. package/dist/core/StoreSnapshot.d.ts +455 -0
  12. package/dist/core/StoreSnapshot.d.ts.map +1 -0
  13. package/dist/core/StoreSnapshot.js +786 -0
  14. package/dist/core/StoreSnapshot.js.map +1 -0
  15. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  16. package/dist/core/devGatedFeatures.js +6 -0
  17. package/dist/core/devGatedFeatures.js.map +1 -1
  18. package/dist/core/stateSyncConfig.js +1 -1
  19. package/dist/core/stateSyncConfig.js.map +1 -1
  20. package/dist/core/storeSnapshotBuild.worker.d.ts +2 -0
  21. package/dist/core/storeSnapshotBuild.worker.d.ts.map +1 -0
  22. package/dist/core/storeSnapshotBuild.worker.js +32 -0
  23. package/dist/core/storeSnapshotBuild.worker.js.map +1 -0
  24. package/dist/core/types.d.ts +17 -0
  25. package/dist/core/types.d.ts.map +1 -1
  26. package/dist/core/types.js.map +1 -1
  27. package/dist/monitoring/OrphanedWorkSentinel.d.ts +148 -0
  28. package/dist/monitoring/OrphanedWorkSentinel.d.ts.map +1 -0
  29. package/dist/monitoring/OrphanedWorkSentinel.js +177 -0
  30. package/dist/monitoring/OrphanedWorkSentinel.js.map +1 -0
  31. package/dist/monitoring/guardManifest.d.ts.map +1 -1
  32. package/dist/monitoring/guardManifest.js +14 -0
  33. package/dist/monitoring/guardManifest.js.map +1 -1
  34. package/dist/monitoring/orphanedWorkGit.d.ts +30 -0
  35. package/dist/monitoring/orphanedWorkGit.d.ts.map +1 -0
  36. package/dist/monitoring/orphanedWorkGit.js +124 -0
  37. package/dist/monitoring/orphanedWorkGit.js.map +1 -0
  38. package/dist/server/AgentServer.d.ts +3 -0
  39. package/dist/server/AgentServer.d.ts.map +1 -1
  40. package/dist/server/AgentServer.js +1 -0
  41. package/dist/server/AgentServer.js.map +1 -1
  42. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  43. package/dist/server/CapabilityIndex.js +1 -0
  44. package/dist/server/CapabilityIndex.js.map +1 -1
  45. package/dist/server/routes.d.ts +3 -0
  46. package/dist/server/routes.d.ts.map +1 -1
  47. package/dist/server/routes.js +11 -0
  48. package/dist/server/routes.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/data/builtin-manifest.json +47 -47
  51. package/upgrades/1.3.522.md +25 -0
  52. package/upgrades/1.3.523.md +31 -0
  53. package/upgrades/side-effects/hlc-step3-snapshot-tail.md +133 -0
  54. package/upgrades/side-effects/orphaned-work-sentinel.md +40 -0
@@ -0,0 +1,455 @@
1
+ /**
2
+ * StoreSnapshot — single-origin snapshot-then-tail machinery (WS2 replicated-store
3
+ * foundation, Component 4 / build-order step 3).
4
+ *
5
+ * Spec: docs/specs/multi-machine-replicated-store-foundation.md §6 (snapshot-then-
6
+ * tail), §6.1 (single-origin anti-forgery), §6.2 (snapshot format + watermark
7
+ * VECTOR), §6.3 (the cutover apply path), §6.4 (HLC demoted to SECONDARY dedup),
8
+ * §6.5 (tombstone high-water seed), §6.6 (why a per-(origin,kind) seq-watermark
9
+ * vector, not a scalar), §8.2 (snapshot-cache fixed ceiling).
10
+ *
11
+ * GENERIC foundation machinery ONLY — there is NO concrete store kind here
12
+ * (preferences/relationships are later consumers, WS2.1+). Everything ships DARK
13
+ * behind `multiMachine.stateSync.<store>.enabled` (default false); a single-machine
14
+ * agent (no peers to pull from) is a strict no-op.
15
+ *
16
+ * This module is PURE LOGIC. The 67MB-class whole-store materialization happens OFF
17
+ * the event loop in `storeSnapshotBuild.worker.ts` (the instar#1069 requirement,
18
+ * mirroring CartographerSweepEngine / cartographerDetect.worker.ts) — this file
19
+ * exports the pure `materializeSnapshot()` the worker dispatches to, plus the
20
+ * non-worker cache/breaker/cutover seams that run on the main thread but do only
21
+ * bounded, O(records-in-one-batch) work. No fs, no Date directly, no network.
22
+ *
23
+ * THE CORRECTNESS BORROW (§6.3): the whole no-gap/no-double-apply guarantee is
24
+ * inherited from the EXISTING seq-contiguous transport. This module:
25
+ * 1. builds a single-origin snapshot (origin === serving machine, §6.1),
26
+ * 2. computes a per-(origin,kind) seq-watermark VECTOR from the entries that
27
+ * actually materialized (§6.2 — it cannot lie),
28
+ * 3. seeds `PeerMeta.kinds[kind].lastHeldSeq = snapshotSeq` (§6.3 step 3),
29
+ * 4. then TAILS via the UNCHANGED `buildServeBatch(kind, snapshotSeq, M)` — the
30
+ * existing applier seq-contiguity (lastHeldSeq+1) does the gap/dup work.
31
+ * There is NO new gap-detection here. HLC is a SECONDARY dedup hint only (§6.4).
32
+ */
33
+ import { type HlcTimestamp } from './HybridLogicalClock.js';
34
+ /**
35
+ * One materialized record in a single-origin snapshot (§6.2). Carries its `hlc`
36
+ * (the merge total order), `op` (put/delete tombstone), `recordKey`, `origin`
37
+ * (=== the serving machine, §6.1), and the opaque store-specific `data`. A delete
38
+ * is a TOMBSTONE record (kept within the tombstone horizon, §6.5), never a physical
39
+ * removal — so the apply path can resolve a delete↔put race deterministically.
40
+ */
41
+ export interface SnapshotRecord {
42
+ recordKey: string;
43
+ hlc: HlcTimestamp;
44
+ op: 'put' | 'delete';
45
+ origin: string;
46
+ /** The store-specific portion of the journal `data` (envelope fields stripped). */
47
+ data: Record<string, unknown>;
48
+ }
49
+ /**
50
+ * The per-(origin,kind) seq-watermark — a VECTOR component, not a scalar (§6.6).
51
+ * `snapshotSeq` is the highest journal `seq` of an origin-M-authored entry that
52
+ * materialized into the snapshot. It is THE TAIL CURSOR: the cutover seeds
53
+ * `lastHeldSeq = snapshotSeq` then tails `buildServeBatch(kind, snapshotSeq, M)`.
54
+ */
55
+ export interface SnapshotKindWatermark {
56
+ /** The highest seq this snapshot materialized from for (origin, kind). */
57
+ snapshotSeq: number;
58
+ }
59
+ /**
60
+ * The snapshot's seq-watermark — a per-(origin,kind) VECTOR (§6.2 / §6.6). One
61
+ * snapshot is single-origin, so `origin` is a scalar; the VECTOR is over the
62
+ * contributing KINDS (a store may ride more than one journal kind). `maxHlc` is
63
+ * SECONDARY (§6.4): an idempotency/dedup hint at cutover, NEVER the tail cursor.
64
+ */
65
+ export interface SnapshotWatermark {
66
+ /** === the serving machine (single-origin, §6.1). */
67
+ origin: string;
68
+ /** Per contributing (origin, kind) stream: the highest seq materialized. */
69
+ kinds: Record<string, SnapshotKindWatermark>;
70
+ /** SECONDARY dedup hint only — the max HLC over all records (§6.4). */
71
+ maxHlc: HlcTimestamp;
72
+ }
73
+ /**
74
+ * A built single-origin snapshot of store S from origin M (§6.2): the CURRENT
75
+ * materialized state (latest record per recordKey; deletes tombstoned), plus the
76
+ * watermark vector. `deleteWatermarks` carries the per-recordKey deleted-keys
77
+ * high-water SEED (§6.5) — the HLC of the highest delete the snapshot reflects per
78
+ * key — so the receiver can seed its resurrection guard from the snapshot.
79
+ */
80
+ export interface StoreSnapshot {
81
+ /** The store key (config sub-key / advert suffix, e.g. 'pref'). */
82
+ store: string;
83
+ /** === the serving machine (single-origin invariant, §6.1). */
84
+ origin: string;
85
+ /** The materialized records (latest per recordKey, deletes tombstoned). */
86
+ records: SnapshotRecord[];
87
+ /** The per-(origin,kind) seq-watermark VECTOR + the secondary maxHlc (§6.2). */
88
+ watermark: SnapshotWatermark;
89
+ /**
90
+ * The tombstone high-water SEED (§6.5): per recordKey that is currently a
91
+ * tombstone in this snapshot, the HLC of that delete. A receiver seeds its
92
+ * deleted-keys high-water from these so a stale pre-delete put cannot resurrect
93
+ * the key even after the tombstone record itself rotates out.
94
+ */
95
+ deleteWatermarks: Record<string, HlcTimestamp>;
96
+ /** Total serialized byte size — for the cache byte ceiling (§8.2). */
97
+ sizeBytes: number;
98
+ /**
99
+ * True iff the materialization hit `maxSnapshotBytes` and DROPPED records while
100
+ * keeping the full watermark (§6.3 boundedness). A truncated snapshot is a
101
+ * SUB-WATERMARK GAP TRAP: it seeds `lastHeldSeq = snapshotSeq` but does NOT
102
+ * contain every record at-or-below that seq, so the subsequent tail (which serves
103
+ * only `seq > snapshotSeq`) would NEVER replay the dropped records — a silent gap
104
+ * the seq-contiguity cannot catch (it starts above them). Therefore a truncated
105
+ * snapshot is structurally REFUSED, never applied: `applySnapshotCutover` throws on
106
+ * it and `StoreSnapshotEngine.serveSnapshot` returns `build-truncated` so the caller
107
+ * falls back to a from-genesis tail (the safe, complete path). The flag travels ON
108
+ * the snapshot (not just the serve-result envelope) so the refusal cannot be
109
+ * bypassed by a consumer that consumes a bare `StoreSnapshot`.
110
+ */
111
+ truncated: boolean;
112
+ }
113
+ /**
114
+ * The minimal journal-entry shape the materializer reads. Matches `JournalEntry`
115
+ * (CoherenceJournal) but typed locally so this module stays import-light and the
116
+ * worker can pass plain objects across the thread boundary. `data` must carry the
117
+ * replicated-record envelope fields (recordKey/hlc/op/origin) — already validated
118
+ * by the applier before they landed; the materializer re-narrows defensively.
119
+ */
120
+ export interface RawJournalEntry {
121
+ seq: number;
122
+ ts: string;
123
+ machine: string;
124
+ kind: string;
125
+ data: Record<string, unknown>;
126
+ }
127
+ /** Input to the (worker-dispatched) pure materializer. */
128
+ export interface MaterializeInput {
129
+ /** The store key (advert suffix). */
130
+ store: string;
131
+ /** The single origin (=== serving machine, §6.1) this snapshot is built for. */
132
+ origin: string;
133
+ /**
134
+ * The own-stream entries to materialize, keyed by journal kind. EVERY entry MUST
135
+ * have `entry.machine === origin` (the §6.1 single-origin invariant) — the
136
+ * materializer DROPS (counts) any cross-origin entry rather than trusting it, so
137
+ * a buggy caller cannot smuggle a foreign-origin record into a single-origin
138
+ * snapshot. Within a kind, order does not matter — the materializer resolves by
139
+ * HLC-max per recordKey.
140
+ */
141
+ entriesByKind: Record<string, RawJournalEntry[]>;
142
+ /**
143
+ * The per-kind byte ceiling for the materialized snapshot. A materialization
144
+ * that would exceed it is TRUNCATED deterministically (highest-seq-first kept)
145
+ * and the result is flagged `truncated` — bounded, never an unbounded build
146
+ * (instar#1069). 0/absent ⇒ no per-build truncation (the cache byte ceiling
147
+ * still bounds what is RETAINED).
148
+ */
149
+ maxSnapshotBytes?: number;
150
+ }
151
+ /** Result of the pure materializer (what the worker posts back). */
152
+ export interface MaterializeResult {
153
+ snapshot: StoreSnapshot;
154
+ /** Count of entries dropped for `entry.machine !== origin` (anti-forgery, §6.1). */
155
+ crossOriginDropped: number;
156
+ /** Count of entries dropped for a malformed/missing envelope (defensive re-narrow). */
157
+ malformedDropped: number;
158
+ /** True iff the materialization hit `maxSnapshotBytes` and was truncated. */
159
+ truncated: boolean;
160
+ }
161
+ /**
162
+ * Materialize a single-origin snapshot (§6.1 + §6.2). PURE — no I/O, no Date, no
163
+ * network. This is the function the worker dispatches to (the heavy walk runs on
164
+ * the worker thread).
165
+ *
166
+ * Algorithm:
167
+ * 1. For every contributing kind, fold the own-stream entries into a per-recordKey
168
+ * LATEST record (HLC-max via HybridLogicalClock.compare). A cross-origin entry
169
+ * (`entry.machine !== origin`) is DROPPED + counted — the §6.1 anti-forgery
170
+ * invariant enforced at materialization, not trusted to the caller.
171
+ * 2. Track the per-(origin,kind) `snapshotSeq` = the highest origin-authored
172
+ * `entry.seq` reflected (§6.2 — computed from what materialized, cannot lie).
173
+ * 3. Track the per-recordKey delete high-water (§6.5) for the seed.
174
+ * 4. Emit the materialized set (latest per key, deletes tombstoned) + the
175
+ * watermark vector + the secondary maxHlc.
176
+ *
177
+ * Boundedness (instar#1069): the per-key fold is O(entries); the result is the
178
+ * live key count (≤ entries). `maxSnapshotBytes` deterministically truncates a
179
+ * pathologically-large materialization (highest-seq-first kept).
180
+ */
181
+ export declare function materializeSnapshot(input: MaterializeInput): MaterializeResult;
182
+ /** A cache key (§8.2): keyed by (origin, store, maxHlc). Deterministic string. */
183
+ export declare function snapshotCacheKey(origin: string, store: string, maxHlc: HlcTimestamp): string;
184
+ /**
185
+ * The snapshot cache — a FIXED-ceiling ring (§8.2), NOT pool-size-scaled. Bounded
186
+ * by BOTH `maxCachedSnapshots` (count) AND `maxCacheBytes` (bytes), whichever
187
+ * binds first; LRU eviction with a monotonic `cacheLossCounter` (mirroring the
188
+ * quarantine ring's lossCounter). An evicted snapshot is just rebuilt on next
189
+ * demand (breaker-gated, §6.3) — eviction is a recompute, never a correctness
190
+ * loss; the counter makes the recompute visible in degradation.
191
+ *
192
+ * A stale entry (its source stream advanced past the cached maxHlc) is dropped on
193
+ * the next put for the same (origin, store) — the cache key embeds maxHlc, so a
194
+ * fresher build is a DIFFERENT key; `invalidateOlder` drops the superseded ones.
195
+ */
196
+ export declare class SnapshotCache {
197
+ private readonly maxCount;
198
+ private readonly maxBytes;
199
+ private readonly entries;
200
+ private clock;
201
+ private bytes;
202
+ private _cacheLossCounter;
203
+ constructor(opts: {
204
+ maxCachedSnapshots: number;
205
+ maxCacheBytes: number;
206
+ });
207
+ /** Monotonic count of LRU evictions (§8.2 — surfaced in degradation). */
208
+ get cacheLossCounter(): number;
209
+ /** Current live entry count. */
210
+ get size(): number;
211
+ /** Current total cached bytes. */
212
+ get byteSize(): number;
213
+ /** Get a cached snapshot by key (refreshes its LRU position). */
214
+ get(key: string): StoreSnapshot | undefined;
215
+ /**
216
+ * Put a freshly-built snapshot. Drops any STALE entry for the same (origin,
217
+ * store) whose maxHlc is older than this one (the source stream advanced past
218
+ * it, §8.2) — those are superseded, not LRU-evicted (no lossCounter bump). Then
219
+ * enforces the count + byte ceilings via LRU eviction (lossCounter bumped).
220
+ */
221
+ put(snapshot: StoreSnapshot): void;
222
+ /** Drop cached entries for the same (origin, store) with a strictly-older maxHlc. */
223
+ private invalidateOlder;
224
+ /** Evict LRU entries until BOTH the count and byte ceilings hold (§8.2). */
225
+ private evictToBounds;
226
+ /** Iterate cached snapshots for a given (origin, store) — bounded by maxCount.
227
+ * Does NOT touch LRU (a read-only scan; the caller touches LRU on a real serve). */
228
+ entriesFor(origin: string, store: string): Iterable<StoreSnapshot>;
229
+ /** Drop ALL cached entries for an origin (the §7.4 rollback-unmerge hook). */
230
+ dropOrigin(origin: string): void;
231
+ }
232
+ /** Options for the rebuild breaker (§6.3 minimum-rebuild window + frequency cap). */
233
+ export interface RebuildBreakerOptions {
234
+ /** Minimum ms between actual rebuilds for one (peer, origin, store) — a flapping
235
+ * peer is served the CACHED snapshot within this window. Default 30s. */
236
+ minRebuildIntervalMs?: number;
237
+ /** Max rebuilds allowed within `windowMs` before the breaker trips. Default 5. */
238
+ maxRebuildsPerWindow?: number;
239
+ /** The rolling window for the frequency cap. Default 5min. */
240
+ windowMs?: number;
241
+ /** How long the breaker stays tripped once it fires. Default 1min. */
242
+ cooldownMs?: number;
243
+ }
244
+ /** A rebuild decision (§6.3): allow a fresh build, or serve the cache / refuse. */
245
+ export type RebuildDecision = {
246
+ allow: true;
247
+ } | {
248
+ allow: false;
249
+ reason: 'within-min-interval' | 'breaker-open';
250
+ serveCache: boolean;
251
+ };
252
+ /**
253
+ * Per-peer snapshot-build-frequency breaker (§6.3). Prevents rebuild storms from a
254
+ * flapping peer: within `minRebuildIntervalMs` the cached snapshot is served; more
255
+ * than `maxRebuildsPerWindow` actual rebuilds in `windowMs` trips a breaker that
256
+ * stays open for `cooldownMs`. Bounded (No-Unbounded-Loops). PURE-ish: the clock
257
+ * is injected so it is unit-testable across simulated windows.
258
+ *
259
+ * Keyed by (peer, origin, store) so one peer flapping on store A does not throttle
260
+ * its store-B rebuilds, and two peers requesting the same snapshot are independent.
261
+ */
262
+ export declare class SnapshotRebuildBreaker {
263
+ private readonly minIntervalMs;
264
+ private readonly maxPerWindow;
265
+ private readonly windowMs;
266
+ private readonly cooldownMs;
267
+ private readonly now;
268
+ /** Per key: the recent rebuild timestamps (bounded by the window) + breaker state.
269
+ * `lastRebuildAt === null` means "never rebuilt" — distinct from a real now()=0
270
+ * timestamp, so the min-interval check is correct even when the clock starts at 0. */
271
+ private readonly state;
272
+ constructor(opts: RebuildBreakerOptions & {
273
+ now: () => number;
274
+ });
275
+ private keyOf;
276
+ /**
277
+ * Decide whether a fresh rebuild is allowed RIGHT NOW for (peer, origin, store).
278
+ * Returns `{ allow: true }` to build; otherwise a refusal with `serveCache`
279
+ * (true = serve the cached snapshot instead of building). This does NOT record
280
+ * the rebuild — call `recordRebuild()` after an ACTUAL build completes, so a
281
+ * cache-served request never counts against the frequency cap.
282
+ */
283
+ shouldRebuild(peer: string, origin: string, store: string): RebuildDecision;
284
+ /** Record that an actual rebuild completed for (peer, origin, store). */
285
+ recordRebuild(peer: string, origin: string, store: string): void;
286
+ /** Is the breaker currently open for (peer, origin, store)? (for tests/observability). */
287
+ isOpen(peer: string, origin: string, store: string): boolean;
288
+ }
289
+ /**
290
+ * The receiver-side seams the cutover needs from the existing applier — injected
291
+ * so the cutover is unit-testable with in-memory fakes and never duplicates the
292
+ * applier's seq-contiguity logic (§6.3: "rides the EXISTING seq transport"). The
293
+ * real wiring binds these to JournalSyncApplier's PeerMeta + buildServeBatch.
294
+ */
295
+ export interface CutoverApplierSeams {
296
+ /**
297
+ * Apply ONE materialized snapshot record into store S's (origin M) replicated
298
+ * namespace via a per-recordKey HLC-max merge (§6.3 step 3). Returns whether the
299
+ * record landed as the new winner (false = an already-present newer record was
300
+ * not overwritten — idempotent). A `put` whose hlc is below the deleted-keys
301
+ * high-water for its key is the receiver's resurrection-drop (§6.5 guard 1) and
302
+ * returns false.
303
+ */
304
+ applySnapshotRecord(store: string, origin: string, rec: SnapshotRecord): boolean;
305
+ /**
306
+ * SEED, per contributing (origin, kind) stream, `PeerMeta.kinds[kind].lastHeldSeq
307
+ * = snapshotSeq` (§6.3 step 3 — the load-bearing cursor placement). Idempotent:
308
+ * re-seeding to the same seq is a no-op; the real applier never LOWERS a
309
+ * lastHeldSeq it already advanced past (a re-cutover with a stale snapshot does
310
+ * not rewind the cursor).
311
+ */
312
+ seedLastHeldSeq(origin: string, kind: string, snapshotSeq: number): void;
313
+ /**
314
+ * Seed the per-recordKey deleted-keys high-water from the snapshot (§6.5). The
315
+ * receiver MERGES (HLC-max) — never lowers an existing high-water.
316
+ */
317
+ seedDeleteWatermark(store: string, origin: string, recordKey: string, hlc: HlcTimestamp): void;
318
+ /**
319
+ * Record an HLC-identity (recordKey, origin, hlc) as already-merged (the §6.4
320
+ * SECONDARY dedup set). Returns whether the identity was NEW (false = already
321
+ * seen ⇒ the caller drops the duplicate). This is a redundant safety net layered
322
+ * ON the seq contiguity, never a substitute for it.
323
+ */
324
+ recordHlcIdentity(store: string, recordKey: string, origin: string, hlc: HlcTimestamp): boolean;
325
+ }
326
+ /** The verdict for one applied snapshot record (for the cutover result tally). */
327
+ export interface CutoverApplyTally {
328
+ /** Records that landed as the new winner. */
329
+ applied: number;
330
+ /** Records skipped as an HLC-identity duplicate (the §6.4 secondary net). */
331
+ dedupSkipped: number;
332
+ /** Records the applier did not land (older than the present winner, or a
333
+ * resurrection drop — §6.5). */
334
+ notWinner: number;
335
+ }
336
+ /**
337
+ * Apply a single-origin snapshot at cutover (§6.3 steps 3–5 + §6.4 + §6.5). This
338
+ * is the load-bearing seam: it (a) applies each record via HLC-max merge through
339
+ * the injected applier, (b) seeds the deleted-keys high-water from the snapshot,
340
+ * (c) seeds `lastHeldSeq = snapshotSeq` per contributing kind so the NEXT ordinary
341
+ * tail is already in-contiguity, and (d) uses the §6.4 secondary HLC-identity set
342
+ * as a belt-and-suspenders dedup.
343
+ *
344
+ * It does NOT do the tail — that is the UNCHANGED `buildServeBatch(kind,
345
+ * snapshotSeq, M)` path the caller drives next. There is NO gap-detection here;
346
+ * the seq contiguity already there does the work (§6.3 step 4).
347
+ *
348
+ * Idempotency (§6.3 step 5): re-running is safe — re-seeding the cursor to the
349
+ * same snapshotSeq + re-applying is a per-recordKey HLC-max merge (an
350
+ * already-present newer record is not overwritten) and the seq tail drops
351
+ * at-or-below entries as duplicates.
352
+ *
353
+ * PURE-ish: all state mutation is funnelled through the injected seams.
354
+ */
355
+ export declare function applySnapshotCutover(snapshot: StoreSnapshot, seams: CutoverApplierSeams): CutoverApplyTally;
356
+ /**
357
+ * Compute the tail request cursor for a contributing (origin, kind) AFTER a
358
+ * cutover (§6.3 step 4). The tail rides the UNCHANGED transport:
359
+ * `buildServeBatch(kind, snapshotSeq, origin)`. This helper is the single place
360
+ * that names the contract — the cursor is the snapshot's per-kind `snapshotSeq`,
361
+ * NOTHING HLC-derived (HLC is demoted to secondary dedup, §6.4).
362
+ */
363
+ export declare function tailCursorAfterCutover(snapshot: StoreSnapshot, kind: string): number;
364
+ /** Validate + narrow an untrusted wire snapshot (the receiver side of the mesh
365
+ * verb). Returns null to REJECT the whole snapshot (single-origin violated, a
366
+ * malformed record, etc.) — the §6.1 anti-forgery + §6.2 format gate at the door.
367
+ * `expectedOrigin` is the AUTHENTICATED sender (the mesh layer proved it); a
368
+ * snapshot whose `origin` or any record `origin` disagrees is rejected wholesale
369
+ * (the cross-origin-snapshot attack, §6.1). */
370
+ export declare function validateWireSnapshot(raw: unknown, expectedOrigin: string): StoreSnapshot | null;
371
+ /** Result of a snapshot-serve request (§6.3): the snapshot + how it was produced. */
372
+ export type SnapshotServeResult = {
373
+ ok: true;
374
+ snapshot: StoreSnapshot;
375
+ source: 'built' | 'cache';
376
+ durationMs: number;
377
+ truncated: false;
378
+ } | {
379
+ ok: false;
380
+ reason: 'breaker-open' | 'build-failed' | 'build-timeout' | 'no-entries' | 'build-truncated';
381
+ durationMs: number;
382
+ };
383
+ /** The seams the engine needs to LOAD a store's own-stream entries (injected so
384
+ * the engine is testable without touching disk; the real wiring reads the
385
+ * CoherenceJournal own streams for the contributing kinds). */
386
+ export interface SnapshotEngineSeams {
387
+ /** Load the own-stream entries for (origin, store), keyed by contributing kind.
388
+ * The caller (real wiring) returns ONLY this machine's own entries (single-
389
+ * origin, §6.1); the materializer ALSO enforces it (drops cross-origin). */
390
+ loadOwnEntries(store: string, origin: string): Record<string, RawJournalEntry[]>;
391
+ /** Injected wall clock (ms) — for the breaker + duration. */
392
+ now(): number;
393
+ }
394
+ /** Engine construction options. */
395
+ export interface StoreSnapshotEngineOptions {
396
+ cache: SnapshotCache;
397
+ breaker: SnapshotRebuildBreaker;
398
+ seams: SnapshotEngineSeams;
399
+ /** Per-build worker timeout (ms). Default 120s (mirrors CartographerSweepEngine). */
400
+ buildTimeoutMs?: number;
401
+ /** Worker heap ceiling (MB). Default 1536 (mirrors CartographerSweepEngine). */
402
+ workerHeapMb?: number;
403
+ /** The per-build snapshot byte ceiling passed to the materializer (deterministic
404
+ * truncation). Default 0 ⇒ no per-build truncation (cache byte ceiling still
405
+ * bounds retention). */
406
+ maxSnapshotBytes?: number;
407
+ /** Optional structured logger for build observability (default no-op). */
408
+ log?: (event: string, detail: Record<string, unknown>) => void;
409
+ /** Test seam: run the materializer INLINE instead of spawning a worker (the unit
410
+ * tests assert the cache/breaker orchestration without a real thread). Default
411
+ * false ⇒ the real off-event-loop worker is spawned (the instar#1069 path). */
412
+ runInline?: boolean;
413
+ }
414
+ /**
415
+ * StoreSnapshotEngine — the §6.3 serve orchestrator. For a (peer, origin, store)
416
+ * request it:
417
+ * 1. asks the rebuild breaker whether a fresh build is allowed (a flapping peer
418
+ * is served the cached snapshot; a tripped breaker refuses — §6.3),
419
+ * 2. on a build: loads the own-stream entries (single-origin), runs the
420
+ * materializer OFF THE EVENT LOOP in a worker thread (instar#1069), bounded
421
+ * by `buildTimeoutMs`, caches the result (LRU + cacheLossCounter, §8.2), and
422
+ * records the rebuild against the breaker,
423
+ * 3. on a within-window / breaker-open decision: serves the cached snapshot if
424
+ * present, else refuses (the caller falls back / retries later).
425
+ *
426
+ * The HEAVY work (whole-store materialization) is the worker's; the engine itself
427
+ * does only bounded orchestration on the main thread.
428
+ */
429
+ export declare class StoreSnapshotEngine {
430
+ private readonly cache;
431
+ private readonly breaker;
432
+ private readonly seams;
433
+ private readonly buildTimeoutMs;
434
+ private readonly workerHeapMb;
435
+ private readonly maxSnapshotBytes;
436
+ private readonly log;
437
+ private readonly runInline;
438
+ constructor(opts: StoreSnapshotEngineOptions);
439
+ /** The snapshot cache (for observability / the rollback-unmerge dropOrigin hook). */
440
+ getCache(): SnapshotCache;
441
+ /**
442
+ * Serve a single-origin snapshot of (origin, store) to `peer` (§6.3). Builds off
443
+ * the event loop, reuses the cache within the rebuild window, breaker-gated.
444
+ */
445
+ serveSnapshot(peer: string, origin: string, store: string): Promise<SnapshotServeResult>;
446
+ /** The most-recent cached snapshot for (origin, store), if any. */
447
+ private mostRecentCached;
448
+ /** Run the materializer — off the event loop (worker), or inline for tests. */
449
+ private runBuild;
450
+ /** Minimal env allowlist for a spawned worker — NEVER the parent process.env. */
451
+ private workerEnv;
452
+ /** Spawn the build worker, await its single message, bound by buildTimeoutMs. */
453
+ private runWorker;
454
+ }
455
+ //# sourceMappingURL=StoreSnapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StoreSnapshot.d.ts","sourceRoot":"","sources":["../../src/core/StoreSnapshot.ts"],"names":[],"mappings":"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"}