@indigoai-us/hq-cloud 6.15.0 → 6.15.2

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 (329) hide show
  1. package/dist/bin/sync-mutation.d.ts +16 -0
  2. package/dist/bin/sync-mutation.d.ts.map +1 -0
  3. package/dist/bin/sync-mutation.js +60 -0
  4. package/dist/bin/sync-mutation.js.map +1 -0
  5. package/dist/bin/sync-mutation.test.d.ts +2 -0
  6. package/dist/bin/sync-mutation.test.d.ts.map +1 -0
  7. package/dist/bin/sync-mutation.test.js +165 -0
  8. package/dist/bin/sync-mutation.test.js.map +1 -0
  9. package/dist/bin/sync-runner-company.d.ts +8 -0
  10. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  11. package/dist/bin/sync-runner-company.js +16 -0
  12. package/dist/bin/sync-runner-company.js.map +1 -1
  13. package/dist/bin/sync-runner-company.test.d.ts +2 -0
  14. package/dist/bin/sync-runner-company.test.d.ts.map +1 -0
  15. package/dist/bin/sync-runner-company.test.js +36 -0
  16. package/dist/bin/sync-runner-company.test.js.map +1 -0
  17. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  18. package/dist/bin/sync-runner-watch-loop.js +98 -8
  19. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  20. package/dist/bin/sync-runner.d.ts +17 -0
  21. package/dist/bin/sync-runner.d.ts.map +1 -1
  22. package/dist/bin/sync-runner.js.map +1 -1
  23. package/dist/bin/sync-runner.test.js +109 -0
  24. package/dist/bin/sync-runner.test.js.map +1 -1
  25. package/dist/cli/conflict-recovery.test.d.ts +2 -0
  26. package/dist/cli/conflict-recovery.test.d.ts.map +1 -0
  27. package/dist/cli/conflict-recovery.test.js +201 -0
  28. package/dist/cli/conflict-recovery.test.js.map +1 -0
  29. package/dist/cli/conflict.d.ts +60 -0
  30. package/dist/cli/conflict.d.ts.map +1 -1
  31. package/dist/cli/conflict.js +333 -0
  32. package/dist/cli/conflict.js.map +1 -1
  33. package/dist/cli/sync.d.ts +35 -0
  34. package/dist/cli/sync.d.ts.map +1 -1
  35. package/dist/cli/sync.js +100 -0
  36. package/dist/cli/sync.js.map +1 -1
  37. package/dist/cli/sync.test.js +85 -1
  38. package/dist/cli/sync.test.js.map +1 -1
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +1 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/skill-telemetry.d.ts +6 -0
  44. package/dist/skill-telemetry.d.ts.map +1 -1
  45. package/dist/skill-telemetry.js +14 -2
  46. package/dist/skill-telemetry.js.map +1 -1
  47. package/dist/skill-telemetry.test.js +79 -0
  48. package/dist/skill-telemetry.test.js.map +1 -1
  49. package/dist/sync/candidate-uploader.d.ts +88 -0
  50. package/dist/sync/candidate-uploader.d.ts.map +1 -0
  51. package/dist/sync/candidate-uploader.js +212 -0
  52. package/dist/sync/candidate-uploader.js.map +1 -0
  53. package/dist/sync/candidate-uploader.test.d.ts +2 -0
  54. package/dist/sync/candidate-uploader.test.d.ts.map +1 -0
  55. package/dist/sync/candidate-uploader.test.js +132 -0
  56. package/dist/sync/candidate-uploader.test.js.map +1 -0
  57. package/dist/sync/delta-client.d.ts +73 -0
  58. package/dist/sync/delta-client.d.ts.map +1 -0
  59. package/dist/sync/delta-client.js +201 -0
  60. package/dist/sync/delta-client.js.map +1 -0
  61. package/dist/sync/delta-client.test.d.ts +2 -0
  62. package/dist/sync/delta-client.test.d.ts.map +1 -0
  63. package/dist/sync/delta-client.test.js +97 -0
  64. package/dist/sync/delta-client.test.js.map +1 -0
  65. package/dist/sync/durable-apply.d.ts +76 -0
  66. package/dist/sync/durable-apply.d.ts.map +1 -0
  67. package/dist/sync/durable-apply.js +530 -0
  68. package/dist/sync/durable-apply.js.map +1 -0
  69. package/dist/sync/durable-apply.test.d.ts +2 -0
  70. package/dist/sync/durable-apply.test.d.ts.map +1 -0
  71. package/dist/sync/durable-apply.test.js +180 -0
  72. package/dist/sync/durable-apply.test.js.map +1 -0
  73. package/dist/sync/event-sync.d.ts +33 -1
  74. package/dist/sync/event-sync.d.ts.map +1 -1
  75. package/dist/sync/event-sync.js +149 -1
  76. package/dist/sync/event-sync.js.map +1 -1
  77. package/dist/sync/event-sync.test.js +142 -1
  78. package/dist/sync/event-sync.test.js.map +1 -1
  79. package/dist/sync/index.d.ts +2 -0
  80. package/dist/sync/index.d.ts.map +1 -1
  81. package/dist/sync/index.js +1 -0
  82. package/dist/sync/index.js.map +1 -1
  83. package/dist/sync/multipart-uploader.d.ts +99 -0
  84. package/dist/sync/multipart-uploader.d.ts.map +1 -0
  85. package/dist/sync/multipart-uploader.js +447 -0
  86. package/dist/sync/multipart-uploader.js.map +1 -0
  87. package/dist/sync/multipart-uploader.test.d.ts +2 -0
  88. package/dist/sync/multipart-uploader.test.d.ts.map +1 -0
  89. package/dist/sync/multipart-uploader.test.js +119 -0
  90. package/dist/sync/multipart-uploader.test.js.map +1 -0
  91. package/dist/sync/mutation-client.d.ts +85 -0
  92. package/dist/sync/mutation-client.d.ts.map +1 -0
  93. package/dist/sync/mutation-client.js +245 -0
  94. package/dist/sync/mutation-client.js.map +1 -0
  95. package/dist/sync/mutation-client.test.d.ts +2 -0
  96. package/dist/sync/mutation-client.test.d.ts.map +1 -0
  97. package/dist/sync/mutation-client.test.js +51 -0
  98. package/dist/sync/mutation-client.test.js.map +1 -0
  99. package/dist/sync/push-receiver.d.ts +45 -0
  100. package/dist/sync/push-receiver.d.ts.map +1 -1
  101. package/dist/sync/push-receiver.js +101 -0
  102. package/dist/sync/push-receiver.js.map +1 -1
  103. package/dist/sync/push-receiver.test.js +54 -2
  104. package/dist/sync/push-receiver.test.js.map +1 -1
  105. package/dist/sync/scope-inventory-client.d.ts +69 -0
  106. package/dist/sync/scope-inventory-client.d.ts.map +1 -0
  107. package/dist/sync/scope-inventory-client.js +210 -0
  108. package/dist/sync/scope-inventory-client.js.map +1 -0
  109. package/dist/sync/scope-inventory-client.test.d.ts +2 -0
  110. package/dist/sync/scope-inventory-client.test.d.ts.map +1 -0
  111. package/dist/sync/scope-inventory-client.test.js +94 -0
  112. package/dist/sync/scope-inventory-client.test.js.map +1 -0
  113. package/dist/sync/snapshot-client.d.ts +98 -0
  114. package/dist/sync/snapshot-client.d.ts.map +1 -0
  115. package/dist/sync/snapshot-client.js +402 -0
  116. package/dist/sync/snapshot-client.js.map +1 -0
  117. package/dist/sync/snapshot-client.test.d.ts +2 -0
  118. package/dist/sync/snapshot-client.test.d.ts.map +1 -0
  119. package/dist/sync/snapshot-client.test.js +169 -0
  120. package/dist/sync/snapshot-client.test.js.map +1 -0
  121. package/dist/sync/uploader-finalization.d.ts +97 -0
  122. package/dist/sync/uploader-finalization.d.ts.map +1 -0
  123. package/dist/sync/uploader-finalization.js +273 -0
  124. package/dist/sync/uploader-finalization.js.map +1 -0
  125. package/dist/sync/uploader-finalization.test.d.ts +2 -0
  126. package/dist/sync/uploader-finalization.test.d.ts.map +1 -0
  127. package/dist/sync/uploader-finalization.test.js +92 -0
  128. package/dist/sync/uploader-finalization.test.js.map +1 -0
  129. package/dist/telemetry.d.ts +11 -1
  130. package/dist/telemetry.d.ts.map +1 -1
  131. package/dist/telemetry.js +21 -2
  132. package/dist/telemetry.js.map +1 -1
  133. package/dist/telemetry.test.js +80 -0
  134. package/dist/telemetry.test.js.map +1 -1
  135. package/package.json +6 -1
  136. package/.claude/policies/hq-cloud-esm-cannot-spy-fs-builtins.md +0 -30
  137. package/.claude/policies/hq-cloud-strip-types-no-parameter-properties.md +0 -22
  138. package/.github/workflows/ci.yml +0 -84
  139. package/.github/workflows/publish.yml +0 -56
  140. package/.github/workflows/unreleased-commits-nag.yml +0 -256
  141. package/eslint.config.js +0 -67
  142. package/pnpm-workspace.yaml +0 -2
  143. package/scripts/presign-transport-e2e.mjs +0 -250
  144. package/scripts/vault-rebaseline.sh +0 -323
  145. package/scripts/vault-rescue.sh +0 -332
  146. package/src/active-company.test.ts +0 -188
  147. package/src/active-company.ts +0 -168
  148. package/src/agent-codex-instructions.test.ts +0 -332
  149. package/src/agent-codex-instructions.ts +0 -309
  150. package/src/auth.ts +0 -146
  151. package/src/backup-prune.test.ts +0 -98
  152. package/src/backup-prune.ts +0 -182
  153. package/src/bin/backup-prune-runner.ts +0 -33
  154. package/src/bin/rescue-runner.ts +0 -25
  155. package/src/bin/sync-runner-company.ts +0 -695
  156. package/src/bin/sync-runner-events.test.ts +0 -143
  157. package/src/bin/sync-runner-events.ts +0 -55
  158. package/src/bin/sync-runner-planning.test.ts +0 -311
  159. package/src/bin/sync-runner-planning.ts +0 -258
  160. package/src/bin/sync-runner-rollup.test.ts +0 -37
  161. package/src/bin/sync-runner-rollup.ts +0 -97
  162. package/src/bin/sync-runner-telemetry.ts +0 -15
  163. package/src/bin/sync-runner-watch-loop.ts +0 -1235
  164. package/src/bin/sync-runner-watch-routes.test.ts +0 -71
  165. package/src/bin/sync-runner-watch-routes.ts +0 -184
  166. package/src/bin/sync-runner.test.ts +0 -8767
  167. package/src/bin/sync-runner.ts +0 -2190
  168. package/src/cli/accept.ts +0 -124
  169. package/src/cli/conflict.ts +0 -119
  170. package/src/cli/doctor.test.ts +0 -581
  171. package/src/cli/doctor.ts +0 -642
  172. package/src/cli/index.ts +0 -49
  173. package/src/cli/invite.test.ts +0 -250
  174. package/src/cli/invite.ts +0 -214
  175. package/src/cli/promote.ts +0 -157
  176. package/src/cli/reindex-knowledge.test.ts +0 -307
  177. package/src/cli/reindex-knowledge.ts +0 -450
  178. package/src/cli/reindex.test.ts +0 -957
  179. package/src/cli/reindex.ts +0 -979
  180. package/src/cli/rescue-classify-ordering.test.ts +0 -548
  181. package/src/cli/rescue-clone-diagnostics.test.ts +0 -120
  182. package/src/cli/rescue-core.ts +0 -3011
  183. package/src/cli/rescue-drift-reconcile.test.ts +0 -179
  184. package/src/cli/rescue-drop-dir-symlink.test.ts +0 -224
  185. package/src/cli/rescue-exec-bit-preserve.test.ts +0 -187
  186. package/src/cli/rescue-hq-root-guard.test.ts +0 -232
  187. package/src/cli/rescue-journal-reconcile.test.ts +0 -215
  188. package/src/cli/rescue-mtime-preserve.test.ts +0 -203
  189. package/src/cli/rescue-settings-reconcile.test.ts +0 -637
  190. package/src/cli/rescue-snapshot.test.ts +0 -57
  191. package/src/cli/rescue-snapshot.ts +0 -51
  192. package/src/cli/rescue.reindex.test.ts +0 -63
  193. package/src/cli/rescue.test.ts +0 -131
  194. package/src/cli/rescue.ts +0 -182
  195. package/src/cli/share.test.ts +0 -7843
  196. package/src/cli/share.ts +0 -3663
  197. package/src/cli/sync-scope.test.ts +0 -652
  198. package/src/cli/sync.test.ts +0 -5207
  199. package/src/cli/sync.ts +0 -3470
  200. package/src/cli/tombstones.ts +0 -106
  201. package/src/cli/watch-event-push-conflict.test.ts +0 -234
  202. package/src/client-info.test.ts +0 -214
  203. package/src/client-info.ts +0 -121
  204. package/src/cognito-auth.test.ts +0 -712
  205. package/src/cognito-auth.ts +0 -1422
  206. package/src/company-resolver.test.ts +0 -618
  207. package/src/company-resolver.ts +0 -521
  208. package/src/context.test.ts +0 -583
  209. package/src/context.ts +0 -378
  210. package/src/daemon-worker.ts +0 -26
  211. package/src/daemon.ts +0 -99
  212. package/src/entity-resolver.test.ts +0 -315
  213. package/src/entity-resolver.ts +0 -180
  214. package/src/ignore.test.ts +0 -466
  215. package/src/ignore.ts +0 -469
  216. package/src/index.ts +0 -439
  217. package/src/journal.test.ts +0 -968
  218. package/src/journal.ts +0 -765
  219. package/src/lib/cloud-authoritative.test.ts +0 -45
  220. package/src/lib/cloud-authoritative.ts +0 -59
  221. package/src/lib/conflict-file.ts +0 -86
  222. package/src/lib/conflict-index.ts +0 -289
  223. package/src/lib/conflict.test.ts +0 -348
  224. package/src/lib/describe-error.test.ts +0 -100
  225. package/src/lib/describe-error.ts +0 -58
  226. package/src/lib/exit-codes.ts +0 -24
  227. package/src/lib/machine-id.test.ts +0 -231
  228. package/src/lib/machine-id.ts +0 -175
  229. package/src/lib/net-errors.test.ts +0 -65
  230. package/src/lib/net-errors.ts +0 -86
  231. package/src/lib/readlink-safe.test.ts +0 -43
  232. package/src/lib/readlink-safe.ts +0 -29
  233. package/src/local-path-codec.test.ts +0 -138
  234. package/src/local-path-codec.ts +0 -161
  235. package/src/machine-auth.test.ts +0 -1323
  236. package/src/manifest-reconcile.test.ts +0 -1123
  237. package/src/manifest-reconcile.ts +0 -518
  238. package/src/object-io.test.ts +0 -1221
  239. package/src/object-io.ts +0 -1306
  240. package/src/operation-lock.test.ts +0 -484
  241. package/src/operation-lock.ts +0 -680
  242. package/src/outcome-telemetry.test.ts +0 -498
  243. package/src/outcome-telemetry.ts +0 -639
  244. package/src/personal-vault-exclusions.test.ts +0 -308
  245. package/src/personal-vault-exclusions.ts +0 -354
  246. package/src/personal-vault.test.ts +0 -756
  247. package/src/personal-vault.ts +0 -496
  248. package/src/prefix-coalesce.test.ts +0 -240
  249. package/src/prefix-coalesce.ts +0 -273
  250. package/src/public-surface.test.ts +0 -117
  251. package/src/qmd-reindex.test.ts +0 -877
  252. package/src/qmd-reindex.ts +0 -842
  253. package/src/read-only-state-dir.test.ts +0 -188
  254. package/src/remote-pull.test.ts +0 -1130
  255. package/src/remote-pull.ts +0 -618
  256. package/src/s3.symlink-materialize.test.ts +0 -492
  257. package/src/s3.test.ts +0 -1789
  258. package/src/s3.ts +0 -1532
  259. package/src/schemas/signal-types.test.ts +0 -82
  260. package/src/schemas/signal-types.ts +0 -38
  261. package/src/schemas/source-channels.test.ts +0 -82
  262. package/src/schemas/source-channels.ts +0 -53
  263. package/src/scope-shrink.test.ts +0 -633
  264. package/src/scope-shrink.ts +0 -481
  265. package/src/signals/get.test.ts +0 -310
  266. package/src/signals/get.ts +0 -75
  267. package/src/signals/internals.ts +0 -195
  268. package/src/signals/list.test.ts +0 -420
  269. package/src/signals/list.ts +0 -79
  270. package/src/signals/parse.ts +0 -8
  271. package/src/signals/types.ts +0 -91
  272. package/src/skill-telemetry.test.ts +0 -1825
  273. package/src/skill-telemetry.ts +0 -1439
  274. package/src/sources/get.test.ts +0 -293
  275. package/src/sources/get.ts +0 -66
  276. package/src/sources/internals.ts +0 -198
  277. package/src/sources/list.test.ts +0 -402
  278. package/src/sources/list.ts +0 -84
  279. package/src/sources/parse.ts +0 -43
  280. package/src/sources/types.ts +0 -84
  281. package/src/sync/event-sync.test.ts +0 -594
  282. package/src/sync/event-sync.ts +0 -545
  283. package/src/sync/feature-flags.test.ts +0 -378
  284. package/src/sync/feature-flags.ts +0 -62
  285. package/src/sync/index.ts +0 -76
  286. package/src/sync/lease-client.test.ts +0 -128
  287. package/src/sync/lease-client.ts +0 -207
  288. package/src/sync/logger.test.ts +0 -242
  289. package/src/sync/logger.ts +0 -79
  290. package/src/sync/metrics.test.ts +0 -462
  291. package/src/sync/metrics.ts +0 -213
  292. package/src/sync/pull-scope.ts +0 -265
  293. package/src/sync/push-event.test.ts +0 -266
  294. package/src/sync/push-event.ts +0 -224
  295. package/src/sync/push-receiver.test.ts +0 -566
  296. package/src/sync/push-receiver.ts +0 -1048
  297. package/src/sync/push-transport.ts +0 -231
  298. package/src/sync/realtime-rollout.test.ts +0 -86
  299. package/src/sync/realtime-rollout.ts +0 -262
  300. package/src/sync/state-store.test.ts +0 -194
  301. package/src/sync/state-store.ts +0 -727
  302. package/src/sync-core.ts +0 -58
  303. package/src/sync-progress.test.ts +0 -94
  304. package/src/sync-progress.ts +0 -140
  305. package/src/telemetry-events.test.ts +0 -88
  306. package/src/telemetry-events.ts +0 -205
  307. package/src/telemetry.test.ts +0 -1280
  308. package/src/telemetry.ts +0 -1109
  309. package/src/types.ts +0 -314
  310. package/src/vault-client.test.ts +0 -1380
  311. package/src/vault-client.ts +0 -1694
  312. package/src/version.ts +0 -24
  313. package/src/watch-roots.test.ts +0 -278
  314. package/src/watch-roots.ts +0 -162
  315. package/src/watcher-event-gate.test.ts +0 -212
  316. package/src/watcher.test.ts +0 -1079
  317. package/src/watcher.ts +0 -1741
  318. package/test/e2e/sync/cross-tenant-isolation.test.ts +0 -630
  319. package/test/e2e/sync/skill-telemetry-oversized-transcript.test.ts +0 -124
  320. package/test/e2e/sync/transient-company-leg.test.ts +0 -384
  321. package/test/e2e/sync/windows-unreadable-link-leg.test.ts +0 -191
  322. package/test/e2e/watcher-real-chokidar.test.ts +0 -165
  323. package/test/e2e/watcher-recursive-backend.test.ts +0 -181
  324. package/test/e2e/watcher-scoped-coverage.test.ts +0 -381
  325. package/test/invite-flow.integration.test.ts +0 -244
  326. package/test/joiner-manifest-reconcile.integration.test.ts +0 -322
  327. package/test/share-sync.integration.test.ts +0 -213
  328. package/tsconfig.json +0 -19
  329. package/vitest.config.ts +0 -22
@@ -1,695 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import {
4
- computePersonalVaultPaths,
5
- readCloudBackedSlugsFromManifest,
6
- } from "../personal-vault.js";
7
- import type { SyncOptions, SyncProgressEvent, SyncResult } from "../cli/sync.js";
8
- import type { ShareOptions, ShareResult } from "../cli/share.js";
9
- import type { ConflictStrategy } from "../cli/conflict.js";
10
- import type { UploadAuthor } from "../s3.js";
11
- import type { VaultServiceConfig } from "../index.js";
12
- import { resolvePullScope, type PullScope } from "../sync/pull-scope.js";
13
- import {
14
- emitCloudTelemetry,
15
- type CloudTelemetryClient,
16
- type TelemetryClaims,
17
- } from "../telemetry-events.js";
18
- import {
19
- coalescePrefixes,
20
- pathToScopePrefix,
21
- toScopePrefixEntries,
22
- type ScopePrefixEntry,
23
- type ScopePrefixInput,
24
- } from "../prefix-coalesce.js";
25
- import { describeError } from "../lib/describe-error.js";
26
- import { isTransientNetworkError } from "../lib/net-errors.js";
27
- import { VaultAuthError } from "../vault-client.js";
28
- import type {
29
- DeletePropagationPolicy,
30
- Direction,
31
- RunnerEvent,
32
- VaultClientSurface,
33
- } from "./sync-runner.js";
34
- import type { RunnerTarget } from "./sync-runner-planning.js";
35
- import {
36
- createCompanyState,
37
- type CompanyState,
38
- } from "./sync-runner-rollup.js";
39
-
40
- export interface CompanyFanoutResult {
41
- stateByCompany: Map<string, CompanyState>;
42
- errors: Array<{ company: string; message: string }>;
43
- companyFailures: Array<{ company: string; message: string }>;
44
- transientCompanies: Array<{ company: string; message: string }>;
45
- meaningfulOutcomeCount: number;
46
- allConflicts: Array<{
47
- company: string;
48
- path: string;
49
- direction: "pull" | "push";
50
- }>;
51
- pushPathResults: Array<{
52
- relativePath: string;
53
- status: "accepted" | "refused";
54
- operation: "delete" | "tombstone";
55
- reason?: string;
56
- }>;
57
- }
58
-
59
- export async function executeCompanyFanout(options: {
60
- plan: RunnerTarget[];
61
- direction: Direction;
62
- hqRoot: string;
63
- onConflict: ConflictStrategy;
64
- client: VaultClientSurface;
65
- vaultConfig: VaultServiceConfig;
66
- uploadAuthor?: UploadAuthor;
67
- operationLockAlreadyHeld?: boolean;
68
- syncFn: (options: SyncOptions) => Promise<SyncResult>;
69
- shareFn: (options: ShareOptions) => Promise<ShareResult>;
70
- resolveDeletePolicy: () => DeletePropagationPolicy;
71
- emit: (event: RunnerEvent) => void;
72
- scopePaths?: string[];
73
- deleteScopePaths?: string[];
74
- telemetryClient?: CloudTelemetryClient | null;
75
- telemetryClaims?: TelemetryClaims | null;
76
- telemetrySessionId?: string;
77
- /**
78
- * Ensure a company's local directory exists (recursive mkdir). Injected so
79
- * tests don't touch a developer's filesystem. Defaults to `fs.mkdirSync(dir,
80
- * { recursive: true })`. Called after a completed non-personal pull leg so a
81
- * company a member can SEE is materialized locally even when zero files were
82
- * shared with them (see the ensure block below).
83
- */
84
- ensureCompanyDir?: (dir: string) => void;
85
- }): Promise<CompanyFanoutResult> {
86
- const ensureCompanyDir =
87
- options.ensureCompanyDir ??
88
- ((dir: string) => {
89
- fs.mkdirSync(dir, { recursive: true });
90
- });
91
- const doPush =
92
- options.direction === "push" || options.direction === "both";
93
- const doPull =
94
- options.direction === "pull" || options.direction === "both";
95
- const errors: Array<{ company: string; message: string }> = [];
96
- const companyFailures: Array<{ company: string; message: string }> = [];
97
- const transientCompanies: Array<{ company: string; message: string }> = [];
98
- const allConflicts: Array<{
99
- company: string;
100
- path: string;
101
- direction: "pull" | "push";
102
- }> = [];
103
- const stateByCompany = new Map<string, CompanyState>();
104
- let meaningfulOutcomeCount = 0;
105
- const pushPathResults: CompanyFanoutResult["pushPathResults"] = [];
106
-
107
- for (const target of options.plan) {
108
- const companyLabel = target.slug;
109
- const state = createCompanyState(companyLabel);
110
- stateByCompany.set(companyLabel, state);
111
-
112
- let activePhase: "pull" | "push" = doPush && !doPull ? "push" : "pull";
113
- let companyHadTransferError = false;
114
- const tagAndEmit = (event: SyncProgressEvent): void => {
115
- if (event.type === "plan") {
116
- options.emit({
117
- type: "plan",
118
- company: companyLabel,
119
- filesToDownload: event.filesToDownload,
120
- bytesToDownload: event.bytesToDownload,
121
- filesToUpload: event.filesToUpload,
122
- bytesToUpload: event.bytesToUpload,
123
- filesToSkip: event.filesToSkip,
124
- filesToConflict: event.filesToConflict,
125
- filesToDelete: event.filesToDelete,
126
- });
127
- } else if (event.type === "progress") {
128
- if (activePhase === "push") {
129
- state.filesUploaded += 1;
130
- state.bytesUploaded += event.bytes;
131
- } else {
132
- state.filesDownloaded += 1;
133
- state.bytesDownloaded += event.bytes;
134
- }
135
- options.emit({
136
- type: "progress",
137
- company: companyLabel,
138
- path: event.path,
139
- bytes: event.bytes,
140
- direction: activePhase === "push" ? "up" : "down",
141
- ...(event.message ? { message: event.message } : {}),
142
- ...(event.deleted ? { deleted: event.deleted } : {}),
143
- });
144
- } else if (event.type === "conflict") {
145
- options.emit({
146
- type: "conflict",
147
- company: companyLabel,
148
- path: event.path,
149
- direction: event.direction,
150
- resolution: event.resolution,
151
- });
152
- } else if (event.type === "error") {
153
- companyHadTransferError = true;
154
- errors.push({
155
- company: companyLabel,
156
- message: event.path
157
- ? `${event.path}: ${event.message}`
158
- : event.message,
159
- });
160
- options.emit({
161
- type: "error",
162
- company: companyLabel,
163
- path: event.path,
164
- message: event.message,
165
- });
166
- } else if (event.type === "new-files") {
167
- options.emit({
168
- type: "new-files",
169
- company: companyLabel,
170
- files: event.files,
171
- });
172
- } else if (event.type === "scope-excluded") {
173
- options.emit({
174
- type: "scope-excluded",
175
- company: companyLabel,
176
- count: event.count,
177
- samplePaths: event.samplePaths,
178
- });
179
- } else if (event.type === "scope-materialization-gap") {
180
- options.emit({
181
- type: "scope-materialization-gap",
182
- company: companyLabel,
183
- count: event.count,
184
- samplePaths: event.samplePaths,
185
- syncMode: event.syncMode,
186
- });
187
- } else if (event.type === "ignore-excluded") {
188
- options.emit({
189
- type: "ignore-excluded",
190
- company: companyLabel,
191
- count: event.count,
192
- totalExcluded: event.totalExcluded,
193
- samplePaths: event.samplePaths,
194
- });
195
- } else if (event.type === "skip-invalid-scoped-key") {
196
- options.emit({
197
- type: "skip-invalid-scoped-key",
198
- company: companyLabel,
199
- path: event.path,
200
- ...(event.errorCode ? { errorCode: event.errorCode } : {}),
201
- });
202
- } else if (event.type === "skip-junk-key-spelling") {
203
- options.emit({
204
- type: "skip-junk-key-spelling",
205
- company: companyLabel,
206
- path: event.path,
207
- journaledKey: event.journaledKey,
208
- });
209
- } else if (event.type === "not-shipped") {
210
- options.emit({
211
- type: "not-shipped",
212
- company: companyLabel,
213
- reason: event.reason,
214
- count: event.count,
215
- samplePaths: event.samplePaths,
216
- });
217
- }
218
- };
219
-
220
- try {
221
- let pushResult: ShareResult = {
222
- filesUploaded: 0,
223
- bytesUploaded: 0,
224
- filesSkipped: 0,
225
- filesDeleted: 0,
226
- filesTombstoned: 0,
227
- filesRefusedStale: 0,
228
- filesRefusedStalePaths: [],
229
- filesSuppressedByTombstone: 0,
230
- filesExcludedByPolicy: 0,
231
- filesExcludedByScope: 0,
232
- filesExcludedByIgnore: 0,
233
- unreachablePaths: [],
234
- linkedSubtreesNotShipped: [],
235
- conflictPaths: [],
236
- aborted: false,
237
- };
238
- let pullResult: SyncResult = {
239
- filesDownloaded: 0,
240
- bytesDownloaded: 0,
241
- filesSkipped: 0,
242
- conflicts: 0,
243
- conflictPaths: [],
244
- aborted: false,
245
- newFiles: [],
246
- newFilesCount: 0,
247
- filesExcludedByPolicy: 0,
248
- filesTombstoned: 0,
249
- filesOutOfScope: 0,
250
- scopeOrphansRemoved: 0,
251
- scopeOrphansBlocked: 0,
252
- changedPaths: [],
253
- };
254
-
255
- // Local (non-cloud) companies always sync to the personal vault.
256
- // Cloud-backed signals (union):
257
- // 1. Membership fanout: every non-personal target in the run plan
258
- // is a company the operator has an active Membership for.
259
- // 2. companies/manifest.yaml cloud_uid: full-HQ agent boxes often
260
- // run `--personal` alone (no company fanout), so (1) is empty
261
- // even when indigo is cloud-backed. Manifest cloud_uid is the
262
- // durable routing signal — exclude + decommission those slugs
263
- // so multi-GB team trees never re-enter the agent personal vault.
264
- // Those slugs are excluded from the personal push (no double-
265
- // bucketing) and decommissioned from the personal vault if a stale
266
- // pre-promotion copy lingers — this is how a company "becoming
267
- // cloud-true" is handled.
268
- const teamSyncedSlugs = new Set(
269
- options.plan
270
- .filter((p) => p.personalMode !== true)
271
- .map((p) => p.slug),
272
- );
273
- if (target.personalMode === true) {
274
- for (const slug of readCloudBackedSlugsFromManifest(options.hqRoot)) {
275
- teamSyncedSlugs.add(slug);
276
- }
277
- }
278
-
279
- const scope: PullScope =
280
- target.personalMode === true
281
- ? // Personal vault: single-owner, no multi-tenant sessions ACL problem,
282
- // so preserve its existing full-pull behavior — no session exclusion.
283
- // (The sessions push-only contract is a company-vault concern; US-006.)
284
- { syncMode: "all", excludePrefixes: [] }
285
- : await resolvePullScope(
286
- options.client,
287
- target.uid,
288
- target.slug,
289
- options.hqRoot,
290
- );
291
-
292
- if (doPush) {
293
- activePhase = "push";
294
- const pushPrefixSet = effectivePushPrefixSet(
295
- scope.prefixSet,
296
- options.scopePaths ?? [],
297
- );
298
- const pushPaths =
299
- target.personalMode === true
300
- ? computePersonalVaultPaths(options.hqRoot, { teamSyncedSlugs })
301
- : [path.join(options.hqRoot, "companies", target.slug)];
302
- const decommissionPrefixes =
303
- target.personalMode === true
304
- ? Array.from(teamSyncedSlugs).map((slug) => `companies/${slug}`)
305
- : undefined;
306
- pushResult = await options.shareFn({
307
- paths: pushPaths,
308
- company: target.uid,
309
- vaultConfig: options.vaultConfig,
310
- hqRoot: options.hqRoot,
311
- onConflict: options.onConflict,
312
- skipUnchanged: true,
313
- propagateDeletes: true,
314
- propagateDeletePolicy: options.resolveDeletePolicy(),
315
- // `pushPaths` are INTERNAL walk roots (the company folder / the
316
- // personal-vault dirs), never user input, so an unreachable entry
317
- // here is a benign race — a directory removed between the scan and
318
- // the push — and must not fail an unattended background sync. Record
319
- // + report it (`not-shipped` event, `ShareResult.unreachablePaths`)
320
- // and carry on. The interactive `hq sync push <path>` keeps the
321
- // default "error" policy, which is where a user-typed path that
322
- // silently ships nothing is a real bug (feedback_a51cb63d ask #2).
323
- unreachablePathPolicy: "warn",
324
- onEvent: tagAndEmit,
325
- ...(options.uploadAuthor ? { author: options.uploadAuthor } : {}),
326
- ...(target.personalMode !== undefined
327
- ? { personalMode: target.personalMode }
328
- : {}),
329
- ...(target.journalSlug !== undefined
330
- ? { journalSlug: target.journalSlug }
331
- : {}),
332
- ...(decommissionPrefixes && decommissionPrefixes.length > 0
333
- ? { decommissionPrefixes }
334
- : {}),
335
- ...(pushPrefixSet !== undefined
336
- ? { prefixSet: pushPrefixSet }
337
- : {}),
338
- ...(options.deleteScopePaths?.length
339
- ? { deleteScopeRoots: options.deleteScopePaths }
340
- : {}),
341
- });
342
- for (const result of pushResult.pathResults ?? []) {
343
- pushPathResults.push({
344
- relativePath:
345
- target.personalMode === true
346
- ? result.path
347
- : `companies/${target.slug}/${result.path}`,
348
- status: result.status,
349
- operation: result.operation,
350
- ...(result.reason ? { reason: result.reason } : {}),
351
- });
352
- }
353
- }
354
-
355
- if (doPull && !pushResult.aborted) {
356
- activePhase = "pull";
357
- const pullScope: PullScope = scope;
358
- pullResult = await options.syncFn({
359
- company: target.uid,
360
- vaultConfig: options.vaultConfig,
361
- hqRoot: options.hqRoot,
362
- onConflict: options.onConflict,
363
- syncMode: pullScope.syncMode,
364
- scopeShrinkPolicy: "auto-recover",
365
- ...(options.uploadAuthor?.userSub !== undefined
366
- ? { callerSub: options.uploadAuthor.userSub }
367
- : {}),
368
- ...(options.telemetryClaims ? { telemetryClaims: options.telemetryClaims } : {}),
369
- ...(pullScope.prefixSet !== undefined
370
- ? { prefixSet: pullScope.prefixSet }
371
- : {}),
372
- // Push-only exclusions (US-006: sessions/). Subtracted from the pull's
373
- // download filter in EVERY mode — including 'all' — but NOT fed into
374
- // scope-shrink, so locally-authored / pinned sessions are never
375
- // orphaned. Guarded like prefixSet: only threaded when non-empty
376
- // (personal-vault legs carry [] and keep their full-pull behavior).
377
- ...(pullScope.excludePrefixes.length > 0
378
- ? { excludePrefixes: pullScope.excludePrefixes }
379
- : {}),
380
- ...(target.personalMode !== undefined
381
- ? { personalMode: target.personalMode }
382
- : {}),
383
- ...(target.journalSlug !== undefined
384
- ? { journalSlug: target.journalSlug }
385
- : {}),
386
- ...(target.personalMode === true ? { teamSyncedSlugs } : {}),
387
- ...(options.operationLockAlreadyHeld
388
- ? { operationLockAlreadyHeld: true }
389
- : {}),
390
- skipReindex: true,
391
- onEvent: tagAndEmit,
392
- });
393
- }
394
-
395
- const seenConflictPaths = new Set<string>();
396
- const mergedConflictPaths: string[] = [];
397
- for (const p of [
398
- ...pullResult.conflictPaths,
399
- ...pushResult.conflictPaths,
400
- ]) {
401
- if (seenConflictPaths.has(p)) continue;
402
- seenConflictPaths.add(p);
403
- mergedConflictPaths.push(p);
404
- }
405
- const aborted = pullResult.aborted || pushResult.aborted;
406
-
407
- state.filesDownloaded = pullResult.filesDownloaded;
408
- state.bytesDownloaded = pullResult.bytesDownloaded;
409
- state.filesTombstoned = pullResult.filesTombstoned;
410
- state.scopeOrphansRemoved = pullResult.scopeOrphansRemoved;
411
- state.changedPaths = normalizePullChangedPaths(
412
- target,
413
- pullResult.changedPaths,
414
- );
415
- state.filesUploaded = pushResult.filesUploaded;
416
- state.bytesUploaded = pushResult.bytesUploaded;
417
- state.status = companyHadTransferError
418
- ? "errored"
419
- : aborted
420
- ? "aborted"
421
- : "complete";
422
-
423
- // Materialize the company directory even when the pull downloaded zero
424
- // files. A member who can SEE a company should have it appear locally —
425
- // `companies/<slug>/` on disk plus a manifest entry — whether or not
426
- // anything has been shared with them yet. Without this, a company with no
427
- // shared files (e.g. an agency-created client account, or a member on
428
- // shared-mode with no grants) never gets a local directory, so the
429
- // post-fanout reconciler (which gates on the directory existing) skips it
430
- // and HQ "doesn't know about" a company the person is plainly a member
431
- // of. Only on a clean pull leg for a real company; best-effort, mirroring
432
- // the reconciler's own non-fatal posture — a mkdir failure just leaves
433
- // the slug unmaterialized exactly as before.
434
- if (
435
- doPull &&
436
- target.personalMode !== true &&
437
- state.status === "complete"
438
- ) {
439
- try {
440
- ensureCompanyDir(
441
- path.join(options.hqRoot, "companies", target.slug),
442
- );
443
- } catch (err) {
444
- // Best-effort: a mkdir failure just leaves this slug unmaterialized,
445
- // exactly as before this change. Never fail the (successful) pull
446
- // over it — but never swallow it silently either.
447
- console.warn(
448
- "[sync-runner-company] ensure company directory failed (non-fatal)",
449
- {
450
- slug: target.slug,
451
- err: err instanceof Error ? err.message : String(err),
452
- },
453
- );
454
- }
455
- }
456
-
457
- options.emit({
458
- type: "complete",
459
- company: companyLabel,
460
- filesDownloaded: pullResult.filesDownloaded,
461
- bytesDownloaded: pullResult.bytesDownloaded,
462
- filesUploaded: pushResult.filesUploaded,
463
- bytesUploaded: pushResult.bytesUploaded,
464
- filesSkipped: pullResult.filesSkipped + pushResult.filesSkipped,
465
- filesTombstoned:
466
- pushResult.filesTombstoned + pullResult.filesTombstoned,
467
- filesRefusedStale: pushResult.filesRefusedStale,
468
- filesRefusedStalePaths: pushResult.filesRefusedStalePaths,
469
- filesExcludedByPolicy:
470
- pushResult.filesExcludedByPolicy +
471
- pullResult.filesExcludedByPolicy,
472
- conflicts: mergedConflictPaths.length,
473
- conflictPaths: mergedConflictPaths,
474
- aborted,
475
- newFiles: pullResult.newFiles,
476
- newFilesCount: pullResult.newFilesCount,
477
- filesOutOfScope: pullResult.filesOutOfScope,
478
- scopeOrphansRemoved: pullResult.scopeOrphansRemoved,
479
- scopeOrphansBlocked: pullResult.scopeOrphansBlocked,
480
- });
481
- const tombstonedCount =
482
- pushResult.filesTombstoned + pullResult.filesTombstoned;
483
- const hasMeaningfulOutcome =
484
- state.status !== "complete" ||
485
- pullResult.filesDownloaded > 0 ||
486
- pushResult.filesUploaded > 0 ||
487
- pushResult.filesDeleted > 0 ||
488
- tombstonedCount > 0 ||
489
- pullResult.scopeOrphansRemoved > 0 ||
490
- mergedConflictPaths.length > 0 ||
491
- pushResult.filesRefusedStale > 0 ||
492
- pullResult.scopeOrphansBlocked > 0;
493
- if (hasMeaningfulOutcome) {
494
- meaningfulOutcomeCount += 1;
495
- void emitCloudTelemetry(
496
- options.telemetryClient,
497
- {
498
- eventName: "company_sync_leg_completed",
499
- source: "sync-runner",
500
- ...(target.personalMode !== true ? { companyUid: target.uid } : {}),
501
- ...(options.telemetrySessionId
502
- ? { sessionId: options.telemetrySessionId }
503
- : {}),
504
- properties: {
505
- direction: options.direction,
506
- status: state.status,
507
- filesDownloaded: pullResult.filesDownloaded,
508
- bytesDownloaded: pullResult.bytesDownloaded,
509
- filesUploaded: pushResult.filesUploaded,
510
- bytesUploaded: pushResult.bytesUploaded,
511
- deletedCount: pushResult.filesDeleted,
512
- tombstonedCount,
513
- scopeOrphansRemovedCount: pullResult.scopeOrphansRemoved,
514
- refusedStaleCount: pushResult.filesRefusedStale,
515
- scopeOrphansBlockedCount: pullResult.scopeOrphansBlocked,
516
- conflictCount: mergedConflictPaths.length,
517
- newFilesCount: pullResult.newFilesCount,
518
- },
519
- },
520
- { claims: options.telemetryClaims },
521
- );
522
- }
523
- for (const p of pullResult.conflictPaths) {
524
- allConflicts.push({
525
- company: companyLabel,
526
- path: p,
527
- direction: "pull",
528
- });
529
- }
530
- for (const p of pushResult.conflictPaths) {
531
- allConflicts.push({
532
- company: companyLabel,
533
- path: p,
534
- direction: "push",
535
- });
536
- }
537
- } catch (err) {
538
- // Auth is a run-level terminal condition, not a per-company transfer
539
- // error. Stop the fanout immediately so the caller can clear the stale
540
- // session and emit one auth-error event for manual and watch consumers.
541
- if (err instanceof VaultAuthError) throw err;
542
- const message = describeError(err);
543
- const transientNetwork = isTransientNetworkError(err);
544
- // A prior path-level error is deterministic and must continue to win in
545
- // the all-complete status and telemetry even if the leg later encounters
546
- // a retryable transport failure.
547
- const transientOnly = transientNetwork && !companyHadTransferError;
548
- if (transientNetwork) {
549
- transientCompanies.push({ company: companyLabel, message });
550
- if (transientOnly) state.status = "transient-network";
551
- } else {
552
- errors.push({ company: companyLabel, message });
553
- companyFailures.push({ company: companyLabel, message });
554
- }
555
- meaningfulOutcomeCount += 1;
556
- options.emit({
557
- type: "complete",
558
- company: companyLabel,
559
- filesDownloaded: state.filesDownloaded,
560
- bytesDownloaded: state.bytesDownloaded,
561
- filesUploaded: state.filesUploaded,
562
- bytesUploaded: state.bytesUploaded,
563
- filesSkipped: 0,
564
- filesTombstoned: 0,
565
- filesRefusedStale: 0,
566
- filesRefusedStalePaths: [],
567
- filesExcludedByPolicy: 0,
568
- conflicts: 0,
569
- conflictPaths: [],
570
- aborted: true,
571
- newFiles: [],
572
- newFilesCount: 0,
573
- filesOutOfScope: 0,
574
- scopeOrphansRemoved: 0,
575
- scopeOrphansBlocked: 0,
576
- changedPaths: [],
577
- });
578
- if (transientNetwork) {
579
- // This is deliberately not an error event: a retryable transport blip
580
- // must remain visible without being miscounted as a deterministic
581
- // company failure (and therefore an exit-2/Sentry error). The complete
582
- // event above still reports aborted:true and the pass exits 75.
583
- options.emit({
584
- type: "transient-network",
585
- company: companyLabel,
586
- path: "(company)",
587
- message,
588
- });
589
- } else {
590
- options.emit({
591
- type: "error",
592
- company: companyLabel,
593
- path: "(company)",
594
- message,
595
- });
596
- }
597
- void emitCloudTelemetry(
598
- options.telemetryClient,
599
- {
600
- eventName: "company_sync_leg_completed",
601
- source: "sync-runner",
602
- ...(target.personalMode !== true ? { companyUid: target.uid } : {}),
603
- ...(options.telemetrySessionId
604
- ? { sessionId: options.telemetrySessionId }
605
- : {}),
606
- properties: {
607
- direction: options.direction,
608
- status: transientOnly ? "transient-network" : "errored",
609
- filesDownloaded: state.filesDownloaded,
610
- bytesDownloaded: state.bytesDownloaded,
611
- filesUploaded: state.filesUploaded,
612
- bytesUploaded: state.bytesUploaded,
613
- deletedCount: 0,
614
- tombstonedCount: 0,
615
- scopeOrphansRemovedCount: 0,
616
- refusedStaleCount: 0,
617
- scopeOrphansBlockedCount: 0,
618
- conflictCount: 0,
619
- newFilesCount: 0,
620
- },
621
- },
622
- { claims: options.telemetryClaims },
623
- );
624
- }
625
- }
626
-
627
- return {
628
- stateByCompany,
629
- errors,
630
- companyFailures,
631
- transientCompanies,
632
- meaningfulOutcomeCount,
633
- allConflicts,
634
- pushPathResults,
635
- };
636
- }
637
-
638
- function effectivePushPrefixSet(
639
- aclPrefixSet: readonly ScopePrefixInput[] | undefined,
640
- scopePaths: readonly string[],
641
- ): string[] | undefined {
642
- if (scopePaths.length === 0) {
643
- return aclPrefixSet === undefined
644
- ? undefined
645
- : coalescePrefixes(aclPrefixSet);
646
- }
647
-
648
- const requested = scopePaths.flatMap((p) => [
649
- pathToScopePrefix(p),
650
- pathToScopePrefix(p.endsWith("/") ? p : p + "/"),
651
- ]);
652
- if (aclPrefixSet === undefined) {
653
- return coalescePrefixes(requested);
654
- }
655
-
656
- const aclEntries = toScopePrefixEntries(aclPrefixSet);
657
- const requestedEntries = toScopePrefixEntries(requested);
658
- const intersection: ScopePrefixEntry[] = [];
659
- for (const requestedEntry of requestedEntries) {
660
- for (const aclEntry of aclEntries) {
661
- if (entryCoversEntry(aclEntry, requestedEntry)) {
662
- intersection.push(requestedEntry);
663
- } else if (entryCoversEntry(requestedEntry, aclEntry)) {
664
- intersection.push(aclEntry);
665
- }
666
- }
667
- }
668
- return coalescePrefixes(intersection);
669
- }
670
-
671
- function entryCoversEntry(
672
- entry: ScopePrefixEntry,
673
- candidate: ScopePrefixEntry,
674
- ): boolean {
675
- if (entry.prefix === "") return true;
676
- if (entry.match === "prefix") {
677
- return (
678
- candidate.prefix === entry.prefix ||
679
- candidate.prefix.startsWith(entry.prefix)
680
- );
681
- }
682
- return candidate.match === "exact" && candidate.prefix === entry.prefix;
683
- }
684
-
685
- function normalizePullChangedPaths(
686
- target: RunnerTarget,
687
- changedPaths: string[] | undefined,
688
- ): string[] {
689
- const paths = changedPaths ?? [];
690
- return paths.map((p) => {
691
- const normalized = p.split(path.sep).join("/");
692
- if (target.personalMode === true) return normalized;
693
- return `companies/${target.slug}/${normalized}`;
694
- });
695
- }