@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,618 +0,0 @@
1
- /**
2
- * Tests for the cwd → owning-company resolver (surface-hq-console-telemetry
3
- * US-002). Covers manifest parsing, cmp_* extraction, longest-prefix matching,
4
- * the trailing-slash sibling boundary, and the unattributed (no-match) path.
5
- */
6
-
7
- import { describe, it, expect, afterEach } from "vitest";
8
- import * as fs from "fs";
9
- import * as os from "os";
10
- import * as path from "path";
11
- import {
12
- buildRepoCompanyMap,
13
- resolveCompanyForCwd,
14
- resolveCompanyForSkill,
15
- } from "./company-resolver.js";
16
-
17
- const INDIGO = "cmp_01INDIGO";
18
- const HPO = "cmp_01HPO";
19
-
20
- const MANIFEST = `companies:
21
- personal:
22
- name: Personal
23
- repos: []
24
- indigo:
25
- name: Indigo
26
- repos:
27
- - repos/private/hq-cloud
28
- - repos/public/hq-core
29
- cloud_uid: ${INDIGO}
30
- hpo:
31
- name: HPO
32
- repos:
33
- - repos/private/hpo-jobs
34
- cloud_uid: ${HPO}
35
- liverecover:
36
- name: LiveRecover
37
- repos:
38
- - repos/private/liverecover-site
39
- `;
40
-
41
- function setupHqRoot(manifest: string = MANIFEST): string {
42
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "hq-resolver-test-"));
43
- fs.mkdirSync(path.join(root, "companies"), { recursive: true });
44
- fs.writeFileSync(path.join(root, "companies", "manifest.yaml"), manifest);
45
- return root;
46
- }
47
-
48
- describe("buildRepoCompanyMap + resolveCompanyForCwd", () => {
49
- let hqRoot: string;
50
- afterEach(() => {
51
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
52
- });
53
-
54
- it("resolves a cwd inside a company repo to that company's cmp_ uid", async () => {
55
- hqRoot = setupHqRoot();
56
- const map = await buildRepoCompanyMap(hqRoot);
57
- const cwd = path.join(hqRoot, "repos/private/hq-cloud/src");
58
- expect(resolveCompanyForCwd(cwd, map)).toBe(INDIGO);
59
- });
60
-
61
- it("resolves the repo root itself (exact match, no trailing slash)", async () => {
62
- hqRoot = setupHqRoot();
63
- const map = await buildRepoCompanyMap(hqRoot);
64
- const cwd = path.join(hqRoot, "repos/public/hq-core");
65
- expect(resolveCompanyForCwd(cwd, map)).toBe(INDIGO);
66
- });
67
-
68
- it("maps each repo to its own company", async () => {
69
- hqRoot = setupHqRoot();
70
- const map = await buildRepoCompanyMap(hqRoot);
71
- expect(resolveCompanyForCwd(path.join(hqRoot, "repos/private/hpo-jobs/x"), map)).toBe(HPO);
72
- });
73
-
74
- it("returns undefined for a cwd outside any company repo (unattributed)", async () => {
75
- hqRoot = setupHqRoot();
76
- const map = await buildRepoCompanyMap(hqRoot);
77
- expect(resolveCompanyForCwd("/Users/x/random-repo", map)).toBeUndefined();
78
- // The HQ root itself is company-agnostic — it belongs to no single tenant.
79
- expect(resolveCompanyForCwd(hqRoot, map)).toBeUndefined();
80
- });
81
-
82
- /**
83
- * Behaviour change (was: `companies/<slug>` resolved to undefined).
84
- *
85
- * A company's own HQ directory holds its skills, knowledge, and project
86
- * files, so work done there is unambiguously that company's work. The prior
87
- * repos-only scope dropped it, and in production a large share of sessions
88
- * run from inside the HQ root rather than a repo checkout — leaving that work
89
- * unattributed and the owner's Projects view empty. Attribution is scoped to
90
- * the single tenant that owns the directory, so this widens coverage without
91
- * crossing a tenant boundary.
92
- */
93
- it("attributes a cwd inside the company's own HQ directory", async () => {
94
- hqRoot = setupHqRoot();
95
- const map = await buildRepoCompanyMap(hqRoot);
96
- expect(resolveCompanyForCwd(path.join(hqRoot, "companies/indigo"), map)).toBe(INDIGO);
97
- expect(
98
- resolveCompanyForCwd(path.join(hqRoot, "companies/hpo/knowledge"), map),
99
- ).toBe(HPO);
100
- });
101
-
102
- it("does NOT attribute a company that lacks a cloud_uid (not cloud-backed)", async () => {
103
- hqRoot = setupHqRoot();
104
- const map = await buildRepoCompanyMap(hqRoot);
105
- // liverecover-site has no cloud_uid → unattributed.
106
- expect(resolveCompanyForCwd(path.join(hqRoot, "repos/private/liverecover-site"), map)).toBeUndefined();
107
- });
108
-
109
- it("does not match a sibling that merely shares a path prefix", async () => {
110
- hqRoot = setupHqRoot();
111
- const map = await buildRepoCompanyMap(hqRoot);
112
- // `<repo>-other` shares the string prefix but must NOT match (slash boundary).
113
- expect(resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud-other"), map)).toBeUndefined();
114
- });
115
-
116
- it("handles a trailing slash on the cwd", async () => {
117
- hqRoot = setupHqRoot();
118
- const map = await buildRepoCompanyMap(hqRoot);
119
- expect(resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud") + "/", map)).toBe(INDIGO);
120
- });
121
-
122
- it("returns undefined for an undefined/empty cwd", async () => {
123
- hqRoot = setupHqRoot();
124
- const map = await buildRepoCompanyMap(hqRoot);
125
- expect(resolveCompanyForCwd(undefined, map)).toBeUndefined();
126
- expect(resolveCompanyForCwd("", map)).toBeUndefined();
127
- });
128
-
129
- it("never returns the reserved 'unattributed' sentinel", async () => {
130
- hqRoot = setupHqRoot(`companies:
131
- bad:
132
- repos:
133
- - repos/x
134
- cloud_uid: unattributed
135
- `);
136
- const map = await buildRepoCompanyMap(hqRoot);
137
- // `unattributed` is not a cmp_* uid → not added to the map → no match.
138
- expect(map.entries).toHaveLength(0);
139
- expect(resolveCompanyForCwd(path.join(hqRoot, "repos/x"), map)).toBeUndefined();
140
- });
141
-
142
- it("yields an empty map for a missing manifest", async () => {
143
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "hq-resolver-empty-"));
144
- try {
145
- const map = await buildRepoCompanyMap(root);
146
- expect(map.entries).toHaveLength(0);
147
- expect(resolveCompanyForCwd(path.join(root, "repos/private/hq-cloud"), map)).toBeUndefined();
148
- } finally {
149
- fs.rmSync(root, { recursive: true, force: true });
150
- }
151
- });
152
-
153
- it("yields an empty map for an unparseable manifest", async () => {
154
- hqRoot = setupHqRoot(":\n not: [valid: yaml");
155
- const map = await buildRepoCompanyMap(hqRoot);
156
- expect(map.entries).toHaveLength(0);
157
- expect(map.bySlug.size).toBe(0);
158
- });
159
- });
160
-
161
- describe("resolveCompanyForSkill", () => {
162
- let hqRoot: string;
163
- afterEach(() => {
164
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
165
- });
166
-
167
- it("attributes a company-namespaced skill to its company, regardless of cwd", async () => {
168
- hqRoot = setupHqRoot();
169
- const map = await buildRepoCompanyMap(hqRoot);
170
- // The slug map is populated from the manifest keys with a cloud_uid.
171
- expect(resolveCompanyForSkill("indigo:signals", map)).toBe(INDIGO);
172
- expect(resolveCompanyForSkill("hpo:some-skill", map)).toBe(HPO);
173
- // Multi-segment skill name: only the leading slug is the company.
174
- expect(resolveCompanyForSkill("indigo:hq-dev-thread", map)).toBe(INDIGO);
175
- });
176
-
177
- it("returns undefined for a core (un-namespaced) skill", async () => {
178
- hqRoot = setupHqRoot();
179
- const map = await buildRepoCompanyMap(hqRoot);
180
- expect(resolveCompanyForSkill("startwork", map)).toBeUndefined();
181
- expect(resolveCompanyForSkill("review", map)).toBeUndefined();
182
- });
183
-
184
- it("returns undefined for a non-company namespace (personal/pack) and a leading colon", async () => {
185
- hqRoot = setupHqRoot();
186
- const map = await buildRepoCompanyMap(hqRoot);
187
- // `personal` has no cloud_uid → not in the slug map.
188
- expect(resolveCompanyForSkill("personal:startwork", map)).toBeUndefined();
189
- expect(
190
- resolveCompanyForSkill("hq-pack-engineering:review", map),
191
- ).toBeUndefined();
192
- expect(resolveCompanyForSkill(":oops", map)).toBeUndefined();
193
- });
194
-
195
- it("returns undefined for a slug whose company is not cloud-backed", async () => {
196
- hqRoot = setupHqRoot();
197
- const map = await buildRepoCompanyMap(hqRoot);
198
- // `liverecover` is in the manifest but carries no cloud_uid.
199
- expect(resolveCompanyForSkill("liverecover:foo", map)).toBeUndefined();
200
- });
201
-
202
- it("handles a missing/undefined skill safely", async () => {
203
- hqRoot = setupHqRoot();
204
- const map = await buildRepoCompanyMap(hqRoot);
205
- expect(resolveCompanyForSkill(undefined, map)).toBeUndefined();
206
- expect(resolveCompanyForSkill("", map)).toBeUndefined();
207
- });
208
- });
209
-
210
- /**
211
- * Regression: the real `companies/manifest.yaml` records `repos` as BARE NAMES
212
- * (`hq-cloud`), not HQ-root-relative paths (`repos/private/hq-cloud`). The
213
- * original resolver did `path.resolve(hqRoot, repo)` and produced
214
- * `<hqRoot>/hq-cloud` — a path no checkout lives at — so every entry matched
215
- * nothing and NO telemetry event was ever attributed. Verified in prod at the
216
- * time of the fix: 0 of 3200 sampled usage events across 9 people carried a
217
- * `companyUid`, which left the Team Activity "Projects" tab permanently empty.
218
- */
219
- describe("real-manifest shape: bare repo names + company directories", () => {
220
- const ACME = "cmp_01ACME";
221
- const BETA = "cmp_01BETA";
222
-
223
- // Mirrors the live manifest: bare repo names, an explicit `path`, and a
224
- // company with no cloud_uid (not cloud-backed).
225
- const REAL_SHAPE = `companies:
226
- acme:
227
- name: Acme
228
- path: companies/acme
229
- repos:
230
- - hq-cloud
231
- - hq-console
232
- cloud_uid: ${ACME}
233
- beta:
234
- name: Beta
235
- path: companies/beta
236
- repos:
237
- - beta-site
238
- cloud_uid: ${BETA}
239
- nocloud:
240
- name: NoCloud
241
- path: companies/nocloud
242
- repos:
243
- - orphan-repo
244
- `;
245
-
246
- let hqRoot: string;
247
- afterEach(() => {
248
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
249
- });
250
-
251
- it("resolves a bare repo name to its repos/private checkout", async () => {
252
- hqRoot = setupHqRoot(REAL_SHAPE);
253
- const map = await buildRepoCompanyMap(hqRoot);
254
- const cwd = path.join(hqRoot, "repos/private/hq-console/src/app");
255
- expect(resolveCompanyForCwd(cwd, map)).toBe(ACME);
256
- });
257
-
258
- it("resolves a bare repo name to its repos/public checkout", async () => {
259
- hqRoot = setupHqRoot(REAL_SHAPE);
260
- const map = await buildRepoCompanyMap(hqRoot);
261
- const cwd = path.join(hqRoot, "repos/public/hq-cloud");
262
- expect(resolveCompanyForCwd(cwd, map)).toBe(ACME);
263
- });
264
-
265
- it("attributes work inside the company's own HQ directory", async () => {
266
- hqRoot = setupHqRoot(REAL_SHAPE);
267
- const map = await buildRepoCompanyMap(hqRoot);
268
- expect(resolveCompanyForCwd(path.join(hqRoot, "companies/acme"), map)).toBe(ACME);
269
- expect(
270
- resolveCompanyForCwd(path.join(hqRoot, "companies/beta/projects/x"), map),
271
- ).toBe(BETA);
272
- });
273
-
274
- it("still honours an explicit HQ-root-relative repo path (back-compat)", async () => {
275
- hqRoot = setupHqRoot();
276
- const map = await buildRepoCompanyMap(hqRoot);
277
- expect(
278
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud/src"), map),
279
- ).toBe("cmp_01INDIGO");
280
- });
281
-
282
- it("leaves a non-cloud-backed company's repos and directory unattributed", async () => {
283
- hqRoot = setupHqRoot(REAL_SHAPE);
284
- const map = await buildRepoCompanyMap(hqRoot);
285
- expect(
286
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/orphan-repo"), map),
287
- ).toBeUndefined();
288
- expect(resolveCompanyForCwd(path.join(hqRoot, "companies/nocloud"), map)).toBeUndefined();
289
- });
290
-
291
- /**
292
- * Case folding follows the FILESYSTEM, not the OS.
293
- *
294
- * Where the filesystem folds case, `.../HQ` and `.../hq` are literally the
295
- * same directory and a differently-cased cwd must still attribute. Where it
296
- * does not, they are different directories and an unattributed event is the
297
- * correct (safe) outcome.
298
- *
299
- * The expectation is derived from the resolver's own probe (`map.foldsCase`),
300
- * NOT from `process.platform` — case-sensitive APFS and case-insensitive
301
- * volumes mounted on Linux both make the platform a liar, and this test would
302
- * fail on them despite the resolver behaving correctly.
303
- */
304
- it("follows the probed filesystem for a differently-cased path", async () => {
305
- hqRoot = setupHqRoot(REAL_SHAPE);
306
- const map = await buildRepoCompanyMap(hqRoot);
307
- const cwd = path.join(hqRoot, "repos/private/hq-console").toUpperCase();
308
- expect(resolveCompanyForCwd(cwd, map)).toBe(map.foldsCase ? ACME : undefined);
309
- });
310
-
311
- it("always matches an exactly-cased path", async () => {
312
- hqRoot = setupHqRoot(REAL_SHAPE);
313
- const map = await buildRepoCompanyMap(hqRoot);
314
- expect(
315
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-console/src"), map),
316
- ).toBe(ACME);
317
- });
318
-
319
- it("treats backslash-separated paths as equivalent to forward slashes", async () => {
320
- hqRoot = setupHqRoot(REAL_SHAPE);
321
- const map = await buildRepoCompanyMap(hqRoot);
322
- const cwd = path.join(hqRoot, "repos/private/hq-console/src").replace(/\//g, "\\");
323
- expect(resolveCompanyForCwd(cwd, map)).toBe(ACME);
324
- });
325
-
326
- it("does not let a sibling sharing a string prefix match", async () => {
327
- hqRoot = setupHqRoot(REAL_SHAPE);
328
- const map = await buildRepoCompanyMap(hqRoot);
329
- expect(
330
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-console-other"), map),
331
- ).toBeUndefined();
332
- });
333
-
334
- it("emits no duplicate match keys", async () => {
335
- hqRoot = setupHqRoot(REAL_SHAPE);
336
- const map = await buildRepoCompanyMap(hqRoot);
337
- const keys = map.entries.map((e) => e.matchKey);
338
- expect(new Set(keys).size).toBe(keys.length);
339
- });
340
- });
341
-
342
- /**
343
- * Codex-review follow-ups. Each of these is a real HQ layout that pure lexical
344
- * matching against the primary checkout silently misses, leaving the work
345
- * unattributed — the same class of gap that left the Projects tab empty.
346
- */
347
- describe("real HQ layouts: worktrees, symlinks, and ambiguity", () => {
348
- const ACME = "cmp_01ACME";
349
- const BETA = "cmp_01BETA";
350
-
351
- const SHAPE = `companies:
352
- acme:
353
- name: Acme
354
- path: companies/acme
355
- repos:
356
- - hq-cloud
357
- cloud_uid: ${ACME}
358
- beta:
359
- name: Beta
360
- path: companies/beta
361
- repos:
362
- - beta-site
363
- cloud_uid: ${BETA}
364
- `;
365
-
366
- let hqRoot: string;
367
- afterEach(() => {
368
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
369
- });
370
-
371
- function mkdirp(...segments: string[]): string {
372
- const p = path.join(hqRoot, ...segments);
373
- fs.mkdirSync(p, { recursive: true });
374
- return p;
375
- }
376
-
377
- /**
378
- * HQ does most feature work in a sibling git worktree named
379
- * `<repo>-wt-<branch>`. The resolver's slash boundary rejects that as a mere
380
- * string-prefix sibling of `<repo>`, so without explicit expansion every
381
- * worktree session is unattributed.
382
- */
383
- it("attributes work inside a `<repo>-wt-*` git worktree", async () => {
384
- hqRoot = setupHqRoot(SHAPE);
385
- mkdirp("repos/private/hq-cloud");
386
- const wt = mkdirp("repos/private/hq-cloud-wt-feature-x");
387
-
388
- const map = await buildRepoCompanyMap(hqRoot);
389
- expect(resolveCompanyForCwd(wt, map)).toBe(ACME);
390
- expect(resolveCompanyForCwd(path.join(wt, "src/lib"), map)).toBe(ACME);
391
- });
392
-
393
- it("still rejects an unrelated sibling that merely shares the prefix", async () => {
394
- hqRoot = setupHqRoot(SHAPE);
395
- mkdirp("repos/private/hq-cloud");
396
- const other = mkdirp("repos/private/hq-cloud-other");
397
-
398
- const map = await buildRepoCompanyMap(hqRoot);
399
- expect(resolveCompanyForCwd(other, map)).toBeUndefined();
400
- });
401
-
402
- /**
403
- * A symlinked checkout reports its PHYSICAL path as cwd, which lives outside
404
- * the HQ tree entirely and can never match the lexical root.
405
- */
406
- it("attributes a symlinked checkout reached by its physical path", async () => {
407
- hqRoot = setupHqRoot(SHAPE);
408
- const physical = fs.mkdtempSync(path.join(os.tmpdir(), "hq-physical-repo-"));
409
- fs.mkdirSync(path.join(hqRoot, "repos/private"), { recursive: true });
410
- try {
411
- fs.symlinkSync(physical, path.join(hqRoot, "repos/private/hq-cloud"), "dir");
412
- } catch {
413
- return; // symlinks unavailable (e.g. unprivileged Windows) — skip
414
- }
415
-
416
- const map = await buildRepoCompanyMap(hqRoot);
417
- try {
418
- expect(resolveCompanyForCwd(fs.realpathSync(physical), map)).toBe(ACME);
419
- // The lexical path must keep working too.
420
- expect(
421
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud"), map),
422
- ).toBe(ACME);
423
- } finally {
424
- fs.rmSync(physical, { recursive: true, force: true });
425
- }
426
- });
427
-
428
- /**
429
- * Fail CLOSED: when two companies claim the same path, attributing to
430
- * whichever the manifest listed first would be a cross-tenant
431
- * misattribution. Unattributed is the correct answer.
432
- */
433
- it("leaves a path claimed by two companies unattributed", async () => {
434
- const AMBIGUOUS = `companies:
435
- acme:
436
- name: Acme
437
- repos:
438
- - shared-repo
439
- cloud_uid: ${ACME}
440
- beta:
441
- name: Beta
442
- repos:
443
- - shared-repo
444
- cloud_uid: ${BETA}
445
- `;
446
- hqRoot = setupHqRoot(AMBIGUOUS);
447
- mkdirp("repos/private/shared-repo");
448
-
449
- const map = await buildRepoCompanyMap(hqRoot);
450
- expect(
451
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/shared-repo"), map),
452
- ).toBeUndefined();
453
- });
454
-
455
- it("an ambiguous repo does not poison that company's other repos", async () => {
456
- const AMBIGUOUS = `companies:
457
- acme:
458
- name: Acme
459
- repos:
460
- - shared-repo
461
- - acme-only
462
- cloud_uid: ${ACME}
463
- beta:
464
- name: Beta
465
- repos:
466
- - shared-repo
467
- cloud_uid: ${BETA}
468
- `;
469
- hqRoot = setupHqRoot(AMBIGUOUS);
470
- mkdirp("repos/private/shared-repo");
471
- mkdirp("repos/private/acme-only");
472
-
473
- const map = await buildRepoCompanyMap(hqRoot);
474
- expect(
475
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/acme-only"), map),
476
- ).toBe(ACME);
477
- });
478
-
479
- it("reports the probed case-folding behaviour of the real filesystem", async () => {
480
- hqRoot = setupHqRoot(SHAPE);
481
- const map = await buildRepoCompanyMap(hqRoot);
482
- expect(typeof map.foldsCase).toBe("boolean");
483
- // Whatever the probe found, an exactly-cased path always resolves.
484
- mkdirp("repos/private/hq-cloud");
485
- const fresh = await buildRepoCompanyMap(hqRoot);
486
- expect(
487
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud"), fresh),
488
- ).toBe(ACME);
489
- });
490
- });
491
-
492
- /**
493
- * A worktree that no longer exists on disk.
494
- *
495
- * `buildRepoCompanyMap` discovers worktrees by reading the directory, which
496
- * only sees what exists at build time. Telemetry, though, is read from a
497
- * cursor — and a branch worktree is routinely deleted before the next pass,
498
- * while pending JSONL rows still carry its cwd. Since a successful POST
499
- * advances the cursor permanently, failing to resolve those rows loses the
500
- * attribution for good.
501
- */
502
- describe("worktree paths that no longer exist on disk", () => {
503
- const ACME = "cmp_01ACME";
504
- const SHAPE = `companies:
505
- acme:
506
- name: Acme
507
- path: companies/acme
508
- repos:
509
- - hq-cloud
510
- cloud_uid: ${ACME}
511
- `;
512
-
513
- let hqRoot: string;
514
- afterEach(() => {
515
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
516
- });
517
-
518
- it("resolves a deleted worktree's cwd by its documented naming convention", async () => {
519
- hqRoot = setupHqRoot(SHAPE);
520
- fs.mkdirSync(path.join(hqRoot, "repos/private/hq-cloud"), { recursive: true });
521
- // NOTE: the worktree is deliberately NOT created — it was cleaned up before
522
- // this telemetry pass, exactly as in the real failure.
523
- const map = await buildRepoCompanyMap(hqRoot);
524
-
525
- const gone = path.join(hqRoot, "repos/private/hq-cloud-wt-feature-gone");
526
- expect(resolveCompanyForCwd(gone, map)).toBe(ACME);
527
- expect(resolveCompanyForCwd(path.join(gone, "src/lib"), map)).toBe(ACME);
528
- });
529
-
530
- it("does not let the worktree fallback rescue an unrelated sibling", async () => {
531
- hqRoot = setupHqRoot(SHAPE);
532
- fs.mkdirSync(path.join(hqRoot, "repos/private/hq-cloud"), { recursive: true });
533
- const map = await buildRepoCompanyMap(hqRoot);
534
-
535
- // `-other` is not the `-wt-` convention, so it stays a foreign sibling...
536
- expect(
537
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud-other"), map),
538
- ).toBeUndefined();
539
- // ...and a `-wt-` suffix on a repo nobody owns resolves to nothing.
540
- expect(
541
- resolveCompanyForCwd(path.join(hqRoot, "repos/private/stranger-wt-x"), map),
542
- ).toBeUndefined();
543
- });
544
- });
545
-
546
- /**
547
- * Fail-closed must survive the worktree fallback.
548
- *
549
- * The fallback collapses `<repo>-wt-<branch>` to `<repo>`. If the worktree-
550
- * shaped path was itself dropped for ambiguity, collapsing it would hand the
551
- * event to whoever owns the base repo — quietly undoing the protection.
552
- */
553
- describe("ambiguity is not escapable via the worktree fallback", () => {
554
- const ACME = "cmp_01ACME";
555
- const BETA = "cmp_01BETA";
556
-
557
- let hqRoot: string;
558
- afterEach(() => {
559
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
560
- });
561
-
562
- it("keeps an ambiguous `<repo>-wt-*` path unattributed instead of collapsing it", async () => {
563
- // Acme owns `foo`; Beta owns a real repo that happens to be named
564
- // `foo-wt-team`, which also looks like a worktree of Acme's `foo`.
565
- hqRoot = setupHqRoot(`companies:
566
- acme:
567
- name: Acme
568
- repos:
569
- - foo
570
- cloud_uid: ${ACME}
571
- beta:
572
- name: Beta
573
- repos:
574
- - foo-wt-team
575
- cloud_uid: ${BETA}
576
- `);
577
- fs.mkdirSync(path.join(hqRoot, "repos/private/foo"), { recursive: true });
578
- fs.mkdirSync(path.join(hqRoot, "repos/private/foo-wt-team"), { recursive: true });
579
-
580
- const map = await buildRepoCompanyMap(hqRoot);
581
- const contested = path.join(hqRoot, "repos/private/foo-wt-team");
582
-
583
- expect(resolveCompanyForCwd(contested, map)).toBeUndefined();
584
- expect(resolveCompanyForCwd(path.join(contested, "src"), map)).toBeUndefined();
585
- // The unambiguous base repo still resolves.
586
- expect(resolveCompanyForCwd(path.join(hqRoot, "repos/private/foo"), map)).toBe(ACME);
587
- });
588
- });
589
-
590
- /**
591
- * `path` is documented as HQ-root-relative, but the manifest is synced and
592
- * hand-editable. A path that escapes the root would claim a directory spanning
593
- * other tenants and stamp this company's uid on everything beneath it.
594
- */
595
- describe("company paths that escape the HQ root are rejected", () => {
596
- const ACME = "cmp_01ACME";
597
-
598
- let hqRoot: string;
599
- afterEach(() => {
600
- if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
601
- });
602
-
603
- it.each([[" .."], ["."], ["/"]])("ignores path: %s", async (badPath) => {
604
- hqRoot = setupHqRoot(`companies:
605
- acme:
606
- name: Acme
607
- path: "${badPath.trim()}"
608
- repos: []
609
- cloud_uid: ${ACME}
610
- `);
611
- const map = await buildRepoCompanyMap(hqRoot);
612
-
613
- // Neither the HQ root nor its parent may be claimed.
614
- expect(resolveCompanyForCwd(hqRoot, map)).toBeUndefined();
615
- expect(resolveCompanyForCwd(path.dirname(hqRoot), map)).toBeUndefined();
616
- expect(resolveCompanyForCwd(path.join(hqRoot, "companies/other"), map)).toBeUndefined();
617
- });
618
- });