okengine 0.21.1 → 0.23.0

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 (342) hide show
  1. package/AGENTS.md +11 -4
  2. package/LICENSE +202 -0
  3. package/NOTICE +2 -0
  4. package/README.md +15 -9
  5. package/manifest.v1.schema.json +54 -0
  6. package/package.json +53 -38
  7. package/site/content/docs/ai/mcp.mdx +13 -9
  8. package/site/content/docs/ai/skills.mdx +2 -2
  9. package/site/content/docs/client/calling.mdx +27 -19
  10. package/site/content/docs/client/index.mdx +1 -1
  11. package/site/content/docs/elements/ai/agents.mdx +61 -13
  12. package/site/content/docs/elements/ai/decide.mdx +536 -0
  13. package/site/content/docs/elements/ai/events.mdx +373 -0
  14. package/site/content/docs/elements/ai/index.mdx +9 -3
  15. package/site/content/docs/elements/ai/meta.json +1 -1
  16. package/site/content/docs/elements/ai/prompts.mdx +4 -2
  17. package/site/content/docs/elements/channel/email.mdx +1 -1
  18. package/site/content/docs/elements/channel/index.mdx +1 -1
  19. package/site/content/docs/elements/flow/http.mdx +4 -4
  20. package/site/content/docs/elements/flow/index.mdx +3 -3
  21. package/site/content/docs/elements/flow/meta.json +1 -1
  22. package/site/content/docs/elements/gate/auth.mdx +4 -1
  23. package/site/content/docs/elements/gate/tenancy.mdx +1 -1
  24. package/site/content/docs/elements/store/index.mdx +1 -1
  25. package/site/content/docs/elements/store/kv.mdx +5 -5
  26. package/site/content/docs/elements/vault/rotation.mdx +15 -0
  27. package/site/content/docs/plugins/otp.mdx +31 -31
  28. package/site/content/docs/recipes/dragonfly.mdx +5 -5
  29. package/site/content/docs/recipes/index.mdx +6 -6
  30. package/site/content/docs/recipes/mailpit.mdx +1 -1
  31. package/site/content/docs/recipes/meilisearch.mdx +1 -1
  32. package/site/content/docs/recipes/pgdog.mdx +1 -1
  33. package/site/content/docs/recipes/postgres.mdx +12 -8
  34. package/site/content/docs/recipes/redis.mdx +4 -4
  35. package/site/content/docs/reference/cli.mdx +2 -1
  36. package/site/content/docs/reference/configuration.mdx +11 -9
  37. package/site/content/docs/reference/errors.mdx +12 -2
  38. package/site/content/docs/reference/fx.mdx +35 -21
  39. package/site/content/docs/reference/idempotency.mdx +192 -0
  40. package/site/content/docs/reference/index.mdx +6 -1
  41. package/site/content/docs/reference/meta.json +2 -1
  42. package/site/content/docs/understand/meta.json +1 -1
  43. package/site/content/docs/{elements/flow → understand}/routing.mdx +11 -10
  44. package/site/content/docs/understand/the-architecture.mdx +14 -12
  45. package/site/content/docs/understand/try-it.mdx +3 -3
  46. package/src/auth/api-key-sql.test.ts +44 -0
  47. package/src/auth/api-key-sql.ts +1 -0
  48. package/src/auth/api-keys.ts +30 -12
  49. package/src/auth/config.ts +6 -2
  50. package/src/auth/gate-auth.test.ts +20 -0
  51. package/src/cli/agents-md.test.ts +25 -0
  52. package/src/cli/ask-vault-gaps.test.ts +38 -1
  53. package/src/cli/ask-vault-gaps.ts +35 -2
  54. package/src/cli/competitor-mention-removal.test.ts +3 -3
  55. package/src/cli/decide.ts +174 -0
  56. package/src/cli/decision-lock-watch.test.ts +45 -0
  57. package/src/cli/decision-lock-watch.ts +51 -0
  58. package/src/cli/dev-app-runner.ts +34 -8
  59. package/src/cli/dev-auth-secret.test.ts +49 -0
  60. package/src/cli/dev-auth-secret.ts +49 -0
  61. package/src/cli/dev-hot.test.ts +41 -0
  62. package/src/cli/dev-hot.ts +48 -0
  63. package/src/cli/dev.test.ts +1 -1
  64. package/src/cli/dev.ts +24 -6
  65. package/src/cli/docker-cli.test.ts +1 -1
  66. package/src/cli/eval.ts +142 -1
  67. package/src/cli/index.ts +14 -1
  68. package/src/cli/load-config.images.test.ts +12 -12
  69. package/src/cli/load-config.ts +2 -2
  70. package/src/cli/mcp-from-console.ts +48 -0
  71. package/src/cli/registry.ts +35 -1
  72. package/src/cli/vault-cmd.test.ts +23 -0
  73. package/src/cli/vault-cmd.ts +7 -0
  74. package/src/client/agent.ts +307 -0
  75. package/src/client/create.ts +18 -1
  76. package/src/client/transport.test.ts +245 -0
  77. package/src/client/transport.ts +133 -19
  78. package/src/client/types.ts +29 -3
  79. package/src/client-react/index.ts +7 -1
  80. package/src/client-react/use-agent-run.test.ts +271 -0
  81. package/src/client-react/use-agent-run.ts +193 -0
  82. package/src/compiler/ai-approval.test.ts +36 -0
  83. package/src/compiler/ai-repair.test.ts +22 -0
  84. package/src/compiler/decisions.extract.test.ts +164 -0
  85. package/src/compiler/effects-infer.ts +753 -17
  86. package/src/compiler/extract.test.ts +37 -0
  87. package/src/compiler/extract.ts +381 -24
  88. package/src/compiler/fixtures/skyport.expected.json +24 -0
  89. package/src/compiler/fx-follow.test.ts +164 -0
  90. package/src/compiler/fx-index.ts +399 -0
  91. package/src/compiler/response.ts +35 -2
  92. package/src/config/index.ts +21 -3
  93. package/src/console/server/ai-runs-flows.ts +269 -0
  94. package/src/console/server/ai-runs.test.ts +323 -0
  95. package/src/console/server/ai-runs.ts +562 -0
  96. package/src/console/server/ai.ts +12 -1
  97. package/src/console/server/app.ts +10 -1
  98. package/src/console/server/decisions-flows.ts +157 -0
  99. package/src/console/server/decisions.test.ts +66 -0
  100. package/src/console/server/decisions.ts +229 -0
  101. package/src/console/server/flows-invoke.test.ts +2 -2
  102. package/src/console/server/flows.ts +50 -2
  103. package/src/console/server/invoke-user-flow.ts +9 -0
  104. package/src/console/server/runs-ingest.ts +16 -0
  105. package/src/console/server/serve.ts +5 -0
  106. package/src/console/server/state.ts +25 -1
  107. package/src/console/server/store-stats.test.ts +47 -0
  108. package/src/console/server/store-stats.ts +92 -18
  109. package/src/console/ui-next/dist/assets/access-page-DDKkhT9v.js +4 -0
  110. package/src/console/ui-next/dist/assets/agent-disclosure-62Xts8Xi.js +1 -0
  111. package/src/console/ui-next/dist/assets/{cache-glyph-92uM5MO7.js → cache-glyph-DyeoKHKA.js} +1 -1
  112. package/src/console/ui-next/dist/assets/{call-pii-button-DtGVPtcs.js → call-pii-button-DvfpLXsU.js} +1 -1
  113. package/src/console/ui-next/dist/assets/{collapsible-DN7l6zmC.js → collapsible-BY03SeCg.js} +1 -1
  114. package/src/console/ui-next/dist/assets/confirm-sheet-DZfuJXP4.js +1 -0
  115. package/src/console/ui-next/dist/assets/copy-inline-button-BTjPVjRT.js +1 -0
  116. package/src/console/ui-next/dist/assets/decisions-page-CgkKlA56.js +1 -0
  117. package/src/console/ui-next/dist/assets/detail-header-Dk5LipF6.js +1 -0
  118. package/src/console/ui-next/dist/assets/dropdown-menu-CRGrj5JU.js +1 -0
  119. package/src/console/ui-next/dist/assets/duration-tone-Y6HLjyJ5.js +9 -0
  120. package/src/console/ui-next/dist/assets/element-icons-C6t8Vlml.js +1 -0
  121. package/src/console/ui-next/dist/assets/explorer-chrome-Dslh2yE8.js +1 -0
  122. package/src/console/ui-next/dist/assets/explorer-empty-CIVg7Jgb.js +1 -0
  123. package/src/console/ui-next/dist/assets/flows-page-DLPfNy-_.js +1 -0
  124. package/src/console/ui-next/dist/assets/{highlighted-json-BlAEVgNW.js → highlighted-json-D7nNzpgB.js} +1 -1
  125. package/src/console/ui-next/dist/assets/{http-method-BDf7OAHv.js → http-method-DdL19zzh.js} +1 -1
  126. package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
  127. package/src/console/ui-next/dist/assets/index-C0lc_s9d.js +58 -0
  128. package/src/console/ui-next/dist/assets/observability-page-CRlcyLCC.js +8 -0
  129. package/src/console/ui-next/dist/assets/{replica-lag-B3GLNVfF.js → replica-lag-Bb3FWUu9.js} +7 -7
  130. package/src/console/ui-next/dist/assets/request-meta-DYBMWhX8.js +1 -0
  131. package/src/console/ui-next/dist/assets/shortcut-keys-DTE1sjTC.js +1 -0
  132. package/src/console/ui-next/dist/assets/store-page-CcE-SXC8.js +41 -0
  133. package/src/console/ui-next/dist/assets/trace-detail-sheet-CKMSEQ4s.js +2 -0
  134. package/src/console/ui-next/dist/assets/tree-expand-toggle-QOfk0M0H.js +55 -0
  135. package/src/console/ui-next/dist/assets/units-page-DKC1CDDd.js +1 -0
  136. package/src/console/ui-next/dist/assets/use-vault-list-BAyQG8Re.js +1 -0
  137. package/src/console/ui-next/dist/assets/vault-page-B54v4Yit.js +2 -0
  138. package/src/console/ui-next/dist/index.html +4 -3
  139. package/src/console/ui-next/seed-parked-approval.ts +126 -0
  140. package/src/console/ui-next/src/client.ts +224 -1
  141. package/src/console/ui-next/src/components/explorer/detail-header.tsx +9 -0
  142. package/src/console/ui-next/src/components/explorer/explorer-start-toggle.tsx +5 -1
  143. package/src/console/ui-next/src/components/ui/sheet-form.tsx +21 -12
  144. package/src/console/ui-next/src/features/flows/decisions/decisions-page.tsx +216 -0
  145. package/src/console/ui-next/src/features/flows/decisions/resolve.test.ts +48 -0
  146. package/src/console/ui-next/src/features/flows/decisions/resolve.ts +111 -0
  147. package/src/console/ui-next/src/features/flows/flows-page.tsx +10 -0
  148. package/src/console/ui-next/src/features/flows/graph/element-map.ts +1 -0
  149. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +6 -0
  150. package/src/console/ui-next/src/features/flows/traces/effect-summary.test.ts +24 -0
  151. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
  152. package/src/console/ui-next/src/features/flows/traces/http-status.ts +58 -0
  153. package/src/console/ui-next/src/features/flows/traces/request-client-card.tsx +355 -0
  154. package/src/console/ui-next/src/features/flows/traces/request-client.test.ts +114 -0
  155. package/src/console/ui-next/src/features/flows/traces/request-client.ts +423 -0
  156. package/src/console/ui-next/src/features/flows/traces/request-input-view.test.ts +52 -6
  157. package/src/console/ui-next/src/features/flows/traces/request-input-view.ts +85 -19
  158. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +505 -80
  159. package/src/console/ui-next/src/features/flows/traces/trace-lanes.test.ts +61 -0
  160. package/src/console/ui-next/src/features/flows/traces/trace-lanes.ts +78 -0
  161. package/src/console/ui-next/src/features/flows/traces/trace-nav.test.ts +48 -0
  162. package/src/console/ui-next/src/features/flows/traces/trace-nav.ts +57 -0
  163. package/src/console/ui-next/src/features/flows/traces/trace-request-section.tsx +275 -96
  164. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +16 -2
  165. package/src/console/ui-next/src/features/observability/detail/ai-runs.tsx +369 -0
  166. package/src/console/ui-next/src/features/observability/observability-page.tsx +31 -1
  167. package/src/console/ui-next/src/features/observability/state/observability-selection.ts +18 -3
  168. package/src/console/ui-next/src/features/store/performance/kv-performance-panel.tsx +12 -1
  169. package/src/console/ui-next/src/features/store/performance/performance-panel.tsx +14 -1
  170. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +1 -1
  171. package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +15 -2
  172. package/src/console/ui-next/src/features/units/explorer/units-tree.tsx +73 -18
  173. package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +21 -0
  174. package/src/console/ui-next/src/features/units/lib/unit-tree.ts +82 -0
  175. package/src/console/ui-next/src/features/units/units-page.tsx +25 -7
  176. package/src/console/ui-next/src/router.tsx +17 -0
  177. package/src/console/ui-next/ui-next-seed-runs.ts +23 -0
  178. package/src/console/ui-next/vite-console-kernel-plugin.ts +43 -3
  179. package/src/console/ui-next/vite.config.ts +9 -0
  180. package/src/docker/docker.test.ts +13 -13
  181. package/src/docker/images-config.test.ts +12 -12
  182. package/src/docker/recipes/dragonfly.ts +3 -2
  183. package/src/docker/recipes/redis.ts +1 -1
  184. package/src/docker/stack-id.test.ts +1 -1
  185. package/src/drivers/ai-anthropic.ts +212 -21
  186. package/src/drivers/ai-mock.ts +34 -1
  187. package/src/drivers/ai-openai-compatible.ts +67 -26
  188. package/src/drivers/ai-preconnect.ts +18 -0
  189. package/src/drivers/ai-stream.test.ts +62 -0
  190. package/src/drivers/ai-types.ts +20 -0
  191. package/src/drivers/journal-postgres.ts +455 -3
  192. package/src/drivers/meilisearch.ts +2 -0
  193. package/src/drivers/memory-ddl.test.ts +18 -0
  194. package/src/drivers/memory.ts +21 -1
  195. package/src/drivers/pg-rls.test.ts +31 -0
  196. package/src/drivers/pg-rls.ts +15 -0
  197. package/src/drivers/postgres.test.ts +9 -0
  198. package/src/drivers/postgres.ts +49 -11
  199. package/src/drivers/vault-builtin.test.ts +10 -0
  200. package/src/drivers/vault-builtin.ts +13 -1
  201. package/src/drivers/vault-remote-bag.ts +5 -1
  202. package/src/drivers/vault-types.ts +6 -1
  203. package/src/elements/ai/agent-event-slot.ts +27 -0
  204. package/src/elements/ai/approval-http.ts +207 -0
  205. package/src/elements/ai/approval.test.ts +741 -0
  206. package/src/elements/ai/approval.ts +228 -0
  207. package/src/elements/ai/decisions/bind.ts +163 -0
  208. package/src/elements/ai/decisions/certificate.test.ts +291 -0
  209. package/src/elements/ai/decisions/certificate.ts +377 -0
  210. package/src/elements/ai/decisions/certify.ts +314 -0
  211. package/src/elements/ai/decisions/decide.test.ts +953 -0
  212. package/src/elements/ai/decisions/e2e.test.ts +399 -0
  213. package/src/elements/ai/decisions/export.test.ts +120 -0
  214. package/src/elements/ai/decisions/export.ts +146 -0
  215. package/src/elements/ai/decisions/fixtures/openrouter-request.json +24 -0
  216. package/src/elements/ai/decisions/fixtures/openrouter-response.json +22 -0
  217. package/src/elements/ai/decisions/fixtures/typesafe-request.json +19 -0
  218. package/src/elements/ai/decisions/fixtures/typesafe-response.json +13 -0
  219. package/src/elements/ai/decisions/http.test.ts +154 -0
  220. package/src/elements/ai/decisions/http.ts +240 -0
  221. package/src/elements/ai/decisions/labels.test.ts +282 -0
  222. package/src/elements/ai/decisions/labels.ts +257 -0
  223. package/src/elements/ai/decisions/openrouter.live.test.ts +136 -0
  224. package/src/elements/ai/decisions/openrouter.ts +36 -0
  225. package/src/elements/ai/decisions/provider.ts +102 -0
  226. package/src/elements/ai/decisions/typesafe.ts +36 -0
  227. package/src/elements/ai/declare.ts +252 -1
  228. package/src/elements/ai/events.test.ts +379 -0
  229. package/src/elements/ai/events.ts +138 -0
  230. package/src/elements/ai/run-events.test.ts +461 -0
  231. package/src/elements/ai/run-events.ts +522 -0
  232. package/src/elements/ai/runtime.ts +1186 -105
  233. package/src/elements/ai/stream-turn.ts +214 -0
  234. package/src/elements/ai/stream.live.test.ts +76 -0
  235. package/src/elements/ai/subagent.test.ts +136 -0
  236. package/src/elements/ai.test.ts +497 -3
  237. package/src/elements/ai.ts +16 -1
  238. package/src/elements/clock/durable.ts +27 -1
  239. package/src/elements/store/emit-drizzle.ts +2 -2
  240. package/src/elements/store/live-default.test.ts +2 -0
  241. package/src/elements/store/live-http.test.ts +1 -0
  242. package/src/elements/store/resource-list-docs.test.ts +2 -2
  243. package/src/elements/store/resource.test.ts +3 -3
  244. package/src/elements/store/schema-decl.test.ts +1 -0
  245. package/src/elements/store/sql-condition.ts +24 -7
  246. package/src/elements/store/sql-session.test.ts +51 -0
  247. package/src/elements/store/sql-session.ts +111 -1
  248. package/src/elements/store/upsert-app.test.ts +1 -1
  249. package/src/elements/vault/audit.test.ts +137 -0
  250. package/src/elements/vault/audit.ts +106 -13
  251. package/src/elements/vault/boot-chain.ts +9 -1
  252. package/src/elements/vault/builtin-adapter.ts +33 -3
  253. package/src/i18n/catalogs/ar.ts +9 -0
  254. package/src/i18n/catalogs/en.ts +9 -0
  255. package/src/index.ts +2 -0
  256. package/src/kernel/abort-scope.ts +33 -2
  257. package/src/kernel/agent-event-store.ts +448 -0
  258. package/src/kernel/app.ts +294 -19
  259. package/src/kernel/auto-cache.test.ts +6 -6
  260. package/src/kernel/boot-bind/ai.ts +3 -0
  261. package/src/kernel/boot-bind/store.test.ts +26 -0
  262. package/src/kernel/boot-bind/store.ts +82 -6
  263. package/src/kernel/boot-bind/vault.ts +1 -0
  264. package/src/kernel/boot.ts +34 -5
  265. package/src/kernel/builtin-errors.ts +10 -0
  266. package/src/kernel/capability.ts +3 -0
  267. package/src/kernel/client-descriptor.ts +1 -0
  268. package/src/kernel/concurrency.test.ts +33 -0
  269. package/src/kernel/decision-label-store.ts +441 -0
  270. package/src/kernel/effects-stamping.test.ts +2 -2
  271. package/src/kernel/effects.test.ts +2 -1
  272. package/src/kernel/effects.ts +13 -1
  273. package/src/kernel/element-registries.ts +3 -0
  274. package/src/kernel/errors-compiler.ts +20 -0
  275. package/src/kernel/errors-text.ts +4 -0
  276. package/src/kernel/errors.ts +2 -0
  277. package/src/kernel/external-effects.test.ts +36 -0
  278. package/src/kernel/flow.test.ts +2 -3
  279. package/src/kernel/flow.ts +24 -0
  280. package/src/kernel/fx-decide.ts +761 -0
  281. package/src/kernel/fx-fetch.ts +5 -1
  282. package/src/kernel/fx.test.ts +12 -5
  283. package/src/kernel/fx.ts +316 -29
  284. package/src/kernel/http-frame.test.ts +94 -0
  285. package/src/kernel/http-frame.ts +203 -0
  286. package/src/kernel/idempotency-store.ts +578 -0
  287. package/src/kernel/idempotency.test.ts +556 -0
  288. package/src/kernel/idempotency.ts +304 -0
  289. package/src/kernel/index.ts +1 -0
  290. package/src/kernel/journal.ts +160 -0
  291. package/src/kernel/json-result.ts +10 -0
  292. package/src/kernel/pipeline.test.ts +45 -0
  293. package/src/kernel/sse-id.ts +32 -0
  294. package/src/manifest/diff.ts +1 -0
  295. package/src/manifest/types.ts +37 -2
  296. package/src/mcp/ai-tools.test.ts +202 -0
  297. package/src/mcp/authorization.ts +66 -0
  298. package/src/mcp/session.ts +3 -0
  299. package/src/mcp/tools.ts +168 -0
  300. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  301. package/src/plugins/otp.test.ts +65 -1
  302. package/src/plugins/otp.ts +59 -7
  303. package/src/release/absolute-regression.test.ts +63 -0
  304. package/src/release/http-graph.test.ts +27 -0
  305. package/src/release/http-graph.ts +95 -0
  306. package/src/release/index.ts +3 -0
  307. package/src/release/limits.ts +17 -2
  308. package/src/release/measure.ts +144 -28
  309. package/src/runs/collect.ts +4 -1
  310. package/src/runs/parquet.test.ts +27 -1
  311. package/src/runs/parquet.ts +19 -0
  312. package/src/runs/types.ts +31 -0
  313. package/src/runtime/json-code-block.test.ts +106 -47
  314. package/src/runtime/json-code-block.ts +1209 -46
  315. package/src/term.test.ts +13 -0
  316. package/src/term.ts +81 -49
  317. package/src/test/create-test-app.test.ts +3 -1
  318. package/src/test/create-test-app.ts +40 -0
  319. package/src/test/live-signals.test.ts +1 -0
  320. package/src/test/provisions.integration.test.ts +1 -0
  321. package/src/test/tenant-isolation.test.ts +1 -0
  322. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
  323. package/src/console/ui-next/dist/assets/access-page-Bgt9bq2r.js +0 -4
  324. package/src/console/ui-next/dist/assets/agent-disclosure-B43CXZZR.js +0 -1
  325. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +0 -1
  326. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +0 -1
  327. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +0 -1
  328. package/src/console/ui-next/dist/assets/duration-tone-BmIR9FV8.js +0 -9
  329. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +0 -1
  330. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +0 -1
  331. package/src/console/ui-next/dist/assets/flows-page-BMHs-IzK.js +0 -1
  332. package/src/console/ui-next/dist/assets/index-BKpaes3n.js +0 -63
  333. package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
  334. package/src/console/ui-next/dist/assets/observability-page-WnVLI-0j.js +0 -4
  335. package/src/console/ui-next/dist/assets/request-meta-DMbnAe3f.js +0 -1
  336. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +0 -1
  337. package/src/console/ui-next/dist/assets/store-page-KvFDingJ.js +0 -41
  338. package/src/console/ui-next/dist/assets/trace-detail-sheet-Htk8Cm9t.js +0 -2
  339. package/src/console/ui-next/dist/assets/tree-expand-toggle-CFMPWX4f.js +0 -55
  340. package/src/console/ui-next/dist/assets/units-page-C4NdNuxP.js +0 -1
  341. package/src/console/ui-next/dist/assets/use-vault-list-CT4-gajj.js +0 -1
  342. package/src/console/ui-next/dist/assets/vault-page-CBYl0LW_.js +0 -2
@@ -10,9 +10,12 @@
10
10
 
11
11
  import { describe, expect, test } from "bun:test";
12
12
  import { createMockAiDriver, memoryIndexDriver, mockAiDriver } from "../drivers/index.ts";
13
+ import { withAbortSignal } from "../kernel/abort-scope.ts";
13
14
  import { createFx } from "../kernel/fx.ts";
15
+ import { createJournal, createMemoryJournalStore } from "../kernel/journal.ts";
14
16
  import {
15
17
  ai,
18
+ AI_OBSERVABILITY_LIMIT,
16
19
  AiPiiBuildError,
17
20
  AiSchemaValidationError,
18
21
  assertAllowPiiForAsk,
@@ -132,7 +135,8 @@ describe("agent gate denial is recorded", () => {
132
135
  message: "status?",
133
136
  auth: { userId: "u1", scopes: new Set(), verified: true },
134
137
  });
135
- expect(result.ok).toBe(true);
138
+ expect(result.stopReason).toBe("max_steps");
139
+ expect(result.ok).toBe(false);
136
140
  expect(result.denials).toHaveLength(0);
137
141
  expect(result.output).toEqual({ booking: "B1" });
138
142
  });
@@ -189,8 +193,260 @@ describe("pii to third-party model fails the build", () => {
189
193
  });
190
194
  });
191
195
 
196
+ describe("agent maxCostPerRun", () => {
197
+ test("a model call over the cap is the last call and the run resolves", async () => {
198
+ let calls = 0;
199
+ const agent = ai.agent("budget-agent", {
200
+ model: "smart",
201
+ tools: ["orders.get"],
202
+ maxSteps: 4,
203
+ budget: { maxCostPerRun: 0.5 },
204
+ });
205
+ const called: string[] = [];
206
+ const runtime = createAiRuntime({
207
+ models: [ai.model("smart")],
208
+ agents: [agent],
209
+ clients: {
210
+ smart: {
211
+ driverId: "mock",
212
+ model: "smart",
213
+ async complete() {
214
+ calls++;
215
+ return {
216
+ text: "",
217
+ raw: {},
218
+ model: "smart",
219
+ driverId: "mock",
220
+ usage: { cost: 0.8 },
221
+ toolCalls: [{ id: "c1", name: "orders.get", arguments: {} }],
222
+ };
223
+ },
224
+ },
225
+ },
226
+ callFlow: async (name) => {
227
+ called.push(name);
228
+ return { ok: true };
229
+ },
230
+ });
231
+ const result = await runtime.runAgent("budget-agent", { message: "look up" });
232
+ expect(calls).toBe(1);
233
+ expect(called).toEqual([]);
234
+ expect(result.ok).toBe(false);
235
+ expect(result.stopReason).toBe("budget");
236
+ expect(result.cost).toBe(0.8);
237
+ });
238
+
239
+ test("tools run while under the cap, and the loop stops once a later call crosses it", async () => {
240
+ let calls = 0;
241
+ const agent = ai.agent("budget-round", {
242
+ model: "smart",
243
+ tools: ["orders.get"],
244
+ maxSteps: 4,
245
+ budget: { maxCostPerRun: 0.5 },
246
+ });
247
+ const called: string[] = [];
248
+ const runtime = createAiRuntime({
249
+ models: [ai.model("smart")],
250
+ agents: [agent],
251
+ clients: {
252
+ smart: {
253
+ driverId: "mock",
254
+ model: "smart",
255
+ async complete() {
256
+ calls++;
257
+ return {
258
+ text: calls === 1 ? "" : "done",
259
+ raw: {},
260
+ model: "smart",
261
+ driverId: "mock",
262
+ usage: { cost: calls === 1 ? 0.4 : 0.4 },
263
+ ...(calls === 1
264
+ ? { toolCalls: [{ id: "c1", name: "orders.get", arguments: {} }] }
265
+ : {}),
266
+ };
267
+ },
268
+ },
269
+ },
270
+ callFlow: async (name) => {
271
+ called.push(name);
272
+ return { ok: true };
273
+ },
274
+ });
275
+ const result = await runtime.runAgent("budget-round", { message: "look up" });
276
+ expect(called).toEqual(["orders.get"]);
277
+ expect(calls).toBe(2);
278
+ expect(result.ok).toBe(false);
279
+ expect(result.stopReason).toBe("budget");
280
+ expect(result.cost).toBeCloseTo(0.8);
281
+ });
282
+
283
+ test("maxSteps is not a normal completion", async () => {
284
+ const agent = ai.agent("step-cap", {
285
+ model: "smart",
286
+ tools: ["orders.get"],
287
+ maxSteps: 1,
288
+ });
289
+ const runtime = createAiRuntime({
290
+ models: [ai.model("smart")],
291
+ agents: [agent],
292
+ clients: {
293
+ smart: {
294
+ driverId: "mock",
295
+ model: "smart",
296
+ async complete() {
297
+ return {
298
+ text: "",
299
+ raw: {},
300
+ model: "smart",
301
+ driverId: "mock",
302
+ toolCalls: [{ id: "c1", name: "orders.get", arguments: {} }],
303
+ };
304
+ },
305
+ },
306
+ },
307
+ callFlow: async () => ({ ok: true }),
308
+ });
309
+ const result = await runtime.runAgent("step-cap", { message: "go" });
310
+ expect(result.ok).toBe(false);
311
+ expect(result.stopReason).toBe("max_steps");
312
+ expect(result.steps).toBe(1);
313
+ });
314
+
315
+ test("a denial fed back to the model does not terminate as denied", async () => {
316
+ let calls = 0;
317
+ const member = gate.policy("member", () => false);
318
+ const agent = ai.agent("deny-continue", {
319
+ model: "smart",
320
+ tools: ["orders.refund"],
321
+ maxSteps: 2,
322
+ });
323
+ const runtime = createAiRuntime({
324
+ models: [ai.model("smart")],
325
+ agents: [agent],
326
+ gates: createGateRuntime({ gates: [member] }),
327
+ gatesForFlow: () => ["member"],
328
+ clients: {
329
+ smart: {
330
+ driverId: "mock",
331
+ model: "smart",
332
+ async complete() {
333
+ calls++;
334
+ if (calls === 1) {
335
+ return {
336
+ text: "",
337
+ raw: {},
338
+ model: "smart",
339
+ driverId: "mock",
340
+ toolCalls: [{ id: "c1", name: "orders.refund", arguments: {} }],
341
+ };
342
+ }
343
+ return { text: "done", raw: { done: true }, model: "smart", driverId: "mock" };
344
+ },
345
+ },
346
+ },
347
+ callFlow: async () => ({ ok: true }),
348
+ });
349
+ const result = await runtime.runAgent("deny-continue", {
350
+ message: "refund",
351
+ auth: { userId: "u1", scopes: new Set(), verified: false },
352
+ });
353
+ expect(calls).toBe(2);
354
+ expect(result.stopReason).toBe("completed");
355
+ expect(result.denials).toHaveLength(1);
356
+ expect(result.ok).toBe(false);
357
+ });
358
+
359
+ test("an unknown tool terminates the run as denied", async () => {
360
+ const agent = ai.agent("unknown-tool", {
361
+ model: "smart",
362
+ tools: ["orders.get"],
363
+ maxSteps: 2,
364
+ });
365
+ const runtime = createAiRuntime({
366
+ models: [ai.model("smart")],
367
+ agents: [agent],
368
+ clients: {
369
+ smart: {
370
+ driverId: "mock",
371
+ model: "smart",
372
+ async complete() {
373
+ return {
374
+ text: "",
375
+ raw: {},
376
+ model: "smart",
377
+ driverId: "mock",
378
+ toolCalls: [{ id: "c1", name: "nope", arguments: {} }],
379
+ };
380
+ },
381
+ },
382
+ },
383
+ callFlow: async () => ({ ok: true }),
384
+ });
385
+ const result = await runtime.runAgent("unknown-tool", { message: "go" });
386
+ expect(result.stopReason).toBe("denied");
387
+ expect(result.ok).toBe(false);
388
+ expect(runtime.agentRuns[0]?.stopReason).toBe("denied");
389
+ });
390
+
391
+ test("a thrown tool error is recorded as error and rethrown", async () => {
392
+ const agent = ai.agent("tool-error", {
393
+ model: "smart",
394
+ tools: ["orders.get"],
395
+ maxSteps: 2,
396
+ });
397
+ const runtime = createAiRuntime({
398
+ models: [ai.model("smart")],
399
+ agents: [agent],
400
+ clients: {
401
+ smart: {
402
+ driverId: "mock",
403
+ model: "smart",
404
+ async complete() {
405
+ return {
406
+ text: "",
407
+ raw: {},
408
+ model: "smart",
409
+ driverId: "mock",
410
+ toolCalls: [{ id: "c1", name: "orders.get", arguments: {} }],
411
+ };
412
+ },
413
+ },
414
+ },
415
+ callFlow: async () => {
416
+ throw new Error("store down");
417
+ },
418
+ });
419
+ await expect(runtime.runAgent("tool-error", { message: "go" })).rejects.toThrow("store down");
420
+ expect(runtime.agentRuns[0]?.stopReason).toBe("error");
421
+ expect(runtime.agentRuns[0]?.error).toBe("store down");
422
+ });
423
+
424
+ test("an aborted run is recorded and then rejected", async () => {
425
+ const agent = ai.agent("abort-agent", { model: "smart", tools: [], maxSteps: 2 });
426
+ const runtime = createAiRuntime({
427
+ models: [ai.model("smart")],
428
+ agents: [agent],
429
+ clients: {
430
+ smart: {
431
+ driverId: "mock",
432
+ model: "smart",
433
+ async complete() {
434
+ return { text: "ok", raw: {}, model: "smart", driverId: "mock" };
435
+ },
436
+ },
437
+ },
438
+ });
439
+ const ctrl = new AbortController();
440
+ ctrl.abort();
441
+ await expect(
442
+ withAbortSignal(ctrl.signal, () => runtime.runAgent("abort-agent", { message: "go" })),
443
+ ).rejects.toThrow(/aborted/);
444
+ expect(runtime.agentRuns[0]?.stopReason).toBe("aborted");
445
+ });
446
+ });
447
+
192
448
  describe("journaling forced · auto-cache disabled", () => {
193
- test("ask is journaled and replayed without re-calling the model", async () => {
449
+ test("identical asks both reach the model and both are journaled", async () => {
194
450
  let calls = 0;
195
451
  const driver = createMockAiDriver({
196
452
  "*": { urgency: "high", team: "ops" },
@@ -219,8 +475,81 @@ describe("journaling forced · auto-cache disabled", () => {
219
475
  const a = await runtime.ask("ticket-triage", { subject: "x" });
220
476
  const b = await runtime.ask("ticket-triage", { subject: "x" });
221
477
  expect(a).toEqual(b);
478
+ expect(calls).toBe(2);
479
+ expect(runtime.journal).toHaveLength(2);
480
+ });
481
+
482
+ test("ask journal and agent runs keep only the newest observability rows", async () => {
483
+ const smart = ai.model("ring-smart", { provider: "mock" });
484
+ const prompt = smart.prompt("ring-prompt", { version: 1 });
485
+ const runtime = createAiRuntime({
486
+ models: [smart],
487
+ prompts: [prompt],
488
+ agents: [
489
+ ai.agent("ring-agent", {
490
+ model: "ring-smart",
491
+ tools: [],
492
+ maxSteps: 1,
493
+ }),
494
+ ],
495
+ defaultDriver: createMockAiDriver({
496
+ "*": { ok: true },
497
+ }),
498
+ });
499
+
500
+ for (let i = 0; i < AI_OBSERVABILITY_LIMIT + 1; i++) {
501
+ await runtime.ask("ring-prompt", { n: i });
502
+ await runtime.runAgent("ring-agent", { message: `m-${i}` });
503
+ }
504
+ expect(runtime.journal).toHaveLength(AI_OBSERVABILITY_LIMIT);
505
+ expect(runtime.agentRuns).toHaveLength(AI_OBSERVABILITY_LIMIT);
506
+ expect(runtime.journal[0]?.input).toEqual({ n: 1 });
507
+ expect(runtime.journal.at(-1)?.input).toEqual({ n: AI_OBSERVABILITY_LIMIT });
508
+ expect(runtime.agentRuns[0]?.message).toBe("m-1");
509
+ expect(runtime.agentRuns.at(-1)?.message).toBe(`m-${AI_OBSERVABILITY_LIMIT}`);
510
+ });
511
+
512
+ test("a durable journal replays fx.ask without a second model call", async () => {
513
+ let calls = 0;
514
+ const driver = createMockAiDriver({
515
+ "*": { urgency: "high" },
516
+ });
517
+ const base = await driver.open({ model: "mock" });
518
+ const counting = {
519
+ ...base,
520
+ async complete(opts: Parameters<typeof base.complete>[0]) {
521
+ calls++;
522
+ return base.complete(opts);
523
+ },
524
+ };
525
+ const smart = ai.model("durable-smart", { provider: "mock" });
526
+ const triage = smart.prompt("durable-triage", { version: 1 });
527
+ const runtime = createAiRuntime({
528
+ models: [smart],
529
+ prompts: [triage],
530
+ clients: { "durable-smart": counting },
531
+ });
532
+ const journal = createJournal({ store: createMemoryJournalStore() });
533
+ const session = await journal.start("support.triage", { subject: "x" });
534
+ const fx = createFx({
535
+ flow: "support.triage",
536
+ effects: { asks: ["durable-triage"] },
537
+ aiRuntime: runtime,
538
+ journal: session,
539
+ });
540
+ const first = await fx.ask("durable-triage", { subject: "x" });
222
541
  expect(calls).toBe(1);
223
- expect(runtime.journal).toHaveLength(1);
542
+
543
+ const resumed = await journal.resume(session.runId);
544
+ const again = createFx({
545
+ flow: "support.triage",
546
+ effects: { asks: ["durable-triage"] },
547
+ aiRuntime: runtime,
548
+ journal: resumed,
549
+ });
550
+ const second = await again.ask("durable-triage", { subject: "x" });
551
+ expect(calls).toBe(1);
552
+ expect(second).toEqual(first);
224
553
  });
225
554
 
226
555
  test("fx.cache is disabled when aiRuntime is bound", async () => {
@@ -489,6 +818,171 @@ describe("schema-validation is its own class", () => {
489
818
  expect(runtime.journal[0]!.outcome).toBe("schema_invalid");
490
819
  expect(runtime.journal[0]!.outcome).not.toBe("provider_error");
491
820
  });
821
+
822
+ test("repair: 0 throws on the first mismatch", async () => {
823
+ let calls = 0;
824
+ const smart = ai.model("smart", { provider: "mock" });
825
+ const triage = smart.prompt("ticket-triage-norepair", {
826
+ repair: 0,
827
+ out: {
828
+ type: "object",
829
+ properties: { urgency: { type: "string" }, team: { type: "string" } },
830
+ required: ["urgency", "team"],
831
+ },
832
+ });
833
+ const runtime = createAiRuntime({
834
+ models: [smart],
835
+ prompts: [triage],
836
+ clients: {
837
+ smart: {
838
+ driverId: "mock" as const,
839
+ model: "smart",
840
+ async complete() {
841
+ calls++;
842
+ return {
843
+ text: JSON.stringify({ urgency: "high" }),
844
+ model: "smart",
845
+ driverId: "mock" as const,
846
+ };
847
+ },
848
+ },
849
+ },
850
+ });
851
+ await expect(runtime.ask("ticket-triage-norepair", { subject: "x" })).rejects.toBeInstanceOf(
852
+ AiSchemaValidationError,
853
+ );
854
+ expect(calls).toBe(1);
855
+ });
856
+
857
+ test("repair: 1 follows up once and journals both attempts", async () => {
858
+ let calls = 0;
859
+ const smart = ai.model("smart", { provider: "mock" });
860
+ const triage = smart.prompt("ticket-triage-repair", {
861
+ repair: 1,
862
+ out: {
863
+ type: "object",
864
+ properties: { urgency: { type: "string" }, team: { type: "string" } },
865
+ required: ["urgency", "team"],
866
+ },
867
+ });
868
+ const runtime = createAiRuntime({
869
+ models: [smart],
870
+ prompts: [triage],
871
+ clients: {
872
+ smart: {
873
+ driverId: "mock" as const,
874
+ model: "smart",
875
+ async complete(opts: { messages: readonly { role: string; content: string }[] }) {
876
+ calls++;
877
+ const last = opts.messages.at(-1)?.content ?? "";
878
+ if (calls === 1) {
879
+ return {
880
+ text: JSON.stringify({ urgency: "high" }),
881
+ model: "smart",
882
+ driverId: "mock" as const,
883
+ usage: { cost: 0.01 },
884
+ };
885
+ }
886
+ expect(opts.messages).toHaveLength(2);
887
+ expect(opts.messages[0]?.role).toBe("user");
888
+ expect(last).toContain("Schema mismatch");
889
+ return {
890
+ text: JSON.stringify({ urgency: "high", team: "ops" }),
891
+ model: "smart",
892
+ driverId: "mock" as const,
893
+ usage: { cost: 0.01 },
894
+ };
895
+ },
896
+ },
897
+ },
898
+ });
899
+ const output = await runtime.ask("ticket-triage-repair", { subject: "x" });
900
+ expect(output.team).toBe("ops");
901
+ expect(calls).toBe(2);
902
+ expect(runtime.journal.map((entry) => entry.outcome)).toEqual(["schema_invalid", "ok"]);
903
+ });
904
+
905
+ test("a repair that exceeds maxCostPerCall throws and does not return output", async () => {
906
+ let calls = 0;
907
+ const smart = ai.model("smart", { provider: "mock" });
908
+ const triage = smart.prompt("ticket-triage-repair-cap", {
909
+ repair: 1,
910
+ budget: { maxCostPerCall: 0.02 },
911
+ out: {
912
+ type: "object",
913
+ properties: { urgency: { type: "string" }, team: { type: "string" } },
914
+ required: ["urgency", "team"],
915
+ },
916
+ });
917
+ const runtime = createAiRuntime({
918
+ models: [smart],
919
+ prompts: [triage],
920
+ clients: {
921
+ smart: {
922
+ driverId: "mock" as const,
923
+ model: "smart",
924
+ async complete() {
925
+ calls++;
926
+ return {
927
+ text: JSON.stringify(
928
+ calls === 1 ? { urgency: "high" } : { urgency: "high", team: "ops" },
929
+ ),
930
+ model: "smart",
931
+ driverId: "mock" as const,
932
+ usage: { cost: calls === 1 ? 0.01 : 0.05 },
933
+ };
934
+ },
935
+ },
936
+ },
937
+ });
938
+ await expect(runtime.ask("ticket-triage-repair-cap", { subject: "x" })).rejects.toThrow(
939
+ /exceeded maxCostPerCall/,
940
+ );
941
+ expect(calls).toBe(2);
942
+ expect(runtime.journal.map((entry) => entry.outcome)).toEqual([
943
+ "schema_invalid",
944
+ "budget_exceeded",
945
+ ]);
946
+ });
947
+
948
+ test("a second mismatch after repair still throws", async () => {
949
+ let calls = 0;
950
+ const smart = ai.model("smart", { provider: "mock" });
951
+ const triage = smart.prompt("ticket-triage-repair-fail", {
952
+ repair: 1,
953
+ out: {
954
+ type: "object",
955
+ properties: { urgency: { type: "string" }, team: { type: "string" } },
956
+ required: ["urgency", "team"],
957
+ },
958
+ });
959
+ const runtime = createAiRuntime({
960
+ models: [smart],
961
+ prompts: [triage],
962
+ clients: {
963
+ smart: {
964
+ driverId: "mock" as const,
965
+ model: "smart",
966
+ async complete() {
967
+ calls++;
968
+ return {
969
+ text: JSON.stringify({ urgency: "high" }),
970
+ model: "smart",
971
+ driverId: "mock" as const,
972
+ };
973
+ },
974
+ },
975
+ },
976
+ });
977
+ await expect(runtime.ask("ticket-triage-repair-fail", { subject: "x" })).rejects.toBeInstanceOf(
978
+ AiSchemaValidationError,
979
+ );
980
+ expect(calls).toBe(2);
981
+ expect(runtime.journal.map((entry) => entry.outcome)).toEqual([
982
+ "schema_invalid",
983
+ "schema_invalid",
984
+ ]);
985
+ });
492
986
  });
493
987
 
494
988
  describe("agent tool trail carries effects; denials are not errors", () => {
@@ -8,14 +8,24 @@
8
8
  * Prompts are versioned artifacts with validated output shapes and eval sets
9
9
  * (`oke eval` gates CI). Agents' tools are the app's own flows (gates apply).
10
10
  * PII fields cannot reach a third-party model without explicit `allowPii`.
11
- * Nondeterministic ⇒ journaling forced, auto-cache disabled.
11
+ * Nondeterministic ⇒ journaling forced for Console, auto-cache disabled.
12
+ * Replay belongs to a durable run's journal, not this process-wide log.
12
13
  * @module
13
14
  */
14
15
 
15
16
  export { ai, listAiDecls, resetAiDecls } from "./ai/declare.ts";
16
17
  export type {
17
18
  AiAgentDecl,
19
+ AiBooleanQuestion,
20
+ AiChoiceQuestion,
21
+ AiDecisionDecl,
22
+ AiDecisionOptions,
23
+ AiDecisionQuestion,
24
+ AiScoreQuestion,
25
+ AiAgentApprovalDecl,
18
26
  AiAgentOptions,
27
+ AiAgentToolOptions,
28
+ AiToolApproval,
19
29
  AiBudgetDecl,
20
30
  AiEmbedDecl,
21
31
  AiEmbedOptions,
@@ -39,16 +49,21 @@ export {
39
49
  } from "./ai/errors.ts";
40
50
  export type { AiErrorFields } from "./ai/errors.ts";
41
51
 
52
+ export type { AgentEventEmit, AgUiEvent, AgUiUsage } from "./ai/events.ts";
53
+ export { createEventQueue, emitAssistantText } from "./ai/events.ts";
54
+
42
55
  export {
43
56
  createAiRuntime,
44
57
  parsePromptRef,
45
58
  AiSchemaValidationError,
46
59
  promptContentFromInput,
47
60
  AI_DEFAULT_MAX_STEPS,
61
+ AI_OBSERVABILITY_LIMIT,
48
62
  } from "./ai/runtime.ts";
49
63
  export type {
50
64
  AgentDenial,
51
65
  AgentRunRecord,
66
+ AgentStopReason,
52
67
  AgentToolEffect,
53
68
  AgentToolStep,
54
69
  AiAgentRunOptions,
@@ -11,6 +11,7 @@ import { failureCodeOf, rebindUndosFromDo, runCompensationPhase } from "../../ke
11
11
  import type { AnyFlowDef } from "../../kernel/flow.ts";
12
12
  import { createFxContext, type CreateFxOptions, type Fx } from "../../kernel/fx.ts";
13
13
  import { isFlowFailure } from "../../kernel/hooks.ts";
14
+ import { isJsonStreamResult } from "../../kernel/json-result.ts";
14
15
  import {
15
16
  createJournal,
16
17
  isJournalSuspend,
@@ -142,7 +143,32 @@ export async function runDurable<O = unknown>(
142
143
  session.rewind();
143
144
  return options.flow.do(options.input as never, fx);
144
145
  };
145
- const output = await (options.flow.retry ? fxRetry(run, options.flow.retry) : run());
146
+ const produced = await (options.flow.retry ? fxRetry(run, options.flow.retry) : run());
147
+ if (isJsonStreamResult(produced)) {
148
+ try {
149
+ for await (const chunk of produced.chunks) {
150
+ void chunk;
151
+ }
152
+ } catch (err) {
153
+ if (isJournalSuspend(err)) {
154
+ return {
155
+ status: "sleeping",
156
+ runId: session.runId,
157
+ wakeAt: err.wakeAt,
158
+ label: err.label,
159
+ };
160
+ }
161
+ throw err;
162
+ }
163
+ const settled = await produced.result;
164
+ await session.commit("completed", { output: settled });
165
+ return {
166
+ status: "completed",
167
+ runId: session.runId,
168
+ output: settled as O,
169
+ };
170
+ }
171
+ const output = produced;
146
172
  if (isFlowFailure(output)) {
147
173
  await runCompensationPhase({
148
174
  flow: options.flow,
@@ -519,12 +519,12 @@ function emitTableBlock(table: SchemaTableDecl, dialect: SqlDialect): string {
519
519
  const exportName = exportNameForTable(sqlName);
520
520
  const cols = `{\n${lines.join("\n")}\n}`;
521
521
  const policies = table.policies ?? [];
522
+ const ctor = table.rls === true ? "pgTable.withRLS" : "pgTable";
522
523
  if (policies.length === 0) {
523
- const ctor = table.rls === true ? "pgTable.withRLS" : "pgTable";
524
524
  return `export const ${exportName} = ${ctor}(${JSON.stringify(sqlName)}, ${cols});`;
525
525
  }
526
526
  const extras = policies.map((policy) => ` ${emitPolicyExtra(policy, sqlName)},`).join("\n");
527
- return `export const ${exportName} = pgTable(${JSON.stringify(sqlName)}, ${cols}, (_t) => [\n${extras}\n]);`;
527
+ return `export const ${exportName} = ${ctor}(${JSON.stringify(sqlName)}, ${cols}, (_t) => [\n${extras}\n]);`;
528
528
  }
529
529
 
530
530
  function emitPolicyExtra(policy: SchemaPolicyDecl, table: string): string {
@@ -88,6 +88,7 @@ describe("project-wide store.live default — real boot", () => {
88
88
  });
89
89
  try {
90
90
  await createTestApp(app, {
91
+ capability: "open",
91
92
  boot: {
92
93
  stores: [db],
93
94
  config: { drivers: { store: { sql: { test: "pglite" } } } },
@@ -127,6 +128,7 @@ describe("project-wide store.live default — real boot", () => {
127
128
  });
128
129
  try {
129
130
  await createTestApp(app, {
131
+ capability: "open",
130
132
  boot: {
131
133
  stores: [db],
132
134
  config: { drivers: { store: { sql: { test: "pglite" } } } },
@@ -124,6 +124,7 @@ describe("manual live flow (.live(table) + liveQuery) — real boot", () => {
124
124
  // SQL unless boot.config.drivers.store.sql is set explicitly.
125
125
  try {
126
126
  await createTestApp(app, {
127
+ capability: "open",
127
128
  boot: {
128
129
  stores: [db],
129
130
  config: { drivers: { store: { sql: { test: "pglite" } } } },
@@ -40,7 +40,7 @@ describe("resource-list-docs fixtures — documented behaviors stay real", () =>
40
40
  // (and cleared by the global afterEach registry reset).
41
41
  Object.assign(app.$options, { env: "test", stores: [docsListItemsStore] });
42
42
 
43
- const t = await createTestApp(app);
43
+ const t = await createTestApp(app, { capability: "open" });
44
44
 
45
45
  const bad = await app.fetch(new Request("http://localhost/docs-restricted?secret=eq.x"));
46
46
  expect(bad.status).toBe(422);
@@ -70,7 +70,7 @@ describe("resource-list-docs fixtures — documented behaviors stay real", () =>
70
70
  const app = oke({ name: "docs-admin-test" }).adopt({ docs: exactMounted });
71
71
  Object.assign(app.$options, { env: "test", stores: [docsListItemsStore] });
72
72
 
73
- const t = await createTestApp(app);
73
+ const t = await createTestApp(app, { capability: "open" });
74
74
  const created = await app.fetch(
75
75
  new Request("http://localhost/docs-admin", {
76
76
  method: "POST",