@sixb/core 0.1.5 → 0.1.7

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 (564) hide show
  1. package/dist/actions/index.js +37 -33
  2. package/dist/actions/index.js.map +1 -1
  3. package/dist/actions/types/definitions.d.ts +8 -2
  4. package/dist/actions/types/definitions.d.ts.map +1 -1
  5. package/dist/actions/worker.js +3 -3
  6. package/dist/agents/adapters.d.ts +33 -76
  7. package/dist/agents/adapters.d.ts.map +1 -1
  8. package/dist/agents/authority.d.ts +22 -6
  9. package/dist/agents/authority.d.ts.map +1 -1
  10. package/dist/agents/builders.d.ts +3 -11
  11. package/dist/agents/builders.d.ts.map +1 -1
  12. package/dist/agents/context.js +2 -2
  13. package/dist/agents/dispatch.d.ts +9 -1
  14. package/dist/agents/dispatch.d.ts.map +1 -1
  15. package/dist/agents/errors.d.ts +2 -2
  16. package/dist/agents/errors.d.ts.map +1 -1
  17. package/dist/agents/execution.d.ts +9 -10
  18. package/dist/agents/execution.d.ts.map +1 -1
  19. package/dist/agents/ids.d.ts +4 -0
  20. package/dist/agents/ids.d.ts.map +1 -1
  21. package/dist/agents/index.d.ts +13 -9
  22. package/dist/agents/index.d.ts.map +1 -1
  23. package/dist/agents/index.js +80 -68
  24. package/dist/agents/index.js.map +1 -1
  25. package/dist/agents/message.d.ts +17 -10
  26. package/dist/agents/message.d.ts.map +1 -1
  27. package/dist/agents/model-loop.d.ts +56 -0
  28. package/dist/agents/model-loop.d.ts.map +1 -0
  29. package/dist/agents/reference.d.ts +4 -0
  30. package/dist/agents/reference.d.ts.map +1 -0
  31. package/dist/agents/request.d.ts +10 -8
  32. package/dist/agents/request.d.ts.map +1 -1
  33. package/dist/agents/retired-config.d.ts +4 -0
  34. package/dist/agents/retired-config.d.ts.map +1 -0
  35. package/dist/agents/streams.d.ts +13 -8
  36. package/dist/agents/streams.d.ts.map +1 -1
  37. package/dist/agents/streams.js +7 -5
  38. package/dist/agents/streams.js.map +1 -1
  39. package/dist/agents/tool-catalog.d.ts +9 -0
  40. package/dist/agents/tool-catalog.d.ts.map +1 -0
  41. package/dist/agents/tool-definition.d.ts +1 -0
  42. package/dist/agents/tool-definition.d.ts.map +1 -1
  43. package/dist/agents/types.d.ts +31 -68
  44. package/dist/agents/types.d.ts.map +1 -1
  45. package/dist/agents/validation.d.ts +4 -10
  46. package/dist/agents/validation.d.ts.map +1 -1
  47. package/dist/ai-usage/execution.d.ts +53 -0
  48. package/dist/ai-usage/execution.d.ts.map +1 -0
  49. package/dist/ai-usage/index.d.ts +3 -0
  50. package/dist/ai-usage/index.d.ts.map +1 -0
  51. package/dist/auth/index.d.ts +1 -1
  52. package/dist/auth/index.d.ts.map +1 -1
  53. package/dist/auth/index.js +7 -7
  54. package/dist/auth/runtime.d.ts.map +1 -1
  55. package/dist/auth/strategy.d.ts +1 -1
  56. package/dist/auth/strategy.d.ts.map +1 -1
  57. package/dist/auth/types.d.ts +13 -0
  58. package/dist/auth/types.d.ts.map +1 -1
  59. package/dist/authorization/decision.d.ts +5 -2
  60. package/dist/authorization/decision.d.ts.map +1 -1
  61. package/dist/authorization/grant-kinds.d.ts +24 -8
  62. package/dist/authorization/grant-kinds.d.ts.map +1 -1
  63. package/dist/authorization/index.d.ts +1 -1
  64. package/dist/authorization/index.d.ts.map +1 -1
  65. package/dist/authorization/index.js +8 -6
  66. package/dist/authorization/index.js.map +1 -1
  67. package/dist/authorization/resolve.d.ts +3 -3
  68. package/dist/authorization/resolve.d.ts.map +1 -1
  69. package/dist/authorization/types.d.ts +7 -5
  70. package/dist/authorization/types.d.ts.map +1 -1
  71. package/dist/blob-storage/browser.js +2 -2
  72. package/dist/blob-storage/index.js +3 -3
  73. package/dist/bootstrap/discovery.d.ts +1 -7
  74. package/dist/bootstrap/discovery.d.ts.map +1 -1
  75. package/dist/bootstrap/index.js +52 -48
  76. package/dist/bootstrap/index.js.map +1 -1
  77. package/dist/broker/index.js +2 -2
  78. package/dist/{chunk-zqysyfjt.js → chunk-08m3b6w8.js} +1 -1
  79. package/dist/{chunk-xdf9yswn.js → chunk-08twe8m6.js} +93 -8
  80. package/dist/chunk-08twe8m6.js.map +12 -0
  81. package/dist/{chunk-xkthy56g.js → chunk-1kamz6j0.js} +1 -1
  82. package/dist/{chunk-h6h91mfy.js → chunk-2b5qtban.js} +12 -12
  83. package/dist/{chunk-h6h91mfy.js.map → chunk-2b5qtban.js.map} +3 -3
  84. package/dist/{chunk-0172wgm9.js → chunk-2xns679x.js} +10 -2
  85. package/dist/{chunk-0172wgm9.js.map → chunk-2xns679x.js.map} +3 -3
  86. package/dist/{chunk-f83ka50g.js → chunk-37wk412q.js} +86 -28
  87. package/dist/{chunk-f83ka50g.js.map → chunk-37wk412q.js.map} +8 -8
  88. package/dist/{chunk-6d5xdj1q.js → chunk-3fqt22c4.js} +4 -4
  89. package/dist/{chunk-t9xghkva.js → chunk-3kv70xwp.js} +3 -2
  90. package/dist/{chunk-t9xghkva.js.map → chunk-3kv70xwp.js.map} +3 -3
  91. package/dist/{chunk-z3zp0bj1.js → chunk-407j3msh.js} +1 -1
  92. package/dist/{chunk-rhrbz7d4.js → chunk-4a849fr1.js} +1 -1
  93. package/dist/{chunk-62c7t4jc.js → chunk-4nxeacx7.js} +1 -1
  94. package/dist/{chunk-cy0n5dms.js → chunk-5jcdmjam.js} +1 -1
  95. package/dist/{chunk-819wnjz8.js → chunk-64sr682n.js} +7 -7
  96. package/dist/{chunk-v0egge8b.js → chunk-6ed5bwnd.js} +666 -81
  97. package/dist/chunk-6ed5bwnd.js.map +57 -0
  98. package/dist/{chunk-x2yw73hy.js → chunk-7ds9620f.js} +1 -1
  99. package/dist/{chunk-dm72y8wt.js → chunk-7y0f6rf1.js} +29 -16
  100. package/dist/chunk-7y0f6rf1.js.map +10 -0
  101. package/dist/{chunk-72q2y8hn.js → chunk-8tsf6dmc.js} +2 -2
  102. package/dist/{chunk-72q2y8hn.js.map → chunk-8tsf6dmc.js.map} +3 -3
  103. package/dist/{chunk-tbv03sx2.js → chunk-9aehf39w.js} +7 -7
  104. package/dist/{chunk-ezake7g1.js → chunk-agsvg9ar.js} +1 -1
  105. package/dist/{chunk-rdeagz6b.js → chunk-atmkaf60.js} +1 -1
  106. package/dist/{chunk-rdeagz6b.js.map → chunk-atmkaf60.js.map} +2 -2
  107. package/dist/chunk-c15dr03f.js +43 -0
  108. package/dist/chunk-c15dr03f.js.map +10 -0
  109. package/dist/{chunk-78c6xvyr.js → chunk-d2ndstdf.js} +8 -6
  110. package/dist/{chunk-78c6xvyr.js.map → chunk-d2ndstdf.js.map} +2 -2
  111. package/dist/{chunk-rnshztfq.js → chunk-d63tybny.js} +2 -2
  112. package/dist/{chunk-t0jjkep3.js → chunk-d8qkxt2v.js} +4 -2
  113. package/dist/chunk-d8qkxt2v.js.map +10 -0
  114. package/dist/{chunk-vybf6pkp.js → chunk-dbc80ad7.js} +1 -1
  115. package/dist/{chunk-4gz41why.js → chunk-dqn23610.js} +1 -1
  116. package/dist/{chunk-z3ga9q5m.js → chunk-e4rvtvpw.js} +13 -11
  117. package/dist/{chunk-z3ga9q5m.js.map → chunk-e4rvtvpw.js.map} +2 -2
  118. package/dist/{chunk-h9kxd41f.js → chunk-gbrpsx4m.js} +19 -6
  119. package/dist/chunk-gbrpsx4m.js.map +11 -0
  120. package/dist/{chunk-84h6qhmp.js → chunk-h0knpbz5.js} +468 -1236
  121. package/dist/chunk-h0knpbz5.js.map +38 -0
  122. package/dist/{chunk-zp2v2rv6.js → chunk-ha58tjtt.js} +6 -6
  123. package/dist/{chunk-f6erf2vv.js → chunk-hdz94br2.js} +2 -2
  124. package/dist/chunk-jecn6t54.js +307 -0
  125. package/dist/chunk-jecn6t54.js.map +12 -0
  126. package/dist/{chunk-dsrkgtnh.js → chunk-jq43kdvk.js} +1 -1
  127. package/dist/{chunk-wtmzkcht.js → chunk-k34z5rvh.js} +1 -1
  128. package/dist/{chunk-htq0qh7z.js → chunk-ka3a2ft3.js} +2 -2
  129. package/dist/{chunk-4h947jd8.js → chunk-kt81c11h.js} +2 -2
  130. package/dist/chunk-n2arzn6e.js +1745 -0
  131. package/dist/chunk-n2arzn6e.js.map +20 -0
  132. package/dist/{chunk-f2rt4vkh.js → chunk-n2w475r0.js} +3 -3
  133. package/dist/{chunk-t8t39h7v.js → chunk-n6jxh8sw.js} +10 -2
  134. package/dist/chunk-n6jxh8sw.js.map +10 -0
  135. package/dist/{chunk-drjqm1aa.js → chunk-p03r0h5s.js} +310 -28
  136. package/dist/chunk-p03r0h5s.js.map +16 -0
  137. package/dist/chunk-p2vqaxkc.js +528 -0
  138. package/dist/chunk-p2vqaxkc.js.map +15 -0
  139. package/dist/{chunk-gadsm794.js → chunk-qjycjdj6.js} +84 -11
  140. package/dist/chunk-qjycjdj6.js.map +12 -0
  141. package/dist/chunk-qjzp9xyb.js +286 -0
  142. package/dist/chunk-qjzp9xyb.js.map +12 -0
  143. package/dist/{chunk-am6eczah.js → chunk-qv38twt8.js} +50 -69
  144. package/dist/{chunk-am6eczah.js.map → chunk-qv38twt8.js.map} +8 -8
  145. package/dist/{chunk-bhxaq2nn.js → chunk-qw9gvjv5.js} +1 -1
  146. package/dist/{chunk-r0rry8dn.js → chunk-rp1mvzb4.js} +6 -6
  147. package/dist/{chunk-tkdscva6.js → chunk-rre42skh.js} +187 -18
  148. package/dist/chunk-rre42skh.js.map +39 -0
  149. package/dist/{chunk-ghteq96b.js → chunk-s5y30bc9.js} +2 -2
  150. package/dist/{chunk-a31h57d5.js → chunk-tn7xcs2n.js} +2 -2
  151. package/dist/{chunk-6gjqgd3d.js → chunk-tpnd31c1.js} +2 -1
  152. package/dist/chunk-tpnd31c1.js.map +11 -0
  153. package/dist/chunk-trxxmgnf.js +68 -0
  154. package/dist/chunk-trxxmgnf.js.map +11 -0
  155. package/dist/{chunk-ea73pzk7.js → chunk-vjjv9mrb.js} +2 -2
  156. package/dist/{chunk-8b55ygje.js → chunk-w3e09qqk.js} +1 -1
  157. package/dist/{chunk-ckxfnx7f.js → chunk-wjgm6jjt.js} +2 -2
  158. package/dist/{chunk-3j7573mj.js → chunk-x0qhkqje.js} +2 -2
  159. package/dist/{chunk-dysw7nsm.js → chunk-xdpwpkha.js} +2 -2
  160. package/dist/{chunk-1q02e95s.js → chunk-y35ma3mj.js} +11 -16
  161. package/dist/{chunk-1q02e95s.js.map → chunk-y35ma3mj.js.map} +3 -4
  162. package/dist/{chunk-qewtks41.js → chunk-z2sery1b.js} +64 -1
  163. package/dist/{chunk-qewtks41.js.map → chunk-z2sery1b.js.map} +3 -3
  164. package/dist/error-reporting/internal.js +4 -4
  165. package/dist/error-reporting/types.d.ts +7 -2
  166. package/dist/error-reporting/types.d.ts.map +1 -1
  167. package/dist/errors/catalog.d.ts +8 -0
  168. package/dist/errors/catalog.d.ts.map +1 -1
  169. package/dist/errors/internal.js +2 -2
  170. package/dist/events/index.js +4 -4
  171. package/dist/execution/agent.d.ts +18 -9
  172. package/dist/execution/agent.d.ts.map +1 -1
  173. package/dist/execution/agent.js +52 -43
  174. package/dist/execution/agent.js.map +1 -1
  175. package/dist/execution/authorization.d.ts +14 -4
  176. package/dist/execution/authorization.d.ts.map +1 -1
  177. package/dist/execution/primitive.js +45 -43
  178. package/dist/execution/primitive.js.map +2 -2
  179. package/dist/execution/request.d.ts +3 -1
  180. package/dist/execution/request.d.ts.map +1 -1
  181. package/dist/execution/request.js +46 -44
  182. package/dist/execution/request.js.map +3 -3
  183. package/dist/execution/types.d.ts +1 -1
  184. package/dist/execution/types.d.ts.map +1 -1
  185. package/dist/http/api-routes.d.ts.map +1 -1
  186. package/dist/http/index.js +1 -1
  187. package/dist/index.d.ts +9 -6
  188. package/dist/index.d.ts.map +1 -1
  189. package/dist/index.js +82 -77
  190. package/dist/index.js.map +1 -1
  191. package/dist/json.d.ts +2 -0
  192. package/dist/json.d.ts.map +1 -1
  193. package/dist/lake-storage/index.js +5 -5
  194. package/dist/logging/index.js +3 -3
  195. package/dist/logging/types.js +2 -2
  196. package/dist/materialization/index.js +2 -2
  197. package/dist/materializer/index.js +33 -29
  198. package/dist/materializer/index.js.map +1 -1
  199. package/dist/models/catalog.d.ts +40 -0
  200. package/dist/models/catalog.d.ts.map +1 -0
  201. package/dist/models/definitions.d.ts +25 -0
  202. package/dist/models/definitions.d.ts.map +1 -0
  203. package/dist/models/errors.d.ts +52 -0
  204. package/dist/models/errors.d.ts.map +1 -0
  205. package/dist/models/events.d.ts +192 -0
  206. package/dist/models/events.d.ts.map +1 -0
  207. package/dist/models/index.d.ts +17 -39
  208. package/dist/models/index.d.ts.map +1 -1
  209. package/dist/models/index.js +50 -0
  210. package/dist/models/index.js.map +9 -0
  211. package/dist/models/language-model.d.ts +90 -0
  212. package/dist/models/language-model.d.ts.map +1 -0
  213. package/dist/models/messages.d.ts +74 -0
  214. package/dist/models/messages.d.ts.map +1 -0
  215. package/dist/models/pricing.d.ts +69 -0
  216. package/dist/models/pricing.d.ts.map +1 -0
  217. package/dist/models/rate-card.d.ts +24 -0
  218. package/dist/models/rate-card.d.ts.map +1 -0
  219. package/dist/models/tools.d.ts +24 -0
  220. package/dist/models/tools.d.ts.map +1 -0
  221. package/dist/objects/index.js +38 -34
  222. package/dist/objects/index.js.map +1 -1
  223. package/dist/objects/query/index.js +32 -28
  224. package/dist/objects/query/index.js.map +1 -1
  225. package/dist/ontology/index.js +5 -5
  226. package/dist/ontology/internal.js +4 -4
  227. package/dist/pipelines/index.js +37 -35
  228. package/dist/pipelines/index.js.map +1 -1
  229. package/dist/projections/internal.js +35 -31
  230. package/dist/projections/internal.js.map +1 -1
  231. package/dist/queues/in-memory.d.ts +3 -2
  232. package/dist/queues/in-memory.d.ts.map +1 -1
  233. package/dist/queues/index.d.ts +3 -1
  234. package/dist/queues/index.d.ts.map +1 -1
  235. package/dist/queues/index.js +1 -1
  236. package/dist/queues/types.d.ts +15 -3
  237. package/dist/queues/types.d.ts.map +1 -1
  238. package/dist/runtime/create.d.ts +4 -4
  239. package/dist/runtime/create.d.ts.map +1 -1
  240. package/dist/runtime/definitions.d.ts +2 -2
  241. package/dist/runtime/definitions.d.ts.map +1 -1
  242. package/dist/runtime/host.d.ts +3 -2
  243. package/dist/runtime/host.d.ts.map +1 -1
  244. package/dist/runtime/resolve-definitions.d.ts +2 -2
  245. package/dist/runtime/resolve-definitions.d.ts.map +1 -1
  246. package/dist/runtime/sixb.d.ts +4 -2
  247. package/dist/runtime/sixb.d.ts.map +1 -1
  248. package/dist/security/every.d.ts +1 -4
  249. package/dist/security/every.d.ts.map +1 -1
  250. package/dist/security/grants.d.ts +6 -5
  251. package/dist/security/grants.d.ts.map +1 -1
  252. package/dist/security/index.d.ts +1 -1
  253. package/dist/security/index.d.ts.map +1 -1
  254. package/dist/security/registry.d.ts +1 -1
  255. package/dist/security/registry.d.ts.map +1 -1
  256. package/dist/security/types.d.ts +13 -5
  257. package/dist/security/types.d.ts.map +1 -1
  258. package/dist/security/validation.d.ts +4 -2
  259. package/dist/security/validation.d.ts.map +1 -1
  260. package/dist/storage/action-runs/failure.js +3 -3
  261. package/dist/storage/action-runs/provider.js +2 -2
  262. package/dist/storage/agents/errors.d.ts +1 -1
  263. package/dist/storage/agents/errors.d.ts.map +1 -1
  264. package/dist/storage/agents/in-memory.d.ts +3 -2
  265. package/dist/storage/agents/in-memory.d.ts.map +1 -1
  266. package/dist/storage/agents/index.d.ts +1 -1
  267. package/dist/storage/agents/index.d.ts.map +1 -1
  268. package/dist/storage/agents/provider.d.ts +11 -4
  269. package/dist/storage/agents/provider.d.ts.map +1 -1
  270. package/dist/storage/agents/provider.js +18 -15
  271. package/dist/storage/agents/provider.js.map +1 -1
  272. package/dist/storage/agents/types.d.ts +65 -17
  273. package/dist/storage/agents/types.d.ts.map +1 -1
  274. package/dist/storage/ai-cost/analytics.d.ts +2 -1
  275. package/dist/storage/ai-cost/analytics.d.ts.map +1 -1
  276. package/dist/storage/ai-cost/codec.d.ts +3 -2
  277. package/dist/storage/ai-cost/codec.d.ts.map +1 -1
  278. package/dist/storage/ai-cost/groups.d.ts +51 -0
  279. package/dist/storage/ai-cost/groups.d.ts.map +1 -0
  280. package/dist/storage/ai-cost/in-memory.d.ts +11 -1
  281. package/dist/storage/ai-cost/in-memory.d.ts.map +1 -1
  282. package/dist/storage/ai-cost/index.d.ts +1 -1
  283. package/dist/storage/ai-cost/index.d.ts.map +1 -1
  284. package/dist/storage/ai-cost/provider.d.ts +2 -0
  285. package/dist/storage/ai-cost/provider.d.ts.map +1 -1
  286. package/dist/storage/ai-cost/provider.js +29 -13
  287. package/dist/storage/ai-cost/provider.js.map +3 -3
  288. package/dist/storage/ai-cost/types.d.ts +64 -15
  289. package/dist/storage/ai-cost/types.d.ts.map +1 -1
  290. package/dist/storage/ai-cost/validation.d.ts +2 -1
  291. package/dist/storage/ai-cost/validation.d.ts.map +1 -1
  292. package/dist/storage/ai-limits/enforcement.d.ts +14 -0
  293. package/dist/storage/ai-limits/enforcement.d.ts.map +1 -0
  294. package/dist/storage/ai-limits/enforcement.js +21 -0
  295. package/dist/storage/ai-limits/enforcement.js.map +9 -0
  296. package/dist/storage/ai-limits/errors.d.ts +8 -0
  297. package/dist/storage/ai-limits/errors.d.ts.map +1 -0
  298. package/dist/storage/ai-limits/in-memory.d.ts +78 -0
  299. package/dist/storage/ai-limits/in-memory.d.ts.map +1 -0
  300. package/dist/storage/ai-limits/index.d.ts +7 -0
  301. package/dist/storage/ai-limits/index.d.ts.map +1 -0
  302. package/dist/storage/ai-limits/period.d.ts +4 -0
  303. package/dist/storage/ai-limits/period.d.ts.map +1 -0
  304. package/dist/storage/ai-limits/provider.d.ts +85 -0
  305. package/dist/storage/ai-limits/provider.d.ts.map +1 -0
  306. package/dist/storage/ai-limits/provider.js +62 -0
  307. package/dist/storage/ai-limits/provider.js.map +9 -0
  308. package/dist/storage/ai-limits/types.d.ts +175 -0
  309. package/dist/storage/ai-limits/types.d.ts.map +1 -0
  310. package/dist/storage/ai-usage/in-memory.d.ts +4 -0
  311. package/dist/storage/ai-usage/in-memory.d.ts.map +1 -1
  312. package/dist/storage/ai-usage/record.d.ts.map +1 -1
  313. package/dist/storage/ai-usage/types.d.ts +6 -2
  314. package/dist/storage/ai-usage/types.d.ts.map +1 -1
  315. package/dist/storage/auth/errors.d.ts +1 -1
  316. package/dist/storage/auth/errors.d.ts.map +1 -1
  317. package/dist/storage/auth/in-memory/auth-storage.d.ts +4 -1
  318. package/dist/storage/auth/in-memory/auth-storage.d.ts.map +1 -1
  319. package/dist/storage/auth/in-memory/device-authorizations.d.ts +29 -0
  320. package/dist/storage/auth/in-memory/device-authorizations.d.ts.map +1 -0
  321. package/dist/storage/auth/in-memory/shared.d.ts +3 -1
  322. package/dist/storage/auth/in-memory/shared.d.ts.map +1 -1
  323. package/dist/storage/auth/index.d.ts +2 -1
  324. package/dist/storage/auth/index.d.ts.map +1 -1
  325. package/dist/storage/auth/types.d.ts +66 -0
  326. package/dist/storage/auth/types.d.ts.map +1 -1
  327. package/dist/storage/connector-connections/provider.js +2 -2
  328. package/dist/storage/executions/provider.js +1 -1
  329. package/dist/storage/executions/run-link.d.ts +7 -2
  330. package/dist/storage/executions/run-link.d.ts.map +1 -1
  331. package/dist/storage/executions/validation.d.ts.map +1 -1
  332. package/dist/storage/in-memory/index.d.ts +4 -0
  333. package/dist/storage/in-memory/index.d.ts.map +1 -1
  334. package/dist/storage/index.d.ts +8 -5
  335. package/dist/storage/index.d.ts.map +1 -1
  336. package/dist/storage/index.js +51 -38
  337. package/dist/storage/index.js.map +1 -1
  338. package/dist/storage/ontology/in-memory/materializations.d.ts +4 -3
  339. package/dist/storage/ontology/in-memory/materializations.d.ts.map +1 -1
  340. package/dist/storage/ontology/index.d.ts +1 -1
  341. package/dist/storage/ontology/index.d.ts.map +1 -1
  342. package/dist/storage/ontology/materializations.d.ts +7 -3
  343. package/dist/storage/ontology/materializations.d.ts.map +1 -1
  344. package/dist/storage/ontology/provider.d.ts +2 -2
  345. package/dist/storage/ontology/provider.d.ts.map +1 -1
  346. package/dist/storage/ontology/provider.js +3 -3
  347. package/dist/storage/pipeline-runs/provider.js +2 -2
  348. package/dist/storage/projection-runs/provider.js +4 -4
  349. package/dist/storage/sync-runs/provider.js +2 -2
  350. package/dist/storage/types.d.ts +5 -1
  351. package/dist/storage/types.d.ts.map +1 -1
  352. package/dist/storage/webhook-runs/provider.js +2 -2
  353. package/dist/storage/workflow-runs/provider.d.ts +1 -1
  354. package/dist/storage/workflow-runs/provider.js +10 -11
  355. package/dist/storage/workflow-runs/provider.js.map +1 -1
  356. package/dist/storage/workflow-runs/types.d.ts +5 -4
  357. package/dist/storage/workflow-runs/types.d.ts.map +1 -1
  358. package/dist/syncs/index.js +37 -35
  359. package/dist/syncs/index.js.map +1 -1
  360. package/dist/testing/agent-execution.d.ts +3 -1
  361. package/dist/testing/agent-execution.d.ts.map +1 -1
  362. package/dist/testing/agent-storage-contract.d.ts.map +1 -1
  363. package/dist/testing/ai-cost-storage-contract.d.ts.map +1 -1
  364. package/dist/testing/ai-limit-storage-contract.d.ts +18 -0
  365. package/dist/testing/ai-limit-storage-contract.d.ts.map +1 -0
  366. package/dist/testing/ai-model-call-groups-contract.d.ts +6 -0
  367. package/dist/testing/ai-model-call-groups-contract.d.ts.map +1 -0
  368. package/dist/testing/ai-usage-storage-contract.d.ts.map +1 -1
  369. package/dist/testing/auth-storage-contract.d.ts.map +1 -1
  370. package/dist/testing/index.d.ts +2 -0
  371. package/dist/testing/index.d.ts.map +1 -1
  372. package/dist/testing/index.js +3758 -1945
  373. package/dist/testing/index.js.map +15 -13
  374. package/dist/testing/materializer-storage-contract.d.ts.map +1 -1
  375. package/dist/testing/queues-contract.d.ts.map +1 -1
  376. package/dist/webhooks/internal.js +32 -28
  377. package/dist/webhooks/internal.js.map +2 -2
  378. package/dist/workers/index.js +2 -2
  379. package/dist/workflows/agent-step-identity.d.ts +3 -0
  380. package/dist/workflows/agent-step-identity.d.ts.map +1 -0
  381. package/dist/workflows/builders.d.ts +2 -3
  382. package/dist/workflows/builders.d.ts.map +1 -1
  383. package/dist/workflows/execution.d.ts +1 -1
  384. package/dist/workflows/execution.d.ts.map +1 -1
  385. package/dist/workflows/failure.d.ts +1 -1
  386. package/dist/workflows/failure.d.ts.map +1 -1
  387. package/dist/workflows/index.d.ts +3 -2
  388. package/dist/workflows/index.d.ts.map +1 -1
  389. package/dist/workflows/index.js +53 -45
  390. package/dist/workflows/index.js.map +1 -1
  391. package/dist/workflows/types.d.ts +26 -11
  392. package/dist/workflows/types.d.ts.map +1 -1
  393. package/dist/workflows/validation.d.ts +7 -1
  394. package/dist/workflows/validation.d.ts.map +1 -1
  395. package/package.json +19 -4
  396. package/src/actions/types/definitions.ts +24 -2
  397. package/src/agents/adapters.ts +109 -115
  398. package/src/agents/authority.ts +214 -32
  399. package/src/agents/builders.ts +3 -50
  400. package/src/agents/context-compaction.ts +4 -0
  401. package/src/agents/dispatch.ts +59 -2
  402. package/src/agents/errors.ts +5 -2
  403. package/src/agents/execution.ts +53 -52
  404. package/src/agents/ids.ts +16 -1
  405. package/src/agents/index.ts +23 -11
  406. package/src/agents/message.ts +18 -9
  407. package/src/agents/model-loop.ts +1023 -0
  408. package/src/agents/reference.ts +7 -0
  409. package/src/agents/request.ts +141 -74
  410. package/src/agents/retired-config.ts +20 -0
  411. package/src/agents/streams.ts +45 -19
  412. package/src/agents/tool-catalog.ts +21 -0
  413. package/src/agents/tool-definition.ts +23 -0
  414. package/src/agents/types.ts +39 -81
  415. package/src/agents/validation.ts +34 -180
  416. package/src/ai-usage/execution.ts +196 -0
  417. package/src/ai-usage/index.ts +15 -0
  418. package/src/auth/index.ts +1 -0
  419. package/src/auth/runtime.ts +8 -0
  420. package/src/auth/strategy.ts +1 -0
  421. package/src/auth/types.ts +14 -0
  422. package/src/authorization/decision.ts +27 -10
  423. package/src/authorization/grant-kinds.ts +62 -15
  424. package/src/authorization/index.ts +1 -0
  425. package/src/authorization/resolve.ts +12 -5
  426. package/src/authorization/types.ts +7 -5
  427. package/src/bootstrap/discovery.ts +0 -3
  428. package/src/error-reporting/types.ts +4 -2
  429. package/src/errors/catalog.ts +8 -0
  430. package/src/execution/agent.ts +129 -22
  431. package/src/execution/authorization.ts +48 -18
  432. package/src/execution/request.ts +4 -1
  433. package/src/execution/types.ts +1 -1
  434. package/src/http/api-routes.ts +63 -0
  435. package/src/index.ts +36 -7
  436. package/src/json.ts +9 -0
  437. package/src/models/catalog.ts +137 -0
  438. package/src/models/definitions.ts +202 -0
  439. package/src/models/errors.ts +83 -0
  440. package/src/models/events.ts +198 -0
  441. package/src/models/index.ts +89 -71
  442. package/src/models/language-model.ts +143 -0
  443. package/src/models/messages.ts +78 -0
  444. package/src/models/pricing.ts +390 -0
  445. package/src/models/rate-card.ts +101 -0
  446. package/src/models/tools.ts +29 -0
  447. package/src/queues/in-memory.ts +5 -0
  448. package/src/queues/index.ts +11 -0
  449. package/src/queues/types.ts +20 -3
  450. package/src/runtime/create.ts +7 -5
  451. package/src/runtime/definitions.ts +2 -2
  452. package/src/runtime/host.ts +3 -2
  453. package/src/runtime/resolve-definitions.ts +13 -36
  454. package/src/runtime/sixb.ts +6 -8
  455. package/src/security/every.ts +0 -4
  456. package/src/security/grants.ts +55 -11
  457. package/src/security/index.ts +2 -0
  458. package/src/security/registry.ts +6 -5
  459. package/src/security/types.ts +15 -4
  460. package/src/security/validation.ts +30 -9
  461. package/src/storage/agents/errors.ts +1 -0
  462. package/src/storage/agents/in-memory.ts +121 -11
  463. package/src/storage/agents/index.ts +7 -0
  464. package/src/storage/agents/provider.ts +150 -6
  465. package/src/storage/agents/types.ts +73 -16
  466. package/src/storage/ai-cost/analytics.ts +63 -6
  467. package/src/storage/ai-cost/codec.ts +37 -12
  468. package/src/storage/ai-cost/groups.ts +248 -0
  469. package/src/storage/ai-cost/in-memory.ts +42 -0
  470. package/src/storage/ai-cost/index.ts +6 -0
  471. package/src/storage/ai-cost/provider.ts +7 -0
  472. package/src/storage/ai-cost/types.ts +76 -14
  473. package/src/storage/ai-cost/validation.ts +147 -17
  474. package/src/storage/ai-limits/enforcement.ts +68 -0
  475. package/src/storage/ai-limits/errors.ts +23 -0
  476. package/src/storage/ai-limits/in-memory.ts +671 -0
  477. package/src/storage/ai-limits/index.ts +31 -0
  478. package/src/storage/ai-limits/period.ts +11 -0
  479. package/src/storage/ai-limits/provider.ts +430 -0
  480. package/src/storage/ai-limits/types.ts +195 -0
  481. package/src/storage/ai-usage/in-memory.ts +13 -0
  482. package/src/storage/ai-usage/record.ts +17 -0
  483. package/src/storage/ai-usage/types.ts +6 -2
  484. package/src/storage/auth/errors.ts +6 -0
  485. package/src/storage/auth/in-memory/auth-storage.ts +69 -0
  486. package/src/storage/auth/in-memory/device-authorizations.ts +145 -0
  487. package/src/storage/auth/in-memory/shared.ts +7 -0
  488. package/src/storage/auth/index.ts +7 -0
  489. package/src/storage/auth/types.ts +73 -0
  490. package/src/storage/executions/run-link.ts +16 -6
  491. package/src/storage/executions/validation.ts +36 -6
  492. package/src/storage/in-memory/index.ts +48 -4
  493. package/src/storage/index.ts +52 -1
  494. package/src/storage/ontology/in-memory/materializations.ts +34 -22
  495. package/src/storage/ontology/index.ts +1 -0
  496. package/src/storage/ontology/materializations.ts +8 -3
  497. package/src/storage/ontology/provider.ts +2 -2
  498. package/src/storage/types.ts +37 -0
  499. package/src/storage/workflow-runs/in-memory.ts +3 -3
  500. package/src/storage/workflow-runs/provider.ts +2 -2
  501. package/src/storage/workflow-runs/types.ts +6 -3
  502. package/src/testing/agent-execution.ts +40 -28
  503. package/src/testing/agent-storage-contract.ts +249 -28
  504. package/src/testing/ai-cost-storage-contract.ts +96 -8
  505. package/src/testing/ai-limit-storage-contract.ts +1134 -0
  506. package/src/testing/ai-model-call-groups-contract.ts +353 -0
  507. package/src/testing/ai-usage-storage-contract.ts +31 -0
  508. package/src/testing/auth-storage-contract.ts +85 -0
  509. package/src/testing/execution-storage-contract.ts +2 -2
  510. package/src/testing/index.ts +10 -0
  511. package/src/testing/materializer-storage-contract.ts +170 -2
  512. package/src/testing/queues-contract.ts +36 -1
  513. package/src/workflows/agent-step-identity.ts +4 -0
  514. package/src/workflows/builders.ts +95 -9
  515. package/src/workflows/execution.ts +2 -2
  516. package/src/workflows/failure.ts +2 -2
  517. package/src/workflows/index.ts +3 -0
  518. package/src/workflows/types.ts +23 -11
  519. package/src/workflows/validation.ts +100 -7
  520. package/dist/chunk-6gjqgd3d.js.map +0 -11
  521. package/dist/chunk-84h6qhmp.js.map +0 -46
  522. package/dist/chunk-a09jx26t.js +0 -55
  523. package/dist/chunk-a09jx26t.js.map +0 -11
  524. package/dist/chunk-dm72y8wt.js.map +0 -10
  525. package/dist/chunk-drjqm1aa.js.map +0 -15
  526. package/dist/chunk-gadsm794.js.map +0 -11
  527. package/dist/chunk-h9kxd41f.js.map +0 -11
  528. package/dist/chunk-nzw7sm3b.js +0 -137
  529. package/dist/chunk-nzw7sm3b.js.map +0 -10
  530. package/dist/chunk-t0jjkep3.js.map +0 -10
  531. package/dist/chunk-t8t39h7v.js.map +0 -10
  532. package/dist/chunk-tkdscva6.js.map +0 -37
  533. package/dist/chunk-v0egge8b.js.map +0 -56
  534. package/dist/chunk-xdf9yswn.js.map +0 -11
  535. /package/dist/{chunk-zqysyfjt.js.map → chunk-08m3b6w8.js.map} +0 -0
  536. /package/dist/{chunk-xkthy56g.js.map → chunk-1kamz6j0.js.map} +0 -0
  537. /package/dist/{chunk-6d5xdj1q.js.map → chunk-3fqt22c4.js.map} +0 -0
  538. /package/dist/{chunk-z3zp0bj1.js.map → chunk-407j3msh.js.map} +0 -0
  539. /package/dist/{chunk-rhrbz7d4.js.map → chunk-4a849fr1.js.map} +0 -0
  540. /package/dist/{chunk-62c7t4jc.js.map → chunk-4nxeacx7.js.map} +0 -0
  541. /package/dist/{chunk-cy0n5dms.js.map → chunk-5jcdmjam.js.map} +0 -0
  542. /package/dist/{chunk-819wnjz8.js.map → chunk-64sr682n.js.map} +0 -0
  543. /package/dist/{chunk-x2yw73hy.js.map → chunk-7ds9620f.js.map} +0 -0
  544. /package/dist/{chunk-tbv03sx2.js.map → chunk-9aehf39w.js.map} +0 -0
  545. /package/dist/{chunk-ezake7g1.js.map → chunk-agsvg9ar.js.map} +0 -0
  546. /package/dist/{chunk-rnshztfq.js.map → chunk-d63tybny.js.map} +0 -0
  547. /package/dist/{chunk-vybf6pkp.js.map → chunk-dbc80ad7.js.map} +0 -0
  548. /package/dist/{chunk-4gz41why.js.map → chunk-dqn23610.js.map} +0 -0
  549. /package/dist/{chunk-zp2v2rv6.js.map → chunk-ha58tjtt.js.map} +0 -0
  550. /package/dist/{chunk-f6erf2vv.js.map → chunk-hdz94br2.js.map} +0 -0
  551. /package/dist/{chunk-dsrkgtnh.js.map → chunk-jq43kdvk.js.map} +0 -0
  552. /package/dist/{chunk-wtmzkcht.js.map → chunk-k34z5rvh.js.map} +0 -0
  553. /package/dist/{chunk-htq0qh7z.js.map → chunk-ka3a2ft3.js.map} +0 -0
  554. /package/dist/{chunk-4h947jd8.js.map → chunk-kt81c11h.js.map} +0 -0
  555. /package/dist/{chunk-f2rt4vkh.js.map → chunk-n2w475r0.js.map} +0 -0
  556. /package/dist/{chunk-bhxaq2nn.js.map → chunk-qw9gvjv5.js.map} +0 -0
  557. /package/dist/{chunk-r0rry8dn.js.map → chunk-rp1mvzb4.js.map} +0 -0
  558. /package/dist/{chunk-ghteq96b.js.map → chunk-s5y30bc9.js.map} +0 -0
  559. /package/dist/{chunk-a31h57d5.js.map → chunk-tn7xcs2n.js.map} +0 -0
  560. /package/dist/{chunk-ea73pzk7.js.map → chunk-vjjv9mrb.js.map} +0 -0
  561. /package/dist/{chunk-8b55ygje.js.map → chunk-w3e09qqk.js.map} +0 -0
  562. /package/dist/{chunk-ckxfnx7f.js.map → chunk-wjgm6jjt.js.map} +0 -0
  563. /package/dist/{chunk-3j7573mj.js.map → chunk-x0qhkqje.js.map} +0 -0
  564. /package/dist/{chunk-dysw7nsm.js.map → chunk-xdpwpkha.js.map} +0 -0
@@ -0,0 +1,1134 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import type {
3
+ AiCostStorage,
4
+ AiLimitQuantity,
5
+ AiLimitStorage,
6
+ AiLimitSubject,
7
+ AiModelCallCostRecord,
8
+ AiModelCallUsageInput,
9
+ AiUsageStorage,
10
+ AuthStorage,
11
+ ExecutionStorage,
12
+ ReserveAiModelCallInput,
13
+ Storage,
14
+ } from "../storage"
15
+ import { aiLimitCalendarMonth } from "../storage"
16
+
17
+ export interface AiLimitStorageContractStorage {
18
+ readonly aiLimits: AiLimitStorage
19
+ readonly aiCosts: AiCostStorage
20
+ readonly aiUsage: AiUsageStorage
21
+ readonly auth: AuthStorage
22
+ readonly executions: ExecutionStorage
23
+ readonly transaction: Storage["transaction"]
24
+ }
25
+
26
+ export interface AiLimitStorageContractSuiteOptions<
27
+ TStorage extends AiLimitStorageContractStorage = AiLimitStorageContractStorage,
28
+ > {
29
+ readonly createStorage: () => TStorage | Promise<TStorage>
30
+ readonly setup?: (storage: TStorage) => void | Promise<void>
31
+ readonly cleanup?: (storage: TStorage) => void | Promise<void>
32
+ }
33
+
34
+ const projectId = "ai-limit-contract"
35
+ const executionId = "exec_ai_limit_1"
36
+
37
+ const project: AiLimitSubject = { type: "project" }
38
+ const support: AiLimitSubject = { type: "group", id: "support" }
39
+ const user = { type: "user" as const, id: "user-1" }
40
+
41
+ function at(value: string): Date {
42
+ return new Date(value)
43
+ }
44
+
45
+ function reservation(
46
+ callId: string,
47
+ tokens: number,
48
+ overrides: Partial<ReserveAiModelCallInput> = {}
49
+ ): ReserveAiModelCallInput {
50
+ return {
51
+ projectId,
52
+ executionId,
53
+ attempt: 1,
54
+ callId,
55
+ subjects: [support, user],
56
+ estimates: [{ meter: "tokens.total", amount: tokens }],
57
+ reservedAt: at("2026-08-15T12:00:00.000Z"),
58
+ ...overrides,
59
+ }
60
+ }
61
+
62
+ /** Run the provider-neutral AI policy, period-state, and reservation contract. */
63
+ export function runAiLimitStorageContractSuite<TStorage extends AiLimitStorageContractStorage>(
64
+ label: string,
65
+ options: AiLimitStorageContractSuiteOptions<TStorage>
66
+ ): void {
67
+ const withStorage = async (body: (storage: TStorage) => Promise<void>): Promise<void> => {
68
+ const storage = await options.createStorage()
69
+ try {
70
+ await seedAiLimitStorageContractExecution(storage.auth, storage.executions)
71
+ await options.setup?.(storage)
72
+ await body(storage)
73
+ } finally {
74
+ await options.cleanup?.(storage)
75
+ }
76
+ }
77
+
78
+ describe(label, () => {
79
+ test("uses inclusive-start, exclusive-end UTC calendar months", () => {
80
+ expect(aiLimitCalendarMonth(at("2026-12-31T23:59:59.999-05:00"))).toEqual({
81
+ kind: "calendarMonth",
82
+ start: at("2027-01-01T00:00:00.000Z"),
83
+ end: at("2027-02-01T00:00:00.000Z"),
84
+ resetAt: at("2027-02-01T00:00:00.000Z"),
85
+ })
86
+ })
87
+
88
+ test("creates, lists, edits, disables, and deletes policies without ambiguous dimensions", async () => {
89
+ await withStorage(async ({ aiLimits }) => {
90
+ const created = await aiLimits.createPolicy({
91
+ id: "project-tokens",
92
+ projectId,
93
+ subject: project,
94
+ limit: { meter: "tokens.total", amount: 100 },
95
+ createdAt: at("2026-08-10T00:00:00.000Z"),
96
+ })
97
+ expect(created).toMatchObject({ enabled: true, period: "calendarMonth" })
98
+ await expect(
99
+ aiLimits.createPolicy({
100
+ id: "duplicate-dimension",
101
+ projectId,
102
+ subject: project,
103
+ limit: { meter: "tokens.total", amount: 200 },
104
+ })
105
+ ).rejects.toMatchObject({ code: "duplicate_policy" })
106
+
107
+ await expect(
108
+ aiLimits.updatePolicy({
109
+ projectId,
110
+ id: created.id,
111
+ limit: { meter: "tokens.total", amount: 250 },
112
+ enabled: false,
113
+ updatedAt: at("2026-08-11T00:00:00.000Z"),
114
+ })
115
+ ).resolves.toMatchObject({ limit: { amount: 250 }, enabled: false })
116
+ await expect(aiLimits.listPolicies({ projectId })).resolves.toEqual([])
117
+ await expect(
118
+ aiLimits.listPolicies({ projectId, includeDisabled: true })
119
+ ).resolves.toHaveLength(1)
120
+ await expect(aiLimits.deletePolicy({ projectId, id: created.id })).resolves.toBe(true)
121
+ await expect(aiLimits.deletePolicy({ projectId, id: created.id })).resolves.toBe(false)
122
+ await expect(
123
+ aiLimits.createPolicy({
124
+ id: "cost-out-of-range",
125
+ projectId,
126
+ subject: project,
127
+ limit: {
128
+ meter: "cost.catalogEstimated",
129
+ amount: { currency: "USD", amountNanos: "9223372036854775808" },
130
+ },
131
+ })
132
+ ).rejects.toThrow("exceeds the supported range")
133
+ await expect(
134
+ aiLimits.createPolicy({
135
+ id: "cost-unsupported-currency",
136
+ projectId,
137
+ subject: { type: "group", id: "europe" },
138
+ limit: {
139
+ meter: "cost.catalogEstimated",
140
+ amount: { currency: "EUR", amountNanos: "100" },
141
+ } as unknown as AiLimitQuantity,
142
+ })
143
+ ).rejects.toThrow("catalog-estimated cost currency must be 'USD'")
144
+ })
145
+ })
146
+
147
+ test("returns every applicable exhausted policy and the earliest reset", async () => {
148
+ await withStorage(async ({ aiLimits }) => {
149
+ for (const [id, subject, amount] of [
150
+ ["project", project, 10],
151
+ ["support", support, 8],
152
+ ["user", user, 100],
153
+ ] as const) {
154
+ await aiLimits.createPolicy({
155
+ id,
156
+ projectId,
157
+ subject,
158
+ limit: { meter: "tokens.total", amount },
159
+ })
160
+ }
161
+ const result = await aiLimits.reserveModelCall(reservation("denied", 11))
162
+ expect(result).toMatchObject({
163
+ status: "denied",
164
+ exhaustedPolicies: [{ policy: { id: "project" } }, { policy: { id: "support" } }],
165
+ resetAt: at("2026-09-01T00:00:00.000Z"),
166
+ })
167
+ const statuses = await aiLimits.listPolicyStatuses({
168
+ projectId,
169
+ at: at("2026-08-20T00:00:00.000Z"),
170
+ existingGroupIds: [],
171
+ })
172
+ expect(statuses.find((status) => status.policy.id === "support")?.orphaned).toBe(true)
173
+ expect(statuses.every((status) => status.consumption.reserved.amount === 0)).toBe(true)
174
+ })
175
+ })
176
+
177
+ test("atomically prevents concurrent reservations from oversubscribing", async () => {
178
+ await withStorage(async ({ aiLimits }) => {
179
+ await aiLimits.createPolicy({
180
+ id: "project",
181
+ projectId,
182
+ subject: project,
183
+ limit: { meter: "tokens.total", amount: 10 },
184
+ })
185
+ const results = await Promise.all([
186
+ aiLimits.reserveModelCall(reservation("call-a", 6)),
187
+ aiLimits.reserveModelCall(reservation("call-b", 6)),
188
+ ])
189
+ expect(results.map((result) => result.status).sort()).toEqual(["denied", "reserved"])
190
+ const [status] = await aiLimits.listPolicyStatuses({ projectId, at: at("2026-08-20Z") })
191
+ expect(status?.consumption).toMatchObject({
192
+ actual: { amount: 0 },
193
+ reserved: { amount: 6 },
194
+ unknown: { amount: 0 },
195
+ remaining: { amount: 4 },
196
+ })
197
+ })
198
+ })
199
+
200
+ // Regression check: remove the provider's local operation lock and run this suite. Database
201
+ // locks do not serialize concurrent operations using the same transaction client.
202
+ for (const initialized of [false, true]) {
203
+ test(`serializes reservations within one transaction (initialized=${initialized})`, async () => {
204
+ await withStorage(async (storage) => {
205
+ await storage.aiLimits.createPolicy({
206
+ id: "project",
207
+ projectId,
208
+ subject: project,
209
+ limit: { meter: "tokens.total", amount: 10 },
210
+ })
211
+ if (initialized) {
212
+ await storage.aiLimits.listPolicyStatuses({ projectId, at: at("2026-08-20Z") })
213
+ }
214
+ await storage.transaction(async (tx) => {
215
+ const limits = tx.aiLimits!
216
+ const results = await Promise.all([
217
+ limits.reserveModelCall(reservation("call-a", 6)),
218
+ limits.reserveModelCall(reservation("call-b", 6)),
219
+ ])
220
+ expect(results.map((result) => result.status).sort()).toEqual(["denied", "reserved"])
221
+ const [status] = await limits.listPolicyStatuses({ projectId, at: at("2026-08-20Z") })
222
+ expect(status?.consumption.reserved.amount).toBe(6)
223
+ expect(status?.consumption.remaining.amount).toBe(4)
224
+ })
225
+ })
226
+ })
227
+ }
228
+
229
+ test("serializes reservation replays and unknown transitions within one transaction", async () => {
230
+ await withStorage(async (storage) => {
231
+ await storage.aiLimits.createPolicy({
232
+ id: "project",
233
+ projectId,
234
+ subject: project,
235
+ limit: { meter: "tokens.total", amount: 10 },
236
+ })
237
+ await storage.transaction(async (tx) => {
238
+ const limits = tx.aiLimits!
239
+ const identity = reservation("replay", 6)
240
+ const results = await Promise.all([
241
+ limits.reserveModelCall(identity),
242
+ limits.reserveModelCall(identity),
243
+ ])
244
+ expect(results).toMatchObject([
245
+ { status: "reserved", created: true },
246
+ { status: "reserved", created: false },
247
+ ])
248
+ await expect(limits.reserveModelCall(reservation("replay", 7))).rejects.toMatchObject({
249
+ code: "reservation_conflict",
250
+ })
251
+ await Promise.all([
252
+ limits.markReservationUnknown(identity),
253
+ limits.markReservationUnknown(identity),
254
+ ])
255
+ const [status] = await limits.listPolicyStatuses({ projectId, at: at("2026-08-20Z") })
256
+ expect(status?.consumption).toMatchObject({
257
+ reserved: { amount: 0 },
258
+ unknown: { amount: 6 },
259
+ remaining: { amount: 4 },
260
+ })
261
+ })
262
+ })
263
+ })
264
+
265
+ test("rolls back queued limit operations when a parallel operation rejects", async () => {
266
+ await withStorage(async (storage) => {
267
+ await storage.aiLimits.createPolicy({
268
+ id: "project",
269
+ projectId,
270
+ subject: project,
271
+ limit: { meter: "tokens.total", amount: 10 },
272
+ })
273
+ await expect(
274
+ storage.transaction(async (tx) => {
275
+ await Promise.all([
276
+ tx.aiLimits!.reserveModelCall(reservation("invalid", 1, { attempt: 0 })),
277
+ tx.aiLimits!.reserveModelCall(reservation("queued", 6)),
278
+ ])
279
+ })
280
+ ).rejects.toBeInstanceOf(TypeError)
281
+ const [status] = await storage.aiLimits.listPolicyStatuses({
282
+ projectId,
283
+ at: at("2026-08-20Z"),
284
+ })
285
+ expect(status?.consumption.reserved.amount).toBe(0)
286
+ await expect(
287
+ storage.aiLimits.reserveModelCall(reservation("queued", 6))
288
+ ).resolves.toMatchObject({
289
+ status: "reserved",
290
+ created: true,
291
+ })
292
+ })
293
+ })
294
+
295
+ test("reserves only exact subject-meter policy buckets", async () => {
296
+ await withStorage(async ({ aiLimits }) => {
297
+ await aiLimits.createPolicy({
298
+ id: "project-tokens",
299
+ projectId,
300
+ subject: project,
301
+ limit: { meter: "tokens.total", amount: 100 },
302
+ })
303
+ await aiLimits.createPolicy({
304
+ id: "user-cost",
305
+ projectId,
306
+ subject: user,
307
+ limit: {
308
+ meter: "cost.catalogEstimated",
309
+ amount: { currency: "USD", amountNanos: "100" },
310
+ },
311
+ })
312
+ const result = await aiLimits.reserveModelCall(
313
+ reservation("exact-buckets", 5, {
314
+ estimates: [
315
+ { meter: "tokens.total", amount: 5 },
316
+ {
317
+ meter: "cost.catalogEstimated",
318
+ amount: { currency: "USD", amountNanos: "7" },
319
+ },
320
+ ],
321
+ })
322
+ )
323
+ expect(result).toMatchObject({
324
+ status: "reserved",
325
+ reservation: {
326
+ buckets: [
327
+ { subject: project, estimate: { meter: "tokens.total", amount: 5 } },
328
+ {
329
+ subject: user,
330
+ estimate: {
331
+ meter: "cost.catalogEstimated",
332
+ amount: { currency: "USD", amountNanos: "7" },
333
+ },
334
+ },
335
+ ],
336
+ },
337
+ })
338
+ })
339
+ })
340
+
341
+ test("replays an identical reservation and rejects a mismatched identity replay", async () => {
342
+ await withStorage(async ({ aiLimits }) => {
343
+ await aiLimits.createPolicy({
344
+ id: "replay-policy",
345
+ projectId,
346
+ subject: project,
347
+ limit: { meter: "tokens.total", amount: 100 },
348
+ })
349
+ const first = await aiLimits.reserveModelCall(reservation("replay", 5))
350
+ const replay = await aiLimits.reserveModelCall(reservation("replay", 5))
351
+ expect(first).toMatchObject({ status: "reserved", created: true })
352
+ expect(replay).toMatchObject({ status: "reserved", created: false })
353
+ await expect(aiLimits.reserveModelCall(reservation("replay", 6))).rejects.toMatchObject({
354
+ code: "reservation_conflict",
355
+ })
356
+ })
357
+ })
358
+
359
+ test("does not authorize a terminal reservation replay", async () => {
360
+ await withStorage(async (storage) => {
361
+ const { aiLimits } = storage
362
+ await aiLimits.createPolicy({
363
+ id: "terminal-policy",
364
+ projectId,
365
+ subject: project,
366
+ limit: { meter: "tokens.total", amount: 10 },
367
+ })
368
+ const identity = reservation("terminal-replay", 5)
369
+ await aiLimits.reserveModelCall(identity)
370
+ await recordUsage(storage.aiUsage, "usage-terminal", identity.callId, 3)
371
+ await aiLimits.recordModelCallActuals({
372
+ projectId,
373
+ usageRecordId: "usage-terminal",
374
+ })
375
+ await aiLimits.reconcileModelCall({ ...identity, usageRecordId: "usage-terminal" })
376
+ await expect(aiLimits.reserveModelCall(identity)).resolves.toMatchObject({
377
+ status: "terminal",
378
+ created: false,
379
+ reservation: { state: "reconciled" },
380
+ })
381
+ })
382
+ })
383
+
384
+ test("fails closed when an applicable policy has no estimate", async () => {
385
+ await withStorage(async ({ aiLimits }) => {
386
+ await aiLimits.createPolicy({
387
+ id: "tokens",
388
+ projectId,
389
+ subject: project,
390
+ limit: { meter: "tokens.total", amount: 10 },
391
+ })
392
+ const result = await aiLimits.reserveModelCall(
393
+ reservation("missing-estimate", 1, {
394
+ estimates: [
395
+ {
396
+ meter: "cost.catalogEstimated",
397
+ amount: { currency: "USD", amountNanos: "1" },
398
+ },
399
+ ],
400
+ })
401
+ )
402
+ expect(result).toMatchObject({
403
+ status: "unavailable",
404
+ unavailablePolicies: [{ policy: { id: "tokens" } }],
405
+ reasons: ["missingEstimate"],
406
+ })
407
+ })
408
+ })
409
+
410
+ test("reconciles estimates to actual usage exactly once", async () => {
411
+ await withStorage(async (storage) => {
412
+ const { aiLimits } = storage
413
+ await aiLimits.createPolicy({
414
+ id: "project",
415
+ projectId,
416
+ subject: project,
417
+ limit: { meter: "tokens.total", amount: 20 },
418
+ })
419
+ await aiLimits.reserveModelCall(reservation("reconcile", 10))
420
+ await recordUsage(storage.aiUsage, "usage-reconcile", "reconcile", 7)
421
+ await aiLimits.recordModelCallActuals({
422
+ projectId,
423
+ usageRecordId: "usage-reconcile",
424
+ })
425
+ const input = {
426
+ projectId,
427
+ executionId,
428
+ attempt: 1,
429
+ callId: "reconcile",
430
+ usageRecordId: "usage-reconcile",
431
+ reconciledAt: at("2026-08-15T12:00:01.000Z"),
432
+ }
433
+ await expect(aiLimits.reconcileModelCall(input)).resolves.toMatchObject({
434
+ state: "reconciled",
435
+ usageRecordId: "usage-reconcile",
436
+ })
437
+ await expect(aiLimits.reconcileModelCall(input)).resolves.toMatchObject({
438
+ state: "reconciled",
439
+ })
440
+ await expect(
441
+ aiLimits.reconcileModelCall({ ...input, usageRecordId: "another-usage" })
442
+ ).rejects.toMatchObject({ code: "reconciliation_conflict" })
443
+ const [status] = await aiLimits.listPolicyStatuses({ projectId, at: input.reconciledAt })
444
+ expect(status?.consumption).toMatchObject({
445
+ actual: { amount: 7 },
446
+ reserved: { amount: 0 },
447
+ unknown: { amount: 0 },
448
+ remaining: { amount: 13 },
449
+ })
450
+ })
451
+ })
452
+
453
+ test("rejects a usage record that belongs to another provider call", async () => {
454
+ await withStorage(async (storage) => {
455
+ await storage.aiLimits.createPolicy({
456
+ id: "usage-mismatch-policy",
457
+ projectId,
458
+ subject: project,
459
+ limit: { meter: "tokens.total", amount: 100 },
460
+ })
461
+ const identity = reservation("expected-call", 5)
462
+ await storage.aiLimits.reserveModelCall(identity)
463
+ await recordUsage(storage.aiUsage, "usage-wrong-call", "different-call", 3)
464
+ await expect(
465
+ storage.aiLimits.reconcileModelCall({
466
+ ...identity,
467
+ usageRecordId: "usage-wrong-call",
468
+ })
469
+ ).rejects.toMatchObject({ code: "usage_mismatch" })
470
+ })
471
+ })
472
+
473
+ test("conservatively converts a reservation to unknown when actuals are unavailable", async () => {
474
+ await withStorage(async (storage) => {
475
+ await storage.aiLimits.createPolicy({
476
+ id: "tokens",
477
+ projectId,
478
+ subject: project,
479
+ limit: { meter: "tokens.total", amount: 10 },
480
+ })
481
+ const identity = reservation("partial-reconciliation", 5)
482
+ await storage.aiLimits.reserveModelCall(identity)
483
+ await recordUsageInput(storage.aiUsage, {
484
+ id: "usage-partial-reconciliation",
485
+ callId: identity.callId,
486
+ usage: { inputTokens: 3 },
487
+ })
488
+ await storage.aiLimits.recordModelCallActuals({
489
+ projectId,
490
+ usageRecordId: "usage-partial-reconciliation",
491
+ })
492
+ await expect(
493
+ storage.aiLimits.reconcileModelCall({
494
+ ...identity,
495
+ usageRecordId: "usage-partial-reconciliation",
496
+ })
497
+ ).resolves.toMatchObject({ state: "reconciled" })
498
+ const [status] = await storage.aiLimits.listPolicyStatuses({
499
+ projectId,
500
+ at: identity.reservedAt,
501
+ })
502
+ expect(status).toMatchObject({
503
+ accountingStatus: "unavailable",
504
+ consumption: { reserved: { amount: 0 }, unknown: { amount: 5 } },
505
+ })
506
+ })
507
+ })
508
+
509
+ test("fails closed when historical token accounting is incomplete", async () => {
510
+ await withStorage(async (storage) => {
511
+ await recordUsageInput(storage.aiUsage, {
512
+ id: "usage-partial",
513
+ callId: "partial",
514
+ usage: { inputTokens: 3 },
515
+ })
516
+ await storage.aiLimits.createPolicy({
517
+ id: "tokens",
518
+ projectId,
519
+ subject: project,
520
+ limit: { meter: "tokens.total", amount: 10 },
521
+ })
522
+ const [status] = await storage.aiLimits.listPolicyStatuses({
523
+ projectId,
524
+ at: at("2026-08-15T12:00:01.000Z"),
525
+ })
526
+ expect(status).toMatchObject({
527
+ accountingStatus: "unavailable",
528
+ consumption: { actual: { amount: 0 } },
529
+ })
530
+ await expect(
531
+ storage.aiLimits.reserveModelCall(reservation("after-partial", 1))
532
+ ).resolves.toMatchObject({
533
+ status: "unavailable",
534
+ reasons: ["incompleteAccounting"],
535
+ })
536
+ })
537
+ })
538
+
539
+ test("charges actuals to the ledger occurrence month", async () => {
540
+ await withStorage(async (storage) => {
541
+ await storage.aiLimits.createPolicy({
542
+ id: "tokens",
543
+ projectId,
544
+ subject: project,
545
+ limit: { meter: "tokens.total", amount: 20 },
546
+ })
547
+ const identity = reservation("month-boundary", 10, {
548
+ reservedAt: at("2026-08-31T23:59:59.000Z"),
549
+ })
550
+ await storage.aiLimits.reserveModelCall(identity)
551
+ await recordUsage(storage.aiUsage, "usage-month-boundary", "month-boundary", 7, {
552
+ occurredAt: at("2026-09-01T00:00:00.000Z"),
553
+ })
554
+ await storage.aiLimits.recordModelCallActuals({
555
+ projectId,
556
+ usageRecordId: "usage-month-boundary",
557
+ })
558
+ await expect(
559
+ storage.aiLimits.reconcileModelCall({
560
+ ...identity,
561
+ usageRecordId: "usage-month-boundary",
562
+ reconciledAt: at("2026-09-01T00:00:01.000Z"),
563
+ })
564
+ ).resolves.toMatchObject({ state: "reconciled" })
565
+ const [august] = await storage.aiLimits.listPolicyStatuses({
566
+ projectId,
567
+ at: at("2026-08-31T23:59:59.999Z"),
568
+ })
569
+ const [september] = await storage.aiLimits.listPolicyStatuses({
570
+ projectId,
571
+ at: at("2026-09-01T00:00:00.000Z"),
572
+ })
573
+ expect(august?.consumption).toMatchObject({
574
+ actual: { amount: 0 },
575
+ reserved: { amount: 0 },
576
+ })
577
+ expect(september?.consumption).toMatchObject({
578
+ actual: { amount: 7 },
579
+ remaining: { amount: 13 },
580
+ })
581
+ })
582
+ })
583
+
584
+ test("holds unknown capacity until it is reconciled", async () => {
585
+ await withStorage(async (storage) => {
586
+ const { aiLimits } = storage
587
+ await aiLimits.createPolicy({
588
+ id: "project",
589
+ projectId,
590
+ subject: project,
591
+ limit: { meter: "tokens.total", amount: 10 },
592
+ })
593
+ const identity = reservation("unknown", 8)
594
+ await aiLimits.reserveModelCall(identity)
595
+ await aiLimits.markReservationUnknown({ ...identity, markedAt: at("2026-08-15T12:01Z") })
596
+ let [status] = await aiLimits.listPolicyStatuses({ projectId, at: identity.reservedAt })
597
+ expect(status?.consumption).toMatchObject({
598
+ reserved: { amount: 0 },
599
+ unknown: { amount: 8 },
600
+ remaining: { amount: 2 },
601
+ })
602
+ await recordUsage(storage.aiUsage, "usage-unknown", identity.callId, 3)
603
+ await aiLimits.recordModelCallActuals({ projectId, usageRecordId: "usage-unknown" })
604
+ await aiLimits.reconcileModelCall({
605
+ ...identity,
606
+ usageRecordId: "usage-unknown",
607
+ reconciledAt: at("2026-08-15T12:02Z"),
608
+ })
609
+ ;[status] = await aiLimits.listPolicyStatuses({ projectId, at: identity.reservedAt })
610
+ expect(status?.consumption).toMatchObject({
611
+ actual: { amount: 3 },
612
+ unknown: { amount: 0 },
613
+ remaining: { amount: 7 },
614
+ })
615
+ })
616
+ })
617
+
618
+ test("counts usage recorded before a policy is created and preserves it across recreation", async () => {
619
+ await withStorage(async (storage) => {
620
+ const identity = reservation("before-policy", 10)
621
+ await recordUsage(storage.aiUsage, "usage-before-policy", "before-policy", 7)
622
+ await storage.aiLimits.createPolicy({
623
+ id: "created-mid-month",
624
+ projectId,
625
+ subject: project,
626
+ limit: { meter: "tokens.total", amount: 10 },
627
+ })
628
+ let [status] = await storage.aiLimits.listPolicyStatuses({
629
+ projectId,
630
+ at: identity.reservedAt,
631
+ })
632
+ expect(status?.consumption.actual).toEqual({ meter: "tokens.total", amount: 7 })
633
+ await storage.aiLimits.updatePolicy({
634
+ projectId,
635
+ id: "created-mid-month",
636
+ limit: { meter: "tokens.total", amount: 20 },
637
+ })
638
+ await storage.aiLimits.deletePolicy({ projectId, id: "created-mid-month" })
639
+ await storage.aiLimits.createPolicy({
640
+ id: "recreated",
641
+ projectId,
642
+ subject: project,
643
+ limit: { meter: "tokens.total", amount: 30 },
644
+ })
645
+ ;[status] = await storage.aiLimits.listPolicyStatuses({
646
+ projectId,
647
+ at: identity.reservedAt,
648
+ })
649
+ expect(status?.consumption).toMatchObject({
650
+ actual: { amount: 7 },
651
+ remaining: { amount: 23 },
652
+ })
653
+ })
654
+ })
655
+
656
+ test("derives each subject's actuals from immutable execution and usage attribution", async () => {
657
+ await withStorage(async (storage) => {
658
+ const engineering = { type: "group" as const, id: "engineering" }
659
+ const serviceAccount = { type: "serviceAccount" as const, id: "automation" }
660
+ await recordUsage(storage.aiUsage, "usage-all-subjects", "all-subjects", 7, {
661
+ requesterGroupIds: ["support", "engineering"],
662
+ })
663
+
664
+ const serviceExecutionId = "exec_ai_limit_service"
665
+ await seedPrincipalExecution(
666
+ storage.auth,
667
+ storage.executions,
668
+ serviceExecutionId,
669
+ serviceAccount
670
+ )
671
+ await recordUsage(storage.aiUsage, "usage-service-account", "service-account", 3, {
672
+ executionId: serviceExecutionId,
673
+ requesterGroupIds: [],
674
+ })
675
+
676
+ for (const [id, subject] of [
677
+ ["project-subject", project],
678
+ ["support-subject", support],
679
+ ["engineering-subject", engineering],
680
+ ["user-subject", user],
681
+ ["service-account-subject", serviceAccount],
682
+ ] as const) {
683
+ await storage.aiLimits.createPolicy({
684
+ id,
685
+ projectId,
686
+ subject,
687
+ limit: { meter: "tokens.total", amount: 20 },
688
+ })
689
+ }
690
+ const august = await storage.aiLimits.listPolicyStatuses({
691
+ projectId,
692
+ at: at("2026-08-31T23:59:59.999Z"),
693
+ })
694
+ expect(
695
+ Object.fromEntries(
696
+ august.map((status) => [status.policy.id, status.consumption.actual.amount])
697
+ )
698
+ ).toEqual({
699
+ "engineering-subject": 7,
700
+ "project-subject": 10,
701
+ "service-account-subject": 3,
702
+ "support-subject": 7,
703
+ "user-subject": 7,
704
+ })
705
+ const september = await storage.aiLimits.listPolicyStatuses({
706
+ projectId,
707
+ at: at("2026-09-01T00:00:00.000Z"),
708
+ })
709
+ expect(september.map((status) => status.consumption.actual.amount)).toEqual([0, 0, 0, 0, 0])
710
+ })
711
+ })
712
+
713
+ test("derives catalog-estimated cost from the immutable valuation ledger", async () => {
714
+ await withStorage(async (storage) => {
715
+ await storage.aiLimits.createPolicy({
716
+ id: "cost",
717
+ projectId,
718
+ subject: project,
719
+ limit: {
720
+ meter: "cost.catalogEstimated",
721
+ amount: { currency: "USD", amountNanos: "1000000000" },
722
+ },
723
+ })
724
+ const identity = reservation("cost", 1, {
725
+ estimates: [
726
+ {
727
+ meter: "cost.catalogEstimated",
728
+ amount: { currency: "USD", amountNanos: "250000000" },
729
+ },
730
+ ],
731
+ })
732
+ const result = await storage.aiLimits.reserveModelCall(identity)
733
+ expect(result.status).toBe("reserved")
734
+ await recordUsage(storage.aiUsage, "usage-cost", "cost", 1)
735
+ await storage.aiCosts.recordModelCallCost(ratedCost("usage-cost", "250000000"))
736
+ await storage.aiLimits.recordModelCallActuals({
737
+ projectId,
738
+ usageRecordId: "usage-cost",
739
+ })
740
+ await expect(
741
+ storage.aiLimits.reconcileModelCall({
742
+ ...identity,
743
+ usageRecordId: "usage-cost",
744
+ })
745
+ ).resolves.toMatchObject({ state: "reconciled" })
746
+ const [status] = await storage.aiLimits.listPolicyStatuses({
747
+ projectId,
748
+ at: at("2026-08-15T12:00:00.000Z"),
749
+ })
750
+ expect(status).toMatchObject({
751
+ accountingStatus: "complete",
752
+ consumption: {
753
+ actual: { amount: { currency: "USD", amountNanos: "250000000" } },
754
+ reserved: { amount: { currency: "USD", amountNanos: "0" } },
755
+ remaining: { amount: { currency: "USD", amountNanos: "750000000" } },
756
+ },
757
+ })
758
+ })
759
+ })
760
+
761
+ // Regression check: remove the unavailable-period refresh in the provider's status
762
+ // path and run this suite with --test-name-pattern "refreshes repaired cost accounting".
763
+ for (const initialized of [false, true]) {
764
+ for (const via of ["status", "admission"] as const) {
765
+ test(`refreshes repaired cost accounting via ${via} (initialized=${initialized})`, async () => {
766
+ await withStorage(async (storage) => {
767
+ const cost = (amountNanos: string): AiLimitQuantity => ({
768
+ meter: "cost.catalogEstimated",
769
+ amount: { currency: "USD", amountNanos },
770
+ })
771
+ const request = (callId: string, amount: string) =>
772
+ reservation(callId, 0, { estimates: [cost(amount)] })
773
+ const statuses = () =>
774
+ storage.aiLimits.listPolicyStatuses({
775
+ projectId,
776
+ at: at("2026-08-15T12:00:00.000Z"),
777
+ })
778
+ await storage.aiLimits.createPolicy({
779
+ id: "cost",
780
+ projectId,
781
+ subject: support,
782
+ limit: cost("1000"),
783
+ })
784
+ if (initialized) {
785
+ expect(
786
+ (await storage.aiLimits.reserveModelCall(request("active", "100"))).status
787
+ ).toBe("reserved")
788
+ expect(
789
+ (await storage.aiLimits.reserveModelCall(request("unknown", "200"))).status
790
+ ).toBe("reserved")
791
+ await storage.aiLimits.markReservationUnknown(request("unknown", "200"))
792
+ }
793
+ for (const id of ["missing-a", "missing-b"]) {
794
+ await recordUsage(storage.aiUsage, id, id, 1)
795
+ if (initialized) {
796
+ await storage.aiLimits.recordModelCallActuals({ projectId, usageRecordId: id })
797
+ }
798
+ }
799
+ expect((await statuses())[0]?.accountingStatus).toBe("unavailable")
800
+ await storage.aiCosts.recordModelCallCost(ratedCost("missing-a", "150"))
801
+ expect((await statuses())[0]).toMatchObject({
802
+ accountingStatus: "unavailable",
803
+ consumption: { actual: cost("150") },
804
+ })
805
+ await expect(
806
+ storage.aiLimits.reserveModelCall(request("still-incomplete", "1"))
807
+ ).resolves.toMatchObject({ status: "unavailable", reasons: ["incompleteAccounting"] })
808
+
809
+ await expect(
810
+ storage.transaction(async (tx) => {
811
+ if (!tx.aiCosts || !tx.aiLimits) throw new Error("Expected accounting storage")
812
+ await tx.aiCosts.recordModelCallCost(ratedCost("missing-b", "50"))
813
+ const [status] = await tx.aiLimits.listPolicyStatuses({
814
+ projectId,
815
+ at: at("2026-08-15T12:00:00.000Z"),
816
+ })
817
+ expect(status?.accountingStatus).toBe("complete")
818
+ throw new Error("roll back accounting repair")
819
+ })
820
+ ).rejects.toThrow("roll back accounting repair")
821
+ expect((await statuses())[0]?.accountingStatus).toBe("unavailable")
822
+
823
+ // Recovery appends the missing valuation, but replays an existing usage record:
824
+ // recordModelCallActuals must not be called again or tokens would be double-counted.
825
+ await storage.aiCosts.recordModelCallCost(ratedCost("missing-b", "50"))
826
+ if (via === "admission") {
827
+ await expect(
828
+ storage.aiLimits.reserveModelCall(request("too-large", "1001"))
829
+ ).resolves.toMatchObject({ status: "denied" })
830
+ }
831
+ for (let read = 0; read < 2; read++) {
832
+ expect((await statuses())[0]).toMatchObject({
833
+ accountingStatus: "complete",
834
+ consumption: {
835
+ actual: cost("200"),
836
+ reserved: cost(initialized ? "100" : "0"),
837
+ unknown: cost(initialized ? "200" : "0"),
838
+ remaining: cost(initialized ? "500" : "800"),
839
+ },
840
+ })
841
+ }
842
+ await recordUsage(storage.aiUsage, "next-actual", "next-actual", 1)
843
+ await storage.aiCosts.recordModelCallCost(ratedCost("next-actual", "50"))
844
+ await storage.aiLimits.recordModelCallActuals({
845
+ projectId,
846
+ usageRecordId: "next-actual",
847
+ })
848
+ const remaining = initialized ? 450 : 750
849
+ await expect(
850
+ storage.aiLimits.reserveModelCall(request("over-budget", String(remaining + 1)))
851
+ ).resolves.toMatchObject({ status: "denied" })
852
+ await expect(
853
+ storage.aiLimits.reserveModelCall(request("fits", String(remaining)))
854
+ ).resolves.toMatchObject({ status: "reserved" })
855
+ })
856
+ })
857
+ }
858
+ }
859
+
860
+ test("fails cost admission closed until every applicable call is valued", async () => {
861
+ await withStorage(async (storage) => {
862
+ await recordUsage(storage.aiUsage, "usage-unvalued", "unvalued", 1)
863
+ await storage.aiLimits.createPolicy({
864
+ id: "cost",
865
+ projectId,
866
+ subject: project,
867
+ limit: {
868
+ meter: "cost.catalogEstimated",
869
+ amount: { currency: "USD", amountNanos: "1000000000" },
870
+ },
871
+ })
872
+ await expect(
873
+ storage.aiLimits.reserveModelCall(
874
+ reservation("after-unvalued", 1, {
875
+ estimates: [
876
+ {
877
+ meter: "cost.catalogEstimated",
878
+ amount: { currency: "USD", amountNanos: "1" },
879
+ },
880
+ ],
881
+ })
882
+ )
883
+ ).resolves.toMatchObject({
884
+ status: "unavailable",
885
+ reasons: ["incompleteAccounting"],
886
+ })
887
+
888
+ await storage.aiCosts.recordModelCallCost(unpriceableCost("usage-unvalued"))
889
+ await expect(
890
+ storage.aiLimits.reserveModelCall(
891
+ reservation("after-unpriceable", 1, {
892
+ estimates: [
893
+ {
894
+ meter: "cost.catalogEstimated",
895
+ amount: { currency: "USD", amountNanos: "1" },
896
+ },
897
+ ],
898
+ })
899
+ )
900
+ ).resolves.toMatchObject({
901
+ status: "unavailable",
902
+ reasons: ["incompleteAccounting"],
903
+ })
904
+ })
905
+ })
906
+
907
+ test("rolls policy and reservation state back with the root storage transaction", async () => {
908
+ await withStorage(async (storage) => {
909
+ await expect(
910
+ storage.transaction(async (tx) => {
911
+ const limits = tx.aiLimits
912
+ if (!limits) throw new Error("Expected AI limit storage in transaction")
913
+ await limits.createPolicy({
914
+ id: "rolled-back",
915
+ projectId,
916
+ subject: project,
917
+ limit: { meter: "tokens.total", amount: 10 },
918
+ })
919
+ await limits.reserveModelCall(reservation("rolled-back", 5))
920
+ throw new Error("rollback")
921
+ })
922
+ ).rejects.toThrow("rollback")
923
+ await expect(
924
+ storage.aiLimits.getPolicy({ projectId, id: "rolled-back" })
925
+ ).resolves.toBeNull()
926
+ await expect(
927
+ storage.aiLimits.reserveModelCall(reservation("after-rollback", 10))
928
+ ).resolves.toEqual({ status: "notRequired" })
929
+ })
930
+ })
931
+
932
+ test("rolls ledger accounting and reconciliation back together", async () => {
933
+ await withStorage(async (storage) => {
934
+ await storage.aiLimits.createPolicy({
935
+ id: "transactional-accounting",
936
+ projectId,
937
+ subject: project,
938
+ limit: { meter: "tokens.total", amount: 10 },
939
+ })
940
+ const identity = reservation("transactional-accounting", 5)
941
+ await storage.aiLimits.reserveModelCall(identity)
942
+
943
+ await expect(
944
+ storage.transaction(async (tx) => {
945
+ if (!tx.aiUsage || !tx.aiLimits) {
946
+ throw new Error("Expected AI accounting and limit storage in transaction")
947
+ }
948
+ await recordUsage(tx.aiUsage, "usage-transactional-accounting", identity.callId, 3)
949
+ await tx.aiLimits.recordModelCallActuals({
950
+ projectId,
951
+ usageRecordId: "usage-transactional-accounting",
952
+ })
953
+ await tx.aiLimits.reconcileModelCall({
954
+ ...identity,
955
+ usageRecordId: "usage-transactional-accounting",
956
+ })
957
+ throw new Error("rollback accounting")
958
+ })
959
+ ).rejects.toThrow("rollback accounting")
960
+
961
+ await expect(
962
+ storage.aiUsage.summarizeExecution({ projectId, executionId })
963
+ ).resolves.toMatchObject({ modelCallCount: 0 })
964
+ const [status] = await storage.aiLimits.listPolicyStatuses({
965
+ projectId,
966
+ at: identity.reservedAt,
967
+ })
968
+ expect(status?.consumption).toMatchObject({
969
+ actual: { amount: 0 },
970
+ reserved: { amount: 5 },
971
+ remaining: { amount: 5 },
972
+ })
973
+ })
974
+ })
975
+ })
976
+ }
977
+
978
+ export async function seedAiLimitStorageContractExecution(
979
+ auth: AuthStorage,
980
+ executions: ExecutionStorage
981
+ ): Promise<void> {
982
+ if (!(await auth.users.getById({ projectId, id: user.id }))) {
983
+ await auth.users.create({
984
+ id: user.id,
985
+ projectId,
986
+ email: "ai-limit-user@example.test",
987
+ createdAt: at("2026-08-01T00:00:00.000Z"),
988
+ updatedAt: at("2026-08-01T00:00:00.000Z"),
989
+ })
990
+ }
991
+ if (await executions.getById({ projectId, id: executionId })) return
992
+ const primitive = { kind: "workflow" as const, id: "ai-limit-contract", runId: executionId }
993
+ await executions.create({
994
+ id: executionId,
995
+ projectId,
996
+ requestedBy: user,
997
+ executor: { type: "primitive", kind: primitive.kind, runId: primitive.runId },
998
+ source: { type: "event", eventId: `test_event:${executionId}` },
999
+ correlationId: `test_correlation:${executionId}`,
1000
+ authorizationRef: { type: "trustedPrimitive", primitive },
1001
+ })
1002
+ }
1003
+
1004
+ async function seedPrincipalExecution(
1005
+ auth: AuthStorage,
1006
+ executions: ExecutionStorage,
1007
+ id: string,
1008
+ principal:
1009
+ | { readonly type: "user"; readonly id: string }
1010
+ | { readonly type: "serviceAccount"; readonly id: string }
1011
+ ): Promise<void> {
1012
+ if (principal.type === "serviceAccount") {
1013
+ if (!(await auth.serviceAccounts.getById({ projectId, id: principal.id }))) {
1014
+ await auth.serviceAccounts.create({
1015
+ id: principal.id,
1016
+ projectId,
1017
+ name: "AI limit contract service account",
1018
+ createdAt: at("2026-08-01T00:00:00.000Z"),
1019
+ updatedAt: at("2026-08-01T00:00:00.000Z"),
1020
+ })
1021
+ }
1022
+ }
1023
+ if (await executions.getById({ projectId, id })) return
1024
+ const primitive = { kind: "workflow" as const, id: "ai-limit-contract", runId: id }
1025
+ await executions.create({
1026
+ id,
1027
+ projectId,
1028
+ requestedBy: principal,
1029
+ executor: { type: "primitive", kind: primitive.kind, runId: primitive.runId },
1030
+ source: { type: "event", eventId: `test_event:${id}` },
1031
+ correlationId: `test_correlation:${id}`,
1032
+ authorizationRef: { type: "trustedPrimitive", primitive },
1033
+ })
1034
+ }
1035
+
1036
+ async function recordUsage(
1037
+ storage: AiUsageStorage,
1038
+ id: string,
1039
+ callId: string,
1040
+ totalTokens: number,
1041
+ overrides: {
1042
+ readonly executionId?: string
1043
+ readonly requesterGroupIds?: readonly string[]
1044
+ readonly occurredAt?: Date
1045
+ } = {}
1046
+ ): Promise<void> {
1047
+ await recordUsageInput(storage, {
1048
+ id,
1049
+ callId,
1050
+ executionId: overrides.executionId,
1051
+ requesterGroupIds: overrides.requesterGroupIds,
1052
+ occurredAt: overrides.occurredAt,
1053
+ usage: { inputTokens: totalTokens, outputTokens: 0 },
1054
+ })
1055
+ }
1056
+
1057
+ async function recordUsageInput(
1058
+ storage: AiUsageStorage,
1059
+ input: {
1060
+ readonly id: string
1061
+ readonly callId: string
1062
+ readonly executionId?: string
1063
+ readonly requesterGroupIds?: readonly string[]
1064
+ readonly occurredAt?: Date
1065
+ readonly usage: AiModelCallUsageInput
1066
+ }
1067
+ ): Promise<void> {
1068
+ await storage.recordModelCall({
1069
+ id: input.id,
1070
+ projectId,
1071
+ executionId: input.executionId ?? executionId,
1072
+ attempt: 1,
1073
+ callId: input.callId,
1074
+ requesterGroupIds: input.requesterGroupIds ?? ["support"],
1075
+ providerId: "test",
1076
+ requestedModelId: "test/model",
1077
+ responseId: `response:${input.callId}`,
1078
+ usage: input.usage,
1079
+ occurredAt: input.occurredAt ?? at("2026-08-15T12:00:00.500Z"),
1080
+ recordedAt: at("2026-08-15T12:00:00.600Z"),
1081
+ })
1082
+ }
1083
+
1084
+ function ratedCost(usageRecordId: string, amountNanos: string): AiModelCallCostRecord {
1085
+ return {
1086
+ projectId,
1087
+ usageRecordId,
1088
+ status: "rated",
1089
+ billingIdentity: { providerId: "test", modelId: "test/model" },
1090
+ pricingContext: {},
1091
+ priceSource: {
1092
+ sourceId: "test-catalog",
1093
+ sourceEntryId: "test/model",
1094
+ sourceVersion: "v1",
1095
+ sourceUrl: "https://example.test/catalog.json",
1096
+ observedAt: at("2026-08-01T00:00:00.000Z"),
1097
+ },
1098
+ money: { currency: "USD", amountNanos },
1099
+ components: [
1100
+ {
1101
+ meter: "tokens.input.total",
1102
+ quantity: "1",
1103
+ rateAmountNanosPerMillion: (BigInt(amountNanos) * 1_000_000n).toString(),
1104
+ chargeAmountNanos: amountNanos,
1105
+ },
1106
+ {
1107
+ meter: "tokens.output.total",
1108
+ quantity: "0",
1109
+ rateAmountNanosPerMillion: "0",
1110
+ chargeAmountNanos: "0",
1111
+ },
1112
+ ],
1113
+ ratedAt: at("2026-08-15T12:00:00.700Z"),
1114
+ }
1115
+ }
1116
+
1117
+ function unpriceableCost(usageRecordId: string): AiModelCallCostRecord {
1118
+ return {
1119
+ projectId,
1120
+ usageRecordId,
1121
+ status: "unpriceable",
1122
+ billingIdentity: { providerId: "test", modelId: "test/model" },
1123
+ pricingContext: {},
1124
+ priceSource: {
1125
+ sourceId: "test-catalog",
1126
+ sourceEntryId: "test/model",
1127
+ sourceVersion: "v1",
1128
+ sourceUrl: "https://example.test/catalog.json",
1129
+ observedAt: at("2026-08-01T00:00:00.000Z"),
1130
+ },
1131
+ reason: "unsupportedPricingDimension",
1132
+ ratedAt: at("2026-08-15T12:00:00.700Z"),
1133
+ }
1134
+ }