@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
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
} from "../watcher.js";
|
|
43
43
|
import {
|
|
44
44
|
adaptivePollMs,
|
|
45
|
+
advanceMonotonicDeadline,
|
|
45
46
|
journalEntriesForWatcherDelete,
|
|
46
47
|
shouldQuarantineWatcherDeleteIntents,
|
|
47
48
|
} from "./sync-runner-watch-loop.js";
|
|
@@ -4205,6 +4206,22 @@ describe("adaptivePollMs (whole-box CPU backoff)", () => {
|
|
|
4205
4206
|
});
|
|
4206
4207
|
});
|
|
4207
4208
|
|
|
4209
|
+
describe("advanceMonotonicDeadline", () => {
|
|
4210
|
+
it("runs one immediate catch-up after missed slots, then resumes at the first future slot", () => {
|
|
4211
|
+
// A pass due at t=0 took through t=1,800 with a 600 ms cadence. It does
|
|
4212
|
+
// not replay the three missed slots; exactly one catch-up is immediate and
|
|
4213
|
+
// the following regular deadline is t=2,400.
|
|
4214
|
+
expect(advanceMonotonicDeadline(0, 1_800, 600)).toEqual({
|
|
4215
|
+
nextDueAt: 2_400,
|
|
4216
|
+
delayMs: 0,
|
|
4217
|
+
});
|
|
4218
|
+
expect(advanceMonotonicDeadline(2_400, 1_800, 600)).toEqual({
|
|
4219
|
+
nextDueAt: 3_000,
|
|
4220
|
+
delayMs: 1_200,
|
|
4221
|
+
});
|
|
4222
|
+
});
|
|
4223
|
+
});
|
|
4224
|
+
|
|
4208
4225
|
describe("runRunnerWithLoop — adaptive poll interval (no --poll-remote-ms)", () => {
|
|
4209
4226
|
// Capture the ms handed to sleep on the FIRST poll, then hang so the loop
|
|
4210
4227
|
// parks until shutdown (mirrors the never-resolving sleep the other loop
|
|
@@ -4220,8 +4237,14 @@ describe("runRunnerWithLoop — adaptive poll interval (no --poll-remote-ms)", (
|
|
|
4220
4237
|
};
|
|
4221
4238
|
}
|
|
4222
4239
|
|
|
4223
|
-
it("
|
|
4240
|
+
it("waits only the bounded remainder of the idle cadence slot", async () => {
|
|
4224
4241
|
const { calls, sleep } = makeCapturingSleep();
|
|
4242
|
+
const monotonicNow = vi
|
|
4243
|
+
.fn<() => number>()
|
|
4244
|
+
.mockReturnValueOnce(1_000)
|
|
4245
|
+
// A 20 ms pass consumes part of this 60 s slot. The scheduler must
|
|
4246
|
+
// retain the original deadline rather than slewing a new 60 s wait.
|
|
4247
|
+
.mockReturnValueOnce(1_020);
|
|
4225
4248
|
let triggerShutdown = () => {};
|
|
4226
4249
|
const loop = runRunnerWithLoop(
|
|
4227
4250
|
["--companies", "--watch", "--hq-root", "/tmp/hq"],
|
|
@@ -4229,6 +4252,7 @@ describe("runRunnerWithLoop — adaptive poll interval (no --poll-remote-ms)", (
|
|
|
4229
4252
|
runPass: vi.fn().mockResolvedValue(0),
|
|
4230
4253
|
sleep,
|
|
4231
4254
|
sampleLoadAvg: () => 0,
|
|
4255
|
+
monotonicNow,
|
|
4232
4256
|
onShutdownSignal: (handler) => {
|
|
4233
4257
|
triggerShutdown = handler;
|
|
4234
4258
|
return () => {};
|
|
@@ -4236,13 +4260,19 @@ describe("runRunnerWithLoop — adaptive poll interval (no --poll-remote-ms)", (
|
|
|
4236
4260
|
},
|
|
4237
4261
|
);
|
|
4238
4262
|
await flushLoopMicrotasks();
|
|
4239
|
-
expect(calls[0]).toBe(
|
|
4263
|
+
expect(calls[0]).toBe(59_980);
|
|
4264
|
+
expect(calls[0]).toBeGreaterThan(0);
|
|
4265
|
+
expect(calls[0]).toBeLessThanOrEqual(60_000);
|
|
4240
4266
|
triggerShutdown();
|
|
4241
4267
|
await loop;
|
|
4242
4268
|
});
|
|
4243
4269
|
|
|
4244
|
-
it("
|
|
4270
|
+
it("waits only the bounded remainder of the saturated cadence slot", async () => {
|
|
4245
4271
|
const { calls, sleep } = makeCapturingSleep();
|
|
4272
|
+
const monotonicNow = vi
|
|
4273
|
+
.fn<() => number>()
|
|
4274
|
+
.mockReturnValueOnce(1_000)
|
|
4275
|
+
.mockReturnValueOnce(1_075);
|
|
4246
4276
|
let triggerShutdown = () => {};
|
|
4247
4277
|
const loop = runRunnerWithLoop(
|
|
4248
4278
|
["--companies", "--watch", "--hq-root", "/tmp/hq"],
|
|
@@ -4250,6 +4280,7 @@ describe("runRunnerWithLoop — adaptive poll interval (no --poll-remote-ms)", (
|
|
|
4250
4280
|
runPass: vi.fn().mockResolvedValue(0),
|
|
4251
4281
|
sleep,
|
|
4252
4282
|
sampleLoadAvg: () => 1_000, // far above any core count → clamps to ceil
|
|
4283
|
+
monotonicNow,
|
|
4253
4284
|
onShutdownSignal: (handler) => {
|
|
4254
4285
|
triggerShutdown = handler;
|
|
4255
4286
|
return () => {};
|
|
@@ -4257,14 +4288,20 @@ describe("runRunnerWithLoop — adaptive poll interval (no --poll-remote-ms)", (
|
|
|
4257
4288
|
},
|
|
4258
4289
|
);
|
|
4259
4290
|
await flushLoopMicrotasks();
|
|
4260
|
-
expect(calls[0]).toBe(
|
|
4291
|
+
expect(calls[0]).toBe(599_925);
|
|
4292
|
+
expect(calls[0]).toBeGreaterThan(0);
|
|
4293
|
+
expect(calls[0]).toBeLessThanOrEqual(600_000);
|
|
4261
4294
|
triggerShutdown();
|
|
4262
4295
|
await loop;
|
|
4263
4296
|
});
|
|
4264
4297
|
|
|
4265
|
-
it("
|
|
4298
|
+
it("uses the bounded remainder of an explicit cadence slot and ignores load", async () => {
|
|
4266
4299
|
const { calls, sleep } = makeCapturingSleep();
|
|
4267
4300
|
const sampleLoadAvg = vi.fn(() => 1_000);
|
|
4301
|
+
const monotonicNow = vi
|
|
4302
|
+
.fn<() => number>()
|
|
4303
|
+
.mockReturnValueOnce(1_000)
|
|
4304
|
+
.mockReturnValueOnce(1_123);
|
|
4268
4305
|
let triggerShutdown = () => {};
|
|
4269
4306
|
const loop = runRunnerWithLoop(
|
|
4270
4307
|
["--companies", "--watch", "--poll-remote-ms", "123456", "--hq-root", "/tmp/hq"],
|
|
@@ -4272,6 +4309,7 @@ describe("runRunnerWithLoop — adaptive poll interval (no --poll-remote-ms)", (
|
|
|
4272
4309
|
runPass: vi.fn().mockResolvedValue(0),
|
|
4273
4310
|
sleep,
|
|
4274
4311
|
sampleLoadAvg,
|
|
4312
|
+
monotonicNow,
|
|
4275
4313
|
onShutdownSignal: (handler) => {
|
|
4276
4314
|
triggerShutdown = handler;
|
|
4277
4315
|
return () => {};
|
|
@@ -4279,7 +4317,9 @@ describe("runRunnerWithLoop — adaptive poll interval (no --poll-remote-ms)", (
|
|
|
4279
4317
|
},
|
|
4280
4318
|
);
|
|
4281
4319
|
await flushLoopMicrotasks();
|
|
4282
|
-
expect(calls[0]).toBe(
|
|
4320
|
+
expect(calls[0]).toBe(123_333);
|
|
4321
|
+
expect(calls[0]).toBeGreaterThan(0);
|
|
4322
|
+
expect(calls[0]).toBeLessThanOrEqual(123_456);
|
|
4283
4323
|
expect(sampleLoadAvg).not.toHaveBeenCalled();
|
|
4284
4324
|
triggerShutdown();
|
|
4285
4325
|
await loop;
|
|
@@ -4317,19 +4357,32 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
4317
4357
|
expect(watcher.disposed).toBe(true);
|
|
4318
4358
|
});
|
|
4319
4359
|
|
|
4320
|
-
it("
|
|
4360
|
+
it("U07: watcher interrupts a fake 600 s wait and drains through the guarded scheduler", async () => {
|
|
4321
4361
|
const watcher = makeWatcherStub();
|
|
4322
4362
|
const clock = new FakeClock();
|
|
4323
4363
|
let triggerShutdown = () => {};
|
|
4324
4364
|
const runPass = vi.fn().mockResolvedValue(0);
|
|
4365
|
+
const sleepCalls: number[] = [];
|
|
4325
4366
|
|
|
4326
4367
|
const loop = runRunnerWithLoop(
|
|
4327
|
-
[
|
|
4368
|
+
[
|
|
4369
|
+
"--companies",
|
|
4370
|
+
"--watch",
|
|
4371
|
+
"--event-push",
|
|
4372
|
+
"--poll-remote-ms",
|
|
4373
|
+
"600000",
|
|
4374
|
+
"--hq-root",
|
|
4375
|
+
"/tmp/hq",
|
|
4376
|
+
],
|
|
4328
4377
|
{
|
|
4329
4378
|
runPass,
|
|
4330
4379
|
clock,
|
|
4331
4380
|
createWatcher: () => watcher,
|
|
4332
|
-
|
|
4381
|
+
monotonicNow: () => 0,
|
|
4382
|
+
sleep: (ms) => {
|
|
4383
|
+
sleepCalls.push(ms);
|
|
4384
|
+
return new Promise<void>(() => {});
|
|
4385
|
+
},
|
|
4333
4386
|
onShutdownSignal: (handler) => {
|
|
4334
4387
|
triggerShutdown = handler;
|
|
4335
4388
|
return () => {};
|
|
@@ -4337,24 +4390,20 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
4337
4390
|
},
|
|
4338
4391
|
);
|
|
4339
4392
|
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
await Promise.resolve();
|
|
4343
|
-
runPass.mockClear();
|
|
4393
|
+
await flushLoopMicrotasks();
|
|
4394
|
+
expect(sleepCalls).toEqual([600_000]);
|
|
4344
4395
|
|
|
4345
|
-
//
|
|
4396
|
+
// The fake 600-second timer remains unresolved. The watcher wake must
|
|
4397
|
+
// nonetheless start the next pass immediately through runGuarded.
|
|
4346
4398
|
watcher.emit("companies/indigo/board.json");
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
await Promise.resolve();
|
|
4350
|
-
|
|
4351
|
-
expect(runPass).not.toHaveBeenCalled();
|
|
4399
|
+
await flushLoopMicrotasks();
|
|
4400
|
+
expect(runPass).toHaveBeenCalledTimes(2);
|
|
4352
4401
|
|
|
4353
4402
|
triggerShutdown();
|
|
4354
4403
|
await loop;
|
|
4355
4404
|
});
|
|
4356
4405
|
|
|
4357
|
-
it("bare signal
|
|
4406
|
+
it("bare watcher signal interrupts the wait and schedules a full reconcile", async () => {
|
|
4358
4407
|
const watcher = makeWatcherStub();
|
|
4359
4408
|
const clock = new FakeClock();
|
|
4360
4409
|
let triggerShutdown = () => {};
|
|
@@ -4380,10 +4429,9 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
4380
4429
|
|
|
4381
4430
|
watcher.emit(); // no path — TreeWatcher's bare signal shape
|
|
4382
4431
|
clock.advance(0);
|
|
4383
|
-
await
|
|
4384
|
-
await Promise.resolve();
|
|
4432
|
+
await flushLoopMicrotasks();
|
|
4385
4433
|
|
|
4386
|
-
expect(runPass).
|
|
4434
|
+
expect(runPass).toHaveBeenCalledTimes(1);
|
|
4387
4435
|
|
|
4388
4436
|
triggerShutdown();
|
|
4389
4437
|
await loop;
|
|
@@ -4453,7 +4501,61 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
4453
4501
|
await loop;
|
|
4454
4502
|
});
|
|
4455
4503
|
|
|
4456
|
-
it("
|
|
4504
|
+
it("U07: retains a watcher event raised before the next wait registers, without guarded overlap", async () => {
|
|
4505
|
+
const watcher = makeWatcherStub();
|
|
4506
|
+
let triggerShutdown = () => {};
|
|
4507
|
+
let releaseFirstPass: () => void = () => {};
|
|
4508
|
+
const firstPassGate = new Promise<void>((resolve) => {
|
|
4509
|
+
releaseFirstPass = resolve;
|
|
4510
|
+
});
|
|
4511
|
+
const spans: Array<{ enteredAt: bigint; exitedAt: bigint }> = [];
|
|
4512
|
+
let passes = 0;
|
|
4513
|
+
const runPass = vi.fn().mockImplementation(async () => {
|
|
4514
|
+
const span = { enteredAt: process.hrtime.bigint(), exitedAt: 0n };
|
|
4515
|
+
spans.push(span);
|
|
4516
|
+
passes += 1;
|
|
4517
|
+
if (passes === 1) await firstPassGate;
|
|
4518
|
+
span.exitedAt = process.hrtime.bigint();
|
|
4519
|
+
return 0;
|
|
4520
|
+
});
|
|
4521
|
+
|
|
4522
|
+
const loop = runRunnerWithLoop(
|
|
4523
|
+
[
|
|
4524
|
+
"--companies",
|
|
4525
|
+
"--watch",
|
|
4526
|
+
"--event-push",
|
|
4527
|
+
"--poll-remote-ms",
|
|
4528
|
+
"600000",
|
|
4529
|
+
"--hq-root",
|
|
4530
|
+
"/tmp/hq",
|
|
4531
|
+
],
|
|
4532
|
+
{
|
|
4533
|
+
runPass,
|
|
4534
|
+
createWatcher: () => watcher,
|
|
4535
|
+
sleep: () => new Promise<void>(() => {}),
|
|
4536
|
+
onShutdownSignal: (handler) => {
|
|
4537
|
+
triggerShutdown = handler;
|
|
4538
|
+
return () => {};
|
|
4539
|
+
},
|
|
4540
|
+
},
|
|
4541
|
+
);
|
|
4542
|
+
|
|
4543
|
+
await flushLoopMicrotasks();
|
|
4544
|
+
expect(spans).toHaveLength(1);
|
|
4545
|
+
// This event arrives while the initial guarded pass owns the scheduler,
|
|
4546
|
+
// before its next sleep/wait has been registered.
|
|
4547
|
+
watcher.emit("companies/indigo/pre-registration.md");
|
|
4548
|
+
releaseFirstPass();
|
|
4549
|
+
await flushLoopMicrotasks();
|
|
4550
|
+
|
|
4551
|
+
expect(spans).toHaveLength(2);
|
|
4552
|
+
expect(spans[1]!.enteredAt >= spans[0]!.exitedAt).toBe(true);
|
|
4553
|
+
|
|
4554
|
+
triggerShutdown();
|
|
4555
|
+
await loop;
|
|
4556
|
+
});
|
|
4557
|
+
|
|
4558
|
+
it("F05: guard-held watcher changes stay accumulated, then drain immediately after the guarded pass", async () => {
|
|
4457
4559
|
const watcher = makeWatcherStub();
|
|
4458
4560
|
const clock = new FakeClock();
|
|
4459
4561
|
let triggerShutdown = () => {};
|
|
@@ -4551,8 +4653,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
4551
4653
|
|
|
4552
4654
|
// Step the poll interval → a second poll pass runs (safety net cadence).
|
|
4553
4655
|
poll.resolve?.();
|
|
4554
|
-
await
|
|
4555
|
-
await Promise.resolve();
|
|
4656
|
+
await flushLoopMicrotasks();
|
|
4556
4657
|
expect(passes).toBeGreaterThanOrEqual(2);
|
|
4557
4658
|
|
|
4558
4659
|
triggerShutdown();
|
|
@@ -4876,7 +4977,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4876
4977
|
};
|
|
4877
4978
|
}
|
|
4878
4979
|
|
|
4879
|
-
it("startup runs one full reconcile, then watcher edits
|
|
4980
|
+
it("startup runs one full reconcile, then watcher edits interrupt the pending wait", async () => {
|
|
4880
4981
|
const runPass = vi.fn().mockResolvedValue(0);
|
|
4881
4982
|
const { loop, watcher, tick, shutdown } = startDrainLoop(runPass);
|
|
4882
4983
|
await flushLoopMicrotasks();
|
|
@@ -4886,10 +4987,6 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4886
4987
|
|
|
4887
4988
|
watcher.emit("companies/indigo/knowledge/a.md");
|
|
4888
4989
|
await flushLoopMicrotasks();
|
|
4889
|
-
expect(runPass).not.toHaveBeenCalled();
|
|
4890
|
-
|
|
4891
|
-
tick();
|
|
4892
|
-
await flushLoopMicrotasks();
|
|
4893
4990
|
|
|
4894
4991
|
expect(runPass.mock.calls.map((call) => call[0])).toEqual([
|
|
4895
4992
|
[
|
|
@@ -4942,6 +5039,28 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4942
5039
|
await loop;
|
|
4943
5040
|
});
|
|
4944
5041
|
|
|
5042
|
+
it("U08: --companies --skip-personal drops a personal watcher edit before it queues work", async () => {
|
|
5043
|
+
const runPass = vi.fn().mockResolvedValue(0);
|
|
5044
|
+
const { loop, watcher, tick, shutdown } = startDrainLoopWithArgv(runPass, [
|
|
5045
|
+
...watchArgv,
|
|
5046
|
+
"--skip-personal",
|
|
5047
|
+
]);
|
|
5048
|
+
await flushLoopMicrotasks();
|
|
5049
|
+
runPass.mockClear();
|
|
5050
|
+
|
|
5051
|
+
watcher.emit("knowledge/private.md");
|
|
5052
|
+
await flushLoopMicrotasks();
|
|
5053
|
+
|
|
5054
|
+
// The watcher event is rejected before the scheduler is interrupted:
|
|
5055
|
+
// no route-specific pass is queued and therefore no runner/network work
|
|
5056
|
+
// is reachable for a disabled personal scope.
|
|
5057
|
+
expect(runPass).not.toHaveBeenCalled();
|
|
5058
|
+
|
|
5059
|
+
shutdown();
|
|
5060
|
+
tick();
|
|
5061
|
+
await loop;
|
|
5062
|
+
});
|
|
5063
|
+
|
|
4945
5064
|
it("drains an unlink path through the same scoped tombstone push path", async () => {
|
|
4946
5065
|
const runPass = vi.fn().mockResolvedValue(0);
|
|
4947
5066
|
const { loop, watcher, tick, shutdown } = startDrainLoop(runPass);
|
|
@@ -6215,7 +6334,7 @@ function makeBatchWatcherStub(): BatchWatcherStub {
|
|
|
6215
6334
|
}
|
|
6216
6335
|
|
|
6217
6336
|
describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", () => {
|
|
6218
|
-
const
|
|
6337
|
+
const EMAIL_CLAIMS = { email: "hassaan@getindigo.ai" };
|
|
6219
6338
|
const UNENROLLED_CLAIMS = { email: "someone@getindigo.ai" };
|
|
6220
6339
|
|
|
6221
6340
|
function runLoop(opts: {
|
|
@@ -6271,7 +6390,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6271
6390
|
for (let i = 0; i < n; i++) await Promise.resolve();
|
|
6272
6391
|
}
|
|
6273
6392
|
|
|
6274
|
-
it("
|
|
6393
|
+
it("does not use an unenrolled email as a client-side denial", async () => {
|
|
6275
6394
|
const startEventSync = vi.fn().mockResolvedValue(null);
|
|
6276
6395
|
const { loop, shutdown } = runLoop({
|
|
6277
6396
|
claims: UNENROLLED_CLAIMS,
|
|
@@ -6280,22 +6399,22 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6280
6399
|
await microtasks();
|
|
6281
6400
|
shutdown();
|
|
6282
6401
|
await loop;
|
|
6283
|
-
expect(startEventSync).
|
|
6402
|
+
expect(startEventSync).toHaveBeenCalledTimes(1);
|
|
6284
6403
|
});
|
|
6285
6404
|
|
|
6286
|
-
it("
|
|
6405
|
+
it("does not use missing cached claims as a client-side denial", async () => {
|
|
6287
6406
|
const startEventSync = vi.fn().mockResolvedValue(null);
|
|
6288
6407
|
const { loop, shutdown } = runLoop({ claims: null, startEventSync });
|
|
6289
6408
|
await microtasks();
|
|
6290
6409
|
shutdown();
|
|
6291
6410
|
await loop;
|
|
6292
|
-
expect(startEventSync).
|
|
6411
|
+
expect(startEventSync).toHaveBeenCalledTimes(1);
|
|
6293
6412
|
});
|
|
6294
6413
|
|
|
6295
|
-
it("
|
|
6414
|
+
it("asks the server for authority before wiring publish and receive", async () => {
|
|
6296
6415
|
const startEventSync = vi.fn().mockResolvedValue(null);
|
|
6297
6416
|
const { loop, shutdown } = runLoop({
|
|
6298
|
-
claims:
|
|
6417
|
+
claims: EMAIL_CLAIMS,
|
|
6299
6418
|
startEventSync,
|
|
6300
6419
|
});
|
|
6301
6420
|
await microtasks();
|
|
@@ -6319,9 +6438,18 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6319
6438
|
ownDeviceId: "dev-test",
|
|
6320
6439
|
dispose: vi.fn().mockResolvedValue(undefined),
|
|
6321
6440
|
});
|
|
6322
|
-
|
|
6323
|
-
const
|
|
6324
|
-
|
|
6441
|
+
let finishTargetedPush: (outcome: number) => void = () => {};
|
|
6442
|
+
const targetedPush = new Promise<number>((resolve) => {
|
|
6443
|
+
finishTargetedPush = resolve;
|
|
6444
|
+
});
|
|
6445
|
+
const runPass = vi
|
|
6446
|
+
.fn()
|
|
6447
|
+
.mockResolvedValueOnce(0)
|
|
6448
|
+
.mockImplementation((passArgv: string[]) =>
|
|
6449
|
+
passArgv.includes("--company") ? targetedPush : Promise.resolve(0),
|
|
6450
|
+
);
|
|
6451
|
+
const { loop, watcher, clock, shutdown } = runLoop({
|
|
6452
|
+
claims: EMAIL_CLAIMS,
|
|
6325
6453
|
startEventSync,
|
|
6326
6454
|
runPass,
|
|
6327
6455
|
});
|
|
@@ -6334,14 +6462,21 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6334
6462
|
watcher.emit("companies/indigo/a.md", batch);
|
|
6335
6463
|
clock.advance(0);
|
|
6336
6464
|
await microtasks();
|
|
6337
|
-
|
|
6465
|
+
|
|
6466
|
+
// U07 wakes the scheduler immediately. It may start the scoped push, but
|
|
6467
|
+
// it must not announce the batch until that push has succeeded.
|
|
6468
|
+
expect(runPass).toHaveBeenCalledWith(expect.arrayContaining([
|
|
6469
|
+
"--company",
|
|
6470
|
+
"indigo",
|
|
6471
|
+
"--direction",
|
|
6472
|
+
"push",
|
|
6473
|
+
]));
|
|
6338
6474
|
expect(publishBatch).not.toHaveBeenCalled();
|
|
6339
6475
|
|
|
6340
|
-
|
|
6476
|
+
finishTargetedPush(0);
|
|
6341
6477
|
await microtasks();
|
|
6342
6478
|
|
|
6343
6479
|
// Scoped push drained and succeeded → batch published.
|
|
6344
|
-
expect(runPass).toHaveBeenCalled();
|
|
6345
6480
|
expect(publishBatch).toHaveBeenCalledTimes(1);
|
|
6346
6481
|
expect([...publishBatch.mock.calls[0][0].paths.values()]).toEqual([
|
|
6347
6482
|
"companies/indigo/a.md",
|
|
@@ -6365,7 +6500,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6365
6500
|
.mockResolvedValueOnce(0)
|
|
6366
6501
|
.mockResolvedValue(1);
|
|
6367
6502
|
const { loop, watcher, clock, tick, shutdown } = runLoop({
|
|
6368
|
-
claims:
|
|
6503
|
+
claims: EMAIL_CLAIMS,
|
|
6369
6504
|
startEventSync,
|
|
6370
6505
|
runPass,
|
|
6371
6506
|
});
|
|
@@ -6402,7 +6537,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6402
6537
|
});
|
|
6403
6538
|
const runPass = vi.fn().mockResolvedValue(0);
|
|
6404
6539
|
const { loop, watcher, clock, tick, shutdown } = runLoop({
|
|
6405
|
-
claims:
|
|
6540
|
+
claims: EMAIL_CLAIMS,
|
|
6406
6541
|
startEventSync,
|
|
6407
6542
|
runPass,
|
|
6408
6543
|
});
|
|
@@ -6459,7 +6594,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6459
6594
|
})
|
|
6460
6595
|
.mockResolvedValue(0);
|
|
6461
6596
|
const { loop, watcher, tick, shutdown } = runLoop({
|
|
6462
|
-
claims:
|
|
6597
|
+
claims: EMAIL_CLAIMS,
|
|
6463
6598
|
startEventSync,
|
|
6464
6599
|
runPass,
|
|
6465
6600
|
});
|
|
@@ -6502,7 +6637,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6502
6637
|
})
|
|
6503
6638
|
.mockResolvedValue(0);
|
|
6504
6639
|
const { loop, watcher, tick, shutdown } = runLoop({
|
|
6505
|
-
claims:
|
|
6640
|
+
claims: EMAIL_CLAIMS,
|
|
6506
6641
|
startEventSync,
|
|
6507
6642
|
runPass,
|
|
6508
6643
|
});
|
|
@@ -6533,7 +6668,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6533
6668
|
});
|
|
6534
6669
|
const runPass = vi.fn().mockResolvedValue(0);
|
|
6535
6670
|
const { loop, watcher, clock, tick, shutdown } = runLoop({
|
|
6536
|
-
claims:
|
|
6671
|
+
claims: EMAIL_CLAIMS,
|
|
6537
6672
|
startEventSync,
|
|
6538
6673
|
runPass,
|
|
6539
6674
|
});
|
|
@@ -6646,7 +6781,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6646
6781
|
triggerShutdown = handler;
|
|
6647
6782
|
return () => {};
|
|
6648
6783
|
},
|
|
6649
|
-
getIdTokenClaims: () =>
|
|
6784
|
+
getIdTokenClaims: () => EMAIL_CLAIMS as never,
|
|
6650
6785
|
getAccessToken: async () => "jwt-test",
|
|
6651
6786
|
startEventSync,
|
|
6652
6787
|
},
|
|
@@ -6757,7 +6892,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6757
6892
|
dispose: vi.fn().mockResolvedValue(undefined),
|
|
6758
6893
|
});
|
|
6759
6894
|
const { loop, watcher, clock, tick, shutdown } = runLoop({
|
|
6760
|
-
claims:
|
|
6895
|
+
claims: EMAIL_CLAIMS,
|
|
6761
6896
|
startEventSync,
|
|
6762
6897
|
});
|
|
6763
6898
|
await microtasks();
|
|
@@ -6787,7 +6922,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6787
6922
|
dispose,
|
|
6788
6923
|
});
|
|
6789
6924
|
const { loop, shutdown } = runLoop({
|
|
6790
|
-
claims:
|
|
6925
|
+
claims: EMAIL_CLAIMS,
|
|
6791
6926
|
startEventSync,
|
|
6792
6927
|
});
|
|
6793
6928
|
await microtasks();
|
|
@@ -6802,7 +6937,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
6802
6937
|
try {
|
|
6803
6938
|
const startEventSync = vi.fn().mockResolvedValue(null);
|
|
6804
6939
|
const { loop, shutdown } = runLoop({
|
|
6805
|
-
claims:
|
|
6940
|
+
claims: EMAIL_CLAIMS,
|
|
6806
6941
|
startEventSync,
|
|
6807
6942
|
});
|
|
6808
6943
|
await microtasks();
|
package/src/bin/sync-runner.ts
CHANGED
|
@@ -2010,21 +2010,14 @@ const isDirectInvocation = (() => {
|
|
|
2010
2010
|
}
|
|
2011
2011
|
})();
|
|
2012
2012
|
|
|
2013
|
-
/**
|
|
2014
|
-
* Auto-sync (Beta) watch loop. Re-runs the one-shot runner every
|
|
2015
|
-
* `pollRemoteMs` until the process is killed (SIGTERM from the menubar's
|
|
2016
|
-
* stop_daemon command) or until a pass returns a non-zero exit code (hard
|
|
2017
|
-
* error worth surfacing to the operator). `setup-needed` and `auth-error`
|
|
2018
|
-
* exit 0 today and so will retry — acceptable noise for the beta; deal with
|
|
2019
|
-
* it via a richer return shape if it shows up in Sentry.
|
|
2020
|
-
*/
|
|
2021
2013
|
/**
|
|
2022
2014
|
* Test/event-driven seam (US-001).
|
|
2023
2015
|
*
|
|
2024
|
-
* `runRunnerWithLoop` performs an unbounded
|
|
2025
|
-
*
|
|
2026
|
-
*
|
|
2027
|
-
*
|
|
2016
|
+
* `runRunnerWithLoop` performs an unbounded, monotonic-cadence loop in
|
|
2017
|
+
* production. The initial pass is immediate; subsequent deadlines advance
|
|
2018
|
+
* from their prior deadline rather than pass completion. Watcher and receiver
|
|
2019
|
+
* signals interrupt the wait but still use the same guarded pass queue. To
|
|
2020
|
+
* test it deterministically, both waiting and monotonic time are injectable.
|
|
2028
2021
|
*
|
|
2029
2022
|
* A test (or US-003's wiring) injects a fake sleep that resolves immediately
|
|
2030
2023
|
* and/or coordinates with the {@link WatchPushDriver} seam in `../watcher.js`,
|
|
@@ -2033,6 +2026,12 @@ const isDirectInvocation = (() => {
|
|
|
2033
2026
|
export interface RunnerLoopDeps {
|
|
2034
2027
|
/** Sleep `ms` between passes. Default: host setTimeout. */
|
|
2035
2028
|
sleep?: (ms: number) => Promise<void>;
|
|
2029
|
+
/**
|
|
2030
|
+
* Monotonic milliseconds source for cadence deadlines. Defaults to
|
|
2031
|
+
* `performance.now()`; tests inject it to prove missed-deadline behavior
|
|
2032
|
+
* without depending on wall-clock time.
|
|
2033
|
+
*/
|
|
2034
|
+
monotonicNow?: () => number;
|
|
2036
2035
|
/**
|
|
2037
2036
|
* 1-minute whole-box load average source for the adaptive remote-poll
|
|
2038
2037
|
* backoff (used only when `--poll-remote-ms` is omitted). Defaults to
|