@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,378 +0,0 @@
1
- /**
2
- * US-008 — client push transport unit tests.
3
- *
4
- * The PRD names this file as the home for US-008's coverage; it spans the
5
- * four required assertions across the three US-008 modules:
6
- *
7
- * 1. flag gating — feature-flags.ts (env list, legacy global,
8
- * static provider, malformed-entry tolerance)
9
- * 2. POST — push-transport.ts HttpPushTransport posts an
10
- * encoded PushEvent to `<apiUrl>/sync/push` with
11
- * a Bearer token (mocked fetch — no real server)
12
- * 3. emit — watcher.ts PushEventEmitter builds a valid
13
- * PushEvent (sha256 hash, monotonic seq, tenant)
14
- * per debounced change and hands it to the
15
- * transport; dormant when the flag is OFF
16
- * 4. transport failure — a publish rejection (or hash error) is caught,
17
- * surfaced via onError, and never crashes / never
18
- * propagates (cadence poll is the safety net)
19
- *
20
- * The live `/sync/push` endpoint is NOT deployed yet — every POST is asserted
21
- * against a mocked fetch / mocked transport.
22
- */
23
-
24
- import { createHash } from "node:crypto";
25
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
26
- import { tmpdir } from "node:os";
27
- import path from "node:path";
28
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
29
-
30
- import {
31
- EVENT_SYNC_LOCAL_DISABLE_ENV_VAR,
32
- isEventSyncLocallyDisabled,
33
- } from "./feature-flags.js";
34
- import {
35
- HttpPushTransport,
36
- NoopPushTransport,
37
- type FetchLike,
38
- type PushTransport,
39
- } from "./push-transport.js";
40
- import { decodePushEvent, type PushEvent } from "./push-event.js";
41
- import { PushEventEmitter } from "../watcher.js";
42
- import type { TreeChangeBatch } from "../watcher.js";
43
-
44
- // ─── Helpers ─────────────────────────────────────────────────────────────────
45
-
46
- function fakePushEvent(overrides: Partial<PushEvent> = {}): PushEvent {
47
- return {
48
- relativePath: "personal/notes/a.md",
49
- kind: "upsert",
50
- contentHash: `sha256:${"a".repeat(64)}`,
51
- mtime: "2026-05-21T12:00:00.000Z",
52
- originDeviceId: "device-1",
53
- originTenantId: "indigo",
54
- sequenceNumber: 0,
55
- eventTimestamp: "2026-05-21T12:00:01.000Z",
56
- ...overrides,
57
- };
58
- }
59
-
60
- /** A fetch double that records calls and returns a configurable response. */
61
- function makeFetch(
62
- res: { ok: boolean; status: number; body?: string } = {
63
- ok: true,
64
- status: 200,
65
- },
66
- ): { fetch: FetchLike; calls: Array<{ url: string; init: unknown }> } {
67
- const calls: Array<{ url: string; init: unknown }> = [];
68
- const fetch: FetchLike = async (url, init) => {
69
- calls.push({ url, init });
70
- return {
71
- ok: res.ok,
72
- status: res.status,
73
- text: async () => res.body ?? "",
74
- };
75
- };
76
- return { fetch, calls };
77
- }
78
-
79
- // ─── 1. Local disable (feature-flags.ts) ─────────────────────────────────────
80
-
81
- const serverGranted = { isEnabled: () => true };
82
- const serverDenied = { isEnabled: () => false };
83
-
84
- describe("Event sync local control", () => {
85
- it("exports only the local disable control", () => {
86
- expect(EVENT_SYNC_LOCAL_DISABLE_ENV_VAR).toBe("HQ_SYNC_EVENT_SYNC");
87
- });
88
-
89
- it.each(["0", "false", "no", "off", " OFF "])(
90
- "recognizes %j as a local disable",
91
- (value) => expect(isEventSyncLocallyDisabled(value)).toBe(true),
92
- );
93
-
94
- it.each([undefined, "", "1", "true", "yes", "on", "indigo,acme"])(
95
- "%j does not grant authority",
96
- (value) => expect(isEventSyncLocallyDisabled(value)).toBe(false),
97
- );
98
- });
99
-
100
- // ─── 2. POST (HttpPushTransport) ──────────────────────────────────────────────
101
-
102
- describe("HttpPushTransport — POST /sync/push", () => {
103
- it("POSTs an encoded PushEvent to <apiUrl>/sync/push with a Bearer token", async () => {
104
- const { fetch, calls } = makeFetch({ ok: true, status: 200 });
105
- const transport = new HttpPushTransport({
106
- apiUrl: "https://vault-api.example.com/",
107
- authToken: "tok-123",
108
- fetchImpl: fetch,
109
- });
110
- await transport.start();
111
- expect(transport.connected).toBe(true);
112
-
113
- const event = fakePushEvent();
114
- await transport.publish(event);
115
-
116
- expect(calls).toHaveLength(1);
117
- const call = calls[0];
118
- // Trailing slash on apiUrl is normalized; default path is /sync/push.
119
- expect(call.url).toBe("https://vault-api.example.com/sync/push");
120
- const init = call.init as {
121
- method: string;
122
- headers: Record<string, string>;
123
- body: string;
124
- };
125
- expect(init.method).toBe("POST");
126
- expect(init.headers.Authorization).toBe("Bearer tok-123");
127
- expect(init.headers["Content-Type"]).toBe("application/json");
128
- // Body is a valid encoded PushEvent that round-trips.
129
- expect(decodePushEvent(init.body)).toEqual(event);
130
- });
131
-
132
- it("POSTs a delete tombstone without contentHash or mtime", async () => {
133
- const { fetch, calls } = makeFetch({ ok: true, status: 200 });
134
- const transport = new HttpPushTransport({
135
- apiUrl: "https://vault-api.example.com/",
136
- authToken: "tok-123",
137
- fetchImpl: fetch,
138
- });
139
- const event = fakePushEvent({
140
- kind: "delete",
141
- contentHash: undefined,
142
- mtime: undefined,
143
- });
144
-
145
- await transport.publish(event);
146
-
147
- expect(calls).toHaveLength(1);
148
- const body = (calls[0].init as { body: string }).body;
149
- expect(JSON.parse(body)).toEqual({
150
- relativePath: "personal/notes/a.md",
151
- kind: "delete",
152
- originDeviceId: "device-1",
153
- originTenantId: "indigo",
154
- sequenceNumber: 0,
155
- eventTimestamp: "2026-05-21T12:00:01.000Z",
156
- });
157
- expect(decodePushEvent(body)).toEqual(JSON.parse(body));
158
- });
159
-
160
- it("resolves the auth token per-request via an async getter (self-heals across refresh)", async () => {
161
- const { fetch, calls } = makeFetch();
162
- let n = 0;
163
- const transport = new HttpPushTransport({
164
- apiUrl: "https://api.example.com",
165
- authToken: () => `tok-${n++}`,
166
- fetchImpl: fetch,
167
- });
168
- await transport.publish(fakePushEvent());
169
- await transport.publish(fakePushEvent());
170
- expect((calls[0].init as any).headers.Authorization).toBe("Bearer tok-0");
171
- expect((calls[1].init as any).headers.Authorization).toBe("Bearer tok-1");
172
- });
173
-
174
- it("honors a custom pushPath + extra headers", async () => {
175
- const { fetch, calls } = makeFetch();
176
- const transport = new HttpPushTransport({
177
- apiUrl: "https://api.example.com",
178
- pushPath: "v1/sync/push",
179
- authToken: "t",
180
- headers: { "x-hq-client-name": "hq-sync-runner" },
181
- fetchImpl: fetch,
182
- });
183
- await transport.publish(fakePushEvent());
184
- expect(calls[0].url).toBe("https://api.example.com/v1/sync/push");
185
- expect((calls[0].init as any).headers["x-hq-client-name"]).toBe(
186
- "hq-sync-runner",
187
- );
188
- });
189
-
190
- it("rejects when apiUrl is missing", () => {
191
- expect(
192
- () => new HttpPushTransport({ apiUrl: "", authToken: "t" }),
193
- ).toThrow(/apiUrl is required/);
194
- });
195
-
196
- it("rejects (does not swallow) on a non-2xx response", async () => {
197
- const { fetch } = makeFetch({ ok: false, status: 403, body: "forbidden" });
198
- const transport = new HttpPushTransport({
199
- apiUrl: "https://api.example.com",
200
- authToken: "t",
201
- fetchImpl: fetch,
202
- });
203
- await expect(transport.publish(fakePushEvent())).rejects.toThrow(/403/);
204
- });
205
-
206
- it("rejects on a malformed event before hitting the network", async () => {
207
- const { fetch, calls } = makeFetch();
208
- const transport = new HttpPushTransport({
209
- apiUrl: "https://api.example.com",
210
- authToken: "t",
211
- fetchImpl: fetch,
212
- });
213
- await expect(
214
- transport.publish(fakePushEvent({ contentHash: "not-a-hash" })),
215
- ).rejects.toBeTruthy();
216
- expect(calls).toHaveLength(0);
217
- });
218
- });
219
-
220
- // ─── 3 + 4. Emit + gating + failure handling (PushEventEmitter) ───────────────
221
-
222
- describe("PushEventEmitter — emit, gating, failure handling", () => {
223
- let dir: string;
224
- let filePath: string;
225
- const REL = "a.md";
226
-
227
- beforeEach(() => {
228
- dir = mkdtempSync(path.join(tmpdir(), "us008-"));
229
- filePath = path.join(dir, REL);
230
- writeFileSync(filePath, "hello world");
231
- });
232
-
233
- afterEach(() => {
234
- rmSync(dir, { recursive: true, force: true });
235
- });
236
-
237
- function batchFor(...rels: string[]): TreeChangeBatch {
238
- const paths = new Map<string, string>();
239
- for (const rel of rels) paths.set(path.join(dir, rel), rel);
240
- return { paths };
241
- }
242
-
243
- it("emits a valid PushEvent (sha256 hash, tenant) per changed path and ships it", async () => {
244
- const transport = new NoopPushTransport();
245
- const published: PushEvent[] = [];
246
- const spyTransport: PushTransport = {
247
- start: () => transport.start(),
248
- dispose: () => transport.dispose(),
249
- get connected() {
250
- return transport.connected;
251
- },
252
- publish: async (e) => {
253
- published.push(e);
254
- },
255
- };
256
- const emitter = new PushEventEmitter({
257
- originTenantId: "indigo",
258
- originDeviceId: "dev-1",
259
- transport: spyTransport,
260
- flagProvider: serverGranted,
261
- now: () => new Date("2026-05-21T00:00:00.000Z"),
262
- });
263
-
264
- await emitter.emitForBatch(batchFor(REL));
265
-
266
- expect(published).toHaveLength(1);
267
- const e = published[0];
268
- expect(e.relativePath).toBe(REL);
269
- expect(e.kind).toBe("upsert");
270
- expect(e.originTenantId).toBe("indigo");
271
- expect(e.originDeviceId).toBe("dev-1");
272
- const expectedHex = createHash("sha256").update("hello world").digest("hex");
273
- expect(e.contentHash).toBe(`sha256:${expectedHex}`);
274
- // Round-trips through the wire contract.
275
- expect(() => decodePushEvent(e)).not.toThrow();
276
- });
277
-
278
- it("assigns monotonic per-device sequence numbers across emits", async () => {
279
- const published: PushEvent[] = [];
280
- const emitter = new PushEventEmitter({
281
- originTenantId: "indigo",
282
- originDeviceId: "dev-1",
283
- transport: { start: async () => {}, dispose: async () => {}, connected: true, publish: async (e) => { published.push(e); } },
284
- flagProvider: serverGranted,
285
- });
286
- writeFileSync(path.join(dir, "b.md"), "second");
287
- await emitter.emitForBatch(batchFor(REL));
288
- await emitter.emitForBatch(batchFor("b.md"));
289
- expect(published.map((e) => e.sequenceNumber)).toEqual([0, 1]);
290
- });
291
-
292
- it("is DORMANT when the flag is OFF: ships nothing, attach is a no-op", async () => {
293
- const publish = vi.fn(async () => {});
294
- const emitter = new PushEventEmitter({
295
- originTenantId: "indigo",
296
- originDeviceId: "dev-1",
297
- transport: { start: async () => {}, dispose: async () => {}, connected: false, publish },
298
- flagProvider: serverDenied, // indigo NOT enabled
299
- });
300
- expect(emitter.enabled).toBe(false);
301
- await emitter.emitForBatch(batchFor(REL));
302
- expect(publish).not.toHaveBeenCalled();
303
- // attach returns a callable no-op when dormant.
304
- const fakeWatcher = { onChange: vi.fn() } as any;
305
- const off = emitter.attach(fakeWatcher);
306
- expect(fakeWatcher.onChange).not.toHaveBeenCalled();
307
- expect(() => off()).not.toThrow();
308
- });
309
-
310
- it("transport publish failure is caught + surfaced, never throws (cadence safety net)", async () => {
311
- const onError = vi.fn();
312
- const emitter = new PushEventEmitter({
313
- originTenantId: "indigo",
314
- originDeviceId: "dev-1",
315
- transport: {
316
- start: async () => {},
317
- dispose: async () => {},
318
- connected: true,
319
- publish: async () => {
320
- throw new Error("network down");
321
- },
322
- },
323
- flagProvider: serverGranted,
324
- onError,
325
- });
326
- // Must not reject — the daemon stays alive.
327
- await expect(emitter.emitForBatch(batchFor(REL))).resolves.toBeUndefined();
328
- expect(onError).toHaveBeenCalledTimes(1);
329
- expect(onError.mock.calls[0][0]).toBeInstanceOf(Error);
330
- expect(onError.mock.calls[0][1]).toMatchObject({ relativePath: REL });
331
- });
332
-
333
- it("a missing file emits a delete tombstone, and other paths still ship", async () => {
334
- const published: PushEvent[] = [];
335
- const onError = vi.fn();
336
- const emitter = new PushEventEmitter({
337
- originTenantId: "indigo",
338
- originDeviceId: "dev-1",
339
- transport: { start: async () => {}, dispose: async () => {}, connected: true, publish: async (e) => { published.push(e); } },
340
- flagProvider: serverGranted,
341
- onError,
342
- });
343
- // REL exists; "ghost.md" does not.
344
- await emitter.emitForBatch(batchFor(REL, "ghost.md"));
345
- expect(published.map((e) => e.relativePath).sort()).toEqual([
346
- REL,
347
- "ghost.md",
348
- ].sort());
349
- const tombstone = published.find((e) => e.relativePath === "ghost.md");
350
- expect(tombstone).toMatchObject({ kind: "delete" });
351
- expect(tombstone).not.toHaveProperty("contentHash");
352
- expect(tombstone).not.toHaveProperty("mtime");
353
- expect(onError).not.toHaveBeenCalled();
354
- });
355
-
356
- it("end-to-end: emit → HttpPushTransport POST with a valid schema (mocked fetch)", async () => {
357
- const { fetch, calls } = makeFetch({ ok: true, status: 200 });
358
- const transport = new HttpPushTransport({
359
- apiUrl: "https://api.example.com",
360
- authToken: "tok",
361
- fetchImpl: fetch,
362
- });
363
- const emitter = new PushEventEmitter({
364
- originTenantId: "indigo",
365
- originDeviceId: "dev-1",
366
- transport,
367
- flagProvider: serverGranted,
368
- });
369
- await transport.start();
370
- await emitter.emitForBatch(batchFor(REL));
371
-
372
- expect(calls).toHaveLength(1);
373
- expect(calls[0].url).toBe("https://api.example.com/sync/push");
374
- const posted = decodePushEvent((calls[0].init as any).body);
375
- expect(posted.relativePath).toBe(REL);
376
- expect(posted.originTenantId).toBe("indigo");
377
- });
378
- });
@@ -1,62 +0,0 @@
1
- /**
2
- * Local control for the dormant event-sync wiring.
3
- *
4
- * U15 removes client-side positive selectors. Environment values, tenant IDs,
5
- * and cached identity cannot grant authority; the authenticated server
6
- * inventory is the sole positive authority. This module preserves only the
7
- * operator's local off-switch.
8
- */
9
-
10
- /** A local-only kill switch. Any other value is deliberately non-authorizing. */
11
- export const EVENT_SYNC_LOCAL_DISABLE_ENV_VAR = "HQ_SYNC_EVENT_SYNC";
12
-
13
- /**
14
- * Compatibility seam consumed by the existing emitter. Production event-sync
15
- * supplies it only after authenticated server inventory has granted access.
16
- */
17
- export interface EventDrivenPushFlagProvider {
18
- isEnabled(scopeId: string): boolean;
19
- }
20
-
21
- /** Deprecated inert compatibility shape; no environment is ever read. */
22
- export type FlagProviderWarnFn = (
23
- message: string,
24
- context: Record<string, unknown>,
25
- ) => void;
26
-
27
- /** Deprecated inert compatibility shape; no environment is ever read. */
28
- export interface EnvTenantListFlagProviderOptions {
29
- env?: Record<string, string | undefined>;
30
- warn?: FlagProviderWarnFn;
31
- }
32
-
33
- /**
34
- * Compatibility fixture for older direct emitter tests. It represents a
35
- * previously authenticated server inventory result; the supplied legacy value
36
- * is deliberately ignored, so it cannot select a tenant or grant by email.
37
- */
38
- export class StaticFlagProvider implements EventDrivenPushFlagProvider {
39
- constructor(_authenticatedLegacyInventory: unknown) {}
40
-
41
- isEnabled(_scopeId: string): boolean {
42
- return true;
43
- }
44
- }
45
-
46
- /**
47
- * Returns true only for an explicit local disable value. Values such as
48
- * `true`, `1`, a tenant list, or an unrecognized string never enable sync.
49
- */
50
- export function isEventSyncLocallyDisabled(
51
- value: string | undefined,
52
- ): boolean {
53
- switch ((value ?? "").trim().toLowerCase()) {
54
- case "0":
55
- case "false":
56
- case "no":
57
- case "off":
58
- return true;
59
- default:
60
- return false;
61
- }
62
- }
package/src/sync/index.ts DELETED
@@ -1,76 +0,0 @@
1
- /**
2
- * @indigoai-us/hq-cloud — event-driven sync (`src/sync`) barrel.
3
- *
4
- * Surfaces the PushEvent wire contract + the PushTransport shipping seam
5
- * ported from hq-pro PR #112 (project event-driven-sync-menubar US-007).
6
- */
7
-
8
- export {
9
- CONTENT_HASH_PATTERN,
10
- ISO8601_DATETIME_PATTERN,
11
- PushEventSchema,
12
- PushEventDecodeError,
13
- encodePushEvent,
14
- decodePushEvent,
15
- } from "./push-event.js";
16
- export type {
17
- PushEvent,
18
- PushEventInput,
19
- PushEventDecodeIssue,
20
- } from "./push-event.js";
21
-
22
- export { NoopPushTransport, HttpPushTransport } from "./push-transport.js";
23
- export type {
24
- PushTransport,
25
- HttpPushTransportOptions,
26
- AuthTokenSource,
27
- FetchLike,
28
- } from "./push-transport.js";
29
-
30
- export {
31
- EVENT_SYNC_LOCAL_DISABLE_ENV_VAR,
32
- isEventSyncLocallyDisabled,
33
- StaticFlagProvider,
34
- } from "./feature-flags.js";
35
- export type {
36
- EventDrivenPushFlagProvider,
37
- FlagProviderWarnFn,
38
- EnvTenantListFlagProviderOptions,
39
- } from "./feature-flags.js";
40
-
41
- export {
42
- NoopPushReceiver,
43
- SqsPushReceiver,
44
- InMemoryFanout,
45
- InMemoryPushReceiver,
46
- createPushReceiver,
47
- DEFAULT_RECEIVER_DISPOSE_DRAIN_MS,
48
- DEFAULT_WAIT_TIME_SECONDS,
49
- DEFAULT_MAX_MESSAGES,
50
- } from "./push-receiver.js";
51
- export type {
52
- PushReceiver,
53
- PushReceiverContext,
54
- SyncEngineFn,
55
- PublishMetricFn,
56
- ReceiverLogger,
57
- SqsClientLike,
58
- SqsMessageLike,
59
- SqsPushReceiverOptions,
60
- InMemoryPushReceiverOptions,
61
- CreatePushReceiverOptions,
62
- } from "./push-receiver.js";
63
-
64
- export {
65
- SYNC_METRIC_NAMESPACE,
66
- SYNC_LATENCY_METRIC_NAME,
67
- _setSyncCloudWatchClient,
68
- publishSyncLatencyMetric,
69
- } from "./metrics.js";
70
- export type {
71
- SyncLatencyMetric,
72
- PublishSyncLatencyMetricOptions,
73
- } from "./metrics.js";
74
-
75
- export { createLogger } from "./logger.js";
76
- export type { CreateLoggerOptions, Logger } from "./logger.js";
@@ -1,128 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- import { RealtimeLeaseClient, RealtimePassAuthorizer } from "./lease-client.js";
4
- import { RealtimeRolloutClient, type RealtimeCapabilityVector } from "./realtime-rollout.js";
5
-
6
- const capability: RealtimeCapabilityVector = {
7
- hqCloud: "6.14.49",
8
- hqDesktop: "none",
9
- protocol: 2,
10
- journal: 3,
11
- durableApply: 1,
12
- };
13
-
14
- function jsonResponse(body: unknown, status = 200) {
15
- return { ok: status >= 200 && status < 300, status, text: async () => JSON.stringify(body) };
16
- }
17
-
18
- describe("RealtimeLeaseClient", () => {
19
- it("uses the pass bearer and rejects a lease longer than sixty seconds", async () => {
20
- let received: { method?: string; headers?: Record<string, string> } | undefined;
21
- const fetchImpl = async (_url: string, init?: { method?: string; headers?: Record<string, string> }) => {
22
- received = init;
23
- return jsonResponse({
24
- lease: "lease_1",
25
- issuedAt: "2026-08-12T00:00:00.000Z",
26
- expiresAt: "2026-08-12T00:01:01.000Z",
27
- authorizationEpoch: "epoch_1",
28
- });
29
- };
30
- const client = new RealtimeLeaseClient({ apiUrl: "https://api.example", fetchImpl, now: () => Date.parse("2026-08-12T00:00:00.000Z") });
31
-
32
- await expect(client.issue({ bearer: "token-a", deviceId: "machine_a", capabilityVector: capability, scopeHandle: "handle_1", scopeRevision: "rev_1" })).rejects.toThrow("invalid lease lifetime");
33
- expect(received).toMatchObject({
34
- method: "POST",
35
- headers: expect.objectContaining({ Authorization: "Bearer token-a", "X-HQ-Sync-Contract": "2" }),
36
- });
37
- });
38
-
39
- it("rejects a lease issued too far into the future", async () => {
40
- const fetchImpl = async () => jsonResponse({
41
- lease: "lease_1",
42
- issuedAt: "2026-08-12T00:00:06.000Z",
43
- expiresAt: "2026-08-12T00:01:00.000Z",
44
- authorizationEpoch: "epoch_1",
45
- });
46
- const client = new RealtimeLeaseClient({ apiUrl: "https://api.example", fetchImpl, now: () => Date.parse("2026-08-12T00:00:00.000Z") });
47
- await expect(client.issue({ bearer: "token-a", deviceId: "machine_a", capabilityVector: capability, scopeHandle: "handle_1", scopeRevision: "rev_1" })).rejects.toThrow("invalid lease lifetime");
48
- });
49
-
50
- it("resolves one bearer and freshly leases every selected scope before I/O", async () => {
51
- const now = Date.parse("2026-08-12T00:00:00.000Z");
52
- const requests: Array<{ headers?: Record<string, string> }> = [];
53
- const fetchImpl = async (url: string, init?: { headers?: Record<string, string> }) => {
54
- requests.push({ headers: init?.headers });
55
- if (url.endsWith("/scopes")) {
56
- return jsonResponse({ scopes: [
57
- { scopeUid: "prs_1", scopeHandle: "handle_1", scopeRevision: "rev_1", state: "v2-ready", cursor: "cursor_1" },
58
- { scopeUid: "cmp_1", scopeHandle: "handle_2", scopeRevision: "rev_2", state: "v2-ready", cursor: "cursor_2" },
59
- ] });
60
- }
61
- return jsonResponse({
62
- lease: "lease_issued",
63
- issuedAt: "2026-08-12T00:00:00.000Z",
64
- expiresAt: "2026-08-12T00:01:00.000Z",
65
- authorizationEpoch: "epoch_1",
66
- }, 201);
67
- };
68
- const token = vi.fn(async () => "token-a");
69
- const inventory = new RealtimeRolloutClient({ apiUrl: "https://api.example", fetchImpl, now: () => now });
70
- const leases = new RealtimeLeaseClient({ apiUrl: "https://api.example", fetchImpl, now: () => now });
71
- const authorizer = new RealtimePassAuthorizer(token, inventory, leases);
72
- const operation = vi.fn(async () => "done");
73
-
74
- await expect(authorizer.runBeforeIo({
75
- deviceId: "machine_a",
76
- capabilityVector: capability,
77
- configurationFingerprint: "config-a",
78
- scopeFingerprint: "prs_1,cmp_1",
79
- scopeUids: ["prs_1", "cmp_1"],
80
- }, operation)).resolves.toBe("done");
81
-
82
- expect(token).toHaveBeenCalledTimes(1);
83
- expect(requests).toHaveLength(3);
84
- for (const call of requests) expect(call.headers?.Authorization).toBe("Bearer token-a");
85
- expect(operation).toHaveBeenCalledTimes(1);
86
- });
87
-
88
- it("does not invoke I/O when inventory loses a requested scope", async () => {
89
- const fetchImpl = vi.fn(async () => jsonResponse({ scopes: [
90
- { scopeUid: "prs_1", scopeHandle: "handle_1", scopeRevision: "rev_1", state: "paused", reasonCode: "maintenance" },
91
- ] }));
92
- const inventory = new RealtimeRolloutClient({ apiUrl: "https://api.example", fetchImpl });
93
- const leases = new RealtimeLeaseClient({ apiUrl: "https://api.example", fetchImpl });
94
- const authorizer = new RealtimePassAuthorizer("token-a", inventory, leases);
95
- const operation = vi.fn(async () => undefined);
96
-
97
- await expect(authorizer.runBeforeIo({
98
- deviceId: "machine_a", capabilityVector: capability, configurationFingerprint: "config-a", scopeFingerprint: "prs_1", scopeUids: ["prs_1"],
99
- }, operation)).rejects.toThrow("does not authorize every requested scope");
100
- expect(operation).not.toHaveBeenCalled();
101
- });
102
-
103
- it("does not invoke I/O when parallel lease issues disagree on epoch", async () => {
104
- let leaseCalls = 0;
105
- const fetchImpl = async (url: string) => {
106
- if (url.endsWith("/scopes")) return jsonResponse({ scopes: [
107
- { scopeUid: "prs_1", scopeHandle: "handle_1", scopeRevision: "rev_1", state: "v2-ready", cursor: "cursor_1" },
108
- { scopeUid: "cmp_1", scopeHandle: "handle_2", scopeRevision: "rev_2", state: "v2-ready", cursor: "cursor_2" },
109
- ] });
110
- leaseCalls += 1;
111
- return jsonResponse({
112
- lease: `lease_${leaseCalls}`,
113
- issuedAt: "2026-08-12T00:00:00.000Z",
114
- expiresAt: "2026-08-12T00:00:30.000Z",
115
- authorizationEpoch: `epoch_${leaseCalls}`,
116
- }, 201);
117
- };
118
- const now = () => Date.parse("2026-08-12T00:00:00.000Z");
119
- const inventory = new RealtimeRolloutClient({ apiUrl: "https://api.example", fetchImpl, now });
120
- const leases = new RealtimeLeaseClient({ apiUrl: "https://api.example", fetchImpl, now });
121
- const authorizer = new RealtimePassAuthorizer("token-a", inventory, leases);
122
- const operation = vi.fn(async () => undefined);
123
- await expect(authorizer.runBeforeIo({
124
- deviceId: "machine_a", capabilityVector: capability, configurationFingerprint: "config-a", scopeFingerprint: "all", scopeUids: ["prs_1", "cmp_1"],
125
- }, operation)).rejects.toThrow("leases disagree on authorization epoch");
126
- expect(operation).not.toHaveBeenCalled();
127
- });
128
- });