@indigoai-us/hq-cloud 6.11.19 → 6.12.0
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/.github/workflows/ci.yml +1 -1
- package/dist/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +9 -11
- package/dist/bin/sync-runner-company.js.map +1 -1
- 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 +35 -64
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/rescue-core.js +1 -1
- package/dist/cli/rescue-core.js.map +1 -1
- package/dist/cli/share.js +25 -19
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +63 -9
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +11 -25
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +16 -17
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +40 -42
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/company-resolver.test.js.map +1 -1
- package/dist/lib/conflict-file.js +1 -1
- package/dist/lib/conflict-file.js.map +1 -1
- package/dist/personal-vault.d.ts +23 -24
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +38 -30
- package/dist/personal-vault.js.map +1 -1
- package/dist/personal-vault.test.d.ts +9 -6
- package/dist/personal-vault.test.d.ts.map +1 -1
- package/dist/personal-vault.test.js +90 -79
- package/dist/personal-vault.test.js.map +1 -1
- package/dist/prefix-coalesce.d.ts.map +1 -1
- package/dist/prefix-coalesce.js +0 -3
- package/dist/prefix-coalesce.js.map +1 -1
- package/dist/s3.test.js +0 -1
- package/dist/s3.test.js.map +1 -1
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/dist/vault-client.d.ts +1 -1
- package/dist/vault-client.d.ts.map +1 -1
- package/dist/vault-client.js +1 -1
- package/dist/vault-client.js.map +1 -1
- package/dist/vault-client.test.js +15 -0
- package/dist/vault-client.test.js.map +1 -1
- package/eslint.config.js +67 -0
- package/package.json +5 -1
- package/src/bin/sync-runner-company.ts +9 -12
- package/src/bin/sync-runner.test.ts +35 -74
- package/src/bin/sync-runner.ts +0 -1
- package/src/cli/rescue-core.ts +1 -1
- package/src/cli/share.test.ts +79 -11
- package/src/cli/share.ts +24 -20
- package/src/cli/sync.test.ts +40 -42
- package/src/cli/sync.ts +25 -42
- package/src/company-resolver.test.ts +1 -1
- package/src/lib/conflict-file.ts +1 -1
- package/src/personal-vault.test.ts +91 -83
- package/src/personal-vault.ts +45 -41
- package/src/prefix-coalesce.ts +0 -4
- package/src/s3.test.ts +0 -2
- package/src/sync/push-receiver.test.ts +1 -1
- package/src/vault-client.test.ts +20 -0
- package/src/vault-client.ts +2 -2
package/dist/cli/share.test.js
CHANGED
|
@@ -831,7 +831,7 @@ describe("share", () => {
|
|
|
831
831
|
expect(putKeys).not.toContain("settings/.gitkeep");
|
|
832
832
|
expect(putKeys).not.toContain("projects/.gitkeep");
|
|
833
833
|
});
|
|
834
|
-
it("scoped push defense-in-depth: a 403 on HEAD skips that key
|
|
834
|
+
it("scoped push defense-in-depth: a 403 on HEAD skips that key as scope-excluded without an error event", async () => {
|
|
835
835
|
// Belt-and-suspenders: even if a key slips past the prefix filter (a grant
|
|
836
836
|
// that changed mid-run, a pin outside the grant, prefix-coalesce
|
|
837
837
|
// imprecision), the server's correct 403 on the HEAD must NOT abort the
|
|
@@ -866,20 +866,72 @@ describe("share", () => {
|
|
|
866
866
|
expect(result.filesUploaded).toBe(1);
|
|
867
867
|
const putKeys = vi.mocked(uploadFile).mock.calls.map((c) => c[2]);
|
|
868
868
|
expect(putKeys).toEqual(["in-scope.md"]);
|
|
869
|
-
|
|
869
|
+
expect(result.filesExcludedByScope).toBe(1);
|
|
870
|
+
const scopeEv = events.find((e) => e.type === "scope-excluded");
|
|
871
|
+
expect(scopeEv).toBeDefined();
|
|
872
|
+
expect(scopeEv.count).toBe(1);
|
|
873
|
+
expect(scopeEv.samplePaths).toEqual(["out-of-reach.md"]);
|
|
874
|
+
expect(events.some((e) => e.type === "error")).toBe(false);
|
|
875
|
+
});
|
|
876
|
+
it("scoped push defense-in-depth: a 403 on PUT skips that key as scope-excluded without an error event", async () => {
|
|
877
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
878
|
+
fs.mkdirSync(companyRoot, { recursive: true });
|
|
879
|
+
const blockedFile = path.join(companyRoot, "put-denied.md");
|
|
880
|
+
fs.writeFileSync(blockedFile, "denied\n");
|
|
881
|
+
vi.mocked(uploadFile).mockImplementation(async (_ctx, _abs, key) => {
|
|
882
|
+
if (key === "put-denied.md") {
|
|
883
|
+
const err = new Error("access denied");
|
|
884
|
+
err.name = "AccessDenied";
|
|
885
|
+
throw err;
|
|
886
|
+
}
|
|
887
|
+
return { etag: '"upload-etag"' };
|
|
888
|
+
});
|
|
889
|
+
const events = [];
|
|
890
|
+
const result = await share({
|
|
891
|
+
paths: [blockedFile],
|
|
892
|
+
company: "acme",
|
|
893
|
+
vaultConfig: mockConfig,
|
|
894
|
+
hqRoot: tmpDir,
|
|
895
|
+
onEvent: (e) => events.push(e),
|
|
896
|
+
});
|
|
897
|
+
expect(result.aborted).toBe(false);
|
|
898
|
+
expect(result.filesUploaded).toBe(0);
|
|
899
|
+
expect(result.filesExcludedByScope).toBe(1);
|
|
900
|
+
const scopeEv = events.find((e) => e.type === "scope-excluded");
|
|
901
|
+
expect(scopeEv).toBeDefined();
|
|
902
|
+
expect(scopeEv.count).toBe(1);
|
|
903
|
+
expect(scopeEv.samplePaths).toEqual(["put-denied.md"]);
|
|
904
|
+
expect(events.some((e) => e.type === "error")).toBe(false);
|
|
905
|
+
});
|
|
906
|
+
it("scoped push defense-in-depth: a non-access-denied PUT failure still emits an error event", async () => {
|
|
907
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
908
|
+
fs.mkdirSync(companyRoot, { recursive: true });
|
|
909
|
+
const failedFile = path.join(companyRoot, "put-failed.md");
|
|
910
|
+
fs.writeFileSync(failedFile, "failed\n");
|
|
911
|
+
vi.mocked(uploadFile).mockRejectedValueOnce(new Error("S3 down"));
|
|
912
|
+
const events = [];
|
|
913
|
+
const result = await share({
|
|
914
|
+
paths: [failedFile],
|
|
915
|
+
company: "acme",
|
|
916
|
+
vaultConfig: mockConfig,
|
|
917
|
+
hqRoot: tmpDir,
|
|
918
|
+
onEvent: (e) => events.push(e),
|
|
919
|
+
});
|
|
920
|
+
expect(result.filesUploaded).toBe(0);
|
|
921
|
+
expect(result.filesExcludedByScope).toBe(0);
|
|
870
922
|
const errs = events.filter((e) => e.type === "error");
|
|
871
923
|
expect(errs).toHaveLength(1);
|
|
872
|
-
expect(errs[0].path).toBe("
|
|
873
|
-
expect(errs[0].message).
|
|
924
|
+
expect(errs[0].path).toBe("put-failed.md");
|
|
925
|
+
expect(errs[0].message).toBe("S3 down");
|
|
874
926
|
});
|
|
875
|
-
it("a Forbidden HEAD (presigned-transport denial) skips the key — NEVER an unconditional PUT", async () => {
|
|
927
|
+
it("a Forbidden HEAD (presigned-transport denial) skips the key as scope-excluded — NEVER an unconditional PUT", async () => {
|
|
876
928
|
// Regression for the 2026-06-10..12 vault regression storm: the presigned
|
|
877
929
|
// transport used to map per-key denials and signed-GET 403s to `null`,
|
|
878
930
|
// which `processUploadItem` read as "no remote object" — skipping every
|
|
879
931
|
// conflict guard and blind-PUTting this machine's (possibly stale) bytes
|
|
880
932
|
// over a newer remote. The transport now throws `name: "Forbidden"`
|
|
881
933
|
// (SDK HeadObject parity); this locks the share side of that contract:
|
|
882
|
-
// a Forbidden HEAD must skip the key
|
|
934
|
+
// a Forbidden HEAD must skip the key as scope-excluded and issue NO PUT.
|
|
883
935
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
884
936
|
fs.mkdirSync(companyRoot, { recursive: true });
|
|
885
937
|
const staleFile = path.join(companyRoot, "stale-local.md");
|
|
@@ -901,9 +953,11 @@ describe("share", () => {
|
|
|
901
953
|
expect(vi.mocked(uploadFile)).not.toHaveBeenCalled();
|
|
902
954
|
expect(result.filesUploaded).toBe(0);
|
|
903
955
|
expect(result.aborted).toBe(false);
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
expect(
|
|
956
|
+
expect(result.filesExcludedByScope).toBe(1);
|
|
957
|
+
const scopeEv = events.find((e) => e.type === "scope-excluded");
|
|
958
|
+
expect(scopeEv).toBeDefined();
|
|
959
|
+
expect(scopeEv.samplePaths).toEqual(["stale-local.md"]);
|
|
960
|
+
expect(events.some((e) => e.type === "error")).toBe(false);
|
|
907
961
|
});
|
|
908
962
|
it("uploads (no conflict) when only the local side changed since last sync", async () => {
|
|
909
963
|
// Regression for hq-cloud#<conflict-detection>: a local edit to a file
|