@indigoai-us/hq-cloud 6.18.44-alpha.9 → 6.18.44

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 (1068) hide show
  1. package/dist/bin/sync-runner-planning.d.ts +12 -0
  2. package/dist/bin/sync-runner-planning.d.ts.map +1 -1
  3. package/dist/bin/sync-runner-planning.js +18 -0
  4. package/dist/bin/sync-runner-planning.js.map +1 -1
  5. package/dist/bin/sync-runner-watch-loop.d.ts +18 -0
  6. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  7. package/dist/bin/sync-runner-watch-loop.js +75 -6
  8. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  9. package/dist/bin/sync-runner.d.ts +22 -1
  10. package/dist/bin/sync-runner.d.ts.map +1 -1
  11. package/dist/bin/sync-runner.js +33 -4
  12. package/dist/bin/sync-runner.js.map +1 -1
  13. package/dist/cli/conflict.d.ts.map +1 -1
  14. package/dist/cli/conflict.js +2 -1
  15. package/dist/cli/conflict.js.map +1 -1
  16. package/dist/cli/rescue-core.d.ts +6 -0
  17. package/dist/cli/rescue-core.d.ts.map +1 -1
  18. package/dist/cli/rescue-core.js +10 -1
  19. package/dist/cli/rescue-core.js.map +1 -1
  20. package/dist/cli/rescue-windows-test-harness.d.ts +19 -0
  21. package/dist/cli/rescue-windows-test-harness.d.ts.map +1 -0
  22. package/dist/cli/rescue-windows-test-harness.js +443 -0
  23. package/dist/cli/rescue-windows-test-harness.js.map +1 -0
  24. package/dist/company-resolver.d.ts +22 -0
  25. package/dist/company-resolver.d.ts.map +1 -1
  26. package/dist/company-resolver.js +1 -1
  27. package/dist/company-resolver.js.map +1 -1
  28. package/dist/ignore.d.ts +4 -1
  29. package/dist/ignore.d.ts.map +1 -1
  30. package/dist/ignore.js +10 -12
  31. package/dist/ignore.js.map +1 -1
  32. package/dist/journal-snapshot-decoder.js +15 -2
  33. package/dist/journal-snapshot-decoder.js.map +1 -1
  34. package/dist/sync/durable-apply.d.ts +2 -0
  35. package/dist/sync/durable-apply.d.ts.map +1 -1
  36. package/dist/sync/durable-apply.js +9 -12
  37. package/dist/sync/durable-apply.js.map +1 -1
  38. package/dist/sync/fsync-compat.d.ts +13 -0
  39. package/dist/sync/fsync-compat.d.ts.map +1 -0
  40. package/dist/sync/fsync-compat.js +33 -0
  41. package/dist/sync/fsync-compat.js.map +1 -0
  42. package/dist/sync/multipart-uploader.d.ts.map +1 -1
  43. package/dist/sync/multipart-uploader.js +2 -1
  44. package/dist/sync/multipart-uploader.js.map +1 -1
  45. package/dist/sync/scope-inventory-client.d.ts.map +1 -1
  46. package/dist/sync/scope-inventory-client.js +7 -2
  47. package/dist/sync/scope-inventory-client.js.map +1 -1
  48. package/dist/telemetry.d.ts +31 -0
  49. package/dist/telemetry.d.ts.map +1 -1
  50. package/dist/telemetry.js +177 -17
  51. package/dist/telemetry.js.map +1 -1
  52. package/dist/usage-company-fallback.d.ts +104 -0
  53. package/dist/usage-company-fallback.d.ts.map +1 -0
  54. package/dist/usage-company-fallback.js +599 -0
  55. package/dist/usage-company-fallback.js.map +1 -0
  56. package/dist/watch-owner-lease.d.ts +3 -0
  57. package/dist/watch-owner-lease.d.ts.map +1 -1
  58. package/dist/watch-owner-lease.js +14 -3
  59. package/dist/watch-owner-lease.js.map +1 -1
  60. package/package.json +2 -2
  61. package/dist/active-company.test.d.ts +0 -2
  62. package/dist/active-company.test.d.ts.map +0 -1
  63. package/dist/active-company.test.js +0 -149
  64. package/dist/active-company.test.js.map +0 -1
  65. package/dist/agent-codex-instructions.test.d.ts +0 -2
  66. package/dist/agent-codex-instructions.test.d.ts.map +0 -1
  67. package/dist/agent-codex-instructions.test.js +0 -271
  68. package/dist/agent-codex-instructions.test.js.map +0 -1
  69. package/dist/backup-prune.test.d.ts +0 -2
  70. package/dist/backup-prune.test.d.ts.map +0 -1
  71. package/dist/backup-prune.test.js +0 -154
  72. package/dist/backup-prune.test.js.map +0 -1
  73. package/dist/bandwidth.test.d.ts +0 -2
  74. package/dist/bandwidth.test.d.ts.map +0 -1
  75. package/dist/bandwidth.test.js +0 -342
  76. package/dist/bandwidth.test.js.map +0 -1
  77. package/dist/best-effort-error.test.d.ts +0 -2
  78. package/dist/best-effort-error.test.d.ts.map +0 -1
  79. package/dist/best-effort-error.test.js +0 -54
  80. package/dist/best-effort-error.test.js.map +0 -1
  81. package/dist/bin/area-ledger-migration.test.d.ts +0 -2
  82. package/dist/bin/area-ledger-migration.test.d.ts.map +0 -1
  83. package/dist/bin/area-ledger-migration.test.js +0 -410
  84. package/dist/bin/area-ledger-migration.test.js.map +0 -1
  85. package/dist/bin/backup-prune-runner.test.d.ts +0 -2
  86. package/dist/bin/backup-prune-runner.test.d.ts.map +0 -1
  87. package/dist/bin/backup-prune-runner.test.js +0 -22
  88. package/dist/bin/backup-prune-runner.test.js.map +0 -1
  89. package/dist/bin/journal-repair.test.d.ts +0 -2
  90. package/dist/bin/journal-repair.test.d.ts.map +0 -1
  91. package/dist/bin/journal-repair.test.js +0 -53
  92. package/dist/bin/journal-repair.test.js.map +0 -1
  93. package/dist/bin/layout-compatibility.test.d.ts +0 -2
  94. package/dist/bin/layout-compatibility.test.d.ts.map +0 -1
  95. package/dist/bin/layout-compatibility.test.js +0 -87
  96. package/dist/bin/layout-compatibility.test.js.map +0 -1
  97. package/dist/bin/outpost-heartbeat.test.d.ts +0 -7
  98. package/dist/bin/outpost-heartbeat.test.d.ts.map +0 -1
  99. package/dist/bin/outpost-heartbeat.test.js +0 -84
  100. package/dist/bin/outpost-heartbeat.test.js.map +0 -1
  101. package/dist/bin/outpost-session-host.test.d.ts +0 -7
  102. package/dist/bin/outpost-session-host.test.d.ts.map +0 -1
  103. package/dist/bin/outpost-session-host.test.js +0 -52
  104. package/dist/bin/outpost-session-host.test.js.map +0 -1
  105. package/dist/bin/sync-mutation.test.d.ts +0 -2
  106. package/dist/bin/sync-mutation.test.d.ts.map +0 -1
  107. package/dist/bin/sync-mutation.test.js +0 -64
  108. package/dist/bin/sync-mutation.test.js.map +0 -1
  109. package/dist/bin/sync-runner-args.test.d.ts +0 -2
  110. package/dist/bin/sync-runner-args.test.d.ts.map +0 -1
  111. package/dist/bin/sync-runner-args.test.js +0 -82
  112. package/dist/bin/sync-runner-args.test.js.map +0 -1
  113. package/dist/bin/sync-runner-auth-wait.test.d.ts +0 -2
  114. package/dist/bin/sync-runner-auth-wait.test.d.ts.map +0 -1
  115. package/dist/bin/sync-runner-auth-wait.test.js +0 -406
  116. package/dist/bin/sync-runner-auth-wait.test.js.map +0 -1
  117. package/dist/bin/sync-runner-backoff.regression.test.d.ts +0 -29
  118. package/dist/bin/sync-runner-backoff.regression.test.d.ts.map +0 -1
  119. package/dist/bin/sync-runner-backoff.regression.test.js +0 -224
  120. package/dist/bin/sync-runner-backoff.regression.test.js.map +0 -1
  121. package/dist/bin/sync-runner-company.test.d.ts +0 -2
  122. package/dist/bin/sync-runner-company.test.d.ts.map +0 -1
  123. package/dist/bin/sync-runner-company.test.js +0 -2439
  124. package/dist/bin/sync-runner-company.test.js.map +0 -1
  125. package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.d.ts +0 -2
  126. package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.d.ts.map +0 -1
  127. package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.js +0 -648
  128. package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.js.map +0 -1
  129. package/dist/bin/sync-runner-delete-snapshot.bench.test.d.ts +0 -2
  130. package/dist/bin/sync-runner-delete-snapshot.bench.test.d.ts.map +0 -1
  131. package/dist/bin/sync-runner-delete-snapshot.bench.test.js +0 -329
  132. package/dist/bin/sync-runner-delete-snapshot.bench.test.js.map +0 -1
  133. package/dist/bin/sync-runner-events.test.d.ts +0 -2
  134. package/dist/bin/sync-runner-events.test.d.ts.map +0 -1
  135. package/dist/bin/sync-runner-events.test.js +0 -364
  136. package/dist/bin/sync-runner-events.test.js.map +0 -1
  137. package/dist/bin/sync-runner-heap-telemetry.test.d.ts +0 -2
  138. package/dist/bin/sync-runner-heap-telemetry.test.d.ts.map +0 -1
  139. package/dist/bin/sync-runner-heap-telemetry.test.js +0 -1966
  140. package/dist/bin/sync-runner-heap-telemetry.test.js.map +0 -1
  141. package/dist/bin/sync-runner-manifest.test.d.ts +0 -12
  142. package/dist/bin/sync-runner-manifest.test.d.ts.map +0 -1
  143. package/dist/bin/sync-runner-manifest.test.js +0 -921
  144. package/dist/bin/sync-runner-manifest.test.js.map +0 -1
  145. package/dist/bin/sync-runner-planning.test.d.ts +0 -2
  146. package/dist/bin/sync-runner-planning.test.d.ts.map +0 -1
  147. package/dist/bin/sync-runner-planning.test.js +0 -448
  148. package/dist/bin/sync-runner-planning.test.js.map +0 -1
  149. package/dist/bin/sync-runner-post-sync-flags.test.d.ts +0 -2
  150. package/dist/bin/sync-runner-post-sync-flags.test.d.ts.map +0 -1
  151. package/dist/bin/sync-runner-post-sync-flags.test.js +0 -64
  152. package/dist/bin/sync-runner-post-sync-flags.test.js.map +0 -1
  153. package/dist/bin/sync-runner-publish-expansion.test.d.ts +0 -2
  154. package/dist/bin/sync-runner-publish-expansion.test.d.ts.map +0 -1
  155. package/dist/bin/sync-runner-publish-expansion.test.js +0 -54
  156. package/dist/bin/sync-runner-publish-expansion.test.js.map +0 -1
  157. package/dist/bin/sync-runner-receiver-unresolved-company.test.d.ts +0 -2
  158. package/dist/bin/sync-runner-receiver-unresolved-company.test.d.ts.map +0 -1
  159. package/dist/bin/sync-runner-receiver-unresolved-company.test.js +0 -379
  160. package/dist/bin/sync-runner-receiver-unresolved-company.test.js.map +0 -1
  161. package/dist/bin/sync-runner-rollup.test.d.ts +0 -2
  162. package/dist/bin/sync-runner-rollup.test.d.ts.map +0 -1
  163. package/dist/bin/sync-runner-rollup.test.js +0 -24
  164. package/dist/bin/sync-runner-rollup.test.js.map +0 -1
  165. package/dist/bin/sync-runner-watch-loop.test.d.ts +0 -2
  166. package/dist/bin/sync-runner-watch-loop.test.d.ts.map +0 -1
  167. package/dist/bin/sync-runner-watch-loop.test.js +0 -734
  168. package/dist/bin/sync-runner-watch-loop.test.js.map +0 -1
  169. package/dist/bin/sync-runner-watch-routes.test.d.ts +0 -2
  170. package/dist/bin/sync-runner-watch-routes.test.d.ts.map +0 -1
  171. package/dist/bin/sync-runner-watch-routes.test.js +0 -181
  172. package/dist/bin/sync-runner-watch-routes.test.js.map +0 -1
  173. package/dist/bin/sync-runner.test.d.ts +0 -10
  174. package/dist/bin/sync-runner.test.d.ts.map +0 -1
  175. package/dist/bin/sync-runner.test.js +0 -18984
  176. package/dist/bin/sync-runner.test.js.map +0 -1
  177. package/dist/cli/case-alias.test.d.ts +0 -2
  178. package/dist/cli/case-alias.test.d.ts.map +0 -1
  179. package/dist/cli/case-alias.test.js +0 -96
  180. package/dist/cli/case-alias.test.js.map +0 -1
  181. package/dist/cli/conflict-recovery.test.d.ts +0 -2
  182. package/dist/cli/conflict-recovery.test.d.ts.map +0 -1
  183. package/dist/cli/conflict-recovery.test.js +0 -201
  184. package/dist/cli/conflict-recovery.test.js.map +0 -1
  185. package/dist/cli/conflict-winner.test.d.ts +0 -7
  186. package/dist/cli/conflict-winner.test.d.ts.map +0 -1
  187. package/dist/cli/conflict-winner.test.js +0 -98
  188. package/dist/cli/conflict-winner.test.js.map +0 -1
  189. package/dist/cli/doctor.test.d.ts +0 -13
  190. package/dist/cli/doctor.test.d.ts.map +0 -1
  191. package/dist/cli/doctor.test.js +0 -607
  192. package/dist/cli/doctor.test.js.map +0 -1
  193. package/dist/cli/invite.test.d.ts +0 -5
  194. package/dist/cli/invite.test.d.ts.map +0 -1
  195. package/dist/cli/invite.test.js +0 -178
  196. package/dist/cli/invite.test.js.map +0 -1
  197. package/dist/cli/onepass-pull-planner-regressions.test.d.ts +0 -2
  198. package/dist/cli/onepass-pull-planner-regressions.test.d.ts.map +0 -1
  199. package/dist/cli/onepass-pull-planner-regressions.test.js +0 -300
  200. package/dist/cli/onepass-pull-planner-regressions.test.js.map +0 -1
  201. package/dist/cli/onepass-pull-planner.test.d.ts +0 -2
  202. package/dist/cli/onepass-pull-planner.test.d.ts.map +0 -1
  203. package/dist/cli/onepass-pull-planner.test.js +0 -266
  204. package/dist/cli/onepass-pull-planner.test.js.map +0 -1
  205. package/dist/cli/push-scan-exclusions.test.d.ts +0 -2
  206. package/dist/cli/push-scan-exclusions.test.d.ts.map +0 -1
  207. package/dist/cli/push-scan-exclusions.test.js +0 -99
  208. package/dist/cli/push-scan-exclusions.test.js.map +0 -1
  209. package/dist/cli/reindex-knowledge.test.d.ts +0 -12
  210. package/dist/cli/reindex-knowledge.test.d.ts.map +0 -1
  211. package/dist/cli/reindex-knowledge.test.js +0 -260
  212. package/dist/cli/reindex-knowledge.test.js.map +0 -1
  213. package/dist/cli/reindex.test.d.ts +0 -11
  214. package/dist/cli/reindex.test.d.ts.map +0 -1
  215. package/dist/cli/reindex.test.js +0 -811
  216. package/dist/cli/reindex.test.js.map +0 -1
  217. package/dist/cli/rescue-breaker.test.d.ts +0 -2
  218. package/dist/cli/rescue-breaker.test.d.ts.map +0 -1
  219. package/dist/cli/rescue-breaker.test.js +0 -812
  220. package/dist/cli/rescue-breaker.test.js.map +0 -1
  221. package/dist/cli/rescue-classify-ordering.test.d.ts +0 -2
  222. package/dist/cli/rescue-classify-ordering.test.d.ts.map +0 -1
  223. package/dist/cli/rescue-classify-ordering.test.js +0 -500
  224. package/dist/cli/rescue-classify-ordering.test.js.map +0 -1
  225. package/dist/cli/rescue-clone-diagnostics.test.d.ts +0 -2
  226. package/dist/cli/rescue-clone-diagnostics.test.d.ts.map +0 -1
  227. package/dist/cli/rescue-clone-diagnostics.test.js +0 -101
  228. package/dist/cli/rescue-clone-diagnostics.test.js.map +0 -1
  229. package/dist/cli/rescue-clone-network-retry.test.d.ts +0 -2
  230. package/dist/cli/rescue-clone-network-retry.test.d.ts.map +0 -1
  231. package/dist/cli/rescue-clone-network-retry.test.js +0 -415
  232. package/dist/cli/rescue-clone-network-retry.test.js.map +0 -1
  233. package/dist/cli/rescue-core-stamp.test.d.ts +0 -2
  234. package/dist/cli/rescue-core-stamp.test.d.ts.map +0 -1
  235. package/dist/cli/rescue-core-stamp.test.js +0 -364
  236. package/dist/cli/rescue-core-stamp.test.js.map +0 -1
  237. package/dist/cli/rescue-drift-reconcile.test.d.ts +0 -2
  238. package/dist/cli/rescue-drift-reconcile.test.d.ts.map +0 -1
  239. package/dist/cli/rescue-drift-reconcile.test.js +0 -162
  240. package/dist/cli/rescue-drift-reconcile.test.js.map +0 -1
  241. package/dist/cli/rescue-drop-dir-symlink.test.d.ts +0 -2
  242. package/dist/cli/rescue-drop-dir-symlink.test.d.ts.map +0 -1
  243. package/dist/cli/rescue-drop-dir-symlink.test.js +0 -255
  244. package/dist/cli/rescue-drop-dir-symlink.test.js.map +0 -1
  245. package/dist/cli/rescue-exec-bit-preserve.test.d.ts +0 -2
  246. package/dist/cli/rescue-exec-bit-preserve.test.d.ts.map +0 -1
  247. package/dist/cli/rescue-exec-bit-preserve.test.js +0 -177
  248. package/dist/cli/rescue-exec-bit-preserve.test.js.map +0 -1
  249. package/dist/cli/rescue-hq-root-guard.test.d.ts +0 -2
  250. package/dist/cli/rescue-hq-root-guard.test.d.ts.map +0 -1
  251. package/dist/cli/rescue-hq-root-guard.test.js +0 -210
  252. package/dist/cli/rescue-hq-root-guard.test.js.map +0 -1
  253. package/dist/cli/rescue-journal-reconcile.test.d.ts +0 -2
  254. package/dist/cli/rescue-journal-reconcile.test.d.ts.map +0 -1
  255. package/dist/cli/rescue-journal-reconcile.test.js +0 -186
  256. package/dist/cli/rescue-journal-reconcile.test.js.map +0 -1
  257. package/dist/cli/rescue-keep-local-ignore.test.d.ts +0 -2
  258. package/dist/cli/rescue-keep-local-ignore.test.d.ts.map +0 -1
  259. package/dist/cli/rescue-keep-local-ignore.test.js +0 -315
  260. package/dist/cli/rescue-keep-local-ignore.test.js.map +0 -1
  261. package/dist/cli/rescue-move.test.d.ts +0 -2
  262. package/dist/cli/rescue-move.test.d.ts.map +0 -1
  263. package/dist/cli/rescue-move.test.js +0 -166
  264. package/dist/cli/rescue-move.test.js.map +0 -1
  265. package/dist/cli/rescue-mtime-preserve.test.d.ts +0 -2
  266. package/dist/cli/rescue-mtime-preserve.test.d.ts.map +0 -1
  267. package/dist/cli/rescue-mtime-preserve.test.js +0 -186
  268. package/dist/cli/rescue-mtime-preserve.test.js.map +0 -1
  269. package/dist/cli/rescue-preserve-copy-symlink.test.d.ts +0 -2
  270. package/dist/cli/rescue-preserve-copy-symlink.test.d.ts.map +0 -1
  271. package/dist/cli/rescue-preserve-copy-symlink.test.js +0 -483
  272. package/dist/cli/rescue-preserve-copy-symlink.test.js.map +0 -1
  273. package/dist/cli/rescue-realpath.test.d.ts +0 -2
  274. package/dist/cli/rescue-realpath.test.d.ts.map +0 -1
  275. package/dist/cli/rescue-realpath.test.js +0 -109
  276. package/dist/cli/rescue-realpath.test.js.map +0 -1
  277. package/dist/cli/rescue-restore-flag.test.d.ts +0 -2
  278. package/dist/cli/rescue-restore-flag.test.d.ts.map +0 -1
  279. package/dist/cli/rescue-restore-flag.test.js +0 -82
  280. package/dist/cli/rescue-restore-flag.test.js.map +0 -1
  281. package/dist/cli/rescue-rsync-preflight.test.d.ts +0 -2
  282. package/dist/cli/rescue-rsync-preflight.test.d.ts.map +0 -1
  283. package/dist/cli/rescue-rsync-preflight.test.js +0 -579
  284. package/dist/cli/rescue-rsync-preflight.test.js.map +0 -1
  285. package/dist/cli/rescue-rsync-status.test.d.ts +0 -2
  286. package/dist/cli/rescue-rsync-status.test.d.ts.map +0 -1
  287. package/dist/cli/rescue-rsync-status.test.js +0 -406
  288. package/dist/cli/rescue-rsync-status.test.js.map +0 -1
  289. package/dist/cli/rescue-settings-reconcile.test.d.ts +0 -2
  290. package/dist/cli/rescue-settings-reconcile.test.d.ts.map +0 -1
  291. package/dist/cli/rescue-settings-reconcile.test.js +0 -610
  292. package/dist/cli/rescue-settings-reconcile.test.js.map +0 -1
  293. package/dist/cli/rescue-snapshot-copy.test.d.ts +0 -2
  294. package/dist/cli/rescue-snapshot-copy.test.d.ts.map +0 -1
  295. package/dist/cli/rescue-snapshot-copy.test.js +0 -790
  296. package/dist/cli/rescue-snapshot-copy.test.js.map +0 -1
  297. package/dist/cli/rescue-snapshot-recovery.test.d.ts +0 -2
  298. package/dist/cli/rescue-snapshot-recovery.test.d.ts.map +0 -1
  299. package/dist/cli/rescue-snapshot-recovery.test.js +0 -265
  300. package/dist/cli/rescue-snapshot-recovery.test.js.map +0 -1
  301. package/dist/cli/rescue-snapshot.test.d.ts +0 -2
  302. package/dist/cli/rescue-snapshot.test.d.ts.map +0 -1
  303. package/dist/cli/rescue-snapshot.test.js +0 -46
  304. package/dist/cli/rescue-snapshot.test.js.map +0 -1
  305. package/dist/cli/rescue-source-switch-floor.test.d.ts +0 -2
  306. package/dist/cli/rescue-source-switch-floor.test.d.ts.map +0 -1
  307. package/dist/cli/rescue-source-switch-floor.test.js +0 -247
  308. package/dist/cli/rescue-source-switch-floor.test.js.map +0 -1
  309. package/dist/cli/rescue-unchanged-delete.test.d.ts +0 -2
  310. package/dist/cli/rescue-unchanged-delete.test.d.ts.map +0 -1
  311. package/dist/cli/rescue-unchanged-delete.test.js +0 -305
  312. package/dist/cli/rescue-unchanged-delete.test.js.map +0 -1
  313. package/dist/cli/rescue.reindex.test.d.ts +0 -2
  314. package/dist/cli/rescue.reindex.test.d.ts.map +0 -1
  315. package/dist/cli/rescue.reindex.test.js +0 -72
  316. package/dist/cli/rescue.reindex.test.js.map +0 -1
  317. package/dist/cli/rescue.test.d.ts +0 -2
  318. package/dist/cli/rescue.test.d.ts.map +0 -1
  319. package/dist/cli/rescue.test.js +0 -127
  320. package/dist/cli/rescue.test.js.map +0 -1
  321. package/dist/cli/share-plan-limit-presign.test.d.ts +0 -2
  322. package/dist/cli/share-plan-limit-presign.test.d.ts.map +0 -1
  323. package/dist/cli/share-plan-limit-presign.test.js +0 -271
  324. package/dist/cli/share-plan-limit-presign.test.js.map +0 -1
  325. package/dist/cli/share-presign-transport.test.d.ts +0 -22
  326. package/dist/cli/share-presign-transport.test.d.ts.map +0 -1
  327. package/dist/cli/share-presign-transport.test.js +0 -388
  328. package/dist/cli/share-presign-transport.test.js.map +0 -1
  329. package/dist/cli/share.test.d.ts +0 -5
  330. package/dist/cli/share.test.d.ts.map +0 -1
  331. package/dist/cli/share.test.js +0 -12335
  332. package/dist/cli/share.test.js.map +0 -1
  333. package/dist/cli/streaming-pull-planner.bench.test.d.ts +0 -15
  334. package/dist/cli/streaming-pull-planner.bench.test.d.ts.map +0 -1
  335. package/dist/cli/streaming-pull-planner.bench.test.js +0 -273
  336. package/dist/cli/streaming-pull-planner.bench.test.js.map +0 -1
  337. package/dist/cli/sync-scope.test.d.ts +0 -22
  338. package/dist/cli/sync-scope.test.d.ts.map +0 -1
  339. package/dist/cli/sync-scope.test.js +0 -903
  340. package/dist/cli/sync-scope.test.js.map +0 -1
  341. package/dist/cli/sync.test.d.ts +0 -5
  342. package/dist/cli/sync.test.d.ts.map +0 -1
  343. package/dist/cli/sync.test.js +0 -9028
  344. package/dist/cli/sync.test.js.map +0 -1
  345. package/dist/cli/tombstones.test.d.ts +0 -2
  346. package/dist/cli/tombstones.test.d.ts.map +0 -1
  347. package/dist/cli/tombstones.test.js +0 -120
  348. package/dist/cli/tombstones.test.js.map +0 -1
  349. package/dist/cli/watch-event-push-conflict.test.d.ts +0 -25
  350. package/dist/cli/watch-event-push-conflict.test.d.ts.map +0 -1
  351. package/dist/cli/watch-event-push-conflict.test.js +0 -230
  352. package/dist/cli/watch-event-push-conflict.test.js.map +0 -1
  353. package/dist/client-info.test.d.ts +0 -11
  354. package/dist/client-info.test.d.ts.map +0 -1
  355. package/dist/client-info.test.js +0 -168
  356. package/dist/client-info.test.js.map +0 -1
  357. package/dist/cognito-auth-lock-no-hard-links.test.d.ts +0 -11
  358. package/dist/cognito-auth-lock-no-hard-links.test.d.ts.map +0 -1
  359. package/dist/cognito-auth-lock-no-hard-links.test.js +0 -76
  360. package/dist/cognito-auth-lock-no-hard-links.test.js.map +0 -1
  361. package/dist/cognito-auth.test.d.ts +0 -9
  362. package/dist/cognito-auth.test.d.ts.map +0 -1
  363. package/dist/cognito-auth.test.js +0 -745
  364. package/dist/cognito-auth.test.js.map +0 -1
  365. package/dist/company-resolver.test.d.ts +0 -7
  366. package/dist/company-resolver.test.d.ts.map +0 -1
  367. package/dist/company-resolver.test.js +0 -533
  368. package/dist/company-resolver.test.js.map +0 -1
  369. package/dist/company-slug.test.d.ts +0 -7
  370. package/dist/company-slug.test.d.ts.map +0 -1
  371. package/dist/company-slug.test.js +0 -43
  372. package/dist/company-slug.test.js.map +0 -1
  373. package/dist/company-vault-transport.test.d.ts +0 -12
  374. package/dist/company-vault-transport.test.d.ts.map +0 -1
  375. package/dist/company-vault-transport.test.js +0 -97
  376. package/dist/company-vault-transport.test.js.map +0 -1
  377. package/dist/context.test.d.ts +0 -7
  378. package/dist/context.test.d.ts.map +0 -1
  379. package/dist/context.test.js +0 -646
  380. package/dist/context.test.js.map +0 -1
  381. package/dist/entity-resolver.test.d.ts +0 -10
  382. package/dist/entity-resolver.test.d.ts.map +0 -1
  383. package/dist/entity-resolver.test.js +0 -244
  384. package/dist/entity-resolver.test.js.map +0 -1
  385. package/dist/getindigo-dead-refs-contract.test.d.ts +0 -2
  386. package/dist/getindigo-dead-refs-contract.test.d.ts.map +0 -1
  387. package/dist/getindigo-dead-refs-contract.test.js +0 -58
  388. package/dist/getindigo-dead-refs-contract.test.js.map +0 -1
  389. package/dist/hermetic-test-env.test.d.ts +0 -2
  390. package/dist/hermetic-test-env.test.d.ts.map +0 -1
  391. package/dist/hermetic-test-env.test.js +0 -61
  392. package/dist/hermetic-test-env.test.js.map +0 -1
  393. package/dist/hq-root-recovery.test.d.ts +0 -2
  394. package/dist/hq-root-recovery.test.d.ts.map +0 -1
  395. package/dist/hq-root-recovery.test.js +0 -65
  396. package/dist/hq-root-recovery.test.js.map +0 -1
  397. package/dist/ignore.test.d.ts +0 -10
  398. package/dist/ignore.test.d.ts.map +0 -1
  399. package/dist/ignore.test.js +0 -926
  400. package/dist/ignore.test.js.map +0 -1
  401. package/dist/index.test.d.ts +0 -2
  402. package/dist/index.test.d.ts.map +0 -1
  403. package/dist/index.test.js +0 -26
  404. package/dist/index.test.js.map +0 -1
  405. package/dist/journal-area-ledger-read-boundary.test.d.ts +0 -2
  406. package/dist/journal-area-ledger-read-boundary.test.d.ts.map +0 -1
  407. package/dist/journal-area-ledger-read-boundary.test.js +0 -420
  408. package/dist/journal-area-ledger-read-boundary.test.js.map +0 -1
  409. package/dist/journal-baseline.test.d.ts +0 -2
  410. package/dist/journal-baseline.test.d.ts.map +0 -1
  411. package/dist/journal-baseline.test.js +0 -218
  412. package/dist/journal-baseline.test.js.map +0 -1
  413. package/dist/journal-decode-window.test.d.ts +0 -2
  414. package/dist/journal-decode-window.test.d.ts.map +0 -1
  415. package/dist/journal-decode-window.test.js +0 -99
  416. package/dist/journal-decode-window.test.js.map +0 -1
  417. package/dist/journal-delta-writes.test.d.ts +0 -2
  418. package/dist/journal-delta-writes.test.d.ts.map +0 -1
  419. package/dist/journal-delta-writes.test.js +0 -159
  420. package/dist/journal-delta-writes.test.js.map +0 -1
  421. package/dist/journal-ledger-index.test.d.ts +0 -15
  422. package/dist/journal-ledger-index.test.d.ts.map +0 -1
  423. package/dist/journal-ledger-index.test.js +0 -269
  424. package/dist/journal-ledger-index.test.js.map +0 -1
  425. package/dist/journal-public-boundary.test.d.ts +0 -2
  426. package/dist/journal-public-boundary.test.d.ts.map +0 -1
  427. package/dist/journal-public-boundary.test.js +0 -216
  428. package/dist/journal-public-boundary.test.js.map +0 -1
  429. package/dist/journal-row-store.test.d.ts +0 -2
  430. package/dist/journal-row-store.test.d.ts.map +0 -1
  431. package/dist/journal-row-store.test.js +0 -729
  432. package/dist/journal-row-store.test.js.map +0 -1
  433. package/dist/journal-snapshot-memory-helpers.d.ts +0 -33
  434. package/dist/journal-snapshot-memory-helpers.d.ts.map +0 -1
  435. package/dist/journal-snapshot-memory-helpers.js +0 -212
  436. package/dist/journal-snapshot-memory-helpers.js.map +0 -1
  437. package/dist/journal-snapshot-memory.test.d.ts +0 -2
  438. package/dist/journal-snapshot-memory.test.d.ts.map +0 -1
  439. package/dist/journal-snapshot-memory.test.js +0 -47
  440. package/dist/journal-snapshot-memory.test.js.map +0 -1
  441. package/dist/journal-snapshot-v4-memory.test.d.ts +0 -2
  442. package/dist/journal-snapshot-v4-memory.test.d.ts.map +0 -1
  443. package/dist/journal-snapshot-v4-memory.test.js +0 -24
  444. package/dist/journal-snapshot-v4-memory.test.js.map +0 -1
  445. package/dist/journal-snapshot-v4-writer-memory.test.d.ts +0 -2
  446. package/dist/journal-snapshot-v4-writer-memory.test.d.ts.map +0 -1
  447. package/dist/journal-snapshot-v4-writer-memory.test.js +0 -76
  448. package/dist/journal-snapshot-v4-writer-memory.test.js.map +0 -1
  449. package/dist/journal-snapshot-v4-writer.test.d.ts +0 -2
  450. package/dist/journal-snapshot-v4-writer.test.d.ts.map +0 -1
  451. package/dist/journal-snapshot-v4-writer.test.js +0 -282
  452. package/dist/journal-snapshot-v4-writer.test.js.map +0 -1
  453. package/dist/journal-snapshot-v4.test.d.ts +0 -2
  454. package/dist/journal-snapshot-v4.test.d.ts.map +0 -1
  455. package/dist/journal-snapshot-v4.test.js +0 -281
  456. package/dist/journal-snapshot-v4.test.js.map +0 -1
  457. package/dist/journal-store.test.d.ts +0 -2
  458. package/dist/journal-store.test.d.ts.map +0 -1
  459. package/dist/journal-store.test.js +0 -1227
  460. package/dist/journal-store.test.js.map +0 -1
  461. package/dist/journal-streaming.heap.test.d.ts +0 -2
  462. package/dist/journal-streaming.heap.test.d.ts.map +0 -1
  463. package/dist/journal-streaming.heap.test.js +0 -190
  464. package/dist/journal-streaming.heap.test.js.map +0 -1
  465. package/dist/journal.test.d.ts +0 -9
  466. package/dist/journal.test.d.ts.map +0 -1
  467. package/dist/journal.test.js +0 -1074
  468. package/dist/journal.test.js.map +0 -1
  469. package/dist/kms10-etag-safe-pull.test.d.ts +0 -2
  470. package/dist/kms10-etag-safe-pull.test.d.ts.map +0 -1
  471. package/dist/kms10-etag-safe-pull.test.js +0 -53
  472. package/dist/kms10-etag-safe-pull.test.js.map +0 -1
  473. package/dist/lib/cloud-authoritative.test.d.ts +0 -2
  474. package/dist/lib/cloud-authoritative.test.d.ts.map +0 -1
  475. package/dist/lib/cloud-authoritative.test.js +0 -42
  476. package/dist/lib/cloud-authoritative.test.js.map +0 -1
  477. package/dist/lib/conflict-reconcile.test.d.ts +0 -7
  478. package/dist/lib/conflict-reconcile.test.d.ts.map +0 -1
  479. package/dist/lib/conflict-reconcile.test.js +0 -246
  480. package/dist/lib/conflict-reconcile.test.js.map +0 -1
  481. package/dist/lib/conflict.test.d.ts +0 -9
  482. package/dist/lib/conflict.test.d.ts.map +0 -1
  483. package/dist/lib/conflict.test.js +0 -340
  484. package/dist/lib/conflict.test.js.map +0 -1
  485. package/dist/lib/describe-error.test.d.ts +0 -2
  486. package/dist/lib/describe-error.test.d.ts.map +0 -1
  487. package/dist/lib/describe-error.test.js +0 -148
  488. package/dist/lib/describe-error.test.js.map +0 -1
  489. package/dist/lib/machine-id.test.d.ts +0 -8
  490. package/dist/lib/machine-id.test.d.ts.map +0 -1
  491. package/dist/lib/machine-id.test.js +0 -228
  492. package/dist/lib/machine-id.test.js.map +0 -1
  493. package/dist/lib/net-errors.test.d.ts +0 -2
  494. package/dist/lib/net-errors.test.d.ts.map +0 -1
  495. package/dist/lib/net-errors.test.js +0 -111
  496. package/dist/lib/net-errors.test.js.map +0 -1
  497. package/dist/lib/ontology-entity-meta.test.d.ts +0 -2
  498. package/dist/lib/ontology-entity-meta.test.d.ts.map +0 -1
  499. package/dist/lib/ontology-entity-meta.test.js +0 -79
  500. package/dist/lib/ontology-entity-meta.test.js.map +0 -1
  501. package/dist/lib/ontology-entity-push.test.d.ts +0 -2
  502. package/dist/lib/ontology-entity-push.test.d.ts.map +0 -1
  503. package/dist/lib/ontology-entity-push.test.js +0 -156
  504. package/dist/lib/ontology-entity-push.test.js.map +0 -1
  505. package/dist/lib/personal-connector-settings-sync.test.d.ts +0 -2
  506. package/dist/lib/personal-connector-settings-sync.test.d.ts.map +0 -1
  507. package/dist/lib/personal-connector-settings-sync.test.js +0 -34
  508. package/dist/lib/personal-connector-settings-sync.test.js.map +0 -1
  509. package/dist/lib/readlink-safe.test.d.ts +0 -2
  510. package/dist/lib/readlink-safe.test.d.ts.map +0 -1
  511. package/dist/lib/readlink-safe.test.js +0 -34
  512. package/dist/lib/readlink-safe.test.js.map +0 -1
  513. package/dist/lib/s3-content-checksum.test.d.ts +0 -2
  514. package/dist/lib/s3-content-checksum.test.d.ts.map +0 -1
  515. package/dist/lib/s3-content-checksum.test.js +0 -26
  516. package/dist/lib/s3-content-checksum.test.js.map +0 -1
  517. package/dist/lib/skill-register-landed-bytes.test.d.ts +0 -2
  518. package/dist/lib/skill-register-landed-bytes.test.d.ts.map +0 -1
  519. package/dist/lib/skill-register-landed-bytes.test.js +0 -83
  520. package/dist/lib/skill-register-landed-bytes.test.js.map +0 -1
  521. package/dist/local-path-codec.test.d.ts +0 -14
  522. package/dist/local-path-codec.test.d.ts.map +0 -1
  523. package/dist/local-path-codec.test.js +0 -122
  524. package/dist/local-path-codec.test.js.map +0 -1
  525. package/dist/machine-auth.test.d.ts +0 -14
  526. package/dist/machine-auth.test.d.ts.map +0 -1
  527. package/dist/machine-auth.test.js +0 -1492
  528. package/dist/machine-auth.test.js.map +0 -1
  529. package/dist/machine-token-cache-isolation.test.d.ts +0 -24
  530. package/dist/machine-token-cache-isolation.test.d.ts.map +0 -1
  531. package/dist/machine-token-cache-isolation.test.js +0 -284
  532. package/dist/machine-token-cache-isolation.test.js.map +0 -1
  533. package/dist/manifest/US-002.acceptance.test.d.ts +0 -11
  534. package/dist/manifest/US-002.acceptance.test.d.ts.map +0 -1
  535. package/dist/manifest/US-002.acceptance.test.js +0 -160
  536. package/dist/manifest/US-002.acceptance.test.js.map +0 -1
  537. package/dist/manifest/US-004.acceptance.test.d.ts +0 -12
  538. package/dist/manifest/US-004.acceptance.test.d.ts.map +0 -1
  539. package/dist/manifest/US-004.acceptance.test.js +0 -173
  540. package/dist/manifest/US-004.acceptance.test.js.map +0 -1
  541. package/dist/manifest/build-manifest-resident-parity.test.d.ts +0 -2
  542. package/dist/manifest/build-manifest-resident-parity.test.d.ts.map +0 -1
  543. package/dist/manifest/build-manifest-resident-parity.test.js +0 -69
  544. package/dist/manifest/build-manifest-resident-parity.test.js.map +0 -1
  545. package/dist/manifest/build-manifest.bench.test.d.ts +0 -22
  546. package/dist/manifest/build-manifest.bench.test.d.ts.map +0 -1
  547. package/dist/manifest/build-manifest.bench.test.js +0 -930
  548. package/dist/manifest/build-manifest.bench.test.js.map +0 -1
  549. package/dist/manifest/build-manifest.test.d.ts +0 -10
  550. package/dist/manifest/build-manifest.test.d.ts.map +0 -1
  551. package/dist/manifest/build-manifest.test.js +0 -1217
  552. package/dist/manifest/build-manifest.test.js.map +0 -1
  553. package/dist/manifest/contract.test.d.ts +0 -17
  554. package/dist/manifest/contract.test.d.ts.map +0 -1
  555. package/dist/manifest/contract.test.js +0 -346
  556. package/dist/manifest/contract.test.js.map +0 -1
  557. package/dist/manifest/import-graph.test.d.ts +0 -25
  558. package/dist/manifest/import-graph.test.d.ts.map +0 -1
  559. package/dist/manifest/import-graph.test.js +0 -120
  560. package/dist/manifest/import-graph.test.js.map +0 -1
  561. package/dist/manifest/ledger-shard-isolation.test.d.ts +0 -18
  562. package/dist/manifest/ledger-shard-isolation.test.d.ts.map +0 -1
  563. package/dist/manifest/ledger-shard-isolation.test.js +0 -153
  564. package/dist/manifest/ledger-shard-isolation.test.js.map +0 -1
  565. package/dist/manifest/snapshot-store.test.d.ts +0 -11
  566. package/dist/manifest/snapshot-store.test.d.ts.map +0 -1
  567. package/dist/manifest/snapshot-store.test.js +0 -206
  568. package/dist/manifest/snapshot-store.test.js.map +0 -1
  569. package/dist/manifest/upload-manifest.test.d.ts +0 -10
  570. package/dist/manifest/upload-manifest.test.d.ts.map +0 -1
  571. package/dist/manifest/upload-manifest.test.js +0 -1335
  572. package/dist/manifest/upload-manifest.test.js.map +0 -1
  573. package/dist/manifest-reconcile.test.d.ts +0 -2
  574. package/dist/manifest-reconcile.test.d.ts.map +0 -1
  575. package/dist/manifest-reconcile.test.js +0 -1029
  576. package/dist/manifest-reconcile.test.js.map +0 -1
  577. package/dist/object-io.test.d.ts +0 -11
  578. package/dist/object-io.test.d.ts.map +0 -1
  579. package/dist/object-io.test.js +0 -2376
  580. package/dist/object-io.test.js.map +0 -1
  581. package/dist/operation-lock.test.d.ts +0 -5
  582. package/dist/operation-lock.test.d.ts.map +0 -1
  583. package/dist/operation-lock.test.js +0 -796
  584. package/dist/operation-lock.test.js.map +0 -1
  585. package/dist/outcome-telemetry.test.d.ts +0 -9
  586. package/dist/outcome-telemetry.test.d.ts.map +0 -1
  587. package/dist/outcome-telemetry.test.js +0 -663
  588. package/dist/outcome-telemetry.test.js.map +0 -1
  589. package/dist/outposts/client.test.d.ts +0 -9
  590. package/dist/outposts/client.test.d.ts.map +0 -1
  591. package/dist/outposts/client.test.js +0 -415
  592. package/dist/outposts/client.test.js.map +0 -1
  593. package/dist/outposts/command.test.d.ts +0 -18
  594. package/dist/outposts/command.test.d.ts.map +0 -1
  595. package/dist/outposts/command.test.js +0 -1230
  596. package/dist/outposts/command.test.js.map +0 -1
  597. package/dist/outposts/replica-sync.test.d.ts +0 -2
  598. package/dist/outposts/replica-sync.test.d.ts.map +0 -1
  599. package/dist/outposts/replica-sync.test.js +0 -224
  600. package/dist/outposts/replica-sync.test.js.map +0 -1
  601. package/dist/outposts/self-deploy.test.d.ts +0 -2
  602. package/dist/outposts/self-deploy.test.d.ts.map +0 -1
  603. package/dist/outposts/self-deploy.test.js +0 -218
  604. package/dist/outposts/self-deploy.test.js.map +0 -1
  605. package/dist/outposts/session-heartbeat-bounds.test.d.ts +0 -25
  606. package/dist/outposts/session-heartbeat-bounds.test.d.ts.map +0 -1
  607. package/dist/outposts/session-heartbeat-bounds.test.js +0 -156
  608. package/dist/outposts/session-heartbeat-bounds.test.js.map +0 -1
  609. package/dist/outposts/session-heartbeat-guard.test.d.ts +0 -16
  610. package/dist/outposts/session-heartbeat-guard.test.d.ts.map +0 -1
  611. package/dist/outposts/session-heartbeat-guard.test.js +0 -88
  612. package/dist/outposts/session-heartbeat-guard.test.js.map +0 -1
  613. package/dist/outposts/session-heartbeat-publisher.test.d.ts +0 -10
  614. package/dist/outposts/session-heartbeat-publisher.test.d.ts.map +0 -1
  615. package/dist/outposts/session-heartbeat-publisher.test.js +0 -153
  616. package/dist/outposts/session-heartbeat-publisher.test.js.map +0 -1
  617. package/dist/outposts/session-heartbeat-retain-guard.test.d.ts +0 -18
  618. package/dist/outposts/session-heartbeat-retain-guard.test.d.ts.map +0 -1
  619. package/dist/outposts/session-heartbeat-retain-guard.test.js +0 -106
  620. package/dist/outposts/session-heartbeat-retain-guard.test.js.map +0 -1
  621. package/dist/outposts/session-heartbeat-scan.test.d.ts +0 -20
  622. package/dist/outposts/session-heartbeat-scan.test.d.ts.map +0 -1
  623. package/dist/outposts/session-heartbeat-scan.test.js +0 -241
  624. package/dist/outposts/session-heartbeat-scan.test.js.map +0 -1
  625. package/dist/outposts/session-heartbeat.test.d.ts +0 -11
  626. package/dist/outposts/session-heartbeat.test.d.ts.map +0 -1
  627. package/dist/outposts/session-heartbeat.test.js +0 -370
  628. package/dist/outposts/session-heartbeat.test.js.map +0 -1
  629. package/dist/outposts/session-host-auth.test.d.ts +0 -16
  630. package/dist/outposts/session-host-auth.test.d.ts.map +0 -1
  631. package/dist/outposts/session-host-auth.test.js +0 -284
  632. package/dist/outposts/session-host-auth.test.js.map +0 -1
  633. package/dist/outposts/session-host-command.test.d.ts +0 -10
  634. package/dist/outposts/session-host-command.test.d.ts.map +0 -1
  635. package/dist/outposts/session-host-command.test.js +0 -180
  636. package/dist/outposts/session-host-command.test.js.map +0 -1
  637. package/dist/outposts/session-host-connection.test.d.ts +0 -18
  638. package/dist/outposts/session-host-connection.test.d.ts.map +0 -1
  639. package/dist/outposts/session-host-connection.test.js +0 -392
  640. package/dist/outposts/session-host-connection.test.js.map +0 -1
  641. package/dist/outposts/session-host-fakes.d.ts +0 -61
  642. package/dist/outposts/session-host-fakes.d.ts.map +0 -1
  643. package/dist/outposts/session-host-fakes.js +0 -115
  644. package/dist/outposts/session-host-fakes.js.map +0 -1
  645. package/dist/outposts/session-host-ndjson.test.d.ts +0 -11
  646. package/dist/outposts/session-host-ndjson.test.d.ts.map +0 -1
  647. package/dist/outposts/session-host-ndjson.test.js +0 -73
  648. package/dist/outposts/session-host-ndjson.test.js.map +0 -1
  649. package/dist/outposts/session-host-protocol.test.d.ts +0 -15
  650. package/dist/outposts/session-host-protocol.test.d.ts.map +0 -1
  651. package/dist/outposts/session-host-protocol.test.js +0 -0
  652. package/dist/outposts/session-host-protocol.test.js.map +0 -1
  653. package/dist/outposts/session-host-server.test.d.ts +0 -10
  654. package/dist/outposts/session-host-server.test.d.ts.map +0 -1
  655. package/dist/outposts/session-host-server.test.js +0 -118
  656. package/dist/outposts/session-host-server.test.js.map +0 -1
  657. package/dist/outposts/session-host-sessions.test.d.ts +0 -18
  658. package/dist/outposts/session-host-sessions.test.d.ts.map +0 -1
  659. package/dist/outposts/session-host-sessions.test.js +0 -439
  660. package/dist/outposts/session-host-sessions.test.js.map +0 -1
  661. package/dist/outposts/terminal.test.d.ts +0 -2
  662. package/dist/outposts/terminal.test.d.ts.map +0 -1
  663. package/dist/outposts/terminal.test.js +0 -146
  664. package/dist/outposts/terminal.test.js.map +0 -1
  665. package/dist/outposts/transport.test.d.ts +0 -10
  666. package/dist/outposts/transport.test.d.ts.map +0 -1
  667. package/dist/outposts/transport.test.js +0 -205
  668. package/dist/outposts/transport.test.js.map +0 -1
  669. package/dist/packed-journal-phase2.test.d.ts +0 -2
  670. package/dist/packed-journal-phase2.test.d.ts.map +0 -1
  671. package/dist/packed-journal-phase2.test.js +0 -273
  672. package/dist/packed-journal-phase2.test.js.map +0 -1
  673. package/dist/path-ancestors.test.d.ts +0 -2
  674. package/dist/path-ancestors.test.d.ts.map +0 -1
  675. package/dist/path-ancestors.test.js +0 -21
  676. package/dist/path-ancestors.test.js.map +0 -1
  677. package/dist/personal-vault-exclusions.test.d.ts +0 -22
  678. package/dist/personal-vault-exclusions.test.d.ts.map +0 -1
  679. package/dist/personal-vault-exclusions.test.js +0 -294
  680. package/dist/personal-vault-exclusions.test.js.map +0 -1
  681. package/dist/personal-vault.test.d.ts +0 -18
  682. package/dist/personal-vault.test.d.ts.map +0 -1
  683. package/dist/personal-vault.test.js +0 -1099
  684. package/dist/personal-vault.test.js.map +0 -1
  685. package/dist/plan-limit-pause.test.d.ts +0 -2
  686. package/dist/plan-limit-pause.test.d.ts.map +0 -1
  687. package/dist/plan-limit-pause.test.js +0 -150
  688. package/dist/plan-limit-pause.test.js.map +0 -1
  689. package/dist/plan-limit.fixtures.d.ts +0 -34
  690. package/dist/plan-limit.fixtures.d.ts.map +0 -1
  691. package/dist/plan-limit.fixtures.js +0 -96
  692. package/dist/plan-limit.fixtures.js.map +0 -1
  693. package/dist/plan-limit.test.d.ts +0 -2
  694. package/dist/plan-limit.test.d.ts.map +0 -1
  695. package/dist/plan-limit.test.js +0 -132
  696. package/dist/plan-limit.test.js.map +0 -1
  697. package/dist/prefix-coalesce.test.d.ts +0 -2
  698. package/dist/prefix-coalesce.test.d.ts.map +0 -1
  699. package/dist/prefix-coalesce.test.js +0 -260
  700. package/dist/prefix-coalesce.test.js.map +0 -1
  701. package/dist/private-folder-transport.test.d.ts +0 -2
  702. package/dist/private-folder-transport.test.d.ts.map +0 -1
  703. package/dist/private-folder-transport.test.js +0 -33
  704. package/dist/private-folder-transport.test.js.map +0 -1
  705. package/dist/public-surface.test.d.ts +0 -15
  706. package/dist/public-surface.test.d.ts.map +0 -1
  707. package/dist/public-surface.test.js +0 -110
  708. package/dist/public-surface.test.js.map +0 -1
  709. package/dist/qmd-reindex.test.d.ts +0 -14
  710. package/dist/qmd-reindex.test.d.ts.map +0 -1
  711. package/dist/qmd-reindex.test.js +0 -899
  712. package/dist/qmd-reindex.test.js.map +0 -1
  713. package/dist/read-only-state-dir.test.d.ts +0 -27
  714. package/dist/read-only-state-dir.test.d.ts.map +0 -1
  715. package/dist/read-only-state-dir.test.js +0 -167
  716. package/dist/read-only-state-dir.test.js.map +0 -1
  717. package/dist/remote-pull.test.d.ts +0 -2
  718. package/dist/remote-pull.test.d.ts.map +0 -1
  719. package/dist/remote-pull.test.js +0 -1147
  720. package/dist/remote-pull.test.js.map +0 -1
  721. package/dist/s3-download-stream-hash.test.d.ts +0 -2
  722. package/dist/s3-download-stream-hash.test.d.ts.map +0 -1
  723. package/dist/s3-download-stream-hash.test.js +0 -70
  724. package/dist/s3-download-stream-hash.test.js.map +0 -1
  725. package/dist/s3.list-cost.test.d.ts +0 -2
  726. package/dist/s3.list-cost.test.d.ts.map +0 -1
  727. package/dist/s3.list-cost.test.js +0 -51
  728. package/dist/s3.list-cost.test.js.map +0 -1
  729. package/dist/s3.rename-retry.test.d.ts +0 -2
  730. package/dist/s3.rename-retry.test.d.ts.map +0 -1
  731. package/dist/s3.rename-retry.test.js +0 -217
  732. package/dist/s3.rename-retry.test.js.map +0 -1
  733. package/dist/s3.symlink-materialize.test.d.ts +0 -18
  734. package/dist/s3.symlink-materialize.test.d.ts.map +0 -1
  735. package/dist/s3.symlink-materialize.test.js +0 -579
  736. package/dist/s3.symlink-materialize.test.js.map +0 -1
  737. package/dist/s3.test.d.ts +0 -9
  738. package/dist/s3.test.d.ts.map +0 -1
  739. package/dist/s3.test.js +0 -2047
  740. package/dist/s3.test.js.map +0 -1
  741. package/dist/s3.windows-portability.test.d.ts +0 -12
  742. package/dist/s3.windows-portability.test.d.ts.map +0 -1
  743. package/dist/s3.windows-portability.test.js +0 -223
  744. package/dist/s3.windows-portability.test.js.map +0 -1
  745. package/dist/schemas/signal-types.test.d.ts +0 -2
  746. package/dist/schemas/signal-types.test.d.ts.map +0 -1
  747. package/dist/schemas/signal-types.test.js +0 -65
  748. package/dist/schemas/signal-types.test.js.map +0 -1
  749. package/dist/schemas/source-channels.test.d.ts +0 -2
  750. package/dist/schemas/source-channels.test.d.ts.map +0 -1
  751. package/dist/schemas/source-channels.test.js +0 -65
  752. package/dist/schemas/source-channels.test.js.map +0 -1
  753. package/dist/scope-shrink.test.d.ts +0 -13
  754. package/dist/scope-shrink.test.d.ts.map +0 -1
  755. package/dist/scope-shrink.test.js +0 -558
  756. package/dist/scope-shrink.test.js.map +0 -1
  757. package/dist/session-log-capture.test.d.ts +0 -2
  758. package/dist/session-log-capture.test.d.ts.map +0 -1
  759. package/dist/session-log-capture.test.js +0 -533
  760. package/dist/session-log-capture.test.js.map +0 -1
  761. package/dist/session-log-retention.test.d.ts +0 -2
  762. package/dist/session-log-retention.test.d.ts.map +0 -1
  763. package/dist/session-log-retention.test.js +0 -383
  764. package/dist/session-log-retention.test.js.map +0 -1
  765. package/dist/signals/get.test.d.ts +0 -5
  766. package/dist/signals/get.test.d.ts.map +0 -1
  767. package/dist/signals/get.test.js +0 -254
  768. package/dist/signals/get.test.js.map +0 -1
  769. package/dist/signals/list.test.d.ts +0 -9
  770. package/dist/signals/list.test.d.ts.map +0 -1
  771. package/dist/signals/list.test.js +0 -330
  772. package/dist/signals/list.test.js.map +0 -1
  773. package/dist/skill-telemetry.test.d.ts +0 -2
  774. package/dist/skill-telemetry.test.d.ts.map +0 -1
  775. package/dist/skill-telemetry.test.js +0 -1484
  776. package/dist/skill-telemetry.test.js.map +0 -1
  777. package/dist/sources/get.test.d.ts +0 -5
  778. package/dist/sources/get.test.d.ts.map +0 -1
  779. package/dist/sources/get.test.js +0 -236
  780. package/dist/sources/get.test.js.map +0 -1
  781. package/dist/sources/list.test.d.ts +0 -8
  782. package/dist/sources/list.test.d.ts.map +0 -1
  783. package/dist/sources/list.test.js +0 -318
  784. package/dist/sources/list.test.js.map +0 -1
  785. package/dist/sts-credential-refresh.test.d.ts +0 -2
  786. package/dist/sts-credential-refresh.test.d.ts.map +0 -1
  787. package/dist/sts-credential-refresh.test.js +0 -181
  788. package/dist/sts-credential-refresh.test.js.map +0 -1
  789. package/dist/swallowed-error-sites.test.d.ts +0 -2
  790. package/dist/swallowed-error-sites.test.d.ts.map +0 -1
  791. package/dist/swallowed-error-sites.test.js +0 -199
  792. package/dist/swallowed-error-sites.test.js.map +0 -1
  793. package/dist/swallowed-error-w32.test.d.ts +0 -2
  794. package/dist/swallowed-error-w32.test.d.ts.map +0 -1
  795. package/dist/swallowed-error-w32.test.js +0 -171
  796. package/dist/swallowed-error-w32.test.js.map +0 -1
  797. package/dist/swallowed-error-w33.test.d.ts +0 -2
  798. package/dist/swallowed-error-w33.test.d.ts.map +0 -1
  799. package/dist/swallowed-error-w33.test.js +0 -125
  800. package/dist/swallowed-error-w33.test.js.map +0 -1
  801. package/dist/swallowed-error-w34.test.d.ts +0 -2
  802. package/dist/swallowed-error-w34.test.d.ts.map +0 -1
  803. package/dist/swallowed-error-w34.test.js +0 -110
  804. package/dist/swallowed-error-w34.test.js.map +0 -1
  805. package/dist/swallowed-error-w35.test.d.ts +0 -2
  806. package/dist/swallowed-error-w35.test.d.ts.map +0 -1
  807. package/dist/swallowed-error-w35.test.js +0 -223
  808. package/dist/swallowed-error-w35.test.js.map +0 -1
  809. package/dist/sync/area-ledger-auto-migration.test.d.ts +0 -2
  810. package/dist/sync/area-ledger-auto-migration.test.d.ts.map +0 -1
  811. package/dist/sync/area-ledger-auto-migration.test.js +0 -291
  812. package/dist/sync/area-ledger-auto-migration.test.js.map +0 -1
  813. package/dist/sync/area-ledger-migration.test.d.ts +0 -2
  814. package/dist/sync/area-ledger-migration.test.d.ts.map +0 -1
  815. package/dist/sync/area-ledger-migration.test.js +0 -554
  816. package/dist/sync/area-ledger-migration.test.js.map +0 -1
  817. package/dist/sync/area-ledger-recovery.bench.test.d.ts +0 -2
  818. package/dist/sync/area-ledger-recovery.bench.test.d.ts.map +0 -1
  819. package/dist/sync/area-ledger-recovery.bench.test.js +0 -60
  820. package/dist/sync/area-ledger-recovery.bench.test.js.map +0 -1
  821. package/dist/sync/area-ledger-writer-lock.bench.test.d.ts +0 -2
  822. package/dist/sync/area-ledger-writer-lock.bench.test.d.ts.map +0 -1
  823. package/dist/sync/area-ledger-writer-lock.bench.test.js +0 -57
  824. package/dist/sync/area-ledger-writer-lock.bench.test.js.map +0 -1
  825. package/dist/sync/area-ledger.test.d.ts +0 -2
  826. package/dist/sync/area-ledger.test.d.ts.map +0 -1
  827. package/dist/sync/area-ledger.test.js +0 -1401
  828. package/dist/sync/area-ledger.test.js.map +0 -1
  829. package/dist/sync/area-resolver.test.d.ts +0 -2
  830. package/dist/sync/area-resolver.test.d.ts.map +0 -1
  831. package/dist/sync/area-resolver.test.js +0 -234
  832. package/dist/sync/area-resolver.test.js.map +0 -1
  833. package/dist/sync/base-version-resolver.test.d.ts +0 -2
  834. package/dist/sync/base-version-resolver.test.d.ts.map +0 -1
  835. package/dist/sync/base-version-resolver.test.js +0 -24
  836. package/dist/sync/base-version-resolver.test.js.map +0 -1
  837. package/dist/sync/candidate-uploader.test.d.ts +0 -2
  838. package/dist/sync/candidate-uploader.test.d.ts.map +0 -1
  839. package/dist/sync/candidate-uploader.test.js +0 -132
  840. package/dist/sync/candidate-uploader.test.js.map +0 -1
  841. package/dist/sync/child-process-sync-worker-env.test.d.ts +0 -2
  842. package/dist/sync/child-process-sync-worker-env.test.d.ts.map +0 -1
  843. package/dist/sync/child-process-sync-worker-env.test.js +0 -38
  844. package/dist/sync/child-process-sync-worker-env.test.js.map +0 -1
  845. package/dist/sync/child-process-sync-worker-errors.test.d.ts +0 -2
  846. package/dist/sync/child-process-sync-worker-errors.test.d.ts.map +0 -1
  847. package/dist/sync/child-process-sync-worker-errors.test.js +0 -121
  848. package/dist/sync/child-process-sync-worker-errors.test.js.map +0 -1
  849. package/dist/sync/child-process-sync-worker.test.d.ts +0 -2
  850. package/dist/sync/child-process-sync-worker.test.d.ts.map +0 -1
  851. package/dist/sync/child-process-sync-worker.test.js +0 -262
  852. package/dist/sync/child-process-sync-worker.test.js.map +0 -1
  853. package/dist/sync/content-hash.test.d.ts +0 -2
  854. package/dist/sync/content-hash.test.d.ts.map +0 -1
  855. package/dist/sync/content-hash.test.js +0 -16
  856. package/dist/sync/content-hash.test.js.map +0 -1
  857. package/dist/sync/delta-client.test.d.ts +0 -2
  858. package/dist/sync/delta-client.test.d.ts.map +0 -1
  859. package/dist/sync/delta-client.test.js +0 -97
  860. package/dist/sync/delta-client.test.js.map +0 -1
  861. package/dist/sync/durable-apply.test.d.ts +0 -2
  862. package/dist/sync/durable-apply.test.d.ts.map +0 -1
  863. package/dist/sync/durable-apply.test.js +0 -180
  864. package/dist/sync/durable-apply.test.js.map +0 -1
  865. package/dist/sync/event-sync.test.d.ts +0 -14
  866. package/dist/sync/event-sync.test.d.ts.map +0 -1
  867. package/dist/sync/event-sync.test.js +0 -1714
  868. package/dist/sync/event-sync.test.js.map +0 -1
  869. package/dist/sync/feature-flags.test.d.ts +0 -24
  870. package/dist/sync/feature-flags.test.d.ts.map +0 -1
  871. package/dist/sync/feature-flags.test.js +0 -594
  872. package/dist/sync/feature-flags.test.js.map +0 -1
  873. package/dist/sync/full-pass-cap-governor.bench.test.d.ts +0 -2
  874. package/dist/sync/full-pass-cap-governor.bench.test.d.ts.map +0 -1
  875. package/dist/sync/full-pass-cap-governor.bench.test.js +0 -179
  876. package/dist/sync/full-pass-cap-governor.bench.test.js.map +0 -1
  877. package/dist/sync/full-pass-caps.test.d.ts +0 -2
  878. package/dist/sync/full-pass-caps.test.d.ts.map +0 -1
  879. package/dist/sync/full-pass-caps.test.js +0 -550
  880. package/dist/sync/full-pass-caps.test.js.map +0 -1
  881. package/dist/sync/idle-full-reconcile.test.d.ts +0 -2
  882. package/dist/sync/idle-full-reconcile.test.d.ts.map +0 -1
  883. package/dist/sync/idle-full-reconcile.test.js +0 -62
  884. package/dist/sync/idle-full-reconcile.test.js.map +0 -1
  885. package/dist/sync/inbound-event-admission.test.d.ts +0 -2
  886. package/dist/sync/inbound-event-admission.test.d.ts.map +0 -1
  887. package/dist/sync/inbound-event-admission.test.js +0 -125
  888. package/dist/sync/inbound-event-admission.test.js.map +0 -1
  889. package/dist/sync/journal-ledger-stream.test.d.ts +0 -12
  890. package/dist/sync/journal-ledger-stream.test.d.ts.map +0 -1
  891. package/dist/sync/journal-ledger-stream.test.js +0 -134
  892. package/dist/sync/journal-ledger-stream.test.js.map +0 -1
  893. package/dist/sync/lease-client.test.d.ts +0 -2
  894. package/dist/sync/lease-client.test.d.ts.map +0 -1
  895. package/dist/sync/lease-client.test.js +0 -119
  896. package/dist/sync/lease-client.test.js.map +0 -1
  897. package/dist/sync/logger.test.d.ts +0 -19
  898. package/dist/sync/logger.test.d.ts.map +0 -1
  899. package/dist/sync/logger.test.js +0 -204
  900. package/dist/sync/logger.test.js.map +0 -1
  901. package/dist/sync/metrics.test.d.ts +0 -19
  902. package/dist/sync/metrics.test.d.ts.map +0 -1
  903. package/dist/sync/metrics.test.js +0 -357
  904. package/dist/sync/metrics.test.js.map +0 -1
  905. package/dist/sync/multipart-uploader.test.d.ts +0 -2
  906. package/dist/sync/multipart-uploader.test.d.ts.map +0 -1
  907. package/dist/sync/multipart-uploader.test.js +0 -135
  908. package/dist/sync/multipart-uploader.test.js.map +0 -1
  909. package/dist/sync/mutation-client.test.d.ts +0 -2
  910. package/dist/sync/mutation-client.test.d.ts.map +0 -1
  911. package/dist/sync/mutation-client.test.js +0 -100
  912. package/dist/sync/mutation-client.test.js.map +0 -1
  913. package/dist/sync/mutation-session.test.d.ts +0 -2
  914. package/dist/sync/mutation-session.test.d.ts.map +0 -1
  915. package/dist/sync/mutation-session.test.js +0 -190
  916. package/dist/sync/mutation-session.test.js.map +0 -1
  917. package/dist/sync/parallel-sync-coordinator.test.d.ts +0 -2
  918. package/dist/sync/parallel-sync-coordinator.test.d.ts.map +0 -1
  919. package/dist/sync/parallel-sync-coordinator.test.js +0 -564
  920. package/dist/sync/parallel-sync-coordinator.test.js.map +0 -1
  921. package/dist/sync/path-leases.test.d.ts +0 -2
  922. package/dist/sync/path-leases.test.d.ts.map +0 -1
  923. package/dist/sync/path-leases.test.js +0 -51
  924. package/dist/sync/path-leases.test.js.map +0 -1
  925. package/dist/sync/per-area-maintenance.test.d.ts +0 -2
  926. package/dist/sync/per-area-maintenance.test.d.ts.map +0 -1
  927. package/dist/sync/per-area-maintenance.test.js +0 -100
  928. package/dist/sync/per-area-maintenance.test.js.map +0 -1
  929. package/dist/sync/push-event.test.d.ts +0 -15
  930. package/dist/sync/push-event.test.d.ts.map +0 -1
  931. package/dist/sync/push-event.test.js +0 -282
  932. package/dist/sync/push-event.test.js.map +0 -1
  933. package/dist/sync/push-receiver.test.d.ts +0 -25
  934. package/dist/sync/push-receiver.test.d.ts.map +0 -1
  935. package/dist/sync/push-receiver.test.js +0 -2851
  936. package/dist/sync/push-receiver.test.js.map +0 -1
  937. package/dist/sync/push-transport.test.d.ts +0 -2
  938. package/dist/sync/push-transport.test.d.ts.map +0 -1
  939. package/dist/sync/push-transport.test.js +0 -70
  940. package/dist/sync/push-transport.test.js.map +0 -1
  941. package/dist/sync/push-walk-matchers.bench.test.d.ts +0 -2
  942. package/dist/sync/push-walk-matchers.bench.test.d.ts.map +0 -1
  943. package/dist/sync/push-walk-matchers.bench.test.js +0 -230
  944. package/dist/sync/push-walk-matchers.bench.test.js.map +0 -1
  945. package/dist/sync/realtime-network-integration.test.d.ts +0 -2
  946. package/dist/sync/realtime-network-integration.test.d.ts.map +0 -1
  947. package/dist/sync/realtime-network-integration.test.js +0 -83
  948. package/dist/sync/realtime-network-integration.test.js.map +0 -1
  949. package/dist/sync/realtime-rollout.test.d.ts +0 -2
  950. package/dist/sync/realtime-rollout.test.d.ts.map +0 -1
  951. package/dist/sync/realtime-rollout.test.js +0 -94
  952. package/dist/sync/realtime-rollout.test.js.map +0 -1
  953. package/dist/sync/realtime-telemetry.test.d.ts +0 -2
  954. package/dist/sync/realtime-telemetry.test.d.ts.map +0 -1
  955. package/dist/sync/realtime-telemetry.test.js +0 -300
  956. package/dist/sync/realtime-telemetry.test.js.map +0 -1
  957. package/dist/sync/receiver-origin-config.test.d.ts +0 -2
  958. package/dist/sync/receiver-origin-config.test.d.ts.map +0 -1
  959. package/dist/sync/receiver-origin-config.test.js +0 -39
  960. package/dist/sync/receiver-origin-config.test.js.map +0 -1
  961. package/dist/sync/reconcile-cursor.test.d.ts +0 -2
  962. package/dist/sync/reconcile-cursor.test.d.ts.map +0 -1
  963. package/dist/sync/reconcile-cursor.test.js +0 -139
  964. package/dist/sync/reconcile-cursor.test.js.map +0 -1
  965. package/dist/sync/remote-file-spool.test.d.ts +0 -2
  966. package/dist/sync/remote-file-spool.test.d.ts.map +0 -1
  967. package/dist/sync/remote-file-spool.test.js +0 -77
  968. package/dist/sync/remote-file-spool.test.js.map +0 -1
  969. package/dist/sync/scope-inventory-client.test.d.ts +0 -2
  970. package/dist/sync/scope-inventory-client.test.d.ts.map +0 -1
  971. package/dist/sync/scope-inventory-client.test.js +0 -103
  972. package/dist/sync/scope-inventory-client.test.js.map +0 -1
  973. package/dist/sync/secret-path-policy.test.d.ts +0 -2
  974. package/dist/sync/secret-path-policy.test.d.ts.map +0 -1
  975. package/dist/sync/secret-path-policy.test.js +0 -183
  976. package/dist/sync/secret-path-policy.test.js.map +0 -1
  977. package/dist/sync/snapshot-client.test.d.ts +0 -2
  978. package/dist/sync/snapshot-client.test.d.ts.map +0 -1
  979. package/dist/sync/snapshot-client.test.js +0 -169
  980. package/dist/sync/snapshot-client.test.js.map +0 -1
  981. package/dist/sync/state-store.test.d.ts +0 -2
  982. package/dist/sync/state-store.test.d.ts.map +0 -1
  983. package/dist/sync/state-store.test.js +0 -1556
  984. package/dist/sync/state-store.test.js.map +0 -1
  985. package/dist/sync/sync-contract.test.d.ts +0 -2
  986. package/dist/sync/sync-contract.test.d.ts.map +0 -1
  987. package/dist/sync/sync-contract.test.js +0 -141
  988. package/dist/sync/sync-contract.test.js.map +0 -1
  989. package/dist/sync/targeted-pull-prefixes.test.d.ts +0 -2
  990. package/dist/sync/targeted-pull-prefixes.test.d.ts.map +0 -1
  991. package/dist/sync/targeted-pull-prefixes.test.js +0 -33
  992. package/dist/sync/targeted-pull-prefixes.test.js.map +0 -1
  993. package/dist/sync/targeted-upsert-exact-list.test.d.ts +0 -2
  994. package/dist/sync/targeted-upsert-exact-list.test.d.ts.map +0 -1
  995. package/dist/sync/targeted-upsert-exact-list.test.js +0 -247
  996. package/dist/sync/targeted-upsert-exact-list.test.js.map +0 -1
  997. package/dist/sync/unchanged-push-events.test.d.ts +0 -2
  998. package/dist/sync/unchanged-push-events.test.d.ts.map +0 -1
  999. package/dist/sync/unchanged-push-events.test.js +0 -76
  1000. package/dist/sync/unchanged-push-events.test.js.map +0 -1
  1001. package/dist/sync/uploader-finalization.test.d.ts +0 -2
  1002. package/dist/sync/uploader-finalization.test.d.ts.map +0 -1
  1003. package/dist/sync/uploader-finalization.test.js +0 -107
  1004. package/dist/sync/uploader-finalization.test.js.map +0 -1
  1005. package/dist/sync-core.test.d.ts +0 -2
  1006. package/dist/sync-core.test.d.ts.map +0 -1
  1007. package/dist/sync-core.test.js +0 -189
  1008. package/dist/sync-core.test.js.map +0 -1
  1009. package/dist/sync-progress.test.d.ts +0 -2
  1010. package/dist/sync-progress.test.d.ts.map +0 -1
  1011. package/dist/sync-progress.test.js +0 -277
  1012. package/dist/sync-progress.test.js.map +0 -1
  1013. package/dist/telemetry-events.test.d.ts +0 -2
  1014. package/dist/telemetry-events.test.d.ts.map +0 -1
  1015. package/dist/telemetry-events.test.js +0 -113
  1016. package/dist/telemetry-events.test.js.map +0 -1
  1017. package/dist/telemetry.test.d.ts +0 -11
  1018. package/dist/telemetry.test.d.ts.map +0 -1
  1019. package/dist/telemetry.test.js +0 -1603
  1020. package/dist/telemetry.test.js.map +0 -1
  1021. package/dist/vault-client.test.d.ts +0 -7
  1022. package/dist/vault-client.test.d.ts.map +0 -1
  1023. package/dist/vault-client.test.js +0 -1334
  1024. package/dist/vault-client.test.js.map +0 -1
  1025. package/dist/watch-coverage.test.d.ts +0 -2
  1026. package/dist/watch-coverage.test.d.ts.map +0 -1
  1027. package/dist/watch-coverage.test.js +0 -56
  1028. package/dist/watch-coverage.test.js.map +0 -1
  1029. package/dist/watch-owner-lease.test.d.ts +0 -2
  1030. package/dist/watch-owner-lease.test.d.ts.map +0 -1
  1031. package/dist/watch-owner-lease.test.js +0 -353
  1032. package/dist/watch-owner-lease.test.js.map +0 -1
  1033. package/dist/watch-parent.test.d.ts +0 -2
  1034. package/dist/watch-parent.test.d.ts.map +0 -1
  1035. package/dist/watch-parent.test.js +0 -131
  1036. package/dist/watch-parent.test.js.map +0 -1
  1037. package/dist/watch-roots.test.d.ts +0 -18
  1038. package/dist/watch-roots.test.d.ts.map +0 -1
  1039. package/dist/watch-roots.test.js +0 -395
  1040. package/dist/watch-roots.test.js.map +0 -1
  1041. package/dist/watcher-continuity-hot-path.test.d.ts +0 -2
  1042. package/dist/watcher-continuity-hot-path.test.d.ts.map +0 -1
  1043. package/dist/watcher-continuity-hot-path.test.js +0 -40
  1044. package/dist/watcher-continuity-hot-path.test.js.map +0 -1
  1045. package/dist/watcher-event-gate.test.d.ts +0 -16
  1046. package/dist/watcher-event-gate.test.d.ts.map +0 -1
  1047. package/dist/watcher-event-gate.test.js +0 -191
  1048. package/dist/watcher-event-gate.test.js.map +0 -1
  1049. package/dist/watcher-replan-sweep.test.d.ts +0 -2
  1050. package/dist/watcher-replan-sweep.test.d.ts.map +0 -1
  1051. package/dist/watcher-replan-sweep.test.js +0 -245
  1052. package/dist/watcher-replan-sweep.test.js.map +0 -1
  1053. package/dist/watcher-stdout.test.d.ts +0 -2
  1054. package/dist/watcher-stdout.test.d.ts.map +0 -1
  1055. package/dist/watcher-stdout.test.js +0 -86
  1056. package/dist/watcher-stdout.test.js.map +0 -1
  1057. package/dist/watcher.test.d.ts +0 -2
  1058. package/dist/watcher.test.d.ts.map +0 -1
  1059. package/dist/watcher.test.js +0 -4301
  1060. package/dist/watcher.test.js.map +0 -1
  1061. package/dist/watcher.warmup-cancel.test.d.ts +0 -2
  1062. package/dist/watcher.warmup-cancel.test.d.ts.map +0 -1
  1063. package/dist/watcher.warmup-cancel.test.js +0 -59
  1064. package/dist/watcher.warmup-cancel.test.js.map +0 -1
  1065. package/dist/watcher.warmup-responsive.test.d.ts +0 -2
  1066. package/dist/watcher.warmup-responsive.test.d.ts.map +0 -1
  1067. package/dist/watcher.warmup-responsive.test.js +0 -62
  1068. package/dist/watcher.warmup-responsive.test.js.map +0 -1
@@ -1,2851 +0,0 @@
1
- /**
2
- * US-009 — push-receiver unit tests.
3
- *
4
- * Covers the five required behaviors from the PRD AC, against the SQS-backed
5
- * {@link SqsPushReceiver} (mocked SQS client — NO real AWS) and the
6
- * {@link InMemoryPushReceiver} (in-process fanout analogue):
7
- *
8
- * 1. subscribe lifecycle — start opens the poll loop / subscription, dispose
9
- * stops it; double-start is a no-op; flag-OFF stays dormant.
10
- * 2. dedupe — an event with sequenceNumber <= the highest seen
11
- * for its path is skipped; per-path counters are independent.
12
- * 3. reconnect-replay — a transient receiveMessage failure backs off and
13
- * resumes; SQS retention redelivers the buffered events; dedupe absorbs
14
- * redelivery (in-memory: disconnect buffers, reconnect drains).
15
- * 4. flag gating — dormant when the per-tenant flag is OFF; no queue
16
- * polled, connected stays false.
17
- * 5. dispose drain — dispose aborts + awaits the in-flight syncFn, then
18
- * disconnects; the loop terminates with no leaked timers.
19
- *
20
- * The live per-client SQS queue is NOT provisioned yet (server provisioning
21
- * Lambda is an unbuilt follow-up — see references.md). Every test injects a
22
- * fake SQS client / in-memory fanout.
23
- */
24
- import * as fs from "node:fs";
25
- import * as os from "node:os";
26
- import * as path from "node:path";
27
- import { describe, expect, it, vi } from "vitest";
28
- import { encodePushEvent } from "./push-event.js";
29
- import { hashFile } from "../journal.js";
30
- import { InMemoryFanout, InMemoryPushReceiver, NoopPushReceiver, RealtimeWakeReceiver, SqsPushReceiver, createPushReceiver, decodeRealtimeWake, } from "./push-receiver.js";
31
- import { isInboundEventPlaceable } from "./inbound-event-admission.js";
32
- // ─── Fixtures ────────────────────────────────────────────────────────────────
33
- const TENANT = "tenant-indigo";
34
- const QUEUE_URL = "https://sqs.us-east-1.amazonaws.com/123456789012/sync-push-indigo-deviceA";
35
- function makeEvent(overrides = {}) {
36
- return {
37
- relativePath: "companies/indigo/notes.md",
38
- kind: "upsert",
39
- contentHash: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
40
- mtime: "2026-05-21T12:34:56.000Z",
41
- originDeviceId: "device-A",
42
- originTenantId: TENANT,
43
- sequenceNumber: 1,
44
- eventTimestamp: "2026-05-21T12:34:56.500Z",
45
- ...overrides,
46
- };
47
- }
48
- function msg(event, receiptHandle = `rh-${event.sequenceNumber}`) {
49
- return {
50
- Body: encodePushEvent(event),
51
- ReceiptHandle: receiptHandle,
52
- MessageId: `m-${event.relativePath}-${event.sequenceNumber}`,
53
- };
54
- }
55
- async function changeVisibilityBatchIndividually(changeVisibility, args) {
56
- const failedEntries = [];
57
- for (const entry of args.entries) {
58
- try {
59
- await changeVisibility({
60
- queueUrl: args.queueUrl,
61
- receiptHandle: entry.receiptHandle,
62
- visibilityTimeoutSeconds: args.visibilityTimeoutSeconds,
63
- });
64
- }
65
- catch (error) {
66
- const failure = error;
67
- failedEntries.push({
68
- id: entry.id,
69
- ...(failure.code === undefined ? {} : { code: failure.code }),
70
- message: failure.message ?? String(error),
71
- });
72
- }
73
- }
74
- return { failedEntries };
75
- }
76
- /**
77
- * A scriptable fake SQS client. `enqueue` adds batches that successive
78
- * `receiveMessage` calls drain (one batch per call); once drained, it blocks
79
- * on the abort signal (modeling an idle long-poll) so dispose ends it cleanly.
80
- * `failNext(n)` makes the next `n` receiveMessage calls reject (transient).
81
- */
82
- class FakeSqs {
83
- batches = [];
84
- inFlight = new Map();
85
- released = [];
86
- failCount = 0;
87
- deleted = [];
88
- visibilityChanges = [];
89
- visibilityBatchCalls = [];
90
- visibilityFailure = null;
91
- visibilityBatchFailures = new Map();
92
- receiveCalls = 0;
93
- receivedReceiptHandles = [];
94
- receiveWaitTimes = [];
95
- enqueue(messages) {
96
- this.batches.push({ messages, drainable: false });
97
- }
98
- /** A page already available to a zero-wait drain receive. */
99
- enqueueDrainable(messages) {
100
- this.batches.push({ messages, drainable: true });
101
- }
102
- failNext(n) {
103
- this.failCount += n;
104
- }
105
- failNextVisibilityChange(error = new Error("simulated visibility change failure")) {
106
- this.visibilityFailure = error;
107
- }
108
- failVisibilityBatchEntry(receiptHandle, failure = {
109
- code: "InvalidParameterValue",
110
- message: "simulated per-entry visibility failure",
111
- }) {
112
- this.visibilityBatchFailures.set(receiptHandle, failure);
113
- }
114
- async receiveMessage(args) {
115
- this.receiveCalls += 1;
116
- this.receiveWaitTimes.push(args.waitTimeSeconds);
117
- if (this.failCount > 0) {
118
- this.failCount -= 1;
119
- throw new Error("simulated SQS receive failure");
120
- }
121
- if (this.released.length > 0 && args.waitTimeSeconds > 1) {
122
- const messages = this.released.splice(0);
123
- for (const message of messages) {
124
- if (message.ReceiptHandle) {
125
- this.inFlight.set(message.ReceiptHandle, message);
126
- this.receivedReceiptHandles.push(message.ReceiptHandle);
127
- }
128
- }
129
- return { messages };
130
- }
131
- const next = this.batches[0];
132
- // Ordinary enqueued batches model work that arrives after this drain's
133
- // one-second confirmation window; only a regular long poll starts their
134
- // next receive cycle. Dedicated drainable and false-negative fakes cover
135
- // pages that must be collected by the current drain.
136
- if (next && (next.drainable || args.waitTimeSeconds > 1)) {
137
- const messages = next.messages.splice(0, args.maxMessages);
138
- if (next.messages.length === 0)
139
- this.batches.shift();
140
- for (const message of messages) {
141
- if (message.ReceiptHandle) {
142
- this.inFlight.set(message.ReceiptHandle, message);
143
- this.receivedReceiptHandles.push(message.ReceiptHandle);
144
- }
145
- }
146
- return { messages };
147
- }
148
- if (args.waitTimeSeconds === 0)
149
- return { messages: [] };
150
- // Idle long-poll: resolve empty on the next tick (or promptly on abort).
151
- return new Promise((resolve) => {
152
- if (args.signal.aborted)
153
- return resolve({ messages: [] });
154
- const t = setTimeout(() => resolve({ messages: [] }), 5);
155
- t.unref?.();
156
- args.signal.addEventListener("abort", () => {
157
- clearTimeout(t);
158
- resolve({ messages: [] });
159
- }, { once: true });
160
- });
161
- }
162
- async deleteMessage(args) {
163
- this.deleted.push(args.receiptHandle);
164
- this.inFlight.delete(args.receiptHandle);
165
- }
166
- async changeMessageVisibility(args) {
167
- this.visibilityChanges.push({
168
- receiptHandle: args.receiptHandle,
169
- visibilityTimeoutSeconds: args.visibilityTimeoutSeconds,
170
- });
171
- if (this.visibilityFailure) {
172
- const error = this.visibilityFailure;
173
- this.visibilityFailure = null;
174
- throw error;
175
- }
176
- if (args.visibilityTimeoutSeconds !== 0)
177
- return;
178
- const message = this.inFlight.get(args.receiptHandle);
179
- if (!message)
180
- throw new Error(`unknown receipt ${args.receiptHandle}`);
181
- this.inFlight.delete(args.receiptHandle);
182
- this.released.push(message);
183
- }
184
- async changeMessageVisibilityBatch(args) {
185
- this.visibilityBatchCalls.push({
186
- entries: args.entries.map(({ id, receiptHandle }) => ({ id, receiptHandle })),
187
- visibilityTimeoutSeconds: args.visibilityTimeoutSeconds,
188
- });
189
- const failedEntries = [];
190
- for (const entry of args.entries) {
191
- const failure = this.visibilityBatchFailures.get(entry.receiptHandle);
192
- if (failure) {
193
- this.visibilityBatchFailures.delete(entry.receiptHandle);
194
- failedEntries.push({ id: entry.id, ...failure });
195
- continue;
196
- }
197
- await this.changeMessageVisibility({
198
- queueUrl: args.queueUrl,
199
- receiptHandle: entry.receiptHandle,
200
- visibilityTimeoutSeconds: args.visibilityTimeoutSeconds,
201
- });
202
- }
203
- return { failedEntries };
204
- }
205
- }
206
- /** FIFO fake used to prove that a slow pass cannot stall backlog intake. */
207
- class BacklogSqs {
208
- visible;
209
- inFlight = new Map();
210
- deleted = [];
211
- released = [];
212
- received = [];
213
- constructor(messages) {
214
- this.visible = [...messages];
215
- }
216
- get remaining() {
217
- return this.visible.length + this.inFlight.size;
218
- }
219
- async receiveMessage(args) {
220
- const messages = this.visible.splice(0, args.maxMessages);
221
- if (messages.length > 0) {
222
- for (const message of messages) {
223
- if (message.ReceiptHandle) {
224
- this.inFlight.set(message.ReceiptHandle, message);
225
- this.received.push(message.ReceiptHandle);
226
- }
227
- }
228
- return { messages };
229
- }
230
- if (args.waitTimeSeconds === 0)
231
- return { messages: [] };
232
- // Drain confirmations must observe an empty queue; the outer long-poll
233
- // remains blocked until disposal so this fixture cannot busy-spin.
234
- if (args.waitTimeSeconds <= 1)
235
- return { messages: [] };
236
- return new Promise((resolve) => {
237
- if (args.signal.aborted)
238
- return resolve({ messages: [] });
239
- args.signal.addEventListener("abort", () => resolve({ messages: [] }), { once: true });
240
- });
241
- }
242
- async deleteMessage(args) {
243
- if (!this.inFlight.delete(args.receiptHandle)) {
244
- throw new Error(`delete received an unknown receipt ${args.receiptHandle}`);
245
- }
246
- this.deleted.push(args.receiptHandle);
247
- }
248
- async changeMessageVisibility(args) {
249
- if (args.visibilityTimeoutSeconds !== 0)
250
- return;
251
- const message = this.inFlight.get(args.receiptHandle);
252
- if (!message)
253
- throw new Error(`release received an unknown receipt ${args.receiptHandle}`);
254
- this.inFlight.delete(args.receiptHandle);
255
- this.released.push(args.receiptHandle);
256
- this.visible.unshift(message);
257
- }
258
- async changeMessageVisibilityBatch(args) {
259
- return changeVisibilityBatchIndividually((entry) => this.changeMessageVisibility(entry), args);
260
- }
261
- }
262
- /** Models SQS short-poll sampling: receive #2 is empty while work remains. */
263
- class FalseNegativeSqs {
264
- messages;
265
- skippedSecondReceive = false;
266
- deleted = [];
267
- receiveWaitTimes = [];
268
- constructor(messages) {
269
- this.messages = [...messages];
270
- }
271
- async receiveMessage(args) {
272
- this.receiveWaitTimes.push(args.waitTimeSeconds);
273
- if (!this.skippedSecondReceive && this.receiveWaitTimes.length === 2) {
274
- this.skippedSecondReceive = true;
275
- return { messages: [] };
276
- }
277
- const page = this.messages.splice(0, args.maxMessages);
278
- if (page.length > 0)
279
- return { messages: page };
280
- if (args.waitTimeSeconds === 0)
281
- return { messages: [] };
282
- return new Promise((resolve) => {
283
- if (args.signal.aborted)
284
- return resolve({ messages: [] });
285
- const t = setTimeout(() => resolve({ messages: [] }), 5);
286
- t.unref?.();
287
- args.signal.addEventListener("abort", () => {
288
- clearTimeout(t);
289
- resolve({ messages: [] });
290
- }, { once: true });
291
- });
292
- }
293
- async deleteMessage(args) {
294
- this.deleted.push(args.receiptHandle);
295
- }
296
- async changeMessageVisibility(_args) {
297
- // This fixture never returns more paths than its configured cap.
298
- }
299
- async changeMessageVisibilityBatch(_args) {
300
- return { failedEntries: [] };
301
- }
302
- }
303
- /**
304
- * Scaled-time SQS fixture: an omitted receive visibility lease expires before
305
- * the delayed pass can delete its receipt. A supplied 600-second lease does
306
- * not. This makes the production redelivery failure deterministic in millis.
307
- */
308
- class VisibilityExpirySqs {
309
- event;
310
- visibleAt = 0;
311
- leaseExpiresAt = 0;
312
- currentReceipt = "";
313
- nextReceipt = 0;
314
- deliveries = [];
315
- deleted = [];
316
- deleteAttempts = [];
317
- requestedVisibilityTimeouts = [];
318
- visibilityChanges = [];
319
- deleteDelayMs = 0;
320
- constructor(event) {
321
- this.event = event;
322
- }
323
- async receiveMessage(args) {
324
- this.requestedVisibilityTimeouts.push(args.visibilityTimeoutSeconds);
325
- if (Date.now() < this.visibleAt) {
326
- if (args.waitTimeSeconds <= 1)
327
- return { messages: [] };
328
- return new Promise((resolve) => {
329
- if (args.signal.aborted)
330
- return resolve({ messages: [] });
331
- args.signal.addEventListener("abort", () => resolve({ messages: [] }), { once: true });
332
- });
333
- }
334
- const receiptHandle = `visibility-rh-${++this.nextReceipt}`;
335
- this.currentReceipt = receiptHandle;
336
- this.deliveries.push(receiptHandle);
337
- // One fake second is five real milliseconds. The queue's default lease is
338
- // deliberately one fake second; the receiver's 600-second lease is 3s.
339
- this.leaseExpiresAt = Date.now() + (args.visibilityTimeoutSeconds ?? 1) * 5;
340
- this.visibleAt = this.leaseExpiresAt;
341
- return { messages: [msg(this.event, receiptHandle)] };
342
- }
343
- async deleteMessage(args) {
344
- if (this.deleteDelayMs > 0) {
345
- await new Promise((resolve) => setTimeout(resolve, this.deleteDelayMs));
346
- }
347
- if (Date.now() >= this.leaseExpiresAt) {
348
- this.visibleAt = 0;
349
- this.deleteAttempts.push({ receiptHandle: args.receiptHandle, expired: true });
350
- throw new Error("stale receipt handle after visibility expiry");
351
- }
352
- if (args.receiptHandle !== this.currentReceipt) {
353
- this.deleteAttempts.push({ receiptHandle: args.receiptHandle, expired: true });
354
- throw new Error("stale receipt handle");
355
- }
356
- this.deleted.push(args.receiptHandle);
357
- this.deleteAttempts.push({ receiptHandle: args.receiptHandle, expired: false });
358
- this.visibleAt = Number.POSITIVE_INFINITY;
359
- }
360
- async changeMessageVisibility(args) {
361
- if (args.receiptHandle !== this.currentReceipt)
362
- throw new Error("stale receipt handle");
363
- this.visibilityChanges.push({
364
- receiptHandle: args.receiptHandle,
365
- visibilityTimeoutSeconds: args.visibilityTimeoutSeconds,
366
- });
367
- this.visibleAt = Date.now() + args.visibilityTimeoutSeconds * 5;
368
- this.leaseExpiresAt = this.visibleAt;
369
- }
370
- async changeMessageVisibilityBatch(args) {
371
- return changeVisibilityBatchIndividually((entry) => this.changeMessageVisibility(entry), args);
372
- }
373
- }
374
- /** Queue fixture that honors ReceiveMessage.MaxNumberOfMessages. */
375
- class OverCapSqs {
376
- queued;
377
- inFlight = new Map();
378
- receiveCalls = [];
379
- deleted = [];
380
- releases = [];
381
- constructor(initialMessages) {
382
- this.queued = [...initialMessages];
383
- }
384
- enqueue(message) {
385
- this.queued.push(message);
386
- }
387
- async receiveMessage(args) {
388
- this.receiveCalls.push({
389
- waitTimeSeconds: args.waitTimeSeconds,
390
- maxMessages: args.maxMessages,
391
- });
392
- if (this.queued.length > 0) {
393
- const messages = this.queued.splice(0, args.maxMessages);
394
- for (const message of messages) {
395
- if (message.ReceiptHandle)
396
- this.inFlight.set(message.ReceiptHandle, message);
397
- }
398
- return { messages };
399
- }
400
- if (args.waitTimeSeconds <= 1)
401
- return { messages: [] };
402
- return new Promise((resolve) => {
403
- if (args.signal.aborted)
404
- return resolve({ messages: [] });
405
- args.signal.addEventListener("abort", () => resolve({ messages: [] }), { once: true });
406
- });
407
- }
408
- async deleteMessage(args) {
409
- this.inFlight.delete(args.receiptHandle);
410
- this.deleted.push(args.receiptHandle);
411
- }
412
- async changeMessageVisibility(args) {
413
- this.releases.push({ receiptHandle: args.receiptHandle, visibilityTimeoutSeconds: args.visibilityTimeoutSeconds });
414
- const released = this.inFlight.get(args.receiptHandle);
415
- if (!released)
416
- throw new Error("unknown receipt handle");
417
- this.inFlight.delete(args.receiptHandle);
418
- this.queued.unshift(released);
419
- }
420
- async changeMessageVisibilityBatch(args) {
421
- return changeVisibilityBatchIndividually((entry) => this.changeMessageVisibility(entry), args);
422
- }
423
- }
424
- /** Wait until `predicate()` is true or `timeoutMs` elapses. */
425
- async function waitFor(predicate, timeoutMs = 1000) {
426
- const start = Date.now();
427
- while (!predicate()) {
428
- if (Date.now() - start > timeoutMs) {
429
- throw new Error("waitFor: timed out");
430
- }
431
- await new Promise((r) => setTimeout(r, 5));
432
- }
433
- }
434
- // fast, abortable sleep so backoff doesn't slow the suite
435
- const fastSleep = (ms, signal) => new Promise((resolve) => {
436
- if (signal.aborted)
437
- return resolve();
438
- const t = setTimeout(resolve, Math.min(ms, 5));
439
- signal.addEventListener("abort", () => { clearTimeout(t); resolve(); }, { once: true });
440
- });
441
- describe("realtime v2 wake envelope", () => {
442
- it("accepts only the cursor-only normative envelope", () => {
443
- expect(decodeRealtimeWake(JSON.stringify({
444
- contractVersion: 2,
445
- eventType: "vault.changed",
446
- scopeHandle: "scope_handle_1",
447
- cursor: "cursor_hint_1",
448
- }))).toEqual({
449
- contractVersion: 2,
450
- eventType: "vault.changed",
451
- scopeHandle: "scope_handle_1",
452
- cursor: "cursor_hint_1",
453
- });
454
- });
455
- it("accepts the existing opaque cursor alphabet without interpreting it", () => {
456
- expect(decodeRealtimeWake(JSON.stringify({
457
- contractVersion: 2,
458
- eventType: "vault.changed",
459
- scopeHandle: "scope_handle_1",
460
- cursor: "cursor/+==",
461
- })).cursor).toBe("cursor/+==");
462
- });
463
- it("rejects path-bearing or extended wakes before they reach scheduling", () => {
464
- expect(() => decodeRealtimeWake(JSON.stringify({
465
- contractVersion: 2,
466
- eventType: "vault.changed",
467
- scopeHandle: "scope_handle_1",
468
- cursor: "cursor_hint_1",
469
- relativePath: "companies/indigo/private.md",
470
- }))).toThrow(/unknown field/);
471
- });
472
- it("does not compare opaque cursors and stops delivery after disposal", async () => {
473
- const sourceState = { listener: null };
474
- const source = {
475
- start: (next) => { sourceState.listener = next; },
476
- dispose: vi.fn(),
477
- };
478
- const received = [];
479
- const receiver = new RealtimeWakeReceiver({
480
- source,
481
- onWake: (wake) => received.push(wake.scopeHandle),
482
- });
483
- await receiver.start();
484
- sourceState.listener?.(JSON.stringify({ contractVersion: 2, eventType: "vault.changed", scopeHandle: "scope_a", cursor: "cursor_z" }));
485
- sourceState.listener?.(JSON.stringify({ contractVersion: 2, eventType: "vault.changed", scopeHandle: "scope_a", cursor: "cursor_a" }));
486
- expect(received).toEqual(["scope_a", "scope_a"]);
487
- await receiver.dispose();
488
- sourceState.listener?.(JSON.stringify({ contractVersion: 2, eventType: "vault.changed", scopeHandle: "scope_a", cursor: "cursor_new" }));
489
- expect(received).toEqual(["scope_a", "scope_a"]);
490
- expect(source.dispose).toHaveBeenCalledOnce();
491
- });
492
- });
493
- // ─── SqsPushReceiver ─────────────────────────────────────────────────────────
494
- describe("US-OBS: SqsPushReceiver — origin observability", () => {
495
- async function collectOriginRecords(logOrigins) {
496
- const sqs = new FakeSqs();
497
- const records = [];
498
- sqs.enqueue([msg(makeEvent({ originDeviceId: "device-origin", sequenceNumber: 1 }))]);
499
- const receiver = new SqsPushReceiver({
500
- tenantId: TENANT,
501
- queueUrl: QUEUE_URL,
502
- sqs,
503
- syncBatchFn: async () => undefined,
504
- sleep: fastSleep,
505
- publishMetric: async () => undefined,
506
- ...(logOrigins === undefined ? {} : { logOrigins }),
507
- logger: {
508
- debug: () => undefined,
509
- warn: () => undefined,
510
- error: () => undefined,
511
- info: (record) => { records.push(record); },
512
- },
513
- });
514
- await receiver.start();
515
- await waitFor(() => records.some((record) => record.event === "fanout.receive") &&
516
- records.some((record) => record.event === "receiver.drain"));
517
- await receiver.dispose();
518
- return records;
519
- }
520
- it("omits both origin fields by default", async () => {
521
- const records = await collectOriginRecords();
522
- const fanout = records.find((record) => record.event === "fanout.receive");
523
- const drain = records.find((record) => record.event === "receiver.drain");
524
- expect(fanout).toBeDefined();
525
- expect(drain).toBeDefined();
526
- expect([
527
- Object.hasOwn(fanout, "originDeviceId"),
528
- Object.hasOwn(drain, "origins"),
529
- ]).toEqual([false, false]);
530
- });
531
- it("omits both origin fields when logOrigins is false", async () => {
532
- const records = await collectOriginRecords(false);
533
- const fanout = records.find((record) => record.event === "fanout.receive");
534
- const drain = records.find((record) => record.event === "receiver.drain");
535
- expect(fanout).toBeDefined();
536
- expect(drain).toBeDefined();
537
- expect([
538
- Object.hasOwn(fanout, "originDeviceId"),
539
- Object.hasOwn(drain, "origins"),
540
- ]).toEqual([false, false]);
541
- });
542
- it("includes both origin fields when logOrigins is true", async () => {
543
- const records = await collectOriginRecords(true);
544
- const fanout = records.find((record) => record.event === "fanout.receive");
545
- const drain = records.find((record) => record.event === "receiver.drain");
546
- expect(fanout).toMatchObject({ originDeviceId: "device-origin" });
547
- expect(drain).toMatchObject({ origins: { "device-origin": 1 } });
548
- });
549
- it("includes the event originDeviceId in each fanout.receive record", async () => {
550
- const sqs = new FakeSqs();
551
- const receivedRecords = [];
552
- sqs.enqueue([msg(makeEvent({ originDeviceId: "device-origin", sequenceNumber: 1 }))]);
553
- const receiver = new SqsPushReceiver({
554
- tenantId: TENANT,
555
- queueUrl: QUEUE_URL,
556
- sqs,
557
- syncBatchFn: async () => undefined,
558
- sleep: fastSleep,
559
- publishMetric: async () => undefined,
560
- logOrigins: true,
561
- logger: {
562
- debug: () => undefined,
563
- warn: () => undefined,
564
- error: () => undefined,
565
- info: (record) => { receivedRecords.push(record); },
566
- },
567
- });
568
- await receiver.start();
569
- await waitFor(() => receivedRecords.some((record) => record.event === "fanout.receive" && record.relativePath === "companies/indigo/notes.md"));
570
- await receiver.dispose();
571
- expect(receivedRecords.find((record) => record.event === "fanout.receive"))
572
- .toMatchObject({ originDeviceId: "device-origin" });
573
- });
574
- it("counts filtered and processed origin events in the receiver.drain record", async () => {
575
- const sqs = new FakeSqs();
576
- const drainRecords = [];
577
- sqs.enqueueDrainable([
578
- msg(makeEvent({ originDeviceId: "device-A", relativePath: "companies/indigo/a.md", sequenceNumber: 1 }), "rh-origin-a1"),
579
- msg(makeEvent({ originDeviceId: "device-A", relativePath: "companies/indigo/b.md", sequenceNumber: 2 }), "rh-origin-a2"),
580
- msg(makeEvent({ originDeviceId: "device-self", relativePath: "companies/indigo/self.md", sequenceNumber: 3 }), "rh-origin-self"),
581
- ]);
582
- const receiver = new SqsPushReceiver({
583
- tenantId: TENANT,
584
- queueUrl: QUEUE_URL,
585
- sqs,
586
- shouldProcess: (event) => event.originDeviceId !== "device-self",
587
- syncBatchFn: async () => undefined,
588
- sleep: fastSleep,
589
- publishMetric: async () => undefined,
590
- logOrigins: true,
591
- logger: {
592
- debug: () => undefined,
593
- warn: () => undefined,
594
- error: () => undefined,
595
- info: (record) => {
596
- if (record.event === "receiver.drain")
597
- drainRecords.push(record);
598
- },
599
- },
600
- });
601
- await receiver.start();
602
- await waitFor(() => drainRecords.some((record) => record.received === 3));
603
- await receiver.dispose();
604
- expect(drainRecords.find((record) => record.received === 3)?.origins)
605
- .toEqual({ "device-A": 2, "device-self": 1 });
606
- });
607
- it("caps receiver.drain origins at twenty device keys plus _other", async () => {
608
- const sqs = new FakeSqs();
609
- const drainRecords = [];
610
- const events = Array.from({ length: 25 }, (_, index) => makeEvent({
611
- originDeviceId: `device-${String(index).padStart(2, "0")}`,
612
- relativePath: `companies/indigo/origins/${index}.md`,
613
- sequenceNumber: index + 1,
614
- }));
615
- sqs.enqueueDrainable(events.map((event, index) => msg(event, `rh-origin-cap-${index}`)));
616
- const receiver = new SqsPushReceiver({
617
- tenantId: TENANT,
618
- queueUrl: QUEUE_URL,
619
- sqs,
620
- syncBatchFn: async () => undefined,
621
- sleep: fastSleep,
622
- publishMetric: async () => undefined,
623
- logOrigins: true,
624
- logger: {
625
- debug: () => undefined,
626
- warn: () => undefined,
627
- error: () => undefined,
628
- info: (record) => {
629
- if (record.event === "receiver.drain")
630
- drainRecords.push(record);
631
- },
632
- },
633
- });
634
- await receiver.start();
635
- await waitFor(() => drainRecords.some((record) => record.received === 25));
636
- await receiver.dispose();
637
- const origins = drainRecords.find((record) => record.received === 25)?.origins;
638
- expect(origins).toEqual({
639
- ...Object.fromEntries(Array.from({ length: 20 }, (_, index) => [
640
- `device-${String(index).padStart(2, "0")}`,
641
- 1,
642
- ])),
643
- _other: 5,
644
- });
645
- expect(Object.keys(origins)).toHaveLength(21);
646
- });
647
- });
648
- describe("US-009: SqsPushReceiver — subscribe lifecycle", () => {
649
- it("start opens the poll loop and processes a received event via syncFn, then deletes it", async () => {
650
- const sqs = new FakeSqs();
651
- const seen = [];
652
- const syncFn = async (ctx) => {
653
- seen.push(ctx.event.relativePath);
654
- };
655
- sqs.enqueue([msg(makeEvent({ sequenceNumber: 1 }))]);
656
- const receiver = new SqsPushReceiver({
657
- tenantId: TENANT,
658
- queueUrl: QUEUE_URL,
659
- sqs,
660
- syncFn,
661
- sleep: fastSleep,
662
- });
663
- await receiver.start();
664
- expect(receiver.connected).toBe(true);
665
- await waitFor(() => receiver.processedCount === 1);
666
- expect(seen).toEqual(["companies/indigo/notes.md"]);
667
- expect(sqs.deleted).toContain("rh-1");
668
- await receiver.dispose();
669
- expect(receiver.connected).toBe(false);
670
- });
671
- it("double-start is a no-op (single poll loop)", async () => {
672
- const sqs = new FakeSqs();
673
- const receiver = new SqsPushReceiver({
674
- tenantId: TENANT,
675
- queueUrl: QUEUE_URL,
676
- sqs,
677
- syncFn: async () => undefined,
678
- sleep: fastSleep,
679
- });
680
- await receiver.start();
681
- await receiver.start();
682
- // Let a couple poll iterations happen; a duplicated loop would roughly
683
- // double the receive call rate. We just assert it stays connected + clean.
684
- await new Promise((r) => setTimeout(r, 30));
685
- expect(receiver.connected).toBe(true);
686
- await receiver.dispose();
687
- });
688
- it("subscribes ONLY to its own tenant's queue (cross-tenant isolation boundary)", async () => {
689
- const sqs = new FakeSqs();
690
- const urls = [];
691
- const spy = {
692
- receiveMessage: (a) => {
693
- urls.push(a.queueUrl);
694
- return sqs.receiveMessage(a);
695
- },
696
- deleteMessage: (a) => sqs.deleteMessage(a),
697
- changeMessageVisibility: (a) => sqs.changeMessageVisibility(a),
698
- changeMessageVisibilityBatch: (a) => sqs.changeMessageVisibilityBatch(a),
699
- };
700
- const receiver = new SqsPushReceiver({
701
- tenantId: TENANT,
702
- queueUrl: QUEUE_URL,
703
- sqs: spy,
704
- syncFn: async () => undefined,
705
- sleep: fastSleep,
706
- });
707
- await receiver.start();
708
- await waitFor(() => urls.length >= 1);
709
- await receiver.dispose();
710
- expect(new Set(urls)).toEqual(new Set([QUEUE_URL]));
711
- });
712
- it("constructor rejects empty tenantId / queueUrl", () => {
713
- expect(() => new SqsPushReceiver({
714
- tenantId: "",
715
- queueUrl: QUEUE_URL,
716
- sqs: new FakeSqs(),
717
- syncFn: async () => undefined,
718
- })).toThrow(/tenantId is required/);
719
- expect(() => new SqsPushReceiver({
720
- tenantId: TENANT,
721
- queueUrl: "",
722
- sqs: new FakeSqs(),
723
- syncFn: async () => undefined,
724
- })).toThrow(/queueUrl is required/);
725
- });
726
- });
727
- describe("receiver drain diagnostics", () => {
728
- it("acknowledges paths proven unchanged without dispatching a pull", async () => {
729
- const event = makeEvent({ relativePath: "companies/indigo/unchanged.md" });
730
- const sqs = new FakeSqs();
731
- sqs.enqueueDrainable([msg(event, "rh-unchanged")]);
732
- const syncBatchFn = vi.fn(async () => undefined);
733
- const drainRecords = [];
734
- const receiver = new SqsPushReceiver({
735
- tenantId: TENANT,
736
- queueUrl: QUEUE_URL,
737
- sqs,
738
- syncBatchFn,
739
- skipUnchangedFn: async (events) => new Set(events.map((entry) => entry.relativePath)),
740
- sleep: fastSleep,
741
- publishMetric: async () => undefined,
742
- logger: {
743
- info: (record) => {
744
- if (record.event === "receiver.drain")
745
- drainRecords.push(record);
746
- },
747
- warn: () => undefined,
748
- error: () => undefined,
749
- debug: () => undefined,
750
- },
751
- });
752
- await receiver.start();
753
- await waitFor(() => sqs.deleted.length === 1 && drainRecords.length === 1);
754
- await receiver.dispose();
755
- expect(sqs.deleted).toEqual(["rh-unchanged"]);
756
- expect(syncBatchFn).not.toHaveBeenCalled();
757
- expect(drainRecords[0]).toMatchObject({
758
- received: 1,
759
- coalesced: 1,
760
- processed: 0,
761
- skippedUnchanged: 1,
762
- deleted: 1,
763
- remaining: 0,
764
- });
765
- });
766
- it("falls back to a targeted pull when the unchanged proof fails", async () => {
767
- const event = makeEvent({ relativePath: "companies/indigo/proof-error.md" });
768
- const sqs = new FakeSqs();
769
- sqs.enqueueDrainable([msg(event, "rh-proof-error")]);
770
- const syncBatchFn = vi.fn(async () => undefined);
771
- const warnings = [];
772
- const receiver = new SqsPushReceiver({
773
- tenantId: TENANT,
774
- queueUrl: QUEUE_URL,
775
- sqs,
776
- syncBatchFn,
777
- skipUnchangedFn: async () => { throw new Error("journal unavailable"); },
778
- sleep: fastSleep,
779
- publishMetric: async () => undefined,
780
- logger: {
781
- info: () => undefined,
782
- warn: (record) => warnings.push(record),
783
- error: () => undefined,
784
- debug: () => undefined,
785
- },
786
- });
787
- await receiver.start();
788
- await waitFor(() => sqs.deleted.length === 1 && syncBatchFn.mock.calls.length === 1);
789
- await receiver.dispose();
790
- expect(syncBatchFn.mock.calls[0]?.[0].events).toEqual([event]);
791
- expect(warnings).toContainEqual(expect.objectContaining({
792
- event: "receiver.unchanged.check.failed",
793
- err: { message: "journal unavailable" },
794
- }));
795
- });
796
- it("reports redelivery count, oldest sent age, and filtered self echoes", async () => {
797
- const nowMs = 10_000;
798
- const sqs = new FakeSqs();
799
- sqs.enqueueDrainable([
800
- {
801
- ...msg(makeEvent({ relativePath: "companies/indigo/first.md", sequenceNumber: 1 })),
802
- approximateReceiveCount: 1,
803
- sentTimestampMs: 7_000,
804
- },
805
- {
806
- ...msg(makeEvent({ relativePath: "companies/indigo/second.md", sequenceNumber: 2 })),
807
- approximateReceiveCount: 3,
808
- sentTimestampMs: 8_000,
809
- },
810
- {
811
- ...msg(makeEvent({
812
- relativePath: "companies/indigo/self.md",
813
- sequenceNumber: 3,
814
- originDeviceId: "this-device",
815
- })),
816
- approximateReceiveCount: 2,
817
- sentTimestampMs: 9_000,
818
- },
819
- ]);
820
- const drainRecords = [];
821
- const receiver = new SqsPushReceiver({
822
- tenantId: TENANT,
823
- queueUrl: QUEUE_URL,
824
- sqs,
825
- shouldProcess: (event) => event.originDeviceId !== "this-device",
826
- syncBatchFn: async () => undefined,
827
- sleep: fastSleep,
828
- now: () => nowMs,
829
- publishMetric: async () => undefined,
830
- logger: {
831
- info: (record) => {
832
- if (record.event === "receiver.drain")
833
- drainRecords.push(record);
834
- },
835
- warn: () => undefined,
836
- error: () => undefined,
837
- debug: () => undefined,
838
- },
839
- });
840
- await receiver.start();
841
- await waitFor(() => sqs.deleted.length === 3);
842
- await receiver.dispose();
843
- expect(sqs.deleted).toHaveLength(3);
844
- expect(drainRecords).toHaveLength(1);
845
- expect(drainRecords[0]).toMatchObject({
846
- received: 3,
847
- coalesced: 2,
848
- processed: 2,
849
- deleted: 3,
850
- released: 0,
851
- remaining: 0,
852
- redelivered: 2,
853
- maxReceiveCount: 3,
854
- oldestAgeMs: 3_000,
855
- filtered: 1,
856
- });
857
- });
858
- it("clamps oldest message age to zero when the local clock trails SQS", async () => {
859
- const sqs = new FakeSqs();
860
- sqs.enqueueDrainable([{
861
- ...msg(makeEvent()),
862
- sentTimestampMs: 11_000,
863
- }]);
864
- const drainRecords = [];
865
- const receiver = new SqsPushReceiver({
866
- tenantId: TENANT,
867
- queueUrl: QUEUE_URL,
868
- sqs,
869
- syncBatchFn: async () => undefined,
870
- sleep: fastSleep,
871
- now: () => 10_000,
872
- publishMetric: async () => undefined,
873
- logger: {
874
- info: (record) => {
875
- if (record.event === "receiver.drain")
876
- drainRecords.push(record);
877
- },
878
- warn: () => undefined,
879
- error: () => undefined,
880
- debug: () => undefined,
881
- },
882
- });
883
- await receiver.start();
884
- await waitFor(() => sqs.deleted.length === 1);
885
- await receiver.dispose();
886
- expect(drainRecords).toHaveLength(1);
887
- expect(drainRecords[0]?.oldestAgeMs).toBe(0);
888
- });
889
- });
890
- describe("US-009: SqsPushReceiver — dedupe", () => {
891
- it("REGRESSION: collapses 500 received messages across 3 paths into one sync pass", async () => {
892
- const sqs = new FakeSqs();
893
- const batches = [];
894
- const syncBatchFn = async (ctx) => {
895
- batches.push(ctx);
896
- };
897
- const paths = [
898
- "companies/indigo/a.md",
899
- "companies/indigo/b.md",
900
- "companies/indigo/c.md",
901
- ];
902
- const messages = Array.from({ length: 500 }, (_, index) => msg(makeEvent({
903
- relativePath: paths[index % paths.length],
904
- sequenceNumber: index + 1,
905
- }), `rh-batch-${index}`));
906
- for (let start = 0; start < messages.length; start += 10) {
907
- sqs.enqueueDrainable(messages.slice(start, start + 10));
908
- }
909
- const receiver = new SqsPushReceiver({
910
- tenantId: TENANT,
911
- queueUrl: QUEUE_URL,
912
- sqs,
913
- syncBatchFn,
914
- sleep: fastSleep,
915
- });
916
- await receiver.start();
917
- await waitFor(() => receiver.processedCount === 3);
918
- await receiver.dispose();
919
- expect(batches).toHaveLength(1);
920
- expect(batches[0].events.map((event) => event.relativePath).sort()).toEqual(paths);
921
- });
922
- it("fills a 500-receipt drain after an empty short-poll false negative", async () => {
923
- const receipts = Array.from({ length: 500 }, (_, index) => msg(makeEvent({
924
- relativePath: `companies/indigo/false-negative/${index}.md`,
925
- sequenceNumber: index + 1,
926
- }), `rh-false-negative-${index}`));
927
- const sqs = new FalseNegativeSqs(receipts);
928
- const batches = [];
929
- const receiver = new SqsPushReceiver({
930
- tenantId: TENANT,
931
- queueUrl: QUEUE_URL,
932
- sqs,
933
- drainMaxMessages: 500,
934
- batchMaxPaths: 500,
935
- syncBatchFn: async (ctx) => { batches.push(ctx); },
936
- sleep: fastSleep,
937
- publishMetric: async () => undefined,
938
- });
939
- await receiver.start();
940
- await waitFor(() => receiver.processedCount === 500);
941
- await receiver.dispose();
942
- expect(batches.map((batch) => batch.events.length)).toEqual([500]);
943
- expect(sqs.deleted).toHaveLength(500);
944
- expect(sqs.receiveWaitTimes.slice(0, 3)).toEqual([20, 0, 1]);
945
- });
946
- it("caps each 1,000-receipt drain at 256 paths and processes the remaining receipts", async () => {
947
- const sqs = new FakeSqs();
948
- const receipts = Array.from({ length: 1_000 }, (_, index) => msg(makeEvent({
949
- relativePath: `companies/indigo/deep/${index % 256}.md`,
950
- sequenceNumber: index + 1,
951
- }), `rh-deep-${index}`));
952
- for (let start = 0; start < receipts.length; start += 10) {
953
- sqs.enqueueDrainable(receipts.slice(start, start + 10));
954
- }
955
- const batches = [];
956
- const receiver = new SqsPushReceiver({
957
- tenantId: TENANT,
958
- queueUrl: QUEUE_URL,
959
- sqs,
960
- syncBatchFn: async (ctx) => { batches.push(ctx); },
961
- sleep: fastSleep,
962
- publishMetric: async () => undefined,
963
- });
964
- await receiver.start();
965
- await waitFor(() => sqs.deleted.length === 1_000, 5_000);
966
- await receiver.dispose();
967
- expect(batches.map((batch) => batch.events.length)).toEqual([256]);
968
- expect(sqs.deleted).toHaveLength(1_000);
969
- expect(sqs.visibilityChanges).toEqual([]);
970
- expect(sqs.receiveWaitTimes[0]).toBe(20);
971
- });
972
- it("reports a nonempty large drain before it can later report an empty long-poll", async () => {
973
- const sqs = new FakeSqs();
974
- const activity = [];
975
- const receipts = Array.from({ length: 1_000 }, (_, index) => msg(makeEvent({
976
- relativePath: `companies/indigo/bulk/${index % 256}.md`,
977
- sequenceNumber: index + 1,
978
- }), `rh-activity-${index}`));
979
- for (let start = 0; start < receipts.length; start += 10) {
980
- sqs.enqueueDrainable(receipts.slice(start, start + 10));
981
- }
982
- const receiver = new SqsPushReceiver({
983
- tenantId: TENANT,
984
- queueUrl: QUEUE_URL,
985
- sqs,
986
- syncBatchFn: async () => undefined,
987
- onReceiveActivity: (hasMessages) => activity.push(hasMessages),
988
- sleep: fastSleep,
989
- publishMetric: async () => undefined,
990
- });
991
- await receiver.start();
992
- await waitFor(() => receiver.processedCount >= 256 && activity.includes(false));
993
- await receiver.dispose();
994
- expect(activity[0]).toBe(true);
995
- expect(activity).toContain(false);
996
- expect(sqs.deleted).toHaveLength(1_000);
997
- });
998
- it("ends a near-empty drain after three short-poll confirmations without busy-looping", async () => {
999
- const sqs = new FakeSqs();
1000
- sqs.enqueue([msg(makeEvent({ sequenceNumber: 1 }), "rh-near-empty")]);
1001
- const batches = [];
1002
- const startedAt = Date.now();
1003
- const receiver = new SqsPushReceiver({
1004
- tenantId: TENANT,
1005
- queueUrl: QUEUE_URL,
1006
- sqs,
1007
- syncBatchFn: async (ctx) => { batches.push(ctx); },
1008
- sleep: fastSleep,
1009
- publishMetric: async () => undefined,
1010
- });
1011
- await receiver.start();
1012
- await waitFor(() => receiver.processedCount === 1);
1013
- const elapsedMs = Date.now() - startedAt;
1014
- await receiver.dispose();
1015
- expect(batches).toHaveLength(1);
1016
- expect(sqs.receiveWaitTimes.slice(0, 5)).toEqual([20, 0, 1, 1, 1]);
1017
- expect(sqs.receiveCalls).toBeLessThanOrEqual(6);
1018
- expect(elapsedMs).toBeLessThan(5_000);
1019
- });
1020
- it("REGRESSION: leases a receipt through a slow pass so it is received exactly once", async () => {
1021
- const sqs = new VisibilityExpirySqs(makeEvent({ sequenceNumber: 41 }));
1022
- const receiver = new SqsPushReceiver({
1023
- tenantId: TENANT,
1024
- queueUrl: QUEUE_URL,
1025
- sqs,
1026
- syncBatchFn: async () => {
1027
- await new Promise((resolve) => setTimeout(resolve, 25));
1028
- },
1029
- sleep: fastSleep,
1030
- publishMetric: async () => undefined,
1031
- });
1032
- await receiver.start();
1033
- await waitFor(() => sqs.deleted.length === 1);
1034
- await receiver.dispose();
1035
- expect(sqs.requestedVisibilityTimeouts[0]).toBe(600);
1036
- expect(sqs.deliveries).toHaveLength(1);
1037
- });
1038
- it("heartbeats a receipt when a targeted pull outlives its visibility lease", async () => {
1039
- const sqs = new VisibilityExpirySqs(makeEvent({ sequenceNumber: 42 }));
1040
- const receiver = new SqsPushReceiver({
1041
- tenantId: TENANT,
1042
- queueUrl: QUEUE_URL,
1043
- sqs,
1044
- visibilityTimeoutSeconds: 1,
1045
- visibilityHeartbeatIntervalMs: 1,
1046
- syncBatchFn: async () => {
1047
- await new Promise((resolve) => setTimeout(resolve, 30));
1048
- },
1049
- sleep: fastSleep,
1050
- publishMetric: async () => undefined,
1051
- });
1052
- await receiver.start();
1053
- try {
1054
- await waitFor(() => sqs.deleteAttempts.length === 1);
1055
- }
1056
- finally {
1057
- await receiver.dispose();
1058
- }
1059
- expect(sqs.requestedVisibilityTimeouts[0]).toBe(1);
1060
- expect(sqs.visibilityChanges.length).toBeGreaterThan(0);
1061
- expect(sqs.deliveries).toHaveLength(1);
1062
- expect(sqs.deleted).toHaveLength(1);
1063
- });
1064
- it("keeps the receipt lease alive until a slow successful delete completes", async () => {
1065
- const sqs = new VisibilityExpirySqs(makeEvent({ sequenceNumber: 43 }));
1066
- sqs.deleteDelayMs = 30;
1067
- const receiver = new SqsPushReceiver({
1068
- tenantId: TENANT,
1069
- queueUrl: QUEUE_URL,
1070
- sqs,
1071
- visibilityTimeoutSeconds: 1,
1072
- visibilityHeartbeatIntervalMs: 1,
1073
- syncBatchFn: async () => undefined,
1074
- sleep: fastSleep,
1075
- publishMetric: async () => undefined,
1076
- });
1077
- await receiver.start();
1078
- try {
1079
- await waitFor(() => sqs.deleteAttempts.length === 1);
1080
- }
1081
- finally {
1082
- await receiver.dispose();
1083
- }
1084
- expect(sqs.visibilityChanges.some((change) => change.visibilityTimeoutSeconds === 1)).toBe(true);
1085
- expect(sqs.deleteAttempts).toEqual([{ receiptHandle: "visibility-rh-1", expired: false }]);
1086
- expect(sqs.deliveries).toHaveLength(1);
1087
- expect(sqs.deleted).toHaveLength(1);
1088
- });
1089
- it("waits for an in-flight receipt heartbeat before releasing a failed delete", async () => {
1090
- let delivered = false;
1091
- let resolveHeartbeat;
1092
- const heartbeatGate = new Promise((resolve) => { resolveHeartbeat = resolve; });
1093
- let releaseDelete;
1094
- const deleteGate = new Promise((resolve) => { releaseDelete = resolve; });
1095
- const calls = [];
1096
- const sqs = {
1097
- receiveMessage: async ({ signal, waitTimeSeconds }) => {
1098
- if (!delivered) {
1099
- delivered = true;
1100
- return { messages: [msg(makeEvent({ sequenceNumber: 44 }), "rh-failed-delete")] };
1101
- }
1102
- if (signal.aborted || waitTimeSeconds <= 1)
1103
- return { messages: [] };
1104
- return new Promise((resolve) => {
1105
- signal.addEventListener("abort", () => resolve({ messages: [] }), { once: true });
1106
- });
1107
- },
1108
- deleteMessage: async () => {
1109
- calls.push("delete-start");
1110
- await deleteGate;
1111
- calls.push("delete-failed");
1112
- throw new Error("simulated delete failure");
1113
- },
1114
- changeMessageVisibility: async ({ visibilityTimeoutSeconds }) => {
1115
- if (visibilityTimeoutSeconds === 0) {
1116
- calls.push("release");
1117
- return;
1118
- }
1119
- calls.push("heartbeat-start");
1120
- await heartbeatGate;
1121
- calls.push("heartbeat-end");
1122
- },
1123
- changeMessageVisibilityBatch: async ({ entries, visibilityTimeoutSeconds }) => {
1124
- for (const _entry of entries) {
1125
- if (visibilityTimeoutSeconds === 0) {
1126
- calls.push("release");
1127
- }
1128
- else {
1129
- calls.push("heartbeat-start");
1130
- await heartbeatGate;
1131
- calls.push("heartbeat-end");
1132
- }
1133
- }
1134
- return { failedEntries: [] };
1135
- },
1136
- };
1137
- const receiver = new SqsPushReceiver({
1138
- tenantId: TENANT,
1139
- queueUrl: QUEUE_URL,
1140
- sqs,
1141
- visibilityHeartbeatIntervalMs: 5,
1142
- syncBatchFn: async () => undefined,
1143
- sleep: fastSleep,
1144
- publishMetric: async () => undefined,
1145
- });
1146
- await receiver.start();
1147
- try {
1148
- await waitFor(() => calls.includes("delete-start"));
1149
- await waitFor(() => calls.includes("heartbeat-start"));
1150
- releaseDelete();
1151
- await waitFor(() => calls.includes("delete-failed"));
1152
- expect(calls).toEqual(["delete-start", "heartbeat-start", "delete-failed"]);
1153
- resolveHeartbeat();
1154
- await waitFor(() => calls.includes("release"));
1155
- expect(calls).toEqual([
1156
- "delete-start",
1157
- "heartbeat-start",
1158
- "delete-failed",
1159
- "heartbeat-end",
1160
- "release",
1161
- ]);
1162
- }
1163
- finally {
1164
- releaseDelete();
1165
- resolveHeartbeat();
1166
- await receiver.dispose();
1167
- }
1168
- });
1169
- it("REGRESSION: drains a 5,000-message FIFO backlog through bounded slow passes without lease-expiry redelivery", async () => {
1170
- const total = 5_000;
1171
- const sqs = new BacklogSqs(Array.from({ length: total }, (_, index) => msg(makeEvent({
1172
- relativePath: `companies/indigo/backlog/${index}.md`,
1173
- sequenceNumber: index + 1,
1174
- }), `rh-backlog-${index + 1}`)));
1175
- const drains = [];
1176
- let activePasses = 0;
1177
- let maxActivePasses = 0;
1178
- const startedAt = Date.now();
1179
- const receiver = new SqsPushReceiver({
1180
- tenantId: TENANT,
1181
- queueUrl: QUEUE_URL,
1182
- sqs,
1183
- batchMaxPaths: 256,
1184
- drainMaxMessages: 256,
1185
- maxInFlightDrains: 2,
1186
- syncBatchFn: async () => {
1187
- activePasses += 1;
1188
- maxActivePasses = Math.max(maxActivePasses, activePasses);
1189
- await new Promise((resolve) => setTimeout(resolve, 30));
1190
- activePasses -= 1;
1191
- },
1192
- sleep: fastSleep,
1193
- publishMetric: async () => undefined,
1194
- logger: {
1195
- debug: () => undefined,
1196
- warn: () => undefined,
1197
- error: () => undefined,
1198
- info: (record) => {
1199
- if (record.event === "receiver.drain")
1200
- drains.push(record);
1201
- },
1202
- },
1203
- });
1204
- await receiver.start();
1205
- await waitFor(() => sqs.deleted.length === total, 10_000);
1206
- const elapsedMs = Date.now() - startedAt;
1207
- await receiver.dispose();
1208
- const throughputPerMinute = total / elapsedMs * 60_000;
1209
- expect(throughputPerMinute).toBeGreaterThanOrEqual(300);
1210
- expect(maxActivePasses).toBe(2);
1211
- expect(sqs.received).toHaveLength(total);
1212
- expect(sqs.deleted).toHaveLength(total);
1213
- expect(sqs.released).toEqual([]);
1214
- expect(sqs.remaining).toBe(0);
1215
- expect(sqs.deleted[0]).toBe("rh-backlog-1");
1216
- expect(drains).toHaveLength(Math.ceil(total / 256));
1217
- expect(drains).toEqual(expect.arrayContaining([
1218
- expect.objectContaining({ remaining: 0 }),
1219
- ]));
1220
- }, 15_000);
1221
- it("stops a capped distinct-path drain before receiving and releasing more messages", async () => {
1222
- const first = [
1223
- msg(makeEvent({ relativePath: "companies/indigo/a.md", sequenceNumber: 1 }), "rh-a"),
1224
- msg(makeEvent({ relativePath: "companies/indigo/b.md", sequenceNumber: 2 }), "rh-b"),
1225
- msg(makeEvent({ relativePath: "companies/indigo/backlog.md", sequenceNumber: 3 }), "rh-backlog"),
1226
- ];
1227
- const sqs = new OverCapSqs(first);
1228
- const batches = [];
1229
- const fresh = msg(makeEvent({ relativePath: "companies/indigo/fresh.md", sequenceNumber: 4 }), "rh-fresh");
1230
- const receiver = new SqsPushReceiver({
1231
- tenantId: TENANT,
1232
- queueUrl: QUEUE_URL,
1233
- sqs,
1234
- batchMaxPaths: 2,
1235
- drainMaxPaths: 2,
1236
- drainMaxMessages: 3,
1237
- syncBatchFn: async ({ events }) => {
1238
- batches.push(events.map((event) => event.relativePath));
1239
- if (batches.length === 1)
1240
- sqs.enqueue(fresh);
1241
- },
1242
- sleep: fastSleep,
1243
- publishMetric: async () => undefined,
1244
- });
1245
- await receiver.start();
1246
- await waitFor(() => batches.some((batch) => batch.includes("companies/indigo/fresh.md")));
1247
- await receiver.dispose();
1248
- expect(sqs.receiveCalls[0]).toEqual({ waitTimeSeconds: 20, maxMessages: 2 });
1249
- expect(sqs.receiveCalls.filter((call) => call.waitTimeSeconds === 0)).toEqual([]);
1250
- expect(sqs.releases).toEqual([]);
1251
- expect(sqs.deleted).toHaveLength(4);
1252
- expect(batches[0]).toEqual([
1253
- "companies/indigo/a.md",
1254
- "companies/indigo/b.md",
1255
- ]);
1256
- expect(batches[1]).toContain("companies/indigo/fresh.md");
1257
- });
1258
- it("dispatches the collected batch and keeps polling when a confirmation receive fails", async () => {
1259
- const initial = msg(makeEvent({ sequenceNumber: 1 }), "rh-confirmation-failure");
1260
- const receiveWaitTimes = [];
1261
- let receiveCalls = 0;
1262
- let initialDelivered = false;
1263
- let confirmationFailed = false;
1264
- const deleted = [];
1265
- const warnings = [];
1266
- const sqs = {
1267
- receiveMessage: async (args) => {
1268
- receiveCalls += 1;
1269
- receiveWaitTimes.push(args.waitTimeSeconds);
1270
- if (!initialDelivered && args.waitTimeSeconds === 20) {
1271
- initialDelivered = true;
1272
- return { messages: [initial] };
1273
- }
1274
- if (args.waitTimeSeconds === 0)
1275
- return { messages: [] };
1276
- if (args.waitTimeSeconds === 1 && !confirmationFailed) {
1277
- confirmationFailed = true;
1278
- throw new Error("transient confirmation receive failure");
1279
- }
1280
- return new Promise((resolve) => {
1281
- if (args.signal.aborted)
1282
- return resolve({ messages: [] });
1283
- args.signal.addEventListener("abort", () => resolve({ messages: [] }), { once: true });
1284
- });
1285
- },
1286
- deleteMessage: async ({ receiptHandle }) => { deleted.push(receiptHandle); },
1287
- changeMessageVisibility: async () => { },
1288
- changeMessageVisibilityBatch: async () => ({ failedEntries: [] }),
1289
- };
1290
- const batches = [];
1291
- const receiver = new SqsPushReceiver({
1292
- tenantId: TENANT,
1293
- queueUrl: QUEUE_URL,
1294
- sqs,
1295
- syncBatchFn: async (ctx) => { batches.push(ctx); },
1296
- sleep: fastSleep,
1297
- publishMetric: async () => undefined,
1298
- reconnect: { initialMs: 1, maxMs: 4, jitter: false },
1299
- logger: {
1300
- info: () => undefined,
1301
- warn: (record) => { warnings.push(record); },
1302
- error: () => undefined,
1303
- debug: () => undefined,
1304
- },
1305
- });
1306
- await receiver.start();
1307
- await waitFor(() => batches.length === 1 && deleted.length === 1 && receiveCalls >= 4);
1308
- await receiver.dispose();
1309
- expect(batches).toHaveLength(1);
1310
- expect(batches[0].events).toHaveLength(1);
1311
- expect(deleted).toEqual(["rh-confirmation-failure"]);
1312
- expect(receiveWaitTimes.slice(0, 4)).toEqual([20, 0, 1, 20]);
1313
- expect(receiver.receiveErrorCount).toBe(1);
1314
- expect(warnings).toContainEqual(expect.objectContaining({
1315
- event: "receiver.receive.failed",
1316
- tenantId: TENANT,
1317
- err: { message: "transient confirmation receive failure" },
1318
- }));
1319
- });
1320
- it("keeps only the highest sequence number for each path within a coalesced pass", async () => {
1321
- const sqs = new FakeSqs();
1322
- const batches = [];
1323
- sqs.enqueue([
1324
- msg(makeEvent({ sequenceNumber: 1 }), "rh-seq-1"),
1325
- msg(makeEvent({ sequenceNumber: 3 }), "rh-seq-3"),
1326
- msg(makeEvent({ sequenceNumber: 2 }), "rh-seq-2"),
1327
- ]);
1328
- const receiver = new SqsPushReceiver({
1329
- tenantId: TENANT,
1330
- queueUrl: QUEUE_URL,
1331
- sqs,
1332
- syncBatchFn: async (ctx) => { batches.push(ctx); },
1333
- sleep: fastSleep,
1334
- publishMetric: async () => undefined,
1335
- });
1336
- await receiver.start();
1337
- await waitFor(() => receiver.processedCount === 1);
1338
- await receiver.dispose();
1339
- expect(batches).toHaveLength(1);
1340
- expect(batches[0].events).toHaveLength(1);
1341
- expect(batches[0].events[0].sequenceNumber).toBe(3);
1342
- expect(sqs.deleted).toEqual(expect.arrayContaining(["rh-seq-1", "rh-seq-2", "rh-seq-3"]));
1343
- });
1344
- it("REGRESSION: coalesces create-then-delete receipts across receive pages before bounded pulls", async () => {
1345
- const pathCount = 256;
1346
- const creates = Array.from({ length: pathCount }, (_, index) => msg(makeEvent({
1347
- relativePath: `companies/indigo/dead-burst/${index}.md`,
1348
- sequenceNumber: index + 1,
1349
- kind: "upsert",
1350
- }), `rh-create-${index}`));
1351
- const deletes = Array.from({ length: pathCount }, (_, index) => msg(makeEvent({
1352
- relativePath: `companies/indigo/dead-burst/${index}.md`,
1353
- sequenceNumber: pathCount + index + 1,
1354
- kind: "delete",
1355
- }), `rh-delete-${index}`));
1356
- const sqs = new FakeSqs();
1357
- sqs.enqueueDrainable([...creates, ...deletes]);
1358
- const batches = [];
1359
- const receiver = new SqsPushReceiver({
1360
- tenantId: TENANT,
1361
- queueUrl: QUEUE_URL,
1362
- sqs,
1363
- batchMaxPaths: 64,
1364
- drainMaxPaths: pathCount * 2,
1365
- drainMaxMessages: pathCount * 2,
1366
- syncBatchFn: async ({ events }) => { batches.push([...events]); },
1367
- sleep: fastSleep,
1368
- publishMetric: async () => undefined,
1369
- });
1370
- await receiver.start();
1371
- await waitFor(() => receiver.processedCount === pathCount);
1372
- await receiver.dispose();
1373
- expect(batches).toHaveLength(4);
1374
- expect(batches.flat()).toHaveLength(pathCount);
1375
- expect(batches.every((batch) => batch.length <= 64)).toBe(true);
1376
- expect(batches.flat().every((event) => event.kind === "delete")).toBe(true);
1377
- expect(sqs.deleted).toHaveLength(pathCount * 2);
1378
- });
1379
- it("keeps a newer create after delete and the latest repeated overwrite", async () => {
1380
- const sqs = new FakeSqs();
1381
- const earlierDelete = makeEvent({
1382
- relativePath: "companies/indigo/revived.md",
1383
- sequenceNumber: 10,
1384
- kind: "delete",
1385
- });
1386
- const latestCreate = makeEvent({
1387
- relativePath: "companies/indigo/revived.md",
1388
- sequenceNumber: 11,
1389
- kind: "upsert",
1390
- contentHash: `sha256:${"b".repeat(64)}`,
1391
- });
1392
- const firstOverwrite = makeEvent({
1393
- relativePath: "companies/indigo/overwritten.md",
1394
- sequenceNumber: 12,
1395
- kind: "upsert",
1396
- contentHash: `sha256:${"c".repeat(64)}`,
1397
- });
1398
- const latestOverwrite = makeEvent({
1399
- relativePath: "companies/indigo/overwritten.md",
1400
- sequenceNumber: 13,
1401
- kind: "upsert",
1402
- contentHash: `sha256:${"d".repeat(64)}`,
1403
- });
1404
- sqs.enqueue([
1405
- msg(earlierDelete, "rh-revived-delete"),
1406
- msg(latestCreate, "rh-revived-create"),
1407
- msg(firstOverwrite, "rh-overwrite-1"),
1408
- msg(latestOverwrite, "rh-overwrite-2"),
1409
- ]);
1410
- const batches = [];
1411
- const receiver = new SqsPushReceiver({
1412
- tenantId: TENANT,
1413
- queueUrl: QUEUE_URL,
1414
- sqs,
1415
- syncBatchFn: async (context) => { batches.push(context); },
1416
- sleep: fastSleep,
1417
- publishMetric: async () => undefined,
1418
- });
1419
- await receiver.start();
1420
- await waitFor(() => receiver.processedCount === 2);
1421
- await receiver.dispose();
1422
- expect(batches).toHaveLength(1);
1423
- expect(batches[0].events).toEqual([latestCreate, latestOverwrite]);
1424
- expect(sqs.deleted).toHaveLength(4);
1425
- });
1426
- it("splits a backlog above the distinct-path bound into bounded sequential passes", async () => {
1427
- const sqs = new FakeSqs();
1428
- const batches = [];
1429
- sqs.enqueue(Array.from({ length: 5 }, (_, index) => msg(makeEvent({ relativePath: `companies/indigo/${index}.md`, sequenceNumber: index + 1 }), `rh-limit-${index}`)));
1430
- const receiver = new SqsPushReceiver({
1431
- tenantId: TENANT,
1432
- queueUrl: QUEUE_URL,
1433
- sqs,
1434
- batchMaxPaths: 3,
1435
- syncBatchFn: async ({ events }) => { batches.push(events.length); },
1436
- sleep: fastSleep,
1437
- publishMetric: async () => undefined,
1438
- });
1439
- await receiver.start();
1440
- await waitFor(() => receiver.processedCount === 5);
1441
- await receiver.dispose();
1442
- expect(batches).toEqual([3, 2]);
1443
- expect(batches.every((count) => count <= 3)).toBe(true);
1444
- });
1445
- it("dispatches each bounded partition array itself so the runner keeps its lane", async () => {
1446
- // The runner keys each partition array's fast/slow lane in a WeakMap.
1447
- // Copying an array that already fits the bound loses that lane and gives
1448
- // every chunk a fresh fast-lane budget.
1449
- const sqs = new FakeSqs();
1450
- sqs.enqueue(Array.from({ length: 5 }, (_, index) => msg(makeEvent({ relativePath: `companies/indigo/${index}.md`, sequenceNumber: index + 1 }), `rh-lane-${index}`)));
1451
- const returned = [];
1452
- const dispatched = [];
1453
- const syncBatchFn = async ({ events }) => { dispatched.push(events); };
1454
- syncBatchFn.partition = (events, maxPaths) => {
1455
- const bound = maxPaths ?? events.length;
1456
- const chunks = [];
1457
- for (let offset = 0; offset < events.length; offset += bound) {
1458
- chunks.push(events.slice(offset, offset + bound));
1459
- }
1460
- returned.push(...chunks);
1461
- return chunks;
1462
- };
1463
- const receiver = new SqsPushReceiver({
1464
- tenantId: TENANT,
1465
- queueUrl: QUEUE_URL,
1466
- sqs,
1467
- batchMaxPaths: 3,
1468
- syncBatchFn,
1469
- sleep: fastSleep,
1470
- publishMetric: async () => undefined,
1471
- });
1472
- await receiver.start();
1473
- await waitFor(() => receiver.processedCount === 5);
1474
- await receiver.dispose();
1475
- expect(dispatched.flat().map((event) => event.sequenceNumber)).toEqual([1, 2, 3, 4, 5]);
1476
- expect(dispatched.every((events) => events.length <= 3)).toBe(true);
1477
- expect(dispatched).toHaveLength(returned.length);
1478
- dispatched.forEach((events, index) => expect(events).toBe(returned[index]));
1479
- });
1480
- it("acknowledges receipts per successful lane and retains failed slow residue", async () => {
1481
- const sqs = new FakeSqs();
1482
- const fast = makeEvent({ relativePath: "companies/indigo/fast.md", sequenceNumber: 1 });
1483
- const slow = makeEvent({ relativePath: "companies/indigo/slow.md", sequenceNumber: 2 });
1484
- sqs.enqueue([msg(fast, "rh-fast"), msg(slow, "rh-slow")]);
1485
- sqs.enqueue([msg(slow, "rh-slow-redelivered")]);
1486
- const dispatched = [];
1487
- let slowAttempts = 0;
1488
- const syncBatchFn = async ({ events }) => {
1489
- const relativePath = events[0].relativePath;
1490
- dispatched.push(relativePath);
1491
- if (relativePath === slow.relativePath && slowAttempts++ === 0) {
1492
- throw new Error("slow lane failed");
1493
- }
1494
- };
1495
- syncBatchFn.partition = (events) => [[events[0]], [events[1]]];
1496
- const receiver = new SqsPushReceiver({
1497
- tenantId: TENANT,
1498
- queueUrl: QUEUE_URL,
1499
- sqs,
1500
- syncBatchFn,
1501
- sleep: fastSleep,
1502
- publishMetric: async () => undefined,
1503
- });
1504
- await receiver.start();
1505
- await waitFor(() => receiver.processedCount === 2);
1506
- await receiver.dispose();
1507
- expect(sqs.deleted).toContain("rh-fast");
1508
- expect(sqs.deleted).not.toContain("rh-slow");
1509
- expect(sqs.deleted).toContain("rh-slow-redelivered");
1510
- // Regression: a slow failure must redeliver only slow. Replaying fast
1511
- // here would pull it again even though its receipt was already durable.
1512
- expect(dispatched).toEqual([fast.relativePath, slow.relativePath, slow.relativePath]);
1513
- });
1514
- it("deletes a self echo immediately and performs zero sync passes", async () => {
1515
- const sqs = new FakeSqs();
1516
- const syncBatchFn = vi.fn(async () => undefined);
1517
- sqs.enqueue([msg(makeEvent({ originDeviceId: "this-device" }), "rh-self")]);
1518
- const receiver = new SqsPushReceiver({
1519
- tenantId: TENANT,
1520
- queueUrl: QUEUE_URL,
1521
- sqs,
1522
- shouldProcess: (event) => event.originDeviceId !== "this-device",
1523
- syncBatchFn,
1524
- sleep: fastSleep,
1525
- publishMetric: async () => undefined,
1526
- });
1527
- await receiver.start();
1528
- await waitFor(() => sqs.deleted.includes("rh-self"));
1529
- await receiver.dispose();
1530
- expect(syncBatchFn).not.toHaveBeenCalled();
1531
- expect(receiver.processedCount).toBe(0);
1532
- });
1533
- it("deletes filtered retired-overlay events while dispatching a valid absent path", async () => {
1534
- const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-push-receiver-"));
1535
- const personalPath = path.join(hqRoot, "personal/policies/legacy-rule.md");
1536
- fs.mkdirSync(path.dirname(personalPath), { recursive: true });
1537
- fs.writeFileSync(personalPath, "fixture\n");
1538
- const sqs = new FakeSqs();
1539
- const syncBatchFn = vi.fn(async () => undefined);
1540
- const filtered = vi.fn();
1541
- const valid = makeEvent({
1542
- relativePath: "personal/policies/new-on-this-device.md",
1543
- sequenceNumber: 1,
1544
- });
1545
- const retiredUpsert = makeEvent({
1546
- relativePath: "core/policies/legacy-rule.md",
1547
- contentHash: `sha256:${hashFile(personalPath)}`,
1548
- sequenceNumber: 2,
1549
- });
1550
- const retiredDelete = makeEvent({
1551
- relativePath: "core/policies/legacy-rule.md",
1552
- kind: "delete",
1553
- contentHash: undefined,
1554
- mtime: undefined,
1555
- sequenceNumber: 3,
1556
- });
1557
- sqs.enqueue([msg(valid, "rh-valid"), msg(retiredUpsert, "rh-retired-upsert"), msg(retiredDelete, "rh-retired-delete")]);
1558
- const receiver = new SqsPushReceiver({
1559
- tenantId: TENANT,
1560
- queueUrl: QUEUE_URL,
1561
- sqs,
1562
- shouldProcess: (event) => isInboundEventPlaceable(hqRoot, event),
1563
- onFilteredEvents: filtered,
1564
- syncBatchFn,
1565
- sleep: fastSleep,
1566
- publishMetric: async () => undefined,
1567
- });
1568
- try {
1569
- await receiver.start();
1570
- await waitFor(() => syncBatchFn.mock.calls.length === 1);
1571
- await waitFor(() => sqs.deleted.includes("rh-retired-delete"));
1572
- await receiver.dispose();
1573
- expect(syncBatchFn).toHaveBeenCalledWith(expect.objectContaining({
1574
- events: [valid],
1575
- }));
1576
- expect(sqs.deleted).toEqual(expect.arrayContaining(["rh-valid", "rh-retired-upsert", "rh-retired-delete"]));
1577
- expect(filtered).toHaveBeenCalledWith([retiredUpsert, retiredDelete]);
1578
- }
1579
- finally {
1580
- await receiver.dispose();
1581
- fs.rmSync(hqRoot, { recursive: true, force: true });
1582
- }
1583
- });
1584
- it("skips an event whose sequenceNumber <= the highest seen for that path", async () => {
1585
- const sqs = new FakeSqs();
1586
- const processed = [];
1587
- const syncFn = async (ctx) => {
1588
- processed.push(ctx.event.sequenceNumber);
1589
- };
1590
- // seq 1, then a redelivery of 1, then 2 — same path.
1591
- sqs.enqueue([msg(makeEvent({ sequenceNumber: 1 }), "rh-a")]);
1592
- sqs.enqueue([msg(makeEvent({ sequenceNumber: 1 }), "rh-b")]);
1593
- sqs.enqueue([msg(makeEvent({ sequenceNumber: 2 }), "rh-c")]);
1594
- const receiver = new SqsPushReceiver({
1595
- tenantId: TENANT,
1596
- queueUrl: QUEUE_URL,
1597
- sqs,
1598
- syncFn,
1599
- sleep: fastSleep,
1600
- });
1601
- await receiver.start();
1602
- await waitFor(() => receiver.processedCount === 2);
1603
- await receiver.dispose();
1604
- expect(processed).toEqual([1, 2]);
1605
- expect(receiver.dedupedCount).toBe(1);
1606
- // The deduped redelivery is still deleted (we don't need it again).
1607
- expect(sqs.deleted).toContain("rh-b");
1608
- });
1609
- it("dedupe is per-path — same sequence number on different paths both process", async () => {
1610
- const sqs = new FakeSqs();
1611
- const processed = [];
1612
- const syncFn = async (ctx) => {
1613
- processed.push(ctx.event.relativePath);
1614
- };
1615
- sqs.enqueue([
1616
- msg(makeEvent({ relativePath: "companies/indigo/a.md", sequenceNumber: 5 }), "rh-1"),
1617
- msg(makeEvent({ relativePath: "companies/indigo/b.md", sequenceNumber: 5 }), "rh-2"),
1618
- ]);
1619
- const receiver = new SqsPushReceiver({
1620
- tenantId: TENANT,
1621
- queueUrl: QUEUE_URL,
1622
- sqs,
1623
- syncFn,
1624
- sleep: fastSleep,
1625
- });
1626
- await receiver.start();
1627
- await waitFor(() => receiver.processedCount === 2);
1628
- await receiver.dispose();
1629
- expect(new Set(processed)).toEqual(new Set(["companies/indigo/a.md", "companies/indigo/b.md"]));
1630
- expect(receiver.dedupedCount).toBe(0);
1631
- });
1632
- it("drops an undecodable message at the wire boundary without crashing the loop", async () => {
1633
- const sqs = new FakeSqs();
1634
- sqs.enqueue([{ Body: "{not json", ReceiptHandle: "rh-bad", MessageId: "m-bad" }]);
1635
- sqs.enqueue([msg(makeEvent({ sequenceNumber: 1 }), "rh-good")]);
1636
- const receiver = new SqsPushReceiver({
1637
- tenantId: TENANT,
1638
- queueUrl: QUEUE_URL,
1639
- sqs,
1640
- syncFn: async () => undefined,
1641
- sleep: fastSleep,
1642
- });
1643
- await receiver.start();
1644
- await waitFor(() => receiver.processedCount === 1);
1645
- await receiver.dispose();
1646
- expect(receiver.decodeFailureCount).toBe(1);
1647
- expect(sqs.deleted).toContain("rh-bad"); // poison message deleted
1648
- expect(sqs.deleted).toContain("rh-good");
1649
- });
1650
- });
1651
- describe("US-009: SqsPushReceiver — reconnect / catch-up replay", () => {
1652
- it("leaves only the failed path unacknowledged and retries it", async () => {
1653
- const sqs = new FakeSqs();
1654
- const events = [
1655
- makeEvent({ relativePath: "companies/indigo/retry-a.md", sequenceNumber: 1 }),
1656
- makeEvent({ relativePath: "companies/indigo/retry-b.md", sequenceNumber: 1 }),
1657
- makeEvent({ relativePath: "companies/indigo/retry-c.md", sequenceNumber: 1 }),
1658
- ];
1659
- sqs.enqueue(events.map((event, index) => msg(event, `rh-failed-${index}`)));
1660
- sqs.enqueue([msg(events[0], "rh-redelivered-a")]);
1661
- let batchCalls = 0;
1662
- let retryAPathCalls = 0;
1663
- const receiver = new SqsPushReceiver({
1664
- tenantId: TENANT,
1665
- queueUrl: QUEUE_URL,
1666
- sqs,
1667
- maxInFlightDrains: 1,
1668
- syncBatchFn: async ({ events: batch }) => {
1669
- if (batch.length > 1) {
1670
- batchCalls += 1;
1671
- throw new Error("coalesced pull failed for one path");
1672
- }
1673
- if (batch[0]?.relativePath === events[0].relativePath) {
1674
- retryAPathCalls += 1;
1675
- if (retryAPathCalls === 1)
1676
- throw new Error("retry-a is temporarily unavailable");
1677
- }
1678
- },
1679
- sleep: fastSleep,
1680
- publishMetric: async () => undefined,
1681
- });
1682
- await receiver.start();
1683
- await waitFor(() => receiver.processedCount === 3 && sqs.deleted.includes("rh-redelivered-a"));
1684
- await receiver.dispose();
1685
- expect(batchCalls).toBe(2);
1686
- expect(retryAPathCalls).toBe(2);
1687
- expect(sqs.deleted).not.toContain("rh-failed-0");
1688
- expect(sqs.deleted).toEqual(expect.arrayContaining(["rh-failed-1", "rh-failed-2", "rh-redelivered-a"]));
1689
- expect(sqs.visibilityChanges).toEqual([
1690
- { receiptHandle: "rh-failed-0", visibilityTimeoutSeconds: 20 },
1691
- ]);
1692
- });
1693
- it("recovers from transient receiveMessage failures (backoff + resume) and replays the queue", async () => {
1694
- const sqs = new FakeSqs();
1695
- sqs.failNext(2); // two transient disconnects before the message lands
1696
- sqs.enqueue([msg(makeEvent({ sequenceNumber: 1 }), "rh-1")]);
1697
- const receiver = new SqsPushReceiver({
1698
- tenantId: TENANT,
1699
- queueUrl: QUEUE_URL,
1700
- sqs,
1701
- syncFn: async () => undefined,
1702
- sleep: fastSleep,
1703
- reconnect: { initialMs: 1, maxMs: 4, jitter: false },
1704
- });
1705
- await receiver.start();
1706
- await waitFor(() => receiver.processedCount === 1);
1707
- await receiver.dispose();
1708
- expect(receiver.receiveErrorCount).toBe(2);
1709
- expect(sqs.deleted).toContain("rh-1");
1710
- });
1711
- it("a syncFn throw does not crash the loop and the next event still processes", async () => {
1712
- const sqs = new FakeSqs();
1713
- let calls = 0;
1714
- const syncFn = async () => {
1715
- calls += 1;
1716
- if (calls === 1)
1717
- throw new Error("boom");
1718
- };
1719
- sqs.enqueue([msg(makeEvent({ relativePath: "companies/indigo/x.md", sequenceNumber: 1 }), "rh-1")]);
1720
- sqs.enqueue([msg(makeEvent({ relativePath: "companies/indigo/y.md", sequenceNumber: 1 }), "rh-2")]);
1721
- const receiver = new SqsPushReceiver({
1722
- tenantId: TENANT,
1723
- queueUrl: QUEUE_URL,
1724
- sqs,
1725
- syncFn,
1726
- sleep: fastSleep,
1727
- });
1728
- await receiver.start();
1729
- await waitFor(() => calls === 2);
1730
- await receiver.dispose();
1731
- // First threw (processedCount not incremented), second succeeded.
1732
- expect(receiver.processedCount).toBe(1);
1733
- // F10: a failed targeted pull is left undeleted for SQS redelivery; only the
1734
- // successful message (rh-2) is acknowledged. The thrown one (rh-1) stays on
1735
- // the queue so the visibility-timeout redelivery can retry it.
1736
- expect(sqs.deleted).toContain("rh-2");
1737
- expect(sqs.deleted).not.toContain("rh-1");
1738
- });
1739
- it("F10: leaves failed targeted pull messages undeleted so redelivery retries", async () => {
1740
- const sqs = new FakeSqs();
1741
- let calls = 0;
1742
- const syncFn = async () => {
1743
- calls += 1;
1744
- if (calls === 1)
1745
- throw new Error("targeted pull failed");
1746
- };
1747
- const event = makeEvent({
1748
- relativePath: "companies/indigo/retry-after-failure.md",
1749
- sequenceNumber: 7,
1750
- });
1751
- sqs.enqueue([msg(event, "rh-first-attempt")]);
1752
- sqs.enqueue([msg(event, "rh-redelivery")]);
1753
- const receiver = new SqsPushReceiver({
1754
- tenantId: TENANT,
1755
- queueUrl: QUEUE_URL,
1756
- sqs,
1757
- syncFn,
1758
- sleep: fastSleep,
1759
- });
1760
- await receiver.start();
1761
- await waitFor(() => sqs.deleted.includes("rh-redelivery"));
1762
- await receiver.dispose();
1763
- expect(calls).toBe(2);
1764
- expect(receiver.processedCount).toBe(1);
1765
- expect(receiver.dedupedCount).toBe(0);
1766
- expect(sqs.deleted).not.toContain("rh-first-attempt");
1767
- expect(sqs.deleted).toContain("rh-redelivery");
1768
- });
1769
- it("F29: bounds dedupe state by evicting least-recently-used paths", async () => {
1770
- const sqs = new FakeSqs();
1771
- const processed = [];
1772
- const syncFn = async (ctx) => {
1773
- processed.push(ctx.event.relativePath);
1774
- };
1775
- sqs.enqueue([msg(makeEvent({ relativePath: "companies/indigo/a.md", sequenceNumber: 1 }), "rh-a1")]);
1776
- sqs.enqueue([msg(makeEvent({ relativePath: "companies/indigo/b.md", sequenceNumber: 1 }), "rh-b1")]);
1777
- sqs.enqueue([msg(makeEvent({ relativePath: "companies/indigo/c.md", sequenceNumber: 1 }), "rh-c1")]);
1778
- sqs.enqueue([msg(makeEvent({ relativePath: "companies/indigo/a.md", sequenceNumber: 1 }), "rh-a-redelivered")]);
1779
- const receiver = new SqsPushReceiver({
1780
- tenantId: TENANT,
1781
- queueUrl: QUEUE_URL,
1782
- sqs,
1783
- syncFn,
1784
- sleep: fastSleep,
1785
- dedupeMaxPaths: 2,
1786
- });
1787
- await receiver.start();
1788
- await waitFor(() => receiver.processedCount === 4);
1789
- await receiver.dispose();
1790
- expect(processed).toEqual([
1791
- "companies/indigo/a.md",
1792
- "companies/indigo/b.md",
1793
- "companies/indigo/c.md",
1794
- "companies/indigo/a.md",
1795
- ]);
1796
- expect(receiver.dedupedCount).toBe(0);
1797
- expect(sqs.deleted).toContain("rh-a-redelivered");
1798
- });
1799
- });
1800
- describe("U15: SqsPushReceiver — server-granted handoff", () => {
1801
- it("starts from supplied server queue material with no tenant client gate", async () => {
1802
- const sqs = new FakeSqs();
1803
- sqs.enqueue([msg(makeEvent({ sequenceNumber: 1 }))]);
1804
- const receiver = new SqsPushReceiver({
1805
- tenantId: TENANT,
1806
- queueUrl: QUEUE_URL,
1807
- sqs,
1808
- syncFn: async () => undefined,
1809
- sleep: fastSleep,
1810
- });
1811
- await receiver.start();
1812
- await waitFor(() => receiver.processedCount === 1);
1813
- expect(receiver.connected).toBe(true);
1814
- await receiver.dispose();
1815
- });
1816
- });
1817
- describe("US-009: SqsPushReceiver — dispose drain", () => {
1818
- it("aborts and drains the in-flight syncFn, then disconnects", async () => {
1819
- const sqs = new FakeSqs();
1820
- let aborted = false;
1821
- let resolveSync = null;
1822
- const syncFn = (ctx) => new Promise((resolve) => {
1823
- resolveSync = resolve;
1824
- ctx.signal.addEventListener("abort", () => {
1825
- aborted = true;
1826
- resolve();
1827
- });
1828
- });
1829
- sqs.enqueue([msg(makeEvent({ sequenceNumber: 1 }))]);
1830
- const receiver = new SqsPushReceiver({
1831
- tenantId: TENANT,
1832
- queueUrl: QUEUE_URL,
1833
- sqs,
1834
- syncFn,
1835
- sleep: fastSleep,
1836
- disposeDrainMs: 200,
1837
- });
1838
- await receiver.start();
1839
- await waitFor(() => resolveSync !== null);
1840
- await receiver.dispose();
1841
- expect(aborted).toBe(true);
1842
- expect(receiver.connected).toBe(false);
1843
- });
1844
- it("dispose is idempotent", async () => {
1845
- const sqs = new FakeSqs();
1846
- const receiver = new SqsPushReceiver({
1847
- tenantId: TENANT,
1848
- queueUrl: QUEUE_URL,
1849
- sqs,
1850
- syncFn: async () => undefined,
1851
- sleep: fastSleep,
1852
- });
1853
- await receiver.start();
1854
- await Promise.all([receiver.dispose(), receiver.dispose()]);
1855
- expect(receiver.connected).toBe(false);
1856
- });
1857
- });
1858
- // ─── InMemoryPushReceiver (transport analogue) ───────────────────────────────
1859
- describe("US-009: InMemoryPushReceiver — disconnect buffering + reconnect drain", () => {
1860
- it("buffers events during disconnect and drains them on reconnect, deduping replays", async () => {
1861
- const fanout = new InMemoryFanout();
1862
- const processed = [];
1863
- const receiver = new InMemoryPushReceiver({
1864
- tenantId: TENANT,
1865
- fanout,
1866
- syncFn: async (ctx) => {
1867
- processed.push(ctx.event.sequenceNumber);
1868
- },
1869
- });
1870
- await receiver.start();
1871
- fanout.publish(encodePushEvent(makeEvent({ sequenceNumber: 1 })));
1872
- await waitFor(() => receiver.processedCount === 1);
1873
- receiver.simulateDisconnect();
1874
- expect(receiver.connected).toBe(false);
1875
- // Published while disconnected — buffered, not processed.
1876
- fanout.publish(encodePushEvent(makeEvent({ sequenceNumber: 2 })));
1877
- fanout.publish(encodePushEvent(makeEvent({ sequenceNumber: 1 }))); // stale replay
1878
- expect(receiver.bufferedCount).toBe(2);
1879
- receiver.simulateReconnect();
1880
- await waitFor(() => receiver.processedCount === 2);
1881
- await receiver.dispose();
1882
- // seq 2 processed; the stale seq-1 replay deduped.
1883
- expect(processed).toEqual([1, 2]);
1884
- expect(receiver.dedupedCount).toBe(1);
1885
- });
1886
- it("does not derive activation from a tenant flag", async () => {
1887
- const fanout = new InMemoryFanout();
1888
- let calls = 0;
1889
- const receiver = new InMemoryPushReceiver({
1890
- tenantId: TENANT,
1891
- fanout,
1892
- syncFn: async () => {
1893
- calls += 1;
1894
- },
1895
- });
1896
- await receiver.start();
1897
- fanout.publish(encodePushEvent(makeEvent()));
1898
- await new Promise((r) => setTimeout(r, 20));
1899
- await waitFor(() => calls === 1);
1900
- expect(receiver.connected).toBe(true);
1901
- expect(calls).toBe(1);
1902
- await receiver.dispose();
1903
- });
1904
- });
1905
- // ─── Factory + Noop ──────────────────────────────────────────────────────────
1906
- describe("US-009: createPushReceiver factory", () => {
1907
- it("returns NoopPushReceiver for kind:noop", () => {
1908
- const r = createPushReceiver({ kind: "noop" });
1909
- expect(r).toBeInstanceOf(NoopPushReceiver);
1910
- });
1911
- it("returns SqsPushReceiver when a queue + sqs client are supplied", () => {
1912
- const r = createPushReceiver({
1913
- tenantId: TENANT,
1914
- queueUrl: QUEUE_URL,
1915
- sqs: new FakeSqs(),
1916
- syncFn: async () => undefined,
1917
- });
1918
- expect(r).toBeInstanceOf(SqsPushReceiver);
1919
- });
1920
- it("NoopPushReceiver flips connected on start/dispose with no work", async () => {
1921
- const r = new NoopPushReceiver();
1922
- expect(r.connected).toBe(false);
1923
- await r.start();
1924
- expect(r.connected).toBe(true);
1925
- await r.dispose();
1926
- expect(r.connected).toBe(false);
1927
- });
1928
- });
1929
- describe("SqsPushReceiver failed-batch retry visibility", () => {
1930
- it("releases an overlong drain so the receiver resumes polling after two stuck drains", async () => {
1931
- vi.useFakeTimers();
1932
- const sqs = new FakeSqs();
1933
- const events = [
1934
- makeEvent({ relativePath: "companies/indigo/stuck-one.md", sequenceNumber: 1 }),
1935
- makeEvent({ relativePath: "companies/indigo/stuck-two.md", sequenceNumber: 2 }),
1936
- makeEvent({ relativePath: "companies/indigo/stuck-three.md", sequenceNumber: 3 }),
1937
- ];
1938
- sqs.enqueueDrainable([msg(events[0])]);
1939
- let syncCalls = 0;
1940
- const receiver = new SqsPushReceiver({
1941
- tenantId: TENANT,
1942
- queueUrl: QUEUE_URL,
1943
- sqs,
1944
- maxInFlightDrains: 2,
1945
- visibilityTimeoutSeconds: 600,
1946
- visibilityHeartbeatIntervalMs: 300_000,
1947
- disposeDrainMs: 1,
1948
- syncBatchFn: async () => {
1949
- if (syncCalls === 0)
1950
- sqs.enqueueDrainable([msg(events[1])]);
1951
- syncCalls += 1;
1952
- return new Promise(() => undefined);
1953
- },
1954
- sleep: fastSleep,
1955
- publishMetric: async () => undefined,
1956
- logger: makeDrainLogger([]),
1957
- });
1958
- try {
1959
- await receiver.start();
1960
- await vi.waitFor(() => expect(syncCalls).toBe(2));
1961
- await vi.advanceTimersByTimeAsync(600_000);
1962
- await vi.waitFor(() => expect(sqs.receiveCalls).toBeGreaterThan(2));
1963
- expect(sqs.visibilityChanges).toContainEqual({
1964
- receiptHandle: "rh-1",
1965
- visibilityTimeoutSeconds: 20,
1966
- });
1967
- expect(sqs.visibilityChanges).toContainEqual({
1968
- receiptHandle: "rh-2",
1969
- visibilityTimeoutSeconds: 20,
1970
- });
1971
- }
1972
- finally {
1973
- const disposing = receiver.dispose();
1974
- await vi.advanceTimersByTimeAsync(10);
1975
- await disposing;
1976
- vi.useRealTimers();
1977
- }
1978
- });
1979
- it("settles a completed route group's receipt while another group is still running", async () => {
1980
- const sqs = new FakeSqs();
1981
- const fast = makeEvent({ relativePath: "personal/notes/fast.md", sequenceNumber: 1 });
1982
- const slow = makeEvent({ relativePath: "companies/indigo/slow.md", sequenceNumber: 2 });
1983
- sqs.enqueueDrainable([msg(fast, "rh-fast"), msg(slow, "rh-slow")]);
1984
- let started;
1985
- const passStarted = new Promise((resolve) => { started = resolve; });
1986
- let releaseSlow;
1987
- const slowGate = new Promise((resolve) => { releaseSlow = resolve; });
1988
- const receiver = new SqsPushReceiver({
1989
- tenantId: TENANT,
1990
- queueUrl: QUEUE_URL,
1991
- sqs,
1992
- maxInFlightDrains: 1,
1993
- syncBatchFn: async (context) => {
1994
- started();
1995
- await context.settleEvents?.([fast]);
1996
- await slowGate;
1997
- await context.settleEvents?.([slow]);
1998
- },
1999
- sleep: fastSleep,
2000
- publishMetric: async () => undefined,
2001
- logger: makeDrainLogger([]),
2002
- });
2003
- try {
2004
- await receiver.start();
2005
- await passStarted;
2006
- await vi.waitFor(() => expect(sqs.deleted).toContain("rh-fast"));
2007
- expect(sqs.deleted).not.toContain("rh-slow");
2008
- releaseSlow();
2009
- await vi.waitFor(() => expect(sqs.deleted).toContain("rh-slow"));
2010
- }
2011
- finally {
2012
- releaseSlow();
2013
- await receiver.dispose();
2014
- }
2015
- });
2016
- it("resumes lease heartbeats and accepts a later route settle after one settle throws", async () => {
2017
- const sqs = new FakeSqs();
2018
- const failed = makeEvent({ relativePath: "companies/indigo/failed-settle.md", sequenceNumber: 21 });
2019
- const next = makeEvent({ relativePath: "companies/indigo/next-settle.md", sequenceNumber: 22 });
2020
- const held = makeEvent({ relativePath: "companies/indigo/held-settle.md", sequenceNumber: 23 });
2021
- sqs.enqueueDrainable([
2022
- msg(failed, "rh-failed-settle"),
2023
- msg(next, "rh-next-settle"),
2024
- msg(held, "rh-held-settle"),
2025
- ]);
2026
- let releasePass;
2027
- const passGate = new Promise((resolve) => { releasePass = resolve; });
2028
- let laterSettleFinished = false;
2029
- const receiver = new SqsPushReceiver({
2030
- tenantId: TENANT,
2031
- queueUrl: QUEUE_URL,
2032
- sqs,
2033
- visibilityTimeoutSeconds: 20,
2034
- visibilityHeartbeatIntervalMs: 5,
2035
- syncBatchFn: async (context) => {
2036
- const internals = receiver;
2037
- const originalSafeDelete = internals.safeDelete.bind(receiver);
2038
- internals.safeDelete = async (message) => {
2039
- if (message.ReceiptHandle === "rh-failed-settle") {
2040
- throw new Error("simulated acknowledgement failure");
2041
- }
2042
- return originalSafeDelete(message);
2043
- };
2044
- await expect(context.settleEvents?.([failed])).rejects.toThrow("simulated acknowledgement failure");
2045
- await context.settleEvents?.([next]);
2046
- laterSettleFinished = true;
2047
- await passGate;
2048
- },
2049
- sleep: fastSleep,
2050
- publishMetric: async () => undefined,
2051
- logger: makeDrainLogger([]),
2052
- });
2053
- try {
2054
- await receiver.start();
2055
- await vi.waitFor(() => expect(laterSettleFinished).toBe(true));
2056
- await vi.waitFor(() => expect(sqs.deleted).toContain("rh-next-settle"));
2057
- await vi.waitFor(() => expect(sqs.visibilityChanges).toContainEqual({
2058
- receiptHandle: "rh-held-settle",
2059
- visibilityTimeoutSeconds: 20,
2060
- }));
2061
- }
2062
- finally {
2063
- releasePass();
2064
- await receiver.dispose();
2065
- }
2066
- });
2067
- it("keeps the outer heartbeat pause active when a nested delete fails", async () => {
2068
- const sqs = new FakeSqs();
2069
- const event = makeEvent({ relativePath: "companies/indigo/nested-settle.md", sequenceNumber: 24 });
2070
- const message = msg(event, "rh-delete-fails");
2071
- vi.useFakeTimers();
2072
- const receiver = new SqsPushReceiver({
2073
- tenantId: TENANT,
2074
- queueUrl: QUEUE_URL,
2075
- sqs,
2076
- visibilityTimeoutSeconds: 20,
2077
- visibilityHeartbeatIntervalMs: 5,
2078
- syncFn: async () => undefined,
2079
- sleep: fastSleep,
2080
- publishMetric: async () => undefined,
2081
- logger: makeDrainLogger([]),
2082
- });
2083
- const internals = receiver;
2084
- const heartbeat = internals.startReceiptLeaseHeartbeat();
2085
- heartbeat.add(message);
2086
- sqs.deleteMessage = async () => { throw new Error("simulated delete failure"); };
2087
- try {
2088
- await heartbeat.pause(); // settleEvents owns the outer pause.
2089
- try {
2090
- await sqs.deleteMessage({ queueUrl: QUEUE_URL, receiptHandle: "rh-delete-fails" });
2091
- }
2092
- catch {
2093
- await heartbeat.pause(); // acknowledge pauses while releasing a failed delete.
2094
- }
2095
- finally {
2096
- heartbeat.resume(); // acknowledge's finally must not resume the outer pause.
2097
- }
2098
- await vi.advanceTimersByTimeAsync(10);
2099
- expect(sqs.visibilityBatchCalls).toHaveLength(0);
2100
- heartbeat.resume();
2101
- await vi.advanceTimersByTimeAsync(5);
2102
- expect(sqs.visibilityBatchCalls).toHaveLength(1);
2103
- expect(sqs.visibilityBatchCalls[0]?.entries).toContainEqual({
2104
- id: "receipt-0",
2105
- receiptHandle: "rh-delete-fails",
2106
- });
2107
- }
2108
- finally {
2109
- await heartbeat.stop();
2110
- vi.useRealTimers();
2111
- await receiver.dispose();
2112
- }
2113
- });
2114
- it("waits for a timed-out pull to stop before applying its redelivery once", async () => {
2115
- const sqs = new FakeSqs();
2116
- const event = makeEvent({ relativePath: "companies/indigo/timeout-redelivery.md" });
2117
- sqs.enqueueDrainable([msg(event, "rh-timeout-original")]);
2118
- let oldPullStarted;
2119
- const oldPullStartedPromise = new Promise((resolve) => { oldPullStarted = resolve; });
2120
- let syncCalls = 0;
2121
- let oldPullStopped = false;
2122
- let applied = 0;
2123
- const receiver = new SqsPushReceiver({
2124
- tenantId: TENANT,
2125
- queueUrl: QUEUE_URL,
2126
- sqs,
2127
- maxInFlightDrains: 2,
2128
- drainTimeoutMs: 100,
2129
- disposeDrainMs: 1,
2130
- syncBatchFn: async (context) => {
2131
- syncCalls += 1;
2132
- if (syncCalls === 1) {
2133
- oldPullStarted();
2134
- await new Promise((resolve) => {
2135
- if (context.signal.aborted)
2136
- resolve();
2137
- else
2138
- context.signal.addEventListener("abort", () => resolve(), { once: true });
2139
- });
2140
- oldPullStopped = context.signal.aborted;
2141
- return;
2142
- }
2143
- expect(oldPullStopped).toBe(true);
2144
- applied += 1;
2145
- },
2146
- sleep: fastSleep,
2147
- publishMetric: async () => undefined,
2148
- logger: makeDrainLogger([]),
2149
- });
2150
- try {
2151
- await receiver.start();
2152
- await oldPullStartedPromise;
2153
- await new Promise((resolve) => setTimeout(resolve, 120));
2154
- await vi.waitFor(() => expect(sqs.visibilityChanges).toContainEqual({
2155
- receiptHandle: "rh-timeout-original",
2156
- visibilityTimeoutSeconds: 20,
2157
- }));
2158
- sqs.enqueueDrainable([msg(event, "rh-timeout-redelivered")]);
2159
- await vi.waitFor(() => expect(sqs.receivedReceiptHandles).toContain("rh-timeout-redelivered"));
2160
- await vi.waitFor(() => expect(applied).toBe(1));
2161
- expect(oldPullStopped).toBe(true);
2162
- expect(sqs.deleted).toContain("rh-timeout-redelivered");
2163
- expect(applied).toBe(1);
2164
- }
2165
- finally {
2166
- await receiver.dispose();
2167
- }
2168
- });
2169
- it("releases a timed-out multi-path batch once without reporting success telemetry", async () => {
2170
- const sqs = new FakeSqs();
2171
- const firstEvent = makeEvent({ relativePath: "companies/indigo/timeout-first.md", sequenceNumber: 31 });
2172
- const secondEvent = makeEvent({ relativePath: "companies/indigo/timeout-second.md", sequenceNumber: 32 });
2173
- sqs.enqueueDrainable([
2174
- msg(firstEvent, "rh-timeout-first"),
2175
- msg(secondEvent, "rh-timeout-second"),
2176
- ]);
2177
- const metric = vi.fn(async () => undefined);
2178
- const infos = [];
2179
- let syncCalls = 0;
2180
- const receiver = new SqsPushReceiver({
2181
- tenantId: TENANT,
2182
- queueUrl: QUEUE_URL,
2183
- sqs,
2184
- maxInFlightDrains: 2,
2185
- drainTimeoutMs: 30,
2186
- syncBatchFn: async ({ signal }) => {
2187
- syncCalls += 1;
2188
- await new Promise((resolve) => {
2189
- if (signal.aborted)
2190
- resolve();
2191
- else
2192
- signal.addEventListener("abort", () => resolve(), { once: true });
2193
- });
2194
- },
2195
- sleep: fastSleep,
2196
- publishMetric: metric,
2197
- logger: {
2198
- debug: () => undefined,
2199
- warn: () => undefined,
2200
- error: () => undefined,
2201
- info: (record) => infos.push(record),
2202
- },
2203
- });
2204
- try {
2205
- await receiver.start();
2206
- await vi.waitFor(() => {
2207
- expect(sqs.visibilityChanges).toEqual(expect.arrayContaining([
2208
- { receiptHandle: "rh-timeout-first", visibilityTimeoutSeconds: 20 },
2209
- { receiptHandle: "rh-timeout-second", visibilityTimeoutSeconds: 20 },
2210
- ]));
2211
- });
2212
- expect(syncCalls).toBe(1);
2213
- expect(metric).not.toHaveBeenCalled();
2214
- expect(infos.some((record) => record.event === "fanout.receive")).toBe(false);
2215
- }
2216
- finally {
2217
- await receiver.dispose();
2218
- }
2219
- });
2220
- it("applies a released receipt once when SQS redelivers it", async () => {
2221
- vi.useFakeTimers();
2222
- const sqs = new FakeSqs();
2223
- const event = makeEvent({ relativePath: "companies/indigo/redelivery-once.md" });
2224
- sqs.enqueueDrainable([msg(event)]);
2225
- let syncCalls = 0;
2226
- let applied = 0;
2227
- const receiver = new SqsPushReceiver({
2228
- tenantId: TENANT,
2229
- queueUrl: QUEUE_URL,
2230
- sqs,
2231
- maxInFlightDrains: 2,
2232
- drainTimeoutMs: 100,
2233
- visibilityTimeoutSeconds: 600,
2234
- visibilityHeartbeatIntervalMs: 300_000,
2235
- disposeDrainMs: 1,
2236
- syncBatchFn: async ({ signal }) => {
2237
- syncCalls += 1;
2238
- if (syncCalls === 1) {
2239
- await new Promise((resolve) => {
2240
- signal.addEventListener("abort", () => resolve(), { once: true });
2241
- });
2242
- return;
2243
- }
2244
- applied += 1;
2245
- },
2246
- sleep: fastSleep,
2247
- publishMetric: async () => undefined,
2248
- logger: makeDrainLogger([]),
2249
- });
2250
- try {
2251
- await receiver.start();
2252
- await vi.waitFor(() => expect(syncCalls).toBe(1));
2253
- await vi.advanceTimersByTimeAsync(100);
2254
- expect(sqs.visibilityChanges).toContainEqual({
2255
- receiptHandle: "rh-1",
2256
- visibilityTimeoutSeconds: 20,
2257
- });
2258
- // A new receipt handle for the same SQS message models the retry delivery.
2259
- sqs.enqueueDrainable([msg(event, "rh-redelivered")]);
2260
- await vi.waitFor(() => expect(syncCalls).toBe(2));
2261
- expect(applied).toBe(1);
2262
- expect(sqs.deleted).toEqual(["rh-redelivered"]);
2263
- expect(syncCalls).toBe(2);
2264
- }
2265
- finally {
2266
- const disposing = receiver.dispose();
2267
- await vi.advanceTimersByTimeAsync(10);
2268
- await disposing;
2269
- vi.useRealTimers();
2270
- }
2271
- });
2272
- const first = makeEvent({
2273
- relativePath: "companies/indigo/retry-first.md",
2274
- sequenceNumber: 101,
2275
- });
2276
- const second = makeEvent({
2277
- relativePath: "companies/indigo/retry-second.md",
2278
- sequenceNumber: 102,
2279
- });
2280
- function partitionEachEvent(events) {
2281
- return events.map((event) => [event]);
2282
- }
2283
- function makeDrainLogger(drains, warnings = []) {
2284
- return {
2285
- debug: () => undefined,
2286
- warn: (record) => warnings.push(record),
2287
- error: () => undefined,
2288
- info: (record) => {
2289
- if (record.event === "receiver.drain")
2290
- drains.push(record);
2291
- },
2292
- };
2293
- }
2294
- it("removes an invalid heartbeat receipt after logging the failure once", async () => {
2295
- const sqs = new FakeSqs();
2296
- sqs.enqueueDrainable([msg(first, "rh-invalid-heartbeat")]);
2297
- sqs.failVisibilityBatchEntry("rh-invalid-heartbeat", {
2298
- code: "ReceiptHandleIsInvalid",
2299
- message: "receipt handle is no longer valid",
2300
- senderFault: true,
2301
- });
2302
- const warnings = [];
2303
- let started = false;
2304
- let releasePass;
2305
- const passBlockedOnAdmission = new Promise((resolve) => {
2306
- releasePass = resolve;
2307
- });
2308
- const receiver = new SqsPushReceiver({
2309
- tenantId: TENANT,
2310
- queueUrl: QUEUE_URL,
2311
- sqs,
2312
- syncBatchFn: async () => {
2313
- started = true;
2314
- await passBlockedOnAdmission;
2315
- },
2316
- sleep: fastSleep,
2317
- publishMetric: async () => undefined,
2318
- visibilityHeartbeatIntervalMs: 5,
2319
- logger: makeDrainLogger([], warnings),
2320
- });
2321
- try {
2322
- await receiver.start();
2323
- await waitFor(() => started && sqs.visibilityBatchCalls.length >= 1);
2324
- await new Promise((resolve) => setTimeout(resolve, 40));
2325
- expect(sqs.visibilityBatchCalls).toHaveLength(1);
2326
- expect(warnings.filter((warning) => warning.event === "receiver.lease.extend.failed"))
2327
- .toHaveLength(1);
2328
- expect(warnings[0]).toMatchObject({
2329
- event: "receiver.lease.extend.failed",
2330
- code: "ReceiptHandleIsInvalid",
2331
- });
2332
- }
2333
- finally {
2334
- releasePass();
2335
- await receiver.dispose();
2336
- }
2337
- });
2338
- it("sets the failed batch receipts to 20 seconds without deleting them", async () => {
2339
- const sqs = new FakeSqs();
2340
- sqs.enqueueDrainable([
2341
- msg(first, "rh-retry-first"),
2342
- msg(second, "rh-retry-second"),
2343
- ]);
2344
- const calls = [];
2345
- const drains = [];
2346
- const syncBatchFn = Object.assign(async ({ events }) => {
2347
- calls.push(events.map((event) => event.relativePath));
2348
- if (calls.length === 1)
2349
- throw new Error("first targeted pull failed");
2350
- }, { partition: partitionEachEvent });
2351
- const receiver = new SqsPushReceiver({
2352
- tenantId: TENANT,
2353
- queueUrl: QUEUE_URL,
2354
- sqs,
2355
- syncBatchFn,
2356
- sleep: fastSleep,
2357
- publishMetric: async () => undefined,
2358
- logger: makeDrainLogger(drains),
2359
- });
2360
- await receiver.start();
2361
- await waitFor(() => drains.length === 1);
2362
- await receiver.dispose();
2363
- expect(calls[0]).toEqual([first.relativePath]);
2364
- expect(sqs.visibilityChanges).toEqual([
2365
- { receiptHandle: "rh-retry-first", visibilityTimeoutSeconds: 20 },
2366
- ]);
2367
- expect(sqs.deleted).not.toContain("rh-retry-first");
2368
- expect(drains[0]).toMatchObject({ retried: 1 });
2369
- });
2370
- it("dispatches the next batch in the same drain and deletes its receipts", async () => {
2371
- const sqs = new FakeSqs();
2372
- sqs.enqueueDrainable([
2373
- msg(first, "rh-next-first"),
2374
- msg(second, "rh-next-second"),
2375
- ]);
2376
- const calls = [];
2377
- const drains = [];
2378
- const syncBatchFn = Object.assign(async ({ events }) => {
2379
- calls.push(events.map((event) => event.relativePath));
2380
- if (calls.length === 1)
2381
- throw new Error("first targeted pull failed");
2382
- }, { partition: partitionEachEvent });
2383
- const receiver = new SqsPushReceiver({
2384
- tenantId: TENANT,
2385
- queueUrl: QUEUE_URL,
2386
- sqs,
2387
- syncBatchFn,
2388
- sleep: fastSleep,
2389
- publishMetric: async () => undefined,
2390
- logger: makeDrainLogger(drains),
2391
- });
2392
- await receiver.start();
2393
- await waitFor(() => drains.length === 1);
2394
- await receiver.dispose();
2395
- expect(calls).toEqual([[first.relativePath], [second.relativePath]]);
2396
- expect(sqs.deleted).toContain("rh-next-second");
2397
- expect(sqs.deleted).not.toContain("rh-next-first");
2398
- });
2399
- it("acks successful paths and retries only paths that fail inside a coalesced batch", async () => {
2400
- const blocked = makeEvent({
2401
- relativePath: "companies/indigo/out-of-scope.md",
2402
- sequenceNumber: 103,
2403
- });
2404
- const allowed = makeEvent({
2405
- relativePath: "companies/indigo/in-scope.md",
2406
- sequenceNumber: 104,
2407
- });
2408
- const sqs = new FakeSqs();
2409
- sqs.enqueueDrainable([
2410
- msg(blocked, "rh-out-of-scope"),
2411
- msg(allowed, "rh-in-scope"),
2412
- ]);
2413
- const calls = [];
2414
- const drains = [];
2415
- const syncBatchFn = async ({ events }) => {
2416
- const paths = events.map((event) => event.relativePath);
2417
- calls.push(paths);
2418
- if (paths.length > 1 || paths[0] === blocked.relativePath) {
2419
- throw new Error("targeted batch includes an out-of-scope path");
2420
- }
2421
- };
2422
- const receiver = new SqsPushReceiver({
2423
- tenantId: TENANT,
2424
- queueUrl: QUEUE_URL,
2425
- sqs,
2426
- syncBatchFn,
2427
- sleep: fastSleep,
2428
- publishMetric: async () => undefined,
2429
- logger: makeDrainLogger(drains),
2430
- });
2431
- await receiver.start();
2432
- await waitFor(() => drains.length === 1);
2433
- await receiver.dispose();
2434
- expect(calls).toEqual([
2435
- [blocked.relativePath, allowed.relativePath],
2436
- [blocked.relativePath],
2437
- [allowed.relativePath],
2438
- ]);
2439
- expect(sqs.deleted).toEqual(["rh-in-scope"]);
2440
- expect(sqs.visibilityChanges).toEqual([
2441
- { receiptHandle: "rh-out-of-scope", visibilityTimeoutSeconds: 20 },
2442
- ]);
2443
- expect(drains[0]).toMatchObject({ deleted: 1, retried: 1 });
2444
- });
2445
- it("holds the lease and resumes locally after a realtime-admission timeout", async () => {
2446
- const sqs = new FakeSqs();
2447
- sqs.enqueueDrainable([msg(first, "rh-admission-timeout")]);
2448
- const drains = [];
2449
- const warnings = [];
2450
- const errors = [];
2451
- const infos = [];
2452
- let attempts = 0;
2453
- const admissionTimeout = new Error("realtime admission timeout after 120000ms");
2454
- admissionTimeout.name = "RealtimeAdmissionTimeout";
2455
- const drainLogger = makeDrainLogger(drains, warnings);
2456
- const receiver = new SqsPushReceiver({
2457
- tenantId: TENANT,
2458
- queueUrl: QUEUE_URL,
2459
- sqs,
2460
- syncBatchFn: async () => {
2461
- attempts += 1;
2462
- if (attempts === 1)
2463
- throw admissionTimeout;
2464
- },
2465
- sleep: fastSleep,
2466
- publishMetric: async () => undefined,
2467
- logger: {
2468
- ...drainLogger,
2469
- error: (record) => errors.push(record),
2470
- info: (record) => {
2471
- infos.push(record);
2472
- drainLogger.info(record);
2473
- },
2474
- },
2475
- });
2476
- await receiver.start();
2477
- await waitFor(() => sqs.deleted.length === 1);
2478
- await receiver.dispose();
2479
- expect(attempts).toBe(2);
2480
- expect(sqs.visibilityChanges).toEqual([]);
2481
- expect(sqs.deleted).toEqual(["rh-admission-timeout"]);
2482
- expect(warnings.some((warning) => warning.event === "receiver.retry.failed")).toBe(false);
2483
- expect(drains[0]).toMatchObject({ deleted: 1, retried: 0 });
2484
- // A deferral, not a failure: no error-level line, one info line.
2485
- expect(errors).toEqual([]);
2486
- expect(infos.filter((record) => record.event === "receiver.sync.admission-timeout"))
2487
- .toHaveLength(1);
2488
- });
2489
- it("renews the receipt lease while it waits out repeated realtime-admission timeouts", async () => {
2490
- // The admission retry can hold a receipt for minutes behind a long
2491
- // reconcile. The lease heartbeat must keep renewing it for the whole
2492
- // wait, so the message cannot become visible to another receive and be
2493
- // delivered twice while this receiver still owns it.
2494
- const sqs = new FakeSqs();
2495
- sqs.enqueueDrainable([msg(first, "rh-admission-lease")]);
2496
- let attempts = 0;
2497
- const admissionTimeout = new Error("realtime admission timeout after 30000ms");
2498
- admissionTimeout.name = "RealtimeAdmissionTimeout";
2499
- const receiver = new SqsPushReceiver({
2500
- tenantId: TENANT,
2501
- queueUrl: QUEUE_URL,
2502
- sqs,
2503
- visibilityTimeoutSeconds: 600,
2504
- visibilityHeartbeatIntervalMs: 5,
2505
- syncBatchFn: async () => {
2506
- attempts += 1;
2507
- if (attempts <= 3)
2508
- throw admissionTimeout;
2509
- },
2510
- // Each retry wait spans several heartbeat intervals.
2511
- sleep: (_ms, signal) => new Promise((resolve) => {
2512
- if (signal.aborted)
2513
- return resolve();
2514
- setTimeout(resolve, 30);
2515
- }),
2516
- publishMetric: async () => undefined,
2517
- logger: { debug: () => undefined, info: () => undefined, warn: () => undefined, error: () => undefined },
2518
- });
2519
- await receiver.start();
2520
- await waitFor(() => sqs.deleted.length === 1);
2521
- await receiver.dispose();
2522
- expect(attempts).toBe(4);
2523
- const leaseChanges = sqs.visibilityChanges.filter((change) => change.receiptHandle === "rh-admission-lease");
2524
- // Renewed at least once per retry wait, always to the full lease, and
2525
- // never released for redelivery.
2526
- expect(leaseChanges.length).toBeGreaterThanOrEqual(3);
2527
- expect(leaseChanges.every((change) => change.visibilityTimeoutSeconds === 600)).toBe(true);
2528
- expect(sqs.deleted).toEqual(["rh-admission-lease"]);
2529
- });
2530
- it("keeps the all-success path free of visibility changes and deletes every receipt", async () => {
2531
- const sqs = new FakeSqs();
2532
- sqs.enqueueDrainable([
2533
- msg(first, "rh-success-first"),
2534
- msg(second, "rh-success-second"),
2535
- ]);
2536
- const drains = [];
2537
- const syncBatchFn = Object.assign(async () => undefined, { partition: partitionEachEvent });
2538
- const receiver = new SqsPushReceiver({
2539
- tenantId: TENANT,
2540
- queueUrl: QUEUE_URL,
2541
- sqs,
2542
- syncBatchFn,
2543
- sleep: fastSleep,
2544
- publishMetric: async () => undefined,
2545
- logger: makeDrainLogger(drains),
2546
- });
2547
- await receiver.start();
2548
- await waitFor(() => drains.length === 1);
2549
- await receiver.dispose();
2550
- expect(sqs.visibilityChanges).toEqual([]);
2551
- expect(sqs.deleted).toEqual(["rh-success-first", "rh-success-second"]);
2552
- });
2553
- it("logs a retry visibility failure and still dispatches the next batch", async () => {
2554
- const sqs = new FakeSqs();
2555
- sqs.failNextVisibilityChange();
2556
- sqs.enqueueDrainable([
2557
- msg(first, "rh-visibility-failure-first"),
2558
- msg(second, "rh-visibility-failure-second"),
2559
- ]);
2560
- const calls = [];
2561
- const drains = [];
2562
- const warnings = [];
2563
- const syncBatchFn = Object.assign(async ({ events }) => {
2564
- calls.push(events.map((event) => event.relativePath));
2565
- if (calls.length === 1)
2566
- throw new Error("first targeted pull failed");
2567
- }, { partition: partitionEachEvent });
2568
- const receiver = new SqsPushReceiver({
2569
- tenantId: TENANT,
2570
- queueUrl: QUEUE_URL,
2571
- sqs,
2572
- syncBatchFn,
2573
- sleep: fastSleep,
2574
- publishMetric: async () => undefined,
2575
- logger: makeDrainLogger(drains, warnings),
2576
- });
2577
- await receiver.start();
2578
- await waitFor(() => drains.length === 1);
2579
- await receiver.dispose();
2580
- expect(calls).toEqual([[first.relativePath], [second.relativePath]]);
2581
- expect(sqs.deleted).toContain("rh-visibility-failure-second");
2582
- expect(warnings).toContainEqual(expect.objectContaining({
2583
- event: "receiver.retry.failed",
2584
- messageId: `m-${first.relativePath}-${first.sequenceNumber}`,
2585
- }));
2586
- });
2587
- it("bounds a 64-path transport failure to one dispatch and batches the retry lease", async () => {
2588
- const total = 64;
2589
- const events = Array.from({ length: total }, (_, index) => makeEvent({
2590
- relativePath: `companies/indigo/transport-${index}.md`,
2591
- sequenceNumber: index + 1,
2592
- }));
2593
- const sqs = new FakeSqs();
2594
- sqs.enqueueDrainable(events.map((event, index) => msg(event, `rh-transport-${index}`)));
2595
- const drains = [];
2596
- let dispatchCalls = 0;
2597
- const transportError = Object.assign(new Error("ECONNRESET: socket hang up while pulling remote objects"), { code: "ECONNRESET" });
2598
- const startedAt = Date.now();
2599
- const receiver = new SqsPushReceiver({
2600
- tenantId: TENANT,
2601
- queueUrl: QUEUE_URL,
2602
- sqs,
2603
- drainMaxMessages: total,
2604
- batchMaxPaths: total,
2605
- syncBatchFn: async () => {
2606
- dispatchCalls += 1;
2607
- throw transportError;
2608
- },
2609
- sleep: fastSleep,
2610
- publishMetric: async () => undefined,
2611
- logger: makeDrainLogger(drains),
2612
- });
2613
- await receiver.start();
2614
- await waitFor(() => drains.length === 1, 5_000);
2615
- await receiver.dispose();
2616
- expect(dispatchCalls).toBe(1);
2617
- expect(Date.now() - startedAt).toBeLessThan(5_000);
2618
- expect(sqs.deleted).toEqual([]);
2619
- expect(sqs.visibilityChanges.filter((change) => change.visibilityTimeoutSeconds === 20))
2620
- .toHaveLength(total);
2621
- const retryCalls = sqs.visibilityBatchCalls.filter((call) => call.visibilityTimeoutSeconds === 20);
2622
- expect(retryCalls).toHaveLength(7);
2623
- expect(retryCalls.every((call) => call.entries.length <= 10)).toBe(true);
2624
- });
2625
- it("bisects a path-specific batch failure and acknowledges the other 63 paths", async () => {
2626
- const total = 64;
2627
- const blocked = makeEvent({
2628
- relativePath: `companies/indigo/blocked-${total - 1}.md`,
2629
- sequenceNumber: total,
2630
- });
2631
- const events = Array.from({ length: total - 1 }, (_, index) => makeEvent({
2632
- relativePath: `companies/indigo/allowed-${index}.md`,
2633
- sequenceNumber: index + 1,
2634
- })).concat(blocked);
2635
- const sqs = new FakeSqs();
2636
- sqs.enqueueDrainable(events.map((event, index) => msg(event, `rh-path-${index}`)));
2637
- const drains = [];
2638
- let dispatchCalls = 0;
2639
- const receiver = new SqsPushReceiver({
2640
- tenantId: TENANT,
2641
- queueUrl: QUEUE_URL,
2642
- sqs,
2643
- drainMaxMessages: total,
2644
- batchMaxPaths: total,
2645
- syncBatchFn: async ({ events: batch }) => {
2646
- dispatchCalls += 1;
2647
- if (batch.some((event) => event.relativePath === blocked.relativePath)) {
2648
- throw new Error("out-of-scope path rejected by targeted pull");
2649
- }
2650
- },
2651
- sleep: fastSleep,
2652
- publishMetric: async () => undefined,
2653
- logger: makeDrainLogger(drains),
2654
- });
2655
- await receiver.start();
2656
- await waitFor(() => drains.length === 1, 5_000);
2657
- await receiver.dispose();
2658
- expect(dispatchCalls).toBeLessThanOrEqual(13);
2659
- expect(sqs.deleted).toHaveLength(total - 1);
2660
- expect(sqs.deleted).not.toContain(`rh-path-${total - 1}`);
2661
- expect(sqs.visibilityChanges.filter((change) => change.visibilityTimeoutSeconds === 20))
2662
- .toEqual([{ receiptHandle: `rh-path-${total - 1}`, visibilityTimeoutSeconds: 20 }]);
2663
- expect(drains[0]).toMatchObject({ processed: total - 1, deleted: total - 1, retried: 1 });
2664
- });
2665
- it("reports failed retry entries separately from successful entries", async () => {
2666
- const sqs = new FakeSqs();
2667
- sqs.failVisibilityBatchEntry("rh-retry-entry-failed", {
2668
- code: "ReceiptHandleIsInvalid",
2669
- message: "one receipt was rejected",
2670
- senderFault: true,
2671
- });
2672
- sqs.enqueueDrainable([
2673
- msg(first, "rh-retry-entry-failed"),
2674
- msg(second, "rh-retry-entry-succeeded"),
2675
- ]);
2676
- const warnings = [];
2677
- const drains = [];
2678
- const transportError = Object.assign(new Error("network connection reset"), {
2679
- code: "ECONNRESET",
2680
- });
2681
- const receiver = new SqsPushReceiver({
2682
- tenantId: TENANT,
2683
- queueUrl: QUEUE_URL,
2684
- sqs,
2685
- syncBatchFn: async () => { throw transportError; },
2686
- sleep: fastSleep,
2687
- publishMetric: async () => undefined,
2688
- logger: makeDrainLogger(drains, warnings),
2689
- });
2690
- await receiver.start();
2691
- await waitFor(() => drains.length === 1);
2692
- await receiver.dispose();
2693
- const failures = warnings.filter((warning) => warning.event === "receiver.retry.failed");
2694
- expect(failures).toHaveLength(1);
2695
- expect(failures[0]).toMatchObject({
2696
- messageId: `m-${first.relativePath}-${first.sequenceNumber}`,
2697
- code: "ReceiptHandleIsInvalid",
2698
- failureMessage: "one receipt was rejected",
2699
- senderFault: true,
2700
- });
2701
- expect(sqs.visibilityChanges).toEqual([
2702
- { receiptHandle: "rh-retry-entry-succeeded", visibilityTimeoutSeconds: 20 },
2703
- ]);
2704
- expect(drains[0]).toMatchObject({ retried: 1 });
2705
- expect(failures[0]).not.toMatchObject({
2706
- messageId: `m-${second.relativePath}-${second.sequenceNumber}`,
2707
- });
2708
- });
2709
- it("uses about 100 ten-receipt API calls for a 1,000-receipt heartbeat", async () => {
2710
- const total = 1_000;
2711
- const sqs = new FakeSqs();
2712
- const receipts = Array.from({ length: total }, (_, index) => msg(makeEvent({
2713
- relativePath: `companies/indigo/heartbeat-${index}.md`,
2714
- sequenceNumber: index + 1,
2715
- }), `rh-heartbeat-${index}`));
2716
- for (let offset = 0; offset < receipts.length; offset += 10) {
2717
- sqs.enqueueDrainable(receipts.slice(offset, offset + 10));
2718
- }
2719
- let startPass;
2720
- const passStarted = new Promise((resolve) => { startPass = resolve; });
2721
- let releasePass;
2722
- const passGate = new Promise((resolve) => { releasePass = resolve; });
2723
- let finishDrain;
2724
- const drainFinished = new Promise((resolve) => { finishDrain = resolve; });
2725
- const receiver = new SqsPushReceiver({
2726
- tenantId: TENANT,
2727
- queueUrl: QUEUE_URL,
2728
- sqs,
2729
- drainMaxMessages: total,
2730
- batchMaxPaths: total,
2731
- maxInFlightDrains: 1,
2732
- visibilityTimeoutSeconds: 600,
2733
- visibilityHeartbeatIntervalMs: 1_000,
2734
- syncBatchFn: async () => {
2735
- startPass();
2736
- await passGate;
2737
- throw Object.assign(new Error("ECONNRESET while pulling the batch"), {
2738
- code: "ECONNRESET",
2739
- });
2740
- },
2741
- sleep: fastSleep,
2742
- publishMetric: async () => undefined,
2743
- logger: {
2744
- debug: () => undefined,
2745
- warn: () => undefined,
2746
- error: () => undefined,
2747
- info: (record) => {
2748
- if (record.event === "receiver.drain")
2749
- finishDrain();
2750
- },
2751
- },
2752
- });
2753
- vi.useFakeTimers();
2754
- try {
2755
- await receiver.start();
2756
- await passStarted;
2757
- await vi.advanceTimersByTimeAsync(1_000);
2758
- const heartbeatCalls = sqs.visibilityBatchCalls.filter((call) => call.visibilityTimeoutSeconds === 600);
2759
- expect(heartbeatCalls.length).toBeLessThanOrEqual(110);
2760
- expect(heartbeatCalls.every((call) => call.entries.length <= 10)).toBe(true);
2761
- expect(heartbeatCalls.reduce((count, call) => count + call.entries.length, 0))
2762
- .toBe(total);
2763
- releasePass();
2764
- await drainFinished;
2765
- await receiver.dispose();
2766
- const retryCalls = sqs.visibilityBatchCalls.filter((call) => call.visibilityTimeoutSeconds === 20);
2767
- expect(retryCalls.length).toBeLessThanOrEqual(110);
2768
- expect(retryCalls.every((call) => call.entries.length <= 10)).toBe(true);
2769
- expect(retryCalls.reduce((count, call) => count + call.entries.length, 0))
2770
- .toBe(total);
2771
- }
2772
- finally {
2773
- releasePass();
2774
- await receiver.dispose();
2775
- vi.useRealTimers();
2776
- }
2777
- });
2778
- it("updates a large failed batch with bounded parallel visibility calls", async () => {
2779
- const total = 512;
2780
- const baseSqs = new FakeSqs();
2781
- const receipts = Array.from({ length: total }, (_, index) => msg(makeEvent({
2782
- relativePath: "companies/indigo/retry-large-batch.md",
2783
- sequenceNumber: index + 1,
2784
- }), `rh-retry-large-${index}`));
2785
- for (let offset = 0; offset < receipts.length; offset += 10) {
2786
- baseSqs.enqueueDrainable(receipts.slice(offset, offset + 10));
2787
- }
2788
- const visibilityBatchCalls = [];
2789
- let activeCalls = 0;
2790
- let maxActiveCalls = 0;
2791
- let releaseVisibility;
2792
- const visibilityGate = new Promise((resolve) => {
2793
- releaseVisibility = resolve;
2794
- });
2795
- let firstCallStarted;
2796
- const firstStarted = new Promise((resolve) => {
2797
- firstCallStarted = resolve;
2798
- });
2799
- const sqs = {
2800
- receiveMessage: (args) => baseSqs.receiveMessage(args),
2801
- deleteMessage: (args) => baseSqs.deleteMessage(args),
2802
- changeMessageVisibility: async ({ receiptHandle }) => {
2803
- visibilityBatchCalls.push({ receiptHandles: [receiptHandle], visibilityTimeoutSeconds: 20 });
2804
- activeCalls += 1;
2805
- maxActiveCalls = Math.max(maxActiveCalls, activeCalls);
2806
- firstCallStarted();
2807
- await visibilityGate;
2808
- activeCalls -= 1;
2809
- },
2810
- changeMessageVisibilityBatch: async ({ entries, visibilityTimeoutSeconds }) => {
2811
- visibilityBatchCalls.push({
2812
- receiptHandles: entries.map((entry) => entry.receiptHandle),
2813
- visibilityTimeoutSeconds,
2814
- });
2815
- activeCalls += 1;
2816
- maxActiveCalls = Math.max(maxActiveCalls, activeCalls);
2817
- firstCallStarted();
2818
- await visibilityGate;
2819
- activeCalls -= 1;
2820
- return { failedEntries: [] };
2821
- },
2822
- };
2823
- const drains = [];
2824
- const receiver = new SqsPushReceiver({
2825
- tenantId: TENANT,
2826
- queueUrl: QUEUE_URL,
2827
- sqs,
2828
- syncBatchFn: async () => {
2829
- throw new Error("large targeted pull failed");
2830
- },
2831
- drainMaxMessages: total,
2832
- sleep: fastSleep,
2833
- publishMetric: async () => undefined,
2834
- logger: makeDrainLogger(drains),
2835
- });
2836
- await receiver.start();
2837
- await firstStarted;
2838
- const parallelCallsBeforeRelease = maxActiveCalls;
2839
- releaseVisibility();
2840
- await waitFor(() => drains.length === 1);
2841
- await receiver.dispose();
2842
- expect(parallelCallsBeforeRelease).toBeGreaterThan(1);
2843
- expect(parallelCallsBeforeRelease).toBeLessThan(total);
2844
- expect(visibilityBatchCalls).toHaveLength(Math.ceil(total / 10));
2845
- expect(visibilityBatchCalls.every((call) => call.receiptHandles.length <= 10)).toBe(true);
2846
- expect(visibilityBatchCalls.flatMap((call) => call.receiptHandles)).toHaveLength(total);
2847
- expect(visibilityBatchCalls.every((call) => call.visibilityTimeoutSeconds === 20)).toBe(true);
2848
- expect(baseSqs.deleted).toEqual([]);
2849
- });
2850
- });
2851
- //# sourceMappingURL=push-receiver.test.js.map