@indigoai-us/hq-cloud 6.15.55 → 6.15.57

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 (39) hide show
  1. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  2. package/dist/bin/sync-runner-watch-loop.js +21 -15
  3. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  4. package/dist/bin/sync-runner.d.ts +4 -4
  5. package/dist/bin/sync-runner.d.ts.map +1 -1
  6. package/dist/bin/sync-runner.test.js +45 -13
  7. package/dist/bin/sync-runner.test.js.map +1 -1
  8. package/dist/cli/sync.d.ts.map +1 -1
  9. package/dist/cli/sync.js +113 -13
  10. package/dist/cli/sync.js.map +1 -1
  11. package/dist/cli/sync.test.js +85 -0
  12. package/dist/cli/sync.test.js.map +1 -1
  13. package/dist/journal.d.ts +2 -0
  14. package/dist/journal.d.ts.map +1 -1
  15. package/dist/journal.js +12 -1
  16. package/dist/journal.js.map +1 -1
  17. package/dist/sync/event-sync.d.ts +6 -5
  18. package/dist/sync/event-sync.d.ts.map +1 -1
  19. package/dist/sync/event-sync.js +13 -8
  20. package/dist/sync/event-sync.js.map +1 -1
  21. package/dist/sync/event-sync.test.js +8 -4
  22. package/dist/sync/event-sync.test.js.map +1 -1
  23. package/dist/sync/index.d.ts +2 -2
  24. package/dist/sync/index.d.ts.map +1 -1
  25. package/dist/sync/index.js +1 -1
  26. package/dist/sync/index.js.map +1 -1
  27. package/dist/sync/push-receiver.d.ts +43 -11
  28. package/dist/sync/push-receiver.d.ts.map +1 -1
  29. package/dist/sync/push-receiver.js +148 -92
  30. package/dist/sync/push-receiver.js.map +1 -1
  31. package/dist/sync/push-receiver.test.js +132 -4
  32. package/dist/sync/push-receiver.test.js.map +1 -1
  33. package/dist/watcher.d.ts +24 -1
  34. package/dist/watcher.d.ts.map +1 -1
  35. package/dist/watcher.js +54 -10
  36. package/dist/watcher.js.map +1 -1
  37. package/dist/watcher.test.js +176 -0
  38. package/dist/watcher.test.js.map +1 -1
  39. package/package.json +1 -1
@@ -120,13 +120,20 @@ vi.mock("../s3.js", async (importOriginal) => {
120
120
  vi.mock("./reindex.js", () => ({
121
121
  reindex: vi.fn(() => ({ status: 0 })),
122
122
  }));
123
+ vi.mock("../remote-pull.js", async (importOriginal) => {
124
+ const actual = await importOriginal();
125
+ const { vi: innerVi } = await import("vitest");
126
+ return { ...actual, listRemoteForScope: innerVi.fn(actual.listRemoteForScope) };
127
+ });
123
128
  import { sync, reportNewFilesToNotify, applyRealtimeDeltaPage, isSyncMutationStdinCommand, runSyncMutationStdinJson, SYNC_MUTATION_STDIN_COMMAND, win32SymlinkFlavorBroken, } from "./sync.js";
124
129
  import { share } from "./share.js";
125
130
  import * as s3Module from "../s3.js";
126
131
  import * as conflictModule from "./conflict.js";
132
+ import * as remotePullModule from "../remote-pull.js";
127
133
  import { reindex } from "./reindex.js";
128
134
  import { readSyncProgress } from "../sync-progress.js";
129
135
  import { VaultAuthError } from "../vault-client.js";
136
+ import * as journalModule from "../journal.js";
130
137
  import { readJournal, writeJournal, getEntry, listJournals } from "../journal.js";
131
138
  /**
132
139
  * Read a journal the way production does.
@@ -238,6 +245,7 @@ describe("sync", () => {
238
245
  });
239
246
  afterEach(() => {
240
247
  vi.unstubAllGlobals();
248
+ vi.mocked(remotePullModule.listRemoteForScope).mockRestore();
241
249
  vi.clearAllMocks();
242
250
  fs.rmSync(tmpDir, { recursive: true, force: true });
243
251
  fs.rmSync(stateDir, { recursive: true, force: true });
@@ -293,6 +301,83 @@ describe("sync", () => {
293
301
  expect(readJournal("acme").files["deleted-during-list.md"]?.removedReason)
294
302
  .toBe("local-delete");
295
303
  });
304
+ it("targeted pulls do not rematerialize the v3 journal after their cold keyed-session open", async () => {
305
+ writeJournal("acme", {
306
+ version: "2",
307
+ lastSync: "",
308
+ pulls: [],
309
+ files: {
310
+ "docs/known.md": {
311
+ hash: "a".repeat(64), size: 1, syncedAt: "2026-08-25T00:00:00.000Z", direction: "down",
312
+ },
313
+ },
314
+ });
315
+ const materialisations = vi.spyOn(journalModule, "readJournal");
316
+ materialisations.mockClear();
317
+ vi.mocked(remotePullModule.listRemoteForScope).mockResolvedValue([
318
+ { key: "docs/known.md", size: 1, lastModified: new Date(), etag: '"known"' },
319
+ ]);
320
+ await sync({ company: "acme", vaultConfig: mockConfig, hqRoot: tmpDir, pullPrefixes: ["docs"], skipReindex: true });
321
+ await sync({ company: "acme", vaultConfig: mockConfig, hqRoot: tmpDir, pullPrefixes: ["docs"], skipReindex: true });
322
+ // Unfixed origin/main fails here with:
323
+ // expected "readJournal" to be called +0 times, but got 2 times
324
+ expect(materialisations).not.toHaveBeenCalled();
325
+ });
326
+ it("keeps warm targeted journal work in milliseconds with a 100k-row journal", async () => {
327
+ const files = {
328
+ "docs/known.md": { hash: "a".repeat(64), size: 1, syncedAt: "2026-08-25T00:00:00.000Z", direction: "down" },
329
+ };
330
+ for (let index = 0; index < 100_000; index++) {
331
+ files[`unrelated/${index}.md`] = {
332
+ hash: "b".repeat(64), size: index, syncedAt: "2026-08-25T00:00:00.000Z", direction: "down",
333
+ };
334
+ }
335
+ writeJournal("acme", { version: "2", lastSync: "", pulls: [], files });
336
+ vi.mocked(remotePullModule.listRemoteForScope).mockResolvedValue([
337
+ { key: "docs/known.md", size: 1, lastModified: new Date(), etag: '"known"' },
338
+ ]);
339
+ const options = { company: "acme", vaultConfig: mockConfig, hqRoot: tmpDir, pullPrefixes: ["docs"], skipReindex: true };
340
+ // Cold open is intentionally excluded: the contract is no *per-pass*
341
+ // materialisation after the long-lived keyed session has opened.
342
+ await sync(options);
343
+ const started = performance.now();
344
+ await sync(options);
345
+ expect(performance.now() - started).toBeLessThan(500);
346
+ });
347
+ it("only tombstones remote deletions inside the targeted prefix", async () => {
348
+ writeJournal("acme", {
349
+ version: "2", lastSync: "", pulls: [], files: {
350
+ "docs/deleted.md": { hash: crypto.createHash("sha256").update("gone").digest("hex"), size: 4, syncedAt: "2026-08-25T00:00:00.000Z", direction: "down" },
351
+ "outside/kept.md": { hash: "b".repeat(64), size: 1, syncedAt: "2026-08-25T00:00:00.000Z", direction: "down" },
352
+ },
353
+ });
354
+ const companyRoot = path.join(tmpDir, "companies", "acme");
355
+ fs.mkdirSync(path.join(companyRoot, "docs"), { recursive: true });
356
+ fs.mkdirSync(path.join(companyRoot, "outside"), { recursive: true });
357
+ fs.writeFileSync(path.join(companyRoot, "docs", "deleted.md"), "gone");
358
+ fs.writeFileSync(path.join(companyRoot, "outside", "kept.md"), "keep");
359
+ vi.mocked(remotePullModule.listRemoteForScope).mockResolvedValue([]);
360
+ vi.mocked(s3Module.headRemoteFile).mockResolvedValue(null);
361
+ await sync({ company: "acme", vaultConfig: mockConfig, hqRoot: tmpDir, pullPrefixes: ["docs"], skipReindex: true });
362
+ expect(readJournal("acme").files["docs/deleted.md"]).toBeUndefined();
363
+ expect(readJournal("acme").files["outside/kept.md"]).toBeDefined();
364
+ expect(fs.readFileSync(path.join(companyRoot, "outside", "kept.md"), "utf8")).toBe("keep");
365
+ });
366
+ it("refreshes a concurrent writer after LIST and explicit keyed delta leaves it intact", async () => {
367
+ writeJournal("acme", { version: "2", lastSync: "", pulls: [], files: {} });
368
+ vi.mocked(remotePullModule.listRemoteForScope).mockImplementationOnce(async () => {
369
+ const concurrent = readJournal("acme");
370
+ concurrent.files["outside/push.md"] = {
371
+ hash: "c".repeat(64), size: 7, syncedAt: "2026-08-25T00:00:00.000Z", direction: "up",
372
+ };
373
+ writeJournal("acme", concurrent);
374
+ return [{ key: "docs/pull.md", size: 4, lastModified: new Date(), etag: '"pull"' }];
375
+ });
376
+ await sync({ company: "acme", vaultConfig: mockConfig, hqRoot: tmpDir, pullPrefixes: ["docs"], skipReindex: true });
377
+ const finalJournal = readJournal("acme");
378
+ expect(finalJournal.files["outside/push.md"]?.direction).toBe("up");
379
+ expect(finalJournal.files["docs/pull.md"]).toBeDefined();
380
+ });
296
381
  it("pulses liveness from the synchronous pull planner", async () => {
297
382
  let heartbeatCalls = 0;
298
383
  await sync({