@indigoai-us/hq-cloud 6.14.49 → 6.15.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.
- package/.github/workflows/unreleased-commits-nag.yml +256 -0
- package/dist/bin/sync-runner-watch-loop.d.ts +12 -0
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +153 -25
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner-watch-routes.d.ts +17 -0
- package/dist/bin/sync-runner-watch-routes.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-routes.js +11 -0
- package/dist/bin/sync-runner-watch-routes.js.map +1 -1
- package/dist/bin/sync-runner-watch-routes.test.d.ts +2 -0
- package/dist/bin/sync-runner-watch-routes.test.d.ts.map +1 -0
- package/dist/bin/sync-runner-watch-routes.test.js +51 -0
- package/dist/bin/sync-runner-watch-routes.test.js.map +1 -0
- package/dist/bin/sync-runner.d.ts +11 -12
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +173 -52
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/reindex-knowledge.d.ts +7 -0
- package/dist/cli/reindex-knowledge.d.ts.map +1 -0
- package/dist/cli/reindex-knowledge.js +389 -0
- package/dist/cli/reindex-knowledge.js.map +1 -0
- package/dist/cli/reindex-knowledge.test.d.ts +12 -0
- package/dist/cli/reindex-knowledge.test.d.ts.map +1 -0
- package/dist/cli/reindex-knowledge.test.js +260 -0
- package/dist/cli/reindex-knowledge.test.js.map +1 -0
- package/dist/cli/reindex.d.ts.map +1 -1
- package/dist/cli/reindex.js +8 -0
- package/dist/cli/reindex.js.map +1 -1
- package/dist/cli/rescue-classify-ordering.test.js +8 -2
- package/dist/cli/rescue-classify-ordering.test.js.map +1 -1
- package/dist/cli/rescue-core.d.ts +85 -0
- package/dist/cli/rescue-core.d.ts.map +1 -1
- package/dist/cli/rescue-core.js +390 -1
- package/dist/cli/rescue-core.js.map +1 -1
- package/dist/cli/rescue-settings-reconcile.test.d.ts +2 -0
- package/dist/cli/rescue-settings-reconcile.test.d.ts.map +1 -0
- package/dist/cli/rescue-settings-reconcile.test.js +523 -0
- package/dist/cli/rescue-settings-reconcile.test.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +81 -51
- package/dist/journal.js.map +1 -1
- package/dist/journal.test.js +11 -19
- package/dist/journal.test.js.map +1 -1
- package/dist/sync/event-sync.d.ts +8 -25
- package/dist/sync/event-sync.d.ts.map +1 -1
- package/dist/sync/event-sync.js +23 -48
- package/dist/sync/event-sync.js.map +1 -1
- package/dist/sync/event-sync.test.js +6 -32
- package/dist/sync/event-sync.test.js.map +1 -1
- package/dist/sync/feature-flags.d.ts +20 -117
- package/dist/sync/feature-flags.d.ts.map +1 -1
- package/dist/sync/feature-flags.js +24 -147
- package/dist/sync/feature-flags.js.map +1 -1
- package/dist/sync/feature-flags.test.js +15 -61
- package/dist/sync/feature-flags.test.js.map +1 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -1
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/lease-client.d.ts +67 -0
- package/dist/sync/lease-client.d.ts.map +1 -0
- package/dist/sync/lease-client.js +158 -0
- package/dist/sync/lease-client.js.map +1 -0
- package/dist/sync/lease-client.test.d.ts +2 -0
- package/dist/sync/lease-client.test.d.ts.map +1 -0
- package/dist/sync/lease-client.test.js +119 -0
- package/dist/sync/lease-client.test.js.map +1 -0
- package/dist/sync/push-receiver.d.ts +8 -19
- package/dist/sync/push-receiver.d.ts.map +1 -1
- package/dist/sync/push-receiver.js +6 -50
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/sync/push-receiver.test.js +6 -57
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/dist/sync/realtime-rollout.d.ts +85 -0
- package/dist/sync/realtime-rollout.d.ts.map +1 -0
- package/dist/sync/realtime-rollout.js +206 -0
- package/dist/sync/realtime-rollout.js.map +1 -0
- package/dist/sync/realtime-rollout.test.d.ts +2 -0
- package/dist/sync/realtime-rollout.test.d.ts.map +1 -0
- package/dist/sync/realtime-rollout.test.js +71 -0
- package/dist/sync/realtime-rollout.test.js.map +1 -0
- package/dist/sync/state-store.d.ts +40 -0
- package/dist/sync/state-store.d.ts.map +1 -0
- package/dist/sync/state-store.js +680 -0
- package/dist/sync/state-store.js.map +1 -0
- package/dist/sync/state-store.test.d.ts +2 -0
- package/dist/sync/state-store.test.d.ts.map +1 -0
- package/dist/sync/state-store.test.js +159 -0
- package/dist/sync/state-store.test.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/bin/sync-runner-watch-loop.ts +176 -32
- package/src/bin/sync-runner-watch-routes.test.ts +71 -0
- package/src/bin/sync-runner-watch-routes.ts +27 -0
- package/src/bin/sync-runner.test.ts +188 -53
- package/src/bin/sync-runner.ts +11 -12
- package/src/cli/reindex-knowledge.test.ts +307 -0
- package/src/cli/reindex-knowledge.ts +450 -0
- package/src/cli/reindex.ts +9 -0
- package/src/cli/rescue-classify-ordering.test.ts +9 -3
- package/src/cli/rescue-core.ts +459 -1
- package/src/cli/rescue-settings-reconcile.test.ts +637 -0
- package/src/index.ts +2 -4
- package/src/journal.test.ts +19 -21
- package/src/journal.ts +87 -54
- package/src/sync/event-sync.test.ts +9 -44
- package/src/sync/event-sync.ts +26 -52
- package/src/sync/feature-flags.test.ts +22 -72
- package/src/sync/feature-flags.ts +32 -199
- package/src/sync/index.ts +2 -4
- package/src/sync/lease-client.test.ts +128 -0
- package/src/sync/lease-client.ts +207 -0
- package/src/sync/push-receiver.test.ts +6 -58
- package/src/sync/push-receiver.ts +9 -74
- package/src/sync/realtime-rollout.test.ts +86 -0
- package/src/sync/realtime-rollout.ts +262 -0
- package/src/sync/state-store.test.ts +194 -0
- package/src/sync/state-store.ts +727 -0
- package/src/types.ts +14 -0
- package/test/e2e/sync/transient-company-leg.test.ts +8 -4
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
import { describe, expect, it } from "vitest";
|
|
26
26
|
|
|
27
27
|
import { encodePushEvent, type PushEvent } from "./push-event.js";
|
|
28
|
-
import { StaticFlagProvider } from "./feature-flags.js";
|
|
29
28
|
import {
|
|
30
29
|
InMemoryFanout,
|
|
31
30
|
InMemoryPushReceiver,
|
|
@@ -155,7 +154,6 @@ describe("US-009: SqsPushReceiver — subscribe lifecycle", () => {
|
|
|
155
154
|
queueUrl: QUEUE_URL,
|
|
156
155
|
sqs,
|
|
157
156
|
syncFn,
|
|
158
|
-
enabled: true,
|
|
159
157
|
sleep: fastSleep,
|
|
160
158
|
});
|
|
161
159
|
|
|
@@ -176,7 +174,6 @@ describe("US-009: SqsPushReceiver — subscribe lifecycle", () => {
|
|
|
176
174
|
queueUrl: QUEUE_URL,
|
|
177
175
|
sqs,
|
|
178
176
|
syncFn: async () => undefined,
|
|
179
|
-
enabled: true,
|
|
180
177
|
sleep: fastSleep,
|
|
181
178
|
});
|
|
182
179
|
await receiver.start();
|
|
@@ -203,7 +200,6 @@ describe("US-009: SqsPushReceiver — subscribe lifecycle", () => {
|
|
|
203
200
|
queueUrl: QUEUE_URL,
|
|
204
201
|
sqs: spy,
|
|
205
202
|
syncFn: async () => undefined,
|
|
206
|
-
enabled: true,
|
|
207
203
|
sleep: fastSleep,
|
|
208
204
|
});
|
|
209
205
|
await receiver.start();
|
|
@@ -220,7 +216,6 @@ describe("US-009: SqsPushReceiver — subscribe lifecycle", () => {
|
|
|
220
216
|
queueUrl: QUEUE_URL,
|
|
221
217
|
sqs: new FakeSqs(),
|
|
222
218
|
syncFn: async () => undefined,
|
|
223
|
-
enabled: true,
|
|
224
219
|
}),
|
|
225
220
|
).toThrow(/tenantId is required/);
|
|
226
221
|
expect(
|
|
@@ -230,7 +225,6 @@ describe("US-009: SqsPushReceiver — subscribe lifecycle", () => {
|
|
|
230
225
|
queueUrl: "",
|
|
231
226
|
sqs: new FakeSqs(),
|
|
232
227
|
syncFn: async () => undefined,
|
|
233
|
-
enabled: true,
|
|
234
228
|
}),
|
|
235
229
|
).toThrow(/queueUrl is required/);
|
|
236
230
|
});
|
|
@@ -253,7 +247,6 @@ describe("US-009: SqsPushReceiver — dedupe", () => {
|
|
|
253
247
|
queueUrl: QUEUE_URL,
|
|
254
248
|
sqs,
|
|
255
249
|
syncFn,
|
|
256
|
-
enabled: true,
|
|
257
250
|
sleep: fastSleep,
|
|
258
251
|
});
|
|
259
252
|
await receiver.start();
|
|
@@ -281,7 +274,6 @@ describe("US-009: SqsPushReceiver — dedupe", () => {
|
|
|
281
274
|
queueUrl: QUEUE_URL,
|
|
282
275
|
sqs,
|
|
283
276
|
syncFn,
|
|
284
|
-
enabled: true,
|
|
285
277
|
sleep: fastSleep,
|
|
286
278
|
});
|
|
287
279
|
await receiver.start();
|
|
@@ -302,7 +294,6 @@ describe("US-009: SqsPushReceiver — dedupe", () => {
|
|
|
302
294
|
queueUrl: QUEUE_URL,
|
|
303
295
|
sqs,
|
|
304
296
|
syncFn: async () => undefined,
|
|
305
|
-
enabled: true,
|
|
306
297
|
sleep: fastSleep,
|
|
307
298
|
});
|
|
308
299
|
await receiver.start();
|
|
@@ -324,7 +315,6 @@ describe("US-009: SqsPushReceiver — reconnect / catch-up replay", () => {
|
|
|
324
315
|
queueUrl: QUEUE_URL,
|
|
325
316
|
sqs,
|
|
326
317
|
syncFn: async () => undefined,
|
|
327
|
-
enabled: true,
|
|
328
318
|
sleep: fastSleep,
|
|
329
319
|
reconnect: { initialMs: 1, maxMs: 4, jitter: false },
|
|
330
320
|
});
|
|
@@ -349,7 +339,6 @@ describe("US-009: SqsPushReceiver — reconnect / catch-up replay", () => {
|
|
|
349
339
|
queueUrl: QUEUE_URL,
|
|
350
340
|
sqs,
|
|
351
341
|
syncFn,
|
|
352
|
-
enabled: true,
|
|
353
342
|
sleep: fastSleep,
|
|
354
343
|
});
|
|
355
344
|
await receiver.start();
|
|
@@ -383,7 +372,6 @@ describe("US-009: SqsPushReceiver — reconnect / catch-up replay", () => {
|
|
|
383
372
|
queueUrl: QUEUE_URL,
|
|
384
373
|
sqs,
|
|
385
374
|
syncFn,
|
|
386
|
-
enabled: true,
|
|
387
375
|
sleep: fastSleep,
|
|
388
376
|
});
|
|
389
377
|
await receiver.start();
|
|
@@ -414,7 +402,6 @@ describe("US-009: SqsPushReceiver — reconnect / catch-up replay", () => {
|
|
|
414
402
|
queueUrl: QUEUE_URL,
|
|
415
403
|
sqs,
|
|
416
404
|
syncFn,
|
|
417
|
-
enabled: true,
|
|
418
405
|
sleep: fastSleep,
|
|
419
406
|
dedupeMaxPaths: 2,
|
|
420
407
|
});
|
|
@@ -433,25 +420,8 @@ describe("US-009: SqsPushReceiver — reconnect / catch-up replay", () => {
|
|
|
433
420
|
});
|
|
434
421
|
});
|
|
435
422
|
|
|
436
|
-
describe("
|
|
437
|
-
it("
|
|
438
|
-
const sqs = new FakeSqs();
|
|
439
|
-
const receiver = new SqsPushReceiver({
|
|
440
|
-
tenantId: TENANT,
|
|
441
|
-
queueUrl: QUEUE_URL,
|
|
442
|
-
sqs,
|
|
443
|
-
syncFn: async () => undefined,
|
|
444
|
-
flagProvider: new StaticFlagProvider([]), // tenant NOT enabled
|
|
445
|
-
sleep: fastSleep,
|
|
446
|
-
});
|
|
447
|
-
await receiver.start();
|
|
448
|
-
await new Promise((r) => setTimeout(r, 20));
|
|
449
|
-
expect(receiver.connected).toBe(false);
|
|
450
|
-
expect(sqs.receiveCalls).toBe(0);
|
|
451
|
-
await receiver.dispose();
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
it("is active when the per-tenant flag includes the tenant", async () => {
|
|
423
|
+
describe("U15: SqsPushReceiver — server-granted handoff", () => {
|
|
424
|
+
it("starts from supplied server queue material with no tenant client gate", async () => {
|
|
455
425
|
const sqs = new FakeSqs();
|
|
456
426
|
sqs.enqueue([msg(makeEvent({ sequenceNumber: 1 }))]);
|
|
457
427
|
const receiver = new SqsPushReceiver({
|
|
@@ -459,7 +429,6 @@ describe("US-009: SqsPushReceiver — flag gating", () => {
|
|
|
459
429
|
queueUrl: QUEUE_URL,
|
|
460
430
|
sqs,
|
|
461
431
|
syncFn: async () => undefined,
|
|
462
|
-
flagProvider: new StaticFlagProvider([TENANT]),
|
|
463
432
|
sleep: fastSleep,
|
|
464
433
|
});
|
|
465
434
|
await receiver.start();
|
|
@@ -468,23 +437,6 @@ describe("US-009: SqsPushReceiver — flag gating", () => {
|
|
|
468
437
|
await receiver.dispose();
|
|
469
438
|
});
|
|
470
439
|
|
|
471
|
-
it("explicit enabled wins over the provider", async () => {
|
|
472
|
-
const sqs = new FakeSqs();
|
|
473
|
-
const receiver = new SqsPushReceiver({
|
|
474
|
-
tenantId: TENANT,
|
|
475
|
-
queueUrl: QUEUE_URL,
|
|
476
|
-
sqs,
|
|
477
|
-
syncFn: async () => undefined,
|
|
478
|
-
enabled: false,
|
|
479
|
-
flagProvider: new StaticFlagProvider([TENANT]),
|
|
480
|
-
sleep: fastSleep,
|
|
481
|
-
});
|
|
482
|
-
await receiver.start();
|
|
483
|
-
await new Promise((r) => setTimeout(r, 20));
|
|
484
|
-
expect(receiver.connected).toBe(false);
|
|
485
|
-
expect(sqs.receiveCalls).toBe(0);
|
|
486
|
-
await receiver.dispose();
|
|
487
|
-
});
|
|
488
440
|
});
|
|
489
441
|
|
|
490
442
|
describe("US-009: SqsPushReceiver — dispose drain", () => {
|
|
@@ -506,7 +458,6 @@ describe("US-009: SqsPushReceiver — dispose drain", () => {
|
|
|
506
458
|
queueUrl: QUEUE_URL,
|
|
507
459
|
sqs,
|
|
508
460
|
syncFn,
|
|
509
|
-
enabled: true,
|
|
510
461
|
sleep: fastSleep,
|
|
511
462
|
disposeDrainMs: 200,
|
|
512
463
|
});
|
|
@@ -524,7 +475,6 @@ describe("US-009: SqsPushReceiver — dispose drain", () => {
|
|
|
524
475
|
queueUrl: QUEUE_URL,
|
|
525
476
|
sqs,
|
|
526
477
|
syncFn: async () => undefined,
|
|
527
|
-
enabled: true,
|
|
528
478
|
sleep: fastSleep,
|
|
529
479
|
});
|
|
530
480
|
await receiver.start();
|
|
@@ -542,7 +492,6 @@ describe("US-009: InMemoryPushReceiver — disconnect buffering + reconnect drai
|
|
|
542
492
|
const receiver = new InMemoryPushReceiver({
|
|
543
493
|
tenantId: TENANT,
|
|
544
494
|
fanout,
|
|
545
|
-
enabled: true,
|
|
546
495
|
syncFn: async (ctx) => {
|
|
547
496
|
processed.push(ctx.event.sequenceNumber);
|
|
548
497
|
},
|
|
@@ -568,13 +517,12 @@ describe("US-009: InMemoryPushReceiver — disconnect buffering + reconnect drai
|
|
|
568
517
|
expect(receiver.dedupedCount).toBe(1);
|
|
569
518
|
});
|
|
570
519
|
|
|
571
|
-
it("
|
|
520
|
+
it("does not derive activation from a tenant flag", async () => {
|
|
572
521
|
const fanout = new InMemoryFanout();
|
|
573
522
|
let calls = 0;
|
|
574
523
|
const receiver = new InMemoryPushReceiver({
|
|
575
524
|
tenantId: TENANT,
|
|
576
525
|
fanout,
|
|
577
|
-
flagProvider: new StaticFlagProvider([]),
|
|
578
526
|
syncFn: async () => {
|
|
579
527
|
calls += 1;
|
|
580
528
|
},
|
|
@@ -582,8 +530,9 @@ describe("US-009: InMemoryPushReceiver — disconnect buffering + reconnect drai
|
|
|
582
530
|
await receiver.start();
|
|
583
531
|
fanout.publish(encodePushEvent(makeEvent()));
|
|
584
532
|
await new Promise((r) => setTimeout(r, 20));
|
|
585
|
-
|
|
586
|
-
expect(
|
|
533
|
+
await waitFor(() => calls === 1);
|
|
534
|
+
expect(receiver.connected).toBe(true);
|
|
535
|
+
expect(calls).toBe(1);
|
|
587
536
|
await receiver.dispose();
|
|
588
537
|
});
|
|
589
538
|
});
|
|
@@ -602,7 +551,6 @@ describe("US-009: createPushReceiver factory", () => {
|
|
|
602
551
|
queueUrl: QUEUE_URL,
|
|
603
552
|
sqs: new FakeSqs(),
|
|
604
553
|
syncFn: async () => undefined,
|
|
605
|
-
enabled: true,
|
|
606
554
|
});
|
|
607
555
|
expect(r).toBeInstanceOf(SqsPushReceiver);
|
|
608
556
|
});
|
|
@@ -71,13 +71,11 @@
|
|
|
71
71
|
* events are absorbed by the dedupe path. The in-memory fake's
|
|
72
72
|
* `simulateDisconnect()` / `simulateReconnect()` model exactly this buffering.
|
|
73
73
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* Default DISABLED. Resolution precedence: explicit `enabled` > injected
|
|
80
|
-
* `flagProvider.isEnabled(tenantId)` > default env-driven provider.
|
|
74
|
+
* Authority
|
|
75
|
+
* ─────────
|
|
76
|
+
* This receiver is created only after the event-sync wiring obtains
|
|
77
|
+
* authenticated server inventory/credentials. It has no tenant, environment,
|
|
78
|
+
* or queued-hint-derived positive gate.
|
|
81
79
|
*
|
|
82
80
|
* Cross-tenant isolation (US-010)
|
|
83
81
|
* ───────────────────────────────
|
|
@@ -98,10 +96,6 @@
|
|
|
98
96
|
*/
|
|
99
97
|
|
|
100
98
|
import { decodePushEvent, type PushEvent } from "./push-event.js";
|
|
101
|
-
import {
|
|
102
|
-
defaultFlagProvider,
|
|
103
|
-
type EventDrivenPushFlagProvider,
|
|
104
|
-
} from "./feature-flags.js";
|
|
105
99
|
import {
|
|
106
100
|
publishSyncLatencyMetric,
|
|
107
101
|
type SyncLatencyMetric,
|
|
@@ -290,27 +284,6 @@ export class NoopPushReceiver implements PushReceiver {
|
|
|
290
284
|
}
|
|
291
285
|
}
|
|
292
286
|
|
|
293
|
-
// ─── Feature-flag resolution ─────────────────────────────────────────────────
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Resolve the enabled boolean from layered config. Precedence (highest first):
|
|
297
|
-
* 1. `explicit` — `enabled: true|false` wins outright (test contract).
|
|
298
|
-
* 2. `flagProvider.isEnabled(tenantId)` — injected per-tenant provider.
|
|
299
|
-
* 3. default {@link EnvTenantListFlagProvider} from `env ?? process.env`.
|
|
300
|
-
*/
|
|
301
|
-
function resolveEnabled(args: {
|
|
302
|
-
explicit: boolean | undefined;
|
|
303
|
-
flagProvider: EventDrivenPushFlagProvider | undefined;
|
|
304
|
-
tenantId: string;
|
|
305
|
-
env: Record<string, string | undefined> | undefined;
|
|
306
|
-
}): boolean {
|
|
307
|
-
if (args.explicit !== undefined) return args.explicit;
|
|
308
|
-
if (args.flagProvider !== undefined) {
|
|
309
|
-
return args.flagProvider.isEnabled(args.tenantId);
|
|
310
|
-
}
|
|
311
|
-
return defaultFlagProvider(args.env ?? process.env).isEnabled(args.tenantId);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
287
|
// ─── SQS receiver ─────────────────────────────────────────────────────────
|
|
315
288
|
|
|
316
289
|
/**
|
|
@@ -339,15 +312,10 @@ export interface SqsPushReceiverOptions {
|
|
|
339
312
|
/** Structured logger. Default: a no-op (quiet). */
|
|
340
313
|
logger?: ReceiverLogger;
|
|
341
314
|
/**
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* false, and NO queue is polled. (AC#4)
|
|
315
|
+
* Deprecated compatibility field. It is intentionally ignored: it can no
|
|
316
|
+
* longer enable or disable a receiver after server authorization.
|
|
345
317
|
*/
|
|
346
318
|
enabled?: boolean;
|
|
347
|
-
/** Per-tenant flag provider (US-008). Consulted when `enabled` is unset. */
|
|
348
|
-
flagProvider?: EventDrivenPushFlagProvider;
|
|
349
|
-
/** Env snapshot for flag resolution. Default: `process.env`. */
|
|
350
|
-
env?: Record<string, string | undefined>;
|
|
351
319
|
/** SQS long-poll wait seconds. Default {@link DEFAULT_WAIT_TIME_SECONDS}. */
|
|
352
320
|
waitTimeSeconds?: number;
|
|
353
321
|
/** Max messages per receive. Default {@link DEFAULT_MAX_MESSAGES}. */
|
|
@@ -394,7 +362,6 @@ export class SqsPushReceiver implements PushReceiver {
|
|
|
394
362
|
private readonly sqs: SqsClientLike;
|
|
395
363
|
private readonly syncFn: SyncEngineFn;
|
|
396
364
|
private readonly logger: ReceiverLogger;
|
|
397
|
-
private readonly enabled: boolean;
|
|
398
365
|
private readonly waitTimeSeconds: number;
|
|
399
366
|
private readonly maxMessages: number;
|
|
400
367
|
private readonly disposeDrainMs: number;
|
|
@@ -438,12 +405,6 @@ export class SqsPushReceiver implements PushReceiver {
|
|
|
438
405
|
this.sqs = opts.sqs;
|
|
439
406
|
this.syncFn = opts.syncFn;
|
|
440
407
|
this.logger = opts.logger ?? NOOP_LOGGER;
|
|
441
|
-
this.enabled = resolveEnabled({
|
|
442
|
-
explicit: opts.enabled,
|
|
443
|
-
flagProvider: opts.flagProvider,
|
|
444
|
-
tenantId: opts.tenantId,
|
|
445
|
-
env: opts.env,
|
|
446
|
-
});
|
|
447
408
|
this.waitTimeSeconds = opts.waitTimeSeconds ?? DEFAULT_WAIT_TIME_SECONDS;
|
|
448
409
|
this.maxMessages = opts.maxMessages ?? DEFAULT_MAX_MESSAGES;
|
|
449
410
|
this.disposeDrainMs =
|
|
@@ -467,17 +428,6 @@ export class SqsPushReceiver implements PushReceiver {
|
|
|
467
428
|
|
|
468
429
|
async start(): Promise<void> {
|
|
469
430
|
if (this.disposed) return;
|
|
470
|
-
if (!this.enabled) {
|
|
471
|
-
this.logger.info(
|
|
472
|
-
{
|
|
473
|
-
event: "receiver.start.disabled",
|
|
474
|
-
tenantId: this.tenantId,
|
|
475
|
-
reason: "feature flag off",
|
|
476
|
-
},
|
|
477
|
-
"push receiver disabled by feature flag",
|
|
478
|
-
);
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
431
|
if (this.loopAbort !== null) {
|
|
482
432
|
// Double-start is a no-op — matches PushTransport idempotency posture.
|
|
483
433
|
this.logger.debug(
|
|
@@ -853,9 +803,8 @@ export interface InMemoryPushReceiverOptions {
|
|
|
853
803
|
fanout: InMemoryFanout;
|
|
854
804
|
syncFn: SyncEngineFn;
|
|
855
805
|
logger?: ReceiverLogger;
|
|
806
|
+
/** Deprecated compatibility field; intentionally ignored. */
|
|
856
807
|
enabled?: boolean;
|
|
857
|
-
flagProvider?: EventDrivenPushFlagProvider;
|
|
858
|
-
env?: Record<string, string | undefined>;
|
|
859
808
|
disposeDrainMs?: number;
|
|
860
809
|
/** Maximum distinct paths retained in dedupe state. */
|
|
861
810
|
dedupeMaxPaths?: number;
|
|
@@ -873,7 +822,6 @@ export class InMemoryPushReceiver implements PushReceiver {
|
|
|
873
822
|
private readonly fanout: InMemoryFanout;
|
|
874
823
|
private readonly syncFn: SyncEngineFn;
|
|
875
824
|
private readonly logger: ReceiverLogger;
|
|
876
|
-
private readonly enabled: boolean;
|
|
877
825
|
private readonly disposeDrainMs: number;
|
|
878
826
|
|
|
879
827
|
private _connected = false;
|
|
@@ -901,12 +849,6 @@ export class InMemoryPushReceiver implements PushReceiver {
|
|
|
901
849
|
this.disposeDrainMs =
|
|
902
850
|
opts.disposeDrainMs ?? DEFAULT_RECEIVER_DISPOSE_DRAIN_MS;
|
|
903
851
|
this.seenSequencePerPath = new BoundedSequenceDedupe(opts.dedupeMaxPaths);
|
|
904
|
-
this.enabled = resolveEnabled({
|
|
905
|
-
explicit: opts.enabled,
|
|
906
|
-
flagProvider: opts.flagProvider,
|
|
907
|
-
tenantId: opts.tenantId,
|
|
908
|
-
env: opts.env,
|
|
909
|
-
});
|
|
910
852
|
}
|
|
911
853
|
|
|
912
854
|
get connected(): boolean {
|
|
@@ -915,13 +857,6 @@ export class InMemoryPushReceiver implements PushReceiver {
|
|
|
915
857
|
|
|
916
858
|
async start(): Promise<void> {
|
|
917
859
|
if (this.disposed) return;
|
|
918
|
-
if (!this.enabled) {
|
|
919
|
-
this.logger.info(
|
|
920
|
-
{ event: "receiver.start.disabled", tenantId: this.tenantId },
|
|
921
|
-
"push receiver disabled by feature flag",
|
|
922
|
-
);
|
|
923
|
-
return;
|
|
924
|
-
}
|
|
925
860
|
if (this.unsubscribe !== null) return; // double-start no-op
|
|
926
861
|
|
|
927
862
|
this.unsubscribe = this.fanout.subscribe((raw) => {
|
|
@@ -1013,7 +948,7 @@ export class InMemoryPushReceiver implements PushReceiver {
|
|
|
1013
948
|
|
|
1014
949
|
/** Emulate a network blip — events buffer instead of dispatching. */
|
|
1015
950
|
simulateDisconnect(): void {
|
|
1016
|
-
if (
|
|
951
|
+
if (this.unsubscribe === null) return;
|
|
1017
952
|
this.disconnectedFlag = true;
|
|
1018
953
|
this._connected = false;
|
|
1019
954
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
MAX_REALTIME_ROLLOUT_CACHE_MS,
|
|
5
|
+
RealtimeRolloutClient,
|
|
6
|
+
parseRealtimeInventory,
|
|
7
|
+
type RealtimeCapabilityVector,
|
|
8
|
+
} from "./realtime-rollout.js";
|
|
9
|
+
|
|
10
|
+
const capability: RealtimeCapabilityVector = {
|
|
11
|
+
hqCloud: "6.14.49",
|
|
12
|
+
hqDesktop: "none",
|
|
13
|
+
protocol: 2,
|
|
14
|
+
journal: 3,
|
|
15
|
+
durableApply: 1,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function jsonResponse(body: unknown, status = 200) {
|
|
19
|
+
return { ok: status >= 200 && status < 300, status, text: async () => JSON.stringify(body) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function request(overrides: Partial<Parameters<RealtimeRolloutClient["inventory"]>[0]> = {}) {
|
|
23
|
+
return {
|
|
24
|
+
bearer: "token-a",
|
|
25
|
+
deviceId: "machine_a",
|
|
26
|
+
capabilityVector: capability,
|
|
27
|
+
configurationFingerprint: "config-a",
|
|
28
|
+
scopeFingerprint: "personal:prs_1",
|
|
29
|
+
...overrides,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const readyInventory = {
|
|
34
|
+
scopes: [{ scopeUid: "prs_1", scopeHandle: "handle_1", scopeRevision: "rev_1", state: "v2-ready", cursor: "cursor_1" }],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
describe("RealtimeRolloutClient", () => {
|
|
38
|
+
it("never reuses a cached positive across bearer, configuration, or scope changes", async () => {
|
|
39
|
+
let now = 1_000;
|
|
40
|
+
const requests: Array<{ headers?: Record<string, string> }> = [];
|
|
41
|
+
const fetchImpl = async (_url: string, init?: { headers?: Record<string, string> }) => {
|
|
42
|
+
requests.push({ headers: init?.headers });
|
|
43
|
+
return jsonResponse(readyInventory);
|
|
44
|
+
};
|
|
45
|
+
const client = new RealtimeRolloutClient({ apiUrl: "https://api.example/", fetchImpl, now: () => now });
|
|
46
|
+
|
|
47
|
+
await client.inventory(request());
|
|
48
|
+
await client.inventory(request());
|
|
49
|
+
await client.inventory(request({ bearer: "token-b" }));
|
|
50
|
+
await client.inventory(request({ configurationFingerprint: "config-b" }));
|
|
51
|
+
await client.inventory(request({ scopeFingerprint: "company:cmp_1" }));
|
|
52
|
+
now += MAX_REALTIME_ROLLOUT_CACHE_MS;
|
|
53
|
+
await client.inventory(request());
|
|
54
|
+
|
|
55
|
+
expect(requests).toHaveLength(5);
|
|
56
|
+
expect(requests[0]?.headers).toMatchObject({
|
|
57
|
+
Authorization: "Bearer token-a",
|
|
58
|
+
"X-HQ-Sync-Contract": "2",
|
|
59
|
+
"X-HQ-Device-ID": "machine_a",
|
|
60
|
+
"X-HQ-Cloud-Version": "6.14.49",
|
|
61
|
+
"X-HQ-Desktop-Version": "none",
|
|
62
|
+
"X-HQ-Sync-Journal": "3",
|
|
63
|
+
"X-HQ-Sync-Durable-Apply": "1",
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("fails closed for malformed or non-explicit legacy inventory", () => {
|
|
68
|
+
expect(() => parseRealtimeInventory({ scopes: [{ scopeUid: "prs_1", scopeHandle: "h", scopeRevision: "r", state: "legacy", v1Allowed: false }] })).toThrow("legacy must explicitly allow v1");
|
|
69
|
+
expect(() => parseRealtimeInventory({ scopes: [{ scopeUid: "prs_1", scopeHandle: "h", scopeRevision: "r", state: "v2-ready", cursor: "c", surprise: true }] })).toThrow("unknown field");
|
|
70
|
+
expect(() => parseRealtimeInventory({ scopes: [{ scopeUid: "prs_1", scopeHandle: "h", scopeRevision: "r", state: "unknown" }] })).toThrow("unsupported scope state");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("rejects cache durations greater than the protocol maximum", () => {
|
|
74
|
+
expect(() => new RealtimeRolloutClient({ apiUrl: "https://api.example", maxCacheAgeMs: MAX_REALTIME_ROLLOUT_CACHE_MS + 1 })).toThrow("maxCacheAgeMs");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("honors transitional retryAfterMs instead of holding a stale negative state", async () => {
|
|
78
|
+
const fetchImpl = vi.fn(async () => jsonResponse({ scopes: [
|
|
79
|
+
{ scopeUid: "prs_1", scopeHandle: "handle_1", scopeRevision: "rev_1", state: "restoring", retryAfterMs: 0 },
|
|
80
|
+
] }));
|
|
81
|
+
const client = new RealtimeRolloutClient({ apiUrl: "https://api.example", fetchImpl });
|
|
82
|
+
await client.inventory(request());
|
|
83
|
+
await client.inventory(request());
|
|
84
|
+
expect(fetchImpl).toHaveBeenCalledTimes(2);
|
|
85
|
+
});
|
|
86
|
+
});
|