@indigoai-us/hq-cloud 6.18.32 → 6.18.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/bin/sync-runner-delete-snapshot.bench.test.js +13 -3
  2. package/dist/bin/sync-runner-delete-snapshot.bench.test.js.map +1 -1
  3. package/dist/bin/sync-runner-watch-loop.d.ts +9 -1
  4. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  5. package/dist/bin/sync-runner-watch-loop.js +108 -27
  6. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  7. package/dist/bin/sync-runner.d.ts.map +1 -1
  8. package/dist/bin/sync-runner.js +4 -2
  9. package/dist/bin/sync-runner.js.map +1 -1
  10. package/dist/bin/sync-runner.test.js +223 -35
  11. package/dist/bin/sync-runner.test.js.map +1 -1
  12. package/dist/cli/reindex.d.ts +4 -0
  13. package/dist/cli/reindex.d.ts.map +1 -1
  14. package/dist/cli/reindex.js +3 -3
  15. package/dist/cli/reindex.js.map +1 -1
  16. package/dist/cli/rescue-core.d.ts +2 -0
  17. package/dist/cli/rescue-core.d.ts.map +1 -1
  18. package/dist/cli/rescue-core.js +18 -10
  19. package/dist/cli/rescue-core.js.map +1 -1
  20. package/dist/cli/rescue-source-switch-floor.test.js +39 -2
  21. package/dist/cli/rescue-source-switch-floor.test.js.map +1 -1
  22. package/dist/cognito-auth.d.ts +5 -1
  23. package/dist/cognito-auth.d.ts.map +1 -1
  24. package/dist/cognito-auth.js +19 -8
  25. package/dist/cognito-auth.js.map +1 -1
  26. package/dist/http-keepalive.d.ts +7 -0
  27. package/dist/http-keepalive.d.ts.map +1 -0
  28. package/dist/http-keepalive.js +66 -0
  29. package/dist/http-keepalive.js.map +1 -0
  30. package/dist/machine-auth.test.js +87 -0
  31. package/dist/machine-auth.test.js.map +1 -1
  32. package/dist/object-io.d.ts +2 -1
  33. package/dist/object-io.d.ts.map +1 -1
  34. package/dist/object-io.js +37 -32
  35. package/dist/object-io.js.map +1 -1
  36. package/dist/object-io.test.js +8 -1
  37. package/dist/object-io.test.js.map +1 -1
  38. package/dist/s3.d.ts.map +1 -1
  39. package/dist/s3.js +2 -1
  40. package/dist/s3.js.map +1 -1
  41. package/dist/session-log-capture.d.ts +4 -0
  42. package/dist/session-log-capture.d.ts.map +1 -1
  43. package/dist/session-log-capture.js +56 -19
  44. package/dist/session-log-capture.js.map +1 -1
  45. package/dist/session-log-capture.test.js +79 -0
  46. package/dist/session-log-capture.test.js.map +1 -1
  47. package/dist/sync/multipart-uploader.d.ts +2 -0
  48. package/dist/sync/multipart-uploader.d.ts.map +1 -1
  49. package/dist/sync/multipart-uploader.js +31 -1
  50. package/dist/sync/multipart-uploader.js.map +1 -1
  51. package/dist/sync/multipart-uploader.test.js +17 -1
  52. package/dist/sync/multipart-uploader.test.js.map +1 -1
  53. package/dist/sync/realtime-network-integration.test.d.ts +2 -0
  54. package/dist/sync/realtime-network-integration.test.d.ts.map +1 -0
  55. package/dist/sync/realtime-network-integration.test.js +83 -0
  56. package/dist/sync/realtime-network-integration.test.js.map +1 -0
  57. package/dist/sync/realtime-telemetry.d.ts +20 -3
  58. package/dist/sync/realtime-telemetry.d.ts.map +1 -1
  59. package/dist/sync/realtime-telemetry.js +85 -7
  60. package/dist/sync/realtime-telemetry.js.map +1 -1
  61. package/dist/sync/realtime-telemetry.test.js +68 -6
  62. package/dist/sync/realtime-telemetry.test.js.map +1 -1
  63. package/dist/sync/sync-work.d.ts +1 -0
  64. package/dist/sync/sync-work.d.ts.map +1 -1
  65. package/dist/sync/sync-work.js.map +1 -1
  66. package/dist/vault-client.d.ts.map +1 -1
  67. package/dist/vault-client.js +8 -2
  68. package/dist/vault-client.js.map +1 -1
  69. package/package.json +1 -1
@@ -20,8 +20,8 @@ import { AUTH_WAIT_INITIAL_RECHECK_MS, AUTH_WAIT_RESUME_MESSAGE, AUTH_WAIT_START
20
20
  import { PresignObjectIO, S3SdkObjectIO } from "../object-io.js";
21
21
  import { BandwidthGovernor, setBandwidthGovernorForTesting, } from "../bandwidth.js";
22
22
  import { POST_SYNC_FLAG_KEYS, } from "./sync-runner-post-sync-flags.js";
23
- import { FakeClock, TreeWatcher, } from "../watcher.js";
24
- import { adaptivePollMs, advanceMonotonicDeadline, DEFAULT_EVENT_BATCH_LIMIT, DEFAULT_FAST_LANE_MAX_BYTES, DEFAULT_FAST_LANE_MAX_FILE_BYTES, DEFAULT_FAST_LANE_MAX_FILES, DEFAULT_FULL_RECONCILE_MS, EVENT_BATCH_LIMIT_ENV, FAST_LANE_MAX_BYTES_ENV, FAST_LANE_MAX_FILES_ENV, FULL_RECONCILE_MS_ENV, isFastLocalBatch, isFastReceiverBatch, partitionLocalBatch, journalEntriesForWatcherDelete, resolveEventBatchLimit, resolveFullReconcileMs, resolveWatchPollMs, CHORE_STARVATION_CAP_MS, CooperativePassScheduler, REALTIME_ADMISSION_DEADLINE_MS, REALTIME_DRAIN_BURST_MS, REALTIME_WORKER_OCCUPANCY_CAP, RealtimeAdmissionTimeoutError, setRealtimeAdmissionDeadlineMsForTesting, setRealtimeDrainBurstMsForTesting, normalizeCoordinatorPaths, shouldQuarantineWatcherDeleteIntents, shouldUseAreaStorage, WATCHER_REDISCOVERY_RETRY_MS, WATCHER_REDISCOVERY_RETRY_MAX_MS, EVENT_SYNC_STARTUP_SHUTDOWN_WAIT_MS, } from "./sync-runner-watch-loop.js";
23
+ import { FakeClock, TreeWatcher, createWatchPathFilter, } from "../watcher.js";
24
+ import { adaptivePollMs, advanceMonotonicDeadline, DEFAULT_EVENT_BATCH_LIMIT, DEFAULT_FAST_LANE_MAX_BYTES, DEFAULT_FAST_LANE_MAX_FILE_BYTES, DEFAULT_FAST_LANE_MAX_FILES, DEFAULT_FULL_RECONCILE_MS, EVENT_BATCH_LIMIT_ENV, FAST_LANE_MAX_BYTES_ENV, FAST_LANE_MAX_FILES_ENV, FULL_RECONCILE_MS_ENV, isFastLocalBatch, isLargeLocalUpload, localUploadSizeClass, isChoreLocalPush, isSessionLogsPushOnlyPath, isFastReceiverBatch, partitionLocalBatch, journalEntriesForWatcherDelete, resolveEventBatchLimit, resolveFullReconcileMs, resolveWatchPollMs, CHORE_STARVATION_CAP_MS, CooperativePassScheduler, REALTIME_ADMISSION_DEADLINE_MS, REALTIME_DRAIN_BURST_MS, REALTIME_WORKER_OCCUPANCY_CAP, REALTIME_LARGE_UPLOAD_THRESHOLD_BYTES, RealtimeAdmissionTimeoutError, setRealtimeAdmissionDeadlineMsForTesting, setRealtimeDrainBurstMsForTesting, normalizeCoordinatorPaths, shouldQuarantineWatcherDeleteIntents, shouldUseAreaStorage, WATCHER_REDISCOVERY_RETRY_MS, WATCHER_REDISCOVERY_RETRY_MAX_MS, EVENT_SYNC_STARTUP_SHUTDOWN_WAIT_MS, } from "./sync-runner-watch-loop.js";
25
25
  import { FullPassCapController, resetFullPassCapLogForTest, } from "../sync/full-pass-caps.js";
26
26
  import { AreaLedgerMigration, JournalStore, openJournalStateStoreForMigration, PERSONAL_VAULT_JOURNAL_SLUG, readJournal, readJournalForPass, readJournalScoped, readJournalScopedAsync, writeJournal, } from "../journal.js";
27
27
  import { AreaLedger } from "../sync/area-ledger.js";
@@ -3145,11 +3145,12 @@ describe("all-complete aggregate", () => {
3145
3145
  true,
3146
3146
  ]);
3147
3147
  expect(reindex).toHaveBeenCalledTimes(1);
3148
- // Session logs are recopied at most every 10 minutes from the runner.
3148
+ // Session logs are captured only after ten minutes without source changes.
3149
3149
  expect(reindex).toHaveBeenCalledWith({
3150
3150
  repoRoot: "/tmp/fake-hq",
3151
3151
  skipLock: true,
3152
- sessionLogMinIntervalMs: 600_000,
3152
+ sessionLogQuietForMs: 600_000,
3153
+ sessionLogMaxStalenessMs: 3_600_000,
3153
3154
  });
3154
3155
  });
3155
3156
  it("gates qmd to indexable knowledge and project changes and propagates hq-relative paths", async () => {
@@ -6735,7 +6736,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
6735
6736
  });
6736
6737
  await expect(superseded).resolves.toBeUndefined();
6737
6738
  expect(runPass.mock.calls.filter(([argv]) => argv.includes("superseded.md"))).toHaveLength(0);
6738
- expect(stderr.mock.calls.map(([chunk]) => String(chunk)).join("")).toContain('"event":"sync.realtime.timing","schemaVersion":3,"source":"receiver","direction":"pull","outcome":"refused"');
6739
+ expect(stderr.mock.calls.map(([chunk]) => String(chunk)).join("")).toContain('"event":"sync.realtime.timing","schemaVersion":5,"source":"receiver","direction":"pull","sizeClass":"unknown","outcome":"refused"');
6739
6740
  releaseHeld();
6740
6741
  await heldReceiverWork;
6741
6742
  await replacement;
@@ -9073,6 +9074,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
9073
9074
  fs.rmSync(hqRoot, { recursive: true, force: true });
9074
9075
  });
9075
9076
  it("U07: drains the first watcher event after activation without guarded overlap", async () => {
9077
+ const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-watch-first-event-"));
9076
9078
  const watcher = makeWatcherStub();
9077
9079
  let triggerShutdown = () => { };
9078
9080
  let releaseFirstPass = () => { };
@@ -9097,7 +9099,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
9097
9099
  "--poll-remote-ms",
9098
9100
  "600000",
9099
9101
  "--hq-root",
9100
- "/tmp/hq",
9102
+ hqRoot,
9101
9103
  ], {
9102
9104
  runPass,
9103
9105
  createWatcher: () => watcher,
@@ -9114,13 +9116,17 @@ describe("runRunnerWithLoop — event-push wiring", () => {
9114
9116
  expect(watcher.started).toBe(true);
9115
9117
  // Activation happens synchronously after the maintenance-bearing pass,
9116
9118
  // before the next wait is registered. Its first event must still drain.
9119
+ const eventPath = path.join(hqRoot, "companies/indigo/pre-registration.md");
9120
+ fs.mkdirSync(path.dirname(eventPath), { recursive: true });
9121
+ fs.writeFileSync(eventPath, "small watcher edit");
9117
9122
  watcher.emit("companies/indigo/pre-registration.md");
9118
- await flushLoopMicrotasks();
9123
+ await flushLoopMicrotasks(32);
9119
9124
  expect(spans).toHaveLength(3);
9120
9125
  expect(spans[1].enteredAt >= spans[0].exitedAt).toBe(true);
9121
9126
  expect(spans[2].enteredAt >= spans[1].exitedAt).toBe(true);
9122
9127
  triggerShutdown();
9123
9128
  await loop;
9129
+ fs.rmSync(hqRoot, { recursive: true, force: true });
9124
9130
  });
9125
9131
  it("F05: guard-held watcher changes stay accumulated, then drain immediately after the guarded pass", async () => {
9126
9132
  const watcher = makeWatcherStub();
@@ -9343,7 +9349,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
9343
9349
  return () => { };
9344
9350
  },
9345
9351
  });
9346
- await flushLoopMicrotasks();
9352
+ await waitForPollSleep(sleep);
9347
9353
  sleep.tick();
9348
9354
  await flushLoopMicrotasks();
9349
9355
  // The transient pass did NOT end the loop — it ran again, and neither
@@ -9385,6 +9391,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
9385
9391
  },
9386
9392
  });
9387
9393
  await flushLoopMicrotasks();
9394
+ await waitForPollSleep(sleep);
9388
9395
  sleep.tick();
9389
9396
  await flushLoopMicrotasks();
9390
9397
  expect(routedArgv).toEqual([
@@ -9924,6 +9931,152 @@ describe("runRunnerWithLoop — U58 realtime scheduler guard", () => {
9924
9931
  });
9925
9932
  });
9926
9933
  describe("CooperativePassScheduler", () => {
9934
+ it("identifies session-log backups for the chore-only route", () => {
9935
+ const transcript = "workspace/.session-logs/codex/2026/10/01/live.jsonl";
9936
+ expect(isSessionLogsPushOnlyPath(transcript)).toBe(true);
9937
+ expect(isSessionLogsPushOnlyPath("workspace/notes.md")).toBe(false);
9938
+ expect(isChoreLocalPush([transcript], false)).toBe(true);
9939
+ expect(isChoreLocalPush(["workspace/notes.md"], false)).toBe(false);
9940
+ expect(isChoreLocalPush(["workspace/notes.md"], true)).toBe(true);
9941
+ });
9942
+ it("reserves a realtime occupant for each transfer direction", async () => {
9943
+ const scheduler = new CooperativePassScheduler(() => 0);
9944
+ const started = [];
9945
+ let releasePush1 = () => { };
9946
+ const push1Gate = new Promise((resolve) => { releasePush1 = resolve; });
9947
+ let releasePush2 = () => { };
9948
+ const push2Gate = new Promise((resolve) => { releasePush2 = resolve; });
9949
+ let releaseCheckpoint = () => { };
9950
+ const checkpointReady = new Promise((resolve) => { releaseCheckpoint = resolve; });
9951
+ const chore = scheduler.submit(async ({ checkpoint }) => {
9952
+ await checkpointReady;
9953
+ await checkpoint();
9954
+ return 0;
9955
+ }, "chore");
9956
+ await Promise.resolve();
9957
+ const push1 = scheduler.submit(async () => {
9958
+ started.push("push-1");
9959
+ await push1Gate;
9960
+ return 0;
9961
+ }, "realtime", "slow", false, undefined, false, false, "push");
9962
+ releaseCheckpoint();
9963
+ await new Promise((resolve) => setTimeout(resolve, 2));
9964
+ const push2 = scheduler.submit(async () => {
9965
+ started.push("push-2");
9966
+ await push2Gate;
9967
+ return 0;
9968
+ }, "realtime", "slow", false, undefined, false, true, "push");
9969
+ await new Promise((resolve) => setTimeout(resolve, 2));
9970
+ const pull = scheduler.submit(async () => {
9971
+ started.push("pull");
9972
+ return 0;
9973
+ }, "realtime", "slow", false, undefined, false, true, "pull");
9974
+ try {
9975
+ await new Promise((resolve) => setTimeout(resolve, 2));
9976
+ expect(started).toContain("pull");
9977
+ }
9978
+ finally {
9979
+ releasePush1();
9980
+ releasePush2();
9981
+ await Promise.all([chore, push1, push2, pull]);
9982
+ }
9983
+ });
9984
+ it("classifies uploads at the multipart part-size boundary", () => {
9985
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), "hq-large-upload-class-"));
9986
+ try {
9987
+ const small = path.join(directory, "small.txt");
9988
+ const large = path.join(directory, "large.bin");
9989
+ const missing = path.join(directory, "not-created-yet.txt");
9990
+ fs.writeFileSync(small, "small edit");
9991
+ const fd = fs.openSync(large, "w");
9992
+ fs.ftruncateSync(fd, REALTIME_LARGE_UPLOAD_THRESHOLD_BYTES);
9993
+ fs.closeSync(fd);
9994
+ expect(REALTIME_LARGE_UPLOAD_THRESHOLD_BYTES).toBe(8 * 1024 * 1024);
9995
+ expect(isLargeLocalUpload([small])).toBe(false);
9996
+ expect(isLargeLocalUpload([large])).toBe(true);
9997
+ expect(localUploadSizeClass([small])).toBe("small");
9998
+ expect(localUploadSizeClass([large])).toBe("large");
9999
+ expect(localUploadSizeClass([missing])).toBe("unknown");
10000
+ expect(isLargeLocalUpload([missing])).toBe(true);
10001
+ }
10002
+ finally {
10003
+ fs.rmSync(directory, { recursive: true, force: true });
10004
+ }
10005
+ });
10006
+ it("keeps small pulls under 5s during a 60s fake S3 PUT despite excluded path floods", async () => {
10007
+ vi.useFakeTimers();
10008
+ const watchRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-realtime-excluded-flood-"));
10009
+ try {
10010
+ const scheduler = new CooperativePassScheduler(() => Date.now());
10011
+ const shouldWatch = createWatchPathFilter(watchRoot, false);
10012
+ const fakeS3 = new Map();
10013
+ const localFiles = new Map();
10014
+ const mirrorJournal = new Map();
10015
+ const remoteChanges = Array.from({ length: 20 }, (_, index) => [`remote-${index}`, `value-${index}`]);
10016
+ for (const [key, value] of remoteChanges)
10017
+ fakeS3.set(key, value);
10018
+ let markPutStarted = () => { };
10019
+ const putStarted = new Promise((resolve) => { markPutStarted = resolve; });
10020
+ let largePutComplete = false;
10021
+ const upload = scheduler.submit(async ({ checkpoint }) => {
10022
+ const transfer = await checkpoint.runChoreNetworkTransfer(async () => {
10023
+ markPutStarted();
10024
+ await new Promise((resolve) => { setTimeout(resolve, 60_000); });
10025
+ fakeS3.set("large-upload", "large payload");
10026
+ return "large-upload";
10027
+ });
10028
+ largePutComplete = true;
10029
+ mirrorJournal.set(transfer.value, fakeS3.get(transfer.value) ?? "");
10030
+ transfer.complete();
10031
+ return 0;
10032
+ }, "chore");
10033
+ await putStarted;
10034
+ for (let index = 0; index < 1_000; index += 1) {
10035
+ const excluded = path.join(watchRoot, "workspace", "worktrees", `lane-${index}`, "output.ts");
10036
+ if (shouldWatch(excluded)) {
10037
+ void scheduler.submit(async () => 0, "realtime", "slow", false, undefined, false, true, "push", "small");
10038
+ }
10039
+ }
10040
+ expect(scheduler.activeRealtimeWorkerCount).toBe(0);
10041
+ expect(scheduler.pendingLaneCount("slow")).toBe(0);
10042
+ let allPullsSettled = false;
10043
+ const pullWaits = remoteChanges.map(([key]) => {
10044
+ const arrivedAt = Date.now();
10045
+ return scheduler.submit(async () => {
10046
+ const value = fakeS3.get(key);
10047
+ if (value === undefined)
10048
+ throw new Error(`fake S3 object missing: ${key}`);
10049
+ localFiles.set(key, value);
10050
+ mirrorJournal.set(key, value);
10051
+ return Date.now() - arrivedAt;
10052
+ }, "realtime", "slow", false, undefined, false, true, "pull", "small");
10053
+ });
10054
+ const allPullWaits = Promise.all(pullWaits).then((waits) => {
10055
+ allPullsSettled = true;
10056
+ return waits;
10057
+ });
10058
+ await vi.advanceTimersByTimeAsync(5_000);
10059
+ const pullsMetWorkerBound = allPullsSettled;
10060
+ if (!pullsMetWorkerBound)
10061
+ await vi.advanceTimersByTimeAsync(55_000);
10062
+ const waits = await allPullWaits;
10063
+ expect(pullsMetWorkerBound).toBe(true);
10064
+ expect(waits.every((waitMs) => waitMs < 5_000)).toBe(true);
10065
+ expect(localFiles.size).toBe(remoteChanges.length);
10066
+ expect(largePutComplete).toBe(false);
10067
+ expect(mirrorJournal.has("large-upload")).toBe(false);
10068
+ if (!largePutComplete)
10069
+ await vi.advanceTimersByTimeAsync(55_000);
10070
+ await upload;
10071
+ expect(largePutComplete).toBe(true);
10072
+ expect(fakeS3.get("large-upload")).toBe("large payload");
10073
+ expect(mirrorJournal.get("large-upload")).toBe("large payload");
10074
+ }
10075
+ finally {
10076
+ vi.useRealTimers();
10077
+ fs.rmSync(watchRoot, { recursive: true, force: true });
10078
+ }
10079
+ });
9927
10080
  it("admits realtime during payload I/O and fences the next chore transfer until the result is committed", async () => {
9928
10081
  const scheduler = new CooperativePassScheduler(() => 0);
9929
10082
  const order = [];
@@ -10581,6 +10734,7 @@ describe("CooperativePassScheduler", () => {
10581
10734
  });
10582
10735
  describe("first-tick realtime yield", () => {
10583
10736
  it("commits a watcher push at the first chore checkpoint without a wall-clock wait", async () => {
10737
+ const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-first-tick-realtime-"));
10584
10738
  const previousCoordinator = process.env.HQ_SYNC_PARALLEL_COORDINATOR;
10585
10739
  const previousInterval = process.env.HQ_SYNC_FULL_RECONCILE_MS;
10586
10740
  process.env.HQ_SYNC_PARALLEL_COORDINATOR = "1";
@@ -10635,7 +10789,7 @@ describe("first-tick realtime yield", () => {
10635
10789
  "--direction",
10636
10790
  "both",
10637
10791
  "--hq-root",
10638
- "/tmp/hq",
10792
+ hqRoot,
10639
10793
  "--poll-remote-ms",
10640
10794
  "60000",
10641
10795
  ], {
@@ -10651,9 +10805,12 @@ describe("first-tick realtime yield", () => {
10651
10805
  });
10652
10806
  try {
10653
10807
  await flushLoopMicrotasks(40);
10808
+ const notePath = path.join(hqRoot, "companies/indigo/note.md");
10809
+ fs.mkdirSync(path.dirname(notePath), { recursive: true });
10810
+ fs.writeFileSync(notePath, "small watcher edit");
10654
10811
  watcher.emit("companies/indigo/note.md", {
10655
10812
  paths: new Map([
10656
- ["/tmp/hq/companies/indigo/note.md", "companies/indigo/note.md"],
10813
+ [notePath, "companies/indigo/note.md"],
10657
10814
  ]),
10658
10815
  });
10659
10816
  await new Promise((resolve) => setImmediate(resolve));
@@ -10687,6 +10844,7 @@ describe("first-tick realtime yield", () => {
10687
10844
  delete process.env.HQ_SYNC_FULL_RECONCILE_MS;
10688
10845
  else
10689
10846
  process.env.HQ_SYNC_FULL_RECONCILE_MS = previousInterval;
10847
+ fs.rmSync(hqRoot, { recursive: true, force: true });
10690
10848
  }
10691
10849
  });
10692
10850
  });
@@ -11844,6 +12002,10 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
11844
12002
  await loop;
11845
12003
  });
11846
12004
  it("push-only scoped ticks run the scoped push and no pull leg", async () => {
12005
+ const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-push-only-tick-"));
12006
+ const file = path.join(hqRoot, "companies/indigo/knowledge/a.md");
12007
+ fs.mkdirSync(path.dirname(file), { recursive: true });
12008
+ fs.writeFileSync(file, "small watcher edit");
11847
12009
  const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
11848
12010
  const { loop, watcher, tick, shutdown } = startDrainLoopWithArgv(runPass, [
11849
12011
  "--companies",
@@ -11852,15 +12014,15 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
11852
12014
  "--direction",
11853
12015
  "push",
11854
12016
  "--hq-root",
11855
- "/tmp/hq",
12017
+ hqRoot,
11856
12018
  "--poll-remote-ms",
11857
12019
  "60000",
11858
12020
  ]);
11859
- await flushLoopMicrotasks();
12021
+ await flushLoopMicrotasks(30);
11860
12022
  runPass.mockClear();
11861
12023
  watcher.emit("companies/indigo/knowledge/a.md");
11862
12024
  tick();
11863
- await flushLoopMicrotasks();
12025
+ await flushLoopMicrotasks(30);
11864
12026
  expect(runPass.mock.calls.map((call) => call[0])).toEqual([
11865
12027
  [
11866
12028
  "--company",
@@ -11870,12 +12032,13 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
11870
12032
  "--scope-path",
11871
12033
  "knowledge/a.md",
11872
12034
  "--hq-root",
11873
- "/tmp/hq",
12035
+ hqRoot,
11874
12036
  ],
11875
12037
  ]);
11876
12038
  shutdown();
11877
12039
  tick();
11878
12040
  await loop;
12041
+ fs.rmSync(hqRoot, { recursive: true, force: true });
11879
12042
  });
11880
12043
  it("overflow and an elapsed hour run a full unscoped reconcile", async () => {
11881
12044
  const clockState = { now: 0 };
@@ -12141,11 +12304,14 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
12141
12304
  }
12142
12305
  });
12143
12306
  it("a 5,000-path batch under the shipped default stays on the targeted path", async () => {
12307
+ const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-5000-path-batch-"));
12308
+ const argv = [...watchArgv];
12309
+ argv[argv.indexOf("/tmp/hq")] = hqRoot;
12144
12310
  const watcher = makeBatchWatcherStub();
12145
12311
  const sleep = makeSteppableSleep();
12146
12312
  let triggerShutdown = () => { };
12147
12313
  const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
12148
- const loop = runRunnerWithLoop(watchArgv, {
12314
+ const loop = runRunnerWithLoop(argv, {
12149
12315
  runPass,
12150
12316
  clock: new FakeClock(),
12151
12317
  createWatcher: () => watcher,
@@ -12158,13 +12324,16 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
12158
12324
  await flushLoopMicrotasks();
12159
12325
  runPass.mockClear();
12160
12326
  watcher.emit("companies/indigo/a.md", {
12161
- paths: new Map(Array.from({ length: 5_000 }, (_, index) => [
12162
- `/tmp/hq/companies/indigo/${index}.md`,
12163
- `companies/indigo/${index}.md`,
12164
- ])),
12327
+ paths: new Map(Array.from({ length: 5_000 }, (_, index) => {
12328
+ const relativePath = `companies/indigo/${index}.md`;
12329
+ const absolutePath = path.join(hqRoot, relativePath);
12330
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
12331
+ fs.writeFileSync(absolutePath, "x");
12332
+ return [absolutePath, relativePath];
12333
+ })),
12165
12334
  });
12166
12335
  sleep.tick();
12167
- await flushLoopMicrotasks();
12336
+ await waitForCondition("5,000-path batch to split into two scoped passes", () => runPass.mock.calls.length === 2);
12168
12337
  const calls = runPass.mock.calls.map((call) => call[0]);
12169
12338
  expect(calls).not.toContainEqual(fullArgv);
12170
12339
  expect(calls).toHaveLength(2);
@@ -12178,6 +12347,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
12178
12347
  expect(calls[1]?.filter((arg) => arg === "--scope-path")).toHaveLength(5_000);
12179
12348
  triggerShutdown();
12180
12349
  await loop;
12350
+ fs.rmSync(hqRoot, { recursive: true, force: true });
12181
12351
  });
12182
12352
  it("a batch above the shipped 10,000-path default stays on chunked targeted passes before the interval", async () => {
12183
12353
  const saved = process.env.HQ_SYNC_FULL_RECONCILE_MS;
@@ -13835,12 +14005,13 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
13835
14005
  ]]),
13836
14006
  });
13837
14007
  tick();
13838
- await microtasks();
14008
+ await microtasks(30);
13839
14009
  expect(publishBatch).not.toHaveBeenCalled();
13840
14010
  shutdown();
13841
14011
  await loop;
13842
14012
  });
13843
14013
  it("parallel coordinator publishes only durable watcher paths after a subset is superseded", async () => {
14014
+ const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-subset-superseded-"));
13844
14015
  const previousCoordinator = process.env.HQ_SYNC_PARALLEL_COORDINATOR;
13845
14016
  process.env.HQ_SYNC_PARALLEL_COORDINATOR = "1";
13846
14017
  const publishBatch = vi.fn();
@@ -13859,36 +14030,42 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
13859
14030
  });
13860
14031
  const { loop, watcher, clock, waitForPoll, shutdown } = runLoop({
13861
14032
  claims: EMAIL_CLAIMS,
14033
+ hqRoot,
13862
14034
  startEventSync,
13863
14035
  runPass,
13864
14036
  });
13865
14037
  const heldPath = "companies/indigo/held.md";
13866
14038
  const supersededPath = "companies/indigo/superseded.md";
13867
14039
  const durablePath = "companies/indigo/durable.md";
14040
+ for (const relativePath of [heldPath, supersededPath, durablePath]) {
14041
+ const absolutePath = path.join(hqRoot, relativePath);
14042
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
14043
+ fs.writeFileSync(absolutePath, "small watcher edit");
14044
+ }
13868
14045
  try {
13869
14046
  await waitForPoll();
13870
14047
  await waitForObservable("watcher subscription to become active", () => watcher.started && watcher.listenerCount() > 0);
13871
14048
  runPass.mockClear();
13872
14049
  watcher.emit(heldPath, {
13873
- paths: new Map([[`/tmp/hq/${heldPath}`, heldPath]]),
13874
- changes: new Map([[`/tmp/hq/${heldPath}`, { kind: "change" }]]),
14050
+ paths: new Map([[path.join(hqRoot, heldPath), heldPath]]),
14051
+ changes: new Map([[path.join(hqRoot, heldPath), { kind: "change" }]]),
13875
14052
  });
13876
14053
  clock.advance(0);
13877
14054
  await waitForObservable("held watcher push to start", () => runPass.mock.calls.some(([argv]) => argv.includes("held.md")));
13878
14055
  watcher.emit(supersededPath, {
13879
14056
  paths: new Map([
13880
- [`/tmp/hq/${supersededPath}`, supersededPath],
13881
- [`/tmp/hq/${durablePath}`, durablePath],
14057
+ [path.join(hqRoot, supersededPath), supersededPath],
14058
+ [path.join(hqRoot, durablePath), durablePath],
13882
14059
  ]),
13883
14060
  changes: new Map([
13884
- [`/tmp/hq/${supersededPath}`, { kind: "change" }],
13885
- [`/tmp/hq/${durablePath}`, { kind: "change" }],
14061
+ [path.join(hqRoot, supersededPath), { kind: "change" }],
14062
+ [path.join(hqRoot, durablePath), { kind: "change" }],
13886
14063
  ]),
13887
14064
  });
13888
14065
  clock.advance(0);
13889
14066
  watcher.emit(supersededPath, {
13890
- paths: new Map([[`/tmp/hq/${supersededPath}`, supersededPath]]),
13891
- changes: new Map([[`/tmp/hq/${supersededPath}`, { kind: "change" }]]),
14067
+ paths: new Map([[path.join(hqRoot, supersededPath), supersededPath]]),
14068
+ changes: new Map([[path.join(hqRoot, supersededPath), { kind: "change" }]]),
13892
14069
  });
13893
14070
  clock.advance(0);
13894
14071
  releaseHeld();
@@ -13907,9 +14084,11 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
13907
14084
  delete process.env.HQ_SYNC_PARALLEL_COORDINATOR;
13908
14085
  else
13909
14086
  process.env.HQ_SYNC_PARALLEL_COORDINATOR = previousCoordinator;
14087
+ fs.rmSync(hqRoot, { recursive: true, force: true });
13910
14088
  }
13911
14089
  });
13912
14090
  it("parallel coordinator publishes nothing for a wholly superseded watcher batch", async () => {
14091
+ const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-wholly-superseded-"));
13913
14092
  const previousCoordinator = process.env.HQ_SYNC_PARALLEL_COORDINATOR;
13914
14093
  process.env.HQ_SYNC_PARALLEL_COORDINATOR = "1";
13915
14094
  const publishBatch = vi.fn();
@@ -13928,26 +14107,32 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
13928
14107
  });
13929
14108
  const { loop, watcher, clock, shutdown } = runLoop({
13930
14109
  claims: EMAIL_CLAIMS,
14110
+ hqRoot,
13931
14111
  startEventSync,
13932
14112
  runPass,
13933
14113
  });
13934
14114
  const heldPath = "companies/indigo/held.md";
13935
14115
  const supersededPath = "companies/indigo/wholly-superseded.md";
14116
+ const heldAbsolutePath = path.join(hqRoot, heldPath);
14117
+ fs.mkdirSync(path.dirname(heldAbsolutePath), { recursive: true });
14118
+ fs.writeFileSync(heldAbsolutePath, "small watcher edit");
13936
14119
  try {
13937
14120
  await microtasks(30);
13938
14121
  runPass.mockClear();
13939
14122
  watcher.emit(heldPath, {
13940
- paths: new Map([[`/tmp/hq/${heldPath}`, heldPath]]),
14123
+ paths: new Map([[path.join(hqRoot, heldPath), heldPath]]),
13941
14124
  });
13942
14125
  clock.advance(0);
13943
14126
  await microtasks(30);
13944
14127
  watcher.emit(supersededPath, {
13945
- paths: new Map([[`/tmp/hq/${supersededPath}`, supersededPath]]),
14128
+ paths: new Map([[path.join(hqRoot, supersededPath), supersededPath]]),
14129
+ changes: new Map([[path.join(hqRoot, supersededPath), { kind: "unlink" }]]),
13946
14130
  });
13947
14131
  clock.advance(0);
13948
14132
  await microtasks(30);
13949
14133
  watcher.emit(supersededPath, {
13950
- paths: new Map([[`/tmp/hq/${supersededPath}`, supersededPath]]),
14134
+ paths: new Map([[path.join(hqRoot, supersededPath), supersededPath]]),
14135
+ changes: new Map([[path.join(hqRoot, supersededPath), { kind: "unlink" }]]),
13951
14136
  });
13952
14137
  clock.advance(0);
13953
14138
  await microtasks(30);
@@ -13967,7 +14152,9 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
13967
14152
  // overlapping batch is reconciled. It has no accepted delete result,
13968
14153
  // so it must not publish an event.
13969
14154
  expect(supersededPushes).toHaveLength(1);
13970
- expect(publishBatch).not.toHaveBeenCalled();
14155
+ const publishedPaths = publishBatch.mock.calls.flatMap(([batch]) => [...batch.paths.values()]);
14156
+ expect(publishedPaths).toContain(heldPath);
14157
+ expect(publishedPaths).not.toContain(supersededPath);
13971
14158
  let loopExited = false;
13972
14159
  void loop.then(() => { loopExited = true; });
13973
14160
  await microtasks();
@@ -13982,6 +14169,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
13982
14169
  delete process.env.HQ_SYNC_PARALLEL_COORDINATOR;
13983
14170
  else
13984
14171
  process.env.HQ_SYNC_PARALLEL_COORDINATOR = previousCoordinator;
14172
+ fs.rmSync(hqRoot, { recursive: true, force: true });
13985
14173
  }
13986
14174
  });
13987
14175
  it("excludes refused descendants while publishing accepted unlinkDir deletes", async () => {
@@ -14029,7 +14217,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
14029
14217
  changes: new Map([[absolutePath, { kind: "unlinkDir" }]]),
14030
14218
  });
14031
14219
  tick();
14032
- await microtasks();
14220
+ await microtasks(30);
14033
14221
  expect(publishBatch).toHaveBeenCalledTimes(1);
14034
14222
  expect([...publishBatch.mock.calls[0][0].paths.values()]).toEqual([
14035
14223
  "companies/indigo/archive/accepted.md",
@@ -14078,7 +14266,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
14078
14266
  changes: new Map([[absoluteDirectory, { kind: "unlinkDir" }]]),
14079
14267
  });
14080
14268
  tick();
14081
- await microtasks();
14269
+ await microtasks(30);
14082
14270
  expect(publishBatch).toHaveBeenCalledTimes(1);
14083
14271
  expect([...publishBatch.mock.calls[0][0].paths.values()]).toEqual([
14084
14272
  "companies/indigo/archive/first.md",