okengine 0.10.3 → 0.11.1

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 (323) hide show
  1. package/package.json +76 -10
  2. package/site/content/docs/deployment/docker-swarm.mdx +48 -111
  3. package/site/content/docs/deployment/docker.mdx +43 -36
  4. package/site/content/docs/deployment/index.mdx +9 -9
  5. package/site/content/docs/deployment/kubernetes.mdx +4 -4
  6. package/site/content/docs/elements/ai.mdx +7 -7
  7. package/site/content/docs/elements/channel.mdx +9 -7
  8. package/site/content/docs/elements/clock.mdx +8 -8
  9. package/site/content/docs/elements/flow.mdx +46 -47
  10. package/site/content/docs/elements/gate.mdx +6 -5
  11. package/site/content/docs/elements/signal.mdx +2 -2
  12. package/site/content/docs/elements/store.mdx +71 -78
  13. package/site/content/docs/elements/vault.mdx +89 -70
  14. package/site/content/docs/get-started/basic-usage.mdx +8 -8
  15. package/site/content/docs/get-started/installation.mdx +36 -36
  16. package/site/content/docs/get-started/introduction.mdx +11 -0
  17. package/site/content/docs/get-started/why.mdx +2 -2
  18. package/site/content/docs/plugins/anonymous.mdx +1 -1
  19. package/site/content/docs/plugins/magic-link.mdx +11 -11
  20. package/site/content/docs/plugins/otp.mdx +2 -2
  21. package/site/content/docs/plugins/passkey.mdx +1 -1
  22. package/site/content/docs/plugins/two-factor.mdx +1 -1
  23. package/site/content/docs/plugins/username.mdx +1 -1
  24. package/site/content/docs/recipes/caddy.mdx +1 -1
  25. package/site/content/docs/recipes/cockroachdb.mdx +2 -2
  26. package/site/content/docs/recipes/dragonfly.mdx +1 -1
  27. package/site/content/docs/recipes/index.mdx +1 -2
  28. package/site/content/docs/recipes/llama-cpp.mdx +21 -23
  29. package/site/content/docs/recipes/mailpit.mdx +11 -11
  30. package/site/content/docs/recipes/meilisearch.mdx +10 -10
  31. package/site/content/docs/recipes/meta.json +0 -1
  32. package/site/content/docs/recipes/ollama.mdx +1 -1
  33. package/site/content/docs/recipes/pgdog.mdx +12 -9
  34. package/site/content/docs/recipes/postgres.mdx +3 -3
  35. package/site/content/docs/recipes/redis.mdx +2 -2
  36. package/site/content/docs/recipes/rustfs.mdx +10 -7
  37. package/site/content/docs/recipes/sglang.mdx +1 -1
  38. package/site/content/docs/recipes/supabase-docker.mdx +1 -1
  39. package/site/content/docs/recipes/timescale.mdx +2 -2
  40. package/site/content/docs/recipes/valkey.mdx +1 -1
  41. package/site/content/docs/recipes/vllm.mdx +1 -1
  42. package/site/content/docs/recipes/yugabytedb.mdx +2 -2
  43. package/site/content/docs/reference/cli.md +61 -38
  44. package/site/content/docs/reference/configuration.mdx +145 -70
  45. package/site/content/docs/reference/environment-variables.mdx +36 -28
  46. package/site/content/docs/reference/errors.mdx +28 -29
  47. package/site/content/docs/reference/fx.mdx +11 -4
  48. package/site/content/docs/reference/i18n.mdx +17 -6
  49. package/site/content/docs/reference/index.mdx +5 -0
  50. package/site/content/docs/reference/meta.json +1 -0
  51. package/site/content/docs/reference/migrating-environments.mdx +158 -0
  52. package/site/content/docs/reference/plugins.mdx +2 -2
  53. package/src/auth/gate-auth.test.ts +12 -10
  54. package/src/cli/ai-setup/ai-setup.test.ts +27 -15
  55. package/src/cli/ai-setup/apply.ts +111 -15
  56. package/src/cli/ai-setup/catalog.ts +3 -3
  57. package/src/cli/ai-setup/index.ts +1 -1
  58. package/src/cli/ai-setup/prompts.ts +1 -1
  59. package/src/cli/ai-setup/recommend.test.ts +1 -1
  60. package/src/cli/ask-seed.test.ts +3 -3
  61. package/src/cli/build.ts +1 -1
  62. package/src/cli/db-auto-push.test.ts +6 -3
  63. package/src/cli/db-auto-push.ts +7 -5
  64. package/src/cli/db-seed.ts +7 -8
  65. package/src/cli/db.test.ts +70 -71
  66. package/src/cli/db.ts +86 -12
  67. package/src/cli/dev-controller.test.ts +35 -0
  68. package/src/cli/dev-controller.ts +298 -0
  69. package/src/cli/dev-controls.ts +11 -65
  70. package/src/cli/dev-db-push.test.ts +47 -15
  71. package/src/cli/dev-schema-sync.test.ts +11 -11
  72. package/src/cli/dev-schema-sync.ts +10 -10
  73. package/src/cli/dev-session-lock.test.ts +106 -0
  74. package/src/cli/dev-session-lock.ts +209 -0
  75. package/src/cli/dev.test.ts +166 -121
  76. package/src/cli/dev.ts +138 -192
  77. package/src/cli/doc-staleness.test.ts +3 -6
  78. package/src/cli/docker-clean.test.ts +33 -1
  79. package/src/cli/docker-clean.ts +28 -33
  80. package/src/cli/docker-cli.test.ts +12 -11
  81. package/src/cli/docker.ts +95 -20
  82. package/src/cli/doctor.test.ts +40 -0
  83. package/src/cli/doctor.ts +55 -15
  84. package/src/cli/drizzle-env.test.ts +11 -13
  85. package/src/cli/drizzle-env.ts +18 -11
  86. package/src/cli/ensure-drizzle-config.ts +11 -14
  87. package/src/cli/hero-meta.test.ts +15 -15
  88. package/src/cli/hero-meta.ts +10 -9
  89. package/src/cli/index.ts +36 -6
  90. package/src/cli/load-config.images.test.ts +16 -12
  91. package/src/cli/load-config.source.test.ts +50 -0
  92. package/src/cli/load-config.ts +34 -28
  93. package/src/cli/meilisearch-local.ts +1 -2
  94. package/src/cli/mode.ts +7 -58
  95. package/src/cli/registry.ts +175 -19
  96. package/src/cli/resolve-dev-sql-env.test.ts +8 -16
  97. package/src/cli/resolve-dev-sql-env.ts +13 -15
  98. package/src/cli/safe-defaults.test.ts +28 -10
  99. package/src/cli/schema.ts +65 -16
  100. package/src/cli/test.ts +58 -0
  101. package/src/cli/tui/App.tsx +252 -0
  102. package/src/cli/tui/DevLive.tsx +116 -0
  103. package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
  104. package/src/cli/tui/components/HeaderBar.tsx +67 -0
  105. package/src/cli/tui/components/LogStream.tsx +47 -0
  106. package/src/cli/tui/components/PanelCard.tsx +39 -0
  107. package/src/cli/tui/components/SelectRow.tsx +37 -0
  108. package/src/cli/tui/components/SlashPalette.tsx +172 -0
  109. package/src/cli/tui/components/Spinner.tsx +32 -0
  110. package/src/cli/tui/components/StatusBar.tsx +51 -0
  111. package/src/cli/tui/components/StatusDot.tsx +37 -0
  112. package/src/cli/tui/components/TabBar.tsx +49 -0
  113. package/src/cli/tui/components/Wordmark.tsx +43 -0
  114. package/src/cli/tui/format.ts +38 -0
  115. package/src/cli/tui/keys.test.ts +18 -0
  116. package/src/cli/tui/keys.ts +26 -0
  117. package/src/cli/tui/launch.test.ts +18 -0
  118. package/src/cli/tui/launch.ts +57 -0
  119. package/src/cli/tui/panels/Dashboard.tsx +201 -0
  120. package/src/cli/tui/panels/Database.tsx +198 -0
  121. package/src/cli/tui/panels/Dev.tsx +257 -0
  122. package/src/cli/tui/panels/Docker.tsx +357 -0
  123. package/src/cli/tui/panels/Navigator.test.ts +22 -0
  124. package/src/cli/tui/panels/Navigator.tsx +213 -0
  125. package/src/cli/tui/slash-catalog.test.ts +31 -0
  126. package/src/cli/tui/slash-catalog.ts +184 -0
  127. package/src/cli/tui/slash-run.ts +54 -0
  128. package/src/cli/tui/state/ports.ts +95 -0
  129. package/src/cli/tui/theme.ts +24 -0
  130. package/src/cli/vault-cmd.test.ts +402 -0
  131. package/src/cli/vault-cmd.ts +727 -69
  132. package/src/cli/vault-secure-input.test.ts +56 -0
  133. package/src/cli/vault-secure-input.ts +124 -0
  134. package/src/compiler/effects-infer.ts +7 -0
  135. package/src/compiler/fixtures/skyport/oke.config.ts +3 -3
  136. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
  137. package/src/compiler/fixtures/skyport.expected.json +1 -1
  138. package/src/compiler-entry.ts +7 -0
  139. package/src/config/define-config.test.ts +111 -47
  140. package/src/config/driver-defaults.test.ts +16 -23
  141. package/src/config/driver-defaults.ts +33 -63
  142. package/src/config/index.ts +169 -144
  143. package/src/config/resolve-driver.test.ts +14 -19
  144. package/src/console/server/app.ts +4 -2
  145. package/src/console/server/console.test.ts +58 -1
  146. package/src/console/server/flows.ts +22 -2
  147. package/src/console/server/index.ts +3 -0
  148. package/src/console/server/operator-db.test.ts +55 -13
  149. package/src/console/server/operator-db.ts +305 -263
  150. package/src/console/server/serve.ts +16 -7
  151. package/src/console/server/state.ts +31 -9
  152. package/src/console/server/vault.test.ts +120 -5
  153. package/src/console/server/vault.ts +208 -12
  154. package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
  155. package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
  156. package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
  157. package/src/console/ui/dist/index.html +2 -2
  158. package/src/console/ui/shell/client.ts +16 -0
  159. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
  160. package/src/console/ui/vault/VaultA11yView.tsx +33 -1
  161. package/src/console/ui/vault/backend.test.ts +75 -0
  162. package/src/console/ui/vault/backend.ts +117 -0
  163. package/src/console/ui/vault/fixture.ts +21 -2
  164. package/src/console/ui/vault/index.ts +11 -1
  165. package/src/console/ui/vault/types.ts +26 -0
  166. package/src/docker/compose.ts +164 -61
  167. package/src/docker/derive.ts +106 -12
  168. package/src/docker/docker.test.ts +156 -110
  169. package/src/docker/helpers.ts +98 -0
  170. package/src/docker/images-config.test.ts +3 -8
  171. package/src/docker/index.ts +30 -5
  172. package/src/docker/recipes/index.ts +0 -3
  173. package/src/docker/recipes/llama-cpp.ts +4 -4
  174. package/src/docker/recipes/pgdog.ts +9 -2
  175. package/src/docker/resources.test.ts +71 -0
  176. package/src/docker/resources.ts +176 -0
  177. package/src/docker/stack-id.test.ts +11 -4
  178. package/src/docker/stack-id.ts +42 -32
  179. package/src/docker/stack.integration.test.ts +4 -15
  180. package/src/docker/types.ts +35 -3
  181. package/src/drivers/ai-ollama.integration.test.ts +2 -2
  182. package/src/drivers/conformance.test.ts +11 -12
  183. package/src/drivers/drizzle-dialect.test.ts +0 -4
  184. package/src/drivers/drizzle-dialect.ts +7 -13
  185. package/src/drivers/index.ts +24 -7
  186. package/src/drivers/journal-postgres.test.ts +3 -1
  187. package/src/drivers/journal-postgres.ts +4 -2
  188. package/src/drivers/meilisearch.ts +1 -1
  189. package/src/drivers/pglite.ts +28 -5
  190. package/src/drivers/types.ts +9 -11
  191. package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
  192. package/src/drivers/vault-aws-secrets-manager.ts +333 -0
  193. package/src/drivers/vault-builtin.test.ts +110 -0
  194. package/src/drivers/vault-builtin.ts +184 -0
  195. package/src/drivers/vault-driver-removal.test.ts +13 -0
  196. package/src/drivers/vault-managed.test.ts +95 -0
  197. package/src/drivers/vault-managed.ts +101 -25
  198. package/src/drivers/vault-types.ts +25 -5
  199. package/src/elements/ai/runtime.ts +1 -1
  200. package/src/elements/clock/chaos.test.ts +4 -1
  201. package/src/elements/clock/durable.ts +57 -4
  202. package/src/elements/clock.test.ts +13 -14
  203. package/src/elements/gate/config.ts +13 -3
  204. package/src/elements/gate/constants.ts +18 -0
  205. package/src/elements/gate/declare.ts +1 -1
  206. package/src/elements/gate/strategies.ts +2 -12
  207. package/src/elements/gate.test.ts +1 -1
  208. package/src/elements/gate.ts +2 -2
  209. package/src/elements/store/domain-ddl.test.ts +5 -12
  210. package/src/elements/store/emit-drizzle.ts +44 -19
  211. package/src/elements/store/index-boot.test.ts +15 -116
  212. package/src/elements/store/load-plugin-tables.ts +1 -1
  213. package/src/elements/store/resource-list-docs.fixture.ts +12 -6
  214. package/src/elements/store/resource-list-docs.test.ts +64 -44
  215. package/src/elements/store/resource.test.ts +17 -10
  216. package/src/elements/store/runtime.ts +9 -12
  217. package/src/elements/store/schema-decl.test.ts +16 -32
  218. package/src/elements/store/seed.test.ts +1 -2
  219. package/src/elements/store/seed.ts +7 -8
  220. package/src/elements/store/sql-condition.test.ts +2 -2
  221. package/src/elements/store/sql-session.test.ts +8 -10
  222. package/src/elements/store/sql-session.ts +10 -2
  223. package/src/elements/store.test.ts +13 -13
  224. package/src/elements/vault/audit.ts +233 -0
  225. package/src/elements/vault/boot-chain.test.ts +78 -0
  226. package/src/elements/vault/boot-chain.ts +43 -39
  227. package/src/elements/vault/builtin-adapter.test.ts +479 -0
  228. package/src/elements/vault/builtin-adapter.ts +1184 -0
  229. package/src/elements/vault/crypto.test.ts +237 -0
  230. package/src/elements/vault/crypto.ts +431 -0
  231. package/src/elements/vault/declare.ts +164 -3
  232. package/src/elements/vault/errors.ts +56 -0
  233. package/src/elements/vault/kms-unseal.test.ts +76 -0
  234. package/src/elements/vault/kms-unseal.ts +181 -0
  235. package/src/elements/vault/path.ts +90 -0
  236. package/src/elements/vault/resilience.test.ts +102 -0
  237. package/src/elements/vault/resilience.ts +107 -0
  238. package/src/elements/vault/runtime.ts +36 -4
  239. package/src/elements/vault/security-checklist.test.ts +130 -0
  240. package/src/elements/vault/storage.ts +448 -0
  241. package/src/elements/vault/types.ts +288 -0
  242. package/src/elements/vault/unseal.ts +150 -0
  243. package/src/elements/vault.test.ts +72 -4
  244. package/src/elements/vault.ts +91 -2
  245. package/src/full.ts +248 -0
  246. package/src/http.ts +41 -0
  247. package/src/i18n/interpolate-simple.ts +37 -0
  248. package/src/i18n/messages.ts +51 -4
  249. package/src/i18n-entry.ts +7 -0
  250. package/src/index.ts +35 -145
  251. package/src/journal-entry.ts +20 -0
  252. package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
  253. package/src/kernel/app-auth.ts +98 -0
  254. package/src/kernel/app.ts +77 -110
  255. package/src/kernel/auto-registry.test.ts +2 -2
  256. package/src/kernel/boot-bind/ai.test.ts +5 -5
  257. package/src/kernel/boot-bind/ai.ts +2 -1
  258. package/src/kernel/boot-bind/channel.test.ts +6 -6
  259. package/src/kernel/boot-bind/channel.ts +3 -10
  260. package/src/kernel/boot-bind/clock.ts +2 -2
  261. package/src/kernel/boot-bind/gate.ts +5 -9
  262. package/src/kernel/boot-bind/honor-config.test.ts +42 -42
  263. package/src/kernel/boot-bind/journal.ts +2 -2
  264. package/src/kernel/boot-bind/store.test.ts +43 -44
  265. package/src/kernel/boot-bind/store.ts +16 -41
  266. package/src/kernel/boot-bind/vault.ts +16 -1
  267. package/src/kernel/boot.test.ts +13 -19
  268. package/src/kernel/boot.ts +31 -19
  269. package/src/kernel/budget-entry.ts +1 -1
  270. package/src/kernel/compensate.test.ts +288 -1
  271. package/src/kernel/compensate.ts +126 -0
  272. package/src/kernel/concurrency.test.ts +10 -9
  273. package/src/kernel/effects-stamping.test.ts +27 -2
  274. package/src/kernel/element-registries.ts +5 -3
  275. package/src/kernel/flow.ts +4 -2
  276. package/src/kernel/fx.test.ts +119 -3
  277. package/src/kernel/fx.ts +215 -40
  278. package/src/kernel/index.ts +14 -0
  279. package/src/kernel/journal.ts +130 -7
  280. package/src/kernel/pipeline.test.ts +20 -6
  281. package/src/kernel/redacted.test.ts +35 -0
  282. package/src/kernel/redacted.ts +24 -2
  283. package/src/kernel/router/create-default.ts +16 -0
  284. package/src/kernel/router/create-edge.ts +18 -0
  285. package/src/kernel/router/create.ts +23 -0
  286. package/src/kernel/router/index.ts +23 -0
  287. package/src/kernel/router/linear.ts +88 -0
  288. package/src/kernel/router/regexp.ts +188 -0
  289. package/src/kernel/router/smart.ts +120 -0
  290. package/src/kernel/router/trie.ts +108 -0
  291. package/src/kernel/router/types.ts +56 -0
  292. package/src/kernel/router.ts +18 -532
  293. package/src/kernel-entry.ts +43 -0
  294. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  295. package/src/manifest/fixtures/skyport.manifest.json +1 -1
  296. package/src/plugins/config-source.test.ts +7 -7
  297. package/src/release/build-lib.ts +61 -0
  298. package/src/release/exports.test.ts +4 -2
  299. package/src/release/exports.ts +30 -4
  300. package/src/release/measure.ts +101 -22
  301. package/src/runs/duckdb.ts +7 -2
  302. package/src/shared/lazy-src.ts +79 -0
  303. package/src/shared/optional-peer.ts +41 -0
  304. package/src/term.test.ts +3 -44
  305. package/src/test/create-test-app.ts +12 -0
  306. package/src/test/provisions.integration.test.ts +1 -1
  307. package/src/test/reset-element-registries.ts +2 -0
  308. package/src/upgrade/codemods.test.ts +7 -20
  309. package/src/upgrade/codemods.ts +93 -60
  310. package/site/content/docs/recipes/openbao.mdx +0 -141
  311. package/src/cli/ask-dev-mode.ts +0 -40
  312. package/src/cli/dev-mode.test.ts +0 -45
  313. package/src/cli/dev-mode.ts +0 -78
  314. package/src/cli/openbao-bootstrap.test.ts +0 -150
  315. package/src/cli/openbao-bootstrap.ts +0 -321
  316. package/src/cli/openbao-restart.integration.test.ts +0 -145
  317. package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
  318. package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
  319. package/src/docker/recipes/openbao.ts +0 -47
  320. package/src/drivers/libsql.ts +0 -179
  321. package/src/drivers/sqlite.ts +0 -44
  322. package/src/drivers/vault-openbao.test.ts +0 -97
  323. package/src/drivers/vault-openbao.ts +0 -143
@@ -3,8 +3,8 @@
3
3
  * flow under that unit — the stale/missing `.adopt()` barrel case.
4
4
  *
5
5
  * Mirrors `effects-stamping.test.ts`'s OKE1008 shape exactly: opt-in only
6
- * via `rootDir`, `local`/`test` warn once (dev-loop stays unbroken),
7
- * `docker`/`prod` hard-fail (`OKE1009`) — never a silently-incomplete route
6
+ * via `rootDir`, `test` warns once (dev-loop stays unbroken),
7
+ * `dev`+compose / `prod` hard-fail (`OKE1009`) — never a silently-incomplete route
8
8
  * table in a deploy-shaped environment.
9
9
  */
10
10
 
@@ -41,23 +41,23 @@ describe("assertAdoptBarrelFresh — direct", () => {
41
41
  try {
42
42
  await makeUnitDir(root, "notes");
43
43
  const notesFlow = flow("notes.create", { do: () => ({ ok: true as const }) });
44
- await expect(assertAdoptBarrelFresh([notesFlow], "docker", root)).resolves.toBeUndefined();
44
+ await expect(assertAdoptBarrelFresh([notesFlow], "dev", root, true)).resolves.toBeUndefined();
45
45
  } finally {
46
46
  await rm(root, { recursive: true, force: true });
47
47
  }
48
48
  });
49
49
 
50
- test("stale: unit on disk, zero adopted flows for it — docker hard-fails OKE1009", async () => {
50
+ test("stale: unit on disk, zero adopted flows for it — dev+compose hard-fails OKE1009", async () => {
51
51
  const root = await mkdtemp(join(tmpdir(), "oke-adopt-fresh-"));
52
52
  try {
53
53
  await makeUnitDir(root, "notes");
54
- await expect(assertAdoptBarrelFresh([], "docker", root)).rejects.toThrow(/OKE1009/);
54
+ await expect(assertAdoptBarrelFresh([], "dev", root, true)).rejects.toThrow(/OKE1009/);
55
55
  } finally {
56
56
  await rm(root, { recursive: true, force: true });
57
57
  }
58
58
  });
59
59
 
60
- test("stale: prod — same posture as docker", async () => {
60
+ test("stale: prod — same posture as dev+compose", async () => {
61
61
  const root = await mkdtemp(join(tmpdir(), "oke-adopt-fresh-"));
62
62
  try {
63
63
  await makeUnitDir(root, "notes");
@@ -67,11 +67,11 @@ describe("assertAdoptBarrelFresh — direct", () => {
67
67
  }
68
68
  });
69
69
 
70
- test("stale: local — warns, never throws (dev-loop stays unbroken)", async () => {
70
+ test("stale: test — warns, never throws (dev-loop stays unbroken)", async () => {
71
71
  const root = await mkdtemp(join(tmpdir(), "oke-adopt-fresh-"));
72
72
  try {
73
73
  await makeUnitDir(root, "notes");
74
- await expect(assertAdoptBarrelFresh([], "local", root)).resolves.toBeUndefined();
74
+ await expect(assertAdoptBarrelFresh([], "test", root)).resolves.toBeUndefined();
75
75
  } finally {
76
76
  await rm(root, { recursive: true, force: true });
77
77
  }
@@ -79,7 +79,7 @@ describe("assertAdoptBarrelFresh — direct", () => {
79
79
  });
80
80
 
81
81
  describe("boot-level: stale barrel through a real oke() boot", () => {
82
- test("docker: real app.boot() hard-fails OKE1009 when a unit folder was never adopted", async () => {
82
+ test("dev+compose: real app.boot() hard-fails OKE1009 when a unit folder was never adopted", async () => {
83
83
  const root = await mkdtemp(join(tmpdir(), "oke-adopt-fresh-"));
84
84
  try {
85
85
  // "notes" exists on disk but this app only ever adopted "main" —
@@ -94,7 +94,22 @@ describe("boot-level: stale barrel through a real oke() boot", () => {
94
94
  const app = oke({ name: "stale-barrel", gate: { unguardedHttp: "allow" } });
95
95
 
96
96
  await expect(
97
- app.boot({ env: "docker", rootDir: root, unguardedHttp: "allow", startScheduler: false }),
97
+ app.boot({
98
+ env: "dev",
99
+ docker: true,
100
+ rootDir: root,
101
+ unguardedHttp: "allow",
102
+ startScheduler: false,
103
+ config: {
104
+ drivers: {
105
+ store: {
106
+ sql: { dev: "memory" },
107
+ kv: { dev: "memory" },
108
+ },
109
+ channel: { email: { dev: "console" } },
110
+ },
111
+ },
112
+ }),
98
113
  ).rejects.toThrow(/OKE1009/);
99
114
  } finally {
100
115
  await rm(root, { recursive: true, force: true });
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Sync auth wiring for `oke({ gate: { auth } })`.
3
+ *
4
+ * Loaded only when auth is configured so HTTP-only cold graphs never import
5
+ * bindings / sessions / identity (`requirePackageModule` in `app.ts`).
6
+ */
7
+
8
+ import type { ResolvedGateAuth } from "../auth/config.ts";
9
+ import { auth as authPlugin } from "../auth/plugin.ts";
10
+ import { createAuthHttpBindings, type AuthHttpMaterialization } from "../auth/bindings.ts";
11
+ import { tokenFromCookieHeader } from "../auth/cookies.ts";
12
+ import { setActiveGateAuthContext } from "../auth/method-context.ts";
13
+ import type { ResolvedGateConfig } from "../elements/gate/config.ts";
14
+ import { createAppAuthBinding, verifyBearerToken, type AppAuthBinding } from "./auth-resolve.ts";
15
+ import type { PluginDef } from "./plugin.ts";
16
+
17
+ /** Result of wiring Gate auth at construction. */
18
+ export interface WiredGateAuth {
19
+ readonly materialization: AuthHttpMaterialization | undefined;
20
+ readonly authPlugin: PluginDef | undefined;
21
+ readonly authBinding: AppAuthBinding;
22
+ readonly verifyBearerToken: typeof verifyBearerToken;
23
+ readonly tokenFromCookieHeader: typeof tokenFromCookieHeader;
24
+ /** Rebuild binding when boot supplies a clock. */
25
+ rebind(now: () => number): AppAuthBinding;
26
+ }
27
+
28
+ /** Options for {@link wireGateAuth}. */
29
+ export interface WireGateAuthOptions {
30
+ readonly gateConfig: ResolvedGateConfig & { readonly auth: ResolvedGateAuth };
31
+ /** Clock fallback when `gate.auth.now` is omitted (`oke({ fx: { now } })`). */
32
+ readonly now?: () => number;
33
+ }
34
+
35
+ /**
36
+ * Materialize auth HTTP bindings, absorb the builtin auth plugin, and publish
37
+ * the active Gate auth context for `.plug(username())` etc.
38
+ *
39
+ * @param options - Resolved Gate bag + optional clock
40
+ */
41
+ export function wireGateAuth(options: WireGateAuthOptions): WiredGateAuth {
42
+ const { gateConfig } = options;
43
+ const auth = gateConfig.auth;
44
+ let materialization: AuthHttpMaterialization | undefined;
45
+ if (auth.http) {
46
+ materialization = createAuthHttpBindings(auth, {
47
+ rateLimitEnabled: gateConfig.rateLimitEnabled,
48
+ sessions: auth.sessions,
49
+ });
50
+ }
51
+
52
+ const pluginDef = authPlugin({
53
+ secret: auth.secret,
54
+ accessTtlMs: auth.session.accessTtlMs,
55
+ refreshTtlMs: auth.session.refreshTtlMs,
56
+ session: {
57
+ accessTtlMs: auth.session.accessTtlMs,
58
+ refreshTtlMs: auth.session.refreshTtlMs,
59
+ idleTtlMs: auth.session.idleTtlMs,
60
+ absoluteTtlMs: auth.session.absoluteTtlMs,
61
+ singleSessionPerUser: auth.session.singleSessionPerUser,
62
+ },
63
+ password: auth.password,
64
+ passwordPolicy: auth.passwordPolicy,
65
+ breachCheck: auth.breachCheck,
66
+ });
67
+
68
+ let authBinding = createAppAuthBinding({
69
+ secret: auth.secret,
70
+ sessions: materialization?.ctx.sessions ?? auth.sessions,
71
+ now: auth.now ?? options.now,
72
+ });
73
+
74
+ setActiveGateAuthContext({
75
+ secret: authBinding.secret,
76
+ sessions: authBinding.sessions,
77
+ now: authBinding.now,
78
+ passwordPolicy: auth.passwordPolicy,
79
+ password: auth.password,
80
+ breachCheck: auth.breachCheck,
81
+ });
82
+
83
+ return {
84
+ materialization,
85
+ authPlugin: pluginDef,
86
+ authBinding,
87
+ verifyBearerToken,
88
+ tokenFromCookieHeader,
89
+ rebind(now) {
90
+ authBinding = createAppAuthBinding({
91
+ secret: authBinding.secret,
92
+ sessions: authBinding.sessions,
93
+ now,
94
+ });
95
+ return authBinding;
96
+ },
97
+ };
98
+ }
package/src/kernel/app.ts CHANGED
@@ -6,12 +6,9 @@
6
6
  * pipeline, and wires `fx.call` to untriggered (and triggered) flows.
7
7
  */
8
8
 
9
- import {
10
- compileRoute,
11
- encodeExecuteResult,
12
- encodeFailure,
13
- type CompiledRoute,
14
- } from "../compiler/index.ts";
9
+ import { compileRoute } from "../compiler/dynamic.ts";
10
+ import type { CompiledRoute } from "../compiler/aot.ts";
11
+ import { encodeExecuteResult, encodeFailure } from "../compiler/response.ts";
15
12
  import { validate } from "../validation/standard-schema.ts";
16
13
  import {
17
14
  accumulateAdoptArgs,
@@ -25,22 +22,22 @@ import {
25
22
  // when an app actually boots (AGENTS.md / unified-theory budget: cold start < 75 ms).
26
23
  import type { BootOptions, BootResult, ElementRuntimes } from "./boot.ts";
27
24
  import type { CapabilityToken } from "./capability.ts";
28
- import { createAppAuthBinding, verifyBearerToken, type AppAuthBinding } from "./auth-resolve.ts";
29
- import { createAuthHttpBindings, type AuthHttpMaterialization } from "../auth/bindings.ts";
30
- import { auth as authPlugin } from "../auth/plugin.ts";
31
- import { tokenFromCookieHeader } from "../auth/cookies.ts";
32
- import { setActiveGateAuthContext } from "../auth/method-context.ts";
25
+ import type { AppAuthBinding } from "./auth-resolve.ts";
26
+ import type { AuthHttpMaterialization } from "../auth/bindings.ts";
27
+ import type { WiredGateAuth } from "./app-auth.ts";
33
28
  import {
34
29
  resolveGateConfig,
35
30
  type GateOptions,
36
31
  type ResolvedGateConfig,
37
32
  } from "../elements/gate/config.ts";
33
+ import { requirePackageModule } from "../shared/lazy-src.ts";
38
34
  import type { TemplateCatalog } from "../elements/channel/runtime.ts";
39
35
  import { parseAcceptLanguage } from "../elements/channel/locale.ts";
40
36
  import { runWithLocale } from "../i18n/locale-context.ts";
41
37
  import { getMessageCatalogs, matchConfiguredLocale } from "../i18n/messages.ts";
42
38
  import { runDurable } from "../elements/clock/durable.ts";
43
39
  import { isFlow, type AnyFlowDef } from "./flow.ts";
40
+ import { failureCodeOf, runCompensationPhase } from "./compensate.ts";
44
41
  import { fxRetry } from "./concurrency.ts";
45
42
  import {
46
43
  createFxContext,
@@ -85,6 +82,7 @@ import type { JournalRuntime } from "./boot-bind/journal.ts";
85
82
  import { listBindings, resetBindings, type Binding } from "./on.ts";
86
83
  import {
87
84
  channelTemplateRegistry,
85
+ requiredEnvRegistry,
88
86
  secretRegistry,
89
87
  signalRegistry,
90
88
  storeRegistry,
@@ -159,11 +157,11 @@ export interface OkeOptions {
159
157
  * subject key.
160
158
  */
161
159
  readonly archiveInputFields?: readonly string[];
162
- /** Active environment for {@link OkeApp.boot} (defaults to `local`). */
160
+ /** Active environment for {@link OkeApp.boot} (`dev` / `test` / `prod`). */
163
161
  readonly env?: BootOptions["env"];
164
162
  /**
165
- * Docker mode (`oke dev -d` / `OKE_DOCKER=1`) — force the `docker`
166
- * driver profile at boot.
163
+ * Compose mode (`oke dev` / `OKE_DOCKER=1`) — select the `dev` driver
164
+ * profile at boot when `env` is omitted.
167
165
  */
168
166
  readonly docker?: BootOptions["docker"];
169
167
  /** Optional `oke.config.ts` document consumed at boot. */
@@ -485,22 +483,35 @@ export function oke(options: OkeOptions): OkeApp {
485
483
  // explicitly-passed decl that is also in the registry is not doubled.
486
484
  const registrySnapshot =
487
485
  registry === "ignore"
488
- ? { stores: [], secrets: [], signals: [], channelTemplates: [] }
486
+ ? { stores: [], secrets: [], requiredEnv: [], signals: [], channelTemplates: [] }
489
487
  : {
490
488
  stores: storeRegistry.slice(),
491
489
  secrets: secretRegistry.slice(),
490
+ requiredEnv: requiredEnvRegistry.slice(),
492
491
  signals: signalRegistry.slice(),
493
492
  channelTemplates: channelTemplateRegistry.slice(),
494
493
  };
495
494
  if (registry === "consume") {
496
495
  storeRegistry.length = 0;
497
496
  secretRegistry.length = 0;
497
+ requiredEnvRegistry.length = 0;
498
498
  signalRegistry.length = 0;
499
499
  channelTemplateRegistry.length = 0;
500
500
  }
501
501
  const effectiveStores = mergeUnique(options.stores, registrySnapshot.stores);
502
502
  const effectiveSecrets = mergeUnique(options.secrets, registrySnapshot.secrets);
503
503
  const effectiveSignals = mergeUnique(options.signals, registrySnapshot.signals);
504
+ /**
505
+ * Fold registered `vault.env.required` names into the vault boot options so
506
+ * boot reports missing env vars in the same gap list as missing secrets.
507
+ *
508
+ * @param base - Vault options from `oke({ vault })` / a boot override
509
+ */
510
+ const withRequiredEnv = (base: BootOptions["vault"]): BootOptions["vault"] => {
511
+ const names = [...new Set([...(base?.requiredEnv ?? []), ...registrySnapshot.requiredEnv])];
512
+ if (names.length === 0) return base;
513
+ return { ...(base ?? {}), requiredEnv: names };
514
+ };
504
515
  // Stay `undefined` (never a defined-but-empty object) when neither an
505
516
  // explicit `options.channel` nor a registered template exists — a defined
506
517
  // object here would flip `resolveElementNeeds`'s `channel` need to `true`
@@ -519,14 +530,6 @@ export function oke(options: OkeOptions): OkeApp {
519
530
  gate: options.gate,
520
531
  env: options.env,
521
532
  });
522
- let authMaterialization: AuthHttpMaterialization | undefined;
523
- if (gateConfig.auth?.http) {
524
- authMaterialization = createAuthHttpBindings(gateConfig.auth, {
525
- rateLimitEnabled: gateConfig.rateLimitEnabled,
526
- sessions: gateConfig.auth.sessions,
527
- });
528
- adopted.push(...authMaterialization.bindings);
529
- }
530
533
 
531
534
  /** Retained for test harness / boot merges. */
532
535
  const $options = options;
@@ -540,27 +543,26 @@ export function oke(options: OkeOptions): OkeApp {
540
543
  /** Runtime route table — types accumulate on the returned {@link OkeApp}. */
541
544
  const routes: RuntimeRouteMap = {};
542
545
 
543
- // Absorb auth() tables into gate.auth — `.needs("auth")` sees plugin name.
546
+ let authMaterialization: AuthHttpMaterialization | undefined;
547
+ let wiredAuth: WiredGateAuth | undefined;
548
+ let authBinding: AppAuthBinding | undefined;
544
549
  if (gateConfig.auth) {
545
- applyPlugin(
546
- pluginRegistry,
547
- authPlugin({
548
- secret: gateConfig.auth.secret,
549
- accessTtlMs: gateConfig.auth.session.accessTtlMs,
550
- refreshTtlMs: gateConfig.auth.session.refreshTtlMs,
551
- session: {
552
- accessTtlMs: gateConfig.auth.session.accessTtlMs,
553
- refreshTtlMs: gateConfig.auth.session.refreshTtlMs,
554
- idleTtlMs: gateConfig.auth.session.idleTtlMs,
555
- absoluteTtlMs: gateConfig.auth.session.absoluteTtlMs,
556
- singleSessionPerUser: gateConfig.auth.session.singleSessionPerUser,
557
- },
558
- password: gateConfig.auth.password,
559
- passwordPolicy: gateConfig.auth.passwordPolicy,
560
- breachCheck: gateConfig.auth.breachCheck,
561
- }),
562
- appPluginScope,
550
+ const { wireGateAuth } = requirePackageModule<typeof import("./app-auth.ts")>(
551
+ "kernel/app-auth",
552
+ "app-auth",
563
553
  );
554
+ wiredAuth = wireGateAuth({
555
+ gateConfig: { ...gateConfig, auth: gateConfig.auth },
556
+ now: options.fx?.now,
557
+ });
558
+ authMaterialization = wiredAuth.materialization;
559
+ authBinding = wiredAuth.authBinding;
560
+ if (authMaterialization) {
561
+ adopted.push(...authMaterialization.bindings);
562
+ }
563
+ if (wiredAuth.authPlugin) {
564
+ applyPlugin(pluginRegistry, wiredAuth.authPlugin, appPluginScope);
565
+ }
564
566
  }
565
567
 
566
568
  const smart = createRouter<Binding>(options.router ?? "default");
@@ -655,30 +657,11 @@ export function oke(options: OkeOptions): OkeApp {
655
657
  let bootResult: BootResult | undefined;
656
658
  let bootPromise: Promise<BootResult> | undefined;
657
659
  let readyState: ReadyState = "booting";
658
- let bootEnv: BootOptions["env"] = options.env ?? "local";
659
- let authBinding: AppAuthBinding | undefined =
660
- gateConfig.auth !== undefined
661
- ? createAppAuthBinding({
662
- secret: gateConfig.auth.secret,
663
- sessions: authMaterialization?.ctx.sessions ?? gateConfig.auth.sessions,
664
- now: gateConfig.auth.now ?? options.fx?.now,
665
- })
666
- : undefined;
667
- if (authBinding) {
668
- setActiveGateAuthContext({
669
- secret: authBinding.secret,
670
- sessions: authBinding.sessions,
671
- now: authBinding.now,
672
- passwordPolicy: gateConfig.auth?.passwordPolicy,
673
- password: gateConfig.auth?.password,
674
- breachCheck: gateConfig.auth?.breachCheck,
675
- });
676
- } else {
677
- setActiveGateAuthContext(undefined);
678
- }
660
+ let bootEnv: BootOptions["env"] = options.env;
679
661
  // Fallback journal for pre-boot / `autoBoot: false` unit tests. A booted
680
662
  // app replaces this with the bound `drivers.journal` store (postgres etc.).
681
- const fallbackJournalStore = createMemoryJournalStore();
663
+ // Constructed on first use so non-durable HTTP apps skip the alloc.
664
+ let fallbackJournalStore: JournalStore | undefined;
682
665
  const fallbackJournalInstanceId = `app-${crypto.randomUUID()}`;
683
666
  const sleepingRuns = new Map<
684
667
  string,
@@ -700,6 +683,7 @@ export function oke(options: OkeOptions): OkeApp {
700
683
  if (bound) {
701
684
  return { store: bound.store, instanceId: bound.instanceId, leaseMs: bound.leaseMs };
702
685
  }
686
+ fallbackJournalStore ??= createMemoryJournalStore();
703
687
  return {
704
688
  store: fallbackJournalStore,
705
689
  instanceId: fallbackJournalInstanceId,
@@ -787,7 +771,7 @@ export function oke(options: OkeOptions): OkeApp {
787
771
  }
788
772
  }
789
773
 
790
- /** Boot-time orphan scan — `running`/`sleeping` runs with no live lease. */
774
+ /** Boot-time orphan scan — `running`/`sleeping`/`compensating` with no live lease. */
791
775
  async function resumeOrphanedDurableRuns(): Promise<void> {
792
776
  const { store } = activeJournal();
793
777
  if (!hasJournalLease(store)) return;
@@ -807,7 +791,15 @@ export function oke(options: OkeOptions): OkeApp {
807
791
  const { bootApplication, resolveElementNeeds } = await import("./boot.ts");
808
792
  const { assertHttpGatePosture } = await import("../elements/gate/boot.ts");
809
793
  const { assertPluginNeeds, buildAvailableNeedTokens } = await import("./plugin-needs.ts");
810
- bootEnv = overrides?.env ?? options.env ?? "local";
794
+ const dockerFlag =
795
+ overrides?.docker === true ||
796
+ (overrides?.docker !== false &&
797
+ (options.docker === true || (options.docker !== false && process.env.OKE_DOCKER === "1")));
798
+ // Mirror bootApplication: prod / compose-dev / else test (PGLite + memory).
799
+ bootEnv =
800
+ overrides?.env ??
801
+ options.env ??
802
+ (process.env.NODE_ENV === "production" ? "prod" : dockerFlag ? "dev" : "test");
811
803
 
812
804
  // Prod without a real secret fails even if construction minted a dev secret.
813
805
  if (gateConfig.auth?.secretMinted && bootEnv === "prod") {
@@ -830,7 +822,7 @@ export function oke(options: OkeOptions): OkeApp {
830
822
  config: overrides?.config ?? options.config,
831
823
  elements: overrides?.elements ?? options.elements,
832
824
  secrets: overrides?.secrets ?? effectiveSecrets,
833
- vault: overrides?.vault ?? options.vault,
825
+ vault: withRequiredEnv(overrides?.vault ?? options.vault),
834
826
  gates: [...baseGates, ...authGates],
835
827
  signals: overrides?.signals ?? effectiveSignals,
836
828
  clocks: overrides?.clocks ?? options.clocks,
@@ -900,7 +892,7 @@ export function oke(options: OkeOptions): OkeApp {
900
892
  rootDir: overrides?.rootDir ?? options.rootDir,
901
893
  elements: overrides?.elements ?? options.elements,
902
894
  secrets: [...baseSecrets, ...pluginSecrets],
903
- vault: overrides?.vault ?? options.vault,
895
+ vault: withRequiredEnv(overrides?.vault ?? options.vault),
904
896
  gates: [...baseGates, ...authGates, ...pluginGates],
905
897
  unguardedHttp,
906
898
  signals: [...baseSignals, ...pluginSignals],
@@ -955,12 +947,9 @@ export function oke(options: OkeOptions): OkeApp {
955
947
  readyState = "ready";
956
948
  }
957
949
  // Prefer the booted clock for access-token expiry checks.
958
- if (authBinding) {
959
- authBinding = createAppAuthBinding({
960
- secret: authBinding.secret,
961
- sessions: authBinding.sessions,
962
- now: result.clock?.now.bind(result.clock) ?? authBinding.now,
963
- });
950
+ if (wiredAuth && authBinding) {
951
+ const now = result.clock?.now.bind(result.clock) ?? authBinding.now;
952
+ authBinding = wiredAuth.rebind(now);
964
953
  }
965
954
  return result;
966
955
  }
@@ -1013,7 +1002,7 @@ export function oke(options: OkeOptions): OkeApp {
1013
1002
  registerFlow(flowDef);
1014
1003
 
1015
1004
  const i18nConfig = options.config?.i18n;
1016
- const configuredLocales = i18nConfig?.locales ?? ["en", "ar"];
1005
+ const configuredLocales = i18nConfig?.locales ?? ["en"];
1017
1006
  const defaultLocale = i18nConfig?.default ?? "en";
1018
1007
  const acceptLanguage = extras?.request?.headers.get("accept-language") ?? undefined;
1019
1008
  const resolvedLocale = matchConfiguredLocale(
@@ -1121,21 +1110,24 @@ export function oke(options: OkeOptions): OkeApp {
1121
1110
 
1122
1111
  const binding = authBinding;
1123
1112
  const cookieOpts = gateConfig.auth?.cookies;
1113
+ const verifyBearer = wiredAuth?.verifyBearerToken;
1114
+ const cookieToken = wiredAuth?.tokenFromCookieHeader;
1124
1115
  const elementHooks = createElementPipelineHooks({
1125
1116
  gates: booted.gate,
1126
1117
  principals,
1127
1118
  telemetry,
1128
1119
  allowTestPrincipals: testMode,
1129
- verifyBearer: binding ? async (token) => verifyBearerToken(binding, token) : undefined,
1120
+ verifyBearer:
1121
+ binding && verifyBearer ? async (token) => verifyBearer(binding, token) : undefined,
1130
1122
  // Phase 1a: opt-in cookie → Bearer when Authorization is absent.
1131
1123
  resolveToken:
1132
- binding && cookieOpts?.enabled
1124
+ binding && cookieOpts?.enabled && cookieToken
1133
1125
  ? (request) => {
1134
1126
  const header = request.headers.get("authorization");
1135
1127
  if (header?.startsWith("Bearer ")) {
1136
1128
  return header.slice("Bearer ".length).trim() || undefined;
1137
1129
  }
1138
- return tokenFromCookieHeader(request.headers.get("cookie"), cookieOpts);
1130
+ return cookieToken(request.headers.get("cookie"), cookieOpts);
1139
1131
  }
1140
1132
  : undefined,
1141
1133
  });
@@ -1281,30 +1273,13 @@ export function oke(options: OkeOptions): OkeApp {
1281
1273
  });
1282
1274
  } else if (!sleeping && isTerminalFailure(result)) {
1283
1275
  const terminalErr = result.failure ?? result.ctx.error;
1284
- if (flowDef.compensate) {
1285
- try {
1286
- const completedSteps = journalSession.run.entries
1287
- .filter((e) => e.kind === "step")
1288
- .map((e) => e.name);
1289
- await flowDef.compensate(
1290
- {
1291
- input: ctx.input as never,
1292
- error: terminalErr,
1293
- completedSteps,
1294
- },
1295
- fx,
1296
- );
1297
- } catch (compErr) {
1298
- await journalSession.commit("failed", {
1299
- error: `compensate:${failureCodeOf(compErr)}`,
1300
- });
1301
- }
1302
- }
1303
- if (journalSession.run.status === "running") {
1304
- await journalSession.commit("failed", {
1305
- error: failureCodeOf(terminalErr),
1306
- });
1307
- }
1276
+ await runCompensationPhase({
1277
+ flow: flowDef,
1278
+ input: ctx.input,
1279
+ session: journalSession,
1280
+ fx,
1281
+ error: terminalErr,
1282
+ });
1308
1283
  } else if (!sleeping) {
1309
1284
  await journalSession.commit("completed", { output: result.output });
1310
1285
  }
@@ -1699,14 +1674,6 @@ function isTerminalFailure(result: PipelineResult): boolean {
1699
1674
  return result.ctx.error !== undefined && !isJournalSuspend(result.ctx.error);
1700
1675
  }
1701
1676
 
1702
- /** Machine-readable code for journal / Runs failure fields. */
1703
- function failureCodeOf(err: unknown): string {
1704
- if (isFlowFailure(err)) return err.error.code;
1705
- if (err instanceof Error && err.name && err.name !== "Error") return err.name;
1706
- if (err instanceof Error && err.message) return err.message.slice(0, 120);
1707
- return "Error";
1708
- }
1709
-
1710
1677
  /** Coerce a thrown value into a FlowFailure for Runs wide events. */
1711
1678
  function failureFromUnknown(err: unknown): FlowFailure {
1712
1679
  if (isFlowFailure(err)) return err;
@@ -40,7 +40,7 @@ describe("oke() auto-registry — stores/secrets/signals/channel.templates", ()
40
40
  sends: ["before-note-created"],
41
41
  },
42
42
  do: async (_input, fx) => {
43
- const secret = fx.vault(secretBefore);
43
+ const secret = await fx.vault.get(secretBefore);
44
44
  await fx.emit(signalBefore, {});
45
45
  await fx.send(templateBefore, { to: "you@localhost", data: {} });
46
46
  return { secret };
@@ -78,7 +78,7 @@ describe("oke() auto-registry — stores/secrets/signals/channel.templates", ()
78
78
  sends: ["after-note-created"],
79
79
  },
80
80
  do: async (_input, fx) => {
81
- const secret = fx.vault(secretAfter);
81
+ const secret = await fx.vault.get(secretAfter);
82
82
  await fx.emit(signalAfter, {});
83
83
  await fx.send(templateAfter, { to: "you@localhost", data: {} });
84
84
  return { secret };
@@ -16,15 +16,15 @@ describe("ai boot binder", () => {
16
16
  });
17
17
 
18
18
  test("resolveAiDriverId defaults to mock; honours config + OKE_AI_DRIVER in docker", () => {
19
- expect(resolveAiDriverId({}, "local")).toBe("mock");
20
- expect(resolveAiDriverId({ config: { drivers: { ai: { local: "ollama" } } } }, "local")).toBe(
19
+ expect(resolveAiDriverId({}, "test")).toBe("mock");
20
+ expect(resolveAiDriverId({ config: { drivers: { ai: { test: "ollama" } } } }, "test")).toBe(
21
21
  "ollama",
22
22
  );
23
23
 
24
24
  const prev = process.env.OKE_AI_DRIVER;
25
25
  process.env.OKE_AI_DRIVER = "ollama";
26
26
  try {
27
- expect(resolveAiDriverId({}, "local", true)).toBe("ollama");
27
+ expect(resolveAiDriverId({}, "test", true)).toBe("ollama");
28
28
  } finally {
29
29
  if (prev === undefined) delete process.env.OKE_AI_DRIVER;
30
30
  else process.env.OKE_AI_DRIVER = prev;
@@ -48,12 +48,12 @@ describe("ai boot binder", () => {
48
48
  test("bindAi uses injected defaultDriver over config (tests stay on mock)", () => {
49
49
  const runtime = bindAi(
50
50
  {
51
- config: { drivers: { ai: { local: "ollama" } } },
51
+ config: { drivers: { ai: { test: "ollama" } } },
52
52
  ai: { defaultDriver: mockAiDriver },
53
53
  },
54
54
  undefined,
55
55
  () => 0,
56
- "local",
56
+ "test",
57
57
  );
58
58
  expect(runtime.autoCacheDisabled).toBe(true);
59
59
  });
@@ -33,7 +33,7 @@ export function bindAi(
33
33
  options: BootOptions,
34
34
  gate: GateRuntime | undefined,
35
35
  now: () => number,
36
- env: ConfigEnv = "local",
36
+ env: ConfigEnv = "test",
37
37
  docker = false,
38
38
  ): AiRuntime {
39
39
  const id = resolveAiDriverId(options, env, docker);
@@ -115,6 +115,7 @@ export function openDefaultsFor(id: string, docker = false): AiOpenOptions {
115
115
  return {
116
116
  ...(apiKey ? { apiKey } : {}),
117
117
  ...(baseUrl ? { baseUrl } : {}),
118
+ ...(process.env.OKE_AI_MODEL?.trim() ? { model: process.env.OKE_AI_MODEL.trim() } : {}),
118
119
  };
119
120
  }
120
121
  return {};
@@ -58,23 +58,23 @@ describe("bindChannel driver resolution", () => {
58
58
  config: {
59
59
  drivers: {
60
60
  channel: {
61
- email: { local: "console", docker: "smtp", test: "console", prod: "sndr" },
62
- sms: { local: "console", docker: "taqnyat", test: "msegat", prod: "taqnyat" },
61
+ email: { dev: "smtp", test: "console", prod: "sndr" },
62
+ sms: { dev: "taqnyat", test: "msegat", prod: "taqnyat" },
63
63
  },
64
64
  },
65
65
  },
66
66
  };
67
67
 
68
68
  test("uses console locally and smtp in docker", () => {
69
- expect(resolveEmailDriverId(options, "local", false)).toBe("console");
70
- expect(resolveEmailDriverId(options, "docker", true)).toBe("smtp");
69
+ expect(resolveEmailDriverId(options, "test", false)).toBe("console");
70
+ expect(resolveEmailDriverId(options, "dev", true)).toBe("smtp");
71
71
  expect(resolveEmailDriverId(options, "prod", false)).toBe("sndr");
72
72
  });
73
73
 
74
74
  test("resolves sms driver ids", () => {
75
- expect(resolveSmsDriverId(options, "local")).toBe("console");
76
- expect(resolveSmsDriverId(options, "docker")).toBe("taqnyat");
77
75
  expect(resolveSmsDriverId(options, "test")).toBe("msegat");
76
+ expect(resolveSmsDriverId(options, "dev")).toBe("taqnyat");
77
+ expect(resolveSmsDriverId(options, "prod")).toBe("taqnyat");
78
78
  });
79
79
 
80
80
  test("parses SMTP_URL and optional auth overrides", () => {
@@ -3,11 +3,7 @@
3
3
  */
4
4
 
5
5
  import { resolveDriverId, type ConfigEnv } from "../../config/index.ts";
6
- import {
7
- CHANNEL_EMAIL_DEFAULTS,
8
- CHANNEL_SMS_DEFAULTS,
9
- dockerFlagDefaults,
10
- } from "../../config/driver-defaults.ts";
6
+ import { CHANNEL_EMAIL_DEFAULTS, CHANNEL_SMS_DEFAULTS } from "../../config/driver-defaults.ts";
11
7
  import { openConsoleChannel } from "../../drivers/channel-console.ts";
12
8
  import { openMsegatChannel } from "../../drivers/channel-msegat.ts";
13
9
  import { openResendChannel } from "../../drivers/channel-resend.ts";
@@ -86,12 +82,9 @@ export function resolveEmailDriverId(
86
82
  env: ConfigEnv,
87
83
  docker: boolean,
88
84
  ): string {
85
+ void docker;
89
86
  // Defaults cover every ConfigEnv key, so this is never undefined.
90
- return resolveDriverId(
91
- options.config?.drivers?.channel?.email,
92
- env,
93
- dockerFlagDefaults(CHANNEL_EMAIL_DEFAULTS, docker),
94
- )!;
87
+ return resolveDriverId(options.config?.drivers?.channel?.email, env, CHANNEL_EMAIL_DEFAULTS)!;
95
88
  }
96
89
 
97
90
  /**