@bett3r-dev/pv3-types 1.0.0 → 1.4.0

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 (137) hide show
  1. package/CHANGELOG.md +305 -0
  2. package/build/adminPlugin.d.ts +24 -0
  3. package/build/adminPlugin.d.ts.map +1 -0
  4. package/build/{eventsourcing/utils.js → adminPlugin.js} +1 -1
  5. package/build/adminPlugin.js.map +1 -0
  6. package/build/aggregateSchema.d.ts +94 -0
  7. package/build/aggregateSchema.d.ts.map +1 -0
  8. package/build/aggregateSchema.js +287 -0
  9. package/build/aggregateSchema.js.map +1 -0
  10. package/build/authentication.d.ts +179 -78
  11. package/build/authentication.d.ts.map +1 -1
  12. package/build/authentication.js +62 -19
  13. package/build/authentication.js.map +1 -1
  14. package/build/cache.d.ts +50 -4
  15. package/build/cache.d.ts.map +1 -1
  16. package/build/clusterAdministration.d.ts +712 -225
  17. package/build/clusterAdministration.d.ts.map +1 -1
  18. package/build/clusterAdministration.js +175 -52
  19. package/build/clusterAdministration.js.map +1 -1
  20. package/build/configuration.d.ts +26 -5
  21. package/build/configuration.d.ts.map +1 -1
  22. package/build/database.d.ts +240 -56
  23. package/build/database.d.ts.map +1 -1
  24. package/build/database.js +102 -20
  25. package/build/database.js.map +1 -1
  26. package/build/endpoints.d.ts +141 -41
  27. package/build/endpoints.d.ts.map +1 -1
  28. package/build/endpoints.js +50 -26
  29. package/build/endpoints.js.map +1 -1
  30. package/build/errors.d.ts +166 -0
  31. package/build/errors.d.ts.map +1 -0
  32. package/build/errors.js +276 -0
  33. package/build/errors.js.map +1 -0
  34. package/build/evenstore.d.ts +47 -7
  35. package/build/evenstore.d.ts.map +1 -1
  36. package/build/eventsourcing/aggregateBuilder.d.ts +17 -8
  37. package/build/eventsourcing/aggregateBuilder.d.ts.map +1 -1
  38. package/build/eventsourcing/commandBuilder.d.ts +20 -13
  39. package/build/eventsourcing/commandBuilder.d.ts.map +1 -1
  40. package/build/eventsourcing/commandHandlers.d.ts +155 -33
  41. package/build/eventsourcing/commandHandlers.d.ts.map +1 -1
  42. package/build/eventsourcing/commandHandlers.js +62 -14
  43. package/build/eventsourcing/commandHandlers.js.map +1 -1
  44. package/build/eventsourcing/eventHandlers.d.ts +173 -44
  45. package/build/eventsourcing/eventHandlers.d.ts.map +1 -1
  46. package/build/eventsourcing/eventHandlers.js +29 -21
  47. package/build/eventsourcing/eventHandlers.js.map +1 -1
  48. package/build/eventsourcing/eventHandlersBuilder.d.ts +18 -12
  49. package/build/eventsourcing/eventHandlersBuilder.d.ts.map +1 -1
  50. package/build/eventsourcing/eventHandlersBuilder.js.map +1 -1
  51. package/build/eventsourcing/eventsourcing.types.d.ts +322 -0
  52. package/build/eventsourcing/eventsourcing.types.d.ts.map +1 -0
  53. package/build/eventsourcing/eventsourcing.types.js +26 -0
  54. package/build/eventsourcing/eventsourcing.types.js.map +1 -0
  55. package/build/eventsourcing/externalSystemBuilder.d.ts +8 -7
  56. package/build/eventsourcing/externalSystemBuilder.d.ts.map +1 -1
  57. package/build/eventsourcing/index.d.ts +2 -378
  58. package/build/eventsourcing/index.d.ts.map +1 -1
  59. package/build/eventsourcing/index.js +2 -23
  60. package/build/eventsourcing/index.js.map +1 -1
  61. package/build/eventsourcing/invariants.d.ts +34 -10
  62. package/build/eventsourcing/invariants.d.ts.map +1 -1
  63. package/build/eventsourcing/shapeChangeForSubscriber.d.ts +33 -0
  64. package/build/eventsourcing/shapeChangeForSubscriber.d.ts.map +1 -0
  65. package/build/eventsourcing/shapeChangeForSubscriber.js +47 -0
  66. package/build/eventsourcing/shapeChangeForSubscriber.js.map +1 -0
  67. package/build/index.d.ts +6 -1
  68. package/build/index.d.ts.map +1 -1
  69. package/build/index.js +6 -1
  70. package/build/index.js.map +1 -1
  71. package/build/infrastructure.d.ts +9 -1
  72. package/build/infrastructure.d.ts.map +1 -1
  73. package/build/infrastructure.js +33 -1
  74. package/build/infrastructure.js.map +1 -1
  75. package/build/livenessWatchdog.d.ts +28 -0
  76. package/build/livenessWatchdog.d.ts.map +1 -0
  77. package/build/livenessWatchdog.js +43 -0
  78. package/build/livenessWatchdog.js.map +1 -0
  79. package/build/logger.d.ts +8 -9
  80. package/build/logger.d.ts.map +1 -1
  81. package/build/logger.js +18 -6
  82. package/build/logger.js.map +1 -1
  83. package/build/outboxManager.d.ts +858 -14
  84. package/build/outboxManager.d.ts.map +1 -1
  85. package/build/outboxManager.js +173 -15
  86. package/build/outboxManager.js.map +1 -1
  87. package/build/permissions.d.ts +47 -0
  88. package/build/permissions.d.ts.map +1 -0
  89. package/build/permissions.js +3 -0
  90. package/build/permissions.js.map +1 -0
  91. package/build/ports.d.ts +10 -10
  92. package/build/ports.d.ts.map +1 -1
  93. package/build/routesAuth.d.ts +17 -25
  94. package/build/routesAuth.d.ts.map +1 -1
  95. package/build/routesAuth.js +4 -14
  96. package/build/routesAuth.js.map +1 -1
  97. package/build/typedEventEmitter.d.ts +7 -13
  98. package/build/typedEventEmitter.d.ts.map +1 -1
  99. package/build/typedEventEmitter.js +2 -2
  100. package/build/typedEventEmitter.js.map +1 -1
  101. package/build/userSession.d.ts +8 -0
  102. package/build/userSession.d.ts.map +1 -0
  103. package/build/userSession.js +3 -0
  104. package/build/userSession.js.map +1 -0
  105. package/package.json +22 -7
  106. package/build/eventsourcing/utils.d.ts +0 -4
  107. package/build/eventsourcing/utils.d.ts.map +0 -1
  108. package/build/eventsourcing/utils.js.map +0 -1
  109. package/build/validation.d.ts +0 -64
  110. package/build/validation.d.ts.map +0 -1
  111. package/build/validation.js +0 -57
  112. package/build/validation.js.map +0 -1
  113. package/src/authentication.ts +0 -273
  114. package/src/cache.ts +0 -109
  115. package/src/clusterAdministration.ts +0 -216
  116. package/src/configuration.ts +0 -71
  117. package/src/database.ts +0 -157
  118. package/src/endpoints.ts +0 -198
  119. package/src/evenstore.ts +0 -27
  120. package/src/eventsourcing/aggregateBuilder.ts +0 -11
  121. package/src/eventsourcing/commandBuilder.ts +0 -64
  122. package/src/eventsourcing/commandHandlers.ts +0 -137
  123. package/src/eventsourcing/eventHandlers.ts +0 -118
  124. package/src/eventsourcing/eventHandlersBuilder.ts +0 -42
  125. package/src/eventsourcing/externalSystemBuilder.ts +0 -20
  126. package/src/eventsourcing/index.ts +0 -111
  127. package/src/eventsourcing/invariants.ts +0 -27
  128. package/src/eventsourcing/utils.ts +0 -4
  129. package/src/index.ts +0 -15
  130. package/src/infrastructure.ts +0 -41
  131. package/src/logger.ts +0 -45
  132. package/src/outboxManager.ts +0 -36
  133. package/src/ports.ts +0 -42
  134. package/src/routesAuth.ts +0 -58
  135. package/src/typedEventEmitter.ts +0 -73
  136. package/src/validation.ts +0 -89
  137. package/tsconfig.json +0 -15
@@ -1,37 +1,881 @@
1
- import { CommittedEvent } from '@bett3r-dev/pv3-types';
2
- export declare const UdpConfigSchema: import("@bett3r-dev/jsonschema-definer").ObjectSchema<{
3
- port?: number | undefined;
4
- multicastAddress?: string | undefined;
5
- ttl?: number | undefined;
1
+ import { CommittedEvent, CustomCriteriaRegexpType, StructureAggregateType, StructureDispatcherSubscriptionType, StructureState, StructureSubscriptionType, TypedEventEmitter } from './index';
2
+ export declare const EventDispatcherConfigSchema: import("@bett3r-dev/jsonschema-definer").ObjectSchema<{
3
+ maxConsumerQueueSize?: number | undefined;
4
+ metricsInterval?: number | undefined;
5
+ defaultRetryableStatuses?: number[] | undefined;
6
+ retryAfterSeconds?: number | undefined;
7
+ retriesBeforeFailing?: number | undefined;
8
+ breakerThreshold?: number | undefined;
9
+ breakerCooldownSeconds?: number | undefined;
10
+ breakerCooldownMaxSeconds?: number | undefined;
11
+ commitOrderStream?: {
12
+ streamKey?: string | undefined;
13
+ } | undefined;
14
+ seedDedupToTipOnColdStart?: boolean | undefined;
15
+ structureAckReadiness?: {
16
+ enabled?: boolean | undefined;
17
+ windowMs?: number | undefined;
18
+ } | undefined;
6
19
  }, true>;
7
- export type UdpConfigType = typeof UdpConfigSchema.type;
20
+ export type EventDispatcherConfigType = typeof EventDispatcherConfigSchema.type;
8
21
  export declare const AdminServicesConfigSchema: import("@bett3r-dev/jsonschema-definer").ObjectSchema<{
9
- udp?: {
10
- port?: number | undefined;
11
- multicastAddress?: string | undefined;
12
- ttl?: number | undefined;
13
- } | undefined;
14
22
  structure?: {
15
23
  structureEventstoreStream?: string | undefined;
16
24
  structureAggregateCollection?: string | undefined;
17
25
  structureEventstoreCollection?: string | undefined;
26
+ defaultAutomaticDataSplits?: number | undefined;
18
27
  } | undefined;
19
28
  infrastructure?: {
29
+ adapter?: "docker" | "kubernetes" | undefined;
30
+ kubernetes?: {
31
+ namespace?: string | undefined;
32
+ labelSelector?: string | undefined;
33
+ } | undefined;
20
34
  infrastructureLeaderRandomBackoff?: number | undefined;
35
+ infrastructureReconcileIntervalMs?: number | undefined;
36
+ } | undefined;
37
+ eventDispatcher?: {
38
+ maxConsumerQueueSize?: number | undefined;
39
+ metricsInterval?: number | undefined;
40
+ defaultRetryableStatuses?: number[] | undefined;
41
+ retryAfterSeconds?: number | undefined;
42
+ retriesBeforeFailing?: number | undefined;
43
+ breakerThreshold?: number | undefined;
44
+ breakerCooldownSeconds?: number | undefined;
45
+ breakerCooldownMaxSeconds?: number | undefined;
46
+ commitOrderStream?: {
47
+ streamKey?: string | undefined;
48
+ } | undefined;
49
+ seedDedupToTipOnColdStart?: boolean | undefined;
50
+ structureAckReadiness?: {
51
+ enabled?: boolean | undefined;
52
+ windowMs?: number | undefined;
53
+ } | undefined;
21
54
  } | undefined;
22
- metricsInterval?: number | undefined;
23
55
  }, true>;
24
56
  export type AdminServicesConfigType = typeof AdminServicesConfigSchema.type;
57
+ export type StructureChangeQueueType = StructureAggregateType;
58
+ export type EventDispatcherDataSplitType = StructureDispatcherSubscriptionType & {
59
+ id: string;
60
+ };
61
+ export type EventDispatcherSubscriptionState = {
62
+ /** The subscription Definition */
63
+ subscription: StructureSubscriptionType;
64
+ /** The total data splits among all the different outbox services */
65
+ splitCount: number;
66
+ /** The status of the subscription */
67
+ status: StructureSubscriptionType['status'];
68
+ /** The DataSplits handled by the current service */
69
+ dataSplits: Record<string, EventDispatcherDataSplitType>;
70
+ };
71
+ export type CollectServiceStructureAckHelperType = {
72
+ close: () => void;
73
+ waitForRevisionAck: (state: StructureAggregateType) => Promise<boolean>;
74
+ };
75
+ export type ChannelType = {
76
+ send: (event: CommittedEvent, host: DispatcherHostType, endpoint: string) => Promise<void>;
77
+ };
78
+ export type ChannelsType = {
79
+ [name: string]: ChannelType;
80
+ };
81
+ export type InstrumentChannelSend = (send: ChannelType['send']) => ChannelType['send'];
82
+ export type DispatcherHostType = {
83
+ host: string;
84
+ service: string;
85
+ channel: string;
86
+ port: number;
87
+ };
88
+ export declare const EventDispatcherDataSplitErrorSchema: import("@bett3r-dev/jsonschema-definer").ObjectSchema<{
89
+ status?: number | undefined;
90
+ error?: string | undefined;
91
+ event: {
92
+ id: string;
93
+ position: number;
94
+ name?: any;
95
+ timestamp: string;
96
+ data?: import("@bett3r-dev/jsonschema-definer/dist/base").Any;
97
+ version: number | null;
98
+ stream: string;
99
+ metadata: {
100
+ causationId?: string | undefined;
101
+ correlationId?: string | undefined;
102
+ };
103
+ };
104
+ }, true>;
105
+ export type EventDispatcherDataSplitErrorType = typeof EventDispatcherDataSplitErrorSchema.type;
106
+ export declare const EventDispatcherDataSplitStatusSchema: import("@bett3r-dev/jsonschema-definer").ObjectSchema<{
107
+ position: number;
108
+ status: "critical" | "ok" | "retryable";
109
+ lastError?: {
110
+ status?: number | undefined;
111
+ error?: string | undefined;
112
+ event: {
113
+ id: string;
114
+ position: number;
115
+ name?: any;
116
+ timestamp: string;
117
+ data?: import("@bett3r-dev/jsonschema-definer/dist/base").Any;
118
+ version: number | null;
119
+ stream: string;
120
+ metadata: {
121
+ causationId?: string | undefined;
122
+ correlationId?: string | undefined;
123
+ };
124
+ };
125
+ } | undefined;
126
+ }, true>;
127
+ export type EventDispatcherDataSplitStatusType = typeof EventDispatcherDataSplitStatusSchema.type;
128
+ export declare const EventDispatcherSubscriptionEventSchema: import("@bett3r-dev/jsonschema-definer").ObjectSchema<{
129
+ ok: number[];
130
+ retryable: number[];
131
+ critical: number[];
132
+ ignored: number[];
133
+ }, true>;
134
+ export type EventDispatcherSubscriptionEventType = typeof EventDispatcherSubscriptionEventSchema.type;
135
+ export declare const ProcessingByDataSplitSchema: import("@bett3r-dev/jsonschema-definer").ObjectSchema<{
136
+ events: {
137
+ [x: string]: string | null;
138
+ };
139
+ lastProcessed?: number | undefined;
140
+ }, true>;
141
+ export type ProcessingByDataSplitType = typeof ProcessingByDataSplitSchema.type;
142
+ export type EventDispatcherHistoricDataSplitByClusterRevision = {
143
+ dataSplits: {
144
+ [index: number]: {
145
+ id: string;
146
+ index: number;
147
+ position: number;
148
+ status: 'ok' | 'retryable' | 'critical';
149
+ customCriteriaRegexp: CustomCriteriaRegexpType[];
150
+ lastError?: EventDispatcherDataSplitErrorType;
151
+ events?: {
152
+ [event: string]: EventDispatcherSubscriptionEventType;
153
+ };
154
+ };
155
+ };
156
+ clusterRevision: number;
157
+ dataSplitsCount: number;
158
+ };
159
+ export type PullEventsForDataSplitParamsType = {
160
+ subscriptionId: string;
161
+ dataSplitId: string;
162
+ position: number;
163
+ shouldReprocess?: boolean;
164
+ shouldIgnoreSequence?: boolean;
165
+ retryAttempt?: number;
166
+ onBeforeUnpause?: () => Promise<void>;
167
+ };
168
+ export type ProcessEventForDataSplitParamsType = {
169
+ subscriptionId: string;
170
+ event: CommittedEvent;
171
+ dataSplit: EventDispatcherDataSplitType;
172
+ eventstorePosition?: number;
173
+ shouldReprocess?: boolean;
174
+ shouldIgnoreSequence?: boolean;
175
+ retryAttempt?: number;
176
+ streamEntryId?: string;
177
+ asFirstDelivery?: boolean;
178
+ isBreakerProbe?: boolean;
179
+ asNewReplay?: boolean;
180
+ };
181
+ export type EventDispatcherQueueType = ProcessEventForDataSplitParamsType;
182
+ export type ErroredStreamDetailType = {
183
+ stream: string;
184
+ position: number;
185
+ error: string;
186
+ errorDetails?: Record<string, any>;
187
+ stackTrace?: string;
188
+ event?: CommittedEvent;
189
+ erroredAt?: string;
190
+ };
191
+ export type OutboxCriticalErrorPayloadType = {
192
+ subscriptionId: string;
193
+ dataSplit: EventDispatcherDataSplitType;
194
+ event: CommittedEvent;
195
+ dataSplitPosition: number;
196
+ retryAttempt: number;
197
+ error: Error;
198
+ };
199
+ export type OutboxEventEmitterType = {
200
+ 'stream-errored': [OutboxCriticalErrorPayloadType];
201
+ 'stream-fixed': [{
202
+ subscriptionId: string;
203
+ stream: string;
204
+ }];
205
+ 'split-halted': [OutboxCriticalErrorPayloadType];
206
+ 'split-restarted': [{
207
+ subscriptionId: string;
208
+ dataSplitIndex: string;
209
+ }];
210
+ };
211
+ export type EventProcessingType = {
212
+ pullFromAllThisBrokerSubscriptions: (status?: StructureSubscriptionType['status'][]) => Promise<void>;
213
+ pullEventForErroredStream: (subscriptionId: string, stream: string) => Promise<boolean>;
214
+ pullBrokerSubscriptions: (subscriptionId: string) => Promise<void>;
215
+ pullEventsForDataSplit: (params: PullEventsForDataSplitParamsType, retryAttempt?: number) => Promise<void>;
216
+ /**
217
+ * The commit-order stream dispatch (subWm dedup). `version > wm` ⇒ send then CAS-advance the
218
+ * shared per-(sub,stream) watermark; `version <= wm` ⇒ skip — UNLESS `shouldReprocess` (a
219
+ * force-replay) bypasses the skip and re-sends an already-watermarked version, which is then
220
+ * stamped `event.metadata.isRedelivery = true` on a shallow copy. TV1-1950.
221
+ */
222
+ processStreamEventForDataSplit: (params: ProcessEventForDataSplitParamsType) => Promise<void>;
223
+ resumeDataSplit: (dataSplitId: string) => void;
224
+ resumeAllOwnedDataSplits: () => void;
225
+ /**
226
+ * Operator scoped replay (TV1-1950 slice 9; generalized to bulk scope — a stream list or the whole
227
+ * subscription — by TV1-1957). The forward stream loop is cursor-driven and won't react to a stale
228
+ * watermark, so a replay must POKE the recovery path: this rolls back the by-position recovery cursor
229
+ * of EVERY in-scope split and writes one field per in-scope split index onto the per-`(sub)` refill
230
+ * request HASH (`subRefill:{sub}`) that the owning instance's `pumpOnce` honors — each split index has
231
+ * exactly one owner, so its field is written once and cleared once (no atomic RMW / Lua needed).
232
+ * Selector: a non-empty `streams` list scopes to `unique(streams.map(getDataSplitIndex))`;
233
+ * `allStreams: true` scopes to every split `0..splitCount-1`. `stream` is DEPRECATED sugar for
234
+ * `streams: [stream]` (kept for the pre-TV1-1957 single-stream call sites). Neither mode mutates any
235
+ * dedup state (watermark/bitmap) here (TV1-1957 slice 3) — the mode alone decides how `pumpOnce`'s
236
+ * refill pass dispatches each replayed event: `as-new` ⇒ `asNewReplay` bypass (isRedelivery UNSET);
237
+ * `force-replay` ⇒ `shouldReprocess` bypass (isRedelivery = true). See `asNewReplay` on
238
+ * `ProcessEventForDataSplitParamsType` for the dispatch-time mechanics.
239
+ */
240
+ requestScopedRefill: (subscriptionId: string, params: {
241
+ fromPosition: number;
242
+ mode: RefillModeType;
243
+ stream?: string;
244
+ streams?: string[];
245
+ allStreams?: boolean;
246
+ }) => Promise<void>;
247
+ /**
248
+ * Cold-start dedup seed to eventstore tip (TV1-1980 — "Option C"). Seeds this subscription's dedup
249
+ * state (per-(sub,stream) version watermark + null-class position bitmap) to the eventstore TIP so a
250
+ * cold-started (sandbox) instance lifts already "caught up" and dispatches NOTHING at boot, while an
251
+ * operator force-replay still re-emits events stamped `isRedelivery = true`. Gated by the flag
252
+ * (`config.seedDedupToTipOnColdStart`, default false ⇒ dead path in production) AND the persistent
253
+ * `subColdSeeded:{sub}` marker (seed iff flag ON AND marker ABSENT; marker SET after the scan). NEVER
254
+ * sends, NEVER drops (ADR-013) — boot-silence comes from advancing monotonic/idempotent dedup state.
255
+ * Awaited in the subscription bootstrap before the pump; exposed for deterministic test drive.
256
+ */
257
+ seedDedupToTip: (subscriptionId: string) => Promise<void>;
258
+ /**
259
+ * Operator DLQ-drain (TV1-1954 slice 6). Re-reads each dead-lettered ref `(stream, position)` from
260
+ * the EVENTSTORE by position and re-dispatches it as a GENUINE FIRST delivery (`asFirstDelivery` ⇒
261
+ * `isRedelivery = false`) — a dead-lettered event was NEVER successfully delivered, so stamping it a
262
+ * redelivery would suppress the consumer's redelivery-detection signal on what is actually its first
263
+ * successful receipt. On a CONFIRMED successful re-dispatch the entry is REMOVED from `subDlq:{sub}`;
264
+ * a re-dispatch that FAILS AGAIN stays in the DLQ (not silently dropped, and NOT duplicated). The
265
+ * re-dispatch goes through the SAME send→commit boundary (`commitNullEvent` — SETBIT idempotent +
266
+ * breaker reset). Returns the counts `{ drained, remaining }`. With `only` set, retries just the
267
+ * entries matching that (stream, position) ref — the operator single-event retry.
268
+ */
269
+ drainDlq: (subscriptionId: string, only?: {
270
+ stream: string;
271
+ position: number;
272
+ }) => Promise<{
273
+ drained: number;
274
+ remaining: number;
275
+ }>;
276
+ /**
277
+ * Operator restart of a HALTED datasplit (TV1-1954 split-halt). A split whose packed status is
278
+ * `critical` was stopped by the retryable-exhausted escalation (the live dispatch gate drops its
279
+ * events without advancing any watermark, so nothing is lost). This clears the split back to `ok`
280
+ * and replays it BY POSITION from `min(halt position, lowest gate-dropped position) − 1` through the
281
+ * normal subWm/bitmap-deduped dispatch, then live flow resumes. If the consumer is still sick the
282
+ * replay burns another retry budget and re-halts. Returns false (admin maps to 404) for an unknown
283
+ * subscription, a split this instance does not own, or a split that is not halted.
284
+ */
285
+ restartDataSplit: (subscriptionId: string, dataSplitIndex: string) => Promise<boolean>;
286
+ eventEmitter: TypedEventEmitter<OutboxEventEmitterType>;
287
+ };
288
+ /**
289
+ * Consumer lag on the commit-order stream, measured on the POSITION axis (events of the global
290
+ * feed still ahead of the instance's read cursor). Works identically for versioned (subWm) and
291
+ * null-class (bitmap) subscriptions — the cursor is dedup-agnostic. Computed from EXISTING state
292
+ * only: `subCursor:{sub}` (per-instance read cursor), the TTL'd `subHb` heartbeat (liveness +
293
+ * host), and the stream itself (tail / cursor entry positions).
294
+ */
295
+ export type SubscriptionLagType = {
296
+ /** Global position of the stream's live tail entry (undefined when the stream is empty). */
297
+ tailPosition?: number;
298
+ /** Highest lag across live instances — the subscription's headline "events behind" figure. */
299
+ maxLag: number;
300
+ /** Per LIVE instance (dead instances' cursors are excluded via the TTL'd heartbeat). */
301
+ instances: Record<string, {
302
+ host?: string;
303
+ cursor: string;
304
+ lag: number;
305
+ }>;
306
+ };
307
+ export type EventDispatcherType = EventProcessingType & {
308
+ start: (structureState?: StructureState) => Promise<void>;
309
+ stop: () => Promise<void>;
310
+ /**
311
+ * Consumer lag per subscription (see SubscriptionLagType). Optional — wired at the OutboxManager
312
+ * composition root (it needs the commit-order stream handle); bare EventDispatcher test stubs omit it.
313
+ */
314
+ getSubscriptionsLag?: (subscriptionId?: string) => Promise<Record<string, SubscriptionLagType>>;
315
+ };
316
+ export declare const EventDispatcherDataSplitStateSchema: import("@bett3r-dev/jsonschema-definer").ObjectSchema<{
317
+ host: string;
318
+ index: number;
319
+ isManual?: boolean | undefined;
320
+ customCriteriaRegexp?: {
321
+ value: string;
322
+ regexp: string;
323
+ initialPosition?: number | undefined;
324
+ isErrorDataSplit?: boolean | undefined;
325
+ lastErrorDataSplitIndex?: number | undefined;
326
+ lastErrorClusterRevision?: number | undefined;
327
+ }[] | undefined;
328
+ position: number;
329
+ status: "critical" | "ok" | "retryable";
330
+ lastError?: {
331
+ status?: number | undefined;
332
+ error?: string | undefined;
333
+ event: {
334
+ id: string;
335
+ position: number;
336
+ name?: any;
337
+ timestamp: string;
338
+ data?: import("@bett3r-dev/jsonschema-definer/dist/base").Any;
339
+ version: number | null;
340
+ stream: string;
341
+ metadata: {
342
+ causationId?: string | undefined;
343
+ correlationId?: string | undefined;
344
+ };
345
+ };
346
+ } | undefined;
347
+ id: string;
348
+ subscription_id: string;
349
+ cluster_revision: number;
350
+ data_splits_count: number;
351
+ events?: {
352
+ [x: string]: {
353
+ ok: number[];
354
+ retryable: number[];
355
+ critical: number[];
356
+ ignored: number[];
357
+ };
358
+ } | undefined;
359
+ last_updated?: string | undefined;
360
+ }, true>;
361
+ export type EventDispatcherDataSplitStateType = typeof EventDispatcherDataSplitStateSchema.type;
362
+ export declare const ErrorHandlingStrategyMap: {
363
+ readonly 'custom-datasplit': 0;
364
+ readonly 'subscriptionStop': 1;
365
+ };
366
+ export type ErrorHandlingStrategy = keyof typeof ErrorHandlingStrategyMap;
367
+ export declare const DataSplitStatusIntMap: {
368
+ readonly ok: 0;
369
+ readonly retryable: 1;
370
+ readonly critical: 2;
371
+ readonly splitHalted: 3;
372
+ };
373
+ export type DataSplitStatusIntMapType = keyof typeof DataSplitStatusIntMap;
374
+ export declare const DataSplitIntStatusMap: {
375
+ readonly 0: 'ok';
376
+ readonly 1: 'retryable';
377
+ readonly 2: 'critical';
378
+ };
379
+ export type DataSplitIntStatusMapType = keyof typeof DataSplitIntStatusMap;
380
+ export declare const SubscriptionStatusIntMap: {
381
+ readonly off: 0;
382
+ readonly ok: 1;
383
+ readonly withErrors: 2;
384
+ readonly retrying: 3;
385
+ readonly critical: 4;
386
+ };
387
+ export type SubscriptionStatuses = keyof typeof SubscriptionStatusIntMap;
388
+ export declare const SubscriptionIntStatusMap: {
389
+ 0: string;
390
+ 1: string;
391
+ 2: string;
392
+ 3: string;
393
+ 4: string;
394
+ };
395
+ export type AllSubscriptionsStatusType = {
396
+ status: string;
397
+ errorStreams: Record<string, number>;
398
+ dataSplits: Record<string, {
399
+ position: number;
400
+ status: number;
401
+ } | undefined>;
402
+ delivered: number;
403
+ streamsTracked: number;
404
+ dlq?: number;
405
+ };
406
+ /**
407
+ * Operator scoped-replay mode (TV1-1950 slice 9). The admin replay endpoint pokes the cursor-driven
408
+ * recovery path by writing a per-`(sub)` refill marker; the owning instance's `pumpOnce` honors it.
409
+ * Both modes roll back ONLY the recovery cursor — neither touches the watermark/bitmap up front
410
+ * (TV1-1957 slice 3 removed that off-queue reset). The dedup-bypass instead happens at DISPATCH TIME:
411
+ * - `as-new`: the refill dispatches each replayed event with the `asNewReplay` bypass flag ⇒ the
412
+ * redelivery skip is bypassed and the outgoing copy is stamped `isRedelivery = false` (fresh).
413
+ * - `force-replay`: the refill dispatches with `shouldReprocess` ⇒ the skip is bypassed and the
414
+ * outgoing copy is stamped `isRedelivery = true` (consistent with the slice-4 stamp).
415
+ * Either way the only dedup MUTATION is the existing post-send monotonic `advanceSubWatermark` /
416
+ * idempotent `setSubBit`, run on the split's concurrency:1 queue — never off-queue (ADR-007).
417
+ */
418
+ export type RefillModeType = 'as-new' | 'force-replay';
419
+ /**
420
+ * Per-in-scope-split payload of the bulk scoped-refill request HASH (`subRefill:{sub}`, TV1-1957 —
421
+ * generalizes the pre-existing single-key `RefillRequestType` marker, which it REPLACES; a single
422
+ * marker cleared by the first owner to pump silently skipped splits owned by other instances).
423
+ * `streams` (a list selector) and `allStreams` (the whole-subscription selector) are mutually
424
+ * exclusive; `allStreams: true` means "no stream filter within this split" (every stream routed to it).
425
+ */
426
+ export type ScopedRefillEntryType = {
427
+ mode: RefillModeType;
428
+ fromPosition: number;
429
+ streams?: string[];
430
+ allStreams?: boolean;
431
+ };
432
+ /**
433
+ * Live retry progress for a split whose send loop is in the in-place retryable backoff (TV1-1954
434
+ * retry visibility). Written on EACH retry attempt (the loop is about to sleep `retryAfterSecs`, so
435
+ * one HSET per attempt is noise), cleared on every loop exit (success / split-halt / quarantine /
436
+ * null dead-letter) so it can never go stale. `nextRetryAt` is epoch millis — the backoff is a FIXED
437
+ * `retryAfterSecs` per attempt, so the admin UI renders a live "retry N of M · next in Xs" countdown.
438
+ */
439
+ export type SplitRetryStateType = {
440
+ attempt: number;
441
+ maxRetries: number;
442
+ retryAfterSecs: number;
443
+ nextRetryAt: number;
444
+ stream: string;
445
+ position: number;
446
+ error?: {
447
+ status?: number;
448
+ message?: string;
449
+ };
450
+ };
451
+ export type StateType = {
452
+ getDataSplitStateForPosition: (subscriptionId: string, dataSplitId: string, position: number) => Promise<{
453
+ status: 'alreadyProcessed' | 'subscriptionOff' | 'subscriptionHasErrors' | 'ok';
454
+ dataSplitState?: {
455
+ position: number;
456
+ status: DataSplitStatusIntMapType;
457
+ };
458
+ errorStreams?: Record<string, number>;
459
+ }>;
460
+ getErrorStreamPosition: (subscription: string, stream: string) => Promise<number | undefined>;
461
+ getSubscriptionDataSplits: (subscription: string) => Promise<Record<string, {
462
+ position: number;
463
+ status: DataSplitStatusIntMapType;
464
+ delivered: number;
465
+ erroredStreams: number;
466
+ retry?: SplitRetryStateType;
467
+ heldSinceHalt?: number;
468
+ } | undefined>>;
469
+ getSubscriptionStatus: (subscription: string) => Promise<{
470
+ status?: string;
471
+ errorStreams?: Record<string, number>;
472
+ delivered?: number;
473
+ streamsTracked?: number;
474
+ }>;
475
+ getAllSubscriptionsStatus: () => Promise<Record<string, AllSubscriptionsStatusType>>;
476
+ /**
477
+ * Per-`(sub,stream)` version watermark (TV1-1950): the gapless-commit-order-stream dedup.
478
+ * `subWm:{sub}` is a single HASH (field = stream → highest dispatched version); the `{sub}` hash
479
+ * tag co-locates all of a subscription's streams in ONE Redis slot so the CAS advance is a
480
+ * single-slot Lua script. The dispatch decide is `version > storedWm ⇒ send` and the commit is a
481
+ * monotonic CAS advance AFTER a successful send, so an interruption between send and advance
482
+ * re-dispatches (at-least-once, never drop). Because the watermark is SHARED (keyed on
483
+ * `(sub,stream)`, not a per-instance cursor) a rebalance hands a split over cleanly regardless of
484
+ * cursor skew — progress travels with the stream.
485
+ */
486
+ getSubWatermark: (subscription: string, stream: string) => Promise<number | undefined>;
487
+ getSubWatermarks: (subscription: string) => Promise<Record<string, number>>;
488
+ /** Monotonic CAS advance of `subWm:{sub}[stream]` to `version` (never regresses); returns the resulting watermark. */
489
+ advanceSubWatermark: (subscription: string, stream: string, version: number) => Promise<number>;
490
+ /**
491
+ * Entry-id-recording monotonic CAS advance (TV1-1950 slice 8). Same as `advanceSubWatermark`, but it
492
+ * ALSO records — atomically, in the parallel `subWmId:{sub}` HASH (field = stream → entryId) — the
493
+ * stream ENTRY-ID at which the watermark last advanced. That recorded id is the bridge the bounded
494
+ * watermark GC needs: `subWm` stores a per-stream VERSION, but the trim horizon (`firstId()`) is a
495
+ * stream ENTRY-ID, so the GC compares the recorded entry-id (not the version) against the horizon.
496
+ * The dispatch path uses this when it has the live stream entry-id; the by-position refill falls back
497
+ * to `advanceSubWatermark` (no stream entry-id), leaving the field absent from `subWmId` → the GC
498
+ * conservatively retains it.
499
+ */
500
+ advanceSubWatermarkAt: (subscription: string, stream: string, version: number, entryId: string) => Promise<number>;
501
+ /**
502
+ * Bounded watermark GC (TV1-1950 slice 8): drop every `(sub,stream)` watermark (from BOTH `subWm`
503
+ * and `subWmId`) whose recorded entry-id is STRICTLY BELOW the trim horizon (the global stream's
504
+ * first-entry-id, `firstId()`, after the slice-8a XTRIM). An entry strictly below the horizon was
505
+ * already trimmed away and can never be re-read off the live stream, so dropping its watermark
506
+ * induces NO re-dispatch; an entry AT or ABOVE the horizon is still in the live window and is
507
+ * RETAINED. A `subWm` field with no recorded `subWmId` is conservatively retained. An `undefined`
508
+ * horizon (empty stream — nothing trimmed) is a no-op. Returns the dropped stream fields.
509
+ */
510
+ gcSubWatermarks: (subscription: string, trimHorizon: string | undefined) => Promise<string[]>;
511
+ /**
512
+ * Per-`(sub,instance)` read cursor + TTL'd heartbeat for `XREAD` resume. The cursor is
513
+ * instance-local and may skew across instances; correctness rests on the SHARED `subWm` dedup,
514
+ * not on the cursor. The heartbeat is TTL'd so a dead instance can no longer pin the stream
515
+ * (its cursor stops counting toward the trim floor once the heartbeat expires).
516
+ */
517
+ getStreamCursor: (subscription: string, instance: string) => Promise<string | undefined>;
518
+ setStreamCursor: (subscription: string, instance: string, entryId: string) => Promise<void>;
519
+ /**
520
+ * `host` is embedded in the heartbeat VALUE (the key's existence/TTL is the liveness contract and
521
+ * stays untouched — the WAL Reader never parses the value). It maps the per-process instance UUID
522
+ * back to the structure's broker host/IP, so the admin lag view can attribute a cursor to the
523
+ * host that owns each data split without any additional state.
524
+ */
525
+ heartbeatStreamConsumer: (subscription: string, instance: string, ttlSeconds?: number, host?: string) => Promise<void>;
526
+ /**
527
+ * Per-`(sub,split)` by-position recovery cursor (TV1-1950 slice 9). The CONSERVATIVE start point
528
+ * for the by-position refill (the KEPT `reduceStream`; never a by-version read) — it only BOUNDS
529
+ * the re-scan; correctness rests on the version watermark (design critique #3). A refill advances
530
+ * it to the highest scanned position; an operator replay rolls it back to re-open a range. Keyed by
531
+ * split INDEX (deterministic from the stream) so the admin can address it from `(sub,stream)` alone.
532
+ */
533
+ getRecoveryCursor: (subscription: string, splitIndex: string) => Promise<number | undefined>;
534
+ setRecoveryCursor: (subscription: string, splitIndex: string, position: number) => Promise<void>;
535
+ /**
536
+ * Persistent per-`(sub)` cold-seed marker (TV1-1980 — "Option C"). Existence of `subColdSeeded:{sub}`
537
+ * means this subscription's dedup state was already seeded to the eventstore tip on a prior cold start.
538
+ * `seedDedupToTip` seeds iff the flag is ON AND `getColdSeeded` is FALSE, then `setColdSeeded` AFTER the
539
+ * scan (self-healing — a failed seed leaves the marker unset and retries next cycle). The marker gates
540
+ * RE-seeding unconditionally: a warm restart must never re-seed, else a new event that advanced the
541
+ * watermark past the old tip would be masked to a silent drop (violates ADR-013). Torn down with the
542
+ * subscription in `deleteSubscription`.
543
+ */
544
+ getColdSeeded: (subscription: string) => Promise<boolean>;
545
+ setColdSeeded: (subscription: string) => Promise<void>;
546
+ /**
547
+ * Null-class position bitmap dedup (TV1-1954 slice 2 — hybrid dedup). VERSION-NULL streams
548
+ * (external/integration, no OCC) are delivered in the WAL Reader's commit-LSN order, NOT position
549
+ * order, so the ordered `subWm` high-water would skip (silently DROP) a lower-position event that
550
+ * commits AFTER a higher one on the same stream. Instead each null-version event dedups ORDER-FREE
551
+ * on its POSITION bit in `subBitmap:{sub}:{epoch}` (epoch = floor(position / EPOCH_SIZE), offset =
552
+ * position % EPOCH_SIZE, EPOCH_SIZE = 1_000_000 so a single key stays ≤ 2^32 bits): `getSubBit` is
553
+ * the DECIDE (set ⇒ redelivery ⇒ skip) and `setSubBit` is the COMMIT, run ONLY AFTER a successful
554
+ * send (send → commit boundary preserved for both classes → at-least-once, never drop). The `{sub}`
555
+ * hash tag co-locates a subscription's epoch keys in ONE Redis slot.
556
+ */
557
+ getSubBit: (subscription: string, position: number) => Promise<boolean>;
558
+ setSubBit: (subscription: string, position: number) => Promise<void>;
559
+ /**
560
+ * Batched, set-only null-class SETBIT (TV1-1980 slice 2 — cold-seed flush). The same set-only,
561
+ * idempotent semantics as `setSubBit` (SETBIT 1, never CLRBIT) but for MANY positions at once. It
562
+ * groups positions by epoch (each `subBitmap:{sub}:{epoch}` key) and issues one PIPELINED batch of
563
+ * SETBITs PER epoch — collapsing an O(positions) per-event round-trip loop to O(epochs) round trips,
564
+ * the cold-seed batching gate. Correctness rests, like `setSubBit`, on consumer idempotency
565
+ * (at-least-once, ADR-013): it only BOUNDS duplicate volume, it is not itself the never-drop mechanism.
566
+ */
567
+ setSubBits: (subscription: string, positions: number[]) => Promise<void>;
568
+ /**
569
+ * Bounded null-class bitmap GC (TV1-1954 slice 3) — the POSITION-axis analogue of `gcSubWatermarks`.
570
+ * The watermark GC compares ENTRY-IDs (commit order); the null-class bitmap is indexed by POSITION —
571
+ * a DIFFERENT axis — so the caller crosses axes FIRST: it maps the trim floor (`firstId()` ENTRY-ID)
572
+ * to a POSITION by reading the first live stream entry's `event.position` and passes that as
573
+ * `floorPosition`. This DELs every `subBitmap:{sub}:{epoch}` key whose epoch is STRICTLY BELOW
574
+ * `floor((floorPosition − KEEP_FLOOR_MARGIN) / EPOCH_SIZE)` (`KEEP_FLOOR_MARGIN` = one epoch = 1_000_000
575
+ * positions). The one-epoch margin absorbs the commit-order↔position skew: a low-position event that
576
+ * committed LATE is still LIVE (entry-id ≥ floor) and its bit is still needed, so its epoch (down to
577
+ * floorEpoch − 1) is RETAINED. If the skew ever exceeds the margin the effect is OVER-prune → a
578
+ * duplicate (idempotent-safe), NEVER a drop, so the margin is a dup-tuning knob, not a correctness
579
+ * bound. An undefined/negative/small `floorPosition` (pruneBelowEpoch <= 0) is a no-op. Returns the
580
+ * count of epoch keys deleted (observability).
581
+ */
582
+ pruneBitmap: (subscription: string, floorPosition: number) => Promise<number>;
583
+ /**
584
+ * Null-class dead-letter + circuit-breaker counter (TV1-1954 slice 4 — licensed by order-freeness).
585
+ * A version-null event has NO per-stream order to protect, so on retries-exhausted OR a critical send
586
+ * failure it is DEAD-LETTERED and the split keeps flowing — unlike the versioned per-stream halt. This
587
+ * runs ONE atomic Lua (all three keys share the `{sub}` slot) that (1) LPUSHes a durable ref onto
588
+ * `subDlq:{sub}`, (2) SETBITs the `position` bit (resolved-but-not-delivered — a recovery re-read sees
589
+ * GETBIT=1 and does NOT re-blast it; SETBIT-without-send is SAFE because the DLQ IS the durable
590
+ * capture, so there is no crash window where the event is neither delivered nor captured), and (3)
591
+ * INCRs the per-`(sub,host)` breaker count. Returns the NEW breaker count. Slice 4 only RECORDS the
592
+ * count; the trip/pause + success-reset is slice 5. `error` is SANITIZED to `{ status, message }` —
593
+ * stacks/secrets are NEVER written to the durable DLQ.
594
+ */
595
+ deadLetterNullEvent: (subscription: string, host: string, stream: string, position: number, name: string, error: {
596
+ status?: number;
597
+ message?: string;
598
+ name?: string;
599
+ details?: any;
600
+ }) => Promise<number>;
601
+ /**
602
+ * Read the dead-letter queue for a subscription, newest-first (matching the LPUSH in
603
+ * `deadLetterNullEvent`). Each ref is the `{ stream, position, name, error }` tuple captured on a
604
+ * null-class failure; the operator DLQ-drain (slice 6) re-reads each from the eventstore by position.
605
+ */
606
+ getDlq: (subscription: string) => Promise<Array<{
607
+ stream: string;
608
+ position: number;
609
+ name: string;
610
+ error: {
611
+ status?: number;
612
+ message?: string;
613
+ name?: string;
614
+ details?: any;
615
+ };
616
+ }>>;
617
+ /**
618
+ * The RAW dead-letter entries for a subscription — the EXACT stored strings (`LRANGE subDlq:{sub}
619
+ * 0 -1`), NOT re-parsed. The operator DLQ-drain (TV1-1954 slice 6) reads these so it can hand each
620
+ * back BYTE-FOR-BYTE to `removeDlqEntry` on a confirmed re-dispatch, avoiding re-serialization drift
621
+ * (`getDlq` re-parses for assertions/display and MUST NOT be used as the LREM key).
622
+ */
623
+ getDlqRaw: (subscription: string) => Promise<string[]>;
624
+ /**
625
+ * Remove ONE exact-match occurrence of a raw dead-letter entry (`LREM subDlq:{sub} 1 rawEntry`) —
626
+ * the DLQ-drain's confirmed-success removal (TV1-1954 slice 6). Using the RAW stored string (from
627
+ * `getDlqRaw`) guarantees the LREM matches byte-for-byte; only ONE occurrence is removed so a
628
+ * duplicate ref (same event dead-lettered twice) is not over-deleted.
629
+ */
630
+ removeDlqEntry: (subscription: string, rawEntry: string) => Promise<void>;
631
+ /**
632
+ * The per-`(sub,host)` consecutive-failure count `deadLetterNullEvent` INCRs. SHARED across outbox
633
+ * instances (one Redis counter) so the effective breaker threshold does not multiply by instance
634
+ * count. Absent key ⇒ 0 (no failures recorded yet).
635
+ */
636
+ getBreakerCount: (subscription: string, host: string) => Promise<number>;
637
+ /**
638
+ * Null-class HAPPY-PATH commit (TV1-1954 slice 5) — REPLACES the bare `setSubBit` at the null commit
639
+ * site so a SUCCESS resets the circuit breaker ATOMICALLY with the position-bit commit (no extra
640
+ * hot-path round-trip). ONE Lua (all keys share the `{sub}` slot) that (1) SETBITs the `position` bit
641
+ * (order-free dedup commit, run ONLY after a successful send — send→commit boundary preserved →
642
+ * at-least-once, never drop), (2) DELs `breaker:{sub}:{host}` (any success resets the consecutive
643
+ * failure count to 0), (3) DELs `breakerCooldown:{sub}:{host}` (reset the half-open backoff to base),
644
+ * and (4) DELs `breakerOpenedAt:{sub}:{host}`. Returns whether the breaker WAS open — i.e. this
645
+ * success CLOSED it — so the caller can resume the host's paused splits + emit `onBreakerClose`.
646
+ */
647
+ /**
648
+ * Happy-path null commit: SETBIT the position bit, bump the per-split delivered counter
649
+ * (`subNullSplit:{sub}` HASH, incremented only when the bit flips 0→1 so redelivered duplicates
650
+ * don't inflate it — the bitmap is streamless, so split attribution only exists at commit time),
651
+ * and reset the (sub,host) circuit breaker — all in ONE atomic Lua. Returns whether the breaker
652
+ * WAS open (this success closed it).
653
+ */
654
+ commitNullEvent: (subscription: string, host: string, position: number, splitIndex: string | number) => Promise<boolean>;
655
+ /**
656
+ * OPEN the per-`(sub,host)` circuit breaker (TV1-1954 slice 5). ONE Lua on the shared breaker clock:
657
+ * if `breakerOpenedAt:{sub}:{host}` is ABSENT → stamp it with redis `TIME` seconds (the SHARED clock,
658
+ * not a local one) and init `breakerCooldown` to `baseCooldownSeconds`, returning `true` (freshly
659
+ * opened). If ALREADY set → return `false` WITHOUT resetting the clock. The cooldown is DOUBLED to the
660
+ * `maxCooldownSeconds` cap ONLY when `probeFailed` is true (a FAILED half-open probe → exponential
661
+ * backoff, measured from the probe the tryProbeBreaker reset stamped); an already-open in-flight-batch
662
+ * failure (`probeFailed` false) is a NO-OP on the clock/cooldown, so a post-trip burst can neither
663
+ * inflate the cooldown to the cap nor storm. Idempotent under concurrent trips.
664
+ */
665
+ openBreaker: (subscription: string, host: string, baseCooldownSeconds: number, maxCooldownSeconds: number, probeFailed: boolean) => Promise<boolean>;
666
+ /**
667
+ * Half-open PROBE decision (TV1-1954 slice 5) — the Lua is the AUTHORITY, a local wake-up timer is
668
+ * only a HINT it can reject (kills local/shared clock drift). ONE Lua that reads
669
+ * `breakerOpenedAt:{sub}:{host}` + redis `TIME`: if `now - openedAt >= cooldownSeconds` it ALLOWS the
670
+ * probe (returns `true`) AND resets `openedAt = now` so the NEXT cooldown is measured from this probe;
671
+ * else it REJECTS (`false`, stay paused). Absent `openedAt` (breaker not open) → `false`.
672
+ */
673
+ tryProbeBreaker: (subscription: string, host: string, cooldownSeconds: number) => Promise<boolean>;
674
+ /**
675
+ * The current half-open cooldown (seconds) for `(sub,host)` — `breakerCooldown:{sub}:{host}`. Set to
676
+ * the base on a fresh open, DOUBLED on each probe failure (cap max), reset (deleted) on close. Absent
677
+ * ⇒ `undefined` (no breaker open / no failures recorded); callers default to the configured base.
678
+ */
679
+ getBreakerCooldown: (subscription: string, host: string) => Promise<number | undefined>;
680
+ /**
681
+ * Per-`(sub)` pending scoped-refill request HASH (TV1-1950 slice 9; generalized to bulk scope by
682
+ * TV1-1957) — field = in-scope SPLIT INDEX → payload — the operator poke the cursor-driven forward
683
+ * loop honors as a THIRD refill trigger (besides cold-start / eviction). REPLACES the earlier
684
+ * single-key `RefillRequestType` marker (first-clearer-wins, so a multi-split request would let the
685
+ * first owning instance to pump clear it before other owners ever saw it). A split index has EXACTLY
686
+ * ONE owner, so `setRefillRequest`/`clearRefillRequest` are single-field HSET/HDEL — no atomic
687
+ * read-modify-write or Lua needed. `getRefillRequests` reads the whole hash (one round-trip); the
688
+ * owning instance's `pumpOnce` filters to the fields it owns, batches them into ONE `refillByPosition`
689
+ * pass, then clears ONLY those fields — and only AFTER that pass's covered splits drain and their
690
+ * recovery cursors checkpoint (HDEL-after-drain).
691
+ */
692
+ getRefillRequests: (subscription: string) => Promise<Record<string, ScopedRefillEntryType>>;
693
+ setRefillRequest: (subscription: string, splitIndex: string, request: ScopedRefillEntryType) => Promise<void>;
694
+ clearRefillRequest: (subscription: string, splitIndex: string) => Promise<void>;
695
+ deleteSubscription: (subscription: string) => Promise<void>;
696
+ storeDataSplitPosition: (subscription: string, dataSplitId: string, stream: string, position: number, markAsProcessed?: boolean, dataSplitStatus?: DataSplitStatusIntMapType, errorHandlingStrategy?: ErrorHandlingStrategy, forceSet?: boolean) => Promise<void>;
697
+ storeErroredStream: (subscription: string, stream: string, position: number, isLastEvent?: boolean) => Promise<void>;
698
+ removeErrorStream: (subscription: string, stream: string, dataSplitId?: string) => Promise<void>;
699
+ /**
700
+ * Rich error detail for a quarantined (sub,stream) — the operator-facing companion of the
701
+ * `subErrorStreams` ZSET (which only holds stream → position). One HASH `subErrorDetails:{sub}`
702
+ * (field = stream → JSON detail) written at quarantine time, removed with the quarantine
703
+ * (removeErrorStream HDELs it; deleteSubscription drops the key). Best-effort bookkeeping: the
704
+ * quarantine itself never depends on it.
705
+ */
706
+ storeErroredStreamDetail: (subscription: string, stream: string, detail: ErroredStreamDetailType) => Promise<void>;
707
+ getErroredStreamDetails: (subscription: string) => Promise<Record<string, ErroredStreamDetailType>>;
708
+ setPositionForAllDataSplits: (subscription: string, splitCount: number, clusterVersion: number) => Promise<number>;
709
+ /**
710
+ * The packed per-split status (one ZSCORE on `subSplit:{sub}` + unpack) — the live dispatch gate's
711
+ * read. `critical` here means the split is HALTED (only the splitHalted write packs critical bits
712
+ * since TV1-1954 split-halt; the per-stream quarantine no longer paints the split).
713
+ */
714
+ getDataSplitStatus: (subscription: string, dataSplitId: string) => Promise<'ok' | 'retryable' | 'critical' | undefined>;
715
+ /**
716
+ * Halt-floor bookkeeping (TV1-1954 split-halt). While a split is halted the dispatch gate DROPS its
717
+ * events; a null-version event can arrive with a position BELOW the halt position (commit-LSN order
718
+ * ≠ position order), so the restart replay must start from the LOWEST dropped position, not just the
719
+ * halt position. `recordHaltDrop` is an atomic min (`ZADD LT` on `subHaltFloor:{sub}`, member =
720
+ * split index); restart reads it to bound the replay and clears it.
721
+ */
722
+ recordHaltDrop: (subscription: string, splitIndex: string, position: number) => Promise<void>;
723
+ getHaltFloor: (subscription: string, splitIndex: string) => Promise<number | undefined>;
724
+ clearHaltFloor: (subscription: string, splitIndex: string) => Promise<void>;
725
+ /**
726
+ * Rich failure detail for a HALTED split — the split-level analogue of `storeErroredStreamDetail`
727
+ * (`subHaltDetails:{sub}` HASH, field = split index → JSON). Written at halt time, removed by
728
+ * restart-datasplit; best-effort operator bookkeeping, never part of the halt correctness contract.
729
+ */
730
+ storeSplitHaltDetail: (subscription: string, splitIndex: string, detail: ErroredStreamDetailType) => Promise<void>;
731
+ getSplitHaltDetails: (subscription: string) => Promise<Record<string, ErroredStreamDetailType>>;
732
+ removeSplitHaltDetail: (subscription: string, splitIndex: string) => Promise<void>;
733
+ /**
734
+ * Live per-split retry progress (TV1-1954 retry visibility) — `subRetryState:{sub}` HASH, field =
735
+ * split index → JSON SplitRetryStateType. Written on each retryable attempt, cleared on every loop
736
+ * exit; surfaced through getSubscriptionDataSplits as each split's `retry` field. Best-effort
737
+ * operator bookkeeping, never part of the retry correctness contract.
738
+ */
739
+ setSplitRetryState: (subscription: string, splitIndex: string, retry: SplitRetryStateType) => Promise<void>;
740
+ clearSplitRetryState: (subscription: string, splitIndex: string) => Promise<void>;
741
+ getSplitRetryStates: (subscription: string) => Promise<Record<string, SplitRetryStateType>>;
742
+ start: () => Promise<void>;
743
+ };
25
744
  export type OutboxManagerType = {
26
- start: (activateDiscoveredSubscriptions?: boolean) => Promise<void>;
745
+ /** Receives an optional Structure State Variable with the result of the infrastructure change that happened when the service started */
746
+ start: (structureState?: StructureState) => Promise<void>;
27
747
  stop: () => Promise<void>;
748
+ state: StateType;
749
+ eventDispatcher: EventDispatcherType;
750
+ };
751
+ /**
752
+ * Callbacks for outbox event processing instrumentation.
753
+ * Implementations MUST NOT throw — callbacks are invoked inline during event processing
754
+ * and an unhandled exception would disrupt outbox behavior. Use try/catch internally.
755
+ * The built-in `instrumentEventProcessing()` factory wraps each callback in try/catch.
756
+ */
757
+ export type EventProcessingInstrumentation = {
758
+ onErrorClassified: (params: {
759
+ type: 'retryable' | 'critical' | 'sequence';
760
+ subscription: string;
761
+ event: string;
762
+ statusCode?: number;
763
+ }) => void;
764
+ onRetryAttempt: (params: {
765
+ subscription: string;
766
+ event: string;
767
+ attempt: number;
768
+ }) => void;
769
+ onRetryExhausted: (params: {
770
+ subscription: string;
771
+ event: string;
772
+ }) => void;
773
+ onStreamErrored: (params: {
774
+ subscription: string;
775
+ stream: string;
776
+ }) => void;
777
+ onStreamFixed: (params: {
778
+ subscription: string;
779
+ stream: string;
780
+ }) => void;
781
+ onDataSplitStatusChange: (params: {
782
+ subscription: string;
783
+ datasplit: string;
784
+ previousStatus: string;
785
+ newStatus: string;
786
+ }) => void;
787
+ onBackpressure: (params: {
788
+ subscription: string;
789
+ datasplit: string;
790
+ }) => void;
791
+ onSequenceRepull: (params: {
792
+ subscription: string;
793
+ datasplit: string;
794
+ }) => void;
795
+ onCacheHit?: (params: {
796
+ subscription: string;
797
+ source: 'debounce' | 'wait' | 'pull';
798
+ eventsServed: number;
799
+ }) => void;
800
+ onCacheMiss?: (params: {
801
+ subscription: string;
802
+ eventsRead: number;
803
+ durationMs: number;
804
+ }) => void;
805
+ onHydrate?: (params: {
806
+ eventstore: string;
807
+ source: 'redis' | 'db';
808
+ eventsRead: number;
809
+ durationMs: number;
810
+ }) => void;
811
+ onLazyFetch?: (params: {
812
+ subscription: string;
813
+ event: string;
814
+ durationMs: number;
815
+ }) => void;
816
+ /**
817
+ * Global commit-order stream DEPTH (`tail − floor`): how many entry-ids separate the stream's
818
+ * last entry from its trim floor (`firstId()`). Emitted per pump from the stream source, which is
819
+ * the layer that has both the tail and the floor. The backlog of un-trimmed events on the stream.
820
+ */
821
+ onStreamDepth?: (params: {
822
+ subscription: string;
823
+ depth: number;
824
+ }) => void;
825
+ /**
826
+ * Per-`(sub,instance)` consumer LAG = `tail − this instance's read cursor`: how far behind the
827
+ * live stream tail this consumer's XREAD cursor is (in entry-id distance). Emitted per pump BEFORE
828
+ * the cursor advances, from the stream source (the only layer that holds the per-instance cursor).
829
+ */
830
+ onConsumerLag?: (params: {
831
+ subscription: string;
832
+ instance: string;
833
+ lag: number;
834
+ }) => void;
835
+ /**
836
+ * By-position REFILL activity (cold-start / eviction / operator-replay recovery): the count of
837
+ * events re-dispatched by one `refillByPosition` pass. Emitted once per refill; a rising rate
838
+ * signals recovery churn (evictions, replays).
839
+ */
840
+ onRefill?: (params: {
841
+ subscription: string;
842
+ refilled: number;
843
+ }) => void;
844
+ /**
845
+ * Per-`(sub,stream)` WATERMARK progress: the version the shared `subWm` advanced to after a
846
+ * successful send (dispatch progress). Emitted on every watermark advance in the stream dispatch.
847
+ */
848
+ onWatermarkAdvance?: (params: {
849
+ subscription: string;
850
+ stream: string;
851
+ version: number;
852
+ }) => void;
853
+ /**
854
+ * Null-class circuit breaker TRIPPED (TV1-1954 slice 5): `breakerThreshold` consecutive failures for
855
+ * `(subscription, host)` reached, the host's split queues were PAUSED. The alarm the operator acts on
856
+ * (a dead consumer). Emitted from the dead-letter branch when the count crosses the threshold.
857
+ */
858
+ onBreakerOpen?: (params: {
859
+ subscription: string;
860
+ host: string;
861
+ count: number;
862
+ }) => void;
863
+ /**
864
+ * Null-class circuit breaker CLOSED (TV1-1954 slice 5): a successful null send (a recovered probe)
865
+ * reset the count and cleared the open clock, the host's split queues resumed. The auto-recovery signal.
866
+ */
867
+ onBreakerClose?: (params: {
868
+ subscription: string;
869
+ host: string;
870
+ }) => void;
28
871
  };
29
872
  export type TcpEventMessageRequest = {
30
873
  endpoint: string;
31
874
  event: CommittedEvent;
32
875
  };
33
876
  export type TcpEventMessageResponse = {
34
- id: number;
877
+ id: string;
878
+ position: number;
35
879
  status: number;
36
880
  error?: string;
37
881
  };