@indigoai-us/hq-cloud 6.14.50 → 6.15.1

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 (340) hide show
  1. package/dist/bin/sync-mutation.d.ts +14 -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 +64 -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/reindex-knowledge.d.ts +7 -0
  34. package/dist/cli/reindex-knowledge.d.ts.map +1 -0
  35. package/dist/cli/reindex-knowledge.js +389 -0
  36. package/dist/cli/reindex-knowledge.js.map +1 -0
  37. package/dist/cli/reindex-knowledge.test.d.ts +12 -0
  38. package/dist/cli/reindex-knowledge.test.d.ts.map +1 -0
  39. package/dist/cli/reindex-knowledge.test.js +260 -0
  40. package/dist/cli/reindex-knowledge.test.js.map +1 -0
  41. package/dist/cli/reindex.d.ts.map +1 -1
  42. package/dist/cli/reindex.js +8 -0
  43. package/dist/cli/reindex.js.map +1 -1
  44. package/dist/cli/rescue-settings-reconcile.test.js +47 -0
  45. package/dist/cli/rescue-settings-reconcile.test.js.map +1 -1
  46. package/dist/cli/sync.d.ts +27 -0
  47. package/dist/cli/sync.d.ts.map +1 -1
  48. package/dist/cli/sync.js +52 -0
  49. package/dist/cli/sync.js.map +1 -1
  50. package/dist/cli/sync.test.js +31 -1
  51. package/dist/cli/sync.test.js.map +1 -1
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +1 -0
  55. package/dist/index.js.map +1 -1
  56. package/dist/skill-telemetry.d.ts +6 -0
  57. package/dist/skill-telemetry.d.ts.map +1 -1
  58. package/dist/skill-telemetry.js +14 -2
  59. package/dist/skill-telemetry.js.map +1 -1
  60. package/dist/skill-telemetry.test.js +79 -0
  61. package/dist/skill-telemetry.test.js.map +1 -1
  62. package/dist/sync/candidate-uploader.d.ts +88 -0
  63. package/dist/sync/candidate-uploader.d.ts.map +1 -0
  64. package/dist/sync/candidate-uploader.js +212 -0
  65. package/dist/sync/candidate-uploader.js.map +1 -0
  66. package/dist/sync/candidate-uploader.test.d.ts +2 -0
  67. package/dist/sync/candidate-uploader.test.d.ts.map +1 -0
  68. package/dist/sync/candidate-uploader.test.js +132 -0
  69. package/dist/sync/candidate-uploader.test.js.map +1 -0
  70. package/dist/sync/delta-client.d.ts +73 -0
  71. package/dist/sync/delta-client.d.ts.map +1 -0
  72. package/dist/sync/delta-client.js +201 -0
  73. package/dist/sync/delta-client.js.map +1 -0
  74. package/dist/sync/delta-client.test.d.ts +2 -0
  75. package/dist/sync/delta-client.test.d.ts.map +1 -0
  76. package/dist/sync/delta-client.test.js +97 -0
  77. package/dist/sync/delta-client.test.js.map +1 -0
  78. package/dist/sync/durable-apply.d.ts +76 -0
  79. package/dist/sync/durable-apply.d.ts.map +1 -0
  80. package/dist/sync/durable-apply.js +530 -0
  81. package/dist/sync/durable-apply.js.map +1 -0
  82. package/dist/sync/durable-apply.test.d.ts +2 -0
  83. package/dist/sync/durable-apply.test.d.ts.map +1 -0
  84. package/dist/sync/durable-apply.test.js +180 -0
  85. package/dist/sync/durable-apply.test.js.map +1 -0
  86. package/dist/sync/event-sync.d.ts +33 -1
  87. package/dist/sync/event-sync.d.ts.map +1 -1
  88. package/dist/sync/event-sync.js +149 -1
  89. package/dist/sync/event-sync.js.map +1 -1
  90. package/dist/sync/event-sync.test.js +142 -1
  91. package/dist/sync/event-sync.test.js.map +1 -1
  92. package/dist/sync/index.d.ts +2 -0
  93. package/dist/sync/index.d.ts.map +1 -1
  94. package/dist/sync/index.js +1 -0
  95. package/dist/sync/index.js.map +1 -1
  96. package/dist/sync/multipart-uploader.d.ts +99 -0
  97. package/dist/sync/multipart-uploader.d.ts.map +1 -0
  98. package/dist/sync/multipart-uploader.js +447 -0
  99. package/dist/sync/multipart-uploader.js.map +1 -0
  100. package/dist/sync/multipart-uploader.test.d.ts +2 -0
  101. package/dist/sync/multipart-uploader.test.d.ts.map +1 -0
  102. package/dist/sync/multipart-uploader.test.js +119 -0
  103. package/dist/sync/multipart-uploader.test.js.map +1 -0
  104. package/dist/sync/mutation-client.d.ts +82 -0
  105. package/dist/sync/mutation-client.d.ts.map +1 -0
  106. package/dist/sync/mutation-client.js +221 -0
  107. package/dist/sync/mutation-client.js.map +1 -0
  108. package/dist/sync/mutation-client.test.d.ts +2 -0
  109. package/dist/sync/mutation-client.test.d.ts.map +1 -0
  110. package/dist/sync/mutation-client.test.js +51 -0
  111. package/dist/sync/mutation-client.test.js.map +1 -0
  112. package/dist/sync/push-receiver.d.ts +45 -0
  113. package/dist/sync/push-receiver.d.ts.map +1 -1
  114. package/dist/sync/push-receiver.js +101 -0
  115. package/dist/sync/push-receiver.js.map +1 -1
  116. package/dist/sync/push-receiver.test.js +54 -2
  117. package/dist/sync/push-receiver.test.js.map +1 -1
  118. package/dist/sync/scope-inventory-client.d.ts +69 -0
  119. package/dist/sync/scope-inventory-client.d.ts.map +1 -0
  120. package/dist/sync/scope-inventory-client.js +210 -0
  121. package/dist/sync/scope-inventory-client.js.map +1 -0
  122. package/dist/sync/scope-inventory-client.test.d.ts +2 -0
  123. package/dist/sync/scope-inventory-client.test.d.ts.map +1 -0
  124. package/dist/sync/scope-inventory-client.test.js +94 -0
  125. package/dist/sync/scope-inventory-client.test.js.map +1 -0
  126. package/dist/sync/snapshot-client.d.ts +98 -0
  127. package/dist/sync/snapshot-client.d.ts.map +1 -0
  128. package/dist/sync/snapshot-client.js +402 -0
  129. package/dist/sync/snapshot-client.js.map +1 -0
  130. package/dist/sync/snapshot-client.test.d.ts +2 -0
  131. package/dist/sync/snapshot-client.test.d.ts.map +1 -0
  132. package/dist/sync/snapshot-client.test.js +169 -0
  133. package/dist/sync/snapshot-client.test.js.map +1 -0
  134. package/dist/sync/uploader-finalization.d.ts +97 -0
  135. package/dist/sync/uploader-finalization.d.ts.map +1 -0
  136. package/dist/sync/uploader-finalization.js +273 -0
  137. package/dist/sync/uploader-finalization.js.map +1 -0
  138. package/dist/sync/uploader-finalization.test.d.ts +2 -0
  139. package/dist/sync/uploader-finalization.test.d.ts.map +1 -0
  140. package/dist/sync/uploader-finalization.test.js +92 -0
  141. package/dist/sync/uploader-finalization.test.js.map +1 -0
  142. package/dist/telemetry.d.ts +11 -1
  143. package/dist/telemetry.d.ts.map +1 -1
  144. package/dist/telemetry.js +21 -2
  145. package/dist/telemetry.js.map +1 -1
  146. package/dist/telemetry.test.js +80 -0
  147. package/dist/telemetry.test.js.map +1 -1
  148. package/package.json +6 -1
  149. package/.claude/policies/hq-cloud-esm-cannot-spy-fs-builtins.md +0 -30
  150. package/.claude/policies/hq-cloud-strip-types-no-parameter-properties.md +0 -22
  151. package/.github/workflows/ci.yml +0 -84
  152. package/.github/workflows/publish.yml +0 -56
  153. package/.github/workflows/unreleased-commits-nag.yml +0 -256
  154. package/eslint.config.js +0 -67
  155. package/pnpm-workspace.yaml +0 -2
  156. package/scripts/presign-transport-e2e.mjs +0 -250
  157. package/scripts/vault-rebaseline.sh +0 -323
  158. package/scripts/vault-rescue.sh +0 -332
  159. package/src/active-company.test.ts +0 -188
  160. package/src/active-company.ts +0 -168
  161. package/src/agent-codex-instructions.test.ts +0 -332
  162. package/src/agent-codex-instructions.ts +0 -309
  163. package/src/auth.ts +0 -146
  164. package/src/backup-prune.test.ts +0 -98
  165. package/src/backup-prune.ts +0 -182
  166. package/src/bin/backup-prune-runner.ts +0 -33
  167. package/src/bin/rescue-runner.ts +0 -25
  168. package/src/bin/sync-runner-company.ts +0 -695
  169. package/src/bin/sync-runner-events.test.ts +0 -143
  170. package/src/bin/sync-runner-events.ts +0 -55
  171. package/src/bin/sync-runner-planning.test.ts +0 -311
  172. package/src/bin/sync-runner-planning.ts +0 -258
  173. package/src/bin/sync-runner-rollup.test.ts +0 -37
  174. package/src/bin/sync-runner-rollup.ts +0 -97
  175. package/src/bin/sync-runner-telemetry.ts +0 -15
  176. package/src/bin/sync-runner-watch-loop.ts +0 -1235
  177. package/src/bin/sync-runner-watch-routes.test.ts +0 -71
  178. package/src/bin/sync-runner-watch-routes.ts +0 -184
  179. package/src/bin/sync-runner.test.ts +0 -8767
  180. package/src/bin/sync-runner.ts +0 -2190
  181. package/src/cli/accept.ts +0 -124
  182. package/src/cli/conflict.ts +0 -119
  183. package/src/cli/doctor.test.ts +0 -581
  184. package/src/cli/doctor.ts +0 -642
  185. package/src/cli/index.ts +0 -49
  186. package/src/cli/invite.test.ts +0 -250
  187. package/src/cli/invite.ts +0 -214
  188. package/src/cli/promote.ts +0 -157
  189. package/src/cli/reindex.test.ts +0 -957
  190. package/src/cli/reindex.ts +0 -970
  191. package/src/cli/rescue-classify-ordering.test.ts +0 -548
  192. package/src/cli/rescue-clone-diagnostics.test.ts +0 -120
  193. package/src/cli/rescue-core.ts +0 -3011
  194. package/src/cli/rescue-drift-reconcile.test.ts +0 -179
  195. package/src/cli/rescue-drop-dir-symlink.test.ts +0 -224
  196. package/src/cli/rescue-exec-bit-preserve.test.ts +0 -187
  197. package/src/cli/rescue-hq-root-guard.test.ts +0 -232
  198. package/src/cli/rescue-journal-reconcile.test.ts +0 -215
  199. package/src/cli/rescue-mtime-preserve.test.ts +0 -203
  200. package/src/cli/rescue-settings-reconcile.test.ts +0 -583
  201. package/src/cli/rescue-snapshot.test.ts +0 -57
  202. package/src/cli/rescue-snapshot.ts +0 -51
  203. package/src/cli/rescue.reindex.test.ts +0 -63
  204. package/src/cli/rescue.test.ts +0 -131
  205. package/src/cli/rescue.ts +0 -182
  206. package/src/cli/share.test.ts +0 -7843
  207. package/src/cli/share.ts +0 -3663
  208. package/src/cli/sync-scope.test.ts +0 -652
  209. package/src/cli/sync.test.ts +0 -5207
  210. package/src/cli/sync.ts +0 -3470
  211. package/src/cli/tombstones.ts +0 -106
  212. package/src/cli/watch-event-push-conflict.test.ts +0 -234
  213. package/src/client-info.test.ts +0 -214
  214. package/src/client-info.ts +0 -121
  215. package/src/cognito-auth.test.ts +0 -712
  216. package/src/cognito-auth.ts +0 -1422
  217. package/src/company-resolver.test.ts +0 -618
  218. package/src/company-resolver.ts +0 -521
  219. package/src/context.test.ts +0 -583
  220. package/src/context.ts +0 -378
  221. package/src/daemon-worker.ts +0 -26
  222. package/src/daemon.ts +0 -99
  223. package/src/entity-resolver.test.ts +0 -315
  224. package/src/entity-resolver.ts +0 -180
  225. package/src/ignore.test.ts +0 -466
  226. package/src/ignore.ts +0 -469
  227. package/src/index.ts +0 -439
  228. package/src/journal.test.ts +0 -968
  229. package/src/journal.ts +0 -765
  230. package/src/lib/cloud-authoritative.test.ts +0 -45
  231. package/src/lib/cloud-authoritative.ts +0 -59
  232. package/src/lib/conflict-file.ts +0 -86
  233. package/src/lib/conflict-index.ts +0 -289
  234. package/src/lib/conflict.test.ts +0 -348
  235. package/src/lib/describe-error.test.ts +0 -100
  236. package/src/lib/describe-error.ts +0 -58
  237. package/src/lib/exit-codes.ts +0 -24
  238. package/src/lib/machine-id.test.ts +0 -231
  239. package/src/lib/machine-id.ts +0 -175
  240. package/src/lib/net-errors.test.ts +0 -65
  241. package/src/lib/net-errors.ts +0 -86
  242. package/src/lib/readlink-safe.test.ts +0 -43
  243. package/src/lib/readlink-safe.ts +0 -29
  244. package/src/local-path-codec.test.ts +0 -138
  245. package/src/local-path-codec.ts +0 -161
  246. package/src/machine-auth.test.ts +0 -1323
  247. package/src/manifest-reconcile.test.ts +0 -1123
  248. package/src/manifest-reconcile.ts +0 -518
  249. package/src/object-io.test.ts +0 -1221
  250. package/src/object-io.ts +0 -1306
  251. package/src/operation-lock.test.ts +0 -484
  252. package/src/operation-lock.ts +0 -680
  253. package/src/outcome-telemetry.test.ts +0 -498
  254. package/src/outcome-telemetry.ts +0 -639
  255. package/src/personal-vault-exclusions.test.ts +0 -308
  256. package/src/personal-vault-exclusions.ts +0 -354
  257. package/src/personal-vault.test.ts +0 -756
  258. package/src/personal-vault.ts +0 -496
  259. package/src/prefix-coalesce.test.ts +0 -240
  260. package/src/prefix-coalesce.ts +0 -273
  261. package/src/public-surface.test.ts +0 -117
  262. package/src/qmd-reindex.test.ts +0 -877
  263. package/src/qmd-reindex.ts +0 -842
  264. package/src/read-only-state-dir.test.ts +0 -188
  265. package/src/remote-pull.test.ts +0 -1130
  266. package/src/remote-pull.ts +0 -618
  267. package/src/s3.symlink-materialize.test.ts +0 -492
  268. package/src/s3.test.ts +0 -1789
  269. package/src/s3.ts +0 -1532
  270. package/src/schemas/signal-types.test.ts +0 -82
  271. package/src/schemas/signal-types.ts +0 -38
  272. package/src/schemas/source-channels.test.ts +0 -82
  273. package/src/schemas/source-channels.ts +0 -53
  274. package/src/scope-shrink.test.ts +0 -633
  275. package/src/scope-shrink.ts +0 -481
  276. package/src/signals/get.test.ts +0 -310
  277. package/src/signals/get.ts +0 -75
  278. package/src/signals/internals.ts +0 -195
  279. package/src/signals/list.test.ts +0 -420
  280. package/src/signals/list.ts +0 -79
  281. package/src/signals/parse.ts +0 -8
  282. package/src/signals/types.ts +0 -91
  283. package/src/skill-telemetry.test.ts +0 -1825
  284. package/src/skill-telemetry.ts +0 -1439
  285. package/src/sources/get.test.ts +0 -293
  286. package/src/sources/get.ts +0 -66
  287. package/src/sources/internals.ts +0 -198
  288. package/src/sources/list.test.ts +0 -402
  289. package/src/sources/list.ts +0 -84
  290. package/src/sources/parse.ts +0 -43
  291. package/src/sources/types.ts +0 -84
  292. package/src/sync/event-sync.test.ts +0 -594
  293. package/src/sync/event-sync.ts +0 -545
  294. package/src/sync/feature-flags.test.ts +0 -378
  295. package/src/sync/feature-flags.ts +0 -62
  296. package/src/sync/index.ts +0 -76
  297. package/src/sync/lease-client.test.ts +0 -128
  298. package/src/sync/lease-client.ts +0 -207
  299. package/src/sync/logger.test.ts +0 -242
  300. package/src/sync/logger.ts +0 -79
  301. package/src/sync/metrics.test.ts +0 -462
  302. package/src/sync/metrics.ts +0 -213
  303. package/src/sync/pull-scope.ts +0 -265
  304. package/src/sync/push-event.test.ts +0 -266
  305. package/src/sync/push-event.ts +0 -224
  306. package/src/sync/push-receiver.test.ts +0 -566
  307. package/src/sync/push-receiver.ts +0 -1048
  308. package/src/sync/push-transport.ts +0 -231
  309. package/src/sync/realtime-rollout.test.ts +0 -86
  310. package/src/sync/realtime-rollout.ts +0 -262
  311. package/src/sync/state-store.test.ts +0 -194
  312. package/src/sync/state-store.ts +0 -727
  313. package/src/sync-core.ts +0 -58
  314. package/src/sync-progress.test.ts +0 -94
  315. package/src/sync-progress.ts +0 -140
  316. package/src/telemetry-events.test.ts +0 -88
  317. package/src/telemetry-events.ts +0 -205
  318. package/src/telemetry.test.ts +0 -1280
  319. package/src/telemetry.ts +0 -1109
  320. package/src/types.ts +0 -314
  321. package/src/vault-client.test.ts +0 -1380
  322. package/src/vault-client.ts +0 -1694
  323. package/src/version.ts +0 -24
  324. package/src/watch-roots.test.ts +0 -278
  325. package/src/watch-roots.ts +0 -162
  326. package/src/watcher-event-gate.test.ts +0 -212
  327. package/src/watcher.test.ts +0 -1079
  328. package/src/watcher.ts +0 -1741
  329. package/test/e2e/sync/cross-tenant-isolation.test.ts +0 -630
  330. package/test/e2e/sync/skill-telemetry-oversized-transcript.test.ts +0 -124
  331. package/test/e2e/sync/transient-company-leg.test.ts +0 -384
  332. package/test/e2e/sync/windows-unreadable-link-leg.test.ts +0 -191
  333. package/test/e2e/watcher-real-chokidar.test.ts +0 -165
  334. package/test/e2e/watcher-recursive-backend.test.ts +0 -181
  335. package/test/e2e/watcher-scoped-coverage.test.ts +0 -381
  336. package/test/invite-flow.integration.test.ts +0 -244
  337. package/test/joiner-manifest-reconcile.integration.test.ts +0 -322
  338. package/test/share-sync.integration.test.ts +0 -213
  339. package/tsconfig.json +0 -19
  340. 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
- }