@indigoai-us/hq-cloud 6.16.65 → 6.16.67

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 (126) hide show
  1. package/dist/bin/sync-runner-company.d.ts +14 -0
  2. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  3. package/dist/bin/sync-runner-company.js +42 -8
  4. package/dist/bin/sync-runner-company.js.map +1 -1
  5. package/dist/bin/sync-runner-company.test.js +62 -7
  6. package/dist/bin/sync-runner-company.test.js.map +1 -1
  7. package/dist/bin/sync-runner-planning.d.ts +12 -3
  8. package/dist/bin/sync-runner-planning.d.ts.map +1 -1
  9. package/dist/bin/sync-runner-planning.js +65 -3
  10. package/dist/bin/sync-runner-planning.js.map +1 -1
  11. package/dist/bin/sync-runner-planning.test.js +172 -1
  12. package/dist/bin/sync-runner-planning.test.js.map +1 -1
  13. package/dist/bin/sync-runner-watch-loop.d.ts +51 -2
  14. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  15. package/dist/bin/sync-runner-watch-loop.js +409 -165
  16. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  17. package/dist/bin/sync-runner-watch-routes.d.ts +19 -2
  18. package/dist/bin/sync-runner-watch-routes.d.ts.map +1 -1
  19. package/dist/bin/sync-runner-watch-routes.js +40 -5
  20. package/dist/bin/sync-runner-watch-routes.js.map +1 -1
  21. package/dist/bin/sync-runner-watch-routes.test.js +47 -0
  22. package/dist/bin/sync-runner-watch-routes.test.js.map +1 -1
  23. package/dist/bin/sync-runner.d.ts +59 -5
  24. package/dist/bin/sync-runner.d.ts.map +1 -1
  25. package/dist/bin/sync-runner.js +48 -4
  26. package/dist/bin/sync-runner.js.map +1 -1
  27. package/dist/bin/sync-runner.test.js +1553 -192
  28. package/dist/bin/sync-runner.test.js.map +1 -1
  29. package/dist/cli/onepass-pull-planner-regressions.test.d.ts +2 -0
  30. package/dist/cli/onepass-pull-planner-regressions.test.d.ts.map +1 -0
  31. package/dist/cli/onepass-pull-planner-regressions.test.js +300 -0
  32. package/dist/cli/onepass-pull-planner-regressions.test.js.map +1 -0
  33. package/dist/cli/onepass-pull-planner.test.d.ts +2 -0
  34. package/dist/cli/onepass-pull-planner.test.d.ts.map +1 -0
  35. package/dist/cli/onepass-pull-planner.test.js +266 -0
  36. package/dist/cli/onepass-pull-planner.test.js.map +1 -0
  37. package/dist/cli/push-scan-exclusions.test.js +4 -4
  38. package/dist/cli/push-scan-exclusions.test.js.map +1 -1
  39. package/dist/cli/share.d.ts +31 -1
  40. package/dist/cli/share.d.ts.map +1 -1
  41. package/dist/cli/share.js +143 -8
  42. package/dist/cli/share.js.map +1 -1
  43. package/dist/cli/share.test.js +350 -5
  44. package/dist/cli/share.test.js.map +1 -1
  45. package/dist/cli/streaming-pull-planner.bench.test.d.ts +15 -0
  46. package/dist/cli/streaming-pull-planner.bench.test.d.ts.map +1 -0
  47. package/dist/cli/streaming-pull-planner.bench.test.js +273 -0
  48. package/dist/cli/streaming-pull-planner.bench.test.js.map +1 -0
  49. package/dist/cli/sync-scope.test.js +8 -0
  50. package/dist/cli/sync-scope.test.js.map +1 -1
  51. package/dist/cli/sync.d.ts +12 -0
  52. package/dist/cli/sync.d.ts.map +1 -1
  53. package/dist/cli/sync.js +297 -125
  54. package/dist/cli/sync.js.map +1 -1
  55. package/dist/cli/sync.test.js +255 -2
  56. package/dist/cli/sync.test.js.map +1 -1
  57. package/dist/journal-row-store.d.ts +40 -5
  58. package/dist/journal-row-store.d.ts.map +1 -1
  59. package/dist/journal-row-store.js +240 -21
  60. package/dist/journal-row-store.js.map +1 -1
  61. package/dist/journal-row-store.test.js +54 -0
  62. package/dist/journal-row-store.test.js.map +1 -1
  63. package/dist/journal-snapshot-v4.d.ts.map +1 -1
  64. package/dist/journal-snapshot-v4.js +11 -6
  65. package/dist/journal-snapshot-v4.js.map +1 -1
  66. package/dist/journal-store.test.js +324 -7
  67. package/dist/journal-store.test.js.map +1 -1
  68. package/dist/journal.d.ts +51 -16
  69. package/dist/journal.d.ts.map +1 -1
  70. package/dist/journal.js +303 -79
  71. package/dist/journal.js.map +1 -1
  72. package/dist/lib/exit-codes.d.ts +15 -0
  73. package/dist/lib/exit-codes.d.ts.map +1 -1
  74. package/dist/lib/exit-codes.js +15 -0
  75. package/dist/lib/exit-codes.js.map +1 -1
  76. package/dist/personal-vault.d.ts.map +1 -1
  77. package/dist/personal-vault.js +10 -3
  78. package/dist/personal-vault.js.map +1 -1
  79. package/dist/personal-vault.test.js +35 -0
  80. package/dist/personal-vault.test.js.map +1 -1
  81. package/dist/remote-pull.d.ts.map +1 -1
  82. package/dist/remote-pull.js +9 -2
  83. package/dist/remote-pull.js.map +1 -1
  84. package/dist/remote-pull.test.js +59 -2
  85. package/dist/remote-pull.test.js.map +1 -1
  86. package/dist/s3.d.ts +1 -0
  87. package/dist/s3.d.ts.map +1 -1
  88. package/dist/s3.js +20 -2
  89. package/dist/s3.js.map +1 -1
  90. package/dist/s3.list-cost.test.d.ts +2 -0
  91. package/dist/s3.list-cost.test.d.ts.map +1 -0
  92. package/dist/s3.list-cost.test.js +51 -0
  93. package/dist/s3.list-cost.test.js.map +1 -0
  94. package/dist/sync/area-ledger.d.ts +27 -3
  95. package/dist/sync/area-ledger.d.ts.map +1 -1
  96. package/dist/sync/area-ledger.js +149 -39
  97. package/dist/sync/area-ledger.js.map +1 -1
  98. package/dist/sync/area-ledger.test.js +192 -5
  99. package/dist/sync/area-ledger.test.js.map +1 -1
  100. package/dist/sync/parallel-sync-coordinator.test.js +29 -0
  101. package/dist/sync/parallel-sync-coordinator.test.js.map +1 -1
  102. package/dist/sync/realtime-telemetry.d.ts +1 -1
  103. package/dist/sync/realtime-telemetry.d.ts.map +1 -1
  104. package/dist/sync/realtime-telemetry.js +14 -6
  105. package/dist/sync/realtime-telemetry.js.map +1 -1
  106. package/dist/sync/realtime-telemetry.test.js +136 -16
  107. package/dist/sync/realtime-telemetry.test.js.map +1 -1
  108. package/dist/sync/remote-file-spool.d.ts +16 -0
  109. package/dist/sync/remote-file-spool.d.ts.map +1 -0
  110. package/dist/sync/remote-file-spool.js +91 -0
  111. package/dist/sync/remote-file-spool.js.map +1 -0
  112. package/dist/sync/remote-file-spool.test.d.ts +2 -0
  113. package/dist/sync/remote-file-spool.test.d.ts.map +1 -0
  114. package/dist/sync/remote-file-spool.test.js +77 -0
  115. package/dist/sync/remote-file-spool.test.js.map +1 -0
  116. package/dist/sync/state-store.d.ts +6 -0
  117. package/dist/sync/state-store.d.ts.map +1 -1
  118. package/dist/sync/state-store.js +14 -2
  119. package/dist/sync/state-store.js.map +1 -1
  120. package/dist/watcher.d.ts +12 -0
  121. package/dist/watcher.d.ts.map +1 -1
  122. package/dist/watcher.js +131 -0
  123. package/dist/watcher.js.map +1 -1
  124. package/dist/watcher.test.js +63 -0
  125. package/dist/watcher.test.js.map +1 -1
  126. package/package.json +2 -1
@@ -95,6 +95,15 @@ vi.mock("../s3.js", async (importOriginal) => {
95
95
  { key: "docs/handoff.md", size: 42, lastModified: new Date(), etag: '"abc123"' },
96
96
  { key: "knowledge/readme.md", size: 100, lastModified: new Date(), etag: '"def456"' },
97
97
  ];
98
+ const listRemoteFiles = innerVi.fn().mockResolvedValue(remoteFiles);
99
+ // Each pull consumes one independent LIST snapshot; never reuse a prior
100
+ // pull's rows, so watcher/list freshness tests observe the actual call.
101
+ async function* streamRemoteFiles(...args) {
102
+ const files = await listRemoteFiles(...args);
103
+ for (const remoteFile of files) {
104
+ yield remoteFile;
105
+ }
106
+ }
98
107
  return {
99
108
  ...actual,
100
109
  toPosixKey: (key) => key.split("\\").join("/"),
@@ -108,7 +117,8 @@ vi.mock("../s3.js", async (importOriginal) => {
108
117
  innerFs.writeFileSync(localPath, "mock file content");
109
118
  return { metadata: {} };
110
119
  }),
111
- listRemoteFiles: innerVi.fn().mockResolvedValue(remoteFiles),
120
+ listRemoteFiles,
121
+ streamRemoteFiles,
112
122
  deleteRemoteFile: innerVi.fn().mockResolvedValue(undefined),
113
123
  headRemoteFile: innerVi.fn().mockResolvedValue(null),
114
124
  primeObjectTransport: innerVi.fn().mockResolvedValue(undefined),
@@ -125,11 +135,12 @@ vi.mock("../remote-pull.js", async (importOriginal) => {
125
135
  const { vi: innerVi } = await import("vitest");
126
136
  return { ...actual, listRemoteForScope: innerVi.fn(actual.listRemoteForScope) };
127
137
  });
128
- import { sync, reportNewFilesToNotify, applyRealtimeDeltaPage, isSyncMutationStdinCommand, runSyncMutationStdinJson, SYNC_MUTATION_STDIN_COMMAND, targetedJournalSessionCensusSizes, win32SymlinkFlavorBroken, } from "./sync.js";
138
+ import { sync, reportNewFilesToNotify, applyRealtimeDeltaPage, isSyncMutationStdinCommand, runSyncMutationStdinJson, SYNC_MUTATION_STDIN_COMMAND, FULL_PULL_COOPERATIVE_CHECKPOINT_BATCH_SIZE, targetedJournalSessionCensusSizes, win32SymlinkFlavorBroken, } from "./sync.js";
129
139
  import { share } from "./share.js";
130
140
  import * as s3Module from "../s3.js";
131
141
  import * as conflictModule from "./conflict.js";
132
142
  import * as remotePullModule from "../remote-pull.js";
143
+ import { CooperativePassScheduler } from "../bin/sync-runner-watch-loop.js";
133
144
  import { reindex } from "./reindex.js";
134
145
  import { readSyncProgress } from "../sync-progress.js";
135
146
  import { VaultAuthError } from "../vault-client.js";
@@ -620,6 +631,248 @@ describe("sync", () => {
620
631
  },
621
632
  });
622
633
  }, 60_000);
634
+ it("hands queued realtime work the durable boundary inside a large full pull", async () => {
635
+ const rowCount = 59_020;
636
+ // Keep the transfer count fixed while tuning checkpoint cadence below; the
637
+ // fixture remains a 59,020-row streamed journal at every candidate size.
638
+ const changedRowCount = 2_000;
639
+ const fixedNow = new Date("2026-09-17T12:00:00.000Z");
640
+ const materializedHash = "5633d479dfae75ba7a78914ee380fa202bd6126e7c6b7c22e3ebc9e1a6ddc871";
641
+ const remoteFiles = Array.from({ length: rowCount }, (_value, index) => ({
642
+ key: `docs/full-pass/${index}.md`,
643
+ size: 17,
644
+ lastModified: fixedNow,
645
+ etag: `"${index < changedRowCount ? "remote" : "stable"}-${index}"`,
646
+ }));
647
+ writeJournal("acme", {
648
+ version: "2",
649
+ lastSync: "",
650
+ pulls: [],
651
+ files: Object.fromEntries(remoteFiles.map((remoteFile, index) => [
652
+ remoteFile.key,
653
+ {
654
+ hash: materializedHash,
655
+ size: 17,
656
+ syncedAt: "2026-09-16T00:00:00.000Z",
657
+ direction: "down",
658
+ remoteEtag: `${index < changedRowCount ? "stale" : "stable"}-${index}`,
659
+ mtimeMs: fixedNow.getTime(),
660
+ ctimeMs: fixedNow.getTime(),
661
+ },
662
+ ])),
663
+ });
664
+ const listRemoteFiles = vi.mocked(s3Module.listRemoteFiles);
665
+ const originalListRemoteFiles = listRemoteFiles.getMockImplementation();
666
+ listRemoteFiles.mockResolvedValue(remoteFiles);
667
+ const materializedPaths = new Set(remoteFiles.map((remoteFile) => path.join(tmpDir, "companies", "acme", remoteFile.key)));
668
+ const originalLstatSync = fs.lstatSync;
669
+ const lstatSync = vi.spyOn(fs, "lstatSync").mockImplementation((filePath) => {
670
+ if (typeof filePath === "string" && materializedPaths.has(filePath)) {
671
+ return {
672
+ isSymbolicLink: () => false,
673
+ isFile: () => true,
674
+ isDirectory: () => false,
675
+ size: 17,
676
+ mtimeMs: fixedNow.getTime(),
677
+ ctimeMs: fixedNow.getTime(),
678
+ };
679
+ }
680
+ return originalLstatSync(filePath);
681
+ });
682
+ const originalReadFileSync = fs.readFileSync;
683
+ const readFileSync = vi.spyOn(fs, "readFileSync").mockImplementation((filePath, ...args) => {
684
+ if (typeof filePath === "string" && materializedPaths.has(filePath)) {
685
+ return Buffer.from("mock file content");
686
+ }
687
+ return Reflect.apply(originalReadFileSync, fs, [filePath, ...args]);
688
+ });
689
+ const downloadFile = vi.mocked(s3Module.downloadFile);
690
+ const originalDownload = downloadFile.getMockImplementation();
691
+ let downloadsCompleted = 0;
692
+ let inFlightDownloads = 0;
693
+ let maxInFlightDownloads = 0;
694
+ let checkpointCalls = 0;
695
+ let realtimeQueued = false;
696
+ let realtimeDispatchedAt = -1;
697
+ const scheduler = new CooperativePassScheduler(() => 0);
698
+ let realtimePass;
699
+ downloadFile.mockImplementation(async (_ctx, _key, localPath, options) => {
700
+ inFlightDownloads += 1;
701
+ maxInFlightDownloads = Math.max(maxInFlightDownloads, inFlightDownloads);
702
+ await Promise.resolve();
703
+ options?.beforeReplace?.();
704
+ materializedPaths.add(localPath);
705
+ downloadsCompleted += 1;
706
+ if (downloadsCompleted === 1) {
707
+ realtimeQueued = true;
708
+ realtimePass = scheduler.submit(async () => {
709
+ // The chore drained its transfer unit and committed it before the
710
+ // scheduler admitted this realtime writer.
711
+ expect(inFlightDownloads).toBe(0);
712
+ realtimeDispatchedAt = downloadsCompleted;
713
+ remoteFiles.push({
714
+ key: "docs/realtime/during-full-pull.md",
715
+ size: 17,
716
+ lastModified: fixedNow,
717
+ etag: '"realtime"',
718
+ });
719
+ materializedPaths.add(path.join(tmpDir, "companies", "acme", "docs", "realtime", "during-full-pull.md"));
720
+ const realtimeJournal = readJournal("acme");
721
+ realtimeJournal.files["docs/realtime/during-full-pull.md"] = {
722
+ hash: materializedHash,
723
+ size: 17,
724
+ syncedAt: fixedNow.toISOString(),
725
+ direction: "up",
726
+ remoteEtag: "realtime",
727
+ mtimeMs: fixedNow.getTime(),
728
+ ctimeMs: fixedNow.getTime(),
729
+ };
730
+ writeJournal("acme", realtimeJournal);
731
+ }, "realtime");
732
+ }
733
+ inFlightDownloads -= 1;
734
+ return {
735
+ metadata: {},
736
+ contentHash: materializedHash,
737
+ contentSize: 17,
738
+ };
739
+ });
740
+ try {
741
+ await scheduler.submit(async ({ checkpoint }) => {
742
+ await sync({
743
+ company: "acme",
744
+ vaultConfig: mockConfig,
745
+ hqRoot: tmpDir,
746
+ skipReindex: true,
747
+ onEvent: () => undefined,
748
+ cooperativeCheckpoint: async () => {
749
+ checkpointCalls += 1;
750
+ return checkpoint();
751
+ },
752
+ });
753
+ }, "chore");
754
+ await realtimePass;
755
+ }
756
+ finally {
757
+ readFileSync.mockRestore();
758
+ lstatSync.mockRestore();
759
+ if (originalDownload)
760
+ downloadFile.mockImplementation(originalDownload);
761
+ else
762
+ downloadFile.mockReset();
763
+ if (originalListRemoteFiles)
764
+ listRemoteFiles.mockImplementation(originalListRemoteFiles);
765
+ else
766
+ listRemoteFiles.mockReset();
767
+ }
768
+ expect(maxInFlightDownloads).toBeGreaterThan(1);
769
+ expect(realtimeQueued).toBe(true);
770
+ expect(checkpointCalls).toBeGreaterThan(0);
771
+ expect(realtimeDispatchedAt).toBeGreaterThan(0);
772
+ expect(realtimeDispatchedAt).toBeLessThanOrEqual(FULL_PULL_COOPERATIVE_CHECKPOINT_BATCH_SIZE);
773
+ const finalJournal = readJournal("acme");
774
+ expect(finalJournal.files["docs/realtime/during-full-pull.md"]).toBeDefined();
775
+ expect(Object.keys(finalJournal.files)).toHaveLength(rowCount + 1);
776
+ expect(remoteFiles.every((remoteFile) => finalJournal.files[remoteFile.key] !== undefined)).toBe(true);
777
+ }, 120_000);
778
+ /**
779
+ * A cooperative hand-off restarts the pull from a freshly built context. The
780
+ * downloads that already completed are durable in the journal, so the
781
+ * restarted plan correctly sees them as unchanged — which means the run's
782
+ * own counters are the ONLY remaining record of them. Discarding those
783
+ * counters loses the reindex paths, the new-file notifications, and the
784
+ * published-content list the watch loop needs before it processes the
785
+ * filesystem events those downloads generated.
786
+ */
787
+ it("keeps downloads completed before a cooperative hand-off in the run's result", async () => {
788
+ let checkpointCalls = 0;
789
+ const result = await sync({
790
+ company: "acme",
791
+ vaultConfig: mockConfig,
792
+ hqRoot: tmpDir,
793
+ skipReindex: true,
794
+ onEvent: () => undefined,
795
+ // Queued realtime work takes the writer exactly once.
796
+ cooperativeCheckpoint: async () => {
797
+ checkpointCalls += 1;
798
+ return checkpointCalls === 1;
799
+ },
800
+ });
801
+ expect(checkpointCalls).toBe(1);
802
+ expect(s3Module.downloadFile).toHaveBeenCalledTimes(2);
803
+ expect(result.filesDownloaded).toBe(2);
804
+ expect([...(result.changedPaths ?? [])].sort()).toEqual([
805
+ "docs/handoff.md",
806
+ "knowledge/readme.md",
807
+ ]);
808
+ expect((result.appliedUpserts ?? []).map((upsert) => upsert.path).sort()).toEqual([
809
+ "docs/handoff.md",
810
+ "knowledge/readme.md",
811
+ ]);
812
+ expect(result.newFiles.map((newFile) => newFile.path).sort()).toEqual([
813
+ "docs/handoff.md",
814
+ "knowledge/readme.md",
815
+ ]);
816
+ expect(result.newFilesCount).toBe(2);
817
+ });
818
+ it("keeps every no-op full-pull journal row and emits no local deletes", async () => {
819
+ const fixedNow = new Date("2026-09-17T12:00:00.000Z");
820
+ const materialized = "mock file content";
821
+ const materializedHash = "5633d479dfae75ba7a78914ee380fa202bd6126e7c6b7c22e3ebc9e1a6ddc871";
822
+ const remoteFiles = Array.from({ length: 8 }, (_value, index) => ({
823
+ key: `docs/noop-full-pull/${index}.md`,
824
+ size: Buffer.byteLength(materialized),
825
+ lastModified: fixedNow,
826
+ etag: `"stable-${index}"`,
827
+ }));
828
+ for (const remoteFile of remoteFiles) {
829
+ const localPath = path.join(tmpDir, "companies", "acme", remoteFile.key);
830
+ fs.mkdirSync(path.dirname(localPath), { recursive: true });
831
+ fs.writeFileSync(localPath, materialized);
832
+ }
833
+ writeJournal("acme", {
834
+ version: "2",
835
+ lastSync: "",
836
+ pulls: [],
837
+ files: Object.fromEntries(remoteFiles.map((remoteFile) => [
838
+ remoteFile.key,
839
+ {
840
+ hash: materializedHash,
841
+ size: remoteFile.size,
842
+ syncedAt: "2026-09-16T00:00:00.000Z",
843
+ direction: "down",
844
+ remoteEtag: remoteFile.etag.slice(1, -1),
845
+ },
846
+ ])),
847
+ });
848
+ const listRemoteFiles = vi.mocked(s3Module.listRemoteFiles);
849
+ const originalListRemoteFiles = listRemoteFiles.getMockImplementation();
850
+ listRemoteFiles.mockResolvedValue(remoteFiles);
851
+ const unlinkSync = vi.spyOn(fs, "unlinkSync");
852
+ try {
853
+ await sync({
854
+ company: "acme",
855
+ vaultConfig: mockConfig,
856
+ hqRoot: tmpDir,
857
+ skipReindex: true,
858
+ onEvent: () => undefined,
859
+ });
860
+ const companyRoot = path.join(tmpDir, "companies", "acme") + path.sep;
861
+ const localDeletes = unlinkSync.mock.calls.filter(([candidate]) => typeof candidate === "string" && candidate.startsWith(companyRoot));
862
+ expect(localDeletes).toEqual([]);
863
+ }
864
+ finally {
865
+ unlinkSync.mockRestore();
866
+ if (originalListRemoteFiles)
867
+ listRemoteFiles.mockImplementation(originalListRemoteFiles);
868
+ else
869
+ listRemoteFiles.mockReset();
870
+ }
871
+ // Re-open through the on-disk journal reader rather than the in-memory
872
+ // object that planned this pass: omitted rows would otherwise look safe.
873
+ const reopened = readJournal("acme");
874
+ expect(Object.keys(reopened.files).sort()).toEqual(remoteFiles.map((file) => file.key).sort());
875
+ });
623
876
  it("retains targeted-pull baselines as row fingerprints rather than entry objects", async () => {
624
877
  writeJournal("acme", {
625
878
  version: "2",