okengine 0.18.5 → 0.19.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 (510) hide show
  1. package/AGENTS.md +18 -10
  2. package/README.md +2 -2
  3. package/package.json +51 -50
  4. package/site/content/docs/ai/llms-txt.mdx +1 -1
  5. package/site/content/docs/ai/meta.json +1 -1
  6. package/site/content/docs/ai/skills.mdx +4 -1
  7. package/site/content/docs/ai/try-it.mdx +58 -0
  8. package/site/content/docs/client/auth.mdx +208 -0
  9. package/site/content/docs/client/calling.mdx +451 -0
  10. package/site/content/docs/client/index.mdx +209 -0
  11. package/site/content/docs/client/live.mdx +234 -0
  12. package/site/content/docs/client/meta.json +5 -0
  13. package/site/content/docs/client/react.mdx +249 -0
  14. package/site/content/docs/elements/ai/agents.mdx +232 -0
  15. package/site/content/docs/elements/ai/index.mdx +341 -0
  16. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  17. package/site/content/docs/elements/ai/meta.json +5 -0
  18. package/site/content/docs/elements/ai/models.mdx +283 -0
  19. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  20. package/site/content/docs/elements/channel/email.mdx +266 -0
  21. package/site/content/docs/elements/channel/index.mdx +344 -0
  22. package/site/content/docs/elements/channel/meta.json +5 -0
  23. package/site/content/docs/elements/channel/push.mdx +218 -0
  24. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  25. package/site/content/docs/elements/channel/sms.mdx +264 -0
  26. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  27. package/site/content/docs/elements/clock/index.mdx +300 -0
  28. package/site/content/docs/elements/clock/meta.json +5 -0
  29. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  30. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  31. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  32. package/site/content/docs/elements/flow/http.mdx +1080 -0
  33. package/site/content/docs/elements/flow/index.mdx +618 -0
  34. package/site/content/docs/elements/flow/meta.json +5 -0
  35. package/site/content/docs/elements/flow/routing.mdx +559 -0
  36. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  37. package/site/content/docs/elements/gate/auth.mdx +371 -0
  38. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  39. package/site/content/docs/elements/gate/index.mdx +418 -0
  40. package/site/content/docs/elements/gate/meta.json +5 -0
  41. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  42. package/site/content/docs/elements/gate/rls.mdx +303 -0
  43. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  44. package/site/content/docs/elements/index.mdx +20 -10
  45. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  46. package/site/content/docs/elements/signal/index.mdx +352 -0
  47. package/site/content/docs/elements/signal/live.mdx +590 -0
  48. package/site/content/docs/elements/signal/meta.json +5 -0
  49. package/site/content/docs/elements/signal/once.mdx +596 -0
  50. package/site/content/docs/elements/store/files.mdx +659 -0
  51. package/site/content/docs/elements/store/index.mdx +310 -0
  52. package/site/content/docs/elements/store/kv.mdx +629 -0
  53. package/site/content/docs/elements/store/meta.json +5 -0
  54. package/site/content/docs/elements/store/search.mdx +946 -0
  55. package/site/content/docs/elements/store/sql.mdx +937 -0
  56. package/site/content/docs/elements/vault/config.mdx +256 -0
  57. package/site/content/docs/elements/vault/index.mdx +356 -0
  58. package/site/content/docs/elements/vault/meta.json +5 -0
  59. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  60. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  61. package/site/content/docs/index.mdx +63 -36
  62. package/site/content/docs/meta.json +8 -7
  63. package/site/content/docs/plugins/cors.mdx +2 -1
  64. package/site/content/docs/plugins/csrf.mdx +4 -1
  65. package/site/content/docs/plugins/headers.mdx +2 -1
  66. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  67. package/site/content/docs/plugins/magic-link.mdx +6 -0
  68. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  69. package/site/content/docs/plugins/oauth.mdx +1 -1
  70. package/site/content/docs/plugins/otp.mdx +12 -1
  71. package/site/content/docs/plugins/passkey.mdx +39 -15
  72. package/site/content/docs/plugins/two-factor.mdx +91 -34
  73. package/site/content/docs/plugins/username.mdx +1 -1
  74. package/site/content/docs/recipes/caddy.mdx +3 -3
  75. package/site/content/docs/recipes/index.mdx +18 -28
  76. package/site/content/docs/recipes/mailpit.mdx +1 -1
  77. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  78. package/site/content/docs/recipes/meta.json +1 -4
  79. package/site/content/docs/recipes/nginx.mdx +2 -2
  80. package/site/content/docs/recipes/openrouter.mdx +283 -0
  81. package/site/content/docs/recipes/pgdog.mdx +3 -3
  82. package/site/content/docs/recipes/postgres.mdx +1 -2
  83. package/site/content/docs/recipes/rustfs.mdx +2 -2
  84. package/site/content/docs/recipes/traefik.mdx +3 -3
  85. package/site/content/docs/reference/cli.mdx +231 -0
  86. package/site/content/docs/reference/configuration.mdx +30 -10
  87. package/site/content/docs/reference/environment-variables.mdx +93 -20
  88. package/site/content/docs/reference/errors.mdx +132 -29
  89. package/site/content/docs/reference/fx.mdx +137 -73
  90. package/site/content/docs/reference/index.mdx +13 -21
  91. package/site/content/docs/reference/meta.json +4 -5
  92. package/site/content/docs/reference/okid.mdx +42 -17
  93. package/site/content/docs/reference/plugins.mdx +4 -3
  94. package/site/content/docs/reference/security.mdx +197 -0
  95. package/site/content/docs/understand/meta.json +5 -0
  96. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  97. package/site/content/docs/understand/the-model.mdx +32 -0
  98. package/site/content/docs/understand/the-problem.mdx +74 -0
  99. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  100. package/src/auth/api-keys.ts +2 -1
  101. package/src/auth/bindings.ts +64 -16
  102. package/src/auth/gate-auth.test.ts +6 -1
  103. package/src/auth/identity.ts +152 -5
  104. package/src/auth/index.ts +29 -0
  105. package/src/auth/invites.ts +2 -1
  106. package/src/auth/method-context.ts +41 -0
  107. package/src/auth/oauth-as/crypto.ts +2 -1
  108. package/src/auth/oauth-as/stores.ts +2 -1
  109. package/src/auth/operator.ts +2 -1
  110. package/src/auth/sessions-jwt.test.ts +77 -0
  111. package/src/auth/sessions.ts +20 -4
  112. package/src/auth/tenants.ts +3 -2
  113. package/src/auth/two-factor-challenge.test.ts +61 -0
  114. package/src/auth/two-factor-challenge.ts +284 -0
  115. package/src/auth/verification.ts +5 -0
  116. package/src/bench/REPORT.md +49 -0
  117. package/src/bench/g03-signal-once.bench.ts +1 -1
  118. package/src/bench/g10-observability-contention.bench.ts +1 -1
  119. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  120. package/src/bench/load-app.ts +2 -10
  121. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  122. package/src/cli/ai-setup/apply.ts +294 -52
  123. package/src/cli/ai-setup/catalog.ts +238 -1342
  124. package/src/cli/ai-setup/index.ts +14 -99
  125. package/src/cli/ai-setup/prompts.ts +60 -443
  126. package/src/cli/ask-seed.test.ts +96 -0
  127. package/src/cli/ask-seed.ts +82 -0
  128. package/src/cli/ask-vault-gaps.test.ts +84 -0
  129. package/src/cli/ask-vault-gaps.ts +146 -0
  130. package/src/cli/build.ts +2 -2
  131. package/src/cli/client-add.test.ts +26 -1
  132. package/src/cli/client-add.ts +106 -27
  133. package/src/cli/db-seed.ts +2 -0
  134. package/src/cli/db.ts +150 -11
  135. package/src/cli/dev.ts +119 -211
  136. package/src/cli/docker-clean.ts +2 -2
  137. package/src/cli/docker-cli.test.ts +1 -1
  138. package/src/cli/doctor-diff.ts +4 -2
  139. package/src/cli/doctor-pii.test.ts +1 -1
  140. package/src/cli/doctor.ts +14 -1
  141. package/src/cli/hero-meta.test.ts +4 -2
  142. package/src/cli/load-config.images.test.ts +8 -10
  143. package/src/cli/load-config.ts +1 -1
  144. package/src/cli/registry.ts +22 -10
  145. package/src/cli/replay.ts +3 -1
  146. package/src/cli/start.ts +1 -1
  147. package/src/cli/tui/keys.ts +2 -3
  148. package/src/client/auth/cookies.ts +74 -0
  149. package/src/client/auth/create-auth-client.ts +636 -0
  150. package/src/client/auth/denials.ts +99 -0
  151. package/src/client/auth/session.ts +283 -0
  152. package/src/client/auth.test.ts +251 -0
  153. package/src/client/auth.ts +39 -114
  154. package/src/client/create-with-session.ts +245 -0
  155. package/src/client/create.ts +51 -5
  156. package/src/client/index.ts +11 -1
  157. package/src/client/live.ts +11 -79
  158. package/src/client/notes-contract.test.ts +5 -10
  159. package/src/client/sse.ts +137 -0
  160. package/src/client/stream.ts +147 -0
  161. package/src/client/transport.test.ts +32 -0
  162. package/src/client/transport.ts +123 -26
  163. package/src/client/types.ts +118 -14
  164. package/src/client-react/index.ts +185 -24
  165. package/src/client-react/use-live-query.ts +13 -3
  166. package/src/compiler/aot.test.ts +7 -4
  167. package/src/compiler/effects-embed.test.ts +56 -0
  168. package/src/compiler/effects-fetch.test.ts +43 -0
  169. package/src/compiler/effects-infer.ts +58 -2
  170. package/src/compiler/extract.test.ts +257 -43
  171. package/src/compiler/extract.ts +566 -48
  172. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  173. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  174. package/src/compiler/fixtures/skyport.expected.json +4 -4
  175. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  176. package/src/compiler/response.ts +2 -2
  177. package/src/compiler/search-writer-isolation.test.ts +40 -0
  178. package/src/config/index.ts +11 -0
  179. package/src/console/server/app.ts +17 -23
  180. package/src/console/server/bind.ts +4 -0
  181. package/src/console/server/console.test.ts +2 -2
  182. package/src/console/server/flows-invoke.test.ts +50 -34
  183. package/src/console/server/flows.ts +436 -255
  184. package/src/console/server/runs-ingest.test.ts +6 -3
  185. package/src/console/server/serve.ts +1 -1
  186. package/src/console/server/signals.test.ts +1 -5
  187. package/src/console/server/signals.ts +8 -6
  188. package/src/console/server/state.ts +6 -1
  189. package/src/console/server/store.ts +1 -1
  190. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  191. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  192. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  193. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  194. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  195. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  196. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  197. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  198. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  199. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  200. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  201. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  202. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  203. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  204. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  205. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  206. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  207. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  208. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  209. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  210. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  211. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  212. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  213. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  214. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  215. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  216. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  217. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  218. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  219. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  220. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  221. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  222. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  223. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  224. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  225. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  226. package/src/console/ui-next/dist/index.html +6 -13
  227. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  228. package/src/console/ui-next/src/client.ts +7 -2
  229. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  230. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  231. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  232. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  233. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  235. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  236. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  237. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  238. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  239. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  240. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  241. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  242. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  243. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  244. package/src/docker/ai-model-status.test.ts +1 -12
  245. package/src/docker/ai-model-status.ts +10 -38
  246. package/src/docker/compose-up.test.ts +100 -0
  247. package/src/docker/compose-up.ts +292 -0
  248. package/src/docker/compose.ts +4 -14
  249. package/src/docker/derive.ts +9 -34
  250. package/src/docker/docker.test.ts +16 -261
  251. package/src/docker/images-config.test.ts +13 -25
  252. package/src/docker/index.ts +1 -24
  253. package/src/docker/recipes/index.ts +0 -23
  254. package/src/docker/recipes/pgdog.ts +6 -4
  255. package/src/docker/stack-id.test.ts +2 -2
  256. package/src/docker/stack-id.ts +0 -1
  257. package/src/docker/types.ts +2 -2
  258. package/src/drivers/ai-anthropic.ts +10 -0
  259. package/src/drivers/ai-openai-compatible.ts +34 -7
  260. package/src/drivers/ai-providers.test.ts +0 -107
  261. package/src/drivers/ai-stream.test.ts +0 -34
  262. package/src/drivers/ai-types.ts +17 -8
  263. package/src/drivers/channel-fcm.ts +6 -1
  264. package/src/drivers/channel-msegat.ts +6 -1
  265. package/src/drivers/channel-resend.ts +5 -1
  266. package/src/drivers/channel-smtp.ts +5 -1
  267. package/src/drivers/channel-sndr.ts +10 -1
  268. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  269. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  270. package/src/drivers/channel-taqnyat.ts +6 -1
  271. package/src/drivers/channel-types.ts +10 -0
  272. package/src/drivers/channel-unifonic.ts +6 -1
  273. package/src/drivers/channel-wa-cloud.ts +6 -1
  274. package/src/drivers/channel-webpush.ts +6 -1
  275. package/src/drivers/external.ts +33 -0
  276. package/src/drivers/index.ts +0 -10
  277. package/src/drivers/meilisearch.ts +6 -0
  278. package/src/drivers/oauth-types.ts +4 -0
  279. package/src/drivers/postgres.test.ts +74 -3
  280. package/src/drivers/postgres.ts +175 -6
  281. package/src/drivers/signal-types.ts +5 -5
  282. package/src/drivers/types.ts +7 -0
  283. package/src/drivers/vault-types.ts +5 -0
  284. package/src/elements/ai/declare.ts +17 -4
  285. package/src/elements/ai/eval.ts +1 -1
  286. package/src/elements/ai/mcp-http.ts +1 -1
  287. package/src/elements/ai/pii.ts +1 -1
  288. package/src/elements/ai/providers.test.ts +289 -0
  289. package/src/elements/ai/providers.ts +169 -0
  290. package/src/elements/ai/runtime.ts +68 -5
  291. package/src/elements/ai/schema.ts +2 -2
  292. package/src/elements/ai.test.ts +7 -7
  293. package/src/elements/ai.ts +17 -1
  294. package/src/elements/channel/runtime.ts +105 -12
  295. package/src/elements/clock/cron-fields.test.ts +144 -0
  296. package/src/elements/clock/cron-fields.ts +185 -0
  297. package/src/elements/clock/declare.ts +247 -3
  298. package/src/elements/clock.ts +21 -1
  299. package/src/elements/index.ts +15 -0
  300. package/src/elements/signal/chaos-child.ts +1 -2
  301. package/src/elements/signal/declare.ts +65 -37
  302. package/src/elements/signal/delivery-modes.test.ts +11 -31
  303. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  304. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  305. package/src/elements/signal/key-ordering.test.ts +6 -26
  306. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  307. package/src/elements/signal/optional-emit.test.ts +6 -9
  308. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  309. package/src/elements/signal/orphan-messages.test.ts +4 -15
  310. package/src/elements/signal/reconcile.test.ts +2 -2
  311. package/src/elements/signal/runtime.ts +1 -1
  312. package/src/elements/signal/schema-emit.test.ts +4 -8
  313. package/src/elements/signal.test.ts +24 -49
  314. package/src/elements/signal.ts +9 -2
  315. package/src/elements/store/domain-ddl.test.ts +1 -1
  316. package/src/elements/store/live-http.test.ts +9 -4
  317. package/src/elements/store/prepare-row.test.ts +123 -0
  318. package/src/elements/store/resource.ts +43 -12
  319. package/src/elements/store/schema-decl.ts +117 -0
  320. package/src/elements/store/search-backfill.ts +205 -0
  321. package/src/elements/store/search-bind.ts +66 -0
  322. package/src/elements/store/search-bm25.ts +78 -0
  323. package/src/elements/store/search-ddl.ts +156 -0
  324. package/src/elements/store/search-embed-flow.ts +141 -0
  325. package/src/elements/store/search-errors.ts +34 -0
  326. package/src/elements/store/search-fusion.ts +112 -0
  327. package/src/elements/store/search-lsh.ts +179 -0
  328. package/src/elements/store/search-runtime.ts +300 -0
  329. package/src/elements/store/search.test.ts +144 -0
  330. package/src/elements/store/sql-session.test.ts +1 -0
  331. package/src/elements/store/sql-session.ts +83 -3
  332. package/src/elements/store/table.ts +23 -2
  333. package/src/elements/store/upsert-app.test.ts +1 -3
  334. package/src/elements/store.ts +50 -0
  335. package/src/full.ts +4 -2
  336. package/src/http.ts +4 -1
  337. package/src/i18n/catalogs/ar.ts +14 -6
  338. package/src/i18n/catalogs/en.ts +14 -6
  339. package/src/index.ts +6 -1
  340. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  341. package/src/kernel/adopt-routes.ts +9 -1
  342. package/src/kernel/app-auth.ts +5 -0
  343. package/src/kernel/app.ts +102 -9
  344. package/src/kernel/auto-cache.test.ts +6 -12
  345. package/src/kernel/auto-registry.test.ts +3 -3
  346. package/src/kernel/boot-bind/ai.test.ts +22 -19
  347. package/src/kernel/boot-bind/ai.ts +5 -43
  348. package/src/kernel/boot-bind/clock.ts +23 -5
  349. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  350. package/src/kernel/boot.test.ts +11 -6
  351. package/src/kernel/boot.ts +66 -16
  352. package/src/kernel/boundary-contract.ts +93 -0
  353. package/src/kernel/budget.test.ts +1 -1
  354. package/src/kernel/call.ts +89 -0
  355. package/src/kernel/capability.ts +6 -0
  356. package/src/kernel/client-descriptor.ts +123 -0
  357. package/src/kernel/clock-timezone.test.ts +80 -0
  358. package/src/kernel/correlation.test.ts +1 -1
  359. package/src/kernel/dry-run.ts +11 -8
  360. package/src/kernel/effects-stamping.test.ts +42 -9
  361. package/src/kernel/effects.test.ts +4 -2
  362. package/src/kernel/effects.ts +51 -9
  363. package/src/kernel/errors-channel.ts +17 -0
  364. package/src/kernel/errors-live-resume.ts +3 -2
  365. package/src/kernel/errors-tenant.ts +7 -4
  366. package/src/kernel/errors.registry-helpers.ts +96 -0
  367. package/src/kernel/errors.registry.test.ts +139 -25
  368. package/src/kernel/errors.ts +102 -23
  369. package/src/kernel/external-effects.test.ts +196 -0
  370. package/src/kernel/flow.test.ts +13 -4
  371. package/src/kernel/flow.ts +69 -71
  372. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  373. package/src/kernel/fx-dead-letters.test.ts +3 -3
  374. package/src/kernel/fx-fetch.ts +55 -0
  375. package/src/kernel/fx-live-stream.ts +2 -2
  376. package/src/kernel/fx-live.test.ts +8 -8
  377. package/src/kernel/fx.test.ts +17 -11
  378. package/src/kernel/fx.ts +180 -57
  379. package/src/kernel/horizontal-child.ts +1 -1
  380. package/src/kernel/http-query.test.ts +3 -6
  381. package/src/kernel/index.ts +13 -3
  382. package/src/kernel/instance-id.ts +1 -1
  383. package/src/kernel/instances.test.ts +40 -0
  384. package/src/kernel/instances.ts +47 -9
  385. package/src/kernel/live-http.test.ts +6 -7
  386. package/src/kernel/live-http.ts +6 -2
  387. package/src/kernel/live-resume.test.ts +1 -1
  388. package/src/kernel/mcp-tool.test.ts +3 -3
  389. package/src/kernel/on.ts +98 -4
  390. package/src/kernel/pipeline.test.ts +12 -15
  391. package/src/kernel/plugin-elements.test.ts +1 -1
  392. package/src/kernel/stamp-http.test.ts +3 -3
  393. package/src/kernel/triggers.ts +210 -95
  394. package/src/kernel-entry.ts +0 -1
  395. package/src/manifest/diff.ts +11 -1
  396. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  397. package/src/manifest/types.ts +46 -2
  398. package/src/mcp/docs-index.ts +3 -3
  399. package/src/mcp/docs-mcp.test.ts +2 -2
  400. package/src/mcp/docs-tools.ts +1 -1
  401. package/src/okid.test.ts +56 -0
  402. package/src/okid.ts +55 -11
  403. package/src/plugins/anonymous.ts +8 -4
  404. package/src/plugins/auth/shared.ts +27 -3
  405. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  406. package/src/plugins/auth-methods.security.test.ts +561 -23
  407. package/src/plugins/config-source.ts +1 -1
  408. package/src/plugins/csrf.test.ts +65 -0
  409. package/src/plugins/index.ts +2 -0
  410. package/src/plugins/magic-link.ts +36 -16
  411. package/src/plugins/oauth/shared.ts +3 -1
  412. package/src/plugins/oauth.ts +22 -14
  413. package/src/plugins/otp.ts +37 -21
  414. package/src/plugins/passkey-webauthn.ts +9 -6
  415. package/src/plugins/passkey.ts +98 -26
  416. package/src/plugins/pre-account-hijack.test.ts +223 -0
  417. package/src/plugins/two-factor.ts +466 -37
  418. package/src/plugins/username.ts +19 -7
  419. package/src/release/build-lib.ts +1 -0
  420. package/src/release/limits.ts +2 -2
  421. package/src/release/measure.ts +1 -1
  422. package/src/runs/collect.test.ts +0 -2
  423. package/src/runtime/json-code-block.test.ts +250 -20
  424. package/src/runtime/json-code-block.ts +1261 -41
  425. package/src/term.test.ts +1 -1
  426. package/src/test/create-test-app.test.ts +12 -12
  427. package/src/test/live-signals.test.ts +6 -6
  428. package/src/test/provisions.integration.test.ts +10 -14
  429. package/src/test/reset-element-registries.ts +1 -1
  430. package/src/test/tenant-isolation.test.ts +12 -6
  431. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  432. package/site/content/docs/deployment/docker.mdx +0 -227
  433. package/site/content/docs/deployment/index.mdx +0 -83
  434. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  435. package/site/content/docs/deployment/meta.json +0 -5
  436. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  437. package/site/content/docs/elements/ai.mdx +0 -385
  438. package/site/content/docs/elements/channel.mdx +0 -346
  439. package/site/content/docs/elements/clock.mdx +0 -244
  440. package/site/content/docs/elements/flow.mdx +0 -420
  441. package/site/content/docs/elements/gate.mdx +0 -436
  442. package/site/content/docs/elements/signal.mdx +0 -380
  443. package/site/content/docs/elements/store.mdx +0 -1099
  444. package/site/content/docs/elements/vault.mdx +0 -405
  445. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  446. package/site/content/docs/get-started/index.mdx +0 -43
  447. package/site/content/docs/get-started/installation.mdx +0 -220
  448. package/site/content/docs/get-started/introduction.mdx +0 -144
  449. package/site/content/docs/get-started/meta.json +0 -13
  450. package/site/content/docs/get-started/project-structure.mdx +0 -925
  451. package/site/content/docs/get-started/testing.mdx +0 -328
  452. package/site/content/docs/get-started/why.mdx +0 -114
  453. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  454. package/site/content/docs/recipes/ollama.mdx +0 -142
  455. package/site/content/docs/recipes/sglang.mdx +0 -105
  456. package/site/content/docs/recipes/vllm.mdx +0 -106
  457. package/site/content/docs/reference/cli.md +0 -232
  458. package/site/content/docs/reference/client.mdx +0 -469
  459. package/site/content/docs/reference/security.md +0 -74
  460. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  461. package/src/cli/ai-setup/recommend.test.ts +0 -225
  462. package/src/cli/ai-setup/recommend.ts +0 -225
  463. package/src/cli/dev-controls.test.ts +0 -122
  464. package/src/cli/dev-controls.ts +0 -164
  465. package/src/cli/tui/DevLive.tsx +0 -126
  466. package/src/console/ui-next/dist/assets/access-page-3-EFj-2G.js +0 -4
  467. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  468. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  469. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  470. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  471. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  472. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  473. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  474. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  475. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  476. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  477. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  478. package/src/console/ui-next/dist/assets/flows-page-cVFnA4HH.js +0 -1
  479. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  480. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  481. package/src/console/ui-next/dist/assets/index-CMIgUbD0.js +0 -66
  482. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  483. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  484. package/src/console/ui-next/dist/assets/observability-page-CQ3p34ip.js +0 -4
  485. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  486. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  487. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  488. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  489. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  490. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  491. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  492. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  493. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  494. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  495. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  496. package/src/console/ui-next/dist/assets/units-page-l8FeKfnP.js +0 -1
  497. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  498. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  499. package/src/console/ui-next/dist/assets/vault-page-CL-d_mLE.js +0 -2
  500. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  501. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  502. package/src/docker/ollama-pull.ts +0 -232
  503. package/src/docker/recipes/llama-cpp.ts +0 -298
  504. package/src/docker/recipes/ollama.ts +0 -44
  505. package/src/docker/recipes/sglang.ts +0 -55
  506. package/src/docker/recipes/vllm.ts +0 -44
  507. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  508. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  509. package/src/drivers/ai-ollama.ts +0 -389
  510. package/src/drivers/ollama.ts +0 -14
package/src/cli/dev.ts CHANGED
@@ -48,7 +48,6 @@ import {
48
48
  } from "../term.ts";
49
49
  import { clientAdd } from "./client-add.ts";
50
50
  import { resolveDriverId } from "../config/index.ts";
51
- import type { DevComposeControlAction } from "./dev-controls.ts";
52
51
  import {
53
52
  createDebouncedRunner,
54
53
  isDomainSchemaWatchPath,
@@ -498,10 +497,12 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
498
497
  let pendingAiModelWatch: {
499
498
  readonly url: string;
500
499
  readonly model: string;
501
- readonly kind: "openai-compatible" | "ollama";
500
+ readonly kind: "openai-compatible";
502
501
  } | null = null;
503
502
  /** Hero AI status dot (yellow while model loads). */
504
503
  let heroAiStatus: DevStatus | undefined;
504
+ /** Compose AI service name when `images.ai` is pinned — undefined for cloud AI. */
505
+ let stackAiServiceName: string | undefined;
505
506
  let loadedConfig: Awaited<ReturnType<typeof loadOkeConfig>>["config"] | null = null;
506
507
  try {
507
508
  loadedConfig = (await loadOkeConfig(cwd)).config;
@@ -628,24 +629,42 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
628
629
  const up =
629
630
  options.composeUp ??
630
631
  (async (files, dir) => {
631
- const args = ["compose", ...files.flatMap((f) => ["-f", f]), "up", "-d"];
632
- const proc = Bun.spawn(["docker", ...args], {
632
+ const {
633
+ runComposeUp,
634
+ formatComposeUpEventMessage,
635
+ composeUpPhaseStatus,
636
+ shortenComposeTarget,
637
+ } = await import("../docker/compose-up.ts");
638
+ await runComposeUp({
639
+ files,
633
640
  cwd: dir,
634
- stdout: "pipe",
635
- stderr: "pipe",
636
- env: { ...process.env, ...stackEnv! },
641
+ env: stackEnv!,
642
+ onEvent: (event) => {
643
+ const message = formatComposeUpEventMessage(event);
644
+ if (!message) return;
645
+ const status = event.phase
646
+ ? composeUpPhaseStatus(event.phase)
647
+ : ("pending" as const);
648
+ const perService =
649
+ event.target &&
650
+ event.phase &&
651
+ event.phase !== "other" &&
652
+ (event.kind === undefined ||
653
+ event.kind === "container" ||
654
+ event.kind === "image" ||
655
+ event.kind === "service");
656
+ if (perService && event.target) {
657
+ const key = `compose:${shortenComposeTarget(event.target)}`;
658
+ bootProgress.set(key, formatStatusLine(message, undefined, status));
659
+ return;
660
+ }
661
+ // Download / network / misc — one activity row so pulls never look frozen.
662
+ bootProgress.set(
663
+ "compose:activity",
664
+ formatStatusLine(message, undefined, status === "error" ? "error" : "pending"),
665
+ );
666
+ },
637
667
  });
638
- const [stdout, stderr, code] = await Promise.all([
639
- new Response(proc.stdout).text(),
640
- new Response(proc.stderr).text(),
641
- proc.exited,
642
- ]);
643
- if (code !== 0) {
644
- const detail = [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
645
- throw new Error(
646
- `oke dev: docker compose exited ${code}` + (detail ? `\n${detail}` : ""),
647
- );
648
- }
649
668
  });
650
669
  bootProgress.set("compose", formatStatusLine("docker compose up…", undefined, "pending"));
651
670
  // Compose files live under docker/; cwd for compose is that directory.
@@ -661,6 +680,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
661
680
 
662
681
  // Stream compose health until infra is up (AI may stay pending — model load).
663
682
  const aiServiceName = derived.specs.find((s) => s.role === "ai")?.serviceName;
683
+ stackAiServiceName = aiServiceName;
664
684
  const labelForService = (serviceName: string): string => {
665
685
  const spec = derived.specs.find((s) => s.serviceName === serviceName);
666
686
  return spec ? roleLabel(spec.role) : serviceName;
@@ -689,9 +709,10 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
689
709
  cwd: dockerOut,
690
710
  env: dockerStarted.env,
691
711
  run: composeHealthRun,
692
- timeoutMs: 20_000,
712
+ // Postgres init + PgDog health can exceed 20s on a cold volume.
713
+ timeoutMs: 90_000,
693
714
  isDone: (map) => {
694
- // Empty ps (compose just-created / injectable gap): do not spin 20s.
715
+ // Empty ps (compose just-created / injectable gap): do not spin forever.
695
716
  if (map.size === 0) return true;
696
717
  return [...map.entries()].every(
697
718
  ([name, s]) => name === aiServiceName || s === "ready" || s === "error",
@@ -712,73 +733,8 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
712
733
  };
713
734
  }
714
735
 
715
- // AI model: Ollama pulls via HTTP (blocking with progress). OpenAI-compatible
716
- // servers (llama.cpp / vLLM / SGLang) load in-container — poll readiness in
717
- // the background. Never treat compose "healthy" as model-ready.
718
- const aiSpec = derived.specs.find((s) => s.role === "ai");
719
- if (aiSpec) {
720
- const { recipeFor } = await import("../docker/recipes/index.ts");
721
- const recipeId = recipeFor(aiSpec.image).id;
722
- const model =
723
- stackEnv!.OKE_AI_MODEL?.trim() ||
724
- process.env.OKE_AI_MODEL?.trim() ||
725
- (recipeId === "ollama" ? "qwen3.5:9b" : "granite3.3:2b");
726
- const url = `http://127.0.0.1:${aiSpec.hostPort}`;
727
- const paintAi = (status: DevStatus, detail?: string) => {
728
- heroAiStatus = status;
729
- if (!pendingStackSummary) return;
730
- pendingStackSummary = {
731
- ...pendingStackSummary,
732
- services: pendingStackSummary.services.map((svc) =>
733
- svc.serviceName === aiSpec.serviceName
734
- ? {
735
- ...svc,
736
- status,
737
- detail: detail ?? svc.detail,
738
- }
739
- : svc,
740
- ),
741
- };
742
- };
743
- if (recipeId === "ollama") {
744
- bootProgress.set(
745
- "ai-model",
746
- formatStatusLine(`AI ${model} — ensuring…`, undefined, "pending"),
747
- );
748
- const { ensureOllamaModel } = await import("../docker/ollama-pull.ts");
749
- await ensureOllamaModel({
750
- url,
751
- model,
752
- onStatus: (line) =>
753
- bootProgress.set("ai-model", formatStatusLine(line, undefined, "pending")),
754
- });
755
- pendingAiModelWatch = { url, model, kind: "ollama" };
756
- paintAi("ready", model);
757
- } else if (recipeId === "llama-cpp" || recipeId === "vllm" || recipeId === "sglang") {
758
- pendingAiModelWatch = {
759
- url: `${url}/v1`,
760
- model,
761
- kind: "openai-compatible",
762
- };
763
- bootProgress.set(
764
- "ai-model",
765
- formatStatusLine(`AI ${model} — probing…`, undefined, "pending"),
766
- );
767
- const { probeAiModelStatus, formatAiModelStatusMessage } =
768
- await import("../docker/ai-model-status.ts");
769
- const snap = await probeAiModelStatus({
770
- url: `${url}/v1`,
771
- model,
772
- kind: "openai-compatible",
773
- });
774
- const status = devStatusFromAiPhase(snap.phase);
775
- paintAi(status, snap.phase === "ready" ? model : `${model} · ${snap.phase}`);
776
- bootProgress.set(
777
- "ai-model",
778
- formatStatusLine(formatAiModelStatusMessage(snap), undefined, status),
779
- );
780
- }
781
- }
736
+ // Compose no longer ships local AI recipes BYO `OKE_AI_URL` watch
737
+ // is armed below after stackEnv / process.env are known.
782
738
  } catch (err) {
783
739
  const msg = err instanceof Error ? err.message : String(err);
784
740
  console.error(msg);
@@ -802,6 +758,19 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
802
758
 
803
759
  const heroAiModel =
804
760
  stackEnv?.OKE_AI_MODEL?.trim() || process.env.OKE_AI_MODEL?.trim() || undefined;
761
+ // BYO OpenAI-compatible server (LM Studio, llama.cpp, …) — poll readiness
762
+ // without blocking boot. Registry cloud (OpenRouter) has no OKE_AI_URL.
763
+ if (!pendingAiModelWatch) {
764
+ const byoUrl = stackEnv?.OKE_AI_URL?.trim() || process.env.OKE_AI_URL?.trim() || undefined;
765
+ const byoModel = heroAiModel;
766
+ if (byoUrl && byoModel) {
767
+ pendingAiModelWatch = {
768
+ url: byoUrl,
769
+ model: byoModel,
770
+ kind: "openai-compatible",
771
+ };
772
+ }
773
+ }
805
774
  // OpenAI-compatible model load can outlive compose health — default AI ● to pending.
806
775
  if (pendingAiModelWatch && heroAiStatus === undefined) {
807
776
  heroAiStatus = "pending";
@@ -840,7 +809,10 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
840
809
  const one = (name: string) => h.get(name);
841
810
  switch (element) {
842
811
  case "ai": {
843
- const container = one("ai");
812
+ // Cloud / host AI has no compose service — ignore orphaned exited
813
+ // containers left over after `images.ai` was removed.
814
+ if (!stackAiServiceName) return aiStatus;
815
+ const container = one(stackAiServiceName);
844
816
  if (container === "error") return "error";
845
817
  return aiStatus ?? container;
846
818
  }
@@ -883,7 +855,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
883
855
  st === "ready" || st === "idle"
884
856
  ? null
885
857
  : st === "error"
886
- ? liveComposeHealth.get("ai") === "error"
858
+ ? stackAiServiceName && liveComposeHealth.get(stackAiServiceName) === "error"
887
859
  ? "stopped"
888
860
  : "error"
889
861
  : "loading";
@@ -901,7 +873,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
901
873
  const fromHealth = svc.serviceName ? liveComposeHealth.get(svc.serviceName) : undefined;
902
874
  let status = fromHealth ?? svc.status ?? "pending";
903
875
  let detail = svc.detail;
904
- if (svc.serviceName === "ai") {
876
+ if (stackAiServiceName && svc.serviceName === stackAiServiceName) {
905
877
  if (fromHealth === "error") {
906
878
  status = "error";
907
879
  detail = heroAiModel ? `${heroAiModel} · stopped` : "stopped";
@@ -941,9 +913,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
941
913
  return { code: 0, plan };
942
914
  }
943
915
 
944
- let boardPaused = false;
945
916
  const repaintBoard = (aiStatus?: DevStatus): void => {
946
- if (boardPaused) return;
947
917
  bootBoard.paint(paintBootBoard(aiStatus ?? heroAiStatus));
948
918
  };
949
919
 
@@ -963,7 +933,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
963
933
  onStatus: (_line, status) => {
964
934
  const st = devStatusFromAiPhase(status.phase);
965
935
  // Container stopped wins over model-phase "loading".
966
- if (liveComposeHealth.get("ai") === "error") {
936
+ if (stackAiServiceName && liveComposeHealth.get(stackAiServiceName) === "error") {
967
937
  heroAiStatus = "error";
968
938
  repaintBoard("error");
969
939
  if (!sawAiPaint) {
@@ -1012,6 +982,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1012
982
 
1013
983
  // One-shot schema sync for the compose (`dev`) profile: emits
1014
984
  // schema.drizzle.ts for the active dialect, then pushes via drizzle-kit.
985
+ let schemaPushOk = options.noDbPush === true;
1015
986
  if (!options.noDbPush) {
1016
987
  const { syncDevSchema } = await import("./dev-schema-sync.ts");
1017
988
  const configEnv = "dev" as const;
@@ -1027,11 +998,40 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1027
998
  `oke db push (dev · ${result.dialect}) ${result.code === 0 ? "ok" : "failed"}`,
1028
999
  ),
1029
1000
  );
1001
+ schemaPushOk = result.code === 0;
1030
1002
  } catch (err) {
1031
1003
  write(await formatDevSchemaSyncFailure(cwd, err, "boot"));
1032
1004
  }
1033
1005
  }
1034
1006
 
1007
+ // Vault gaps before seed / app — missing contracts (e.g. OPENROUTER_API_KEY).
1008
+ {
1009
+ const { maybeAskVaultGaps } = await import("./ask-vault-gaps.ts");
1010
+ const vaultCode = await maybeAskVaultGaps({
1011
+ cwd,
1012
+ entry,
1013
+ write,
1014
+ stdinIsTTY: options.stdinIsTTY ?? process.stdin.isTTY,
1015
+ });
1016
+ if (vaultCode !== 0) {
1017
+ bootBoard.stop();
1018
+ restoreProcessEnv();
1019
+ restoreWarn();
1020
+ return { code: vaultCode };
1021
+ }
1022
+ }
1023
+
1024
+ // First-time seed ask only after a successful push (needs live schema).
1025
+ if (schemaPushOk && !options.noDbPush) {
1026
+ const { maybeAskSeed } = await import("./ask-seed.ts");
1027
+ await maybeAskSeed({
1028
+ cwd,
1029
+ env: "dev",
1030
+ write,
1031
+ stdinIsTTY: options.stdinIsTTY ?? process.stdin.isTTY,
1032
+ });
1033
+ }
1034
+
1035
1035
  const autoPushEnabled = resolveDevAutoPush({
1036
1036
  noDbPush: options.noDbPush,
1037
1037
  docker: Boolean(stackEnv),
@@ -1208,23 +1208,20 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1208
1208
  env[RUNS_INGEST_SECRET_ENV] = ingestSecret;
1209
1209
  }
1210
1210
 
1211
- const app = await startApp(plan.entry, env);
1212
- const boundAppPort = app.port ?? appPort;
1211
+ let app: DevAppHandle | null = await startApp(plan.entry, env);
1212
+ let boundAppPort = app.port ?? appPort;
1213
+ env.PORT = String(boundAppPort);
1213
1214
 
1214
1215
  let mcpServer: DevMcpHandle | null = null;
1215
1216
  let secret: string | null = null;
1216
1217
  let sessions: SessionStore | null = null;
1217
1218
  let attachedHost: { readonly stop: () => Promise<void> } | null = null;
1218
1219
 
1219
- if (consoleServer.console) {
1220
- const state = consoleServer.console.state;
1221
- consoleState = state;
1222
- try {
1223
- const { attachHostToConsole } = await import("./attach-host-console.ts");
1224
- attachedHost = await attachHostToConsole({
1220
+ const attachHostOpts = consoleServer.console
1221
+ ? {
1225
1222
  entry: plan.entry,
1226
1223
  cwd,
1227
- state,
1224
+ state: consoleServer.console.state,
1228
1225
  ...(ingestSecret
1229
1226
  ? {
1230
1227
  runsBridge: {
@@ -1233,7 +1230,15 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1233
1230
  },
1234
1231
  }
1235
1232
  : {}),
1236
- });
1233
+ }
1234
+ : null;
1235
+
1236
+ if (consoleServer.console && attachHostOpts) {
1237
+ const state = consoleServer.console.state;
1238
+ consoleState = state;
1239
+ try {
1240
+ const { attachHostToConsole } = await import("./attach-host-console.ts");
1241
+ attachedHost = await attachHostToConsole(attachHostOpts);
1237
1242
  } catch (err) {
1238
1243
  write(
1239
1244
  formatStatusLine(
@@ -1269,7 +1274,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1269
1274
  const boundDocsMcpPort = docsMcpServer?.port ?? docsMcpPort;
1270
1275
  const docsMcpUrl = docsMcpServer?.url ?? null;
1271
1276
 
1272
- const appUrl = app.url?.origin ?? `http://127.0.0.1:${boundAppPort}`;
1277
+ const appUrl = app?.url?.origin ?? `http://127.0.0.1:${boundAppPort}`;
1273
1278
  await regen(appUrl);
1274
1279
 
1275
1280
  const dbPush =
@@ -1365,15 +1370,11 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1365
1370
  }
1366
1371
  });
1367
1372
 
1368
- let stopDevControls: (() => void) | null = null;
1369
-
1370
1373
  let stopped = false;
1371
1374
  const stop = () => {
1372
1375
  if (stopped) return;
1373
1376
  stopped = true;
1374
1377
  restoreWarn();
1375
- stopDevControls?.();
1376
- stopDevControls = null;
1377
1378
  stopAiModelWatch?.();
1378
1379
  stopAiModelWatch = null;
1379
1380
  stopComposeHealthWatch?.();
@@ -1384,7 +1385,15 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1384
1385
  const host = attachedHost;
1385
1386
  attachedHost = null;
1386
1387
  void host?.stop();
1387
- void Promise.resolve(app.stop());
1388
+ const appHandle = app;
1389
+ app = null;
1390
+ void Promise.resolve(appHandle?.stop());
1391
+ void import("../drivers/postgres.ts")
1392
+ .then(async ({ pauseSharedPostgresClients, resumeSharedPostgresClients }) => {
1393
+ await pauseSharedPostgresClients();
1394
+ resumeSharedPostgresClients();
1395
+ })
1396
+ .catch(() => {});
1388
1397
  consoleServer.stop();
1389
1398
  mcpServer?.stop();
1390
1399
  docsMcpServer?.stop();
@@ -1467,112 +1476,11 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1467
1476
 
1468
1477
  if (options.onReady) await options.onReady(session);
1469
1478
 
1470
- // Keyboard controls (docker + TTY): Ink replaces raw-mode stdin.
1471
- if (keepAlive && dockerStarted && (options.stdinIsTTY ?? process.stdin.isTTY)) {
1472
- const started = dockerStarted;
1473
- const syncComposeBoard = async (): Promise<void> => {
1474
- const { readComposeHealth } = await import("../docker/compose-health.ts");
1475
- liveComposeHealth = await readComposeHealth({
1476
- files: started.files,
1477
- cwd: started.cwd,
1478
- env: started.env,
1479
- run: composeHealthRun,
1480
- });
1481
- if (liveComposeHealth.get("ai") === "error") heroAiStatus = "error";
1482
- repaintBoard();
1483
- };
1484
- const refreshChrome = async (): Promise<void> => {
1485
- await syncComposeBoard();
1486
- paintReadyChrome();
1487
- };
1488
- const waitComposeReady = async (): Promise<void> => {
1489
- const { watchComposeHealth } = await import("../docker/compose-health.ts");
1490
- const aiServiceName = started.aiServiceName;
1491
- liveComposeHealth = await watchComposeHealth({
1492
- files: started.files,
1493
- cwd: started.cwd,
1494
- env: started.env,
1495
- run: composeHealthRun,
1496
- timeoutMs: 20_000,
1497
- isDone: (map) => {
1498
- if (map.size === 0) return true;
1499
- return [...map.entries()].every(
1500
- ([name, s]) => name === aiServiceName || s === "ready" || s === "error",
1501
- );
1502
- },
1503
- });
1504
- if (liveComposeHealth.get("ai") === "error") heroAiStatus = "error";
1505
- };
1506
-
1507
- const composeAction = async (action: DevComposeControlAction): Promise<void> => {
1508
- const files = started.files;
1509
- const finalArgs =
1510
- action === "up"
1511
- ? ["compose", ...files.flatMap((f) => ["-f", f]), "up", "-d"]
1512
- : ["compose", ...files.flatMap((f) => ["-f", f]), action];
1513
- const proc = Bun.spawn(["docker", ...finalArgs], {
1514
- cwd: started.cwd,
1515
- stdout: "pipe",
1516
- stderr: "pipe",
1517
- env: { ...process.env, ...started.env },
1518
- });
1519
- const [stdout, stderr, code] = await Promise.all([
1520
- new Response(proc.stdout).text(),
1521
- new Response(proc.stderr).text(),
1522
- proc.exited,
1523
- ]);
1524
- if (code !== 0) {
1525
- const detail = [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
1526
- throw new Error(`docker compose ${action} exited ${code}` + (detail ? `\n${detail}` : ""));
1527
- }
1528
- if (action === "up") await waitComposeReady();
1529
- await syncComposeBoard();
1530
- };
1531
-
1532
- try {
1533
- const { launchDevLiveControls } = await import("./tui/DevLive.tsx");
1534
- await launchDevLiveControls({
1535
- onRefresh: () => refreshChrome(),
1536
- onSeed: async () => {
1537
- const { runSlashCli } = await import("./tui/slash-run.ts");
1538
- boardPaused = true;
1539
- const lines: string[] = [];
1540
- try {
1541
- const code = await runSlashCli(cwd, ["db", "seed", "--force"], (line) => {
1542
- lines.push(line);
1543
- write(line);
1544
- });
1545
- if (code === 0) return;
1546
- const last = [...lines]
1547
- .reverse()
1548
- .map((line) => line.trim())
1549
- .find((line) => line.length > 0 && !line.startsWith("✗") && !line.startsWith("$"));
1550
- throw new Error(last || "oke db seed failed");
1551
- } finally {
1552
- boardPaused = false;
1553
- }
1554
- },
1555
- onComposeUp: () => composeAction("up"),
1556
- onComposeStop: () => composeAction("stop"),
1557
- onQuit: () => {
1558
- stop();
1559
- process.exit(0);
1560
- },
1561
- });
1562
- // Esc / unmount without q — still tear down.
1563
- stop();
1564
- } catch {
1565
- // Inconsequential: Ink/TUI is optional chrome — fall back to keep-alive without keyboard.
1566
- await new Promise(() => {});
1567
- }
1568
- return { code: 0, plan: session.plan, session };
1569
- }
1570
-
1571
1479
  if (!keepAlive) {
1572
1480
  return { code: 0, plan: session.plan, session };
1573
1481
  }
1574
1482
 
1575
- // Keep the process alive.
1483
+ // Keep the process alive (Ctrl+C / SIGTERM → session.stop).
1576
1484
  await new Promise(() => {});
1577
1485
  return { code: 0, plan: session.plan, session };
1578
1486
  }
@@ -326,7 +326,7 @@ async function askIncludeOthersPrompt(): Promise<boolean | null> {
326
326
  initialValue: false,
327
327
  });
328
328
  if (isCancel(value)) return null;
329
- return value;
329
+ return Boolean(value);
330
330
  }
331
331
 
332
332
  /**
@@ -356,7 +356,7 @@ async function pickProjectKeys(options: {
356
356
  required: false,
357
357
  });
358
358
  if (isCancel(value)) return null;
359
- return value;
359
+ return Array.isArray(value) ? value : null;
360
360
  }
361
361
 
362
362
  /**
@@ -72,7 +72,7 @@ describe("oke stack", () => {
72
72
  const code = await runStackPreview({
73
73
  cwd: dir,
74
74
  images: {
75
- "channel.email": "axllent/mailpit:v1.30.7",
75
+ "channel.email": "axllent/mailpit:v1.31.1",
76
76
  },
77
77
  write: (t) => {
78
78
  out += t;
@@ -81,7 +81,9 @@ export async function runDoctorDiff(options: DoctorDiffOptions = {}): Promise<Do
81
81
  for (const c of undeclared) {
82
82
  write(` [contract-breaking] ${c.path}: ${c.summary}\n`);
83
83
  }
84
- write("Acknowledge intentional breaks with `breaking: true` on the owning flow.\n");
84
+ write(
85
+ "Acknowledge intentional breaks with `breaking: true` on the HTTP / call / MCP / resource contract bag.\n",
86
+ );
85
87
  return { code: EXIT_RUNTIME, undeclared, allChanges: all };
86
88
  }
87
89
 
@@ -223,7 +225,7 @@ export async function doctorDiffCli(args: readonly string[]): Promise<number> {
223
225
 
224
226
  CI gate: fail on undeclared contract-breaking Manifest changes.
225
227
  Default: git merge-base (main/master) vs working-tree manifest.
226
- Acknowledge intentional breaks with breaking: true on the flow.
228
+ Acknowledge intentional breaks with breaking: true on the HTTP / call / MCP / resource contract bag.
227
229
  `);
228
230
  return EXIT_OK;
229
231
  }
@@ -87,7 +87,7 @@ describe("doctor-pii", () => {
87
87
  db: { facet: "sql", classifications: { email: { pii: true } } },
88
88
  },
89
89
  ai: {
90
- models: { m: { provider: "openai-compatible" } },
90
+ models: { m: { provider: "anthropic" } },
91
91
  prompts: { summarize: { model: "m" } },
92
92
  },
93
93
  });
package/src/cli/doctor.ts CHANGED
@@ -102,7 +102,8 @@ export interface DoctorFinding {
102
102
  | "cdc_outbox_backlog"
103
103
  | "cdc_outbox_retention"
104
104
  | "live_subscriber_pressure"
105
- | "live_fanout_queue_saturated";
105
+ | "live_fanout_queue_saturated"
106
+ | "search_backfill_required";
106
107
  readonly severity: "error" | "warn";
107
108
  readonly message: string;
108
109
  }
@@ -414,6 +415,18 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<{
414
415
  }
415
416
 
416
417
  if (manifest) {
418
+ for (const store of Object.values(manifest.stores ?? {})) {
419
+ if (store.facet !== "sql" || !store.tables) continue;
420
+ for (const [tableName, table] of Object.entries(store.tables)) {
421
+ if (table.search?.backfillRequired === true) {
422
+ findings.push({
423
+ code: "search_backfill_required",
424
+ severity: "warn",
425
+ message: `table "${tableName}" has searchable/embed columns on existing data — run \`oke db search-backfill ${tableName}\` (never auto on push)`,
426
+ });
427
+ }
428
+ }
429
+ }
417
430
  findings.push(...checkManifestPiiAsks(manifest));
418
431
  }
419
432
 
@@ -114,9 +114,11 @@ describe("hero-meta", () => {
114
114
  ai: { dev: "openai-compatible", test: "mock", prod: "openai-compatible" },
115
115
  },
116
116
  },
117
- { docker: true, sqlDriver: "postgres", kvDriver: "redis", aiModel: "granite3.3:2b" },
117
+ { docker: true, sqlDriver: "postgres", kvDriver: "redis", aiModel: "openrouter/free" },
118
+ );
119
+ expect(rows.find((r) => r.element === "ai")?.detail).toBe(
120
+ "openai-compatible · openrouter/free",
118
121
  );
119
- expect(rows.find((r) => r.element === "ai")?.detail).toBe("openai-compatible · granite3.3:2b");
120
122
  expect(rows.find((r) => r.element === "ai")?.status).not.toBe("idle");
121
123
  });
122
124
 
@@ -20,9 +20,8 @@ describe("flattenImagesConfig", () => {
20
20
  files: "rustfs/rustfs:1.0.0",
21
21
  index: "getmeili/meilisearch:v1.53",
22
22
  },
23
- channel: { email: "axllent/mailpit:v1.30.7" },
24
- ai: "ollama/ollama:0.32.13",
25
- pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.53",
23
+ channel: { email: "axllent/mailpit:v1.31.1" },
24
+ pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.57",
26
25
  proxy: "caddy:2-alpine",
27
26
  }),
28
27
  ).toEqual({
@@ -30,9 +29,8 @@ describe("flattenImagesConfig", () => {
30
29
  "store.kv": "redis:8-alpine",
31
30
  "store.files": "rustfs/rustfs:1.0.0",
32
31
  "store.index": "getmeili/meilisearch:v1.53",
33
- "channel.email": "axllent/mailpit:v1.30.7",
34
- ai: "ollama/ollama:0.32.13",
35
- pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.53",
32
+ "channel.email": "axllent/mailpit:v1.31.1",
33
+ pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.57",
36
34
  proxy: "caddy:2-alpine",
37
35
  });
38
36
  });
@@ -55,7 +53,7 @@ describe("defaultImagesFromConfig", () => {
55
53
  });
56
54
  expect(images).toEqual({
57
55
  "store.sql": "postgres:18-alpine",
58
- pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.53",
56
+ pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.57",
59
57
  "store.kv": "redis:8-alpine",
60
58
  });
61
59
  });
@@ -70,7 +68,7 @@ describe("defaultImagesFromConfig", () => {
70
68
  },
71
69
  });
72
70
  expect(images["store.sql"]).toBe("pgvector/pgvector:pg17");
73
- expect(images.pgdog).toBe("ghcr.io/pgdogdev/pgdog:v0.1.53");
71
+ expect(images.pgdog).toBe("ghcr.io/pgdogdev/pgdog:v0.1.57");
74
72
  });
75
73
 
76
74
  test("clock postgres alone pulls store.sql image", () => {
@@ -81,7 +79,7 @@ describe("defaultImagesFromConfig", () => {
81
79
  });
82
80
  expect(images).toEqual({
83
81
  "store.sql": "postgres:18-alpine",
84
- pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.53",
82
+ pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.57",
85
83
  });
86
84
  });
87
85
 
@@ -93,7 +91,7 @@ describe("defaultImagesFromConfig", () => {
93
91
  });
94
92
  expect(images).toEqual({
95
93
  "store.sql": "postgres:18-alpine",
96
- pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.53",
94
+ pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.57",
97
95
  });
98
96
  });
99
97
 
@@ -15,7 +15,7 @@ import type { Manifest } from "../manifest/types.ts";
15
15
  /** Default image pins when `images` is omitted but prod drivers need containers. */
16
16
  const DEFAULT_SQL_IMAGE = "postgres:18-alpine";
17
17
  const DEFAULT_PGVECTOR_IMAGE = "pgvector/pgvector:pg17";
18
- const DEFAULT_PGDOG_IMAGE = "ghcr.io/pgdogdev/pgdog:v0.1.53";
18
+ const DEFAULT_PGDOG_IMAGE = "ghcr.io/pgdogdev/pgdog:v0.1.57";
19
19
  const DEFAULT_KV_IMAGE = "redis:8-alpine";
20
20
 
21
21
  /**