@indigoai-us/hq-cloud 6.14.23 → 6.14.25

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 (131) hide show
  1. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  2. package/dist/bin/sync-runner-company.js +11 -0
  3. package/dist/bin/sync-runner-company.js.map +1 -1
  4. package/dist/bin/sync-runner-planning.d.ts +11 -0
  5. package/dist/bin/sync-runner-planning.d.ts.map +1 -1
  6. package/dist/bin/sync-runner-planning.js +37 -2
  7. package/dist/bin/sync-runner-planning.js.map +1 -1
  8. package/dist/bin/sync-runner-planning.test.js +1 -0
  9. package/dist/bin/sync-runner-planning.test.js.map +1 -1
  10. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  11. package/dist/bin/sync-runner-watch-loop.js +17 -1
  12. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  13. package/dist/bin/sync-runner.d.ts +6 -0
  14. package/dist/bin/sync-runner.d.ts.map +1 -1
  15. package/dist/bin/sync-runner.js +14 -2
  16. package/dist/bin/sync-runner.js.map +1 -1
  17. package/dist/bin/sync-runner.test.js +155 -1
  18. package/dist/bin/sync-runner.test.js.map +1 -1
  19. package/dist/cli/doctor.d.ts +119 -0
  20. package/dist/cli/doctor.d.ts.map +1 -0
  21. package/dist/cli/doctor.js +485 -0
  22. package/dist/cli/doctor.js.map +1 -0
  23. package/dist/cli/doctor.test.d.ts +13 -0
  24. package/dist/cli/doctor.test.d.ts.map +1 -0
  25. package/dist/cli/doctor.test.js +485 -0
  26. package/dist/cli/doctor.test.js.map +1 -0
  27. package/dist/cli/index.d.ts +2 -0
  28. package/dist/cli/index.d.ts.map +1 -1
  29. package/dist/cli/index.js +4 -0
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/cli/share.js +6 -1
  32. package/dist/cli/share.js.map +1 -1
  33. package/dist/cli/sync.d.ts +29 -0
  34. package/dist/cli/sync.d.ts.map +1 -1
  35. package/dist/cli/sync.js +140 -2
  36. package/dist/cli/sync.js.map +1 -1
  37. package/dist/cli/sync.test.js +223 -1
  38. package/dist/cli/sync.test.js.map +1 -1
  39. package/dist/cognito-auth.test.js +7 -0
  40. package/dist/cognito-auth.test.js.map +1 -1
  41. package/dist/company-resolver.d.ts +31 -1
  42. package/dist/company-resolver.d.ts.map +1 -1
  43. package/dist/company-resolver.js +329 -21
  44. package/dist/company-resolver.js.map +1 -1
  45. package/dist/company-resolver.test.js +371 -1
  46. package/dist/company-resolver.test.js.map +1 -1
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +3 -0
  50. package/dist/index.js.map +1 -1
  51. package/dist/lib/conflict-file.d.ts.map +1 -1
  52. package/dist/lib/conflict-file.js +5 -1
  53. package/dist/lib/conflict-file.js.map +1 -1
  54. package/dist/lib/conflict-index.d.ts.map +1 -1
  55. package/dist/lib/conflict-index.js +7 -2
  56. package/dist/lib/conflict-index.js.map +1 -1
  57. package/dist/lib/machine-id.test.js +14 -0
  58. package/dist/lib/machine-id.test.js.map +1 -1
  59. package/dist/local-path-codec.d.ts +18 -0
  60. package/dist/local-path-codec.d.ts.map +1 -1
  61. package/dist/local-path-codec.js +63 -0
  62. package/dist/local-path-codec.js.map +1 -1
  63. package/dist/local-path-codec.test.d.ts +14 -0
  64. package/dist/local-path-codec.test.d.ts.map +1 -0
  65. package/dist/local-path-codec.test.js +102 -0
  66. package/dist/local-path-codec.test.js.map +1 -0
  67. package/dist/machine-auth.test.js +7 -0
  68. package/dist/machine-auth.test.js.map +1 -1
  69. package/dist/s3.d.ts +5 -0
  70. package/dist/s3.d.ts.map +1 -1
  71. package/dist/s3.js +129 -3
  72. package/dist/s3.js.map +1 -1
  73. package/dist/s3.symlink-materialize.test.d.ts +18 -0
  74. package/dist/s3.symlink-materialize.test.d.ts.map +1 -0
  75. package/dist/s3.symlink-materialize.test.js +394 -0
  76. package/dist/s3.symlink-materialize.test.js.map +1 -0
  77. package/dist/s3.test.js +19 -0
  78. package/dist/s3.test.js.map +1 -1
  79. package/dist/skill-telemetry.d.ts.map +1 -1
  80. package/dist/skill-telemetry.js +24 -3
  81. package/dist/skill-telemetry.js.map +1 -1
  82. package/dist/skill-telemetry.test.js +17 -0
  83. package/dist/skill-telemetry.test.js.map +1 -1
  84. package/dist/telemetry.d.ts +10 -1
  85. package/dist/telemetry.d.ts.map +1 -1
  86. package/dist/telemetry.js +95 -4
  87. package/dist/telemetry.js.map +1 -1
  88. package/dist/telemetry.test.js +177 -0
  89. package/dist/telemetry.test.js.map +1 -1
  90. package/dist/vault-client.d.ts +42 -0
  91. package/dist/vault-client.d.ts.map +1 -1
  92. package/dist/vault-client.js +40 -0
  93. package/dist/vault-client.js.map +1 -1
  94. package/dist/watcher.d.ts.map +1 -1
  95. package/dist/watcher.js +10 -1
  96. package/dist/watcher.js.map +1 -1
  97. package/dist/watcher.test.js +28 -0
  98. package/dist/watcher.test.js.map +1 -1
  99. package/package.json +1 -1
  100. package/src/bin/sync-runner-company.ts +10 -0
  101. package/src/bin/sync-runner-planning.test.ts +1 -0
  102. package/src/bin/sync-runner-planning.ts +46 -3
  103. package/src/bin/sync-runner-watch-loop.ts +19 -1
  104. package/src/bin/sync-runner.test.ts +174 -0
  105. package/src/bin/sync-runner.ts +17 -1
  106. package/src/cli/doctor.test.ts +581 -0
  107. package/src/cli/doctor.ts +640 -0
  108. package/src/cli/index.ts +14 -0
  109. package/src/cli/share.ts +6 -1
  110. package/src/cli/sync.test.ts +281 -1
  111. package/src/cli/sync.ts +184 -2
  112. package/src/cognito-auth.test.ts +5 -0
  113. package/src/company-resolver.test.ts +432 -1
  114. package/src/company-resolver.ts +362 -23
  115. package/src/index.ts +10 -0
  116. package/src/lib/conflict-file.ts +6 -1
  117. package/src/lib/conflict-index.ts +7 -2
  118. package/src/lib/machine-id.test.ts +10 -0
  119. package/src/local-path-codec.test.ts +138 -0
  120. package/src/local-path-codec.ts +66 -0
  121. package/src/machine-auth.test.ts +5 -0
  122. package/src/s3.symlink-materialize.test.ts +492 -0
  123. package/src/s3.test.ts +24 -0
  124. package/src/s3.ts +148 -4
  125. package/src/skill-telemetry.test.ts +19 -0
  126. package/src/skill-telemetry.ts +32 -3
  127. package/src/telemetry.test.ts +241 -0
  128. package/src/telemetry.ts +124 -6
  129. package/src/vault-client.ts +68 -0
  130. package/src/watcher.test.ts +33 -0
  131. package/src/watcher.ts +10 -1
@@ -16,6 +16,7 @@ import {
16
16
  runRunnerWithLoop,
17
17
  resolveDeletePolicy,
18
18
  resolveSkipPersonal,
19
+ resolveSkipCompanies,
19
20
  selectObjectIOFactory,
20
21
  routeChangeToTarget,
21
22
  buildTargetedPushArgv,
@@ -1091,6 +1092,114 @@ describe("fanout-plan", () => {
1091
1092
  expect(plan.companies).toEqual([{ uid: "cmp_ghost", slug: "cmp_ghost" }]);
1092
1093
  });
1093
1094
 
1095
+ it("honors HQ_SYNC_SKIP_COMPANIES slug denylist in the fanout plan", async () => {
1096
+ const prev = process.env.HQ_SYNC_SKIP_COMPANIES;
1097
+ process.env.HQ_SYNC_SKIP_COMPANIES = "acme";
1098
+ try {
1099
+ const deps = makeDeps({
1100
+ createVaultClient: () =>
1101
+ makeVaultStub({
1102
+ memberships: [{ companyUid: "cmp_a" }, { companyUid: "cmp_b" }],
1103
+ entityGet: (uid: string) =>
1104
+ Promise.resolve({
1105
+ uid,
1106
+ slug: uid === "cmp_a" ? "acme" : "beta",
1107
+ } as unknown as EntityInfo),
1108
+ }),
1109
+ });
1110
+
1111
+ const code = await runRunner(["--companies"], deps);
1112
+ expect(code).toBe(0);
1113
+ const plan = deps.stdout
1114
+ .events()
1115
+ .find((e) => e.type === "fanout-plan") as Extract<
1116
+ RunnerEvent,
1117
+ { type: "fanout-plan" }
1118
+ >;
1119
+ expect(plan.companies.map((c) => c.slug)).toEqual(["beta"]);
1120
+ } finally {
1121
+ if (prev === undefined) delete process.env.HQ_SYNC_SKIP_COMPANIES;
1122
+ else process.env.HQ_SYNC_SKIP_COMPANIES = prev;
1123
+ }
1124
+ });
1125
+
1126
+ it("fail-closes unresolved entities when a skip denylist is active", async () => {
1127
+ const prev = process.env.HQ_SYNC_SKIP_COMPANIES;
1128
+ process.env.HQ_SYNC_SKIP_COMPANIES = "paused-co";
1129
+ try {
1130
+ const deps = makeDeps({
1131
+ createVaultClient: () =>
1132
+ makeVaultStub({
1133
+ memberships: [{ companyUid: "cmp_ambiguous" }],
1134
+ entityGet: () => Promise.reject(new Error("transient discovery blip")),
1135
+ }),
1136
+ });
1137
+
1138
+ const code = await runRunner(["--companies"], deps);
1139
+ // Omit ambiguous memberships from the plan, but retry (do not exit 0) so
1140
+ // watch scoped drain cannot publish without a successful upload pass.
1141
+ expect(code).toBe(TRANSIENT_NETWORK_EXIT);
1142
+ const plan = deps.stdout
1143
+ .events()
1144
+ .find((e) => e.type === "fanout-plan") as Extract<
1145
+ RunnerEvent,
1146
+ { type: "fanout-plan" }
1147
+ >;
1148
+ // Without fail-closed, slug would stay as UID and bypass the slug denylist.
1149
+ expect(plan.companies).toEqual([]);
1150
+ } finally {
1151
+ if (prev === undefined) delete process.env.HQ_SYNC_SKIP_COMPANIES;
1152
+ else process.env.HQ_SYNC_SKIP_COMPANIES = prev;
1153
+ }
1154
+ });
1155
+
1156
+ it("still syncs resolved companies when denylist ambiguity forces a transient retry", async () => {
1157
+ const prev = process.env.HQ_SYNC_SKIP_COMPANIES;
1158
+ process.env.HQ_SYNC_SKIP_COMPANIES = "paused-co";
1159
+ try {
1160
+ const sync = vi.fn().mockResolvedValue(defaultSyncResult());
1161
+ const deps = makeDeps({
1162
+ createVaultClient: () =>
1163
+ makeVaultStub({
1164
+ memberships: [
1165
+ { companyUid: "cmp_live" },
1166
+ { companyUid: "cmp_ambiguous" },
1167
+ ],
1168
+ entityGet: (uid: string) => {
1169
+ if (uid === "cmp_ambiguous") {
1170
+ return Promise.reject(new Error("transient discovery blip"));
1171
+ }
1172
+ return Promise.resolve({
1173
+ uid,
1174
+ slug: "live",
1175
+ type: "company",
1176
+ status: "active",
1177
+ createdAt: "2026-01-01T00:00:00Z",
1178
+ });
1179
+ },
1180
+ }),
1181
+ sync,
1182
+ });
1183
+
1184
+ const code = await runRunner(["--companies"], deps);
1185
+ expect(code).toBe(TRANSIENT_NETWORK_EXIT);
1186
+ expect(sync).toHaveBeenCalledTimes(1);
1187
+ expect(sync).toHaveBeenCalledWith(
1188
+ expect.objectContaining({ company: "cmp_live" }),
1189
+ );
1190
+ const plan = deps.stdout
1191
+ .events()
1192
+ .find((e) => e.type === "fanout-plan") as Extract<
1193
+ RunnerEvent,
1194
+ { type: "fanout-plan" }
1195
+ >;
1196
+ expect(plan.companies.map((c) => c.slug)).toEqual(["live"]);
1197
+ } finally {
1198
+ if (prev === undefined) delete process.env.HQ_SYNC_SKIP_COMPANIES;
1199
+ else process.env.HQ_SYNC_SKIP_COMPANIES = prev;
1200
+ }
1201
+ });
1202
+
1094
1203
  it("skips a ghost membership whose entity is gone so it cannot force exit 2", async () => {
1095
1204
  const sync = vi.fn().mockResolvedValue(defaultSyncResult());
1096
1205
  const deps = makeDeps({
@@ -4789,6 +4898,21 @@ describe("resolveDeletePolicy", () => {
4789
4898
  // child-process default. Both surfaces tested so a regression on either
4790
4899
  // short-circuit path surfaces here.
4791
4900
 
4901
+ describe("resolveSkipCompanies", () => {
4902
+ it("parses comma-separated slugs and drops personal", () => {
4903
+ expect([...resolveSkipCompanies("acme, zeta ,personal,")].sort()).toEqual([
4904
+ "acme",
4905
+ "zeta",
4906
+ ]);
4907
+ });
4908
+
4909
+ it("returns empty set for missing / blank", () => {
4910
+ expect(resolveSkipCompanies(undefined).size).toBe(0);
4911
+ expect(resolveSkipCompanies("").size).toBe(0);
4912
+ expect(resolveSkipCompanies(" ").size).toBe(0);
4913
+ });
4914
+ });
4915
+
4792
4916
  describe("resolveSkipPersonal", () => {
4793
4917
  let originalEnv: string | undefined;
4794
4918
 
@@ -5852,6 +5976,56 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
5852
5976
  await loop;
5853
5977
  });
5854
5978
 
5979
+ it("publishes NOTHING for HQ_SYNC_SKIP_COMPANIES paused workspace edits", async () => {
5980
+ const prev = process.env.HQ_SYNC_SKIP_COMPANIES;
5981
+ process.env.HQ_SYNC_SKIP_COMPANIES = "indigo";
5982
+ try {
5983
+ const publishBatch = vi.fn();
5984
+ const startEventSync = vi.fn().mockResolvedValue({
5985
+ publishBatch,
5986
+ receiver: {
5987
+ start: async () => {},
5988
+ dispose: async () => {},
5989
+ connected: true,
5990
+ },
5991
+ ownDeviceId: "dev-test",
5992
+ dispose: vi.fn().mockResolvedValue(undefined),
5993
+ });
5994
+ const runPass = vi.fn().mockResolvedValue(0);
5995
+ const { loop, watcher, clock, tick, shutdown } = runLoop({
5996
+ claims: ENROLLED_CLAIMS,
5997
+ startEventSync,
5998
+ runPass,
5999
+ });
6000
+ await microtasks();
6001
+ runPass.mockClear();
6002
+
6003
+ watcher.emit("companies/indigo/a.md", {
6004
+ paths: new Map([
6005
+ ["/tmp/hq/companies/indigo/a.md", "companies/indigo/a.md"],
6006
+ ]),
6007
+ });
6008
+ clock.advance(0);
6009
+ await microtasks();
6010
+ tick();
6011
+ await microtasks();
6012
+
6013
+ // Paused: no scoped push, no event-sync publish (path/hash must not leak).
6014
+ expect(
6015
+ runPass.mock.calls.filter((call) =>
6016
+ (call[0] as string[]).includes("--company"),
6017
+ ),
6018
+ ).toEqual([]);
6019
+ expect(publishBatch).not.toHaveBeenCalled();
6020
+
6021
+ shutdown();
6022
+ await loop;
6023
+ } finally {
6024
+ if (prev === undefined) delete process.env.HQ_SYNC_SKIP_COMPANIES;
6025
+ else process.env.HQ_SYNC_SKIP_COMPANIES = prev;
6026
+ }
6027
+ });
6028
+
5855
6029
  it("does not publish an exit-zero deletion that the scoped push refused", async () => {
5856
6030
  const publishBatch = vi.fn();
5857
6031
  const startEventSync = vi.fn().mockResolvedValue({
@@ -144,9 +144,12 @@ import {
144
144
  buildFanoutPlan,
145
145
  emitFanoutPlan,
146
146
  resolveMembershipsForRun,
147
+ resolveSkipCompanies,
147
148
  type RunnerTarget,
148
149
  type SetupNeededReason,
149
150
  } from "./sync-runner-planning.js";
151
+
152
+ export { resolveSkipCompanies } from "./sync-runner-planning.js";
150
153
  import {
151
154
  seedActiveCompany,
152
155
  type SeedActiveCompanyOptions,
@@ -394,6 +397,10 @@ export type RunnerEvent =
394
397
  Extract<SyncProgressEvent, { type: "skip-invalid-scoped-key" }>,
395
398
  "type"
396
399
  >)
400
+ | ({ type: "skip-junk-key-spelling"; company: string } & Omit<
401
+ Extract<SyncProgressEvent, { type: "skip-junk-key-spelling" }>,
402
+ "type"
403
+ >)
397
404
  | ({
398
405
  type: "complete";
399
406
  company: string;
@@ -1441,6 +1448,7 @@ export async function runRunner(
1441
1448
  client,
1442
1449
  claims,
1443
1450
  resolveSkipPersonal,
1451
+ skipCompanies: resolveSkipCompanies(),
1444
1452
  });
1445
1453
  } catch (err) {
1446
1454
  if (err instanceof VaultAuthError) {
@@ -1461,6 +1469,7 @@ export async function runRunner(
1461
1469
  return 0;
1462
1470
  }
1463
1471
  const plan = targetPlan.plan;
1472
+ const deferredUnresolved = targetPlan.deferredUnresolved;
1464
1473
 
1465
1474
  emitFanoutPlan(emit, plan);
1466
1475
 
@@ -1882,7 +1891,14 @@ export async function runRunner(
1882
1891
  // otherwise fire for normal user-policy outcomes. Consumers that want to
1883
1892
  // flag any non-clean outcome (clean-abort + thrown-error) read `partial`
1884
1893
  // from the JSON. The watch loop stays alive on this code (see exit-codes.ts).
1885
- return errors.length > 0 ? PARTIAL_SYNC_EXIT : 0;
1894
+ //
1895
+ // deferredUnresolved: a slug denylist was active but entity discovery blipped
1896
+ // for one or more memberships we could not prove were paused. Fail closed on
1897
+ // the plan (they were omitted) and return TRANSIENT so watch scoped drain
1898
+ // does not publish without a successful upload pass.
1899
+ if (errors.length > 0) return PARTIAL_SYNC_EXIT;
1900
+ if (deferredUnresolved > 0) return TRANSIENT_NETWORK_EXIT;
1901
+ return 0;
1886
1902
  }
1887
1903
 
1888
1904
  // ---------------------------------------------------------------------------