@smthrs/gateway 0.35.0 → 1.0.0-rc.4

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 (605) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/LICENSE +1 -1
  3. package/README.md +92 -0
  4. package/dist/cjs/Diagnosis.d.ts +275 -0
  5. package/dist/cjs/Diagnosis.d.ts.map +1 -0
  6. package/dist/cjs/Diagnosis.js +476 -0
  7. package/dist/cjs/Diagnosis.js.map +7 -0
  8. package/dist/cjs/EngineTrace.d.ts +76 -0
  9. package/dist/cjs/EngineTrace.d.ts.map +1 -0
  10. package/dist/cjs/EngineTrace.js +509 -0
  11. package/dist/cjs/EngineTrace.js.map +7 -0
  12. package/dist/cjs/GatewayError.d.ts +54 -0
  13. package/dist/cjs/GatewayError.d.ts.map +1 -0
  14. package/dist/cjs/GatewayError.js +51 -0
  15. package/dist/cjs/GatewayError.js.map +7 -0
  16. package/dist/cjs/GatewayProjection.d.ts +480 -0
  17. package/dist/cjs/GatewayProjection.d.ts.map +1 -0
  18. package/dist/cjs/GatewayProjection.js +471 -0
  19. package/dist/cjs/GatewayProjection.js.map +7 -0
  20. package/dist/cjs/GatewayRpcs.d.ts +1886 -0
  21. package/dist/cjs/GatewayRpcs.d.ts.map +1 -0
  22. package/dist/cjs/GatewayRpcs.js +121 -0
  23. package/dist/cjs/GatewayRpcs.js.map +7 -0
  24. package/dist/cjs/GatewaySchema.d.ts +3274 -0
  25. package/dist/cjs/GatewaySchema.d.ts.map +1 -0
  26. package/dist/cjs/GatewaySchema.js +141 -0
  27. package/dist/cjs/GatewaySchema.js.map +7 -0
  28. package/dist/cjs/GatewayServer.d.ts +427 -0
  29. package/dist/cjs/GatewayServer.d.ts.map +1 -0
  30. package/dist/cjs/GatewayServer.js +428 -0
  31. package/dist/cjs/GatewayServer.js.map +7 -0
  32. package/dist/cjs/HostBranchPresence.d.ts +19 -0
  33. package/dist/cjs/HostBranchPresence.d.ts.map +1 -0
  34. package/dist/cjs/HostBranchPresence.js +82 -0
  35. package/dist/cjs/HostBranchPresence.js.map +7 -0
  36. package/dist/cjs/Projections.d.ts +209 -0
  37. package/dist/cjs/Projections.d.ts.map +1 -0
  38. package/dist/cjs/Projections.js +906 -0
  39. package/dist/cjs/Projections.js.map +7 -0
  40. package/dist/cjs/RunDevTools.d.ts +181 -0
  41. package/dist/cjs/RunDevTools.d.ts.map +1 -0
  42. package/dist/cjs/RunDevTools.js +195 -0
  43. package/dist/cjs/RunDevTools.js.map +7 -0
  44. package/dist/cjs/RunHistory.d.ts +185 -0
  45. package/dist/cjs/RunHistory.d.ts.map +1 -0
  46. package/dist/cjs/RunHistory.js +68 -0
  47. package/dist/cjs/RunHistory.js.map +7 -0
  48. package/dist/cjs/RunTrace.d.ts +753 -0
  49. package/dist/cjs/RunTrace.d.ts.map +1 -0
  50. package/dist/cjs/RunTrace.js +1595 -0
  51. package/dist/cjs/RunTrace.js.map +7 -0
  52. package/dist/cjs/RuntimeBridge.d.ts +1386 -0
  53. package/dist/cjs/RuntimeBridge.d.ts.map +1 -0
  54. package/dist/cjs/RuntimeBridge.js +598 -0
  55. package/dist/cjs/RuntimeBridge.js.map +7 -0
  56. package/dist/cjs/bun/BunGateway.d.ts +20 -0
  57. package/dist/cjs/bun/BunGateway.d.ts.map +1 -0
  58. package/dist/cjs/bun/BunGateway.js +62 -0
  59. package/dist/cjs/bun/BunGateway.js.map +7 -0
  60. package/dist/cjs/index.d.ts +65 -0
  61. package/dist/cjs/index.d.ts.map +1 -0
  62. package/dist/cjs/index.js +59 -0
  63. package/dist/cjs/index.js.map +7 -0
  64. package/dist/cjs/internal/NativeGateway.d.ts +176 -0
  65. package/dist/cjs/internal/NativeGateway.d.ts.map +1 -0
  66. package/dist/cjs/internal/NativeGateway.js +187 -0
  67. package/dist/cjs/internal/NativeGateway.js.map +7 -0
  68. package/dist/cjs/internal/actionRenderings.d.ts +12 -0
  69. package/dist/cjs/internal/actionRenderings.d.ts.map +1 -0
  70. package/dist/cjs/internal/actionRenderings.js +224 -0
  71. package/dist/cjs/internal/actionRenderings.js.map +7 -0
  72. package/dist/cjs/internal/callEvents.d.ts +97 -0
  73. package/dist/cjs/internal/callEvents.d.ts.map +1 -0
  74. package/dist/cjs/internal/callEvents.js +169 -0
  75. package/dist/cjs/internal/callEvents.js.map +7 -0
  76. package/dist/cjs/internal/digestIndex.d.ts +31 -0
  77. package/dist/cjs/internal/digestIndex.d.ts.map +1 -0
  78. package/dist/cjs/internal/digestIndex.js +105 -0
  79. package/dist/cjs/internal/digestIndex.js.map +7 -0
  80. package/dist/cjs/internal/digestMemory.d.ts +19 -0
  81. package/dist/cjs/internal/digestMemory.d.ts.map +1 -0
  82. package/dist/cjs/internal/digestMemory.js +33 -0
  83. package/dist/cjs/internal/digestMemory.js.map +7 -0
  84. package/dist/cjs/internal/inspectLabels.d.ts +13 -0
  85. package/dist/cjs/internal/inspectLabels.d.ts.map +1 -0
  86. package/dist/cjs/internal/inspectLabels.js +29 -0
  87. package/dist/cjs/internal/inspectLabels.js.map +7 -0
  88. package/dist/cjs/internal/nativeResolution.d.ts +41 -0
  89. package/dist/cjs/internal/nativeResolution.d.ts.map +1 -0
  90. package/dist/cjs/internal/nativeResolution.js +62 -0
  91. package/dist/cjs/internal/nativeResolution.js.map +7 -0
  92. package/dist/cjs/internal/nodeEvents.d.ts +47 -0
  93. package/dist/cjs/internal/nodeEvents.d.ts.map +1 -0
  94. package/dist/cjs/internal/nodeEvents.js +60 -0
  95. package/dist/cjs/internal/nodeEvents.js.map +7 -0
  96. package/dist/cjs/node/NodeGateway.d.ts +16 -0
  97. package/dist/cjs/node/NodeGateway.d.ts.map +1 -0
  98. package/dist/cjs/node/NodeGateway.js +48 -0
  99. package/dist/cjs/node/NodeGateway.js.map +7 -0
  100. package/dist/cjs/package.json +1 -0
  101. package/dist/esm/Diagnosis.d.ts +275 -0
  102. package/dist/esm/Diagnosis.d.ts.map +1 -0
  103. package/dist/esm/Diagnosis.js +697 -0
  104. package/dist/esm/Diagnosis.js.map +1 -0
  105. package/dist/esm/EngineTrace.d.ts +76 -0
  106. package/dist/esm/EngineTrace.d.ts.map +1 -0
  107. package/dist/esm/EngineTrace.js +557 -0
  108. package/dist/esm/EngineTrace.js.map +1 -0
  109. package/dist/esm/GatewayError.d.ts +54 -0
  110. package/dist/esm/GatewayError.d.ts.map +1 -0
  111. package/dist/esm/GatewayError.js +60 -0
  112. package/dist/esm/GatewayError.js.map +1 -0
  113. package/dist/esm/GatewayProjection.d.ts +480 -0
  114. package/dist/esm/GatewayProjection.d.ts.map +1 -0
  115. package/dist/esm/GatewayProjection.js +799 -0
  116. package/dist/esm/GatewayProjection.js.map +1 -0
  117. package/dist/esm/GatewayRpcs.d.ts +1886 -0
  118. package/dist/esm/GatewayRpcs.d.ts.map +1 -0
  119. package/dist/esm/GatewayRpcs.js +145 -0
  120. package/dist/esm/GatewayRpcs.js.map +1 -0
  121. package/dist/esm/GatewaySchema.d.ts +3274 -0
  122. package/dist/esm/GatewaySchema.d.ts.map +1 -0
  123. package/dist/esm/GatewaySchema.js +244 -0
  124. package/dist/esm/GatewaySchema.js.map +1 -0
  125. package/dist/esm/GatewayServer.d.ts +427 -0
  126. package/dist/esm/GatewayServer.d.ts.map +1 -0
  127. package/dist/esm/GatewayServer.js +725 -0
  128. package/dist/esm/GatewayServer.js.map +1 -0
  129. package/dist/esm/HostBranchPresence.d.ts +19 -0
  130. package/dist/esm/HostBranchPresence.d.ts.map +1 -0
  131. package/dist/esm/HostBranchPresence.js +57 -0
  132. package/dist/esm/HostBranchPresence.js.map +1 -0
  133. package/dist/esm/Projections.d.ts +209 -0
  134. package/dist/esm/Projections.d.ts.map +1 -0
  135. package/dist/esm/Projections.js +1254 -0
  136. package/dist/esm/Projections.js.map +1 -0
  137. package/dist/esm/RunDevTools.d.ts +181 -0
  138. package/dist/esm/RunDevTools.d.ts.map +1 -0
  139. package/dist/esm/RunDevTools.js +281 -0
  140. package/dist/esm/RunDevTools.js.map +1 -0
  141. package/dist/esm/RunHistory.d.ts +185 -0
  142. package/dist/esm/RunHistory.d.ts.map +1 -0
  143. package/dist/esm/RunHistory.js +109 -0
  144. package/dist/esm/RunHistory.js.map +1 -0
  145. package/dist/esm/RunTrace.d.ts +753 -0
  146. package/dist/esm/RunTrace.d.ts.map +1 -0
  147. package/dist/esm/RunTrace.js +2106 -0
  148. package/dist/esm/RunTrace.js.map +1 -0
  149. package/dist/esm/RuntimeBridge.d.ts +1386 -0
  150. package/dist/esm/RuntimeBridge.d.ts.map +1 -0
  151. package/dist/esm/RuntimeBridge.js +659 -0
  152. package/dist/esm/RuntimeBridge.js.map +1 -0
  153. package/dist/esm/bun/BunGateway.d.ts +20 -0
  154. package/dist/esm/bun/BunGateway.d.ts.map +1 -0
  155. package/dist/esm/bun/BunGateway.js +32 -0
  156. package/dist/esm/bun/BunGateway.js.map +1 -0
  157. package/dist/esm/index.d.ts +65 -0
  158. package/dist/esm/index.d.ts.map +1 -0
  159. package/dist/esm/index.js +65 -0
  160. package/dist/esm/index.js.map +1 -0
  161. package/dist/esm/internal/NativeGateway.d.ts +176 -0
  162. package/dist/esm/internal/NativeGateway.d.ts.map +1 -0
  163. package/dist/esm/internal/NativeGateway.js +268 -0
  164. package/dist/esm/internal/NativeGateway.js.map +1 -0
  165. package/dist/esm/internal/actionRenderings.d.ts +12 -0
  166. package/dist/esm/internal/actionRenderings.d.ts.map +1 -0
  167. package/dist/esm/internal/actionRenderings.js +212 -0
  168. package/dist/esm/internal/actionRenderings.js.map +1 -0
  169. package/dist/esm/internal/callEvents.d.ts +97 -0
  170. package/dist/esm/internal/callEvents.d.ts.map +1 -0
  171. package/dist/esm/internal/callEvents.js +206 -0
  172. package/dist/esm/internal/callEvents.js.map +1 -0
  173. package/dist/esm/internal/digestIndex.d.ts +31 -0
  174. package/dist/esm/internal/digestIndex.d.ts.map +1 -0
  175. package/dist/esm/internal/digestIndex.js +87 -0
  176. package/dist/esm/internal/digestIndex.js.map +1 -0
  177. package/dist/esm/internal/digestMemory.d.ts +19 -0
  178. package/dist/esm/internal/digestMemory.d.ts.map +1 -0
  179. package/dist/esm/internal/digestMemory.js +23 -0
  180. package/dist/esm/internal/digestMemory.js.map +1 -0
  181. package/dist/esm/internal/inspectLabels.d.ts +13 -0
  182. package/dist/esm/internal/inspectLabels.d.ts.map +1 -0
  183. package/dist/esm/internal/inspectLabels.js +17 -0
  184. package/dist/esm/internal/inspectLabels.js.map +1 -0
  185. package/dist/esm/internal/nativeResolution.d.ts +41 -0
  186. package/dist/esm/internal/nativeResolution.d.ts.map +1 -0
  187. package/dist/esm/internal/nativeResolution.js +78 -0
  188. package/dist/esm/internal/nativeResolution.js.map +1 -0
  189. package/dist/esm/internal/nodeEvents.d.ts +47 -0
  190. package/dist/esm/internal/nodeEvents.d.ts.map +1 -0
  191. package/dist/esm/internal/nodeEvents.js +60 -0
  192. package/dist/esm/internal/nodeEvents.js.map +1 -0
  193. package/dist/esm/node/NodeGateway.d.ts +16 -0
  194. package/dist/esm/node/NodeGateway.d.ts.map +1 -0
  195. package/dist/esm/node/NodeGateway.js +13 -0
  196. package/dist/esm/node/NodeGateway.js.map +1 -0
  197. package/dist/vendor/adapters.json +2707 -0
  198. package/dist/vendor/bun/BunChildProcessSpawner.d.ts +7 -0
  199. package/dist/vendor/bun/BunChildProcessSpawner.js +14 -0
  200. package/dist/vendor/bun/BunClusterHttp.d.ts +83 -0
  201. package/dist/vendor/bun/BunClusterHttp.js +39 -0
  202. package/dist/vendor/bun/BunClusterSocket.d.ts +65 -0
  203. package/dist/vendor/bun/BunClusterSocket.js +22 -0
  204. package/dist/vendor/bun/BunCrypto.d.ts +10 -0
  205. package/dist/vendor/bun/BunCrypto.js +8 -0
  206. package/dist/vendor/bun/BunFileSystem.d.ts +10 -0
  207. package/dist/vendor/bun/BunFileSystem.js +9 -0
  208. package/dist/vendor/bun/BunHttpClient.d.ts +9 -0
  209. package/dist/vendor/bun/BunHttpClient.js +1 -0
  210. package/dist/vendor/bun/BunHttpPlatform.d.ts +10 -0
  211. package/dist/vendor/bun/BunHttpPlatform.js +11 -0
  212. package/dist/vendor/bun/BunHttpServer.d.ts +142 -0
  213. package/dist/vendor/bun/BunHttpServer.js +38 -0
  214. package/dist/vendor/bun/BunHttpServerRequest.d.ts +18 -0
  215. package/dist/vendor/bun/BunHttpServerRequest.js +7 -0
  216. package/dist/vendor/bun/BunMultipart.d.ts +31 -0
  217. package/dist/vendor/bun/BunMultipart.js +12 -0
  218. package/dist/vendor/bun/BunPath.d.ts +24 -0
  219. package/dist/vendor/bun/BunPath.js +12 -0
  220. package/dist/vendor/bun/BunRedis.d.ts +49 -0
  221. package/dist/vendor/bun/BunRedis.js +11 -0
  222. package/dist/vendor/bun/BunRuntime.d.ts +85 -0
  223. package/dist/vendor/bun/BunRuntime.js +8 -0
  224. package/dist/vendor/bun/BunServices.d.ts +34 -0
  225. package/dist/vendor/bun/BunServices.js +23 -0
  226. package/dist/vendor/bun/BunSink.d.ts +8 -0
  227. package/dist/vendor/bun/BunSink.js +12 -0
  228. package/dist/vendor/bun/BunSocket.d.ts +40 -0
  229. package/dist/vendor/bun/BunSocket.js +29 -0
  230. package/dist/vendor/bun/BunSocketServer.d.ts +8 -0
  231. package/dist/vendor/bun/BunSocketServer.js +21 -0
  232. package/dist/vendor/bun/BunStdio.d.ts +11 -0
  233. package/dist/vendor/bun/BunStdio.js +10 -0
  234. package/dist/vendor/bun/BunStream.d.ts +19 -0
  235. package/dist/vendor/bun/BunStream.js +30 -0
  236. package/dist/vendor/bun/BunTerminal.d.ts +21 -0
  237. package/dist/vendor/bun/BunTerminal.js +10 -0
  238. package/dist/vendor/bun/BunWorker.d.ts +20 -0
  239. package/dist/vendor/bun/BunWorker.js +9 -0
  240. package/dist/vendor/bun/BunWorkerRunner.d.ts +12 -0
  241. package/dist/vendor/bun/BunWorkerRunner.js +7 -0
  242. package/dist/vendor/bun/LICENSE +21 -0
  243. package/dist/vendor/bun/index.d.ts +92 -0
  244. package/dist/vendor/bun/index.js +107 -0
  245. package/dist/vendor/chunk-2GCOZTTW.js +48 -0
  246. package/dist/vendor/chunk-37CUR3PO.js +81 -0
  247. package/dist/vendor/chunk-3ARKKENZ.js +45 -0
  248. package/dist/vendor/chunk-3NV2UDBI.js +7 -0
  249. package/dist/vendor/chunk-3OSW7CT7.js +143 -0
  250. package/dist/vendor/chunk-62R63L74.js +62 -0
  251. package/dist/vendor/chunk-6AQLBC2I.js +118 -0
  252. package/dist/vendor/chunk-6C5H7S6B.js +18 -0
  253. package/dist/vendor/chunk-6DY5RBZD.js +85 -0
  254. package/dist/vendor/chunk-6UPBD47P.js +53 -0
  255. package/dist/vendor/chunk-AVVVN75Z.js +96 -0
  256. package/dist/vendor/chunk-B4DHEMBB.js +14 -0
  257. package/dist/vendor/chunk-BCK3NJOX.js +15 -0
  258. package/dist/vendor/chunk-BFBIHJ6X.js +0 -0
  259. package/dist/vendor/chunk-CES4OUM4.js +63 -0
  260. package/dist/vendor/chunk-D64L6YND.js +59 -0
  261. package/dist/vendor/chunk-DS2R6WOM.js +18 -0
  262. package/dist/vendor/chunk-DX4Z23DM.js +65 -0
  263. package/dist/vendor/chunk-DZ2VOUDP.js +564 -0
  264. package/dist/vendor/chunk-FOXN6JPJ.js +61 -0
  265. package/dist/vendor/chunk-FPQF7CR5.js +55 -0
  266. package/dist/vendor/chunk-FUTCTCEG.js +20 -0
  267. package/dist/vendor/chunk-GUPAV2YZ.js +18 -0
  268. package/dist/vendor/chunk-H2Z2Y4QH.js +70 -0
  269. package/dist/vendor/chunk-HAEDVSSM.js +129 -0
  270. package/dist/vendor/chunk-HY7BBL3K.js +93 -0
  271. package/dist/vendor/chunk-IFTTT6D5.js +31 -0
  272. package/dist/vendor/chunk-KQDSNB7M.js +26 -0
  273. package/dist/vendor/chunk-LIHLXXGQ.js +26 -0
  274. package/dist/vendor/chunk-LKCGQEE5.js +28 -0
  275. package/dist/vendor/chunk-MH2J3BYB.js +0 -0
  276. package/dist/vendor/chunk-ML2NAFH2.js +131 -0
  277. package/dist/vendor/chunk-MLFIZ5O2.js +0 -0
  278. package/dist/vendor/chunk-MLKGABMK.js +9 -0
  279. package/dist/vendor/chunk-MSCLEMYX.js +71 -0
  280. package/dist/vendor/chunk-OBWXQKVP.js +57 -0
  281. package/dist/vendor/chunk-QR557K66.js +20 -0
  282. package/dist/vendor/chunk-R4QUY7DH.js +18 -0
  283. package/dist/vendor/chunk-RGECXXJS.js +18 -0
  284. package/dist/vendor/chunk-TFY7W6D4.js +36 -0
  285. package/dist/vendor/chunk-TGPIVY4A.js +104 -0
  286. package/dist/vendor/chunk-TGXCX6W3.js +398 -0
  287. package/dist/vendor/chunk-TOHSFXMV.js +437 -0
  288. package/dist/vendor/chunk-UHIDPUFF.js +34 -0
  289. package/dist/vendor/chunk-ULT6CWUI.js +109 -0
  290. package/dist/vendor/chunk-UXBGYQR3.js +18 -0
  291. package/dist/vendor/chunk-V6LUGQ7O.js +21 -0
  292. package/dist/vendor/chunk-W4OASVX6.js +92 -0
  293. package/dist/vendor/chunk-WAII54GA.js +18 -0
  294. package/dist/vendor/chunk-WECQDT36.js +426 -0
  295. package/dist/vendor/chunk-WHKMOC7Z.js +28 -0
  296. package/dist/vendor/chunk-WVFOZ2UL.js +40 -0
  297. package/dist/vendor/chunk-X73E2UPI.js +18 -0
  298. package/dist/vendor/chunk-XCYARUII.js +63 -0
  299. package/dist/vendor/chunk-XFEVTDVD.js +34 -0
  300. package/dist/vendor/chunk-XFZZYSJ4.js +22 -0
  301. package/dist/vendor/chunk-XJWJXLXJ.js +18 -0
  302. package/dist/vendor/chunk-XY3Q62EO.js +18 -0
  303. package/dist/vendor/chunk-YDASSC75.js +394 -0
  304. package/dist/vendor/chunk-Z6C7SO5Z.js +507 -0
  305. package/dist/vendor/chunk-Z7QNPJEB.js +229 -0
  306. package/dist/vendor/chunk-Z7QPP4YH.js +249 -0
  307. package/dist/vendor/chunk-ZCHYLHJH.js +86 -0
  308. package/dist/vendor/chunk-ZIQVYVMA.js +22 -0
  309. package/dist/vendor/chunk-ZPIG2CUA.js +34 -0
  310. package/dist/vendor/cjs/bun/BunChildProcessSpawner.d.ts +7 -0
  311. package/dist/vendor/cjs/bun/BunChildProcessSpawner.js +1 -0
  312. package/dist/vendor/cjs/bun/BunClusterHttp.d.ts +83 -0
  313. package/dist/vendor/cjs/bun/BunClusterHttp.js +1 -0
  314. package/dist/vendor/cjs/bun/BunClusterSocket.d.ts +65 -0
  315. package/dist/vendor/cjs/bun/BunClusterSocket.js +1 -0
  316. package/dist/vendor/cjs/bun/BunCrypto.d.ts +10 -0
  317. package/dist/vendor/cjs/bun/BunCrypto.js +1 -0
  318. package/dist/vendor/cjs/bun/BunFileSystem.d.ts +10 -0
  319. package/dist/vendor/cjs/bun/BunFileSystem.js +1 -0
  320. package/dist/vendor/cjs/bun/BunHttpClient.d.ts +9 -0
  321. package/dist/vendor/cjs/bun/BunHttpClient.js +1 -0
  322. package/dist/vendor/cjs/bun/BunHttpPlatform.d.ts +10 -0
  323. package/dist/vendor/cjs/bun/BunHttpPlatform.js +1 -0
  324. package/dist/vendor/cjs/bun/BunHttpServer.d.ts +142 -0
  325. package/dist/vendor/cjs/bun/BunHttpServer.js +1 -0
  326. package/dist/vendor/cjs/bun/BunHttpServerRequest.d.ts +18 -0
  327. package/dist/vendor/cjs/bun/BunHttpServerRequest.js +1 -0
  328. package/dist/vendor/cjs/bun/BunMultipart.d.ts +31 -0
  329. package/dist/vendor/cjs/bun/BunMultipart.js +1 -0
  330. package/dist/vendor/cjs/bun/BunPath.d.ts +24 -0
  331. package/dist/vendor/cjs/bun/BunPath.js +1 -0
  332. package/dist/vendor/cjs/bun/BunRedis.d.ts +49 -0
  333. package/dist/vendor/cjs/bun/BunRedis.js +1 -0
  334. package/dist/vendor/cjs/bun/BunRuntime.d.ts +85 -0
  335. package/dist/vendor/cjs/bun/BunRuntime.js +1 -0
  336. package/dist/vendor/cjs/bun/BunServices.d.ts +34 -0
  337. package/dist/vendor/cjs/bun/BunServices.js +1 -0
  338. package/dist/vendor/cjs/bun/BunSink.d.ts +8 -0
  339. package/dist/vendor/cjs/bun/BunSink.js +1 -0
  340. package/dist/vendor/cjs/bun/BunSocket.d.ts +40 -0
  341. package/dist/vendor/cjs/bun/BunSocket.js +1 -0
  342. package/dist/vendor/cjs/bun/BunSocketServer.d.ts +8 -0
  343. package/dist/vendor/cjs/bun/BunSocketServer.js +1 -0
  344. package/dist/vendor/cjs/bun/BunStdio.d.ts +11 -0
  345. package/dist/vendor/cjs/bun/BunStdio.js +1 -0
  346. package/dist/vendor/cjs/bun/BunStream.d.ts +19 -0
  347. package/dist/vendor/cjs/bun/BunStream.js +1 -0
  348. package/dist/vendor/cjs/bun/BunTerminal.d.ts +21 -0
  349. package/dist/vendor/cjs/bun/BunTerminal.js +1 -0
  350. package/dist/vendor/cjs/bun/BunWorker.d.ts +20 -0
  351. package/dist/vendor/cjs/bun/BunWorker.js +1 -0
  352. package/dist/vendor/cjs/bun/BunWorkerRunner.d.ts +12 -0
  353. package/dist/vendor/cjs/bun/BunWorkerRunner.js +1 -0
  354. package/dist/vendor/cjs/bun/index.d.ts +92 -0
  355. package/dist/vendor/cjs/bun/index.js +1 -0
  356. package/dist/vendor/cjs/node/NodeChildProcessSpawner.d.ts +7 -0
  357. package/dist/vendor/cjs/node/NodeChildProcessSpawner.js +1 -0
  358. package/dist/vendor/cjs/node/NodeClusterHttp.d.ts +63 -0
  359. package/dist/vendor/cjs/node/NodeClusterHttp.js +1 -0
  360. package/dist/vendor/cjs/node/NodeClusterSocket.d.ts +79 -0
  361. package/dist/vendor/cjs/node/NodeClusterSocket.js +1 -0
  362. package/dist/vendor/cjs/node/NodeCrypto.d.ts +10 -0
  363. package/dist/vendor/cjs/node/NodeCrypto.js +1 -0
  364. package/dist/vendor/cjs/node/NodeFileSystem.d.ts +10 -0
  365. package/dist/vendor/cjs/node/NodeFileSystem.js +1 -0
  366. package/dist/vendor/cjs/node/NodeHttpClient.d.ts +179 -0
  367. package/dist/vendor/cjs/node/NodeHttpClient.js +1 -0
  368. package/dist/vendor/cjs/node/NodeHttpIncomingMessage.d.ts +62 -0
  369. package/dist/vendor/cjs/node/NodeHttpIncomingMessage.js +1 -0
  370. package/dist/vendor/cjs/node/NodeHttpPlatform.d.ts +36 -0
  371. package/dist/vendor/cjs/node/NodeHttpPlatform.js +1 -0
  372. package/dist/vendor/cjs/node/NodeHttpServer.d.ts +120 -0
  373. package/dist/vendor/cjs/node/NodeHttpServer.js +1 -0
  374. package/dist/vendor/cjs/node/NodeHttpServerRequest.d.ts +29 -0
  375. package/dist/vendor/cjs/node/NodeHttpServerRequest.js +1 -0
  376. package/dist/vendor/cjs/node/NodeMultipart.d.ts +46 -0
  377. package/dist/vendor/cjs/node/NodeMultipart.js +1 -0
  378. package/dist/vendor/cjs/node/NodeMultipartParser.d.ts +97 -0
  379. package/dist/vendor/cjs/node/NodeMultipartParser.js +1 -0
  380. package/dist/vendor/cjs/node/NodePath.d.ts +27 -0
  381. package/dist/vendor/cjs/node/NodePath.js +1 -0
  382. package/dist/vendor/cjs/node/NodeRedis.d.ts +74 -0
  383. package/dist/vendor/cjs/node/NodeRedis.js +1 -0
  384. package/dist/vendor/cjs/node/NodeRuntime.d.ts +85 -0
  385. package/dist/vendor/cjs/node/NodeRuntime.js +1 -0
  386. package/dist/vendor/cjs/node/NodeServices.d.ts +34 -0
  387. package/dist/vendor/cjs/node/NodeServices.js +1 -0
  388. package/dist/vendor/cjs/node/NodeSink.d.ts +8 -0
  389. package/dist/vendor/cjs/node/NodeSink.js +1 -0
  390. package/dist/vendor/cjs/node/NodeSocket.d.ts +38 -0
  391. package/dist/vendor/cjs/node/NodeSocket.js +1 -0
  392. package/dist/vendor/cjs/node/NodeSocketServer.d.ts +8 -0
  393. package/dist/vendor/cjs/node/NodeSocketServer.js +1 -0
  394. package/dist/vendor/cjs/node/NodeStdio.d.ts +11 -0
  395. package/dist/vendor/cjs/node/NodeStdio.js +1 -0
  396. package/dist/vendor/cjs/node/NodeStream.d.ts +8 -0
  397. package/dist/vendor/cjs/node/NodeStream.js +1 -0
  398. package/dist/vendor/cjs/node/NodeTerminal.d.ts +21 -0
  399. package/dist/vendor/cjs/node/NodeTerminal.js +1 -0
  400. package/dist/vendor/cjs/node/NodeWorker.d.ts +22 -0
  401. package/dist/vendor/cjs/node/NodeWorker.js +1 -0
  402. package/dist/vendor/cjs/node/NodeWorkerRunner.d.ts +12 -0
  403. package/dist/vendor/cjs/node/NodeWorkerRunner.js +1 -0
  404. package/dist/vendor/cjs/node/Undici.d.ts +28 -0
  405. package/dist/vendor/cjs/node/Undici.js +1 -0
  406. package/dist/vendor/cjs/node/index.d.ts +100 -0
  407. package/dist/vendor/cjs/node/index.js +1 -0
  408. package/dist/vendor/cjs/node-shared/NodeChildProcessSpawner.d.ts +57 -0
  409. package/dist/vendor/cjs/node-shared/NodeChildProcessSpawner.js +1 -0
  410. package/dist/vendor/cjs/node-shared/NodeClusterSocket.d.ts +22 -0
  411. package/dist/vendor/cjs/node-shared/NodeClusterSocket.js +1 -0
  412. package/dist/vendor/cjs/node-shared/NodeCrypto.d.ts +27 -0
  413. package/dist/vendor/cjs/node-shared/NodeCrypto.js +1 -0
  414. package/dist/vendor/cjs/node-shared/NodeFileSystem.d.ts +11 -0
  415. package/dist/vendor/cjs/node-shared/NodeFileSystem.js +1 -0
  416. package/dist/vendor/cjs/node-shared/NodeHttpCompression.d.ts +36 -0
  417. package/dist/vendor/cjs/node-shared/NodeHttpCompression.js +1 -0
  418. package/dist/vendor/cjs/node-shared/NodePath.d.ts +27 -0
  419. package/dist/vendor/cjs/node-shared/NodePath.js +1 -0
  420. package/dist/vendor/cjs/node-shared/NodeRuntime.d.ts +47 -0
  421. package/dist/vendor/cjs/node-shared/NodeRuntime.js +1 -0
  422. package/dist/vendor/cjs/node-shared/NodeSink.d.ts +69 -0
  423. package/dist/vendor/cjs/node-shared/NodeSink.js +1 -0
  424. package/dist/vendor/cjs/node-shared/NodeSocket.d.ts +134 -0
  425. package/dist/vendor/cjs/node-shared/NodeSocket.js +1 -0
  426. package/dist/vendor/cjs/node-shared/NodeSocketServer.d.ts +80 -0
  427. package/dist/vendor/cjs/node-shared/NodeSocketServer.js +1 -0
  428. package/dist/vendor/cjs/node-shared/NodeStdio.d.ts +12 -0
  429. package/dist/vendor/cjs/node-shared/NodeStdio.js +1 -0
  430. package/dist/vendor/cjs/node-shared/NodeStream.d.ts +179 -0
  431. package/dist/vendor/cjs/node-shared/NodeStream.js +1 -0
  432. package/dist/vendor/cjs/node-shared/NodeTerminal.d.ts +22 -0
  433. package/dist/vendor/cjs/node-shared/NodeTerminal.js +1 -0
  434. package/dist/vendor/cjs/node-shared/index.d.ts +56 -0
  435. package/dist/vendor/cjs/node-shared/index.js +1 -0
  436. package/dist/vendor/cjs/node-shared/internal/nodeChildProcessSpawner.d.ts +4 -0
  437. package/dist/vendor/cjs/node-shared/internal/nodeChildProcessSpawner.js +1 -0
  438. package/dist/vendor/cjs/node-shared/internal/utils.d.ts +2 -0
  439. package/dist/vendor/cjs/node-shared/internal/utils.js +1 -0
  440. package/dist/vendor/cjs/package.json +1 -0
  441. package/dist/vendor/cjs/platform-bun.d.ts +92 -0
  442. package/dist/vendor/cjs/platform-bun.js +1 -0
  443. package/dist/vendor/cjs/platform-node-shared.d.ts +56 -0
  444. package/dist/vendor/cjs/platform-node-shared.js +1 -0
  445. package/dist/vendor/cjs/platform-node.d.ts +100 -0
  446. package/dist/vendor/cjs/platform-node.js +1 -0
  447. package/dist/vendor/node/LICENSE +21 -0
  448. package/dist/vendor/node/NodeChildProcessSpawner.d.ts +7 -0
  449. package/dist/vendor/node/NodeChildProcessSpawner.js +14 -0
  450. package/dist/vendor/node/NodeClusterHttp.d.ts +63 -0
  451. package/dist/vendor/node/NodeClusterHttp.js +43 -0
  452. package/dist/vendor/node/NodeClusterSocket.d.ts +79 -0
  453. package/dist/vendor/node/NodeClusterSocket.js +30 -0
  454. package/dist/vendor/node/NodeCrypto.d.ts +10 -0
  455. package/dist/vendor/node/NodeCrypto.js +8 -0
  456. package/dist/vendor/node/NodeFileSystem.d.ts +10 -0
  457. package/dist/vendor/node/NodeFileSystem.js +9 -0
  458. package/dist/vendor/node/NodeHttpClient.d.ts +179 -0
  459. package/dist/vendor/node/NodeHttpClient.js +46 -0
  460. package/dist/vendor/node/NodeHttpIncomingMessage.d.ts +62 -0
  461. package/dist/vendor/node/NodeHttpIncomingMessage.js +10 -0
  462. package/dist/vendor/node/NodeHttpPlatform.d.ts +36 -0
  463. package/dist/vendor/node/NodeHttpPlatform.js +16 -0
  464. package/dist/vendor/node/NodeHttpServer.d.ts +120 -0
  465. package/dist/vendor/node/NodeHttpServer.js +46 -0
  466. package/dist/vendor/node/NodeHttpServerRequest.d.ts +29 -0
  467. package/dist/vendor/node/NodeHttpServerRequest.js +9 -0
  468. package/dist/vendor/node/NodeMultipart.d.ts +46 -0
  469. package/dist/vendor/node/NodeMultipart.js +15 -0
  470. package/dist/vendor/node/NodeMultipartParser.d.ts +97 -0
  471. package/dist/vendor/node/NodeMultipartParser.js +11 -0
  472. package/dist/vendor/node/NodePath.d.ts +27 -0
  473. package/dist/vendor/node/NodePath.js +12 -0
  474. package/dist/vendor/node/NodeRedis.d.ts +74 -0
  475. package/dist/vendor/node/NodeRedis.js +11 -0
  476. package/dist/vendor/node/NodeRuntime.d.ts +85 -0
  477. package/dist/vendor/node/NodeRuntime.js +8 -0
  478. package/dist/vendor/node/NodeServices.d.ts +34 -0
  479. package/dist/vendor/node/NodeServices.js +23 -0
  480. package/dist/vendor/node/NodeSink.d.ts +8 -0
  481. package/dist/vendor/node/NodeSink.js +12 -0
  482. package/dist/vendor/node/NodeSocket.d.ts +38 -0
  483. package/dist/vendor/node/NodeSocket.js +31 -0
  484. package/dist/vendor/node/NodeSocketServer.d.ts +8 -0
  485. package/dist/vendor/node/NodeSocketServer.js +21 -0
  486. package/dist/vendor/node/NodeStdio.d.ts +11 -0
  487. package/dist/vendor/node/NodeStdio.js +10 -0
  488. package/dist/vendor/node/NodeStream.d.ts +8 -0
  489. package/dist/vendor/node/NodeStream.js +27 -0
  490. package/dist/vendor/node/NodeTerminal.d.ts +21 -0
  491. package/dist/vendor/node/NodeTerminal.js +10 -0
  492. package/dist/vendor/node/NodeWorker.d.ts +22 -0
  493. package/dist/vendor/node/NodeWorker.js +9 -0
  494. package/dist/vendor/node/NodeWorkerRunner.d.ts +12 -0
  495. package/dist/vendor/node/NodeWorkerRunner.js +7 -0
  496. package/dist/vendor/node/Undici.d.ts +28 -0
  497. package/dist/vendor/node/Undici.js +9 -0
  498. package/dist/vendor/node/index.d.ts +100 -0
  499. package/dist/vendor/node/index.js +115 -0
  500. package/dist/vendor/node-shared/LICENSE +21 -0
  501. package/dist/vendor/node-shared/NodeChildProcessSpawner.d.ts +57 -0
  502. package/dist/vendor/node-shared/NodeChildProcessSpawner.js +13 -0
  503. package/dist/vendor/node-shared/NodeClusterSocket.d.ts +22 -0
  504. package/dist/vendor/node-shared/NodeClusterSocket.js +11 -0
  505. package/dist/vendor/node-shared/NodeCrypto.d.ts +27 -0
  506. package/dist/vendor/node-shared/NodeCrypto.js +9 -0
  507. package/dist/vendor/node-shared/NodeFileSystem.d.ts +11 -0
  508. package/dist/vendor/node-shared/NodeFileSystem.js +8 -0
  509. package/dist/vendor/node-shared/NodeHttpCompression.d.ts +36 -0
  510. package/dist/vendor/node-shared/NodeHttpCompression.js +13 -0
  511. package/dist/vendor/node-shared/NodePath.d.ts +27 -0
  512. package/dist/vendor/node-shared/NodePath.js +11 -0
  513. package/dist/vendor/node-shared/NodeRuntime.d.ts +47 -0
  514. package/dist/vendor/node-shared/NodeRuntime.js +7 -0
  515. package/dist/vendor/node-shared/NodeSink.d.ts +69 -0
  516. package/dist/vendor/node-shared/NodeSink.js +11 -0
  517. package/dist/vendor/node-shared/NodeSocket.d.ts +134 -0
  518. package/dist/vendor/node-shared/NodeSocket.js +23 -0
  519. package/dist/vendor/node-shared/NodeSocketServer.d.ts +80 -0
  520. package/dist/vendor/node-shared/NodeSocketServer.js +20 -0
  521. package/dist/vendor/node-shared/NodeStdio.d.ts +12 -0
  522. package/dist/vendor/node-shared/NodeStdio.js +9 -0
  523. package/dist/vendor/node-shared/NodeStream.d.ts +179 -0
  524. package/dist/vendor/node-shared/NodeStream.js +26 -0
  525. package/dist/vendor/node-shared/NodeTerminal.d.ts +22 -0
  526. package/dist/vendor/node-shared/NodeTerminal.js +9 -0
  527. package/dist/vendor/node-shared/index.d.ts +56 -0
  528. package/dist/vendor/node-shared/index.js +58 -0
  529. package/dist/vendor/node-shared/internal/nodeChildProcessSpawner.d.ts +4 -0
  530. package/dist/vendor/node-shared/internal/nodeChildProcessSpawner.js +7 -0
  531. package/dist/vendor/node-shared/internal/utils.d.ts +2 -0
  532. package/dist/vendor/node-shared/internal/utils.js +7 -0
  533. package/dist/vendor/platform-bun.d.ts +92 -0
  534. package/dist/vendor/platform-bun.js +107 -0
  535. package/dist/vendor/platform-node-shared.d.ts +56 -0
  536. package/dist/vendor/platform-node-shared.js +58 -0
  537. package/dist/vendor/platform-node.d.ts +100 -0
  538. package/dist/vendor/platform-node.js +115 -0
  539. package/docs/README.md +158 -0
  540. package/docs/api.md +601 -0
  541. package/docs/concepts/projections.md +250 -0
  542. package/docs/concepts/subscriptions.md +204 -0
  543. package/docs/concepts/trust-boundary.md +256 -0
  544. package/docs/guides/diagnose-a-run.md +115 -0
  545. package/docs/guides/follow-a-run.md +150 -0
  546. package/docs/guides/host-the-gateway.md +153 -0
  547. package/docs/guides/read-a-projection.md +138 -0
  548. package/docs/guides/serve-beyond-loopback.md +166 -0
  549. package/docs/guides/submit-an-approval.md +187 -0
  550. package/docs/guides/testing.md +148 -0
  551. package/docs/installation.md +89 -0
  552. package/docs/quickstart.md +200 -0
  553. package/docs/troubleshooting.md +355 -0
  554. package/package.json +264 -32
  555. package/src/Diagnosis.ts +892 -0
  556. package/src/EngineTrace.ts +627 -0
  557. package/src/GatewayError.ts +71 -0
  558. package/src/GatewayProjection.ts +959 -0
  559. package/src/GatewayRpcs.ts +179 -0
  560. package/src/GatewaySchema.ts +516 -0
  561. package/src/GatewayServer.ts +932 -0
  562. package/src/HostBranchPresence.ts +76 -0
  563. package/src/Projections.ts +1735 -0
  564. package/src/RunDevTools.ts +394 -0
  565. package/src/RunHistory.ts +174 -0
  566. package/src/RunTrace.ts +2796 -0
  567. package/src/RuntimeBridge.ts +835 -0
  568. package/src/bun/BunGateway.ts +52 -0
  569. package/src/index.ts +77 -0
  570. package/src/internal/NativeGateway.ts +360 -0
  571. package/src/internal/actionRenderings.ts +212 -0
  572. package/src/internal/callEvents.ts +257 -0
  573. package/src/internal/digestIndex.ts +134 -0
  574. package/src/internal/digestMemory.ts +27 -0
  575. package/src/internal/inspectLabels.ts +18 -0
  576. package/src/internal/nativeResolution.ts +103 -0
  577. package/src/internal/nodeEvents.ts +100 -0
  578. package/src/node/NodeGateway.ts +30 -0
  579. package/openapi.yaml +0 -29945
  580. package/src/README.md +0 -39
  581. package/src/api/README.md +0 -23
  582. package/src/api/apiCollectionNames.js +0 -10
  583. package/src/api/index.d.ts +0 -126
  584. package/src/api/index.js +0 -14
  585. package/src/api/normalizeApiRow.js +0 -36
  586. package/src/api/serializeAccountRow.js +0 -10
  587. package/src/api/serializeApprovalRow.js +0 -53
  588. package/src/api/serializeComparisonScoreRow.js +0 -18
  589. package/src/api/serializeCronRow.js +0 -30
  590. package/src/api/serializeDocRow.js +0 -20
  591. package/src/api/serializeMemoryFactRow.js +0 -21
  592. package/src/api/serializePromptRow.js +0 -10
  593. package/src/api/serializeRunEventRow.js +0 -28
  594. package/src/api/serializeRunRow.js +0 -93
  595. package/src/api/serializeScoreDetailRow.js +0 -25
  596. package/src/api/serializeScoreRow.js +0 -26
  597. package/src/api/serializeTicketRow.js +0 -20
  598. package/src/api/serializeWorkflowRow.js +0 -10
  599. package/src/auth/scopes.d.ts +0 -19
  600. package/src/auth/scopes.js +0 -164
  601. package/src/index.d.ts +0 -316
  602. package/src/index.js +0 -108
  603. package/src/rpc/gatewayRpcTypes.ts +0 -186
  604. package/src/rpc/index.d.ts +0 -223
  605. package/src/rpc/index.js +0 -2527
@@ -0,0 +1,1254 @@
1
+ /**
2
+ * The gateway read path: served projections computed from the control plane.
3
+ *
4
+ * Every projection here is a fold over facts `@smthrs/control` already
5
+ * publishes: `Control.list` for run and flow listings, `Control.watch` for a
6
+ * run's ordered events. The gateway never opens the engine database, so a
7
+ * projection served to a browser through a relay is the same projection a
8
+ * local reader computes, and a projection cannot drift from the control plane
9
+ * by reading a column the control plane does not expose.
10
+ *
11
+ * A snapshot brackets its journal read with run-summary reads and retries
12
+ * when the summary changes. Rows and cursor share the resulting event buffer,
13
+ * so a completion cannot be acknowledged with the preceding run status.
14
+ *
15
+ * A subscription is a snapshot followed by deltas. A delta recomputes the
16
+ * selector's rows from the accumulated events rather than patching them: a
17
+ * projection is a reproducible fold (`@smthrs/journal` `Projection`), and
18
+ * recomputation is the only delta that cannot disagree with a fresh snapshot.
19
+ * `run-events` appends immutable rows. `transcript` normally appends too, but
20
+ * sends an existing snapshot reset when a committed native call fact upgrades
21
+ * previously delivered telemetry. Events accumulate in the stream, so each delta
22
+ * folds without re-reading history. Resuming a historical run summary first
23
+ * rebuilds its compacted health prefix once through durable follow replay.
24
+ *
25
+ * A workspace subscription follows every journal partition without a cursor.
26
+ * The follower stores each retained run's last position and drops replay
27
+ * through that cutoff. Sources and exclusion verdicts share the snapshot's
28
+ * run ceiling; eviction discards a verdict together with its cursor state.
29
+ * Events are gathered for a short window and folded into one frame, and each
30
+ * run's rows are cached so a burst refolds only the runs whose journals grew.
31
+ *
32
+ * @since 1.0.0
33
+ */
34
+ import { Control } from "@smthrs/control/Control";
35
+ import * as ControlSchema from "@smthrs/control/ControlSchema";
36
+ import * as Health from "@smthrs/control/Health";
37
+ import { Context, Effect, Layer, Schema, Stream } from "effect";
38
+ import * as Diagnosis from "./Diagnosis.js";
39
+ import { GatewayError, settingRefusal } from "./GatewayError.js";
40
+ import * as GatewayProjection from "./GatewayProjection.js";
41
+ import * as GatewaySchema from "./GatewaySchema.js";
42
+ import { callEventKey, nativeCallEvent, nativeStepEvent } from "./internal/callEvents.js";
43
+ import { retainedDigestBytes } from "./internal/digestMemory.js";
44
+ import * as NativeResolution from "./internal/nativeResolution.js";
45
+ /**
46
+ * How often an idle subscription emits a keepalive frame.
47
+ *
48
+ * The Smithers Cloud relay cuts an idle tunnel at 600 s (the deployed relay behavior), so a
49
+ * quiet run must produce a frame well inside that window or the connection is
50
+ * dropped and every follower reconnects. Thirty seconds leaves twenty
51
+ * heartbeats of margin.
52
+ *
53
+ * @since 1.0.0
54
+ * @category models
55
+ */
56
+ export const heartbeatIntervalMillis = 30_000;
57
+ /**
58
+ * The most runs one workspace projection folds, newest first.
59
+ *
60
+ * A workspace projection reads one full journal per run, so the number of
61
+ * runs it folds is bounded on purpose. This is the gateway's own ceiling, not
62
+ * the control plane's default page size. It is 500 because that equals
63
+ * `ControlSchema.maxPageSize`, letting the control plane satisfy the whole
64
+ * gateway allowance in one page when it can.
65
+ *
66
+ * @since 1.0.0
67
+ * @category models
68
+ */
69
+ export const maxWorkspaceRuns = 500;
70
+ /** Whether `run` was created after `other`, ties broken by run id. */
71
+ const createdAfter = (run, other) => run.createdAt !== other.createdAt ? run.createdAt > other.createdAt : run.runId > other.runId;
72
+ /**
73
+ * The most journal events one run projection retains in its window.
74
+ *
75
+ * This is a retention ceiling, not a refusal. A run that journals more keeps
76
+ * its most recent {@link maxEventsPerRun} events and folds everything older
77
+ * into the carried digest that {@link maxProjectionBytes} describes.
78
+ *
79
+ * @since 1.0.0
80
+ * @category models
81
+ */
82
+ export const maxEventsPerRun = 10_000;
83
+ /**
84
+ * The most journal events one run projection reads before it stops reading.
85
+ *
86
+ * The window ceiling bounds memory; this bounds work. A projection folds every
87
+ * event it reads, so a run with a million of them would cost a million decodes
88
+ * on every snapshot however few it kept. Ten times the window is the honest
89
+ * compromise: every real run is far inside it, and a runaway one still answers
90
+ * rather than running the reader out of time.
91
+ *
92
+ * @since 1.0.0
93
+ * @category models
94
+ */
95
+ export const maxEventsScanned = 100_000;
96
+ /**
97
+ * The most events one `run-events` snapshot page carries.
98
+ *
99
+ * `run-events` is the one selector whose rows are the journal itself, so it is
100
+ * the one selector a client must page rather than fold. A snapshot answers at
101
+ * most this many events and a cursor to ask for the next page from, so reading
102
+ * a long run costs one bounded response per page instead of one response the
103
+ * size of the whole run.
104
+ *
105
+ * @since 1.0.0
106
+ * @category models
107
+ */
108
+ export const maxEventsPerPage = 1_000;
109
+ /**
110
+ * The most terminal runs of one flow a duration fold measures.
111
+ *
112
+ * A prediction wants recent history, not all of it: an old run measured a
113
+ * different declaration on a different machine. The newest finished runs are
114
+ * the ones whose durations still describe what the flow does now, and the
115
+ * bound is what keeps a snapshot from reading every journal a flow ever wrote.
116
+ *
117
+ * @since 1.0.0
118
+ * @category models
119
+ */
120
+ export const maxDurationRuns = 20;
121
+ /**
122
+ * The largest encoded event window or projected row set one run admits.
123
+ *
124
+ * The row budget is a refusal: rows go on the wire, and a frame larger than
125
+ * this is one no client asked for. The window budget is retention: events
126
+ * older than the newest {@link maxProjectionBytes} are folded into a carried
127
+ * digest instead of being held. Its compact identity contributions share this
128
+ * budget; if they cannot fit, the read refuses instead of reporting inexact
129
+ * counters or retaining an unbounded identity set.
130
+ *
131
+ * @since 1.0.0
132
+ * @category models
133
+ */
134
+ export const maxProjectionBytes = 4 * 1024 * 1024;
135
+ /**
136
+ * The largest encoded form one retained journal event may take.
137
+ *
138
+ * A model request body, a response transcript, and a tool's captured output
139
+ * are all journaled inline, and any one of them can be megabytes. Nothing a
140
+ * projection reads out of a payload is long: a seat name, a flow name, an
141
+ * outcome, a token count, a first line. A retained event is therefore clipped
142
+ * to this budget rather than held whole, which is what keeps one wiki refresh
143
+ * that retried a model call nine times from costing a run card its whole
144
+ * history. `run-events` pages preserve native engine evidence because clients
145
+ * decode its complete contracts to offer actions. Other large bodies remain
146
+ * clipped; their full content belongs in run artifacts.
147
+ *
148
+ * @since 1.0.0
149
+ * @category models
150
+ */
151
+ export const maxEventBytes = 16 * 1024;
152
+ /**
153
+ * The longest string a clipped payload keeps.
154
+ *
155
+ * Applied only to an event already over {@link maxEventBytes}, so an ordinary
156
+ * event reaches a projection byte for byte.
157
+ */
158
+ const clippedTextPoints = 2_048;
159
+ /**
160
+ * How long a workspace follower gathers events before folding one delta.
161
+ *
162
+ * A workspace delta replaces every row, so its cost is the whole workspace,
163
+ * not the one event that arrived. Gathering a burst into one frame bounds the
164
+ * frame rate at twenty per second however many runs journal at once, and
165
+ * fifty milliseconds is below what a dashboard reader can notice.
166
+ */
167
+ const workspaceDeltaCoalesceMillis = 50;
168
+ /** The most events one workspace batch gathers before folding inside the window. */
169
+ const workspaceDeltaBatchSize = 1_024;
170
+ /**
171
+ * The gateway read path.
172
+ *
173
+ * @since 1.0.0
174
+ * @category services
175
+ */
176
+ export class Projections extends Context.Service()("@smthrs/gateway/Projections") {
177
+ }
178
+ /**
179
+ * The part of a control-plane failure a client is allowed to see: the failure's
180
+ * tag and its stable code, never its message, its nested cause, or the SQL and
181
+ * file paths a `PersistenceError` carries.
182
+ *
183
+ * Server logs carry only an operation identifier and an allowlisted summary.
184
+ * `GatewayError` is the RPC error schema, so anything left on it is serialized
185
+ * to every bearer holder and
186
+ * forwarded to the browser by the product relay, and a cause JSON cannot encode
187
+ * would make the error frame itself fail to encode.
188
+ */
189
+ const summarize = (cause) => {
190
+ const record = typeof cause === "object" && cause !== null ? cause : undefined;
191
+ if (record === undefined || typeof record._tag !== "string")
192
+ return undefined;
193
+ return typeof record.code === "string" ? { _tag: record._tag, code: record.code } : { _tag: record._tag };
194
+ };
195
+ /** Log only known control-plane identifiers, never arbitrary backend text. */
196
+ const logReadFailure = (operation, cause) => {
197
+ const summary = summarize(cause);
198
+ const known = summary?._tag === "/control/PersistenceError" && summary.code === "persistence_failed" ||
199
+ summary?._tag === "/control/Unavailable" && summary.code === "unavailable";
200
+ return Effect.logWarning({ operation, ...(known ? summary : {}) });
201
+ };
202
+ /** The read failed, with only a public error summary. */
203
+ const unavailable = (message, cause) => {
204
+ const summary = summarize(cause);
205
+ return new GatewayError({
206
+ code: "run_unavailable",
207
+ message,
208
+ ...(summary === undefined ? {} : { cause: summary })
209
+ });
210
+ };
211
+ /** The request named something the control plane cannot serve. */
212
+ const malformed = (message) => new GatewayError({ code: "malformed_request", message });
213
+ /** A projection exceeded the bounded read or wire budget. */
214
+ const resourceLimit = (message) => new GatewayError({ code: "resource_limit", message });
215
+ /** The run a selector is scoped to, or undefined for a workspace selector. */
216
+ const scopeOf = (selector) => "runId" in selector ? selector.runId : undefined;
217
+ const emptyEventBuffer = (compactHealth = false) => ({
218
+ compactHealth,
219
+ seen: false,
220
+ events: [],
221
+ encodedBytes: 2,
222
+ lastPosition: { value: 0, offset: 0 },
223
+ carry: undefined,
224
+ dropped: 0
225
+ });
226
+ const textEncoder = new TextEncoder();
227
+ const encodedSize = (value) => textEncoder.encode(JSON.stringify(value)).byteLength;
228
+ /**
229
+ * One payload with every long string cut to {@link clippedTextPoints}.
230
+ *
231
+ * Structure is preserved so a payload stays decodable: a clipped string is
232
+ * still a string, an object keeps its keys, and an array keeps its length.
233
+ * Only the text shrinks, and `Diagnosis.clip` marks the cut.
234
+ */
235
+ const clipDeep = (value) => {
236
+ if (typeof value === "string")
237
+ return Diagnosis.clip(value, clippedTextPoints);
238
+ if (Array.isArray(value))
239
+ return value.map(clipDeep);
240
+ if (typeof value === "object" && value !== null) {
241
+ return Object.fromEntries(Object.entries(value).map(([key, member]) => [key, clipDeep(member)]));
242
+ }
243
+ return value;
244
+ };
245
+ const isJsonObject = (json) => typeof json === "object" && json !== null && !Array.isArray(json);
246
+ /**
247
+ * The retained form of one event: itself when it is small, a clipped copy when
248
+ * it is not.
249
+ *
250
+ * A payload that is still over the budget after clipping is replaced outright.
251
+ * That is a payload whose size is in its shape rather than in its text — tens
252
+ * of thousands of keys — and no projection reads such a shape. A relevance
253
+ * reading is the exception: a large catalog makes its flow and skill verdicts
254
+ * that wide, and the run card reads its memory verdicts
255
+ * (`RunTrace.runMemoryOf`), so those are retained beside the marker.
256
+ */
257
+ const retainedEvent = (event) => {
258
+ if (encodedSize(event) <= maxEventBytes)
259
+ return event;
260
+ const clipped = { ...event, payload: clipDeep(event.payload) };
261
+ if (encodedSize(clipped) <= maxEventBytes)
262
+ return clipped;
263
+ const marker = { truncated: true, encodedBytes: encodedSize(event.payload) };
264
+ const payload = event.payload;
265
+ if (event.kind === "control.agent.relevance-settled" && isJsonObject(payload)) {
266
+ const memory = (verdicts) => Array.isArray(verdicts)
267
+ ? verdicts.filter((verdict) => typeof verdict === "object" && verdict !== null && verdict.kind === "memory")
268
+ : [];
269
+ const compact = {
270
+ ...event,
271
+ payload: {
272
+ ...marker,
273
+ kept: clipDeep(memory(payload.kept)),
274
+ withheld: clipDeep(memory(payload.withheld))
275
+ }
276
+ };
277
+ if (encodedSize(compact) <= maxEventBytes)
278
+ return compact;
279
+ }
280
+ return { ...event, payload: marker };
281
+ };
282
+ const decodeEvent = Schema.decodeUnknownSync(ControlSchema.ControlEvent);
283
+ const decodeFrame = Schema.decodeUnknownSync(GatewaySchema.GatewayFrame);
284
+ const decodeSnapshot = Schema.decodeUnknownSync(GatewaySchema.ProjectionSnapshot);
285
+ const decodedEvent = (candidate, message) => Effect.suspend(() => {
286
+ try {
287
+ const event = decodeEvent(candidate);
288
+ // Graph topology is a bounded contract. Refuse an oversized legacy page
289
+ // intact; clipping a node id or dependency would silently change the DAG.
290
+ const payload = event.payload;
291
+ if (event.kind === "control.engine.event" && typeof payload === "object" && payload !== null &&
292
+ "eventType" in payload && (payload.eventType === "flows.engine.plan-recorded" ||
293
+ payload.eventType === "flows.engine.subgraph-appended") &&
294
+ encodedSize(event) > maxEventBytes) {
295
+ return Effect.fail(resourceLimit(`One graph event exceeds ${maxEventBytes} encoded bytes`));
296
+ }
297
+ return Number.isSafeInteger(event.sequence) && event.sequence >= 0
298
+ ? Effect.succeed(event)
299
+ : Effect.fail(unavailable(message, undefined));
300
+ }
301
+ catch (cause) {
302
+ return Effect.fail(unavailable(message, cause));
303
+ }
304
+ });
305
+ const appendEvent = (state, candidate, message, run) => Effect.flatMap(decodedEvent(candidate, message), (decoded) => {
306
+ // Ordering is checked before admission by eventsOf, runDeltaFrames and
307
+ // workspaceDeltaFrames. bufferOf only replays an ordered cursor prefix.
308
+ // A payload larger than one projection reads is clipped before it is
309
+ // measured, so a journaled model body costs the window its own size and
310
+ // not the body's.
311
+ // A bound root's typed answer must remain exact across event clipping.
312
+ // Retain it in the same bounded digest as evicted root decisions; the
313
+ // event window carries only a marker, never a partial typed answer.
314
+ const resolution = encodedSize(decoded) > maxEventBytes ? NativeResolution.fromEvent(decoded) : undefined;
315
+ let carry = state.carry;
316
+ if (resolution?.result !== undefined) {
317
+ carry = Diagnosis.combine(carry ?? Diagnosis.emptyDigest(), {
318
+ ...Diagnosis.emptyDigest(),
319
+ nativeResolution: resolution
320
+ });
321
+ }
322
+ const event = resolution?.result === undefined ? retainedEvent(decoded) : {
323
+ ...decoded,
324
+ payload: { truncated: true, encodedBytes: encodedSize(decoded.payload) }
325
+ };
326
+ // `decodedEvent` has already rebuilt payload through `Schema.Json`, so it
327
+ // contains neither accessors nor `toJSON` hooks and JSON encoding cannot
328
+ // execute caller code here.
329
+ const bytes = encodedSize(event);
330
+ let removedBytes = 0;
331
+ let keep = true;
332
+ if (state.compactHealth && event.kind === Health.statusObservedEventType) {
333
+ const decoded = Schema.decodeUnknownOption(Health.HealthObservation)(event.payload);
334
+ // Match the subject before incarnation precedence or eviction, as the
335
+ // authoritative health fold does. Foreign evidence must not displace
336
+ // this run's reading, but its raw journal position still advances below.
337
+ if (decoded._tag === "None" || decoded.value.outcome === "discarded" ||
338
+ decoded.value.subjectId !== `run:${run.runId}`)
339
+ keep = false;
340
+ else {
341
+ const same = state.events.findIndex((old) => old.kind === Health.statusObservedEventType &&
342
+ typeof old.payload === "object" && old.payload !== null && !Array.isArray(old.payload) &&
343
+ "incarnation" in old.payload &&
344
+ old.payload.incarnation === decoded.value.incarnation);
345
+ if (same >= 0) {
346
+ const previous = state.events[same];
347
+ const prior = previous.payload;
348
+ if (prior.evidenceSeq > decoded.value.evidenceSeq)
349
+ keep = false;
350
+ else
351
+ removedBytes += textEncoder.encode(JSON.stringify(state.events.splice(same, 1)[0])).byteLength + 1;
352
+ }
353
+ // Pin the authoritative incarnation's strongest reading. Late former
354
+ // owners must not evict it and let weaker evidence replace it later.
355
+ // The other slots retain recent opaque incarnations within the same cap.
356
+ const healthEntries = state.events.filter((old) => old.kind === Health.statusObservedEventType);
357
+ if (keep && healthEntries.length >= 16) {
358
+ const incarnation = Health.runIncarnation(run);
359
+ // There is at most one entry per incarnation, so an unprotected entry exists.
360
+ const oldest = state.events.indexOf(healthEntries.find((old) => old.payload.incarnation !== incarnation));
361
+ removedBytes += textEncoder.encode(JSON.stringify(state.events.splice(oldest, 1)[0])).byteLength + 1;
362
+ }
363
+ }
364
+ }
365
+ else if (state.compactHealth && event.kind === "control.monitor.beat")
366
+ keep = false;
367
+ let encodedBytes = Math.max(2, state.encodedBytes - removedBytes) +
368
+ retainedDigestBytes(carry) - retainedDigestBytes(state.carry) +
369
+ (keep ? bytes + (state.events.length === 0 ? 0 : 1) : 0);
370
+ // Unreachable today: every term is a byte length, `state.encodedBytes` is
371
+ // bounded by `maxProjectionBytes` by the loop below, and one event is
372
+ // bounded by the string the control plane sent, so the sum is always a
373
+ // safe integer. The refusal stays because a later term need not be, and a
374
+ // window that cannot count its own bytes must refuse rather than report.
375
+ /* v8 ignore next 3 -- unreachable while every term above is a byte length. */
376
+ if (!Number.isSafeInteger(encodedBytes)) {
377
+ return Effect.fail(unavailable(message, undefined));
378
+ }
379
+ const offset = state.seen && event.sequence === state.lastPosition.value
380
+ ? state.lastPosition.offset + 1
381
+ : 0;
382
+ if (keep)
383
+ state.events.push(event);
384
+ // Event bodies may leave the window while compact identity contributions
385
+ // keep the diagnosis exact. Both share the byte budget; if even the scalar
386
+ // state cannot fit, refuse instead of silently forgetting identities.
387
+ let dropped = state.dropped;
388
+ while (state.events.length > maxEventsPerRun || encodedBytes > maxProjectionBytes) {
389
+ const evicted = state.events.shift();
390
+ if (evicted === undefined) {
391
+ return Effect.fail(resourceLimit(`Projection identity state exceeds ${maxProjectionBytes} encoded bytes`));
392
+ }
393
+ encodedBytes = Math.max(2, encodedBytes - encodedSize(evicted) - (state.events.length === 0 ? 0 : 1));
394
+ const previousCarryBytes = retainedDigestBytes(carry);
395
+ carry = Diagnosis.combine(carry ?? Diagnosis.emptyDigest(), Diagnosis.digest([evicted], run.runId));
396
+ encodedBytes += retainedDigestBytes(carry) - previousCarryBytes;
397
+ dropped += 1;
398
+ }
399
+ return Effect.succeed({
400
+ compactHealth: state.compactHealth,
401
+ seen: true,
402
+ events: state.events,
403
+ encodedBytes,
404
+ lastPosition: { value: event.sequence, offset },
405
+ carry,
406
+ dropped
407
+ });
408
+ });
409
+ const bufferOf = (events, message, compactHealth, run) => events.reduce((state, event) => Effect.flatMap(state, (buffer) => appendEvent(buffer, event, message, run)), Effect.succeed(emptyEventBuffer(compactHealth)));
410
+ const cursorOf = (selector, position) => ({
411
+ selector,
412
+ projection: selector._tag,
413
+ runId: scopeOf(selector) ?? null,
414
+ value: position.value,
415
+ offset: position.offset
416
+ });
417
+ const comparePosition = (left, right) => left.value === right.value ? left.offset - right.offset : left.value - right.value;
418
+ const positionedEvents = (events) => {
419
+ let sequence = -1;
420
+ let offset = 0;
421
+ return events.map((event) => {
422
+ offset = event.sequence === sequence ? offset + 1 : 0;
423
+ sequence = event.sequence;
424
+ return { event, position: { value: sequence, offset } };
425
+ });
426
+ };
427
+ const sameSelector = (left, right) => {
428
+ if (left._tag !== right._tag)
429
+ return false;
430
+ switch (left._tag) {
431
+ case "workspace-runs":
432
+ return left.flowId === right.flowId;
433
+ case "approvals":
434
+ return left.runId === right.runId;
435
+ case "node-output": {
436
+ const candidate = right;
437
+ return left.runId === candidate.runId && left.nodeId === candidate.nodeId;
438
+ }
439
+ case "flow-durations":
440
+ return left.flowId === right.flowId;
441
+ default:
442
+ return left.runId === right.runId;
443
+ }
444
+ };
445
+ /**
446
+ * The rows one run contributes to a selector.
447
+ *
448
+ * Pure and total over facts already read: it never reads the control plane, so
449
+ * a delta folded from accumulated events and a fresh snapshot compute the same
450
+ * rows from the same events. `workspace-runs` appears here because a workspace
451
+ * listing is one summary row per run, folded by this same function.
452
+ */
453
+ const rowsOfRun = (selector, source, now) => {
454
+ switch (selector._tag) {
455
+ case "workspace-runs":
456
+ case "run-summary":
457
+ return [GatewayProjection.runSummary(source.run, source.events, now, source.carry)];
458
+ case "run-tree":
459
+ return GatewayProjection.runTree(source.run, source.events);
460
+ case "run-events":
461
+ return source.events;
462
+ case "transcript":
463
+ return GatewayProjection.transcript(source.events);
464
+ case "approvals":
465
+ return GatewayProjection.approvals(source.events, source.run);
466
+ case "node-output":
467
+ return GatewayProjection.nodeOutput(source.events).filter((row) => row.nodeId === selector.nodeId);
468
+ case "flow-durations":
469
+ // One run contributes the executions it measured, never a percentile:
470
+ // a percentile over one run is that run, and the rows this selector
471
+ // serves are folded across every run the flow has finished. The carry
472
+ // travels with the window because the record that names this run's
473
+ // native root is the first one a bounded window drops.
474
+ return GatewayProjection.nodeDurations(source.events, source.carry);
475
+ }
476
+ };
477
+ /** Turns opened so far, for numbering appended transcript rows without a refold. */
478
+ const turnsOpened = (events) => GatewayProjection.transcript(events).at(-1)?.turn ?? 0;
479
+ /**
480
+ * The transcript rows one appended event contributes.
481
+ *
482
+ * A transcript row is immutable once folded and only the turn counter carries
483
+ * across events, so folding the one event and offsetting its turn produces
484
+ * exactly the rows a full refold would append.
485
+ */
486
+ const transcriptAppend = (event, turnsBefore) => GatewayProjection.transcript([event]).map((row) => ({ ...row, turn: row.turn + turnsBefore }));
487
+ /** Snapshot and follow admit exactly the same runs to the workspace inbox. */
488
+ const eligibleForWorkspace = (selector, source) => selector._tag !== "approvals" ||
489
+ source.run.status === "waiting-approval" &&
490
+ GatewayProjection.approvals(source.events, source.run).some((row) => row.status === "pending");
491
+ /**
492
+ * Which open human wait a pending approval row is about, if it is about one.
493
+ *
494
+ * A nested wait is rolled onto the root AND onto every execution between, so
495
+ * one question reaches a workspace listing once per ancestor. The inbox is a
496
+ * list of questions, not of the runs that contain them, so it keeps one row
497
+ * for each wait point: the one its outermost owner projects. See
498
+ * {@link ownerRank}.
499
+ */
500
+ const waitIdentity = (row) => row.waitRunId === undefined ? undefined : `${row.waitRunId}:${row.requestId}`;
501
+ /**
502
+ * How far out each listed run sits, as a comparable key: fewer listed
503
+ * ancestors first, then the earlier created, then the run id.
504
+ *
505
+ * Ancestry is the explicit `parentRunId` chain among the runs listed, because
506
+ * the listing order is the control plane's to choose (the SQL listing is
507
+ * newest first) and two runs of one tree can share a creation millisecond.
508
+ * Creation order breaks the ties ancestry leaves: an ancestor exists before
509
+ * the execution it spawned.
510
+ */
511
+ const ownerRank = (runs) => {
512
+ const listed = new Map(runs.map((run) => [run.runId, run]));
513
+ const depths = new Map();
514
+ const depthOf = (run) => {
515
+ const known = depths.get(run.runId);
516
+ if (known !== undefined)
517
+ return known;
518
+ // Marked before the walk so a cyclic parent chain ends instead of looping.
519
+ depths.set(run.runId, 0);
520
+ const parent = run.parentRunId === undefined ? undefined : listed.get(run.parentRunId);
521
+ const depth = parent === undefined ? 0 : depthOf(parent) + 1;
522
+ depths.set(run.runId, depth);
523
+ return depth;
524
+ };
525
+ return (left, right) => depthOf(left) - depthOf(right) || (createdAfter(left, right) ? 1 : -1);
526
+ };
527
+ /**
528
+ * The workspace approvals inbox over each run's own pending rows.
529
+ *
530
+ * Every read of the inbox, snapshot and delta alike, folds through here, so a
531
+ * nested question is one row owned by its outermost run whichever read
532
+ * produced it. Rows keep the order of the runs given.
533
+ */
534
+ const approvalInbox = (perRun) => {
535
+ const owners = new Map();
536
+ const rank = ownerRank(perRun.map(({ run }) => run));
537
+ for (const { run, rows } of [...perRun].sort((left, right) => rank(left.run, right.run))) {
538
+ for (const row of rows) {
539
+ const identity = waitIdentity(row);
540
+ if (identity !== undefined && !owners.has(identity))
541
+ owners.set(identity, run.runId);
542
+ }
543
+ }
544
+ const inbox = [];
545
+ for (const { run, rows } of perRun) {
546
+ for (const row of rows) {
547
+ const identity = waitIdentity(row);
548
+ if (identity !== undefined) {
549
+ if (owners.get(identity) !== run.runId)
550
+ continue;
551
+ // Taken once: a question listed twice by its owner is still one row.
552
+ owners.delete(identity);
553
+ }
554
+ inbox.push(row);
555
+ }
556
+ }
557
+ return inbox;
558
+ };
559
+ /** One run's rows that are still owed an answer: its share of the inbox. */
560
+ const pendingApprovals = (source) => GatewayProjection.approvals(source.events, source.run).filter((row) => row.status === "pending");
561
+ /**
562
+ * The rows a workspace selector projects across every run it read.
563
+ *
564
+ * Most of them are one run's rows, concatenated. Two are not. The approvals
565
+ * inbox wants only what a human still owes an answer to, once per question
566
+ * (see {@link approvalInbox}), where a run card wants that run's decided gates
567
+ * too. And `flow-durations` is a cross-run fold: every run contributes the
568
+ * executions it measured and the percentiles are ranked over all of them at
569
+ * once, so a run contributes a sample rather than a row.
570
+ */
571
+ const rowsOfWorkspace = (selector, runs, now) => {
572
+ if (selector._tag === "flow-durations") {
573
+ return GatewayProjection.flowDurations(selector.flowId, runs.flatMap((source) => rowsOfRun(selector, source, now)));
574
+ }
575
+ if (selector._tag !== "approvals")
576
+ return runs.flatMap((source) => rowsOfRun(selector, source, now));
577
+ return approvalInbox(runs.map((source) => ({ run: source.run, rows: pendingApprovals(source) })));
578
+ };
579
+ /** The rows a selector projects from the facts one read produced. */
580
+ const rowsOf = (selector, source, now) => source._tag === "run" ? rowsOfRun(selector, source.run, now) : rowsOfWorkspace(selector, source.runs, now);
581
+ const boundedRows = (selector, candidate) => Effect.suspend(() => {
582
+ try {
583
+ const schema = Schema.Array(GatewaySchema.rowSchemaFor(selector));
584
+ const rows = Schema.decodeUnknownSync(schema)(candidate);
585
+ const encoded = Schema.encodeUnknownSync(schema)(rows);
586
+ const bytes = textEncoder.encode(JSON.stringify(encoded)).byteLength;
587
+ return bytes <= maxProjectionBytes
588
+ ? Effect.succeed(rows)
589
+ : Effect.fail(resourceLimit(`Projection rows exceed ${maxProjectionBytes} encoded bytes`));
590
+ }
591
+ catch (cause) {
592
+ return Effect.fail(unavailable("Projection produced invalid rows", cause));
593
+ }
594
+ });
595
+ const frameOf = (candidate) => Effect.suspend(() => {
596
+ try {
597
+ return Effect.succeed(decodeFrame(candidate));
598
+ /* v8 ignore next -- rows, selectors, cursors, and tags were admitted before this internal assembly step. */
599
+ }
600
+ catch (cause) {
601
+ /* v8 ignore next -- the assembled frame consists only of already-admitted fields. */
602
+ return Effect.fail(unavailable("Projection produced an invalid frame", cause));
603
+ }
604
+ });
605
+ const snapshotOf = (candidate) => Effect.suspend(() => {
606
+ try {
607
+ return Effect.succeed(decodeSnapshot(candidate));
608
+ /* v8 ignore next -- boundedRows and cursorOf admit every field before this internal assembly step. */
609
+ }
610
+ catch (cause) {
611
+ /* v8 ignore next -- the assembled snapshot consists only of already-admitted fields. */
612
+ return Effect.fail(unavailable("Projection produced an invalid snapshot", cause));
613
+ }
614
+ });
615
+ /**
616
+ * The cursor the read reached.
617
+ *
618
+ * A run's cursor is the last sequence its journal committed, taken from the
619
+ * same array the rows were folded from. A workspace cursor is `0`: control
620
+ * journal sequences belong to per-run partitions (`@smthrs/control`
621
+ * `ControlLive.streamForRun` and `snapshotHighWater` are keyed by `runId`), so
622
+ * there is no workspace-wide sequence to advertise and no workspace resume.
623
+ */
624
+ const cursorPositionOf = (source) => source._tag === "run" ? source.run.lastPosition : { value: 0, offset: 0 };
625
+ /** Implements a read path after its construction settings have been admitted. */
626
+ const makeService = (control, heartbeatMillis, now) => {
627
+ // A bounded, disposable journal read cache. The journal remains authoritative:
628
+ // every read asks it for the suffix, including when the summary is unchanged.
629
+ // Cache eviction or host restart simply rebuilds the same fold from sequence 0.
630
+ const journalBuffers = new Map();
631
+ const eventsOf = (run, compactHealth) => Effect.suspend(() => {
632
+ const runId = run.runId;
633
+ const key = `${runId}:${compactHealth}`;
634
+ const cached = journalBuffers.get(key);
635
+ const seed = cached === undefined ? emptyEventBuffer(compactHealth) : { ...cached, events: [...cached.events] };
636
+ const message = `Reading the events of ${runId} failed`;
637
+ let position;
638
+ return Stream.runFoldEffect(control.watch({
639
+ runId,
640
+ follow: false,
641
+ ...(cached === undefined || !cached.seen || cached.lastPosition.value === 0
642
+ ? {} :
643
+ { afterSequence: cached.lastPosition.value - 1 })
644
+ }).pipe(Stream.tapError((cause) => logReadFailure("read-events", cause)), Stream.mapError((cause) => unavailable(message, cause)), Stream.take(maxEventsScanned)), () => seed, (buffer, candidate) => Effect.flatMap(decodedEvent(candidate, message), (event) => {
645
+ if (position !== undefined && event.sequence < position.value) {
646
+ return Effect.fail(unavailable(message, undefined));
647
+ }
648
+ position = { value: event.sequence, offset: position?.value === event.sequence ? position.offset + 1 : 0 };
649
+ if (cached !== undefined && cached.seen && comparePosition(position, cached.lastPosition) <= 0) {
650
+ return Effect.succeed(buffer);
651
+ }
652
+ return appendEvent(buffer, event, message, run);
653
+ })).pipe(Effect.tap((buffer) => Effect.sync(() => {
654
+ const current = journalBuffers.get(key);
655
+ if (current === undefined || comparePosition(buffer.lastPosition, current.lastPosition) >= 0) {
656
+ journalBuffers.delete(key);
657
+ journalBuffers.set(key, { ...buffer, events: [...buffer.events] });
658
+ if (journalBuffers.size > 32)
659
+ journalBuffers.delete(journalBuffers.keys().next().value);
660
+ }
661
+ })));
662
+ });
663
+ /**
664
+ * One bounded page of a run's journal, starting after `after`.
665
+ *
666
+ * This is the `run-events` read, and it is deliberately not the fold every
667
+ * other selector uses. The other selectors answer rows computed from a
668
+ * journal; `run-events` answers the journal, so its response grows with the
669
+ * run unless the read itself is bounded. It reads from the control plane's
670
+ * own cursor, keeps at most {@link maxEventsPerPage} events and
671
+ * {@link maxProjectionBytes}, and reports the position it reached so the
672
+ * caller asks for the next page from there.
673
+ *
674
+ * `afterSequence` replays the cursor's whole journal entry rather than
675
+ * starting inside it, because an entry expands into several events and their
676
+ * offsets are only countable from the entry's start. The replayed prefix is
677
+ * dropped here.
678
+ */
679
+ const runEventsPage = (run, after) => {
680
+ const runId = run.runId;
681
+ const message = `Reading the events of ${runId} failed`;
682
+ const empty = { events: [], bytes: 2, seen: undefined, last: undefined, full: false };
683
+ // Set when the page is full, so the next pull ends the stream instead of
684
+ // decoding the rest of a long journal into a page that cannot grow.
685
+ let stopped = false;
686
+ return Stream.runFoldEffect(control.watch({
687
+ runId,
688
+ ...(after === undefined || after.value === 0 ? {} : { afterSequence: after.value - 1 }),
689
+ follow: false
690
+ }).pipe(Stream.tapError((cause) => logReadFailure("read-events", cause)), Stream.mapError((cause) => unavailable(message, cause)), Stream.take(maxEventsScanned), Stream.takeWhile(() => !stopped)), () => empty, (page, candidate) => page.full ? Effect.succeed(page) : Effect.flatMap(decodedEvent(candidate, `${message}: the control plane returned an invalid event`), (decoded) => {
691
+ // A journal that moves backward is a broken control plane, not a
692
+ // short page, and the fold refuses it exactly as the window does.
693
+ if (page.seen !== undefined && decoded.sequence < page.seen.value) {
694
+ return Effect.fail(unavailable(message, undefined));
695
+ }
696
+ const position = {
697
+ value: decoded.sequence,
698
+ offset: page.seen?.value === decoded.sequence ? page.seen.offset + 1 : 0
699
+ };
700
+ const seen = position;
701
+ if (after !== undefined && comparePosition(position, after) <= 0) {
702
+ return Effect.succeed({ ...page, seen });
703
+ }
704
+ // Native results are contracts, not display text. Clipping changes
705
+ // their meaning and can hide validated actions such as Vibe.
706
+ // Match live deltas, which already carry the original event.
707
+ const event = decoded.kind === "control.engine.event" ? decoded : retainedEvent(decoded);
708
+ if (encodedSize(event) + 2 > maxProjectionBytes) {
709
+ return Effect.fail(resourceLimit(`One journal event exceeds ${maxProjectionBytes} encoded bytes`));
710
+ }
711
+ const bytes = page.bytes + encodedSize(event) + (page.events.length === 0 ? 0 : 1);
712
+ if (page.events.length > 0 && bytes > maxProjectionBytes) {
713
+ stopped = true;
714
+ return Effect.succeed({ ...page, seen, full: true });
715
+ }
716
+ page.events.push(event);
717
+ stopped = page.events.length >= maxEventsPerPage;
718
+ return Effect.succeed({ events: page.events, bytes, seen, last: position, full: stopped });
719
+ })).pipe(Effect.map((page) => ({
720
+ events: page.events,
721
+ lastPosition: page.last ?? after ?? { value: 0, offset: 0 }
722
+ })));
723
+ };
724
+ const runsMatching = (filters, newest) => {
725
+ const singleRun = filters.runId !== undefined;
726
+ // Only runOf omits the bound: it reads one identified run.
727
+ const ceiling = newest ?? 1;
728
+ const page = (accumulated, seen, cursor) => {
729
+ const remaining = ceiling - accumulated.length;
730
+ return control.list({
731
+ _tag: "runs",
732
+ filters,
733
+ limit: remaining,
734
+ ...(newest === undefined ? {} : { order: "newest" }),
735
+ ...(cursor === undefined ? {} : { cursor })
736
+ }).pipe(Effect.tapError((cause) => logReadFailure("list-runs", cause)), Effect.mapError((cause) => unavailable("Listing runs failed", cause)), Effect.flatMap((response) => {
737
+ if (response._tag !== "runs")
738
+ return Effect.succeed(accumulated);
739
+ const ids = new Set(accumulated.map((run) => run.runId));
740
+ const added = response.items.filter((run) => {
741
+ if (ids.has(run.runId))
742
+ return false;
743
+ ids.add(run.runId);
744
+ return true;
745
+ }).slice(0, remaining);
746
+ const runs = [...accumulated, ...added];
747
+ if (singleRun || added.length === 0 || response.nextCursor === undefined ||
748
+ runs.length >= ceiling || seen.has(response.nextCursor))
749
+ return Effect.succeed(runs);
750
+ seen.add(response.nextCursor);
751
+ // Each continued page adds at least one run, so the run ceiling
752
+ // also caps the number of pages at maxWorkspaceRuns.
753
+ return page(runs, seen, response.nextCursor);
754
+ }));
755
+ };
756
+ return Effect.suspend(() => page([], new Set()));
757
+ };
758
+ /**
759
+ * The one run a run-scoped selector names, or a typed refusal.
760
+ *
761
+ * Every run-scoped selector goes through here, so an unknown run id is
762
+ * refused the same way for all of them. Reading a run's events alone cannot
763
+ * tell an unknown run from a run with no events: `@smthrs/control`
764
+ * `ControlLive.snapshotForRun` answers an empty stream for both.
765
+ */
766
+ const runOf = (runId) => runsMatching({ runId }).pipe(Effect.flatMap((runs) => {
767
+ const run = runs.find((candidate) => candidate.runId === runId);
768
+ return run === undefined
769
+ ? Effect.fail(new GatewayError({ code: "run_not_found", message: `No run ${runId}` }))
770
+ : Effect.succeed(run);
771
+ }));
772
+ /** Pin the journal between equal summaries; refuse a continuously moving row. */
773
+ const consistentRunSource = (before, attempts = 8, compactHealth = true) => Effect.gen(function* () {
774
+ const buffer = yield* eventsOf(before, compactHealth);
775
+ const run = yield* runOf(before.runId);
776
+ if (JSON.stringify(before) === JSON.stringify(run))
777
+ return { run, ...buffer };
778
+ if (attempts === 1)
779
+ return yield* Effect.fail(unavailable("Run changed throughout the snapshot read", undefined));
780
+ return yield* consistentRunSource(run, attempts - 1, compactHealth);
781
+ });
782
+ /** One run and its journal at a reconciled cutoff. */
783
+ const runSourceOf = (runId, compactHealth = true) => Effect.flatMap(runOf(runId), (run) => consistentRunSource(run, 8, compactHealth));
784
+ /** Newest terminal runs, filtered and limited by the control query itself. */
785
+ const durationRunsOf = (flowId) => runsMatching({ flowId, terminal: true }, maxDurationRuns);
786
+ /** Every run a workspace selector folds, and each one's journal. */
787
+ const workspaceSourceOf = (selector) => Effect.flatMap(
788
+ // The inbox asks the control plane which runs are parked rather than
789
+ // reading every run's journal to find out, and a duration fold asks it
790
+ // for one flow's runs rather than for the workspace's.
791
+ selector._tag === "flow-durations"
792
+ ? durationRunsOf(selector.flowId)
793
+ : runsMatching(selector._tag === "approvals" ?
794
+ { status: "waiting-approval" }
795
+ : selector._tag === "workspace-runs" && selector.flowId !== undefined
796
+ ? { flowId: selector.flowId }
797
+ : {}, maxWorkspaceRuns), (runs) => Effect.map(Effect.forEach(runs, (run) => consistentRunSource(run), { concurrency: 8 }), (sources) => sources.filter((source) => eligibleForWorkspace(selector, source))));
798
+ /** The reconciled source a snapshot folds. */
799
+ const sourceOf = (selector) => {
800
+ const runId = scopeOf(selector);
801
+ return runId === undefined
802
+ ? Effect.map(workspaceSourceOf(selector), (runs) => ({ _tag: "workspace", runs }))
803
+ : Effect.map(runSourceOf(runId, selector._tag !== "run-events"), (run) => ({ _tag: "run", run }));
804
+ };
805
+ const snapshot = (selector, after) => Effect.gen(function* () {
806
+ if (after !== undefined) {
807
+ if (selector._tag !== "run-events") {
808
+ return yield* malformed("Only run-events snapshots accept an after cursor");
809
+ }
810
+ const scope = resumeScope(selector, after);
811
+ if (typeof scope !== "string")
812
+ return yield* scope;
813
+ }
814
+ // `run-events` answers the journal itself, so it pages rather than folds:
815
+ // one bounded read from the caller's cursor, and the position it reached
816
+ // to ask for the next page from. Every other selector answers rows whose
817
+ // size is bounded by the selector, so it folds the reconciled source.
818
+ if (selector._tag === "run-events") {
819
+ const run = yield* runOf(selector.runId);
820
+ const page = yield* runEventsPage(run, after);
821
+ const rows = yield* boundedRows(selector, page.events);
822
+ return yield* snapshotOf({ selector, cursor: cursorOf(selector, page.lastPosition), rows });
823
+ }
824
+ const source = yield* sourceOf(selector);
825
+ const rows = yield* boundedRows(selector, rowsOf(selector, source, now()));
826
+ return yield* snapshotOf({ selector, cursor: cursorOf(selector, cursorPositionOf(source)), rows });
827
+ });
828
+ const snapshotFrames = (selector, source, rows) => {
829
+ const cursor = cursorOf(selector, cursorPositionOf(source));
830
+ return Effect.forEach([
831
+ { _tag: "snapshot-start", selector, cursor },
832
+ ...rows.map((row) => ({ _tag: "row", selector, cursor, row })),
833
+ { _tag: "snapshot-end", selector, cursor }
834
+ ], frameOf);
835
+ };
836
+ /**
837
+ * The rows one delta carries.
838
+ *
839
+ * `run-events`' rows are the ordered events themselves, so its delta is the
840
+ * one event that arrived. `transcript` rows are immutable once folded, so
841
+ * its delta is the rows that event appended, numbered after the turns the
842
+ * follower has already counted. Every other selector answers a full
843
+ * replacement folded from the accumulated events, because recomputation is
844
+ * the only delta that cannot disagree with a fresh snapshot. The caller
845
+ * refreshes authoritative rows before compaction so retention and rendering
846
+ * use the same owner even when a fenced status write journals no event.
847
+ */
848
+ const deltaRows = (selector, run, event, events, turnsBefore, duplicateCall, carry) => {
849
+ if (selector._tag === "run-events")
850
+ return Effect.succeed([event]);
851
+ if (selector._tag === "transcript") {
852
+ return Effect.succeed(duplicateCall ? [] : transcriptAppend(event, turnsBefore));
853
+ }
854
+ return Effect.succeed(rowsOfRun(selector, { run, events, carry }, now()));
855
+ };
856
+ /**
857
+ * Deltas for one run, following from `from` and folding over the events the
858
+ * caller already read plus each event that arrives.
859
+ *
860
+ * A historical run summary rebuilds its health prefix once from the follow
861
+ * replay. Later deltas only append: re-reading history for every event would
862
+ * make a subscription quadratic in run length and resend the whole log on
863
+ * each frame.
864
+ */
865
+ const runDeltaFrames = (selector, source, from) => {
866
+ const runId = source.run.runId;
867
+ const message = `Following ${runId} failed`;
868
+ // The final compacted buffer cannot seed an earlier health cursor: a
869
+ // later reading may have replaced the stronger observation held there.
870
+ // Rebuild that prefix in the same bounded fold from the follow replay.
871
+ const replayHealth = selector._tag === "run-summary" && comparePosition(from, source.lastPosition) < 0;
872
+ const seedEvents = positionedEvents(source.events)
873
+ .filter(({ position }) => comparePosition(position, from) <= 0)
874
+ .map(({ event }) => event);
875
+ const seed = replayHealth ?
876
+ Effect.succeed(emptyEventBuffer(true)) :
877
+ comparePosition(from, source.lastPosition) === 0
878
+ ? Effect.succeed({
879
+ compactHealth: source.compactHealth,
880
+ seen: source.seen,
881
+ events: source.events,
882
+ encodedBytes: source.encodedBytes,
883
+ lastPosition: source.lastPosition,
884
+ carry: source.carry,
885
+ dropped: source.dropped
886
+ })
887
+ : bufferOf(seedEvents, `${message}: the cursor seed is invalid`, source.compactHealth, source.run);
888
+ return Stream.unwrap(Effect.map(seed, (initial) => control.watch({
889
+ runId,
890
+ // Sequence zero also needs replay for empty seeds and derived offsets.
891
+ ...(from.value === 0 || replayHealth ? {} : { afterSequence: from.value - 1 }),
892
+ follow: true
893
+ }).pipe(Stream.tapError((cause) => logReadFailure("follow-run", cause)), Stream.mapError((cause) => unavailable(message, cause)), Stream.mapAccumEffect(() => ({
894
+ buffer: initial,
895
+ observed: undefined,
896
+ turns: selector._tag === "transcript" ? turnsOpened(initial.events) : 0,
897
+ reportedCalls: (() => {
898
+ const seen = new Map();
899
+ if (selector._tag === "transcript") {
900
+ for (const event of initial.events) {
901
+ const key = callEventKey(event);
902
+ if (key !== undefined) {
903
+ seen.set(key, seen.get(key) === true || (nativeCallEvent(event) ?? nativeStepEvent(event)) !== undefined);
904
+ }
905
+ }
906
+ }
907
+ return seen;
908
+ })()
909
+ }), (state, candidate) => Effect.flatMap(decodedEvent(candidate, `${message}: the control plane returned an invalid event`), (event) => {
910
+ if (state.observed !== undefined && event.sequence < state.observed.value) {
911
+ return Effect.fail(unavailable(`${message}: event sequences moved backward`, undefined));
912
+ }
913
+ const position = {
914
+ value: event.sequence,
915
+ offset: state.observed?.value === event.sequence ? state.observed.offset + 1 : 0
916
+ };
917
+ const nextState = { ...state, observed: position };
918
+ if ((replayHealth || seedEvents.length > 0 || from.value > 0 || from.offset > 0) &&
919
+ comparePosition(position, from) <= 0) {
920
+ return replayHealth
921
+ ? Effect.map(appendEvent(state.buffer, event, `${message}: cursor history is invalid`, source.run), (buffer) => [{ ...nextState, buffer }, []])
922
+ : Effect.succeed([nextState, []]);
923
+ }
924
+ const turnsBefore = state.turns;
925
+ // A resumed follower must also suppress duplicates of calls
926
+ // reported before its cursor, without re-folding that prefix.
927
+ const callKey = selector._tag === "transcript" ? callEventKey(event) : undefined;
928
+ const duplicateCall = callKey !== undefined && state.reportedCalls.has(callKey);
929
+ const authoritative = (nativeCallEvent(event) ?? nativeStepEvent(event)) !== undefined;
930
+ const replaceTranscript = duplicateCall && authoritative && state.reportedCalls.get(callKey) === false;
931
+ if (callKey !== undefined) {
932
+ state.reportedCalls.set(callKey, authoritative || state.reportedCalls.get(callKey) === true);
933
+ }
934
+ const turns = selector._tag === "transcript"
935
+ ? transcriptAppend(event, turnsBefore).at(-1)?.turn ?? turnsBefore
936
+ : 0;
937
+ return Effect.flatMap(selector._tag === "run-summary" || selector._tag === "run-tree" || selector._tag === "approvals"
938
+ ? runOf(runId)
939
+ : Effect.succeed(source.run), (run) => Effect.map(appendEvent(state.buffer, event, `${message}: event history is invalid`, run), (buffer) => [
940
+ {
941
+ buffer,
942
+ observed: position,
943
+ turns,
944
+ reportedCalls: state.reportedCalls
945
+ },
946
+ [
947
+ {
948
+ run,
949
+ event,
950
+ events: buffer.events,
951
+ carry: buffer.carry,
952
+ position,
953
+ turnsBefore,
954
+ duplicateCall,
955
+ replaceTranscript
956
+ }
957
+ ]
958
+ ]));
959
+ })), Stream.mapEffect(({ carry, duplicateCall, event, events, position, replaceTranscript, run, turnsBefore }) => replaceTranscript
960
+ ? Effect.flatMap(boundedRows(selector, GatewayProjection.transcript(events)), (rows) => Effect.forEach([
961
+ { _tag: "snapshot-start", selector, cursor: cursorOf(selector, position) },
962
+ ...rows.map((row) => ({ _tag: "row", selector, cursor: cursorOf(selector, position), row })),
963
+ { _tag: "snapshot-end", selector, cursor: cursorOf(selector, position) }
964
+ ], frameOf))
965
+ : Effect.flatMap(deltaRows(selector, run, event, events, turnsBefore, duplicateCall, carry), (rows) => Effect.flatMap(boundedRows(selector, rows), (delta) => Effect.map(frameOf({
966
+ _tag: "delta",
967
+ selector,
968
+ cursor: cursorOf(selector, position),
969
+ delta
970
+ }), (frame) => [frame])))), Stream.flattenIterable)));
971
+ };
972
+ /**
973
+ * Workspace deltas from the unscoped control follow.
974
+ *
975
+ * Control sequences belong to run partitions, so this stream cannot start
976
+ * after one workspace cursor. It follows without a cursor instead. That
977
+ * replays partition history, and the sources seeded from the snapshot drop
978
+ * events through each run's last folded position. Excluded runs retain only
979
+ * a verdict and replay positions, sharing the source ceiling. Their replay
980
+ * costs no further journal reads until the verdict is evicted.
981
+ *
982
+ * Events are gathered for `workspaceDeltaCoalesceMillis` and admitted as one
983
+ * batch. A batch re-reads each changed run's row once, refolds only those
984
+ * runs' cached rows, and answers one frame, so a burst across many runs
985
+ * costs one frame and one row read per run that changed.
986
+ */
987
+ const workspaceDeltaFrames = (selector, runs) => Stream.unwrap(Effect.sync(() => {
988
+ const rowsOfSource = (source) => selector._tag === "approvals" ? pendingApprovals(source) : rowsOfRun(selector, source, now());
989
+ const sources = new Map(runs.map((source) => [
990
+ source.run.runId,
991
+ { source, observed: undefined, rows: rowsOfSource(source) }
992
+ ]));
993
+ // A verdict retains no journal. Undefined lastPosition means run_not_found.
994
+ // Sources and verdicts share one ceiling; only verdicts may be evicted.
995
+ const judged = new Map();
996
+ const makeRoom = () => {
997
+ if (sources.size + judged.size >= maxWorkspaceRuns) {
998
+ // Callers have room for a source, so a full cache has a verdict.
999
+ judged.delete(judged.keys().next().value);
1000
+ }
1001
+ };
1002
+ const remember = (runId, lastPosition, observed) => {
1003
+ judged.delete(runId);
1004
+ makeRoom();
1005
+ judged.set(runId, { lastPosition, observed });
1006
+ };
1007
+ /** The followed run created first, the one a newer run displaces at the ceiling. */
1008
+ const oldestFollowed = () => {
1009
+ let oldest;
1010
+ for (const followed of sources.values()) {
1011
+ if (oldest === undefined || createdAfter(oldest.source.run, followed.source.run))
1012
+ oldest = followed;
1013
+ }
1014
+ return oldest;
1015
+ };
1016
+ const noFrames = [];
1017
+ const delta = () => Effect.flatMap(boundedRows(selector, selector._tag === "approvals"
1018
+ // Each run's pending rows are cached apart; the inbox is folded
1019
+ // across all of them, exactly as a snapshot folds it.
1020
+ ? approvalInbox([...sources.values()].map(({ rows, source }) => ({ run: source.run, rows })))
1021
+ : [...sources.values()].flatMap(({ rows }) => rows)), (rows) => Effect.map(frameOf({
1022
+ _tag: "delta",
1023
+ selector,
1024
+ cursor: cursorOf(selector, { value: 0, offset: 0 }),
1025
+ delta: rows
1026
+ }), (frame) => [frame]));
1027
+ const message = "Following the workspace failed";
1028
+ const admit = (event, refreshed) => Effect.gen(function* () {
1029
+ const runId = event.runId;
1030
+ if (runId === undefined)
1031
+ return "unchanged";
1032
+ const current = sources.get(runId);
1033
+ const verdict = judged.get(runId);
1034
+ const previous = current?.observed ?? verdict?.observed;
1035
+ if (previous !== undefined && event.sequence < previous.value) {
1036
+ return yield* Effect.fail(unavailable(`${message}: event sequences moved backward`, undefined));
1037
+ }
1038
+ const position = {
1039
+ value: event.sequence,
1040
+ offset: previous?.value === event.sequence ? previous.offset + 1 : 0
1041
+ };
1042
+ if (current !== undefined) {
1043
+ current.observed = position;
1044
+ if (current.source.events.length > 0 && comparePosition(position, current.source.lastPosition) <= 0)
1045
+ return "unchanged";
1046
+ if (!refreshed.has(runId)) {
1047
+ current.source = { ...current.source, run: yield* runOf(runId) };
1048
+ refreshed.add(runId);
1049
+ }
1050
+ const buffer = yield* appendEvent(current.source, event, `${message}: event history is invalid`, current.source.run);
1051
+ current.source = { run: current.source.run, ...buffer };
1052
+ return "stale";
1053
+ }
1054
+ if (verdict !== undefined) {
1055
+ verdict.observed = position;
1056
+ if (verdict.lastPosition === undefined || comparePosition(position, verdict.lastPosition) <= 0) {
1057
+ return "unchanged";
1058
+ }
1059
+ }
1060
+ // At the ceiling a run newer than the oldest followed run displaces
1061
+ // it, so the workspace keeps the newest runs as the snapshot does.
1062
+ const displaced = sources.size >= maxWorkspaceRuns ? oldestFollowed() : undefined;
1063
+ if (displaced !== undefined) {
1064
+ const newer = yield* runOf(runId).pipe(Effect.map((run) => createdAfter(run, displaced.source.run)), Effect.catchIf((failure) => failure.code === "run_not_found", () => Effect.succeed(false)));
1065
+ if (!newer)
1066
+ return "unchanged";
1067
+ }
1068
+ return yield* runSourceOf(runId).pipe(Effect.flatMap((source) => {
1069
+ const admitted = source.events.length > 0 && comparePosition(position, source.lastPosition) <= 0
1070
+ ? Effect.succeed(source)
1071
+ : Effect.map(appendEvent(source, event, `${message}: event history is invalid`, source.run), (buffer) => ({ run: source.run, ...buffer }));
1072
+ return Effect.map(admitted, (complete) => {
1073
+ if (!eligibleForWorkspace(selector, complete)) {
1074
+ remember(runId, complete.lastPosition, position);
1075
+ return "unchanged";
1076
+ }
1077
+ judged.delete(runId);
1078
+ if (displaced !== undefined && sources.size >= maxWorkspaceRuns) {
1079
+ sources.delete(displaced.source.run.runId);
1080
+ }
1081
+ makeRoom();
1082
+ sources.set(runId, { source: complete, observed: position, rows: rowsOfSource(complete) });
1083
+ return "fresh";
1084
+ });
1085
+ }), Effect.catchIf((failure) => failure.code === "run_not_found", () => {
1086
+ remember(runId, undefined, position);
1087
+ return Effect.succeed("unchanged");
1088
+ }));
1089
+ });
1090
+ /** Refold one grown run using the row refreshed before its batch's compaction, or exclude it. */
1091
+ const refresh = (runId) => Effect.gen(function* () {
1092
+ const current = sources.get(runId);
1093
+ /* v8 ignore next -- only runs still followed after the batch are refreshed. */
1094
+ if (current === undefined || current.observed === undefined)
1095
+ return;
1096
+ const source = current.source;
1097
+ if (eligibleForWorkspace(selector, source)) {
1098
+ current.rows = rowsOfSource(source);
1099
+ }
1100
+ else {
1101
+ sources.delete(runId);
1102
+ remember(runId, source.lastPosition, current.observed);
1103
+ }
1104
+ });
1105
+ return control.watch({ follow: true }).pipe(Stream.tapError((cause) => logReadFailure("follow-workspace", cause)), Stream.mapError((cause) => unavailable(message, cause)), Stream.groupedWithin(workspaceDeltaBatchSize, workspaceDeltaCoalesceMillis), Stream.mapEffect((batch) => Effect.gen(function* () {
1106
+ const stale = new Set();
1107
+ const refreshed = new Set();
1108
+ let changed = false;
1109
+ for (const candidate of batch) {
1110
+ const event = yield* decodedEvent(candidate, `${message}: the control plane returned an invalid event`);
1111
+ const admission = yield* admit(event, refreshed);
1112
+ if (admission === "unchanged")
1113
+ continue;
1114
+ changed = true;
1115
+ if (admission === "stale" && event.runId !== undefined)
1116
+ stale.add(event.runId);
1117
+ }
1118
+ yield* Effect.forEach(stale, refresh, { discard: true });
1119
+ return changed ? yield* delta() : noFrames;
1120
+ })), Stream.flattenIterable);
1121
+ }));
1122
+ /**
1123
+ * Deltas for a selector.
1124
+ *
1125
+ * A run selector follows after its sequence cursor. A workspace selector has
1126
+ * no resume cursor because control journal sequences belong to per-run
1127
+ * partitions, but it can follow every partition without one.
1128
+ */
1129
+ const deltaFrames = (selector, source, from) => {
1130
+ // A duration row is ranked over the runs a flow has ALREADY finished, so
1131
+ // no event of a live run changes one, and the run that would change one
1132
+ // is not in the window until it settles. The honest delta is another
1133
+ // snapshot, which is what a client takes.
1134
+ if (selector._tag === "flow-durations")
1135
+ return Stream.empty;
1136
+ return source._tag === "workspace"
1137
+ ? workspaceDeltaFrames(selector, source.runs)
1138
+ : runDeltaFrames(selector, source.run, from);
1139
+ };
1140
+ /**
1141
+ * The keepalive channel. `Stream.tick` emits immediately and then on the
1142
+ * interval, and an immediate keepalive would arrive before the snapshot it
1143
+ * is supposed to keep alive; dropping the first tick makes the cadence what
1144
+ * it says it is.
1145
+ */
1146
+ const heartbeats = Stream.tick(heartbeatMillis).pipe(Stream.drop(1), Stream.mapEffect(() => Effect.map(Effect.clockWith((clock) => clock.currentTimeMillis), (atMs) => ({
1147
+ _tag: "heartbeat",
1148
+ atMs
1149
+ }))));
1150
+ /** The run a cursor resumes, or the refusal explaining why it resumes none. */
1151
+ const resumeScope = (selector, after) => {
1152
+ const candidate = after;
1153
+ if (!Number.isSafeInteger(candidate.value) || candidate.value < 0) {
1154
+ return malformed(`Cursor value ${String(candidate.value)} must be a non-negative safe integer`);
1155
+ }
1156
+ if (!Number.isSafeInteger(candidate.offset) || candidate.offset < 0) {
1157
+ return malformed(`Cursor offset ${String(candidate.offset)} must be a non-negative safe integer`);
1158
+ }
1159
+ if (!Schema.is(GatewaySchema.ProjectionCursor)(after)) {
1160
+ return malformed("The projection cursor is not a valid gateway cursor");
1161
+ }
1162
+ const runId = scopeOf(selector);
1163
+ if (after.projection !== selector._tag) {
1164
+ return malformed(`A cursor for the ${after.projection} projection cannot resume a ${selector._tag} subscription`);
1165
+ }
1166
+ if (!sameSelector(after.selector, selector)) {
1167
+ return malformed("A cursor can resume only the exact selector that issued it");
1168
+ }
1169
+ if (runId === undefined) {
1170
+ return malformed(`A ${selector._tag} subscription has no resumable cursor, because control journal sequences belong to per-run partitions`);
1171
+ }
1172
+ if (after.runId !== runId) {
1173
+ return malformed(`A cursor for run ${after.runId ?? "none"} cannot resume a subscription to run ${runId}`);
1174
+ }
1175
+ return runId;
1176
+ };
1177
+ /** Snapshot and follow share the reconciled event buffer. */
1178
+ const fromSnapshot = (selector) => Effect.flatMap(sourceOf(selector), (source) => Effect.flatMap(boundedRows(selector, rowsOf(selector, source, now())), (rows) => Effect.map(snapshotFrames(selector, source, rows), (frames) => Stream.concat(Stream.fromIterable(frames), deltaFrames(selector, source, cursorPositionOf(source))))));
1179
+ /**
1180
+ * The deltas after a cursor the client already holds, with no snapshot.
1181
+ *
1182
+ * The read still happens: a folded projection cannot be recomputed from the
1183
+ * events after the cursor alone. What the client skips is receiving rows it
1184
+ * already has.
1185
+ */
1186
+ const fromCursor = (selector, after) => {
1187
+ const scope = resumeScope(selector, after);
1188
+ return typeof scope === "string"
1189
+ ? Effect.flatMap(runSourceOf(scope, selector._tag !== "run-events"), (source) => {
1190
+ const position = { value: after.value, offset: after.offset };
1191
+ const last = source.lastPosition;
1192
+ if (comparePosition(position, last) > 0) {
1193
+ return Effect.fail(malformed(`Cursor ${after.value}:${after.offset} cannot resume run ${scope} past its last position ${last.value}:${last.offset}`));
1194
+ }
1195
+ const retained = position.value === 0 && position.offset === 0 ||
1196
+ positionedEvents(source.events).some((candidate) => comparePosition(candidate.position, position) === 0);
1197
+ // A valid issued health cursor may have been superseded in the bounded fold.
1198
+ // Verify it against the durable stream rather than accepting arbitrary positions.
1199
+ const issued = retained ? Effect.succeed(true) : source.compactHealth ?
1200
+ control.watch({
1201
+ runId: scope,
1202
+ follow: false,
1203
+ ...(position.value === 0 ? {} : { afterSequence: position.value - 1 })
1204
+ }).pipe(Stream.takeWhile((event) => event.sequence <= position.value), Stream.take(maxEventsPerRun + 1), Stream.runCollect, Effect.map((events) => events.length <= maxEventsPerRun && events[position.offset]?.sequence === position.value), Effect.mapError((cause) => unavailable("Checking the projection cursor failed", cause))) :
1205
+ Effect.succeed(false);
1206
+ return Effect.flatMap(issued, (valid) => valid
1207
+ ? Effect.succeed(runDeltaFrames(selector, source, position))
1208
+ : Effect.fail(malformed(`Cursor ${after.value}:${after.offset} was not issued by run ${scope}`)));
1209
+ })
1210
+ : Effect.fail(scope);
1211
+ };
1212
+ const subscribe = (selector, after) => Stream.merge(Stream.unwrap(after === undefined ? fromSnapshot(selector) : fromCursor(selector, after)), heartbeats, { haltStrategy: "left" });
1213
+ /*
1214
+ * The service is generic in its selector; the implementation folds every
1215
+ * selector through one code path and is not. The correlation the signature
1216
+ * promises is enforced at runtime rather than assumed: `boundedRows`
1217
+ * decodes each fold against `GatewaySchema.rowSchemaFor(selector)` and
1218
+ * fails the read when a row does not belong to the selector that asked.
1219
+ */
1220
+ return { snapshot, subscribe };
1221
+ };
1222
+ /**
1223
+ * Builds the read path over a control plane.
1224
+ *
1225
+ * Invalid keepalive settings are returned as typed `bind_failed` failures;
1226
+ * construction never throws synchronously.
1227
+ *
1228
+ * @param control the control service to read through
1229
+ * @param options the keepalive cadence to use
1230
+ * @since 1.0.0
1231
+ * @category constructors
1232
+ */
1233
+ export const make = (control, options = {}) => Effect.suspend(() => {
1234
+ const refusal = settingRefusal("The gateway keepalive cadence", options.heartbeatMillis);
1235
+ return refusal === undefined
1236
+ ? Effect.clockWith((clock) => Effect.succeed(makeService(control, options.heartbeatMillis ?? heartbeatIntervalMillis, () => clock.currentTimeMillisUnsafe())))
1237
+ : Effect.fail(refusal);
1238
+ });
1239
+ /**
1240
+ * Provides the gateway read path over the ambient control plane.
1241
+ *
1242
+ * @since 1.0.0
1243
+ * @category layers
1244
+ */
1245
+ export const layer = Layer.effect(Projections)(Effect.flatMap(Control, (control) => make(control)));
1246
+ /**
1247
+ * Provides the gateway read path under an explicit keepalive cadence.
1248
+ *
1249
+ * @param options the keepalive cadence to use
1250
+ * @since 1.0.0
1251
+ * @category layers
1252
+ */
1253
+ export const layerWith = (options) => Layer.effect(Projections)(Effect.flatMap(Control, (control) => make(control, options)));
1254
+ //# sourceMappingURL=Projections.js.map