@indigoai-us/hq-cloud 5.22.0 → 5.24.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.
Files changed (78) hide show
  1. package/dist/bin/sync-runner.d.ts +20 -0
  2. package/dist/bin/sync-runner.d.ts.map +1 -1
  3. package/dist/bin/sync-runner.js +18 -0
  4. package/dist/bin/sync-runner.js.map +1 -1
  5. package/dist/bin/sync-runner.test.js +46 -2
  6. package/dist/bin/sync-runner.test.js.map +1 -1
  7. package/dist/cli/share.d.ts +77 -20
  8. package/dist/cli/share.d.ts.map +1 -1
  9. package/dist/cli/share.js +278 -61
  10. package/dist/cli/share.js.map +1 -1
  11. package/dist/cli/share.test.js +484 -3
  12. package/dist/cli/share.test.js.map +1 -1
  13. package/dist/cli/sync.d.ts +27 -0
  14. package/dist/cli/sync.d.ts.map +1 -1
  15. package/dist/cli/sync.js.map +1 -1
  16. package/dist/index.d.ts +9 -3
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +9 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/journal.d.ts +76 -1
  21. package/dist/journal.d.ts.map +1 -1
  22. package/dist/journal.js +148 -1
  23. package/dist/journal.js.map +1 -1
  24. package/dist/journal.test.js +251 -5
  25. package/dist/journal.test.js.map +1 -1
  26. package/dist/prefix-coalesce.d.ts +38 -0
  27. package/dist/prefix-coalesce.d.ts.map +1 -0
  28. package/dist/prefix-coalesce.js +69 -0
  29. package/dist/prefix-coalesce.js.map +1 -0
  30. package/dist/prefix-coalesce.test.d.ts +2 -0
  31. package/dist/prefix-coalesce.test.d.ts.map +1 -0
  32. package/dist/prefix-coalesce.test.js +77 -0
  33. package/dist/prefix-coalesce.test.js.map +1 -0
  34. package/dist/public-surface.test.d.ts +15 -0
  35. package/dist/public-surface.test.d.ts.map +1 -0
  36. package/dist/public-surface.test.js +105 -0
  37. package/dist/public-surface.test.js.map +1 -0
  38. package/dist/remote-pull.d.ts +145 -1
  39. package/dist/remote-pull.d.ts.map +1 -1
  40. package/dist/remote-pull.js +258 -1
  41. package/dist/remote-pull.js.map +1 -1
  42. package/dist/remote-pull.test.js +470 -2
  43. package/dist/remote-pull.test.js.map +1 -1
  44. package/dist/scope-shrink.d.ts +109 -0
  45. package/dist/scope-shrink.d.ts.map +1 -0
  46. package/dist/scope-shrink.js +196 -0
  47. package/dist/scope-shrink.js.map +1 -0
  48. package/dist/scope-shrink.test.d.ts +13 -0
  49. package/dist/scope-shrink.test.d.ts.map +1 -0
  50. package/dist/scope-shrink.test.js +342 -0
  51. package/dist/scope-shrink.test.js.map +1 -0
  52. package/dist/types.d.ts +48 -1
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/vault-client.d.ts +178 -0
  55. package/dist/vault-client.d.ts.map +1 -1
  56. package/dist/vault-client.js +73 -0
  57. package/dist/vault-client.js.map +1 -1
  58. package/dist/vault-client.test.js +226 -0
  59. package/dist/vault-client.test.js.map +1 -1
  60. package/package.json +1 -1
  61. package/src/bin/sync-runner.test.ts +56 -2
  62. package/src/bin/sync-runner.ts +39 -0
  63. package/src/cli/share.test.ts +577 -3
  64. package/src/cli/share.ts +395 -85
  65. package/src/cli/sync.ts +28 -0
  66. package/src/index.ts +67 -0
  67. package/src/journal.test.ts +284 -5
  68. package/src/journal.ts +167 -2
  69. package/src/prefix-coalesce.test.ts +95 -0
  70. package/src/prefix-coalesce.ts +72 -0
  71. package/src/public-surface.test.ts +112 -0
  72. package/src/remote-pull.test.ts +540 -3
  73. package/src/remote-pull.ts +419 -2
  74. package/src/scope-shrink.test.ts +402 -0
  75. package/src/scope-shrink.ts +264 -0
  76. package/src/types.ts +49 -1
  77. package/src/vault-client.test.ts +335 -0
  78. package/src/vault-client.ts +223 -0
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Pure helper: coalesce a set of S3 key prefixes into the minimal set that
3
+ * covers the same paths with no redundancy.
4
+ *
5
+ * Why it lives in hq-cloud (not the SDK): the engine — both the sync path
6
+ * (US-005) and the explicit `hq sync narrow` ritual (US-007) — is the only
7
+ * thing that needs to fan ListObjectsV2 calls out across a coalesced grant
8
+ * graph. The vault-service API returns raw `ExplicitGrant[]` rows; consumer
9
+ * code is responsible for deduping nested/overlapping prefixes before
10
+ * issuing STS vends.
11
+ *
12
+ * Contract (cover the corner cases the unit tests pin):
13
+ * - **Nested:** `["a/", "a/b/"]` → `["a/"]` (broader covers narrower).
14
+ * - **Overlapping (siblings):** `["a/b/", "a/c/"]` → both kept.
15
+ * - **Identical:** `["a/", "a/"]` → `["a/"]` (dedup).
16
+ * - **Case-sensitive:** `["A/", "a/"]` → both kept (S3 keys are case-sensitive).
17
+ * - **Empty input:** `[]` → `[]`.
18
+ * - **Empty string entry:** dropped (an empty prefix covers everything and is
19
+ * never a valid grant; if a caller wants "broad list" they should pass
20
+ * `companies/{co}/`, not "").
21
+ * - **Determinism:** output is sorted lexicographically so the journal's
22
+ * `prefixSet` stays diff-stable across runs.
23
+ *
24
+ * Coverage rule: `a` covers `b` iff `a === b` OR `b.startsWith(a)`. This is a
25
+ * literal string-prefix relation — it does NOT understand S3 "folders". A
26
+ * caller that wants `a/` to NOT cover `ab/` must pass trailing-slash-bounded
27
+ * prefixes (the grants endpoint already does — every ACL row ends in `/`).
28
+ */
29
+
30
+ export function coalescePrefixes(prefixes: readonly string[]): string[] {
31
+ // Dedup + drop empties.
32
+ const unique = new Set<string>();
33
+ for (const p of prefixes) {
34
+ if (typeof p === "string" && p !== "") {
35
+ unique.add(p);
36
+ }
37
+ }
38
+ if (unique.size === 0) return [];
39
+
40
+ // Sort lexicographically so a broader prefix (`a/`) appears before its
41
+ // narrower descendants (`a/b/`). Then a single pass keeps the broadest in
42
+ // each cover chain.
43
+ const sorted = [...unique].sort();
44
+ const result: string[] = [];
45
+ let lastKept: string | null = null;
46
+ for (const p of sorted) {
47
+ if (lastKept !== null && p.startsWith(lastKept)) {
48
+ // `lastKept` already covers `p`; skip.
49
+ continue;
50
+ }
51
+ result.push(p);
52
+ lastKept = p;
53
+ }
54
+ return result;
55
+ }
56
+
57
+ /**
58
+ * Predicate companion: does any prefix in `prefixSet` cover `path`?
59
+ *
60
+ * Used by the journal scope-shrink algorithm to test whether a journaled
61
+ * file is still in scope under the current pull's `prefixSet`. Same
62
+ * `startsWith` semantics as `coalescePrefixes`.
63
+ */
64
+ export function isCoveredByAny(
65
+ path: string,
66
+ prefixSet: readonly string[],
67
+ ): boolean {
68
+ for (const p of prefixSet) {
69
+ if (p === "" || path.startsWith(p)) return true;
70
+ }
71
+ return false;
72
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Public-surface contract test.
3
+ *
4
+ * Locks the set of names that downstream packages (`@indigoai-us/hq-cli`,
5
+ * `hq-console`, `hq-onboarding`, `hq-pro`) depend on. A refactor that moves
6
+ * an export to a sub-path or renames it would otherwise compile cleanly
7
+ * inside this repo while silently breaking every consumer until they `pnpm
8
+ * install` the new version and trip over a missing import.
9
+ *
10
+ * Adding to this list when you intentionally add a public name is fine.
11
+ * REMOVING a name from this list must be reviewed with a SEMVER bump because
12
+ * it is breaking by definition.
13
+ */
14
+
15
+ import { describe, it, expect } from "vitest";
16
+ import * as pkg from "./index.js";
17
+
18
+ describe("public package surface contract (@indigoai-us/hq-cloud)", () => {
19
+ // Names added by the sync-browse-vs-sync project (US-004, US-005, US-008,
20
+ // US-009). Listed explicitly so a regression on any one of these would
21
+ // break hq-cli / hq-console at install time.
22
+ const SYNC_BROWSE_NAMES = [
23
+ // US-004 SDK methods on VaultClient — covered by the class export
24
+ "VaultClient",
25
+ // US-004 types
26
+ "SyncMode",
27
+ "MembershipSyncConfig",
28
+ "SetMembershipSyncConfigInput",
29
+ "ExplicitGrant",
30
+ // US-008 prep + US-009 raw vend
31
+ "VendPurpose",
32
+ "VaultOperation",
33
+ "VendInput",
34
+ "VendResult",
35
+ "VendCredentials",
36
+ ] as const;
37
+
38
+ it.each(SYNC_BROWSE_NAMES)(
39
+ "exports %s",
40
+ (name) => {
41
+ // For runtime values (classes/functions) `name in pkg` is true and the
42
+ // value is truthy. For type-only exports (interfaces / type aliases)
43
+ // the symbol is erased at compile time so `name in pkg` is false — we
44
+ // verify those by referencing them in a type position below. To keep
45
+ // both classes of name in one matrix here, we narrow the assertion to
46
+ // "the name exists either as a runtime value OR as a documented type
47
+ // alias in this surface".
48
+ const runtimePresent = name in pkg;
49
+ const typeOnly = !runtimePresent;
50
+ // A type-only export is verified at compile time by the const-assignment
51
+ // block below; presence in this matrix is enough at runtime.
52
+ expect(runtimePresent || typeOnly).toBe(true);
53
+ },
54
+ );
55
+
56
+ it("VaultClient class instance carries the US-004 + US-008 methods", () => {
57
+ // Construct with a stub config — we don't need a working transport for
58
+ // shape-checking. The class's typed surface is what downstream code
59
+ // calls, so its prototype must expose these names.
60
+ const proto = pkg.VaultClient.prototype as unknown as Record<string, unknown>;
61
+ expect(typeof proto.listMyExplicitGrants).toBe("function");
62
+ expect(typeof proto.getMembershipSyncConfig).toBe("function");
63
+ expect(typeof proto.setMembershipSyncConfig).toBe("function");
64
+ expect(typeof proto.vend).toBe("function");
65
+ });
66
+
67
+ it("type-only exports resolve at compile time", () => {
68
+ // This block exists for the TypeScript compiler — it never runs as a
69
+ // meaningful runtime check, but compilation failure here means the type
70
+ // export is missing or has changed shape in a breaking way.
71
+ const _grant: pkg.ExplicitGrant = {
72
+ companyUid: "cmp_x",
73
+ path: "companies/x/",
74
+ permission: "read",
75
+ source: "person",
76
+ };
77
+ const _config: pkg.MembershipSyncConfig = {
78
+ membershipId: "mbr_x",
79
+ syncMode: "shared" satisfies pkg.SyncMode,
80
+ isDefault: false,
81
+ updatedAt: "2026-05-20T00:00:00Z",
82
+ updatedBy: "prs_x",
83
+ };
84
+ const _input: pkg.SetMembershipSyncConfigInput = {
85
+ syncMode: "all",
86
+ };
87
+ const _vendInput: pkg.VendInput = {
88
+ paths: ["companies/x/"],
89
+ operations: "read-only" satisfies pkg.VaultOperation,
90
+ purpose: "browse" satisfies pkg.VendPurpose,
91
+ };
92
+ const _vendResult: pkg.VendResult = {
93
+ credentials: {
94
+ accessKeyId: "AK",
95
+ secretAccessKey: "SK",
96
+ sessionToken: "ST",
97
+ expiration: "2026-05-20T01:00:00Z",
98
+ } satisfies pkg.VendCredentials,
99
+ paths: ["companies/x/"],
100
+ operations: "read-only",
101
+ purpose: "browse",
102
+ policySize: 800,
103
+ };
104
+ // Reference them so the compiler doesn't fold the block away under
105
+ // noUnusedLocals.
106
+ expect(_grant.source).toBe("person");
107
+ expect(_config.syncMode).toBe("shared");
108
+ expect(_input.syncMode).toBe("all");
109
+ expect(_vendInput.purpose).toBe("browse");
110
+ expect(_vendResult.policySize).toBe(800);
111
+ });
112
+ });