@indigoai-us/hq-cloud 6.15.42 → 6.15.44

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 (62) hide show
  1. package/dist/bin/sync-runner-company.d.ts +3 -0
  2. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  3. package/dist/bin/sync-runner-company.js +50 -14
  4. package/dist/bin/sync-runner-company.js.map +1 -1
  5. package/dist/bin/sync-runner-company.test.js +135 -0
  6. package/dist/bin/sync-runner-company.test.js.map +1 -1
  7. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  8. package/dist/bin/sync-runner-watch-loop.js +33 -1
  9. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  10. package/dist/bin/sync-runner.d.ts +5 -5
  11. package/dist/bin/sync-runner.d.ts.map +1 -1
  12. package/dist/bin/sync-runner.js +3 -0
  13. package/dist/bin/sync-runner.js.map +1 -1
  14. package/dist/bin/sync-runner.test.js +96 -2
  15. package/dist/bin/sync-runner.test.js.map +1 -1
  16. package/dist/cli/share.d.ts +5 -17
  17. package/dist/cli/share.d.ts.map +1 -1
  18. package/dist/cli/share.js +16 -77
  19. package/dist/cli/share.js.map +1 -1
  20. package/dist/cli/share.test.js +48 -99
  21. package/dist/cli/share.test.js.map +1 -1
  22. package/dist/cli/sync.d.ts +16 -18
  23. package/dist/cli/sync.d.ts.map +1 -1
  24. package/dist/cli/sync.js +16 -72
  25. package/dist/cli/sync.js.map +1 -1
  26. package/dist/cli/sync.test.js +40 -59
  27. package/dist/cli/sync.test.js.map +1 -1
  28. package/dist/cli/tombstones.d.ts +23 -8
  29. package/dist/cli/tombstones.d.ts.map +1 -1
  30. package/dist/cli/tombstones.js +63 -33
  31. package/dist/cli/tombstones.js.map +1 -1
  32. package/dist/cli/tombstones.test.d.ts +2 -0
  33. package/dist/cli/tombstones.test.d.ts.map +1 -0
  34. package/dist/cli/tombstones.test.js +27 -0
  35. package/dist/cli/tombstones.test.js.map +1 -0
  36. package/dist/cli/watch-event-push-conflict.test.js +7 -0
  37. package/dist/cli/watch-event-push-conflict.test.js.map +1 -1
  38. package/dist/local-path-codec.test.js +20 -0
  39. package/dist/local-path-codec.test.js.map +1 -1
  40. package/dist/s3.d.ts +1 -1
  41. package/dist/s3.d.ts.map +1 -1
  42. package/dist/s3.js +7 -23
  43. package/dist/s3.js.map +1 -1
  44. package/dist/s3.test.js +13 -20
  45. package/dist/s3.test.js.map +1 -1
  46. package/dist/sync-core.d.ts +16 -0
  47. package/dist/sync-core.d.ts.map +1 -1
  48. package/dist/sync-core.js +38 -0
  49. package/dist/sync-core.js.map +1 -1
  50. package/dist/sync-progress.d.ts +18 -1
  51. package/dist/sync-progress.d.ts.map +1 -1
  52. package/dist/sync-progress.js +113 -7
  53. package/dist/sync-progress.js.map +1 -1
  54. package/dist/sync-progress.test.js +32 -2
  55. package/dist/sync-progress.test.js.map +1 -1
  56. package/dist/watcher.d.ts +37 -8
  57. package/dist/watcher.d.ts.map +1 -1
  58. package/dist/watcher.js +137 -23
  59. package/dist/watcher.js.map +1 -1
  60. package/dist/watcher.test.js +116 -9
  61. package/dist/watcher.test.js.map +1 -1
  62. package/package.json +1 -1
@@ -1809,13 +1809,13 @@ describe("sync", () => {
1809
1809
  const sweptJournal = journalAt(journalPath);
1810
1810
  expect(sweptJournal.files[ignoredKey]).toBeDefined();
1811
1811
  });
1812
- it("tombstone lookup failure does not resurrect a current intent-backed delete", async () => {
1812
+ it("tombstone lookup failure fails closed before a current intent-backed delete can resurrect", async () => {
1813
1813
  const key = "docs/watcher-deleted.md";
1814
1814
  const localPath = path.join(tmpDir, "companies", "acme", key);
1815
1815
  vi.mocked(s3Module.listRemoteFiles).mockResolvedValueOnce([
1816
1816
  { key, size: 42, lastModified: new Date(), etag: '"cur123"' },
1817
1817
  ]);
1818
- setupFetchMock({ tombstoneFailure: true });
1818
+ const fetchMock = setupFetchMock({ tombstoneFailure: true });
1819
1819
  fs.writeFileSync(journalPath, JSON.stringify({
1820
1820
  version: "2",
1821
1821
  lastSync: new Date().toISOString(),
@@ -1837,11 +1837,18 @@ describe("sync", () => {
1837
1837
  },
1838
1838
  pulls: [],
1839
1839
  }));
1840
- await sync({ company: "acme", vaultConfig: mockConfig, hqRoot: tmpDir });
1840
+ const log = vi.spyOn(console, "error").mockImplementation(() => { });
1841
+ try {
1842
+ await expect(sync({ company: "acme", vaultConfig: mockConfig, hqRoot: tmpDir })).rejects.toThrow("refusing to sync without delete suppression");
1843
+ expect(log).toHaveBeenCalledWith(expect.stringContaining("WARN tombstone fetch attempt 3/3 failed"));
1844
+ expect(fetchMock.mock.calls.filter((call) => String(call[0]).includes("/v1/files/tombstones"))).toHaveLength(3);
1845
+ }
1846
+ finally {
1847
+ log.mockRestore();
1848
+ }
1841
1849
  expect(s3Module.downloadFile).not.toHaveBeenCalledWith(expect.anything(), key, expect.anything(), expect.anything());
1842
1850
  expect(fs.existsSync(localPath)).toBe(false);
1843
- expect(journalAt(journalPath).files[key]
1844
- ?.removedReason).toBe("local-delete");
1851
+ expect(journalAt(journalPath).files[key]?.localDeleteIntent).toBeDefined();
1845
1852
  });
1846
1853
  it("pull producer: a BULK disappearance is drift-restored, NOT tombstoned (ridge-incident guard in the pull leg)", async () => {
1847
1854
  // 12 clean files vanish at once (corrupt mirror / bulk rm / checkout). The
@@ -3249,18 +3256,11 @@ describe("sync", () => {
3249
3256
  expect(result.filesDownloaded).toBe(1);
3250
3257
  expect(result.filesExcludedByPolicy).toBeGreaterThanOrEqual(1);
3251
3258
  });
3252
- it("skips remote keys under companies/<slug>/ in a company vault (doubly-scoped corrupt object — frogbear exit-2 regression)", async () => {
3253
- // A company vault is already anchored at its company root, so its keys are
3254
- // bucket-relative. A remote key literally beginning with `companies/...` is
3255
- // a doubly-scoped corrupt object: the vault-service refuses to presign it
3256
- // (INVALID_KEY_COMPANIES_SCOPED), so the per-file GET fails and the whole
3257
- // company wedges at "errored" (runner exit 2) on every run. Verified live
3258
- // 2026-06-16 against frogbear. The pull planner must refuse it at planning
3259
- // time — same policy bucket as the malformed-key / ephemeral filters.
3260
- const corruptKey = "companies/acme/drafts/reports/signals-2026-06-15.html";
3259
+ it("round-trips a companies/ key into the identical nested company-vault path", async () => {
3260
+ const nestedKey = "companies/acme/audit/chat-skills/x.json";
3261
3261
  vi.mocked(s3Module.listRemoteFiles).mockResolvedValueOnce([
3262
- // Doubly-scoped corrupt key — must be filtered, never downloaded.
3263
- { key: corruptKey, size: 2152, lastModified: new Date(), etag: '"corrupt"' },
3262
+ // `companies/` is an ordinary directory beneath this company's root.
3263
+ { key: nestedKey, size: 2152, lastModified: new Date(), etag: '"nested"' },
3264
3264
  // A legitimate bucket-relative key — must still download.
3265
3265
  { key: "docs/handoff.md", size: 42, lastModified: new Date(), etag: '"ok"' },
3266
3266
  ]);
@@ -3270,17 +3270,12 @@ describe("sync", () => {
3270
3270
  hqRoot: tmpDir,
3271
3271
  });
3272
3272
  const companyRoot = path.join(tmpDir, "companies", "acme");
3273
- // The corrupt key MUST NOT be materialized at its doubled local path.
3274
- expect(fs.existsSync(path.join(companyRoot, "companies", "acme", "drafts", "reports", "signals-2026-06-15.html"))).toBe(false);
3275
- // The vault-service GET must never be attempted for the corrupt key — the
3276
- // failing presign is the exact symptom this guard removes.
3277
- for (const call of vi.mocked(s3Module.downloadFile).mock.calls) {
3278
- expect(call[1]).not.toBe(corruptKey);
3279
- }
3273
+ // The remote key materializes exactly beneath the selected company root.
3274
+ expect(fs.existsSync(path.join(companyRoot, "companies", "acme", "audit", "chat-skills", "x.json"))).toBe(true);
3275
+ expect(vi.mocked(s3Module.downloadFile).mock.calls.map((call) => call[1])).toContain(nestedKey);
3280
3276
  // The legitimate bucket-relative key MUST still download.
3281
3277
  expect(fs.existsSync(path.join(companyRoot, "docs", "handoff.md"))).toBe(true);
3282
- expect(result.filesDownloaded).toBe(1);
3283
- expect(result.filesExcludedByPolicy).toBeGreaterThanOrEqual(1);
3278
+ expect(result.filesDownloaded).toBe(2);
3284
3279
  expect(result.aborted).toBe(false);
3285
3280
  });
3286
3281
  it("F02: rejects traversal remote keys before they can escape the company root", async () => {
@@ -4753,7 +4748,7 @@ describe("reportNewFilesToNotify chunking (server cap = 1000 files/report)", ()
4753
4748
  expect(fetchMock).not.toHaveBeenCalled();
4754
4749
  });
4755
4750
  });
4756
- describe("scope-invalid key hardening (companies/ prefix in a company vault — incident 2026-07-11)", () => {
4751
+ describe("nested companies directories in a company vault", () => {
4757
4752
  let tmpDir;
4758
4753
  let stateDir;
4759
4754
  let journalPath;
@@ -4781,15 +4776,15 @@ describe("scope-invalid key hardening (companies/ prefix in a company vault —
4781
4776
  fs.rmSync(stateDir, { recursive: true, force: true });
4782
4777
  delete process.env.HQ_STATE_DIR;
4783
4778
  });
4784
- it("skips a companies/-prefixed remote key instead of erroring the whole company sync", async () => {
4785
- // The incident shape: 25 poisoned `companies/indigo/…` objects in the
4786
- // company bucket; the presign download API rejects them
4787
- // (INVALID_KEY_COMPANIES_SCOPED), so attempting to download errored the
4788
- // whole company sync and gated heartbeat toolset refreshes fleet-wide.
4789
- // One bad object must never take down a company sync.
4779
+ it("downloads the live 25-file nested companies/ shape without a skip event", async () => {
4790
4780
  vi.mocked(s3Module.listRemoteFiles).mockReset();
4791
4781
  vi.mocked(s3Module.listRemoteFiles).mockResolvedValue([
4792
- { key: "companies/acme/knowledge/poison.md", size: 10, lastModified: new Date(), etag: '"p1"' },
4782
+ ...Array.from({ length: 25 }, (_, index) => ({
4783
+ key: `companies/acme/audit/chat-skills/file-${index.toString().padStart(2, "0")}.json`,
4784
+ size: 10,
4785
+ lastModified: new Date(),
4786
+ etag: `"nested-${index}"`,
4787
+ })),
4793
4788
  { key: "docs/good.md", size: 5, lastModified: new Date(), etag: '"g1"' },
4794
4789
  ]);
4795
4790
  const events = [];
@@ -4799,26 +4794,16 @@ describe("scope-invalid key hardening (companies/ prefix in a company vault —
4799
4794
  hqRoot: tmpDir,
4800
4795
  onEvent: (e) => events.push(e),
4801
4796
  });
4802
- // The good file syncs; the poisoned key is never downloaded.
4803
4797
  expect(result.aborted).toBe(false);
4804
- expect(result.filesDownloaded).toBe(1);
4798
+ expect(result.filesDownloaded).toBe(26);
4805
4799
  expect(fs.existsSync(path.join(tmpDir, "companies", "acme", "docs", "good.md"))).toBe(true);
4806
- expect(fs.existsSync(path.join(tmpDir, "companies", "acme", "companies", "acme", "knowledge", "poison.md"))).toBe(false);
4800
+ expect(fs.existsSync(path.join(tmpDir, "companies", "acme", "companies", "acme", "audit", "chat-skills", "file-24.json"))).toBe(true);
4807
4801
  const downloadedKeys = vi
4808
4802
  .mocked(s3Module.downloadFile)
4809
4803
  .mock.calls.map((c) => c[1]);
4810
- expect(downloadedKeys).not.toContain("companies/acme/knowledge/poison.md");
4811
- // Counted as policy-excluded, surfaced as a benign per-key warning event —
4812
- // deliberately NOT type:"error" (an error event flips the runner to exit 2).
4813
- expect(result.filesExcludedByPolicy).toBeGreaterThanOrEqual(1);
4804
+ expect(downloadedKeys).toContain("companies/acme/audit/chat-skills/file-00.json");
4814
4805
  const skipEvents = events.filter((e) => e.type === "skip-invalid-scoped-key");
4815
- expect(skipEvents).toEqual([
4816
- {
4817
- type: "skip-invalid-scoped-key",
4818
- path: "companies/acme/knowledge/poison.md",
4819
- errorCode: "INVALID_KEY_COMPANIES_SCOPED",
4820
- },
4821
- ]);
4806
+ expect(skipEvents).toEqual([]);
4822
4807
  expect(events.filter((e) => e.type === "error")).toEqual([]);
4823
4808
  });
4824
4809
  it("skips a control-character remote key before presigning while transferring valid keys", async () => {
@@ -4861,13 +4846,9 @@ describe("scope-invalid key hardening (companies/ prefix in a company vault —
4861
4846
  ]);
4862
4847
  expect(events.filter((event) => event.type === "error")).toEqual([]);
4863
4848
  });
4864
- it("cleans a doubled-tree journal entry via tombstone (local delete + journal drop) without HEAD-verifying the invalid key", async () => {
4865
- // A doubled local tree (companies/acme/companies/acme/…) previously
4866
- // poisoned the bucket; the poisoned objects were deleted server-side.
4867
- // The journal entry at the invalid key must drain as a tombstone —
4868
- // local copy removed, journal entry dropped — and must NOT be
4869
- // HEAD-verified (the presign transport rejects the key, which would
4870
- // defer the tombstone forever) and must NOT be re-uploaded.
4849
+ it("tombstones a remote-missing nested companies/ journal entry after HEAD verification", async () => {
4850
+ // A nested `companies/` key follows the same protected journal-tombstone
4851
+ // path as every other vault-relative file.
4871
4852
  const companyRoot = path.join(tmpDir, "companies", "acme");
4872
4853
  const doubled = path.join(companyRoot, "companies", "acme", "knowledge");
4873
4854
  fs.mkdirSync(doubled, { recursive: true });
@@ -4890,25 +4871,25 @@ describe("scope-invalid key hardening (companies/ prefix in a company vault —
4890
4871
  },
4891
4872
  },
4892
4873
  }));
4893
- // Remote LIST no longer contains the poisoned key (manual cleanup done).
4874
+ // Remote LIST no longer contains the nested key.
4894
4875
  vi.mocked(s3Module.listRemoteFiles).mockReset();
4895
4876
  vi.mocked(s3Module.listRemoteFiles).mockResolvedValue([]);
4877
+ vi.mocked(s3Module.headRemoteFile).mockResolvedValue(null);
4896
4878
  const result = await sync({
4897
4879
  company: "acme",
4898
4880
  vaultConfig: mockConfig,
4899
4881
  hqRoot: tmpDir,
4900
4882
  });
4901
- // Cleaned, not re-uploaded: local doubled-tree copy gone, journal dropped.
4883
+ // Cleaned, not re-uploaded: local nested copy gone, journal dropped.
4902
4884
  expect(fs.existsSync(path.join(doubled, "poison.md"))).toBe(false);
4903
4885
  expect(result.filesTombstoned).toBeGreaterThanOrEqual(1);
4904
4886
  const journal = journalAt(journalPath);
4905
4887
  expect(journal.files["companies/acme/knowledge/poison.md"]).toBeUndefined();
4906
- // No HEAD verify against the invalid key — the server validator would
4907
- // reject it (INVALID_KEY_COMPANIES_SCOPED) and defer the cleanup forever.
4888
+ // The normal HEAD verification confirms it is safe to tombstone.
4908
4889
  const headKeys = vi
4909
4890
  .mocked(s3Module.headRemoteFile)
4910
4891
  .mock.calls.map((c) => c[1]);
4911
- expect(headKeys).not.toContain("companies/acme/knowledge/poison.md");
4892
+ expect(headKeys).toContain("companies/acme/knowledge/poison.md");
4912
4893
  // Never re-uploaded.
4913
4894
  expect(vi.mocked(s3Module.uploadFile)).not.toHaveBeenCalled();
4914
4895
  });