@indigoai-us/hq-cloud 6.15.41 → 6.15.43
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/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +48 -3
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner-company.test.js +135 -0
- package/dist/bin/sync-runner-company.test.js.map +1 -1
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +22 -6
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +2 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +93 -16
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/share.d.ts +5 -17
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +16 -62
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +57 -89
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +16 -5
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +16 -63
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +28 -54
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/local-path-codec.test.js +20 -0
- package/dist/local-path-codec.test.js.map +1 -1
- package/dist/s3.d.ts +1 -1
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +7 -23
- package/dist/s3.js.map +1 -1
- package/dist/s3.test.js +13 -20
- package/dist/s3.test.js.map +1 -1
- package/dist/sync-core.d.ts +16 -0
- package/dist/sync-core.d.ts.map +1 -1
- package/dist/sync-core.js +38 -0
- package/dist/sync-core.js.map +1 -1
- package/dist/sync-progress.d.ts +18 -1
- package/dist/sync-progress.d.ts.map +1 -1
- package/dist/sync-progress.js +113 -7
- package/dist/sync-progress.js.map +1 -1
- package/dist/sync-progress.test.js +32 -2
- package/dist/sync-progress.test.js.map +1 -1
- package/dist/watcher.d.ts +26 -5
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +93 -13
- package/dist/watcher.js.map +1 -1
- package/dist/watcher.test.js +45 -1
- package/dist/watcher.test.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/sync.test.js
CHANGED
|
@@ -3249,18 +3249,11 @@ describe("sync", () => {
|
|
|
3249
3249
|
expect(result.filesDownloaded).toBe(1);
|
|
3250
3250
|
expect(result.filesExcludedByPolicy).toBeGreaterThanOrEqual(1);
|
|
3251
3251
|
});
|
|
3252
|
-
it("
|
|
3253
|
-
|
|
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";
|
|
3252
|
+
it("round-trips a companies/ key into the identical nested company-vault path", async () => {
|
|
3253
|
+
const nestedKey = "companies/acme/audit/chat-skills/x.json";
|
|
3261
3254
|
vi.mocked(s3Module.listRemoteFiles).mockResolvedValueOnce([
|
|
3262
|
-
//
|
|
3263
|
-
{ key:
|
|
3255
|
+
// `companies/` is an ordinary directory beneath this company's root.
|
|
3256
|
+
{ key: nestedKey, size: 2152, lastModified: new Date(), etag: '"nested"' },
|
|
3264
3257
|
// A legitimate bucket-relative key — must still download.
|
|
3265
3258
|
{ key: "docs/handoff.md", size: 42, lastModified: new Date(), etag: '"ok"' },
|
|
3266
3259
|
]);
|
|
@@ -3270,17 +3263,12 @@ describe("sync", () => {
|
|
|
3270
3263
|
hqRoot: tmpDir,
|
|
3271
3264
|
});
|
|
3272
3265
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
3273
|
-
// The
|
|
3274
|
-
expect(fs.existsSync(path.join(companyRoot, "companies", "acme", "
|
|
3275
|
-
|
|
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
|
-
}
|
|
3266
|
+
// The remote key materializes exactly beneath the selected company root.
|
|
3267
|
+
expect(fs.existsSync(path.join(companyRoot, "companies", "acme", "audit", "chat-skills", "x.json"))).toBe(true);
|
|
3268
|
+
expect(vi.mocked(s3Module.downloadFile).mock.calls.map((call) => call[1])).toContain(nestedKey);
|
|
3280
3269
|
// The legitimate bucket-relative key MUST still download.
|
|
3281
3270
|
expect(fs.existsSync(path.join(companyRoot, "docs", "handoff.md"))).toBe(true);
|
|
3282
|
-
expect(result.filesDownloaded).toBe(
|
|
3283
|
-
expect(result.filesExcludedByPolicy).toBeGreaterThanOrEqual(1);
|
|
3271
|
+
expect(result.filesDownloaded).toBe(2);
|
|
3284
3272
|
expect(result.aborted).toBe(false);
|
|
3285
3273
|
});
|
|
3286
3274
|
it("F02: rejects traversal remote keys before they can escape the company root", async () => {
|
|
@@ -4753,7 +4741,7 @@ describe("reportNewFilesToNotify chunking (server cap = 1000 files/report)", ()
|
|
|
4753
4741
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
4754
4742
|
});
|
|
4755
4743
|
});
|
|
4756
|
-
describe("
|
|
4744
|
+
describe("nested companies directories in a company vault", () => {
|
|
4757
4745
|
let tmpDir;
|
|
4758
4746
|
let stateDir;
|
|
4759
4747
|
let journalPath;
|
|
@@ -4781,15 +4769,15 @@ describe("scope-invalid key hardening (companies/ prefix in a company vault —
|
|
|
4781
4769
|
fs.rmSync(stateDir, { recursive: true, force: true });
|
|
4782
4770
|
delete process.env.HQ_STATE_DIR;
|
|
4783
4771
|
});
|
|
4784
|
-
it("
|
|
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.
|
|
4772
|
+
it("downloads the live 25-file nested companies/ shape without a skip event", async () => {
|
|
4790
4773
|
vi.mocked(s3Module.listRemoteFiles).mockReset();
|
|
4791
4774
|
vi.mocked(s3Module.listRemoteFiles).mockResolvedValue([
|
|
4792
|
-
{
|
|
4775
|
+
...Array.from({ length: 25 }, (_, index) => ({
|
|
4776
|
+
key: `companies/acme/audit/chat-skills/file-${index.toString().padStart(2, "0")}.json`,
|
|
4777
|
+
size: 10,
|
|
4778
|
+
lastModified: new Date(),
|
|
4779
|
+
etag: `"nested-${index}"`,
|
|
4780
|
+
})),
|
|
4793
4781
|
{ key: "docs/good.md", size: 5, lastModified: new Date(), etag: '"g1"' },
|
|
4794
4782
|
]);
|
|
4795
4783
|
const events = [];
|
|
@@ -4799,26 +4787,16 @@ describe("scope-invalid key hardening (companies/ prefix in a company vault —
|
|
|
4799
4787
|
hqRoot: tmpDir,
|
|
4800
4788
|
onEvent: (e) => events.push(e),
|
|
4801
4789
|
});
|
|
4802
|
-
// The good file syncs; the poisoned key is never downloaded.
|
|
4803
4790
|
expect(result.aborted).toBe(false);
|
|
4804
|
-
expect(result.filesDownloaded).toBe(
|
|
4791
|
+
expect(result.filesDownloaded).toBe(26);
|
|
4805
4792
|
expect(fs.existsSync(path.join(tmpDir, "companies", "acme", "docs", "good.md"))).toBe(true);
|
|
4806
|
-
expect(fs.existsSync(path.join(tmpDir, "companies", "acme", "companies", "acme", "
|
|
4793
|
+
expect(fs.existsSync(path.join(tmpDir, "companies", "acme", "companies", "acme", "audit", "chat-skills", "file-24.json"))).toBe(true);
|
|
4807
4794
|
const downloadedKeys = vi
|
|
4808
4795
|
.mocked(s3Module.downloadFile)
|
|
4809
4796
|
.mock.calls.map((c) => c[1]);
|
|
4810
|
-
expect(downloadedKeys).
|
|
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);
|
|
4797
|
+
expect(downloadedKeys).toContain("companies/acme/audit/chat-skills/file-00.json");
|
|
4814
4798
|
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
|
-
]);
|
|
4799
|
+
expect(skipEvents).toEqual([]);
|
|
4822
4800
|
expect(events.filter((e) => e.type === "error")).toEqual([]);
|
|
4823
4801
|
});
|
|
4824
4802
|
it("skips a control-character remote key before presigning while transferring valid keys", async () => {
|
|
@@ -4861,13 +4839,9 @@ describe("scope-invalid key hardening (companies/ prefix in a company vault —
|
|
|
4861
4839
|
]);
|
|
4862
4840
|
expect(events.filter((event) => event.type === "error")).toEqual([]);
|
|
4863
4841
|
});
|
|
4864
|
-
it("
|
|
4865
|
-
// A
|
|
4866
|
-
//
|
|
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.
|
|
4842
|
+
it("tombstones a remote-missing nested companies/ journal entry after HEAD verification", async () => {
|
|
4843
|
+
// A nested `companies/` key follows the same protected journal-tombstone
|
|
4844
|
+
// path as every other vault-relative file.
|
|
4871
4845
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
4872
4846
|
const doubled = path.join(companyRoot, "companies", "acme", "knowledge");
|
|
4873
4847
|
fs.mkdirSync(doubled, { recursive: true });
|
|
@@ -4890,25 +4864,25 @@ describe("scope-invalid key hardening (companies/ prefix in a company vault —
|
|
|
4890
4864
|
},
|
|
4891
4865
|
},
|
|
4892
4866
|
}));
|
|
4893
|
-
// Remote LIST no longer contains the
|
|
4867
|
+
// Remote LIST no longer contains the nested key.
|
|
4894
4868
|
vi.mocked(s3Module.listRemoteFiles).mockReset();
|
|
4895
4869
|
vi.mocked(s3Module.listRemoteFiles).mockResolvedValue([]);
|
|
4870
|
+
vi.mocked(s3Module.headRemoteFile).mockResolvedValue(null);
|
|
4896
4871
|
const result = await sync({
|
|
4897
4872
|
company: "acme",
|
|
4898
4873
|
vaultConfig: mockConfig,
|
|
4899
4874
|
hqRoot: tmpDir,
|
|
4900
4875
|
});
|
|
4901
|
-
// Cleaned, not re-uploaded: local
|
|
4876
|
+
// Cleaned, not re-uploaded: local nested copy gone, journal dropped.
|
|
4902
4877
|
expect(fs.existsSync(path.join(doubled, "poison.md"))).toBe(false);
|
|
4903
4878
|
expect(result.filesTombstoned).toBeGreaterThanOrEqual(1);
|
|
4904
4879
|
const journal = journalAt(journalPath);
|
|
4905
4880
|
expect(journal.files["companies/acme/knowledge/poison.md"]).toBeUndefined();
|
|
4906
|
-
//
|
|
4907
|
-
// reject it (INVALID_KEY_COMPANIES_SCOPED) and defer the cleanup forever.
|
|
4881
|
+
// The normal HEAD verification confirms it is safe to tombstone.
|
|
4908
4882
|
const headKeys = vi
|
|
4909
4883
|
.mocked(s3Module.headRemoteFile)
|
|
4910
4884
|
.mock.calls.map((c) => c[1]);
|
|
4911
|
-
expect(headKeys).
|
|
4885
|
+
expect(headKeys).toContain("companies/acme/knowledge/poison.md");
|
|
4912
4886
|
// Never re-uploaded.
|
|
4913
4887
|
expect(vi.mocked(s3Module.uploadFile)).not.toHaveBeenCalled();
|
|
4914
4888
|
});
|