@indigoai-us/hq-cloud 6.15.6 → 6.15.7
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/dist/bin/journal-repair.d.ts +12 -0
- package/dist/bin/journal-repair.d.ts.map +1 -0
- package/dist/bin/journal-repair.js +46 -0
- package/dist/bin/journal-repair.js.map +1 -0
- package/dist/bin/journal-repair.test.d.ts +2 -0
- package/dist/bin/journal-repair.test.d.ts.map +1 -0
- package/dist/bin/journal-repair.test.js +53 -0
- package/dist/bin/journal-repair.test.js.map +1 -0
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +37 -4
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +10 -1
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +43 -1
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +344 -66
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/journal.d.ts +34 -0
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +82 -13
- package/dist/journal.js.map +1 -1
- package/dist/journal.test.js +117 -1
- package/dist/journal.test.js.map +1 -1
- package/dist/sync/state-store.d.ts +52 -0
- package/dist/sync/state-store.d.ts.map +1 -1
- package/dist/sync/state-store.js +411 -99
- package/dist/sync/state-store.js.map +1 -1
- package/dist/sync/state-store.test.js +188 -1
- package/dist/sync/state-store.test.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
|
@@ -1152,6 +1152,108 @@ describe("fanout-plan", () => {
|
|
|
1152
1152
|
expect(plan.companies).toEqual([{ uid: "cmp_empty", slug: "cmp_empty" }]);
|
|
1153
1153
|
});
|
|
1154
1154
|
});
|
|
1155
|
+
describe("automatic journal maintenance", () => {
|
|
1156
|
+
it("repairs every resolved journal shard before fanout and streams known heartbeat events", async () => {
|
|
1157
|
+
const order = [];
|
|
1158
|
+
const repairJournalStateIfNeeded = vi.fn().mockImplementation((slug, options) => {
|
|
1159
|
+
order.push(`repair:${slug}`);
|
|
1160
|
+
options.onProgress?.({ bytesRead: 13, totalBytes: 26 });
|
|
1161
|
+
options.onProgress?.({ bytesRead: 26, totalBytes: 26 });
|
|
1162
|
+
return { status: "repaired", slug };
|
|
1163
|
+
});
|
|
1164
|
+
const deps = makeDeps({
|
|
1165
|
+
createVaultClient: () => makeVaultStub({
|
|
1166
|
+
memberships: [{ companyUid: "cmp_indigo" }],
|
|
1167
|
+
entityGet: (uid) => Promise.resolve({ uid, slug: "indigo" }),
|
|
1168
|
+
listPersons: () => Promise.resolve([
|
|
1169
|
+
{
|
|
1170
|
+
uid: "prs_me",
|
|
1171
|
+
slug: "me",
|
|
1172
|
+
type: "person",
|
|
1173
|
+
status: "active",
|
|
1174
|
+
bucketName: "hq-vault-prs-me",
|
|
1175
|
+
createdAt: "2026-01-01T00:00:00Z",
|
|
1176
|
+
},
|
|
1177
|
+
]),
|
|
1178
|
+
}),
|
|
1179
|
+
repairJournalStateIfNeeded,
|
|
1180
|
+
sync: vi.fn().mockImplementation(async (options) => {
|
|
1181
|
+
order.push(`sync:${options.journalSlug ?? "indigo"}`);
|
|
1182
|
+
return defaultSyncResult();
|
|
1183
|
+
}),
|
|
1184
|
+
});
|
|
1185
|
+
expect(await runRunner(["--companies"], deps)).toBe(0);
|
|
1186
|
+
expect(repairJournalStateIfNeeded.mock.calls.map(([slug]) => slug)).toEqual([
|
|
1187
|
+
"indigo",
|
|
1188
|
+
PERSONAL_VAULT_JOURNAL_SLUG,
|
|
1189
|
+
"personal",
|
|
1190
|
+
]);
|
|
1191
|
+
expect(order.slice(0, 3)).toEqual([
|
|
1192
|
+
"repair:indigo",
|
|
1193
|
+
`repair:${PERSONAL_VAULT_JOURNAL_SLUG}`,
|
|
1194
|
+
"repair:personal",
|
|
1195
|
+
]);
|
|
1196
|
+
expect(order[3]).toBe("sync:indigo");
|
|
1197
|
+
expect(deps.stdout.events().filter((event) => event.type === "maintenance-progress")).toEqual([
|
|
1198
|
+
{
|
|
1199
|
+
type: "maintenance-progress",
|
|
1200
|
+
company: "indigo",
|
|
1201
|
+
bytesProcessed: 13,
|
|
1202
|
+
totalBytes: 26,
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
type: "maintenance-progress",
|
|
1206
|
+
company: "indigo",
|
|
1207
|
+
bytesProcessed: 26,
|
|
1208
|
+
totalBytes: 26,
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
type: "maintenance-progress",
|
|
1212
|
+
company: "personal",
|
|
1213
|
+
bytesProcessed: 13,
|
|
1214
|
+
totalBytes: 26,
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
type: "maintenance-progress",
|
|
1218
|
+
company: "personal",
|
|
1219
|
+
bytesProcessed: 26,
|
|
1220
|
+
totalBytes: 26,
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
type: "maintenance-progress",
|
|
1224
|
+
company: "personal",
|
|
1225
|
+
bytesProcessed: 13,
|
|
1226
|
+
totalBytes: 26,
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
type: "maintenance-progress",
|
|
1230
|
+
company: "personal",
|
|
1231
|
+
bytesProcessed: 26,
|
|
1232
|
+
totalBytes: 26,
|
|
1233
|
+
},
|
|
1234
|
+
]);
|
|
1235
|
+
});
|
|
1236
|
+
it("fails before network work when authoritative local journal recovery fails", async () => {
|
|
1237
|
+
const sync = vi.fn().mockResolvedValue(defaultSyncResult());
|
|
1238
|
+
const deps = makeDeps({
|
|
1239
|
+
createVaultClient: () => makeVaultStub({
|
|
1240
|
+
memberships: [{ companyUid: "cmp_indigo" }],
|
|
1241
|
+
entityGet: (uid) => Promise.resolve({ uid, slug: "indigo" }),
|
|
1242
|
+
}),
|
|
1243
|
+
repairJournalStateIfNeeded: () => {
|
|
1244
|
+
throw new Error("newest journal generation is unreadable");
|
|
1245
|
+
},
|
|
1246
|
+
sync,
|
|
1247
|
+
});
|
|
1248
|
+
expect(await runRunner(["--companies"], deps)).toBe(1);
|
|
1249
|
+
expect(sync).not.toHaveBeenCalled();
|
|
1250
|
+
expect(deps.stderr.events()).toContainEqual({
|
|
1251
|
+
type: "error",
|
|
1252
|
+
path: "(local-state)",
|
|
1253
|
+
message: "newest journal generation is unreadable",
|
|
1254
|
+
});
|
|
1255
|
+
});
|
|
1256
|
+
});
|
|
1155
1257
|
// ---------------------------------------------------------------------------
|
|
1156
1258
|
// per-company event tagging
|
|
1157
1259
|
// ---------------------------------------------------------------------------
|
|
@@ -3334,6 +3436,9 @@ function makeWatcherStub() {
|
|
|
3334
3436
|
};
|
|
3335
3437
|
return stub;
|
|
3336
3438
|
}
|
|
3439
|
+
function completedPassOutcome(exitCode = 0) {
|
|
3440
|
+
return { exitCode, result: { pushPathResults: [] } };
|
|
3441
|
+
}
|
|
3337
3442
|
async function flushLoopMicrotasks(n = 8) {
|
|
3338
3443
|
for (let i = 0; i < n; i++)
|
|
3339
3444
|
await Promise.resolve();
|
|
@@ -3490,10 +3595,164 @@ describe("runRunnerWithLoop — adaptive poll interval (no --poll-remote-ms)", (
|
|
|
3490
3595
|
});
|
|
3491
3596
|
});
|
|
3492
3597
|
describe("runRunnerWithLoop — event-push wiring", () => {
|
|
3598
|
+
it("does not start journal-reading watcher callbacks before the initial pass finishes", async () => {
|
|
3599
|
+
const watcher = makeWatcherStub();
|
|
3600
|
+
let maintenanceComplete = false;
|
|
3601
|
+
let callbackObservedMaintenance = false;
|
|
3602
|
+
let triggerShutdown = () => { };
|
|
3603
|
+
let releaseInitialPass = () => { };
|
|
3604
|
+
const initialPass = new Promise((resolve) => {
|
|
3605
|
+
releaseInitialPass = resolve;
|
|
3606
|
+
});
|
|
3607
|
+
const runPass = vi.fn().mockImplementation(async () => {
|
|
3608
|
+
await initialPass;
|
|
3609
|
+
maintenanceComplete = true;
|
|
3610
|
+
return completedPassOutcome();
|
|
3611
|
+
});
|
|
3612
|
+
const createWatcher = vi.fn((opts) => {
|
|
3613
|
+
watcher.start = () => {
|
|
3614
|
+
watcher.started = true;
|
|
3615
|
+
callbackObservedMaintenance = maintenanceComplete;
|
|
3616
|
+
opts.captureLocalDeleteSnapshots("companies/indigo/knowledge/deleted.md", "unlink");
|
|
3617
|
+
};
|
|
3618
|
+
return watcher;
|
|
3619
|
+
});
|
|
3620
|
+
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"], {
|
|
3621
|
+
runPass,
|
|
3622
|
+
createWatcher: createWatcher,
|
|
3623
|
+
sleep: () => new Promise(() => { }),
|
|
3624
|
+
onShutdownSignal: (handler) => {
|
|
3625
|
+
triggerShutdown = handler;
|
|
3626
|
+
return () => { };
|
|
3627
|
+
},
|
|
3628
|
+
});
|
|
3629
|
+
await flushLoopMicrotasks();
|
|
3630
|
+
expect(runPass).toHaveBeenCalledTimes(1);
|
|
3631
|
+
expect(createWatcher).not.toHaveBeenCalled();
|
|
3632
|
+
expect(watcher.started).toBe(false);
|
|
3633
|
+
releaseInitialPass();
|
|
3634
|
+
await flushLoopMicrotasks();
|
|
3635
|
+
expect(createWatcher).toHaveBeenCalledTimes(1);
|
|
3636
|
+
expect(watcher.started).toBe(true);
|
|
3637
|
+
expect(callbackObservedMaintenance).toBe(true);
|
|
3638
|
+
triggerShutdown();
|
|
3639
|
+
await loop;
|
|
3640
|
+
});
|
|
3641
|
+
it("keeps every event surface off until a result proves maintenance completed", async () => {
|
|
3642
|
+
const watcher = makeWatcherStub();
|
|
3643
|
+
const sleep = makeSteppableSleep();
|
|
3644
|
+
let triggerShutdown = () => { };
|
|
3645
|
+
const runPass = vi.fn()
|
|
3646
|
+
.mockResolvedValueOnce({ exitCode: TRANSIENT_NETWORK_EXIT })
|
|
3647
|
+
.mockResolvedValueOnce(completedPassOutcome());
|
|
3648
|
+
const createWatcher = vi.fn(() => watcher);
|
|
3649
|
+
const receiver = {
|
|
3650
|
+
connected: false,
|
|
3651
|
+
start: vi.fn().mockResolvedValue(undefined),
|
|
3652
|
+
dispose: vi.fn().mockResolvedValue(undefined),
|
|
3653
|
+
};
|
|
3654
|
+
const createReceiver = vi.fn(() => receiver);
|
|
3655
|
+
const scheduler = {
|
|
3656
|
+
signal: vi.fn(),
|
|
3657
|
+
checkHighWater: vi.fn().mockResolvedValue(undefined),
|
|
3658
|
+
dispose: vi.fn().mockResolvedValue(undefined),
|
|
3659
|
+
};
|
|
3660
|
+
const startRealtimeScheduler = vi.fn().mockResolvedValue(scheduler);
|
|
3661
|
+
const startEventSync = vi.fn().mockResolvedValue(null);
|
|
3662
|
+
const priorEventSyncOverride = process.env.HQ_SYNC_EVENT_SYNC;
|
|
3663
|
+
process.env.HQ_SYNC_EVENT_SYNC = "1";
|
|
3664
|
+
try {
|
|
3665
|
+
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"], {
|
|
3666
|
+
runPass,
|
|
3667
|
+
createWatcher,
|
|
3668
|
+
createReceiver,
|
|
3669
|
+
startRealtimeScheduler,
|
|
3670
|
+
startEventSync,
|
|
3671
|
+
getIdTokenClaims: () => null,
|
|
3672
|
+
getAccessToken: async () => "jwt-test",
|
|
3673
|
+
sleep: sleep.sleep,
|
|
3674
|
+
onShutdownSignal: (handler) => {
|
|
3675
|
+
triggerShutdown = handler;
|
|
3676
|
+
return () => { };
|
|
3677
|
+
},
|
|
3678
|
+
});
|
|
3679
|
+
await flushLoopMicrotasks();
|
|
3680
|
+
expect(runPass).toHaveBeenCalledTimes(1);
|
|
3681
|
+
expect(createWatcher).not.toHaveBeenCalled();
|
|
3682
|
+
expect(createReceiver).not.toHaveBeenCalled();
|
|
3683
|
+
expect(startRealtimeScheduler).not.toHaveBeenCalled();
|
|
3684
|
+
expect(startEventSync).not.toHaveBeenCalled();
|
|
3685
|
+
sleep.tick();
|
|
3686
|
+
await flushLoopMicrotasks();
|
|
3687
|
+
expect(runPass).toHaveBeenCalledTimes(2);
|
|
3688
|
+
expect(createWatcher).toHaveBeenCalledTimes(1);
|
|
3689
|
+
expect(createReceiver).toHaveBeenCalledTimes(1);
|
|
3690
|
+
expect(receiver.start).toHaveBeenCalledTimes(1);
|
|
3691
|
+
expect(startRealtimeScheduler).toHaveBeenCalledTimes(1);
|
|
3692
|
+
expect(startEventSync).toHaveBeenCalledTimes(1);
|
|
3693
|
+
expect(watcher.started).toBe(true);
|
|
3694
|
+
triggerShutdown();
|
|
3695
|
+
await loop;
|
|
3696
|
+
}
|
|
3697
|
+
finally {
|
|
3698
|
+
if (priorEventSyncOverride === undefined) {
|
|
3699
|
+
delete process.env.HQ_SYNC_EVENT_SYNC;
|
|
3700
|
+
}
|
|
3701
|
+
else {
|
|
3702
|
+
process.env.HQ_SYNC_EVENT_SYNC = priorEventSyncOverride;
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3705
|
+
});
|
|
3706
|
+
it("rejects paused-company delete snapshots before opening their journal", async () => {
|
|
3707
|
+
const watcher = makeWatcherStub();
|
|
3708
|
+
let capture;
|
|
3709
|
+
let triggerShutdown = () => { };
|
|
3710
|
+
const priorSkipCompanies = process.env.HQ_SYNC_SKIP_COMPANIES;
|
|
3711
|
+
process.env.HQ_SYNC_SKIP_COMPANIES = "indigo";
|
|
3712
|
+
const stateDir = process.env.HQ_STATE_DIR;
|
|
3713
|
+
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"], {
|
|
3714
|
+
runPass: vi.fn().mockResolvedValue(completedPassOutcome()),
|
|
3715
|
+
createWatcher: (options) => {
|
|
3716
|
+
capture = options.captureLocalDeleteSnapshots;
|
|
3717
|
+
return watcher;
|
|
3718
|
+
},
|
|
3719
|
+
startEventSync: vi.fn().mockResolvedValue(null),
|
|
3720
|
+
getIdTokenClaims: () => null,
|
|
3721
|
+
getAccessToken: async () => "jwt-test",
|
|
3722
|
+
sleep: () => new Promise(() => { }),
|
|
3723
|
+
onShutdownSignal: (handler) => {
|
|
3724
|
+
triggerShutdown = handler;
|
|
3725
|
+
return () => { };
|
|
3726
|
+
},
|
|
3727
|
+
});
|
|
3728
|
+
try {
|
|
3729
|
+
await flushLoopMicrotasks();
|
|
3730
|
+
expect(watcher.started).toBe(true);
|
|
3731
|
+
expect(capture).toBeTypeOf("function");
|
|
3732
|
+
// Turn the configured state directory into a regular file. Any attempt
|
|
3733
|
+
// to open a journal beneath it now fails with ENOTDIR, so returning an
|
|
3734
|
+
// empty snapshot proves the pause check happens before journal I/O.
|
|
3735
|
+
fs.rmSync(stateDir, { recursive: true, force: true });
|
|
3736
|
+
fs.writeFileSync(stateDir, "not-a-directory");
|
|
3737
|
+
expect(capture("companies/indigo/knowledge/deleted.md", "unlink")).toEqual([]);
|
|
3738
|
+
}
|
|
3739
|
+
finally {
|
|
3740
|
+
triggerShutdown();
|
|
3741
|
+
await loop;
|
|
3742
|
+
fs.rmSync(stateDir, { force: true });
|
|
3743
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
3744
|
+
if (priorSkipCompanies === undefined) {
|
|
3745
|
+
delete process.env.HQ_SYNC_SKIP_COMPANIES;
|
|
3746
|
+
}
|
|
3747
|
+
else {
|
|
3748
|
+
process.env.HQ_SYNC_SKIP_COMPANIES = priorSkipCompanies;
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
});
|
|
3493
3752
|
it("starts the watcher alongside the poll loop when --event-push is on", async () => {
|
|
3494
3753
|
const watcher = makeWatcherStub();
|
|
3495
3754
|
let triggerShutdown = () => { };
|
|
3496
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
3755
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
3497
3756
|
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"], {
|
|
3498
3757
|
runPass,
|
|
3499
3758
|
clock: new FakeClock(),
|
|
@@ -3518,7 +3777,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3518
3777
|
const watcher = makeWatcherStub();
|
|
3519
3778
|
const clock = new FakeClock();
|
|
3520
3779
|
let triggerShutdown = () => { };
|
|
3521
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
3780
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
3522
3781
|
const sleepCalls = [];
|
|
3523
3782
|
const loop = runRunnerWithLoop([
|
|
3524
3783
|
"--companies",
|
|
@@ -3556,7 +3815,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3556
3815
|
const watcher = makeWatcherStub();
|
|
3557
3816
|
const clock = new FakeClock();
|
|
3558
3817
|
let triggerShutdown = () => { };
|
|
3559
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
3818
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
3560
3819
|
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"], {
|
|
3561
3820
|
runPass,
|
|
3562
3821
|
clock,
|
|
@@ -3596,7 +3855,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3596
3855
|
if (callCount === 1)
|
|
3597
3856
|
await firstGate; // hold the first (poll) pass
|
|
3598
3857
|
active--;
|
|
3599
|
-
return
|
|
3858
|
+
return completedPassOutcome();
|
|
3600
3859
|
});
|
|
3601
3860
|
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"], {
|
|
3602
3861
|
runPass,
|
|
@@ -3631,7 +3890,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3631
3890
|
triggerShutdown();
|
|
3632
3891
|
await loop;
|
|
3633
3892
|
});
|
|
3634
|
-
it("U07:
|
|
3893
|
+
it("U07: drains the first watcher event after activation without guarded overlap", async () => {
|
|
3635
3894
|
const watcher = makeWatcherStub();
|
|
3636
3895
|
let triggerShutdown = () => { };
|
|
3637
3896
|
let releaseFirstPass = () => { };
|
|
@@ -3647,7 +3906,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3647
3906
|
if (passes === 1)
|
|
3648
3907
|
await firstPassGate;
|
|
3649
3908
|
span.exitedAt = process.hrtime.bigint();
|
|
3650
|
-
return
|
|
3909
|
+
return completedPassOutcome();
|
|
3651
3910
|
});
|
|
3652
3911
|
const loop = runRunnerWithLoop([
|
|
3653
3912
|
"--companies",
|
|
@@ -3668,11 +3927,13 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3668
3927
|
});
|
|
3669
3928
|
await flushLoopMicrotasks();
|
|
3670
3929
|
expect(spans).toHaveLength(1);
|
|
3671
|
-
// This event arrives while the initial guarded pass owns the scheduler,
|
|
3672
|
-
// before its next sleep/wait has been registered.
|
|
3673
|
-
watcher.emit("companies/indigo/pre-registration.md");
|
|
3674
3930
|
releaseFirstPass();
|
|
3675
3931
|
await flushLoopMicrotasks();
|
|
3932
|
+
expect(watcher.started).toBe(true);
|
|
3933
|
+
// Activation happens synchronously after the maintenance-bearing pass,
|
|
3934
|
+
// before the next wait is registered. Its first event must still drain.
|
|
3935
|
+
watcher.emit("companies/indigo/pre-registration.md");
|
|
3936
|
+
await flushLoopMicrotasks();
|
|
3676
3937
|
expect(spans).toHaveLength(2);
|
|
3677
3938
|
expect(spans[1].enteredAt >= spans[0].exitedAt).toBe(true);
|
|
3678
3939
|
triggerShutdown();
|
|
@@ -3696,7 +3957,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3696
3957
|
if (calls === 1)
|
|
3697
3958
|
await pollGate;
|
|
3698
3959
|
active--;
|
|
3699
|
-
return
|
|
3960
|
+
return completedPassOutcome();
|
|
3700
3961
|
});
|
|
3701
3962
|
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"], {
|
|
3702
3963
|
runPass,
|
|
@@ -3737,7 +3998,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3737
3998
|
const poll = { resolve: null };
|
|
3738
3999
|
const runPass = vi.fn().mockImplementation(async () => {
|
|
3739
4000
|
passes++;
|
|
3740
|
-
return
|
|
4001
|
+
return completedPassOutcome();
|
|
3741
4002
|
});
|
|
3742
4003
|
// A sleep we can step: resolve once to allow a second poll pass.
|
|
3743
4004
|
const sleep = () => new Promise((resolve) => {
|
|
@@ -3771,7 +4032,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3771
4032
|
const clock = new FakeClock();
|
|
3772
4033
|
let triggerShutdown = () => { };
|
|
3773
4034
|
let detached = false;
|
|
3774
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
4035
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
3775
4036
|
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"], {
|
|
3776
4037
|
runPass,
|
|
3777
4038
|
clock,
|
|
@@ -3806,7 +4067,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3806
4067
|
onShutdownSignal: () => () => { },
|
|
3807
4068
|
});
|
|
3808
4069
|
expect(code).toBe(1);
|
|
3809
|
-
expect(watcher.disposed).toBe(
|
|
4070
|
+
expect(watcher.disposed).toBe(false);
|
|
3810
4071
|
});
|
|
3811
4072
|
it("an exit-0 auth-required pass stops the unattended loop without reporting a crash", async () => {
|
|
3812
4073
|
const watcher = makeWatcherStub();
|
|
@@ -3822,7 +4083,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3822
4083
|
expect(code).toBe(0);
|
|
3823
4084
|
expect(runPass).toHaveBeenCalledTimes(1);
|
|
3824
4085
|
expect(sleep).not.toHaveBeenCalled();
|
|
3825
|
-
expect(watcher.disposed).toBe(
|
|
4086
|
+
expect(watcher.disposed).toBe(false);
|
|
3826
4087
|
});
|
|
3827
4088
|
it("a TRANSIENT_NETWORK_EXIT poll pass does NOT surface as a crash — the loop retries (HQ-SYNC-1W)", async () => {
|
|
3828
4089
|
// A transient offline blip must keep the watcher alive: the loop logs and
|
|
@@ -3849,7 +4110,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3849
4110
|
// …and the transient code was never surfaced as the watcher's exit code.
|
|
3850
4111
|
expect(code).toBe(1);
|
|
3851
4112
|
expect(code).not.toBe(TRANSIENT_NETWORK_EXIT);
|
|
3852
|
-
expect(watcher.disposed).toBe(
|
|
4113
|
+
expect(watcher.disposed).toBe(false);
|
|
3853
4114
|
});
|
|
3854
4115
|
it("keeps the watch loop alive when a company leg returns transient exit 75 (HQ-SYNC-X)", async () => {
|
|
3855
4116
|
const watcher = makeWatcherStub();
|
|
@@ -3883,7 +4144,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3883
4144
|
expect(transientPass.stderr
|
|
3884
4145
|
.events()
|
|
3885
4146
|
.some((event) => event.type === "error" && event.path === "(company)")).toBe(false);
|
|
3886
|
-
expect(watcher.disposed).toBe(
|
|
4147
|
+
expect(watcher.disposed).toBe(false);
|
|
3887
4148
|
});
|
|
3888
4149
|
it("a PARTIAL_SYNC_EXIT poll pass does NOT surface as a crash — the loop retries", async () => {
|
|
3889
4150
|
// A partial pass (some per-file transfers threw: oversized object on an
|
|
@@ -3913,7 +4174,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3913
4174
|
// …and the partial code was never surfaced as the watcher's exit code.
|
|
3914
4175
|
expect(code).toBe(1);
|
|
3915
4176
|
expect(code).not.toBe(PARTIAL_SYNC_EXIT);
|
|
3916
|
-
expect(watcher.disposed).toBe(
|
|
4177
|
+
expect(watcher.disposed).toBe(false);
|
|
3917
4178
|
});
|
|
3918
4179
|
it("alerts when a partial pass failed before a company's download leg", async () => {
|
|
3919
4180
|
const watcher = makeWatcherStub();
|
|
@@ -3961,7 +4222,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3961
4222
|
it("without --event-push, no watcher is created (poll-only safety net)", async () => {
|
|
3962
4223
|
let triggerShutdown = () => { };
|
|
3963
4224
|
const createWatcher = vi.fn(() => makeWatcherStub());
|
|
3964
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
4225
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
3965
4226
|
const loop = runRunnerWithLoop(["--companies", "--watch"], {
|
|
3966
4227
|
runPass,
|
|
3967
4228
|
createWatcher,
|
|
@@ -3992,17 +4253,17 @@ describe("runRunnerWithLoop — U58 realtime scheduler guard", () => {
|
|
|
3992
4253
|
active += 1;
|
|
3993
4254
|
maximumActive = Math.max(maximumActive, active);
|
|
3994
4255
|
passes += 1;
|
|
3995
|
-
if (passes ===
|
|
4256
|
+
if (passes === 2)
|
|
3996
4257
|
await pollGate;
|
|
3997
4258
|
active -= 1;
|
|
3998
|
-
return
|
|
4259
|
+
return completedPassOutcome();
|
|
3999
4260
|
});
|
|
4000
4261
|
let signal = null;
|
|
4001
4262
|
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"], {
|
|
4002
4263
|
runPass,
|
|
4003
4264
|
clock: new FakeClock(),
|
|
4004
4265
|
createWatcher: () => watcher,
|
|
4005
|
-
sleep: ()
|
|
4266
|
+
sleep: makeSteppableSleep().sleep,
|
|
4006
4267
|
onShutdownSignal: (handler) => {
|
|
4007
4268
|
triggerShutdown = handler;
|
|
4008
4269
|
return () => { };
|
|
@@ -4022,7 +4283,12 @@ describe("runRunnerWithLoop — U58 realtime scheduler guard", () => {
|
|
|
4022
4283
|
dispose: async () => undefined,
|
|
4023
4284
|
}),
|
|
4024
4285
|
});
|
|
4025
|
-
for (let i = 0; i <
|
|
4286
|
+
for (let i = 0; i < 12; i++)
|
|
4287
|
+
await Promise.resolve();
|
|
4288
|
+
expect(watcher.started).toBe(true);
|
|
4289
|
+
// A second legacy pass owns the guard after activation.
|
|
4290
|
+
watcher.emit("companies/indigo/legacy.md");
|
|
4291
|
+
for (let i = 0; i < 8; i++)
|
|
4026
4292
|
await Promise.resolve();
|
|
4027
4293
|
expect(active).toBe(1);
|
|
4028
4294
|
watcher.emit("companies/indigo/v2.md");
|
|
@@ -4054,7 +4320,7 @@ describe("runRunnerWithLoop — U58 realtime scheduler guard", () => {
|
|
|
4054
4320
|
passes += 1;
|
|
4055
4321
|
if (passes === 1)
|
|
4056
4322
|
await initial;
|
|
4057
|
-
return
|
|
4323
|
+
return completedPassOutcome();
|
|
4058
4324
|
},
|
|
4059
4325
|
clock: new FakeClock(),
|
|
4060
4326
|
createWatcher: () => watcher,
|
|
@@ -4073,11 +4339,10 @@ describe("runRunnerWithLoop — U58 realtime scheduler guard", () => {
|
|
|
4073
4339
|
dispose: async () => undefined,
|
|
4074
4340
|
}),
|
|
4075
4341
|
});
|
|
4076
|
-
for (let i = 0; i <
|
|
4342
|
+
for (let i = 0; i < 12; i++)
|
|
4077
4343
|
await Promise.resolve();
|
|
4078
4344
|
releaseInitial();
|
|
4079
|
-
|
|
4080
|
-
await Promise.resolve();
|
|
4345
|
+
await flushLoopMicrotasks(20);
|
|
4081
4346
|
expect(sleepState.delays[0]).toBe(300_000);
|
|
4082
4347
|
clockState.now = 300_000;
|
|
4083
4348
|
sleepState.resolve?.();
|
|
@@ -4133,7 +4398,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4133
4398
|
};
|
|
4134
4399
|
}
|
|
4135
4400
|
it("startup runs one full reconcile, then watcher edits interrupt the pending wait", async () => {
|
|
4136
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
4401
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
4137
4402
|
const { loop, watcher, tick, shutdown } = startDrainLoop(runPass);
|
|
4138
4403
|
await flushLoopMicrotasks();
|
|
4139
4404
|
expect(runPass.mock.calls.map((call) => call[0])).toEqual([fullArgv]);
|
|
@@ -4158,7 +4423,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4158
4423
|
await loop;
|
|
4159
4424
|
});
|
|
4160
4425
|
it("coalesces repeated edits to the same path into one scoped push", async () => {
|
|
4161
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
4426
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
4162
4427
|
const { loop, watcher, tick, shutdown } = startDrainLoop(runPass);
|
|
4163
4428
|
await flushLoopMicrotasks();
|
|
4164
4429
|
runPass.mockClear();
|
|
@@ -4186,7 +4451,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4186
4451
|
await loop;
|
|
4187
4452
|
});
|
|
4188
4453
|
it("U08: --companies --skip-personal drops a personal watcher edit before it queues work", async () => {
|
|
4189
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
4454
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
4190
4455
|
const { loop, watcher, tick, shutdown } = startDrainLoopWithArgv(runPass, [
|
|
4191
4456
|
...watchArgv,
|
|
4192
4457
|
"--skip-personal",
|
|
@@ -4204,7 +4469,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4204
4469
|
await loop;
|
|
4205
4470
|
});
|
|
4206
4471
|
it("drains an unlink path through the same scoped tombstone push path", async () => {
|
|
4207
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
4472
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
4208
4473
|
const { loop, watcher, tick, shutdown } = startDrainLoop(runPass);
|
|
4209
4474
|
await flushLoopMicrotasks();
|
|
4210
4475
|
runPass.mockClear();
|
|
@@ -4226,7 +4491,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4226
4491
|
await loop;
|
|
4227
4492
|
});
|
|
4228
4493
|
it("omitted --direction is pull-only (parser default): a scoped tick never pushes", async () => {
|
|
4229
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
4494
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
4230
4495
|
// No --direction flag → parseArgs defaults the run to pull-only. The drain
|
|
4231
4496
|
// must honor that and NOT run a scoped push. Regression: the loop used to
|
|
4232
4497
|
// re-parse argv and wrongly default an omitted direction to "both", so a
|
|
@@ -4260,7 +4525,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4260
4525
|
it("restores failed scoped push paths so the next tick retries them", async () => {
|
|
4261
4526
|
const runPass = vi
|
|
4262
4527
|
.fn()
|
|
4263
|
-
.mockResolvedValueOnce(
|
|
4528
|
+
.mockResolvedValueOnce(completedPassOutcome())
|
|
4264
4529
|
.mockResolvedValueOnce(1)
|
|
4265
4530
|
.mockResolvedValueOnce(0)
|
|
4266
4531
|
.mockResolvedValueOnce(0)
|
|
@@ -4303,7 +4568,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4303
4568
|
await loop;
|
|
4304
4569
|
});
|
|
4305
4570
|
it("empty scoped ticks run only the pull leg", async () => {
|
|
4306
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
4571
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
4307
4572
|
const { loop, tick, shutdown } = startDrainLoop(runPass);
|
|
4308
4573
|
await flushLoopMicrotasks();
|
|
4309
4574
|
runPass.mockClear();
|
|
@@ -4315,7 +4580,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4315
4580
|
await loop;
|
|
4316
4581
|
});
|
|
4317
4582
|
it("personal scoped ticks preserve personal mode for the pull leg", async () => {
|
|
4318
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
4583
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
4319
4584
|
const { loop, watcher, tick, shutdown } = startDrainLoopWithArgv(runPass, [
|
|
4320
4585
|
"--personal",
|
|
4321
4586
|
"--watch",
|
|
@@ -4349,7 +4614,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4349
4614
|
await loop;
|
|
4350
4615
|
});
|
|
4351
4616
|
it("single-company scoped ticks preserve company mode for the pull leg", async () => {
|
|
4352
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
4617
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
4353
4618
|
const { loop, watcher, tick, shutdown } = startDrainLoopWithArgv(runPass, [
|
|
4354
4619
|
"--company",
|
|
4355
4620
|
"acme",
|
|
@@ -4385,7 +4650,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4385
4650
|
await loop;
|
|
4386
4651
|
});
|
|
4387
4652
|
it("push-only scoped ticks run the scoped push and no pull leg", async () => {
|
|
4388
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
4653
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
4389
4654
|
const { loop, watcher, tick, shutdown } = startDrainLoopWithArgv(runPass, [
|
|
4390
4655
|
"--companies",
|
|
4391
4656
|
"--watch",
|
|
@@ -4422,7 +4687,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
4422
4687
|
const watcher = makeBatchWatcherStub();
|
|
4423
4688
|
const sleep = makeSteppableSleep();
|
|
4424
4689
|
let triggerShutdown = () => { };
|
|
4425
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
4690
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
4426
4691
|
const loop = runRunnerWithLoop(watchArgv, {
|
|
4427
4692
|
runPass,
|
|
4428
4693
|
clock: new FakeClock(),
|
|
@@ -5210,7 +5475,8 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
5210
5475
|
const UNENROLLED_CLAIMS = { email: "someone@getindigo.ai" };
|
|
5211
5476
|
function runLoop(opts) {
|
|
5212
5477
|
const watcher = opts.watcher ?? makeBatchWatcherStub();
|
|
5213
|
-
const runPass = opts.runPass ??
|
|
5478
|
+
const runPass = opts.runPass ??
|
|
5479
|
+
vi.fn().mockResolvedValue(completedPassOutcome());
|
|
5214
5480
|
const clock = new FakeClock();
|
|
5215
5481
|
const sleep = makeSteppableSleep();
|
|
5216
5482
|
let triggerShutdown = () => { };
|
|
@@ -5304,7 +5570,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
5304
5570
|
});
|
|
5305
5571
|
const runPass = vi
|
|
5306
5572
|
.fn()
|
|
5307
|
-
.mockResolvedValueOnce(
|
|
5573
|
+
.mockResolvedValueOnce(completedPassOutcome())
|
|
5308
5574
|
.mockImplementation((passArgv) => passArgv.includes("--company") ? targetedPush : Promise.resolve(0));
|
|
5309
5575
|
const { loop, watcher, clock, shutdown } = runLoop({
|
|
5310
5576
|
claims: EMAIL_CLAIMS,
|
|
@@ -5349,7 +5615,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
5349
5615
|
// Initial poll pass succeeds; the targeted pass fails.
|
|
5350
5616
|
const runPass = vi
|
|
5351
5617
|
.fn()
|
|
5352
|
-
.mockResolvedValueOnce(
|
|
5618
|
+
.mockResolvedValueOnce(completedPassOutcome())
|
|
5353
5619
|
.mockResolvedValue(1);
|
|
5354
5620
|
const { loop, watcher, clock, tick, shutdown } = runLoop({
|
|
5355
5621
|
claims: EMAIL_CLAIMS,
|
|
@@ -5381,7 +5647,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
5381
5647
|
ownDeviceId: "dev-test",
|
|
5382
5648
|
dispose: vi.fn().mockResolvedValue(undefined),
|
|
5383
5649
|
});
|
|
5384
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
5650
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
5385
5651
|
const { loop, watcher, clock, tick, shutdown } = runLoop({
|
|
5386
5652
|
claims: EMAIL_CLAIMS,
|
|
5387
5653
|
startEventSync,
|
|
@@ -5421,7 +5687,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
5421
5687
|
});
|
|
5422
5688
|
const runPass = vi
|
|
5423
5689
|
.fn()
|
|
5424
|
-
.mockResolvedValueOnce(
|
|
5690
|
+
.mockResolvedValueOnce(completedPassOutcome())
|
|
5425
5691
|
.mockResolvedValueOnce({
|
|
5426
5692
|
exitCode: 0,
|
|
5427
5693
|
result: {
|
|
@@ -5462,7 +5728,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
5462
5728
|
});
|
|
5463
5729
|
const runPass = vi
|
|
5464
5730
|
.fn()
|
|
5465
|
-
.mockResolvedValueOnce(
|
|
5731
|
+
.mockResolvedValueOnce(completedPassOutcome())
|
|
5466
5732
|
.mockResolvedValueOnce({
|
|
5467
5733
|
exitCode: 0,
|
|
5468
5734
|
result: {
|
|
@@ -5503,7 +5769,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
5503
5769
|
ownDeviceId: "dev-test",
|
|
5504
5770
|
dispose: vi.fn().mockResolvedValue(undefined),
|
|
5505
5771
|
});
|
|
5506
|
-
const runPass = vi.fn().mockResolvedValue(
|
|
5772
|
+
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
5507
5773
|
const { loop, watcher, clock, tick, shutdown } = runLoop({
|
|
5508
5774
|
claims: EMAIL_CLAIMS,
|
|
5509
5775
|
startEventSync,
|
|
@@ -5580,9 +5846,9 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
5580
5846
|
let passCount = 0;
|
|
5581
5847
|
const runPass = vi.fn().mockImplementation(async () => {
|
|
5582
5848
|
passCount += 1;
|
|
5583
|
-
if (passCount ===
|
|
5849
|
+
if (passCount === 2)
|
|
5584
5850
|
await pollGate;
|
|
5585
|
-
return
|
|
5851
|
+
return completedPassOutcome();
|
|
5586
5852
|
});
|
|
5587
5853
|
const loop = runRunnerWithLoop(
|
|
5588
5854
|
// `--direction both` so the queued watcher pushes actually run (an omitted
|
|
@@ -5619,6 +5885,21 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
5619
5885
|
await microtasks();
|
|
5620
5886
|
expect(passCount).toBe(1);
|
|
5621
5887
|
expect(receiverSync).toBeTruthy();
|
|
5888
|
+
const remotePull = receiverSync({
|
|
5889
|
+
event: {
|
|
5890
|
+
kind: "upsert",
|
|
5891
|
+
relativePath: "companies/acme/remote.md",
|
|
5892
|
+
contentHash: "sha256:remote",
|
|
5893
|
+
mtime: "2026-06-18T12:00:00.000Z",
|
|
5894
|
+
originDeviceId: "peer-device",
|
|
5895
|
+
originTenantId: "tenant-acme",
|
|
5896
|
+
sequenceNumber: 7,
|
|
5897
|
+
eventTimestamp: "2026-06-18T12:00:00.000Z",
|
|
5898
|
+
},
|
|
5899
|
+
signal: new AbortController().signal,
|
|
5900
|
+
});
|
|
5901
|
+
await microtasks();
|
|
5902
|
+
expect(passCount).toBe(2);
|
|
5622
5903
|
const indigoBatch = {
|
|
5623
5904
|
paths: new Map([
|
|
5624
5905
|
["/tmp/hq/companies/indigo/a.md", "companies/indigo/a.md"],
|
|
@@ -5635,20 +5916,6 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
5635
5916
|
watcher.emit("companies/beta/b.md", betaBatch);
|
|
5636
5917
|
clock.advance(0);
|
|
5637
5918
|
await microtasks();
|
|
5638
|
-
const remotePull = receiverSync({
|
|
5639
|
-
event: {
|
|
5640
|
-
kind: "upsert",
|
|
5641
|
-
relativePath: "companies/acme/remote.md",
|
|
5642
|
-
contentHash: "sha256:remote",
|
|
5643
|
-
mtime: "2026-06-18T12:00:00.000Z",
|
|
5644
|
-
originDeviceId: "peer-device",
|
|
5645
|
-
originTenantId: "tenant-acme",
|
|
5646
|
-
sequenceNumber: 7,
|
|
5647
|
-
eventTimestamp: "2026-06-18T12:00:00.000Z",
|
|
5648
|
-
},
|
|
5649
|
-
signal: new AbortController().signal,
|
|
5650
|
-
});
|
|
5651
|
-
await microtasks();
|
|
5652
5919
|
releasePoll();
|
|
5653
5920
|
await remotePull;
|
|
5654
5921
|
await microtasks();
|
|
@@ -6622,7 +6889,7 @@ describe("watcher delete intents without pre-seeded authorization", () => {
|
|
|
6622
6889
|
const initialPullGate = new Promise((resolve) => {
|
|
6623
6890
|
releaseInitialPull = resolve;
|
|
6624
6891
|
});
|
|
6625
|
-
let
|
|
6892
|
+
let pullPasses = 0;
|
|
6626
6893
|
const runPass = vi.fn(async (argv) => {
|
|
6627
6894
|
// Lets a test hold a pass mid-flight and act while the loop is awaiting
|
|
6628
6895
|
// it, which is where producer-side state that outlives a pass is decided.
|
|
@@ -6673,8 +6940,8 @@ describe("watcher delete intents without pre-seeded authorization", () => {
|
|
|
6673
6940
|
return { exitCode: 0, result: { pushPathResults: pathResults } };
|
|
6674
6941
|
}
|
|
6675
6942
|
if (direction === "pull" || direction === "both") {
|
|
6676
|
-
|
|
6677
|
-
|
|
6943
|
+
pullPasses += 1;
|
|
6944
|
+
if (options.gateNextPull === true && pullPasses === 2) {
|
|
6678
6945
|
await initialPullGate;
|
|
6679
6946
|
}
|
|
6680
6947
|
const journal = readJournal("indigo");
|
|
@@ -6694,7 +6961,7 @@ describe("watcher delete intents without pre-seeded authorization", () => {
|
|
|
6694
6961
|
downloads++;
|
|
6695
6962
|
}
|
|
6696
6963
|
}
|
|
6697
|
-
return
|
|
6964
|
+
return completedPassOutcome();
|
|
6698
6965
|
});
|
|
6699
6966
|
const loop = runRunnerWithLoop([
|
|
6700
6967
|
"--companies", "--watch", "--event-push",
|
|
@@ -6761,7 +7028,9 @@ describe("watcher delete intents without pre-seeded authorization", () => {
|
|
|
6761
7028
|
await stopHarness(h);
|
|
6762
7029
|
});
|
|
6763
7030
|
it("persists the intent before an in-flight pull plans the missing path", async () => {
|
|
6764
|
-
const h = startDeleteHarness(["knowledge/in-flight.md"], {
|
|
7031
|
+
const h = startDeleteHarness(["knowledge/in-flight.md"], { gateNextPull: true });
|
|
7032
|
+
await settle();
|
|
7033
|
+
h.tick();
|
|
6765
7034
|
await settle();
|
|
6766
7035
|
const relativePath = "companies/indigo/knowledge/in-flight.md";
|
|
6767
7036
|
const absolutePath = path.join(h.companyRoot, "knowledge/in-flight.md");
|
|
@@ -6907,7 +7176,16 @@ describe("watcher delete intents without pre-seeded authorization", () => {
|
|
|
6907
7176
|
"--poll-remote-ms",
|
|
6908
7177
|
"60000",
|
|
6909
7178
|
], {
|
|
6910
|
-
runPass: (passArgv) =>
|
|
7179
|
+
runPass: async (passArgv) => {
|
|
7180
|
+
let result;
|
|
7181
|
+
const exitCode = await runRunner(passArgv, {
|
|
7182
|
+
...runnerDeps,
|
|
7183
|
+
onPassResult: (passResult) => {
|
|
7184
|
+
result = passResult;
|
|
7185
|
+
},
|
|
7186
|
+
});
|
|
7187
|
+
return { exitCode, ...(result ? { result } : {}) };
|
|
7188
|
+
},
|
|
6911
7189
|
createWatcher: (options) => {
|
|
6912
7190
|
capture = options.captureLocalDeleteSnapshots;
|
|
6913
7191
|
return watcher;
|