@vercel/factory 0.0.13 → 0.0.15

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 (458) hide show
  1. package/README.md +307 -1
  2. package/dist/agent-routes.d.mts +69 -0
  3. package/dist/agent-routes.mjs +64 -0
  4. package/dist/agent-routes.mjs.map +1 -0
  5. package/dist/api-contracts.d.mts +1749 -0
  6. package/dist/api-contracts.mjs +118 -0
  7. package/dist/api-contracts.mjs.map +1 -0
  8. package/dist/api-images.d.mts +30 -0
  9. package/dist/api-images.mjs +28 -0
  10. package/dist/api-images.mjs.map +1 -0
  11. package/dist/api-task-graph.mjs +57 -0
  12. package/dist/api-task-graph.mjs.map +1 -0
  13. package/dist/api.d.mts +77 -0
  14. package/dist/api.mjs +1087 -0
  15. package/dist/api.mjs.map +1 -0
  16. package/dist/approval-access.d.mts +39 -0
  17. package/dist/approval-access.mjs +62 -0
  18. package/dist/approval-access.mjs.map +1 -0
  19. package/dist/approval-contracts.d.mts +692 -0
  20. package/dist/approval-contracts.mjs +27 -0
  21. package/dist/approval-contracts.mjs.map +1 -0
  22. package/dist/approval.d.mts +49 -0
  23. package/dist/approval.mjs +39 -0
  24. package/dist/approval.mjs.map +1 -0
  25. package/dist/blob/artifacts.d.mts +37 -0
  26. package/dist/blob/artifacts.mjs +45 -0
  27. package/dist/blob/artifacts.mjs.map +1 -0
  28. package/dist/blob/index.d.mts +44 -8
  29. package/dist/blob/index.mjs +159 -35
  30. package/dist/blob/index.mjs.map +1 -1
  31. package/dist/budget.d.mts +108 -0
  32. package/dist/budget.mjs +141 -0
  33. package/dist/budget.mjs.map +1 -0
  34. package/dist/budgets.d.mts +4 -0
  35. package/dist/budgets.mjs +4 -0
  36. package/dist/build-factory.d.mts +6 -4
  37. package/dist/build-factory.mjs +2 -1
  38. package/dist/build-factory.mjs.map +1 -1
  39. package/dist/change-verification/brief.d.mts +9 -0
  40. package/dist/change-verification/brief.mjs +13 -0
  41. package/dist/change-verification/brief.mjs.map +1 -0
  42. package/dist/change-verification/dispatch.d.mts +24 -0
  43. package/dist/change-verification/dispatch.mjs +45 -0
  44. package/dist/change-verification/dispatch.mjs.map +1 -0
  45. package/dist/change-verification/eve-tool.d.mts +60 -0
  46. package/dist/change-verification/eve-tool.mjs +101 -0
  47. package/dist/change-verification/eve-tool.mjs.map +1 -0
  48. package/dist/changes/eve-record-change.d.mts +56 -0
  49. package/dist/changes/eve-record-change.mjs +97 -0
  50. package/dist/changes/eve-record-change.mjs.map +1 -0
  51. package/dist/changes/presentation.d.mts +14 -0
  52. package/dist/changes.d.mts +23 -0
  53. package/dist/changes.mjs +29 -0
  54. package/dist/changes.mjs.map +1 -0
  55. package/dist/client-events.d.mts +18 -0
  56. package/dist/client-events.mjs +41 -0
  57. package/dist/client-events.mjs.map +1 -0
  58. package/dist/client-stream.d.mts +28 -0
  59. package/dist/client-stream.mjs +204 -0
  60. package/dist/client-stream.mjs.map +1 -0
  61. package/dist/client-transcript.d.mts +41 -0
  62. package/dist/client-transcript.mjs +229 -0
  63. package/dist/client-transcript.mjs.map +1 -0
  64. package/dist/client.d.mts +234 -0
  65. package/dist/client.mjs +258 -0
  66. package/dist/client.mjs.map +1 -0
  67. package/dist/code-review/contracts.d.mts +26 -0
  68. package/dist/code-review/eve-post-review.d.mts +70 -0
  69. package/dist/code-review/eve-post-review.mjs +183 -0
  70. package/dist/code-review/eve-post-review.mjs.map +1 -0
  71. package/dist/code-review/eve-review-comments.d.mts +64 -0
  72. package/dist/code-review/eve-review-comments.mjs +159 -0
  73. package/dist/code-review/eve-review-comments.mjs.map +1 -0
  74. package/dist/code-review/github-contracts.d.mts +10 -0
  75. package/dist/code-review/github-reporter.d.mts +44 -0
  76. package/dist/code-review/github-reporter.mjs +260 -0
  77. package/dist/code-review/github-reporter.mjs.map +1 -0
  78. package/dist/code-review/preparation.d.mts +34 -0
  79. package/dist/code-review/preparation.mjs +23 -0
  80. package/dist/code-review/preparation.mjs.map +1 -0
  81. package/dist/code-review/stall.d.mts +17 -0
  82. package/dist/code-review/stall.mjs +30 -0
  83. package/dist/code-review/stall.mjs.map +1 -0
  84. package/dist/code-review.d.mts +6 -0
  85. package/dist/code-review.mjs +5 -0
  86. package/dist/communication.d.mts +9 -0
  87. package/dist/communication.mjs +9 -0
  88. package/dist/connectors/github.d.mts +26 -0
  89. package/dist/connectors/github.mjs +55 -0
  90. package/dist/connectors/github.mjs.map +1 -0
  91. package/dist/connectors/index.d.mts +35 -2
  92. package/dist/connectors/index.mjs +4 -26
  93. package/dist/connectors/index.mjs.map +1 -1
  94. package/dist/connectors/outbox.d.mts +34 -0
  95. package/dist/connectors/outbox.mjs +50 -0
  96. package/dist/connectors/outbox.mjs.map +1 -0
  97. package/dist/connectors/slack-approval-blocks.mjs +136 -0
  98. package/dist/connectors/slack-approval-blocks.mjs.map +1 -0
  99. package/dist/connectors/slack-approvals.d.mts +29 -0
  100. package/dist/connectors/slack-approvals.mjs +346 -0
  101. package/dist/connectors/slack-approvals.mjs.map +1 -0
  102. package/dist/connectors/slack.d.mts +35 -41
  103. package/dist/connectors/slack.mjs +13 -69
  104. package/dist/connectors/slack.mjs.map +1 -1
  105. package/dist/deepsec/eve-tool.d.mts +110 -0
  106. package/dist/deepsec/eve-tool.mjs +164 -0
  107. package/dist/deepsec/eve-tool.mjs.map +1 -0
  108. package/dist/deepsec/finding.d.mts +84 -0
  109. package/dist/deepsec/finding.mjs +56 -0
  110. package/dist/deepsec/finding.mjs.map +1 -0
  111. package/dist/deepsec.d.mts +3 -0
  112. package/dist/delivery-metrics/metrics.d.mts +93 -0
  113. package/dist/delivery-metrics/metrics.mjs +200 -0
  114. package/dist/delivery-metrics/metrics.mjs.map +1 -0
  115. package/dist/delivery-metrics.d.mts +2 -0
  116. package/dist/delivery-metrics.mjs +2 -0
  117. package/dist/dispatch.d.mts +81 -0
  118. package/dist/dispatch.mjs +140 -0
  119. package/dist/dispatch.mjs.map +1 -0
  120. package/dist/errors.d.mts +15 -0
  121. package/dist/errors.mjs +38 -0
  122. package/dist/errors.mjs.map +1 -0
  123. package/dist/eve/index.d.mts +143 -1561
  124. package/dist/eve/index.mjs +244 -1063
  125. package/dist/eve/index.mjs.map +1 -1
  126. package/dist/eve/invoke.d.mts +144 -0
  127. package/dist/eve/invoke.mjs +241 -0
  128. package/dist/eve/invoke.mjs.map +1 -0
  129. package/dist/eve/session-client.d.mts +68 -0
  130. package/dist/eve/session-client.mjs +131 -0
  131. package/dist/eve/session-client.mjs.map +1 -0
  132. package/dist/eve/task-execution.mjs +18 -0
  133. package/dist/eve/task-execution.mjs.map +1 -0
  134. package/dist/eve/task-session.d.mts +87 -0
  135. package/dist/eve/task-session.mjs +203 -0
  136. package/dist/eve/task-session.mjs.map +1 -0
  137. package/dist/eve/transcript.d.mts +37 -0
  138. package/dist/eve/transcript.mjs +65 -0
  139. package/dist/eve/transcript.mjs.map +1 -0
  140. package/dist/execution-public.d.mts +8 -0
  141. package/dist/execution-public.mjs +7 -0
  142. package/dist/execution.d.mts +75 -0
  143. package/dist/execution.mjs +153 -0
  144. package/dist/execution.mjs.map +1 -0
  145. package/dist/finding-remediation/admission.d.mts +51 -0
  146. package/dist/finding-remediation/admission.mjs +87 -0
  147. package/dist/finding-remediation/admission.mjs.map +1 -0
  148. package/dist/finding-remediation/brief.d.mts +14 -0
  149. package/dist/finding-remediation/brief.mjs +25 -0
  150. package/dist/finding-remediation/brief.mjs.map +1 -0
  151. package/dist/finding-remediation/eve-tool.d.mts +29 -0
  152. package/dist/finding-remediation/eve-tool.mjs +34 -0
  153. package/dist/finding-remediation/eve-tool.mjs.map +1 -0
  154. package/dist/findings/fingerprint.d.mts +14 -0
  155. package/dist/findings/fingerprint.mjs +28 -0
  156. package/dist/findings/fingerprint.mjs.map +1 -0
  157. package/dist/findings.d.mts +6 -0
  158. package/dist/findings.mjs +6 -0
  159. package/dist/github-app.d.mts +54 -0
  160. package/dist/github-app.mjs +121 -0
  161. package/dist/github-app.mjs.map +1 -0
  162. package/dist/github-publication.d.mts +198 -0
  163. package/dist/github-publication.mjs +429 -0
  164. package/dist/github-publication.mjs.map +1 -0
  165. package/dist/github-transfer.d.mts +72 -0
  166. package/dist/github-transfer.mjs +125 -0
  167. package/dist/github-transfer.mjs.map +1 -0
  168. package/dist/github.d.mts +406 -2
  169. package/dist/github.mjs +250 -37
  170. package/dist/github.mjs.map +1 -1
  171. package/dist/image-input.d.mts +51 -0
  172. package/dist/image-input.mjs +63 -0
  173. package/dist/image-input.mjs.map +1 -0
  174. package/dist/index.d.mts +14 -314
  175. package/dist/index.mjs +14 -1124
  176. package/dist/intake-contracts.d.mts +56 -0
  177. package/dist/intake-contracts.mjs +33 -0
  178. package/dist/intake-contracts.mjs.map +1 -0
  179. package/dist/integrations/deepsec.d.mts +4 -0
  180. package/dist/integrations/deepsec.mjs +3 -0
  181. package/dist/integrations/github.d.mts +11 -0
  182. package/dist/integrations/github.mjs +9 -0
  183. package/dist/integrations/slack.d.mts +3 -0
  184. package/dist/integrations/slack.mjs +3 -0
  185. package/dist/integrations/vercel.d.mts +3 -0
  186. package/dist/integrations/vercel.mjs +3 -0
  187. package/dist/merge-resolution/contracts.d.mts +17 -0
  188. package/dist/merge-resolution/contracts.mjs +19 -0
  189. package/dist/merge-resolution/contracts.mjs.map +1 -0
  190. package/dist/merge-resolution/eve-tools.d.mts +46 -0
  191. package/dist/merge-resolution/eve-tools.mjs +117 -0
  192. package/dist/merge-resolution/eve-tools.mjs.map +1 -0
  193. package/dist/multi-repository-changes/coordination.d.mts +49 -0
  194. package/dist/multi-repository-changes/coordination.mjs +115 -0
  195. package/dist/multi-repository-changes/coordination.mjs.map +1 -0
  196. package/dist/planning/cancellation.mjs +82 -0
  197. package/dist/planning/cancellation.mjs.map +1 -0
  198. package/dist/planning/configuration.d.mts +23 -0
  199. package/dist/planning/configuration.mjs +56 -0
  200. package/dist/planning/configuration.mjs.map +1 -0
  201. package/dist/planning/contracts.d.mts +136 -0
  202. package/dist/planning/contracts.mjs +42 -0
  203. package/dist/planning/contracts.mjs.map +1 -0
  204. package/dist/planning/dependencies.mjs +44 -0
  205. package/dist/planning/dependencies.mjs.map +1 -0
  206. package/dist/planning/effects.mjs +122 -0
  207. package/dist/planning/effects.mjs.map +1 -0
  208. package/dist/planning/reconcile.mjs +273 -0
  209. package/dist/planning/reconcile.mjs.map +1 -0
  210. package/dist/planning/runtime.d.mts +27 -0
  211. package/dist/planning/runtime.mjs +25 -0
  212. package/dist/planning/runtime.mjs.map +1 -0
  213. package/dist/planning/state.mjs +92 -0
  214. package/dist/planning/state.mjs.map +1 -0
  215. package/dist/planning/turns.mjs +262 -0
  216. package/dist/planning/turns.mjs.map +1 -0
  217. package/dist/planning/validation.mjs +106 -0
  218. package/dist/planning/validation.mjs.map +1 -0
  219. package/dist/postgres/index.d.mts +7 -1
  220. package/dist/postgres/index.mjs +7 -3
  221. package/dist/postgres/index.mjs.map +1 -1
  222. package/dist/presets/software-development/approval.d.mts +14 -0
  223. package/dist/presets/software-development/approval.mjs +21 -0
  224. package/dist/presets/software-development/approval.mjs.map +1 -0
  225. package/dist/presets/software-development/deepsec-brief.d.mts +8 -0
  226. package/dist/presets/software-development/deepsec-brief.mjs +14 -0
  227. package/dist/presets/software-development/deepsec-brief.mjs.map +1 -0
  228. package/dist/presets/software-development/dispatch.d.mts +53 -0
  229. package/dist/presets/software-development/dispatch.mjs +66 -0
  230. package/dist/presets/software-development/dispatch.mjs.map +1 -0
  231. package/dist/presets/software-development/eve-delegation-policy.d.mts +7 -0
  232. package/dist/presets/software-development/eve-delegation-policy.mjs +34 -0
  233. package/dist/presets/software-development/eve-delegation-policy.mjs.map +1 -0
  234. package/dist/presets/software-development/eve.d.mts +18 -0
  235. package/dist/presets/software-development/eve.mjs +19 -0
  236. package/dist/presets/software-development/eve.mjs.map +1 -0
  237. package/dist/presets/software-development/finish-task-policy.d.mts +7 -0
  238. package/dist/presets/software-development/finish-task-policy.mjs +9 -0
  239. package/dist/presets/software-development/finish-task-policy.mjs.map +1 -0
  240. package/dist/presets/software-development/recovery.d.mts +32 -0
  241. package/dist/presets/software-development/recovery.mjs +120 -0
  242. package/dist/presets/software-development/recovery.mjs.map +1 -0
  243. package/dist/presets/software-development/task-communication.d.mts +23 -0
  244. package/dist/presets/software-development/task-communication.mjs +120 -0
  245. package/dist/presets/software-development/task-communication.mjs.map +1 -0
  246. package/dist/presets/software-development.d.mts +9 -0
  247. package/dist/presets/software-development.mjs +9 -0
  248. package/dist/pull-requests/contracts.d.mts +72 -0
  249. package/dist/pull-requests/eve-fetch.d.mts +57 -0
  250. package/dist/pull-requests/eve-fetch.mjs +151 -0
  251. package/dist/pull-requests/eve-fetch.mjs.map +1 -0
  252. package/dist/pull-requests/github-label-manager.d.mts +27 -0
  253. package/dist/pull-requests/github-label-manager.mjs +40 -0
  254. package/dist/pull-requests/github-label-manager.mjs.map +1 -0
  255. package/dist/pull-requests/github-publisher.d.mts +53 -0
  256. package/dist/pull-requests/github-publisher.mjs +345 -0
  257. package/dist/pull-requests/github-publisher.mjs.map +1 -0
  258. package/dist/pull-requests.d.mts +5 -0
  259. package/dist/pull-requests.mjs +4 -0
  260. package/dist/receipts.d.mts +4 -0
  261. package/dist/receipts.mjs +3 -0
  262. package/dist/repositories.d.mts +18 -0
  263. package/dist/repositories.mjs +33 -0
  264. package/dist/repositories.mjs.map +1 -0
  265. package/dist/sandbox/command.d.mts +18 -0
  266. package/dist/sandbox/execution-abort.d.mts +16 -0
  267. package/dist/sandbox/execution-abort.mjs +49 -0
  268. package/dist/sandbox/execution-abort.mjs.map +1 -0
  269. package/dist/sandbox/execution-environment.d.mts +25 -0
  270. package/dist/sandbox/execution-run-ledger.d.mts +115 -0
  271. package/dist/sandbox/execution-run-ledger.mjs +209 -0
  272. package/dist/sandbox/execution-run-ledger.mjs.map +1 -0
  273. package/dist/sandbox/execution.d.mts +54 -0
  274. package/dist/sandbox/execution.mjs +228 -0
  275. package/dist/sandbox/execution.mjs.map +1 -0
  276. package/dist/sandbox/harness-runner.d.mts +79 -0
  277. package/dist/sandbox/harness-runner.mjs +162 -0
  278. package/dist/sandbox/harness-runner.mjs.map +1 -0
  279. package/dist/sandbox/index.d.mts +9 -0
  280. package/dist/sandbox/index.mjs +7 -0
  281. package/dist/sandbox/task-environment.d.mts +50 -0
  282. package/dist/sandbox/task-environment.mjs +140 -0
  283. package/dist/sandbox/task-environment.mjs.map +1 -0
  284. package/dist/sandbox/workspace.d.mts +29 -0
  285. package/dist/sandbox/workspace.mjs +28 -0
  286. package/dist/sandbox/workspace.mjs.map +1 -0
  287. package/dist/schema/agent-route.d.mts +22 -0
  288. package/dist/schema/agent-route.mjs +15 -0
  289. package/dist/schema/agent-route.mjs.map +1 -0
  290. package/dist/schema/answer.d.mts +23 -0
  291. package/dist/schema/answer.mjs +21 -0
  292. package/dist/schema/answer.mjs.map +1 -0
  293. package/dist/schema/budget.d.mts +38 -0
  294. package/dist/schema/budget.mjs +30 -0
  295. package/dist/schema/budget.mjs.map +1 -0
  296. package/dist/schema/catalog.mjs +72 -0
  297. package/dist/schema/catalog.mjs.map +1 -0
  298. package/dist/schema/change.d.mts +117 -0
  299. package/dist/schema/change.mjs +68 -0
  300. package/dist/schema/change.mjs.map +1 -0
  301. package/dist/schema/common.d.mts +33 -0
  302. package/dist/{id-DwCihQr4.mjs → schema/common.mjs} +7 -14
  303. package/dist/schema/common.mjs.map +1 -0
  304. package/dist/schema/communication.d.mts +52 -0
  305. package/dist/schema/communication.mjs +35 -0
  306. package/dist/schema/communication.mjs.map +1 -0
  307. package/dist/schema/execution.d.mts +101 -0
  308. package/dist/schema/execution.mjs +86 -0
  309. package/dist/schema/execution.mjs.map +1 -0
  310. package/dist/schema/factory-config.d.mts +95 -0
  311. package/dist/schema/factory-config.mjs +60 -0
  312. package/dist/schema/factory-config.mjs.map +1 -0
  313. package/dist/schema/finding.d.mts +125 -0
  314. package/dist/schema/finding.mjs +67 -0
  315. package/dist/schema/finding.mjs.map +1 -0
  316. package/dist/schema/id.d.mts +43 -0
  317. package/dist/schema/id.mjs +32 -0
  318. package/dist/schema/id.mjs.map +1 -0
  319. package/dist/schema/outbox.d.mts +240 -0
  320. package/dist/schema/outbox.mjs +53 -0
  321. package/dist/schema/outbox.mjs.map +1 -0
  322. package/dist/schema/question-route.mjs +16 -0
  323. package/dist/schema/question-route.mjs.map +1 -0
  324. package/dist/schema/receipt.d.mts +81 -0
  325. package/dist/schema/receipt.mjs +94 -0
  326. package/dist/schema/receipt.mjs.map +1 -0
  327. package/dist/schema/repository.d.mts +26 -0
  328. package/dist/schema/repository.mjs +25 -0
  329. package/dist/schema/repository.mjs.map +1 -0
  330. package/dist/schema/session.d.mts +135 -0
  331. package/dist/schema/session.mjs +53 -0
  332. package/dist/schema/session.mjs.map +1 -0
  333. package/dist/schema/signal.d.mts +105 -0
  334. package/dist/schema/signal.mjs +81 -0
  335. package/dist/schema/signal.mjs.map +1 -0
  336. package/dist/schema/stored-session.mjs +10 -0
  337. package/dist/schema/stored-session.mjs.map +1 -0
  338. package/dist/schema/stored-task.mjs +20 -0
  339. package/dist/schema/stored-task.mjs.map +1 -0
  340. package/dist/schema/task-effect.d.mts +39 -0
  341. package/dist/schema/task-effect.mjs +29 -0
  342. package/dist/schema/task-effect.mjs.map +1 -0
  343. package/dist/schema/task-graph.d.mts +3134 -0
  344. package/dist/schema/task-graph.mjs +97 -0
  345. package/dist/schema/task-graph.mjs.map +1 -0
  346. package/dist/schema/task-message.d.mts +254 -0
  347. package/dist/schema/task-message.mjs +189 -0
  348. package/dist/schema/task-message.mjs.map +1 -0
  349. package/dist/schema/task.d.mts +272 -0
  350. package/dist/schema/task.mjs +199 -0
  351. package/dist/schema/task.mjs.map +1 -0
  352. package/dist/schema/transcript.d.mts +170 -0
  353. package/dist/schema/transcript.mjs +86 -0
  354. package/dist/schema/transcript.mjs.map +1 -0
  355. package/dist/schema/transitions.d.mts +50 -0
  356. package/dist/schema/transitions.mjs +303 -0
  357. package/dist/schema/transitions.mjs.map +1 -0
  358. package/dist/schema/work.d.mts +101 -0
  359. package/dist/schema/work.mjs +102 -0
  360. package/dist/schema/work.mjs.map +1 -0
  361. package/dist/signal-triage.d.mts +20 -0
  362. package/dist/signal-triage.mjs +85 -0
  363. package/dist/signal-triage.mjs.map +1 -0
  364. package/dist/signals.d.mts +19 -0
  365. package/dist/signals.mjs +19 -0
  366. package/dist/signals.mjs.map +1 -0
  367. package/dist/stall.d.mts +38 -0
  368. package/dist/stall.mjs +79 -0
  369. package/dist/stall.mjs.map +1 -0
  370. package/dist/storage.d.mts +4 -0
  371. package/dist/storage.mjs +4 -0
  372. package/dist/store/driver.d.mts +65 -0
  373. package/dist/store/driver.mjs +42 -0
  374. package/dist/store/driver.mjs.map +1 -0
  375. package/dist/store/engine.d.mts +485 -0
  376. package/dist/store/engine.mjs +2175 -0
  377. package/dist/store/engine.mjs.map +1 -0
  378. package/dist/store/memory.d.mts +7 -0
  379. package/dist/store/memory.mjs +52 -0
  380. package/dist/store/memory.mjs.map +1 -0
  381. package/dist/store/task-effects.d.mts +38 -0
  382. package/dist/store/task-effects.mjs +118 -0
  383. package/dist/store/task-effects.mjs.map +1 -0
  384. package/dist/store/task-graphs.d.mts +60 -0
  385. package/dist/store/task-graphs.mjs +212 -0
  386. package/dist/store/task-graphs.mjs.map +1 -0
  387. package/dist/store/task-messages.d.mts +136 -0
  388. package/dist/store/task-messages.mjs +475 -0
  389. package/dist/store/task-messages.mjs.map +1 -0
  390. package/dist/store/task-work.d.mts +65 -0
  391. package/dist/store/task-work.mjs +134 -0
  392. package/dist/store/task-work.mjs.map +1 -0
  393. package/dist/store/work-notifications.d.mts +19 -0
  394. package/dist/store/work-notifications.mjs +48 -0
  395. package/dist/store/work-notifications.mjs.map +1 -0
  396. package/dist/sweep.d.mts +57 -0
  397. package/dist/sweep.mjs +143 -0
  398. package/dist/sweep.mjs.map +1 -0
  399. package/dist/task-communication.d.mts +35 -0
  400. package/dist/task-communication.mjs +47 -0
  401. package/dist/task-communication.mjs.map +1 -0
  402. package/dist/task-graph-view.d.mts +264 -0
  403. package/dist/task-graph-view.mjs +17 -0
  404. package/dist/task-graph-view.mjs.map +1 -0
  405. package/dist/task-message-processing.d.mts +60 -0
  406. package/dist/task-message-processing.mjs +200 -0
  407. package/dist/task-message-processing.mjs.map +1 -0
  408. package/dist/task-protocols.d.mts +80 -0
  409. package/dist/task-protocols.mjs +47 -0
  410. package/dist/task-protocols.mjs.map +1 -0
  411. package/dist/tasks.d.mts +17 -0
  412. package/dist/tasks.mjs +17 -0
  413. package/dist/vercel-git.d.mts +21 -0
  414. package/dist/vercel-git.mjs +112 -0
  415. package/dist/vercel-git.mjs.map +1 -0
  416. package/dist/vercel.d.mts +33 -2
  417. package/dist/vercel.mjs +48 -3
  418. package/dist/vercel.mjs.map +1 -1
  419. package/dist/work-triage/eve-tool.d.mts +29 -0
  420. package/dist/work-triage/eve-tool.mjs +32 -0
  421. package/dist/work-triage/eve-tool.mjs.map +1 -0
  422. package/dist/work-triage/find-existing-work.d.mts +32 -0
  423. package/dist/work-triage/find-existing-work.mjs +31 -0
  424. package/dist/work-triage/find-existing-work.mjs.map +1 -0
  425. package/dist/work-triage/github.d.mts +15 -0
  426. package/dist/work-triage/github.mjs +14 -0
  427. package/dist/work-triage/github.mjs.map +1 -0
  428. package/dist/work-triage.d.mts +3 -0
  429. package/dist/work-triage.mjs +3 -0
  430. package/dist/workflows.d.mts +48 -0
  431. package/dist/workflows.mjs +86 -0
  432. package/dist/workflows.mjs.map +1 -0
  433. package/dist/workspace-diff-assembly.d.mts +33 -0
  434. package/dist/workspace-diff-assembly.mjs +95 -0
  435. package/dist/workspace-diff-assembly.mjs.map +1 -0
  436. package/dist/workspace-diff.d.mts +90 -0
  437. package/dist/workspace-diff.mjs +61 -0
  438. package/dist/workspace-diff.mjs.map +1 -0
  439. package/dist/workspace-snapshot.d.mts +26 -0
  440. package/dist/workspace-snapshot.mjs +80 -0
  441. package/dist/workspace-snapshot.mjs.map +1 -0
  442. package/package.json +129 -40
  443. package/dist/approval-CwhTP1XB.mjs +0 -269
  444. package/dist/approval-CwhTP1XB.mjs.map +0 -1
  445. package/dist/change-CzEAoFVE.mjs +0 -620
  446. package/dist/change-CzEAoFVE.mjs.map +0 -1
  447. package/dist/dispatch-B77cuPxh.d.mts +0 -713
  448. package/dist/driver-BJutB0PE.d.mts +0 -34
  449. package/dist/driver-Bbd25rNM.mjs +0 -23
  450. package/dist/driver-Bbd25rNM.mjs.map +0 -1
  451. package/dist/factory-config-BJcjsPKl.mjs +0 -86
  452. package/dist/factory-config-BJcjsPKl.mjs.map +0 -1
  453. package/dist/factory-config-D2yU3G0c.d.mts +0 -58
  454. package/dist/github-DXsxboUL.d.mts +0 -271
  455. package/dist/id-DwCihQr4.mjs.map +0 -1
  456. package/dist/index-Cy4jfXh5.d.mts +0 -67
  457. package/dist/index.mjs.map +0 -1
  458. package/dist/signal-CHVEmzvI.d.mts +0 -59
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/blob/index.ts"],"sourcesContent":["import { get, list, put } from \"@vercel/blob\";\nimport {\n RecordExistsError,\n RecordNotFoundError,\n VersionConflictError,\n type CollectionName,\n type StoredEntry,\n type StoreDriver,\n} from \"../store/driver\";\n\nexport interface BlobObject {\n body: string;\n etag: string;\n}\n\n/** The narrow blob operations the driver needs; injectable for tests. */\nexport interface BlobClient {\n /** Must return the latest content, never a cached copy. */\n read(pathname: string): Promise<BlobObject | null>;\n /** Must reject if overwrite is false and the path exists, or if ifMatch is given and the ETag differs. */\n write(pathname: string, body: string, overwrite: boolean, ifMatch?: string): Promise<void>;\n listPaths(prefix: string): Promise<string[]>;\n}\n\n// @vercel/blob throws BlobNotFoundError with the message\n// \"Vercel Blob: The requested blob does not exist\".\nfunction isNotFoundError(error: unknown): boolean {\n return (\n error instanceof Error &&\n (error.name === \"BlobNotFoundError\" ||\n /does not exist|not.?found|404/iu.test(error.message))\n );\n}\n\nfunction isAlreadyExistsError(error: unknown): boolean {\n return (\n error instanceof Error &&\n /already exists|not allowed to overwrite|overwrite/iu.test(error.message)\n );\n}\n\n// @vercel/blob throws BlobPreconditionFailedError when ifMatch does not hold.\nfunction isPreconditionError(error: unknown): boolean {\n return (\n error instanceof Error &&\n (error.name === \"BlobPreconditionFailedError\" || /precondition|etag|412/iu.test(error.message))\n );\n}\n\nexport const vercelBlobClient: BlobClient = {\n async read(pathname) {\n let result;\n try {\n // Uncached: the ledger is read-modify-write and must see the latest version.\n result = await get(pathname, { access: \"private\", useCache: false });\n } catch (error) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n if (result === null || result.stream === null) {\n return null;\n }\n return { body: await new Response(result.stream).text(), etag: result.blob.etag };\n },\n\n async write(pathname, body, overwrite, ifMatch) {\n await put(pathname, body, {\n access: \"private\",\n allowOverwrite: overwrite,\n contentType: \"application/json\",\n ...(ifMatch === undefined ? {} : { ifMatch }),\n });\n },\n\n async listPaths(prefix) {\n const paths: string[] = [];\n let cursor: string | undefined;\n do {\n const page = await list({ prefix, cursor, limit: 1000 });\n paths.push(...page.blobs.map((blob) => blob.pathname));\n cursor = page.hasMore ? page.cursor : undefined;\n } while (cursor !== undefined);\n return paths;\n },\n};\n\nexport interface BlobDriverOptions {\n client?: BlobClient;\n /** Path prefix inside the blob store; defaults to \"factory\". */\n prefix?: string;\n}\n\n/**\n * Store driver on Vercel Blob: durable and dependency-free (one Blob store on\n * the project, no database). Inserts and receipts are exactly as safe as on\n * Postgres (create-if-absent is atomic). Updates read the latest content,\n * bypassing Blob's CDN cache, and write with an ETag precondition, so a\n * writer that lost a race gets a VersionConflictError rather than\n * overwriting the winner.\n */\nexport function createBlobDriver(options: BlobDriverOptions = {}): StoreDriver {\n const client = options.client ?? vercelBlobClient;\n const prefix = options.prefix ?? \"factory\";\n\n function pathFor(collection: CollectionName, id: string): string {\n return `${prefix}/${collection}/${id}.json`;\n }\n\n async function readRecord(\n collection: CollectionName,\n id: string,\n ): Promise<{ value: unknown; version: number; etag: string } | null> {\n let object: BlobObject | null;\n try {\n object = await client.read(pathFor(collection, id));\n } catch (error) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n if (object === null) {\n return null;\n }\n const parsed = JSON.parse(object.body) as { value: unknown; version: number };\n if (typeof parsed.version !== \"number\") {\n throw new Error(`malformed stored record at ${pathFor(collection, id)}`);\n }\n return { value: parsed.value, version: parsed.version, etag: object.etag };\n }\n\n return {\n name: \"blob\",\n\n async get(collection, id) {\n const record = await readRecord(collection, id);\n return record === null ? null : { value: record.value, version: record.version };\n },\n\n async insert(collection, id, value) {\n try {\n await client.write(\n pathFor(collection, id),\n JSON.stringify({ value, version: 1 }),\n false,\n );\n } catch (error) {\n if (isAlreadyExistsError(error)) {\n throw new RecordExistsError(collection, id);\n }\n throw error;\n }\n },\n\n async update(collection, id, value, expectedVersion) {\n const existing = await readRecord(collection, id);\n if (existing === null) {\n throw new RecordNotFoundError(collection, id);\n }\n if (existing.version !== expectedVersion) {\n throw new VersionConflictError(collection, id, expectedVersion);\n }\n try {\n await client.write(\n pathFor(collection, id),\n JSON.stringify({ value, version: expectedVersion + 1 }),\n true,\n // The write API's ifMatch rejects the weak form (W/\"…\") that reads return.\n existing.etag.replace(/^W\\//u, \"\"),\n );\n } catch (error) {\n if (isPreconditionError(error)) {\n throw new VersionConflictError(collection, id, expectedVersion);\n }\n throw error;\n }\n },\n\n async list(collection) {\n const dir = `${prefix}/${collection}/`;\n const paths = await client.listPaths(dir);\n const entries: StoredEntry[] = [];\n for (const pathname of paths) {\n const id = pathname.slice(dir.length).replace(/\\.json$/u, \"\");\n const record = await readRecord(collection, id);\n if (record !== null) {\n entries.push({ id, record: { value: record.value, version: record.version } });\n }\n }\n return entries;\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/blob/index.ts"],"sourcesContent":["import { get, list, put } from \"@vercel/blob\";\nexport {\n artifactReference,\n createArtifactStore,\n parseArtifactRef,\n type ArtifactRef,\n type ArtifactInput,\n type ArtifactKind,\n type ReadArtifactInput,\n type ArtifactStore,\n} from \"./artifacts\";\nimport {\n RecordExistsError,\n RecordNotFoundError,\n TransientStoreError,\n VersionConflictError,\n type CollectionName,\n type StoredEntry,\n type StoreDriver,\n} from \"../store/driver\";\nimport type { MillisecondClock } from \"../schema/common\";\n\n/** Blob content paired with the ETag required for compare-and-swap persistence. */\nexport interface BlobObject {\n body: string;\n etag: string;\n}\n\n/** Exact blob pathname requested from storage. */\nexport interface ReadBlobInput {\n pathname: string;\n}\n\n/** Blob body and conditional-write policy used for one storage write. */\nexport interface WriteBlobInput extends ReadBlobInput {\n body: string;\n overwrite: boolean;\n ifMatch?: string;\n}\n\n/** Path prefix used to list matching blobs. */\nexport interface ListBlobPathsInput {\n prefix: string;\n}\n\n/** The narrow blob operations the driver needs; injectable for tests. */\nexport interface BlobClient {\n /** Must return the latest content, never a cached copy. */\n read(input: ReadBlobInput): Promise<BlobObject | null>;\n /** Must reject if overwrite is false and the path exists, or if ifMatch is given and the ETag differs. */\n write(input: WriteBlobInput): Promise<void>;\n listPaths(input: ListBlobPathsInput): Promise<readonly string[]>;\n}\n\n// @vercel/blob throws BlobNotFoundError with the message\n// \"Vercel Blob: The requested blob does not exist\".\nfunction isNotFoundError(error: unknown): boolean {\n return (\n error instanceof Error &&\n (error.name === \"BlobNotFoundError\" || /does not exist|not.?found|404/iu.test(error.message))\n );\n}\n\nfunction isAlreadyExistsError(error: unknown): boolean {\n return (\n error instanceof Error &&\n /already exists|not allowed to overwrite|overwrite/iu.test(error.message)\n );\n}\n\n// @vercel/blob throws BlobPreconditionFailedError when ifMatch does not hold.\nfunction isPreconditionError(error: unknown): boolean {\n return (\n error instanceof Error &&\n (error.name === \"BlobPreconditionFailedError\" || /precondition|etag|412/iu.test(error.message))\n );\n}\n\nconst TRANSIENT_NETWORK_ERROR_CODES = new Set([\n \"ECONNRESET\",\n \"ECONNREFUSED\",\n \"ETIMEDOUT\",\n \"ENOTFOUND\",\n \"EAI_AGAIN\",\n \"EPIPE\",\n \"EHOSTUNREACH\",\n \"ENETUNREACH\",\n \"UND_ERR_CONNECT_TIMEOUT\",\n \"UND_ERR_SOCKET\",\n \"UND_ERR_HEADERS_TIMEOUT\",\n]);\n\n// Blob 5xx/429s and plain network failures are transient: the same read or\n// write is worth retrying with backoff. Direct downloads bypass the SDK's\n// own request-retry policy entirely, and outages can also surface as a\n// generic HTTP error on the write path, so this checks the error's code and\n// message rather than one fixed message shape.\nfunction isTransientBlobError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n if (isAlreadyExistsError(error) || isPreconditionError(error) || isNotFoundError(error))\n return false;\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== undefined && TRANSIENT_NETWORK_ERROR_CODES.has(code)) return true;\n if (error.name === \"AbortError\") return true;\n if (\n /fetch failed|network|socket hang up|timed? ?out|ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN/iu.test(\n error.message,\n )\n ) {\n return true;\n }\n // Matches an HTTP 429 or 5xx status appearing anywhere in the message,\n // whether from the direct-download fetch or a generic API error.\n return /(?:^|[^0-9])(?:429|5\\d{2})(?:[^0-9]|$)/u.test(error.message);\n}\n\n// Structural equality over JSON-serializable values: good enough to tell\n// whether a record we just read back is the exact one we tried to write,\n// without depending on key order surviving a JSON round-trip.\nfunction jsonValuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null || typeof a !== \"object\" || typeof b !== \"object\") return false;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;\n return a.every((item, index) => jsonValuesEqual(item, b[index]));\n }\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every(\n (key) =>\n Object.hasOwn(b as Record<string, unknown>, key) &&\n jsonValuesEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n}\n\n/** Default private Vercel Blob client used by the durable Factory store driver. */\nexport const vercelBlobClient: BlobClient = {\n async read({ pathname }) {\n let result;\n try {\n // Uncached: the ledger is read-modify-write and must see the latest version.\n result = await get(pathname, { access: \"private\", useCache: false });\n } catch (error) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n if (result === null || result.stream === null) {\n return null;\n }\n return { body: await new Response(result.stream).text(), etag: result.blob.etag };\n },\n\n async write({ pathname, body, overwrite, ifMatch }) {\n // The SDK treats an empty string as a missing body.\n await put(pathname, body === \"\" ? Buffer.alloc(0) : body, {\n access: \"private\",\n allowOverwrite: overwrite,\n contentType: \"application/json\",\n ...(ifMatch === undefined ? {} : { ifMatch: ifMatch.replace(/^W\\//u, \"\") }),\n });\n },\n\n async listPaths({ prefix }) {\n const paths: string[] = [];\n let cursor: string | undefined;\n do {\n const page = await list({ prefix, cursor, limit: 1000 });\n paths.push(...page.blobs.map((blob) => blob.pathname));\n cursor = page.hasMore ? page.cursor : undefined;\n } while (cursor !== undefined);\n return paths;\n },\n};\n\n/** Bounds retries and exponential backoff for transient Blob operations. */\nexport interface BlobRetryOptions {\n /** Maximum attempts of a single read or write, including the first. Default 3. */\n attempts?: number;\n /** Backoff base in milliseconds; doubles each retry. Default 250. */\n baseDelayMs?: number;\n /** Ceiling on total elapsed retry time so a real outage still surfaces as a failure. Default 5000. */\n maxTotalMs?: number;\n}\n\n/** Configures the Blob client, namespace, read concurrency, and transient retries. */\nexport interface BlobDriverOptions {\n client?: BlobClient;\n /** Path prefix inside the blob store; defaults to \"factory\". */\n prefix?: string;\n /** Maximum Blob downloads in flight while listing a collection. Default 32. */\n readConcurrency?: number;\n /** Bounds retry of transient (5xx/429/network) Blob failures on reads and writes. */\n retry?: BlobRetryOptions;\n /** Millisecond clock used to bound total retry time. */\n now?: MillisecondClock;\n}\n\n/**\n * Store driver on Vercel Blob: durable and dependency-free (one Blob store on\n * the project, no database). Inserts are create-if-absent. Updates read the latest content,\n * bypassing Blob's CDN cache, and write with an ETag precondition, so a\n * writer that lost a race gets a VersionConflictError rather than\n * overwriting the winner. Reads and writes retry with backoff on transient\n * (5xx/429/network) failures, bounded by attempts and total elapsed time;\n * once exhausted, the failure surfaces as TransientStoreError so a real\n * outage does not hang a caller retrying forever.\n */\nexport function createBlobDriver(options: BlobDriverOptions = {}): StoreDriver {\n const client = options.client ?? vercelBlobClient;\n const now = options.now ?? Date.now;\n const prefix = options.prefix ?? \"factory\";\n const readConcurrency = options.readConcurrency ?? 32;\n if (!Number.isInteger(readConcurrency) || readConcurrency < 1) {\n throw new Error(\"Blob read concurrency must be a positive integer\");\n }\n const retry = {\n attempts: options.retry?.attempts ?? 3,\n baseDelayMs: options.retry?.baseDelayMs ?? 250,\n maxTotalMs: options.retry?.maxTotalMs ?? 5000,\n };\n let activeReads = 0;\n const readWaiters: (() => void)[] = [];\n const activeLists = new Map<CollectionName, Promise<StoredEntry[]>>();\n\n // Retries only transient failures, with exponential backoff bounded by\n // both attempt count and total elapsed time. Non-transient errors (not\n // found, already exists, precondition/version conflicts, malformed data)\n // are rethrown immediately and untouched by this loop.\n async function withTransientRetry<T>(\n collection: CollectionName,\n id: string,\n operation: () => Promise<T>,\n ): Promise<T> {\n const start = now();\n for (let attempt = 0; ; attempt += 1) {\n try {\n return await operation();\n } catch (error) {\n if (!isTransientBlobError(error)) throw error;\n const nextDelayMs = retry.baseDelayMs * 2 ** attempt;\n if (attempt >= retry.attempts - 1 || now() - start + nextDelayMs > retry.maxTotalMs) {\n throw new TransientStoreError(collection, id, error);\n }\n await new Promise((resolve) => setTimeout(resolve, nextDelayMs));\n }\n }\n }\n\n async function limitedRead(pathname: string): Promise<BlobObject | null> {\n if (activeReads < readConcurrency) activeReads += 1;\n else await new Promise<void>((resolve) => readWaiters.push(resolve));\n try {\n return await client.read({ pathname });\n } finally {\n const waiter = readWaiters.shift();\n if (waiter === undefined) activeReads -= 1;\n else waiter();\n }\n }\n\n function pathFor(collection: CollectionName, id: string): string {\n return `${prefix}/${collection}/${id}.json`;\n }\n\n async function readRecord(\n collection: CollectionName,\n id: string,\n ): Promise<{ value: unknown; version: number; etag: string } | null> {\n let object: BlobObject | null;\n try {\n object = await withTransientRetry(collection, id, () => limitedRead(pathFor(collection, id)));\n } catch (error) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n if (object === null) {\n return null;\n }\n const parsed = JSON.parse(object.body) as { value: unknown; version: number };\n if (typeof parsed.version !== \"number\") {\n throw new Error(`malformed stored record at ${pathFor(collection, id)}`);\n }\n return { value: parsed.value, version: parsed.version, etag: object.etag };\n }\n\n // A write that fails with a transient error (network drop, 5xx) may still\n // have landed on the server before the response reached us: the retry\n // withTransientRetry just took then legitimately collides with our own\n // prior success. These two helpers disambiguate that case from a real\n // conflict with another writer by reading the record back and comparing\n // it to what this call tried to write; only called after this call itself\n // retried (a fresh, non-retried collision is always a real conflict).\n async function wroteThisInsert(\n collection: CollectionName,\n id: string,\n value: unknown,\n ): Promise<boolean> {\n const after = await readRecord(collection, id);\n return after !== null && after.version === 1 && jsonValuesEqual(after.value, value);\n }\n\n async function wroteThisUpdate(\n collection: CollectionName,\n id: string,\n value: unknown,\n expectedNewVersion: number,\n ): Promise<boolean> {\n const after = await readRecord(collection, id);\n return (\n after !== null && after.version === expectedNewVersion && jsonValuesEqual(after.value, value)\n );\n }\n\n return {\n name: \"blob\",\n\n async get(collection, id) {\n const record = await readRecord(collection, id);\n return record === null ? null : { value: record.value, version: record.version };\n },\n\n async insert({ collection, id, value }) {\n // Tracks whether this call itself retried: a write that fails with a\n // transient error can still have landed on the server before the\n // response was lost, so a *retried* write's own \"already exists\"\n // collision is ambiguous rather than automatically a real conflict.\n let attempts = 0;\n try {\n await withTransientRetry(collection, id, () => {\n attempts += 1;\n return client.write({\n pathname: pathFor(collection, id),\n body: JSON.stringify({ value, version: 1 }),\n overwrite: false,\n });\n });\n } catch (error) {\n if (isAlreadyExistsError(error)) {\n if (attempts > 1 && (await wroteThisInsert(collection, id, value))) {\n return;\n }\n throw new RecordExistsError(collection, id);\n }\n throw error;\n }\n },\n\n async update({ collection, id, value, expectedVersion }) {\n const existing = await readRecord(collection, id);\n if (existing === null) {\n throw new RecordNotFoundError(collection, id);\n }\n if (existing.version !== expectedVersion) {\n throw new VersionConflictError(collection, id, expectedVersion);\n }\n // Tracks whether this call itself retried: see the matching comment on\n // insert() above. A retried update's own precondition failure is\n // ambiguous in the same way.\n let attempts = 0;\n try {\n await withTransientRetry(collection, id, () => {\n attempts += 1;\n return client.write({\n pathname: pathFor(collection, id),\n body: JSON.stringify({ value, version: expectedVersion + 1 }),\n overwrite: true,\n // The write API's ifMatch rejects the weak form (W/\"…\") that reads return.\n ifMatch: existing.etag.replace(/^W\\//u, \"\"),\n });\n });\n } catch (error) {\n if (isPreconditionError(error)) {\n if (attempts > 1 && (await wroteThisUpdate(collection, id, value, expectedVersion + 1))) {\n return;\n }\n throw new VersionConflictError(collection, id, expectedVersion);\n }\n throw error;\n }\n },\n\n async list(collection) {\n const active = activeLists.get(collection);\n if (active !== undefined) return active;\n const listing = (async () => {\n const dir = `${prefix}/${collection}/`;\n const paths = await client.listPaths({ prefix: dir });\n const entries: (StoredEntry | null)[] = new Array(paths.length);\n let next = 0;\n let failure: unknown;\n const workers = Array.from(\n { length: Math.min(readConcurrency, paths.length) },\n async () => {\n while (next < paths.length && failure === undefined) {\n const index = next++;\n const pathname = paths[index]!;\n const id = pathname.slice(dir.length).replace(/\\.json$/u, \"\");\n try {\n const record = await readRecord(collection, id);\n entries[index] =\n record === null\n ? null\n : { id, record: { value: record.value, version: record.version } };\n } catch (error) {\n failure ??= error;\n }\n }\n },\n );\n await Promise.all(workers);\n if (failure !== undefined) throw failure;\n return entries.filter((entry): entry is StoredEntry => entry !== null);\n })();\n activeLists.set(collection, listing);\n try {\n return await listing;\n } finally {\n if (activeLists.get(collection) === listing) activeLists.delete(collection);\n }\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ import { FactoryId, TaskId } from "./schema/id.mjs";
2
+ import { Task } from "./schema/task.mjs";
3
+ import { DateClock } from "./schema/common.mjs";
4
+ import { BudgetLimits, BudgetMonth } from "./schema/budget.mjs";
5
+ import { FactoryStores } from "./store/engine.mjs";
6
+ //#region src/budget.d.ts
7
+ /**
8
+ * Stable factory identity for budget rows. Nothing mints factory ids yet, so
9
+ * the app derives one from a deployment-stable seed such as the Vercel
10
+ * project id.
11
+ */
12
+ declare function deriveFactoryId(seed: string): FactoryId;
13
+ /** The budget month ("YYYY-MM", UTC) a timestamp falls into. */
14
+ declare function budgetMonthOf(isoDateTime: string): BudgetMonth;
15
+ /**
16
+ * Monthly spend is derived from task rows, never stored: each task counts its
17
+ * settled cost once that exists, otherwise its held reservation. A task
18
+ * settles once, so a settled row never also counts its reservation.
19
+ * Attribution uses the reservation time so tasks that wait across a month
20
+ * boundary charge the month they actually ran in.
21
+ */
22
+ declare function computeMonthSpendUsd(tasks: readonly Task[], month: BudgetMonth): number;
23
+ /** Derived monthly capacity state before another per-Task reservation. */
24
+ type BudgetStatus = "ok" | "warning" | "exhausted";
25
+ /**
26
+ * True when one more per-task slice fits under the monthly total. Dispatch
27
+ * refuses on this exact predicate and budgetStatus reports "exhausted" on its
28
+ * negation, so refusing and reporting can never disagree.
29
+ */
30
+ declare function canReserve(spendUsd: number, limits: Pick<BudgetLimits, "totalUsd" | "perTaskReservationUsd">): boolean;
31
+ /** Classify monthly spend using the same exact-fit predicate enforced by dispatch. */
32
+ declare function budgetStatus(spendUsd: number, limits: Pick<BudgetLimits, "totalUsd" | "perTaskReservationUsd" | "warningPercent">): BudgetStatus;
33
+ /** Grace period, clock, and optional snapshot for terminal usage settlement. */
34
+ interface SweepSettlementsOptions {
35
+ /** How long a terminal task waits for late usage before the sweep settles it. */
36
+ graceMinutes?: number;
37
+ now?: DateClock;
38
+ /** A dispatcher-wide snapshot avoids another complete task read. */
39
+ tasks?: readonly Task[];
40
+ }
41
+ /** Task-local charges newly settled by one reconciliation pass. */
42
+ interface SweepSettlementsResult {
43
+ readonly settled: readonly {
44
+ readonly taskId: TaskId;
45
+ readonly totalUsd: number;
46
+ }[];
47
+ }
48
+ /** Task capability required to settle terminal reservations. */
49
+ type SettlementSweepStores = Pick<FactoryStores, "tasks">;
50
+ /**
51
+ * Settle reserved terminal tasks the session hook did not: a task that ended
52
+ * after its session (a change verified later) or whose session died. Settles
53
+ * at the recorded usage, or at zero with a warning reason when none was
54
+ * recorded within the grace window.
55
+ */
56
+ declare function sweepSettlements(stores: SettlementSweepStores, options?: SweepSettlementsOptions): Promise<SweepSettlementsResult>;
57
+ /** Owner-only notification emitted when monthly spend crosses a budget threshold. */
58
+ interface BudgetNotice {
59
+ outcome: "budget_warning" | "budget_exhausted";
60
+ month: BudgetMonth;
61
+ spendUsd: number;
62
+ limits: BudgetLimits;
63
+ }
64
+ /** Monthly limits, delivery hook, and optional snapshot used for budget reconciliation. */
65
+ interface BudgetSweepOptions {
66
+ factoryId: FactoryId;
67
+ limits: BudgetLimits;
68
+ /** Defaults to the current UTC month. */
69
+ month?: BudgetMonth;
70
+ now?: DateClock;
71
+ /** Owner-only delivery; invoked at most once per month per outcome. */
72
+ notify?: (notice: BudgetNotice) => Promise<void>;
73
+ /** A dispatcher-wide snapshot avoids another complete task read. */
74
+ tasks?: readonly Task[];
75
+ }
76
+ /**
77
+ * Derived monthly spend and exact notice outcome produced by budget reconciliation.
78
+ * `stamped` means this sweep recorded the threshold, `already_stamped` means no
79
+ * new stamp was needed, and `delivery_failed` remains eligible for retry.
80
+ */
81
+ type BudgetSweepResult = {
82
+ readonly month: BudgetMonth;
83
+ readonly spendUsd: number;
84
+ readonly status: "ok";
85
+ readonly noticeState: "not_applicable";
86
+ } | {
87
+ readonly month: BudgetMonth;
88
+ readonly spendUsd: number;
89
+ readonly status: "warning";
90
+ readonly noticeState: "stamped" | "already_stamped" | "delivery_failed";
91
+ } | {
92
+ readonly month: BudgetMonth;
93
+ readonly spendUsd: number;
94
+ readonly status: "exhausted";
95
+ readonly noticeState: "stamped" | "already_stamped" | "delivery_failed";
96
+ };
97
+ /** Budget and Task capabilities required to reconcile monthly budget thresholds. */
98
+ type BudgetSweepStores = Pick<FactoryStores, "budgets" | "tasks">;
99
+ /**
100
+ * Ensure the month's budget row, derive spend, and record threshold crossings
101
+ * write-once. Delivery comes first and the stamp only lands after it, so a
102
+ * failed notification retries on the next sweep instead of being lost; the
103
+ * write-once stamp still keeps each threshold to one message per month.
104
+ */
105
+ declare function sweepBudget(stores: BudgetSweepStores, options: BudgetSweepOptions): Promise<BudgetSweepResult>;
106
+ //#endregion
107
+ export { BudgetNotice, BudgetStatus, BudgetSweepOptions, BudgetSweepResult, BudgetSweepStores, SettlementSweepStores, SweepSettlementsOptions, SweepSettlementsResult, budgetMonthOf, budgetStatus, canReserve, computeMonthSpendUsd, deriveFactoryId, sweepBudget, sweepSettlements };
108
+ //# sourceMappingURL=budget.d.mts.map
@@ -0,0 +1,141 @@
1
+ import { factoryIdSchema } from "./schema/id.mjs";
2
+ import { budgetMonthSchema } from "./schema/budget.mjs";
3
+ import { createHash } from "node:crypto";
4
+ //#region src/budget.ts
5
+ /**
6
+ * Stable factory identity for budget rows. Nothing mints factory ids yet, so
7
+ * the app derives one from a deployment-stable seed such as the Vercel
8
+ * project id.
9
+ */
10
+ function deriveFactoryId(seed) {
11
+ return factoryIdSchema.parse(`fac_${createHash("sha256").update(seed).digest("hex").slice(0, 32)}`);
12
+ }
13
+ /** The budget month ("YYYY-MM", UTC) a timestamp falls into. */
14
+ function budgetMonthOf(isoDateTime) {
15
+ return budgetMonthSchema.parse(isoDateTime.slice(0, 7));
16
+ }
17
+ function microsOf(usd) {
18
+ return Math.round(usd * 1e6);
19
+ }
20
+ /**
21
+ * Monthly spend is derived from task rows, never stored: each task counts its
22
+ * settled cost once that exists, otherwise its held reservation. A task
23
+ * settles once, so a settled row never also counts its reservation.
24
+ * Attribution uses the reservation time so tasks that wait across a month
25
+ * boundary charge the month they actually ran in.
26
+ */
27
+ function computeMonthSpendUsd(tasks, month) {
28
+ let spendMicros = 0;
29
+ for (const task of tasks) {
30
+ if (budgetMonthOf(task.reservedAt ?? task.createdAt) !== month) continue;
31
+ spendMicros += microsOf(task.settledUsd ?? task.reservedUsd ?? 0);
32
+ }
33
+ return spendMicros / 1e6;
34
+ }
35
+ /**
36
+ * True when one more per-task slice fits under the monthly total. Dispatch
37
+ * refuses on this exact predicate and budgetStatus reports "exhausted" on its
38
+ * negation, so refusing and reporting can never disagree.
39
+ */
40
+ function canReserve(spendUsd, limits) {
41
+ return microsOf(spendUsd) + microsOf(limits.perTaskReservationUsd) <= microsOf(limits.totalUsd);
42
+ }
43
+ /** Classify monthly spend using the same exact-fit predicate enforced by dispatch. */
44
+ function budgetStatus(spendUsd, limits) {
45
+ if (!canReserve(spendUsd, limits)) return "exhausted";
46
+ if (microsOf(spendUsd) * 100 >= microsOf(limits.totalUsd) * limits.warningPercent) return "warning";
47
+ return "ok";
48
+ }
49
+ const TERMINAL_TASK_STATES = /* @__PURE__ */ new Set([
50
+ "succeeded",
51
+ "failed",
52
+ "cancelled"
53
+ ]);
54
+ /**
55
+ * Settle reserved terminal tasks the session hook did not: a task that ended
56
+ * after its session (a change verified later) or whose session died. Settles
57
+ * at the recorded usage, or at zero with a warning reason when none was
58
+ * recorded within the grace window.
59
+ */
60
+ async function sweepSettlements(stores, options = {}) {
61
+ const graceMs = (options.graceMinutes ?? 10) * 6e4;
62
+ const nowMs = (options.now ?? (() => /* @__PURE__ */ new Date()))().getTime();
63
+ const settled = [];
64
+ for (const task of options.tasks ?? await stores.tasks.list()) {
65
+ if (task.reservedUsd === void 0 || task.settledUsd !== void 0) continue;
66
+ if (!TERMINAL_TASK_STATES.has(task.state)) continue;
67
+ if (nowMs - Date.parse(task.updatedAt) < graceMs) continue;
68
+ const totalUsd = task.usage?.costUsd ?? 0;
69
+ try {
70
+ const result = await stores.tasks.settle(task.id, {
71
+ cost: { totalUsd },
72
+ reason: task.usage === void 0 ? "usage missing after ten minutes; settled at zero" : "measured usage settled by sweep"
73
+ });
74
+ settled.push({
75
+ taskId: result.id,
76
+ totalUsd
77
+ });
78
+ } catch {
79
+ continue;
80
+ }
81
+ }
82
+ return { settled };
83
+ }
84
+ /**
85
+ * Ensure the month's budget row, derive spend, and record threshold crossings
86
+ * write-once. Delivery comes first and the stamp only lands after it, so a
87
+ * failed notification retries on the next sweep instead of being lost; the
88
+ * write-once stamp still keeps each threshold to one message per month.
89
+ */
90
+ async function sweepBudget(stores, options) {
91
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
92
+ const month = options.month ?? budgetMonthOf(now().toISOString());
93
+ const budget = await stores.budgets.ensure({
94
+ factoryId: options.factoryId,
95
+ month,
96
+ limits: options.limits
97
+ });
98
+ const spendUsd = computeMonthSpendUsd(options.tasks ?? await stores.tasks.list(), month);
99
+ const status = budgetStatus(spendUsd, options.limits);
100
+ async function crossThreshold(notice, outcome) {
101
+ const field = notice === "warning" ? "warningAt" : "exhaustedAt";
102
+ if (budget.notices[field] !== void 0) return "already_stamped";
103
+ if (options.notify !== void 0) try {
104
+ await options.notify({
105
+ outcome,
106
+ month,
107
+ spendUsd,
108
+ limits: options.limits
109
+ });
110
+ } catch {
111
+ return "delivery_failed";
112
+ }
113
+ return (await stores.budgets.stampNotice(month, notice)).stamped ? "stamped" : "already_stamped";
114
+ }
115
+ if (status === "exhausted") {
116
+ const noticeState = await crossThreshold("exhausted", "budget_exhausted");
117
+ if (budget.notices.warningAt === void 0) await stores.budgets.stampNotice(month, "warning");
118
+ return {
119
+ month,
120
+ spendUsd,
121
+ status,
122
+ noticeState
123
+ };
124
+ }
125
+ if (status === "warning") return {
126
+ month,
127
+ spendUsd,
128
+ status,
129
+ noticeState: await crossThreshold("warning", "budget_warning")
130
+ };
131
+ return {
132
+ month,
133
+ spendUsd,
134
+ status,
135
+ noticeState: "not_applicable"
136
+ };
137
+ }
138
+ //#endregion
139
+ export { budgetMonthOf, budgetStatus, canReserve, computeMonthSpendUsd, deriveFactoryId, sweepBudget, sweepSettlements };
140
+
141
+ //# sourceMappingURL=budget.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.mjs","names":[],"sources":["../src/budget.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { DateClock } from \"./schema/common\";\nimport { factoryIdSchema, type FactoryId, type TaskId } from \"./schema/id\";\nimport { budgetMonthSchema, type BudgetLimits, type BudgetMonth } from \"./schema/budget\";\nimport type { Task } from \"./schema/task\";\nimport type { FactoryStores } from \"./store/engine\";\n\n/**\n * Stable factory identity for budget rows. Nothing mints factory ids yet, so\n * the app derives one from a deployment-stable seed such as the Vercel\n * project id.\n */\nexport function deriveFactoryId(seed: string): FactoryId {\n return factoryIdSchema.parse(\n `fac_${createHash(\"sha256\").update(seed).digest(\"hex\").slice(0, 32)}`,\n );\n}\n\n/** The budget month (\"YYYY-MM\", UTC) a timestamp falls into. */\nexport function budgetMonthOf(isoDateTime: string): BudgetMonth {\n return budgetMonthSchema.parse(isoDateTime.slice(0, 7));\n}\n\n// Currency math runs in integer micro-dollars so decimal limits compare\n// exactly; 0.1 + 0.2 must never refuse an exact-fit reservation.\nfunction microsOf(usd: number): number {\n return Math.round(usd * 1_000_000);\n}\n\n/**\n * Monthly spend is derived from task rows, never stored: each task counts its\n * settled cost once that exists, otherwise its held reservation. A task\n * settles once, so a settled row never also counts its reservation.\n * Attribution uses the reservation time so tasks that wait across a month\n * boundary charge the month they actually ran in.\n */\nexport function computeMonthSpendUsd(tasks: readonly Task[], month: BudgetMonth): number {\n let spendMicros = 0;\n for (const task of tasks) {\n if (budgetMonthOf(task.reservedAt ?? task.createdAt) !== month) {\n continue;\n }\n spendMicros += microsOf(task.settledUsd ?? task.reservedUsd ?? 0);\n }\n return spendMicros / 1_000_000;\n}\n\n/** Derived monthly capacity state before another per-Task reservation. */\nexport type BudgetStatus = \"ok\" | \"warning\" | \"exhausted\";\n\n/**\n * True when one more per-task slice fits under the monthly total. Dispatch\n * refuses on this exact predicate and budgetStatus reports \"exhausted\" on its\n * negation, so refusing and reporting can never disagree.\n */\nexport function canReserve(\n spendUsd: number,\n limits: Pick<BudgetLimits, \"totalUsd\" | \"perTaskReservationUsd\">,\n): boolean {\n return microsOf(spendUsd) + microsOf(limits.perTaskReservationUsd) <= microsOf(limits.totalUsd);\n}\n\n/** Classify monthly spend using the same exact-fit predicate enforced by dispatch. */\nexport function budgetStatus(\n spendUsd: number,\n limits: Pick<BudgetLimits, \"totalUsd\" | \"perTaskReservationUsd\" | \"warningPercent\">,\n): BudgetStatus {\n if (!canReserve(spendUsd, limits)) {\n return \"exhausted\";\n }\n // Integer comparison: spend/total * 100 >= warningPercent without float\n // drift on decimal limits.\n if (microsOf(spendUsd) * 100 >= microsOf(limits.totalUsd) * limits.warningPercent) {\n return \"warning\";\n }\n return \"ok\";\n}\n\nconst TERMINAL_TASK_STATES = new Set<Task[\"state\"]>([\"succeeded\", \"failed\", \"cancelled\"]);\n\n/** Grace period, clock, and optional snapshot for terminal usage settlement. */\nexport interface SweepSettlementsOptions {\n /** How long a terminal task waits for late usage before the sweep settles it. */\n graceMinutes?: number;\n now?: DateClock;\n /** A dispatcher-wide snapshot avoids another complete task read. */\n tasks?: readonly Task[];\n}\n\n/** Task-local charges newly settled by one reconciliation pass. */\nexport interface SweepSettlementsResult {\n readonly settled: readonly { readonly taskId: TaskId; readonly totalUsd: number }[];\n}\n\n/** Task capability required to settle terminal reservations. */\nexport type SettlementSweepStores = Pick<FactoryStores, \"tasks\">;\n\n/**\n * Settle reserved terminal tasks the session hook did not: a task that ended\n * after its session (a change verified later) or whose session died. Settles\n * at the recorded usage, or at zero with a warning reason when none was\n * recorded within the grace window.\n */\nexport async function sweepSettlements(\n stores: SettlementSweepStores,\n options: SweepSettlementsOptions = {},\n): Promise<SweepSettlementsResult> {\n const graceMs = (options.graceMinutes ?? 10) * 60_000;\n const nowMs = (options.now ?? (() => new Date()))().getTime();\n const settled: { taskId: TaskId; totalUsd: number }[] = [];\n for (const task of options.tasks ?? (await stores.tasks.list())) {\n if (task.reservedUsd === undefined || task.settledUsd !== undefined) {\n continue;\n }\n if (!TERMINAL_TASK_STATES.has(task.state)) {\n continue;\n }\n if (nowMs - Date.parse(task.updatedAt) < graceMs) {\n continue;\n }\n // A concurrent writer (operator retry, another sweep) may move the task\n // between the list and the settle; skip it rather than aborting the pass.\n const totalUsd = task.usage?.costUsd ?? 0;\n try {\n const result = await stores.tasks.settle(task.id, {\n cost: { totalUsd },\n ...{\n reason:\n task.usage === undefined\n ? \"usage missing after ten minutes; settled at zero\"\n : \"measured usage settled by sweep\",\n },\n });\n // Report this settlement's amount, not the task's accumulated total.\n settled.push({ taskId: result.id, totalUsd });\n } catch {\n continue;\n }\n }\n return { settled };\n}\n\n/** Owner-only notification emitted when monthly spend crosses a budget threshold. */\nexport interface BudgetNotice {\n outcome: \"budget_warning\" | \"budget_exhausted\";\n month: BudgetMonth;\n spendUsd: number;\n limits: BudgetLimits;\n}\n\n/** Monthly limits, delivery hook, and optional snapshot used for budget reconciliation. */\nexport interface BudgetSweepOptions {\n factoryId: FactoryId;\n limits: BudgetLimits;\n /** Defaults to the current UTC month. */\n month?: BudgetMonth;\n now?: DateClock;\n /** Owner-only delivery; invoked at most once per month per outcome. */\n notify?: (notice: BudgetNotice) => Promise<void>;\n /** A dispatcher-wide snapshot avoids another complete task read. */\n tasks?: readonly Task[];\n}\n\n/**\n * Derived monthly spend and exact notice outcome produced by budget reconciliation.\n * `stamped` means this sweep recorded the threshold, `already_stamped` means no\n * new stamp was needed, and `delivery_failed` remains eligible for retry.\n */\nexport type BudgetSweepResult =\n | {\n readonly month: BudgetMonth;\n readonly spendUsd: number;\n readonly status: \"ok\";\n readonly noticeState: \"not_applicable\";\n }\n | {\n readonly month: BudgetMonth;\n readonly spendUsd: number;\n readonly status: \"warning\";\n readonly noticeState: \"stamped\" | \"already_stamped\" | \"delivery_failed\";\n }\n | {\n readonly month: BudgetMonth;\n readonly spendUsd: number;\n readonly status: \"exhausted\";\n readonly noticeState: \"stamped\" | \"already_stamped\" | \"delivery_failed\";\n };\n\n/** Budget and Task capabilities required to reconcile monthly budget thresholds. */\nexport type BudgetSweepStores = Pick<FactoryStores, \"budgets\" | \"tasks\">;\n\n/**\n * Ensure the month's budget row, derive spend, and record threshold crossings\n * write-once. Delivery comes first and the stamp only lands after it, so a\n * failed notification retries on the next sweep instead of being lost; the\n * write-once stamp still keeps each threshold to one message per month.\n */\nexport async function sweepBudget(\n stores: BudgetSweepStores,\n options: BudgetSweepOptions,\n): Promise<BudgetSweepResult> {\n const now = options.now ?? (() => new Date());\n const month = options.month ?? budgetMonthOf(now().toISOString());\n const budget = await stores.budgets.ensure({\n factoryId: options.factoryId,\n month,\n limits: options.limits,\n });\n const spendUsd = computeMonthSpendUsd(options.tasks ?? (await stores.tasks.list()), month);\n const status = budgetStatus(spendUsd, options.limits);\n\n async function crossThreshold(\n notice: \"warning\" | \"exhausted\",\n outcome: BudgetNotice[\"outcome\"],\n ): Promise<\"stamped\" | \"already_stamped\" | \"delivery_failed\"> {\n const field = notice === \"warning\" ? \"warningAt\" : \"exhaustedAt\";\n if (budget.notices[field] !== undefined) {\n return \"already_stamped\";\n }\n if (options.notify !== undefined) {\n try {\n await options.notify({ outcome, month, spendUsd, limits: options.limits });\n } catch {\n // Leave the notice unstamped so the next sweep retries delivery.\n return \"delivery_failed\";\n }\n }\n return (await stores.budgets.stampNotice(month, notice)).stamped\n ? \"stamped\"\n : \"already_stamped\";\n }\n\n if (status === \"exhausted\") {\n const noticeState = await crossThreshold(\"exhausted\", \"budget_exhausted\");\n // The warning threshold was necessarily crossed too; stamp it silently so\n // a later settle-down cannot deliver \"warning\" after \"exhausted\".\n if (budget.notices.warningAt === undefined) {\n await stores.budgets.stampNotice(month, \"warning\");\n }\n return { month, spendUsd, status, noticeState };\n }\n if (status === \"warning\") {\n const noticeState = await crossThreshold(\"warning\", \"budget_warning\");\n return { month, spendUsd, status, noticeState };\n }\n return { month, spendUsd, status, noticeState: \"not_applicable\" };\n}\n"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { Budget, BudgetLimits, BudgetMonth, budgetLimitsSchema, budgetMonthSchema, budgetSchema } from "./schema/budget.mjs";
2
+ import { BudgetNoticeStampResult, EnsureBudgetInput, SettleTaskInput, SettlementError } from "./store/engine.mjs";
3
+ import { BudgetNotice, BudgetStatus, BudgetSweepOptions, BudgetSweepResult, BudgetSweepStores, SettlementSweepStores, SweepSettlementsOptions, SweepSettlementsResult, budgetMonthOf, budgetStatus, canReserve, computeMonthSpendUsd, sweepBudget, sweepSettlements } from "./budget.mjs";
4
+ export { type Budget, type BudgetLimits, type BudgetMonth, type BudgetNotice, type BudgetNoticeStampResult, type BudgetStatus, type BudgetSweepOptions, type BudgetSweepResult, type BudgetSweepStores, type EnsureBudgetInput, type SettleTaskInput, SettlementError, type SettlementSweepStores, type SweepSettlementsOptions, type SweepSettlementsResult, budgetLimitsSchema, budgetMonthOf, budgetMonthSchema, budgetSchema, budgetStatus, canReserve, computeMonthSpendUsd, sweepBudget, sweepSettlements };
@@ -0,0 +1,4 @@
1
+ import { budgetLimitsSchema, budgetMonthSchema, budgetSchema } from "./schema/budget.mjs";
2
+ import { budgetMonthOf, budgetStatus, canReserve, computeMonthSpendUsd, sweepBudget, sweepSettlements } from "./budget.mjs";
3
+ import { SettlementError } from "./store/engine.mjs";
4
+ export { SettlementError, budgetLimitsSchema, budgetMonthOf, budgetMonthSchema, budgetSchema, budgetStatus, canReserve, computeMonthSpendUsd, sweepBudget, sweepSettlements };
@@ -1,12 +1,14 @@
1
- import { r as FactoryConfigInput } from "./factory-config-D2yU3G0c.mjs";
1
+ import { FactoryConfigInput } from "./schema/factory-config.mjs";
2
2
  import { EveNextConfig, EveNextConfigFunction, EveNextConfigInput } from "eve/next";
3
3
  //#region src/build-factory.d.ts
4
- interface FactoryArgs<TConfig extends EveNextConfig = EveNextConfig> {
4
+ /** Inputs for validating a Factory configuration and discovering its Eve agents at build time. */
5
+ interface BuildFactoryOptions<TConfig extends EveNextConfig = EveNextConfig> {
5
6
  factoryConfig: FactoryConfigInput;
6
7
  nextConfig: EveNextConfigInput<TConfig>;
7
8
  root?: string;
8
9
  }
9
- declare function buildFactory<TConfig extends EveNextConfig>({ factoryConfig, nextConfig, root }: FactoryArgs<TConfig>): EveNextConfigFunction<TConfig>;
10
+ /** Create an Eve Next.js configuration wrapper for the agents beneath a Factory project root. */
11
+ declare function buildFactory<TConfig extends EveNextConfig>({ factoryConfig, nextConfig, root }: BuildFactoryOptions<TConfig>): EveNextConfigFunction<TConfig>;
10
12
  //#endregion
11
- export { FactoryArgs, buildFactory };
13
+ export { BuildFactoryOptions, buildFactory };
12
14
  //# sourceMappingURL=build-factory.d.mts.map
@@ -1,7 +1,8 @@
1
- import { n as defineFactory } from "./factory-config-BJcjsPKl.mjs";
1
+ import { defineFactory } from "./schema/factory-config.mjs";
2
2
  import { readdirSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  //#region src/build-factory.ts
5
+ /** Create an Eve Next.js configuration wrapper for the agents beneath a Factory project root. */
5
6
  function buildFactory({ factoryConfig, nextConfig, root = process.cwd() }) {
6
7
  defineFactory(factoryConfig);
7
8
  const agentDefinitions = Object.fromEntries(readdirSync(path.join(root, "agents"), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => [entry.name, path.join(root, "agents", entry.name)]));
@@ -1 +1 @@
1
- {"version":3,"file":"build-factory.mjs","names":[],"sources":["../src/build-factory.ts"],"sourcesContent":["import { readdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n type EveNextConfig,\n type EveNextConfigFunction,\n type EveNextConfigInput,\n type WithEveAgentsConfig,\n} from \"eve/next\";\nimport { defineFactory, type FactoryConfigInput } from \"./schema/factory-config\";\n\nexport interface FactoryArgs<TConfig extends EveNextConfig = EveNextConfig> {\n factoryConfig: FactoryConfigInput;\n nextConfig: EveNextConfigInput<TConfig>;\n root?: string;\n}\n\nexport function buildFactory<TConfig extends EveNextConfig>({\n factoryConfig,\n nextConfig,\n root = process.cwd(),\n}: FactoryArgs<TConfig>): EveNextConfigFunction<TConfig> {\n defineFactory(factoryConfig);\n const agentDefinitions: WithEveAgentsConfig = Object.fromEntries(\n readdirSync(path.join(root, \"agents\"), { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => [entry.name, path.join(root, \"agents\", entry.name)]),\n );\n return async (phase, context) => {\n const { withEve } = await import(\"eve/next\");\n return withEve(nextConfig, { agents: agentDefinitions })(phase, context);\n };\n}\n"],"mappings":";;;;AAgBA,SAAgB,aAA4C,EAC1D,eACA,YACA,OAAO,QAAQ,IAAI,KACoC;CACvD,cAAc,aAAa;CAC3B,MAAM,mBAAwC,OAAO,YACnD,YAAY,KAAK,KAAK,MAAM,QAAQ,GAAG,EAAE,eAAe,KAAK,CAAC,CAAC,CAC5D,QAAQ,UAAU,MAAM,YAAY,CAAC,CAAC,CACtC,KAAK,UAAU,CAAC,MAAM,MAAM,KAAK,KAAK,MAAM,UAAU,MAAM,IAAI,CAAC,CAAC,CACvE;CACA,OAAO,OAAO,OAAO,YAAY;EAC/B,MAAM,EAAE,YAAY,MAAM,OAAO;EACjC,OAAO,QAAQ,YAAY,EAAE,QAAQ,iBAAiB,CAAC,CAAC,CAAC,OAAO,OAAO;CACzE;AACF"}
1
+ {"version":3,"file":"build-factory.mjs","names":[],"sources":["../src/build-factory.ts"],"sourcesContent":["import { readdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n type EveNextConfig,\n type EveNextConfigFunction,\n type EveNextConfigInput,\n type WithEveAgentsConfig,\n} from \"eve/next\";\nimport { defineFactory, type FactoryConfigInput } from \"./schema/factory-config\";\n\n/** Inputs for validating a Factory configuration and discovering its Eve agents at build time. */\nexport interface BuildFactoryOptions<TConfig extends EveNextConfig = EveNextConfig> {\n factoryConfig: FactoryConfigInput;\n nextConfig: EveNextConfigInput<TConfig>;\n root?: string;\n}\n\n/** Create an Eve Next.js configuration wrapper for the agents beneath a Factory project root. */\nexport function buildFactory<TConfig extends EveNextConfig>({\n factoryConfig,\n nextConfig,\n root = process.cwd(),\n}: BuildFactoryOptions<TConfig>): EveNextConfigFunction<TConfig> {\n defineFactory(factoryConfig);\n const agentDefinitions: WithEveAgentsConfig = Object.fromEntries(\n readdirSync(path.join(root, \"agents\"), { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => [entry.name, path.join(root, \"agents\", entry.name)]),\n );\n return async (phase, context) => {\n const { withEve } = await import(\"eve/next\");\n return withEve(nextConfig, { agents: agentDefinitions })(phase, context);\n };\n}\n"],"mappings":";;;;;AAkBA,SAAgB,aAA4C,EAC1D,eACA,YACA,OAAO,QAAQ,IAAI,KAC4C;CAC/D,cAAc,aAAa;CAC3B,MAAM,mBAAwC,OAAO,YACnD,YAAY,KAAK,KAAK,MAAM,QAAQ,GAAG,EAAE,eAAe,KAAK,CAAC,CAAC,CAC5D,QAAQ,UAAU,MAAM,YAAY,CAAC,CAAC,CACtC,KAAK,UAAU,CAAC,MAAM,MAAM,KAAK,KAAK,MAAM,UAAU,MAAM,IAAI,CAAC,CAAC,CACvE;CACA,OAAO,OAAO,OAAO,YAAY;EAC/B,MAAM,EAAE,YAAY,MAAM,OAAO;EACjC,OAAO,QAAQ,YAAY,EAAE,QAAQ,iBAAiB,CAAC,CAAC,CAAC,OAAO,OAAO;CACzE;AACF"}
@@ -0,0 +1,9 @@
1
+ import { Repository } from "../schema/repository.mjs";
2
+ import { Task } from "../schema/task.mjs";
3
+ import { Change } from "../schema/change.mjs";
4
+ //#region src/change-verification/brief.d.ts
5
+ /** Format the starting brief for independently verifying a recorded Change. */
6
+ declare function formatChangeVerificationBrief(task: Task, repository: Repository, change: Change): string;
7
+ //#endregion
8
+ export { formatChangeVerificationBrief };
9
+ //# sourceMappingURL=brief.d.mts.map
@@ -0,0 +1,13 @@
1
+ //#region src/change-verification/brief.ts
2
+ /** Format the starting brief for independently verifying a recorded Change. */
3
+ function formatChangeVerificationBrief(task, repository, change) {
4
+ return [
5
+ `You are assigned factory verification task ${task.id} in repository ${repository.slug}.`,
6
+ `Change under review: ${change.id} (${change.prUrl}).`,
7
+ "Read the task with get_task, independently inspect and test the change, then call record_verification with every evidence field."
8
+ ].join("\n");
9
+ }
10
+ //#endregion
11
+ export { formatChangeVerificationBrief };
12
+
13
+ //# sourceMappingURL=brief.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief.mjs","names":[],"sources":["../../src/change-verification/brief.ts"],"sourcesContent":["import type { Change } from \"../schema/change\";\nimport type { Repository } from \"../schema/repository\";\nimport type { Task } from \"../schema/task\";\n\n/** Format the starting brief for independently verifying a recorded Change. */\nexport function formatChangeVerificationBrief(\n task: Task,\n repository: Repository,\n change: Change,\n): string {\n return [\n `You are assigned factory verification task ${task.id} in repository ${repository.slug}.`,\n `Change under review: ${change.id} (${change.prUrl}).`,\n \"Read the task with get_task, independently inspect and test the change, then call record_verification with every evidence field.\",\n ].join(\"\\n\");\n}\n"],"mappings":";;AAKA,SAAgB,8BACd,MACA,YACA,QACQ;CACR,OAAO;EACL,8CAA8C,KAAK,GAAG,iBAAiB,WAAW,KAAK;EACvF,wBAAwB,OAAO,GAAG,IAAI,OAAO,MAAM;EACnD;CACF,CAAC,CAAC,KAAK,IAAI;AACb"}
@@ -0,0 +1,24 @@
1
+ import { TaskId } from "../schema/id.mjs";
2
+ import { AgentRouteBinding } from "../schema/agent-route.mjs";
3
+ import { Task } from "../schema/task.mjs";
4
+ import "../agent-routes.mjs";
5
+ import { FactoryStores } from "../store/engine.mjs";
6
+ //#region src/change-verification/dispatch.d.ts
7
+ /** Store capabilities required to inspect and create verification Tasks. */
8
+ type ChangeVerificationStores = Pick<FactoryStores, "tasks">;
9
+ /** Verification Tasks admitted or found idempotently by one reconciliation pass. */
10
+ interface EnsureChangeVerificationTasksResult {
11
+ readonly created: readonly TaskId[];
12
+ readonly existing: readonly TaskId[];
13
+ }
14
+ /** Stores, optional snapshot, and exact verifier route used by one admission pass. */
15
+ interface EnsureChangeVerificationTasksOptions {
16
+ readonly stores: ChangeVerificationStores;
17
+ readonly tasks?: readonly Task[];
18
+ readonly route?: AgentRouteBinding;
19
+ }
20
+ /** Supplied code-change policy: admit one verification child per Change and exact verifier binding. */
21
+ declare function ensureChangeVerificationTasks(options: EnsureChangeVerificationTasksOptions): Promise<EnsureChangeVerificationTasksResult>;
22
+ //#endregion
23
+ export { ChangeVerificationStores, EnsureChangeVerificationTasksOptions, EnsureChangeVerificationTasksResult, ensureChangeVerificationTasks };
24
+ //# sourceMappingURL=dispatch.d.mts.map
@@ -0,0 +1,45 @@
1
+ import { parseAgentRouteBinding } from "../schema/agent-route.mjs";
2
+ import { routeKey, routeTaskWork } from "../agent-routes.mjs";
3
+ //#region src/change-verification/dispatch.ts
4
+ /** Supplied code-change policy: admit one verification child per Change and exact verifier binding. */
5
+ async function ensureChangeVerificationTasks(options) {
6
+ const { stores } = options;
7
+ const route = options.route ?? parseAgentRouteBinding({
8
+ id: "verification",
9
+ version: 1
10
+ });
11
+ const tasks = options.tasks ?? await stores.tasks.list();
12
+ const existingIds = new Set(tasks.map((task) => task.id));
13
+ const result = {
14
+ created: [],
15
+ existing: []
16
+ };
17
+ const sources = tasks.filter((task) => task.work.route?.id === "code-change" && task.state === "verifying" && task.changeId !== void 0);
18
+ for (const source of sources) {
19
+ const existing = tasks.find((task) => task.work.route !== void 0 && routeKey(task.work.route) === routeKey(route) && task.parentTaskId === source.id && task.changeId === source.changeId);
20
+ if (existing !== void 0) {
21
+ result.existing.push(existing.id);
22
+ continue;
23
+ }
24
+ const verification = await stores.tasks.create({
25
+ repositoryIds: source.repositoryIds,
26
+ kind: "verification",
27
+ work: routeTaskWork({
28
+ route,
29
+ title: `Verify change ${source.changeId}`,
30
+ input: { changeId: source.changeId }
31
+ }),
32
+ origin: {},
33
+ parentTaskId: source.id,
34
+ replyTo: source.replyTo,
35
+ changeId: source.changeId,
36
+ dedupeKey: `verification:${source.id}:${source.changeId}:${routeKey(route)}`
37
+ });
38
+ (existingIds.has(verification.id) ? result.existing : result.created).push(verification.id);
39
+ }
40
+ return result;
41
+ }
42
+ //#endregion
43
+ export { ensureChangeVerificationTasks };
44
+
45
+ //# sourceMappingURL=dispatch.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.mjs","names":[],"sources":["../../src/change-verification/dispatch.ts"],"sourcesContent":["import {\n parseAgentRouteBinding,\n routeKey,\n routeTaskWork,\n type AgentRouteBinding,\n} from \"../agent-routes\";\nimport type { TaskId } from \"../schema/id\";\nimport type { Task } from \"../schema/task\";\nimport type { FactoryStores } from \"../store/engine\";\n\n/** Store capabilities required to inspect and create verification Tasks. */\nexport type ChangeVerificationStores = Pick<FactoryStores, \"tasks\">;\n\n/** Verification Tasks admitted or found idempotently by one reconciliation pass. */\nexport interface EnsureChangeVerificationTasksResult {\n readonly created: readonly TaskId[];\n readonly existing: readonly TaskId[];\n}\n\n/** Stores, optional snapshot, and exact verifier route used by one admission pass. */\nexport interface EnsureChangeVerificationTasksOptions {\n readonly stores: ChangeVerificationStores;\n readonly tasks?: readonly Task[];\n readonly route?: AgentRouteBinding;\n}\n\n/** Supplied code-change policy: admit one verification child per Change and exact verifier binding. */\nexport async function ensureChangeVerificationTasks(\n options: EnsureChangeVerificationTasksOptions,\n): Promise<EnsureChangeVerificationTasksResult> {\n const { stores } = options;\n const route = options.route ?? parseAgentRouteBinding({ id: \"verification\", version: 1 });\n const tasks = options.tasks ?? (await stores.tasks.list());\n const existingIds = new Set(tasks.map((task) => task.id));\n const result: { created: TaskId[]; existing: TaskId[] } = { created: [], existing: [] };\n const sources = tasks.filter(\n (task) =>\n task.work.route?.id === \"code-change\" &&\n task.state === \"verifying\" &&\n task.changeId !== undefined,\n );\n\n for (const source of sources) {\n const existing = tasks.find(\n (task) =>\n task.work.route !== undefined &&\n routeKey(task.work.route) === routeKey(route) &&\n task.parentTaskId === source.id &&\n task.changeId === source.changeId,\n );\n if (existing !== undefined) {\n result.existing.push(existing.id);\n continue;\n }\n const verification = await stores.tasks.create({\n repositoryIds: source.repositoryIds,\n kind: \"verification\",\n work: routeTaskWork({\n route: route,\n title: `Verify change ${source.changeId}`,\n input: {\n changeId: source.changeId!,\n },\n }),\n origin: {},\n parentTaskId: source.id,\n replyTo: source.replyTo,\n changeId: source.changeId,\n dedupeKey: `verification:${source.id}:${source.changeId}:${routeKey(route)}`,\n });\n (existingIds.has(verification.id) ? result.existing : result.created).push(verification.id);\n }\n\n return result;\n}\n"],"mappings":";;;;AA2BA,eAAsB,8BACpB,SAC8C;CAC9C,MAAM,EAAE,WAAW;CACnB,MAAM,QAAQ,QAAQ,SAAS,uBAAuB;EAAE,IAAI;EAAgB,SAAS;CAAE,CAAC;CACxF,MAAM,QAAQ,QAAQ,SAAU,MAAM,OAAO,MAAM,KAAK;CACxD,MAAM,cAAc,IAAI,IAAI,MAAM,KAAK,SAAS,KAAK,EAAE,CAAC;CACxD,MAAM,SAAoD;EAAE,SAAS,CAAC;EAAG,UAAU,CAAC;CAAE;CACtF,MAAM,UAAU,MAAM,QACnB,SACC,KAAK,KAAK,OAAO,OAAO,iBACxB,KAAK,UAAU,eACf,KAAK,aAAa,KAAA,CACtB;CAEA,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,WAAW,MAAM,MACpB,SACC,KAAK,KAAK,UAAU,KAAA,KACpB,SAAS,KAAK,KAAK,KAAK,MAAM,SAAS,KAAK,KAC5C,KAAK,iBAAiB,OAAO,MAC7B,KAAK,aAAa,OAAO,QAC7B;EACA,IAAI,aAAa,KAAA,GAAW;GAC1B,OAAO,SAAS,KAAK,SAAS,EAAE;GAChC;EACF;EACA,MAAM,eAAe,MAAM,OAAO,MAAM,OAAO;GAC7C,eAAe,OAAO;GACtB,MAAM;GACN,MAAM,cAAc;IACX;IACP,OAAO,iBAAiB,OAAO;IAC/B,OAAO,EACL,UAAU,OAAO,SACnB;GACF,CAAC;GACD,QAAQ,CAAC;GACT,cAAc,OAAO;GACrB,SAAS,OAAO;GAChB,UAAU,OAAO;GACjB,WAAW,gBAAgB,OAAO,GAAG,GAAG,OAAO,SAAS,GAAG,SAAS,KAAK;EAC3E,CAAC;EACD,CAAC,YAAY,IAAI,aAAa,EAAE,IAAI,OAAO,WAAW,OAAO,QAAA,CAAS,KAAK,aAAa,EAAE;CAC5F;CAEA,OAAO;AACT"}
@@ -0,0 +1,60 @@
1
+ import { ChangeId, TaskId } from "../schema/id.mjs";
2
+ import { ChangeEvidence, ChangeLevel, ChangeState } from "../schema/change.mjs";
3
+ import { FactoryStores } from "../store/engine.mjs";
4
+ import { ToolContext, ToolDefinition } from "eve/tools";
5
+ //#region src/change-verification/eve-tool.d.ts
6
+ /** Change and Task capabilities required by the verification-result tool. */
7
+ type RecordVerificationToolStores = Pick<FactoryStores, "changes" | "tasks">;
8
+ /** Verified outcome presented after its Task and Change updates commit. */
9
+ interface PresentVerificationResultInput {
10
+ taskId: TaskId;
11
+ changeId: ChangeId;
12
+ verdict: "pass" | "fail";
13
+ summary: string;
14
+ }
15
+ /** Configures presentation after a verification verdict has been durably recorded. */
16
+ interface RecordVerificationToolOptions {
17
+ presentResult?: (input: PresentVerificationResultInput) => Promise<void>;
18
+ }
19
+ /** Evidence and verdict supplied by the verifier for one Change. */
20
+ interface RecordVerificationInput {
21
+ taskId: TaskId;
22
+ changeId: ChangeId;
23
+ verdict: "pass" | "fail";
24
+ evidence: ChangeEvidence;
25
+ confidence: ChangeLevel;
26
+ summary: string;
27
+ }
28
+ /** Bounded refusal returned when a verification result cannot be associated safely. */
29
+ interface RecordVerificationRefusal {
30
+ readonly recorded: false;
31
+ readonly reason: string;
32
+ }
33
+ /** Durable lifecycle state returned after a verification verdict is recorded. */
34
+ interface RecordVerificationSuccess {
35
+ readonly recorded: true;
36
+ readonly verdict: "pass" | "fail";
37
+ readonly changeState: ChangeState;
38
+ readonly taskState: "succeeded" | "failed";
39
+ }
40
+ /** Recorded verification state or bounded association refusal. */
41
+ type RecordVerificationResult = RecordVerificationRefusal | RecordVerificationSuccess;
42
+ /** Eve outcome tool that records evidence and a verdict for one Change. */
43
+ interface RecordVerificationTool extends ToolDefinition<RecordVerificationInput, RecordVerificationResult> {
44
+ execute(input: RecordVerificationInput, ctx: ToolContext): Promise<RecordVerificationResult>;
45
+ }
46
+ /**
47
+ * The verifier's outcome tool. Writes evidence onto the change and moves it
48
+ * to ready only when the verdict is pass; a failed verdict leaves the change
49
+ * verifying with the evidence recorded, and the verification task itself
50
+ * ends with a receipt either way. The change's own code_change task follows
51
+ * the verdict too: succeeded on pass, failed on fail with the verifier's
52
+ * summary as the reason, so the ledger never shows a verified-and-rejected fix
53
+ * as still in progress. Fail closed: the agent cannot mark a change ready
54
+ * without supplying every evidence field. Requires the authenticated current
55
+ * Task execution and matching parent, Change, and repository associations.
56
+ */
57
+ declare function createRecordVerificationTool(stores: RecordVerificationToolStores, options?: RecordVerificationToolOptions): RecordVerificationTool;
58
+ //#endregion
59
+ export { PresentVerificationResultInput, RecordVerificationInput, RecordVerificationRefusal, RecordVerificationResult, RecordVerificationSuccess, RecordVerificationTool, RecordVerificationToolOptions, RecordVerificationToolStores, createRecordVerificationTool };
60
+ //# sourceMappingURL=eve-tool.d.mts.map