okengine 0.10.2 → 0.11.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 (343) hide show
  1. package/package.json +76 -10
  2. package/site/content/docs/ai/mcp.mdx +0 -1
  3. package/site/content/docs/deployment/docker-swarm.mdx +48 -111
  4. package/site/content/docs/deployment/docker.mdx +43 -36
  5. package/site/content/docs/deployment/index.mdx +9 -9
  6. package/site/content/docs/deployment/kubernetes.mdx +4 -4
  7. package/site/content/docs/elements/ai.mdx +7 -13
  8. package/site/content/docs/elements/channel.mdx +9 -13
  9. package/site/content/docs/elements/clock.mdx +8 -14
  10. package/site/content/docs/elements/flow.mdx +46 -54
  11. package/site/content/docs/elements/gate.mdx +6 -11
  12. package/site/content/docs/elements/signal.mdx +2 -8
  13. package/site/content/docs/elements/store.mdx +79 -93
  14. package/site/content/docs/elements/vault.mdx +89 -76
  15. package/site/content/docs/get-started/basic-usage.mdx +8 -14
  16. package/site/content/docs/get-started/installation.mdx +36 -36
  17. package/site/content/docs/get-started/introduction.mdx +11 -0
  18. package/site/content/docs/get-started/why.mdx +3 -4
  19. package/site/content/docs/index.mdx +0 -1
  20. package/site/content/docs/meta.json +0 -1
  21. package/site/content/docs/plugins/anonymous.mdx +1 -1
  22. package/site/content/docs/plugins/magic-link.mdx +11 -11
  23. package/site/content/docs/plugins/otp.mdx +2 -2
  24. package/site/content/docs/plugins/passkey.mdx +1 -1
  25. package/site/content/docs/plugins/two-factor.mdx +1 -1
  26. package/site/content/docs/plugins/username.mdx +1 -1
  27. package/site/content/docs/recipes/caddy.mdx +1 -1
  28. package/site/content/docs/recipes/cockroachdb.mdx +2 -2
  29. package/site/content/docs/recipes/dragonfly.mdx +1 -1
  30. package/site/content/docs/recipes/index.mdx +1 -2
  31. package/site/content/docs/recipes/llama-cpp.mdx +21 -23
  32. package/site/content/docs/recipes/mailpit.mdx +11 -11
  33. package/site/content/docs/recipes/meilisearch.mdx +10 -10
  34. package/site/content/docs/recipes/meta.json +0 -1
  35. package/site/content/docs/recipes/ollama.mdx +1 -1
  36. package/site/content/docs/recipes/pgdog.mdx +12 -9
  37. package/site/content/docs/recipes/postgres.mdx +3 -3
  38. package/site/content/docs/recipes/redis.mdx +2 -2
  39. package/site/content/docs/recipes/rustfs.mdx +10 -7
  40. package/site/content/docs/recipes/sglang.mdx +1 -1
  41. package/site/content/docs/recipes/supabase-docker.mdx +1 -1
  42. package/site/content/docs/recipes/timescale.mdx +2 -2
  43. package/site/content/docs/recipes/valkey.mdx +1 -1
  44. package/site/content/docs/recipes/vllm.mdx +1 -1
  45. package/site/content/docs/recipes/yugabytedb.mdx +2 -2
  46. package/site/content/docs/reference/cli.md +61 -38
  47. package/site/content/docs/reference/configuration.mdx +145 -70
  48. package/site/content/docs/reference/environment-variables.mdx +36 -28
  49. package/site/content/docs/reference/errors.mdx +28 -29
  50. package/site/content/docs/reference/fx.mdx +14 -7
  51. package/site/content/docs/reference/i18n.mdx +17 -6
  52. package/site/content/docs/reference/index.mdx +5 -0
  53. package/site/content/docs/reference/meta.json +1 -0
  54. package/site/content/docs/reference/migrating-environments.mdx +158 -0
  55. package/site/content/docs/reference/plugins.mdx +2 -2
  56. package/src/auth/gate-auth.test.ts +12 -10
  57. package/src/cli/ai-setup/ai-setup.test.ts +30 -16
  58. package/src/cli/ai-setup/apply.ts +111 -15
  59. package/src/cli/ai-setup/catalog.ts +3 -3
  60. package/src/cli/ai-setup/index.ts +1 -1
  61. package/src/cli/ai-setup/prompts.ts +1 -1
  62. package/src/cli/ai-setup/recommend.test.ts +1 -1
  63. package/src/cli/ask-seed.test.ts +3 -3
  64. package/src/cli/build.ts +8 -9
  65. package/src/cli/db-auto-push.test.ts +6 -3
  66. package/src/cli/db-auto-push.ts +7 -5
  67. package/src/cli/db-seed.ts +7 -8
  68. package/src/cli/db.test.ts +70 -71
  69. package/src/cli/db.ts +86 -12
  70. package/src/cli/dev-controller.test.ts +35 -0
  71. package/src/cli/dev-controller.ts +298 -0
  72. package/src/cli/dev-controls.ts +11 -65
  73. package/src/cli/dev-db-push.test.ts +47 -15
  74. package/src/cli/dev-schema-sync.test.ts +11 -11
  75. package/src/cli/dev-schema-sync.ts +10 -10
  76. package/src/cli/dev-session-lock.test.ts +106 -0
  77. package/src/cli/dev-session-lock.ts +209 -0
  78. package/src/cli/dev.test.ts +246 -114
  79. package/src/cli/dev.ts +153 -205
  80. package/src/cli/doc-staleness.test.ts +3 -6
  81. package/src/cli/docker-clean.test.ts +33 -1
  82. package/src/cli/docker-clean.ts +28 -33
  83. package/src/cli/docker-cli.test.ts +12 -11
  84. package/src/cli/docker.ts +95 -20
  85. package/src/cli/doctor.test.ts +40 -0
  86. package/src/cli/doctor.ts +142 -9
  87. package/src/cli/drizzle-env.test.ts +11 -13
  88. package/src/cli/drizzle-env.ts +18 -11
  89. package/src/cli/ensure-drizzle-config.ts +11 -14
  90. package/src/cli/hero-meta.test.ts +15 -15
  91. package/src/cli/hero-meta.ts +10 -9
  92. package/src/cli/index.ts +36 -6
  93. package/src/cli/load-config.images.test.ts +44 -10
  94. package/src/cli/load-config.source.test.ts +50 -0
  95. package/src/cli/load-config.ts +35 -29
  96. package/src/cli/meilisearch-local.ts +1 -2
  97. package/src/cli/mode.ts +7 -58
  98. package/src/cli/registry.ts +175 -19
  99. package/src/cli/resolve-dev-sql-env.test.ts +8 -16
  100. package/src/cli/resolve-dev-sql-env.ts +13 -15
  101. package/src/cli/safe-defaults.test.ts +29 -11
  102. package/src/cli/schema.ts +65 -16
  103. package/src/cli/test.ts +58 -0
  104. package/src/cli/tui/App.tsx +252 -0
  105. package/src/cli/tui/DevLive.tsx +116 -0
  106. package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
  107. package/src/cli/tui/components/HeaderBar.tsx +67 -0
  108. package/src/cli/tui/components/LogStream.tsx +47 -0
  109. package/src/cli/tui/components/PanelCard.tsx +39 -0
  110. package/src/cli/tui/components/SelectRow.tsx +37 -0
  111. package/src/cli/tui/components/SlashPalette.tsx +172 -0
  112. package/src/cli/tui/components/Spinner.tsx +32 -0
  113. package/src/cli/tui/components/StatusBar.tsx +51 -0
  114. package/src/cli/tui/components/StatusDot.tsx +37 -0
  115. package/src/cli/tui/components/TabBar.tsx +49 -0
  116. package/src/cli/tui/components/Wordmark.tsx +43 -0
  117. package/src/cli/tui/format.ts +38 -0
  118. package/src/cli/tui/keys.test.ts +18 -0
  119. package/src/cli/tui/keys.ts +26 -0
  120. package/src/cli/tui/launch.test.ts +18 -0
  121. package/src/cli/tui/launch.ts +57 -0
  122. package/src/cli/tui/panels/Dashboard.tsx +201 -0
  123. package/src/cli/tui/panels/Database.tsx +198 -0
  124. package/src/cli/tui/panels/Dev.tsx +257 -0
  125. package/src/cli/tui/panels/Docker.tsx +357 -0
  126. package/src/cli/tui/panels/Navigator.test.ts +22 -0
  127. package/src/cli/tui/panels/Navigator.tsx +213 -0
  128. package/src/cli/tui/slash-catalog.test.ts +31 -0
  129. package/src/cli/tui/slash-catalog.ts +184 -0
  130. package/src/cli/tui/slash-run.ts +54 -0
  131. package/src/cli/tui/state/ports.ts +95 -0
  132. package/src/cli/tui/theme.ts +24 -0
  133. package/src/cli/vault-cmd.test.ts +402 -0
  134. package/src/cli/vault-cmd.ts +727 -69
  135. package/src/cli/vault-secure-input.test.ts +56 -0
  136. package/src/cli/vault-secure-input.ts +124 -0
  137. package/src/compiler/effects-infer.ts +7 -0
  138. package/src/compiler/extract.ts +12 -0
  139. package/src/compiler/fixtures/skyport/oke.config.ts +7 -5
  140. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
  141. package/src/compiler/fixtures/skyport.expected.json +1 -1
  142. package/src/compiler/generate-adopt.ts +27 -1
  143. package/src/compiler-entry.ts +7 -0
  144. package/src/config/define-config.test.ts +111 -47
  145. package/src/config/driver-defaults.test.ts +115 -0
  146. package/src/config/driver-defaults.ts +156 -0
  147. package/src/config/index.ts +246 -131
  148. package/src/config/resolve-driver.test.ts +14 -19
  149. package/src/console/server/app.ts +4 -2
  150. package/src/console/server/console.test.ts +58 -1
  151. package/src/console/server/flows.ts +22 -2
  152. package/src/console/server/index.ts +3 -0
  153. package/src/console/server/operator-db.test.ts +55 -13
  154. package/src/console/server/operator-db.ts +305 -263
  155. package/src/console/server/serve.ts +16 -7
  156. package/src/console/server/state.ts +31 -9
  157. package/src/console/server/vault.test.ts +120 -5
  158. package/src/console/server/vault.ts +208 -12
  159. package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
  160. package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
  161. package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
  162. package/src/console/ui/dist/index.html +2 -2
  163. package/src/console/ui/shell/client.ts +16 -0
  164. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
  165. package/src/console/ui/vault/VaultA11yView.tsx +33 -1
  166. package/src/console/ui/vault/backend.test.ts +75 -0
  167. package/src/console/ui/vault/backend.ts +117 -0
  168. package/src/console/ui/vault/fixture.ts +21 -2
  169. package/src/console/ui/vault/index.ts +11 -1
  170. package/src/console/ui/vault/types.ts +26 -0
  171. package/src/docker/compose.ts +164 -61
  172. package/src/docker/derive.ts +106 -12
  173. package/src/docker/docker.test.ts +156 -110
  174. package/src/docker/helpers.ts +98 -0
  175. package/src/docker/images-config.test.ts +163 -0
  176. package/src/docker/index.ts +30 -5
  177. package/src/docker/recipes/index.ts +0 -3
  178. package/src/docker/recipes/llama-cpp.ts +4 -4
  179. package/src/docker/recipes/pgdog.ts +9 -2
  180. package/src/docker/resources.test.ts +71 -0
  181. package/src/docker/resources.ts +176 -0
  182. package/src/docker/stack-id.test.ts +11 -4
  183. package/src/docker/stack-id.ts +42 -32
  184. package/src/docker/stack.integration.test.ts +4 -15
  185. package/src/docker/types.ts +35 -3
  186. package/src/drivers/ai-ollama.integration.test.ts +2 -2
  187. package/src/drivers/conformance.test.ts +11 -12
  188. package/src/drivers/drizzle-dialect.test.ts +0 -4
  189. package/src/drivers/drizzle-dialect.ts +7 -13
  190. package/src/drivers/index.ts +24 -7
  191. package/src/drivers/journal-postgres.test.ts +3 -1
  192. package/src/drivers/journal-postgres.ts +4 -2
  193. package/src/drivers/meilisearch.ts +1 -1
  194. package/src/drivers/pglite.ts +28 -5
  195. package/src/drivers/types.ts +9 -11
  196. package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
  197. package/src/drivers/vault-aws-secrets-manager.ts +333 -0
  198. package/src/drivers/vault-builtin.test.ts +110 -0
  199. package/src/drivers/vault-builtin.ts +184 -0
  200. package/src/drivers/vault-driver-removal.test.ts +13 -0
  201. package/src/drivers/vault-managed.test.ts +95 -0
  202. package/src/drivers/vault-managed.ts +101 -25
  203. package/src/drivers/vault-types.ts +25 -5
  204. package/src/elements/ai/runtime.ts +1 -1
  205. package/src/elements/clock/chaos.test.ts +4 -1
  206. package/src/elements/clock/durable.ts +57 -4
  207. package/src/elements/clock.test.ts +13 -14
  208. package/src/elements/gate/constants.ts +18 -0
  209. package/src/elements/gate.test.ts +1 -1
  210. package/src/elements/gate.ts +2 -2
  211. package/src/elements/store/domain-ddl.test.ts +5 -12
  212. package/src/elements/store/emit-drizzle.ts +44 -19
  213. package/src/elements/store/index-boot.test.ts +15 -116
  214. package/src/elements/store/load-plugin-tables.ts +1 -1
  215. package/src/elements/store/resource-list-docs.fixture.ts +12 -6
  216. package/src/elements/store/resource-list-docs.test.ts +64 -44
  217. package/src/elements/store/resource.test.ts +17 -10
  218. package/src/elements/store/runtime.ts +9 -12
  219. package/src/elements/store/schema-decl.test.ts +16 -32
  220. package/src/elements/store/seed.test.ts +1 -2
  221. package/src/elements/store/seed.ts +7 -8
  222. package/src/elements/store/sql-condition.test.ts +2 -2
  223. package/src/elements/store/sql-session.test.ts +8 -10
  224. package/src/elements/store/sql-session.ts +10 -2
  225. package/src/elements/store.test.ts +13 -13
  226. package/src/elements/vault/audit.ts +233 -0
  227. package/src/elements/vault/boot-chain.test.ts +78 -0
  228. package/src/elements/vault/boot-chain.ts +43 -39
  229. package/src/elements/vault/builtin-adapter.test.ts +479 -0
  230. package/src/elements/vault/builtin-adapter.ts +1184 -0
  231. package/src/elements/vault/crypto.test.ts +237 -0
  232. package/src/elements/vault/crypto.ts +431 -0
  233. package/src/elements/vault/declare.ts +164 -3
  234. package/src/elements/vault/errors.ts +56 -0
  235. package/src/elements/vault/kms-unseal.test.ts +76 -0
  236. package/src/elements/vault/kms-unseal.ts +181 -0
  237. package/src/elements/vault/path.ts +90 -0
  238. package/src/elements/vault/resilience.test.ts +102 -0
  239. package/src/elements/vault/resilience.ts +107 -0
  240. package/src/elements/vault/runtime.ts +36 -4
  241. package/src/elements/vault/security-checklist.test.ts +130 -0
  242. package/src/elements/vault/storage.ts +448 -0
  243. package/src/elements/vault/types.ts +288 -0
  244. package/src/elements/vault/unseal.ts +150 -0
  245. package/src/elements/vault.test.ts +72 -4
  246. package/src/elements/vault.ts +91 -2
  247. package/src/full.ts +248 -0
  248. package/src/http.ts +41 -0
  249. package/src/i18n/interpolate-simple.ts +37 -0
  250. package/src/i18n/messages.ts +51 -4
  251. package/src/i18n-entry.ts +7 -0
  252. package/src/index.ts +35 -145
  253. package/src/journal-entry.ts +20 -0
  254. package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
  255. package/src/kernel/app.ts +40 -42
  256. package/src/kernel/auto-registry.test.ts +2 -2
  257. package/src/kernel/boot-bind/ai.test.ts +5 -5
  258. package/src/kernel/boot-bind/ai.ts +2 -1
  259. package/src/kernel/boot-bind/channel.test.ts +6 -6
  260. package/src/kernel/boot-bind/channel.ts +5 -4
  261. package/src/kernel/boot-bind/clock.ts +5 -5
  262. package/src/kernel/boot-bind/gate.ts +6 -7
  263. package/src/kernel/boot-bind/honor-config.test.ts +42 -42
  264. package/src/kernel/boot-bind/journal.ts +5 -5
  265. package/src/kernel/boot-bind/signal.ts +3 -1
  266. package/src/kernel/boot-bind/store.test.ts +55 -37
  267. package/src/kernel/boot-bind/store.ts +24 -34
  268. package/src/kernel/boot-bind/vault.ts +19 -4
  269. package/src/kernel/boot.test.ts +12 -4
  270. package/src/kernel/boot.ts +31 -19
  271. package/src/kernel/budget-entry.ts +1 -1
  272. package/src/kernel/compensate.test.ts +288 -1
  273. package/src/kernel/compensate.ts +126 -0
  274. package/src/kernel/concurrency.test.ts +10 -9
  275. package/src/kernel/effects-stamping.test.ts +27 -2
  276. package/src/kernel/element-registries.ts +5 -3
  277. package/src/kernel/flow.ts +4 -2
  278. package/src/kernel/fx.test.ts +119 -3
  279. package/src/kernel/fx.ts +215 -40
  280. package/src/kernel/index.ts +14 -0
  281. package/src/kernel/journal.ts +130 -7
  282. package/src/kernel/pipeline.test.ts +20 -6
  283. package/src/kernel/redacted.test.ts +35 -0
  284. package/src/kernel/redacted.ts +24 -2
  285. package/src/kernel/router/create-default.ts +16 -0
  286. package/src/kernel/router/create-edge.ts +18 -0
  287. package/src/kernel/router/create.ts +23 -0
  288. package/src/kernel/router/index.ts +23 -0
  289. package/src/kernel/router/linear.ts +88 -0
  290. package/src/kernel/router/regexp.ts +188 -0
  291. package/src/kernel/router/smart.ts +120 -0
  292. package/src/kernel/router/trie.ts +108 -0
  293. package/src/kernel/router/types.ts +56 -0
  294. package/src/kernel/router.ts +18 -532
  295. package/src/kernel-entry.ts +43 -0
  296. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  297. package/src/manifest/fixtures/skyport.manifest.json +1 -1
  298. package/src/plugins/config-source.test.ts +7 -7
  299. package/src/release/build-lib.ts +58 -0
  300. package/src/release/exports.test.ts +4 -2
  301. package/src/release/exports.ts +30 -4
  302. package/src/release/measure.ts +101 -22
  303. package/src/runs/duckdb.ts +7 -2
  304. package/src/shared/optional-peer.ts +41 -0
  305. package/src/term.test.ts +3 -44
  306. package/src/test/create-test-app.ts +12 -0
  307. package/src/test/provisions.integration.test.ts +1 -1
  308. package/src/test/reset-element-registries.ts +2 -0
  309. package/src/upgrade/codemods.test.ts +7 -20
  310. package/src/upgrade/codemods.ts +93 -60
  311. package/site/content/docs/console/access.mdx +0 -29
  312. package/site/content/docs/console/ai.mdx +0 -35
  313. package/site/content/docs/console/architecture.mdx +0 -35
  314. package/site/content/docs/console/channels.mdx +0 -37
  315. package/site/content/docs/console/clock.mdx +0 -51
  316. package/site/content/docs/console/flows.mdx +0 -31
  317. package/site/content/docs/console/gates.mdx +0 -73
  318. package/site/content/docs/console/index.mdx +0 -54
  319. package/site/content/docs/console/manifest-diff.mdx +0 -34
  320. package/site/content/docs/console/meta.json +0 -24
  321. package/site/content/docs/console/overview.mdx +0 -40
  322. package/site/content/docs/console/plugins.mdx +0 -41
  323. package/site/content/docs/console/privacy.mdx +0 -32
  324. package/site/content/docs/console/runs.mdx +0 -50
  325. package/site/content/docs/console/signals.mdx +0 -40
  326. package/site/content/docs/console/store.mdx +0 -32
  327. package/site/content/docs/console/tenancy.mdx +0 -32
  328. package/site/content/docs/console/traces.mdx +0 -38
  329. package/site/content/docs/console/vault.mdx +0 -41
  330. package/site/content/docs/recipes/openbao.mdx +0 -141
  331. package/src/cli/ask-dev-mode.ts +0 -40
  332. package/src/cli/dev-mode.test.ts +0 -45
  333. package/src/cli/dev-mode.ts +0 -78
  334. package/src/cli/openbao-bootstrap.test.ts +0 -150
  335. package/src/cli/openbao-bootstrap.ts +0 -321
  336. package/src/cli/openbao-restart.integration.test.ts +0 -145
  337. package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
  338. package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
  339. package/src/docker/recipes/openbao.ts +0 -47
  340. package/src/drivers/libsql.ts +0 -179
  341. package/src/drivers/sqlite.ts +0 -44
  342. package/src/drivers/vault-openbao.test.ts +0 -97
  343. package/src/drivers/vault-openbao.ts +0 -143
@@ -0,0 +1,184 @@
1
+ /**
2
+ * `vault` driver — okengine's own encrypted-at-rest secret store.
3
+ *
4
+ * The built-in Vault is asynchronous and path-addressed
5
+ * ({@link BuiltinVaultAdapter}), while the boot resolution chain wants a
6
+ * synchronous name→value {@link VaultBag}. This driver bridges the two: at
7
+ * `open` it unseals with `OKE_VAULT_MASTER_KEY`, snapshots every live secret
8
+ * into a bag, and keeps the adapter around for write-through.
9
+ *
10
+ * Boot must never die because the operator has not run `oke vault init` yet,
11
+ * so an uninitialized, sealed, or unreachable backend degrades to the seed
12
+ * bag — the Vault runtime then reports the missing contracts as ordinary
13
+ * gaps, with the same message every other driver produces.
14
+ */
15
+
16
+ import {
17
+ createBuiltinVaultAdapter,
18
+ sqlConnectionAsExec,
19
+ type BuiltinVaultAdapter,
20
+ } from "../elements/vault/builtin-adapter.ts";
21
+ import type { SqlConnection } from "./types.ts";
22
+ import type { VaultBag, VaultDriver, VaultOpenOptions } from "./vault-types.ts";
23
+
24
+ /** Environment variable holding the base64 master key. */
25
+ export const VAULT_MASTER_KEY_ENV = "OKE_VAULT_MASTER_KEY";
26
+
27
+ /** Test-only handle to the adapter a bag is sealing on {@link VaultBag.close}. */
28
+ export const VAULT_BAG_ADAPTER = Symbol.for("oke.vault.bagAdapter");
29
+
30
+ /** Options for {@link openBuiltinVaultAdapter}. */
31
+ export interface OpenBuiltinVaultOptions {
32
+ /** Postgres URL. Falls back to `DATABASE_URL` / `OKE_STORE_SQL_URL`, then PGlite. */
33
+ readonly url?: string;
34
+ /** Pre-opened SQL connection — wins over `url`. */
35
+ readonly connection?: SqlConnection;
36
+ /** Base64 master key to unseal with. Defaults to `OKE_VAULT_MASTER_KEY`. */
37
+ readonly masterKey?: string;
38
+ /** Injected env map for tests. */
39
+ readonly env?: Readonly<Record<string, string | undefined>>;
40
+ /** DEKs re-wrapped per master-rotation batch. */
41
+ readonly kekRewrapBatchSize?: number;
42
+ }
43
+
44
+ /** An adapter plus the connection this module opened for it, if any. */
45
+ export interface OpenedBuiltinVault {
46
+ /** The built-in adapter, unsealed when a valid master key was available. */
47
+ readonly adapter: BuiltinVaultAdapter;
48
+ /** Close the underlying connection when this module opened it. */
49
+ close(): Promise<void>;
50
+ }
51
+
52
+ /**
53
+ * Open the built-in Vault adapter over SQL, unsealing when a master key is
54
+ * available.
55
+ *
56
+ * Used by the CLI (`oke vault …`) and Console, which need the full
57
+ * path-addressed surface rather than a flat bag.
58
+ *
59
+ * @param options - URL / connection / master key / batch size
60
+ */
61
+ export async function openBuiltinVaultAdapter(
62
+ options: OpenBuiltinVaultOptions = {},
63
+ ): Promise<OpenedBuiltinVault> {
64
+ const env = options.env ?? process.env;
65
+ const url = options.url ?? env.DATABASE_URL ?? env.OKE_STORE_SQL_URL;
66
+
67
+ let owned: SqlConnection | undefined;
68
+ let connection = options.connection;
69
+ if (!connection) {
70
+ owned = await connectVaultSql(url);
71
+ connection = owned;
72
+ }
73
+
74
+ const adapter = createBuiltinVaultAdapter({
75
+ db: sqlConnectionAsExec(connection),
76
+ ...(options.kekRewrapBatchSize === undefined
77
+ ? {}
78
+ : { kekRewrapBatchSize: options.kekRewrapBatchSize }),
79
+ });
80
+
81
+ const masterKey = options.masterKey ?? env[VAULT_MASTER_KEY_ENV];
82
+ if (masterKey !== undefined && masterKey.trim().length > 0) {
83
+ await adapter.unseal(masterKey);
84
+ }
85
+
86
+ return {
87
+ adapter,
88
+ async close() {
89
+ await owned?.close();
90
+ },
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Built-in Vault driver. Reads resolve from a snapshot taken at `open`.
96
+ */
97
+ export const builtinVaultDriver: VaultDriver = {
98
+ id: "vault",
99
+ async open(options: VaultOpenOptions = {}): Promise<VaultBag> {
100
+ const map = new Map<string, string>(
101
+ Object.entries(options.secrets ?? {}).filter(
102
+ (e): e is [string, string] => typeof e[1] === "string",
103
+ ),
104
+ );
105
+
106
+ const env = options.env ?? process.env;
107
+ const url = options.url ?? env.DATABASE_URL ?? env.OKE_STORE_SQL_URL;
108
+
109
+ let opened: OpenedBuiltinVault | undefined;
110
+ // With no SQL configured there is nothing to snapshot, and spinning up a
111
+ // throwaway PGlite instance would cost every boot ~1s for an empty bag.
112
+ if (options.connection !== undefined || url !== undefined) {
113
+ try {
114
+ opened = await openBuiltinVaultAdapter({
115
+ env,
116
+ ...(options.connection !== undefined ? { connection: options.connection } : { url }),
117
+ });
118
+ if (opened.adapter.getUnsealer() !== null) {
119
+ for (const entry of await opened.adapter.list()) {
120
+ const secret = await opened.adapter.get(entry.path);
121
+ if (secret) map.set(entry.path, secret.value);
122
+ }
123
+ }
124
+ } catch {
125
+ // Uninitialized / sealed / unreachable: fall back to the seed bag so
126
+ // boot reports missing contracts instead of a driver stack trace.
127
+ await opened?.close().catch(() => undefined);
128
+ opened = undefined;
129
+ }
130
+ }
131
+
132
+ const adapter = opened?.adapter;
133
+ return {
134
+ driverId: "vault",
135
+ ...(adapter === undefined ? {} : { [VAULT_BAG_ADAPTER]: adapter }),
136
+ get(name) {
137
+ return map.get(name);
138
+ },
139
+ names() {
140
+ return [...map.keys()];
141
+ },
142
+ set(name, value) {
143
+ map.set(name, value);
144
+ if (adapter && adapter.getUnsealer() !== null) {
145
+ void adapter.set(name, value).catch(() => undefined);
146
+ }
147
+ },
148
+ delete(name) {
149
+ const had = map.delete(name);
150
+ if (adapter && adapter.getUnsealer() !== null) {
151
+ void adapter.delete(name).catch(() => undefined);
152
+ }
153
+ return had;
154
+ },
155
+ async close() {
156
+ // Auto-seal on bag close (app stop / SIGTERM → bootResult.close → vault.close).
157
+ // Clears the in-memory master key before the SQL connection drops.
158
+ try {
159
+ await adapter?.seal();
160
+ } catch {
161
+ // Already sealed / never unsealed — ignore.
162
+ }
163
+ await opened?.close();
164
+ },
165
+ } as VaultBag;
166
+ },
167
+ };
168
+
169
+ /**
170
+ * Open the SQL connection backing the built-in Vault.
171
+ *
172
+ * A real Postgres URL uses the `postgres` driver; anything else (including
173
+ * no URL at all) uses PGlite so local dev and tests work with no services.
174
+ *
175
+ * @param url - Resolved SQL URL, when configured
176
+ */
177
+ async function connectVaultSql(url: string | undefined): Promise<SqlConnection> {
178
+ if (url !== undefined && /^postgres(ql)?:\/\//.test(url)) {
179
+ const { connectPostgres } = await import("./postgres.ts");
180
+ return connectPostgres({ url });
181
+ }
182
+ const { connectPglite } = await import("./pglite.ts");
183
+ return connectPglite(url === undefined ? {} : { url });
184
+ }
@@ -16,6 +16,10 @@ const FORBIDDEN_DRIVER = ["so", "ps"].join("");
16
16
  const FORBIDDEN_SECRETS_SAAS = ["infi", "sical"].join("");
17
17
  /** Forbidden optional peer, split so this file does not match itself. */
18
18
  const FORBIDDEN_AGE = ["age", "-", "encryption"].join("");
19
+ /** Forbidden external vault driver id, split so this file does not match itself. */
20
+ const FORBIDDEN_EXT_VAULT = ["open", "bao"].join("");
21
+ /** Forbidden vault-provider alias, split so this file does not match itself. */
22
+ const FORBIDDEN_VAULT_ALIAS = ["hashi", "corp"].join("");
19
23
 
20
24
  /** Lockfiles regenerated by `bun install` — not source residue. */
21
25
  const LOCKFILE_PREFIXES = [
@@ -62,4 +66,13 @@ describe("removed vault driver gate", () => {
62
66
  test("tracked tree has zero matches for the removed age peer outside lockfiles", () => {
63
67
  assertZeroGitGrep(FORBIDDEN_AGE, LOCKFILE_PREFIXES);
64
68
  });
69
+
70
+ test("tracked tree has zero matches for the removed external vault driver", () => {
71
+ // Community-plugin notes in changelog / site docs may mention the name.
72
+ assertZeroGitGrep(FORBIDDEN_EXT_VAULT, ["changelog.md:", "site/content/docs/"]);
73
+ });
74
+
75
+ test("tracked tree has zero matches for the removed vault alias outside docs notes", () => {
76
+ assertZeroGitGrep(FORBIDDEN_VAULT_ALIAS, ["changelog.md:", "site/content/docs/"]);
77
+ });
65
78
  });
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Managed vault driver — provider selection, deferred providers, delegation.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { isVaultError } from "../elements/vault/errors.ts";
7
+ import { createManagedVaultBag, managedVaultDriver } from "./vault-managed.ts";
8
+ import type { AwsSecretsManagerClient } from "./vault-aws-secrets-manager.ts";
9
+
10
+ /** In-memory stand-in for the AWS Secrets Manager API. */
11
+ function fakeAwsClient(seed: Record<string, string>): AwsSecretsManagerClient {
12
+ const store = new Map(Object.entries(seed));
13
+ return {
14
+ async list() {
15
+ return [...store.keys()];
16
+ },
17
+ async get(name) {
18
+ return store.get(name);
19
+ },
20
+ async put(name, value) {
21
+ store.set(name, value);
22
+ },
23
+ async remove(name) {
24
+ store.delete(name);
25
+ },
26
+ };
27
+ }
28
+
29
+ describe("managed vault driver", () => {
30
+ test("no provider keeps the platform-injected map", async () => {
31
+ const bag = await managedVaultDriver.open({
32
+ env: { INJECTED: "from-platform" },
33
+ secrets: { SEEDED: "from-seed" },
34
+ });
35
+ expect(bag.driverId).toBe("managed");
36
+ expect(bag.get("INJECTED")).toBe("from-platform");
37
+ expect(bag.get("SEEDED")).toBe("from-seed");
38
+ });
39
+
40
+ test("aws-secrets-manager provider reads through the injected client", async () => {
41
+ const bag = await createManagedVaultBag({
42
+ provider: "aws-secrets-manager",
43
+ client: fakeAwsClient({ STRIPE_KEY: "sk_live" }),
44
+ });
45
+ expect(bag.driverId).toBe("managed");
46
+ expect(bag.get("STRIPE_KEY")).toBe("sk_live");
47
+ });
48
+
49
+ test("provider id is case- and space-insensitive", async () => {
50
+ const bag = await createManagedVaultBag({
51
+ provider: " AWS-Secrets-Manager ",
52
+ client: fakeAwsClient({ A: "1" }),
53
+ });
54
+ expect(bag.get("A")).toBe("1");
55
+ });
56
+
57
+ test("rejects removed external vault providers", async () => {
58
+ // Split tokens so the removal gate never matches this file.
59
+ const removed = [["open", "bao"].join(""), ["hashi", "corp"].join("")];
60
+ for (const provider of removed) {
61
+ let failed: unknown;
62
+ try {
63
+ await createManagedVaultBag({ provider });
64
+ } catch (error) {
65
+ failed = error;
66
+ }
67
+ expect(isVaultError(failed, "UNSUPPORTED")).toBe(true);
68
+ expect((failed as Error).message).toContain(provider);
69
+ }
70
+ });
71
+
72
+ test("deferred providers fail UNSUPPORTED, never silently empty", async () => {
73
+ for (const provider of ["azure-key-vault", "gcp-secret-manager", "doppler", "1password"]) {
74
+ let failed: unknown;
75
+ try {
76
+ await createManagedVaultBag({ provider });
77
+ } catch (error) {
78
+ failed = error;
79
+ }
80
+ expect(isVaultError(failed, "UNSUPPORTED")).toBe(true);
81
+ expect((failed as Error).message).toContain(provider);
82
+ }
83
+ });
84
+
85
+ test("unknown provider fails UNSUPPORTED with the supported list", async () => {
86
+ let failed: unknown;
87
+ try {
88
+ await createManagedVaultBag({ provider: "acme-secrets" });
89
+ } catch (error) {
90
+ failed = error;
91
+ }
92
+ expect(isVaultError(failed, "UNSUPPORTED")).toBe(true);
93
+ expect((failed as Error).message).toContain("aws-secrets-manager");
94
+ });
95
+ });
@@ -1,38 +1,114 @@
1
1
  /**
2
- * `managed` vault driver — platform-injected secrets (Fly / Railway / K8s).
2
+ * `managed` vault driver — one selector over the supported secret backends.
3
3
  *
4
- * Values arrive as an injected map (or env); OKE never invents a vendor client.
4
+ * With no `provider` the driver keeps its original shape: values arrive as an
5
+ * injected map (or env), the way Fly / Railway / K8s hand them over, and OKE
6
+ * never invents a vendor client.
7
+ *
8
+ * With a `provider` the driver delegates, and the bag reports the backend's
9
+ * own protocol id — `managed` is a selector, not a protocol. Backends load
10
+ * dynamically so a project on one provider never pays for the others.
11
+ *
12
+ * Official provider: `aws-secrets-manager`. Community plugins can implement
13
+ * additional adapters; unknown ids fail loud with {@link VaultError} `UNSUPPORTED`.
5
14
  */
6
15
 
16
+ import { VaultError } from "../elements/vault/errors.ts";
17
+ import type { AwsSecretsManagerClient } from "./vault-aws-secrets-manager.ts";
7
18
  import type { VaultBag, VaultDriver, VaultOpenOptions } from "./vault-types.ts";
8
19
 
20
+ /** Official backends `managed` ships today. */
21
+ export type ManagedVaultProviderId = "aws-secrets-manager";
22
+
23
+ /** Providers named in config but not implemented in core — reported, not guessed. */
24
+ const DEFERRED_PROVIDERS: readonly string[] = [
25
+ "azure-key-vault",
26
+ "gcp-secret-manager",
27
+ "doppler",
28
+ "1password",
29
+ ];
30
+
31
+ /** Supported provider ids, for error messages. */
32
+ const SUPPORTED_PROVIDERS = "aws-secrets-manager";
33
+
34
+ /** Options for {@link createManagedVaultBag}. */
35
+ export interface ManagedVaultOptions extends VaultOpenOptions {
36
+ /** Injected AWS Secrets Manager client for tests. */
37
+ readonly client?: AwsSecretsManagerClient;
38
+ }
39
+
40
+ /**
41
+ * Open the bag for the selected managed provider.
42
+ *
43
+ * @param options - Provider id plus that provider's connection options
44
+ * @throws VaultError `UNSUPPORTED` for a deferred or unknown provider
45
+ */
46
+ export async function createManagedVaultBag(options: ManagedVaultOptions = {}): Promise<VaultBag> {
47
+ const provider = options.provider?.trim().toLowerCase();
48
+
49
+ if (provider === undefined || provider === "") return injectedBag(options);
50
+
51
+ if (provider === "aws-secrets-manager") {
52
+ const { openAwsSecretsManagerBag } = await import("./vault-aws-secrets-manager.ts");
53
+ return openAwsSecretsManagerBag({
54
+ ...(options.region === undefined ? {} : { region: options.region }),
55
+ ...(options.mount === undefined ? {} : { prefix: options.mount }),
56
+ ...(options.client === undefined ? {} : { client: options.client }),
57
+ ...(options.secrets === undefined ? {} : { secrets: options.secrets }),
58
+ });
59
+ }
60
+
61
+ if (DEFERRED_PROVIDERS.includes(provider)) {
62
+ throw new VaultError(
63
+ "UNSUPPORTED",
64
+ `vault: managed provider "${provider}" is not implemented yet — official: ${SUPPORTED_PROVIDERS}. ` +
65
+ "Community adapters can implement ManagedAdapter / open via a plugin.",
66
+ );
67
+ }
68
+
69
+ throw new VaultError(
70
+ "UNSUPPORTED",
71
+ `vault: unknown managed provider "${provider}" (expected ${SUPPORTED_PROVIDERS}). ` +
72
+ 'Official backends: aws-secrets-manager or built-in vault (drivers.vault: "vault").',
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Platform-injected bag — env vars plus an explicit seed map.
78
+ *
79
+ * @param options - Seed / env overrides
80
+ */
81
+ function injectedBag(options: VaultOpenOptions): VaultBag {
82
+ const fromSecrets = Object.entries(options.secrets ?? {}).filter(
83
+ (e): e is [string, string] => typeof e[1] === "string",
84
+ );
85
+ const fromEnv = Object.entries(options.env ?? process.env).filter(
86
+ (e): e is [string, string] => typeof e[1] === "string" && e[1].length > 0,
87
+ );
88
+ const map = new Map<string, string>([...fromEnv, ...fromSecrets]);
89
+ return {
90
+ driverId: "managed",
91
+ get(name) {
92
+ return map.get(name);
93
+ },
94
+ names() {
95
+ return [...map.keys()];
96
+ },
97
+ set(name, value) {
98
+ map.set(name, value);
99
+ },
100
+ delete(name) {
101
+ return map.delete(name);
102
+ },
103
+ };
104
+ }
105
+
9
106
  /**
10
107
  * Managed / platform vault driver.
11
108
  */
12
109
  export const managedVaultDriver: VaultDriver = {
13
110
  id: "managed",
14
- async open(options: VaultOpenOptions = {}): Promise<VaultBag> {
15
- const fromSecrets = Object.entries(options.secrets ?? {}).filter(
16
- (e): e is [string, string] => typeof e[1] === "string",
17
- );
18
- const fromEnv = Object.entries(options.env ?? process.env).filter(
19
- (e): e is [string, string] => typeof e[1] === "string" && e[1].length > 0,
20
- );
21
- const map = new Map<string, string>([...fromEnv, ...fromSecrets]);
22
- return {
23
- driverId: "managed",
24
- get(name) {
25
- return map.get(name);
26
- },
27
- names() {
28
- return [...map.keys()];
29
- },
30
- set(name, value) {
31
- map.set(name, value);
32
- },
33
- delete(name) {
34
- return map.delete(name);
35
- },
36
- };
111
+ open(options: ManagedVaultOptions = {}): Promise<VaultBag> {
112
+ return createManagedVaultBag(options);
37
113
  },
38
114
  };
@@ -1,27 +1,47 @@
1
1
  /**
2
2
  * Protocol-named vault driver contracts.
3
3
  *
4
- * Driver ids: `env` · `openbao` · `managed` · `memory`.
4
+ * Driver ids: `env` · `vault` · `managed` · `memory`.
5
5
  * Never vendor names (`neon`, …) — vendor choice lives in `images`.
6
6
  */
7
7
 
8
- /** Protocol ids for vault drivers. */
9
- export type VaultDriverId = "env" | "openbao" | "managed" | "memory";
8
+ /**
9
+ * Protocol ids for vault drivers.
10
+ *
11
+ * `vault` is okengine's own encrypted-at-rest store; `managed` selects a
12
+ * remote provider (official: `aws-secrets-manager`) or a platform-injected bag.
13
+ */
14
+ export type VaultDriverId = "env" | "vault" | "managed" | "memory";
10
15
 
11
16
  /** Options when opening a vault backend. */
12
17
  export interface VaultOpenOptions {
18
+ /**
19
+ * Pre-opened SQL connection for the built-in `vault` driver (tests / CLI).
20
+ * Wins over {@link url} when set.
21
+ */
22
+ readonly connection?: import("./types.ts").SqlConnection;
13
23
  /** Path to a dotenv file (`env` driver). */
14
24
  readonly path?: string;
15
25
  /** Env prefix filter for the `env` driver (default: none — whole `process.env`). */
16
26
  readonly envPrefix?: string;
17
27
  /** Injected env map for tests (defaults to `process.env`). */
18
28
  readonly env?: Readonly<Record<string, string | undefined>>;
19
- /** OpenBao / managed base URL. */
29
+ /**
30
+ * Backend behind the `managed` driver — official: `aws-secrets-manager`.
31
+ * Omit for platform-injected secrets. Community adapters may accept other ids.
32
+ */
33
+ readonly provider?: string;
34
+ /** Base URL for remote managed providers (community adapters). */
20
35
  readonly url?: string;
21
36
  /** Token / API key for remote vaults. */
22
37
  readonly token?: string;
23
- /** Mount / project path for remote vaults. */
38
+ /**
39
+ * Mount / project path for remote vaults. Doubles as the secret-name prefix
40
+ * for `aws-secrets-manager` (`oke/prod/` → `oke/prod/STRIPE_KEY`).
41
+ */
24
42
  readonly mount?: string;
43
+ /** Cloud region for regional managed providers. */
44
+ readonly region?: string;
25
45
  /** Seed map for `memory` / `managed` drivers. */
26
46
  readonly secrets?: Readonly<Record<string, string>>;
27
47
  /** HTTP fetch override for remote drivers (tests). */
@@ -717,7 +717,7 @@ export function createAiRuntime(options: CreateAiRuntimeOptions = {}): AiRuntime
717
717
  }
718
718
  if (index.driverId === "meilisearch") {
719
719
  throw new Error(
720
- `ai: embed into "${decl.into}" needs a vector index (memory/pgvector/libsql) — ` +
720
+ `ai: embed into "${decl.into}" needs a vector index (memory/pgvector) — ` +
721
721
  `"${index.driverId}" is full-text; embeddings don't apply`,
722
722
  );
723
723
  }
@@ -87,7 +87,10 @@ describe("chaos — multi-process leader election", () => {
87
87
  const markerPath = join(dir, "held.json");
88
88
  const fireLogPath = join(dir, "fires.jsonl");
89
89
  const leaseMs = 120;
90
- const tickSlackMs = 250;
90
+ // Shared-runner / loaded laptops routinely miss a 50ms tick; keep this a
91
+ // loose upper bound (not a product SLA) so SIGKILL takeover still proves
92
+ // lease physics without flaking on scheduling noise.
93
+ const tickSlackMs = 500;
91
94
  try {
92
95
  const leader = Bun.spawn({
93
96
  cmd: ["bun", childPath, "hold-lease", storePath, "leader", markerPath, String(leaseMs)],
@@ -7,8 +7,10 @@
7
7
  */
8
8
 
9
9
  import { fxRetry } from "../../kernel/concurrency.ts";
10
+ import { failureCodeOf, rebindUndosFromDo, runCompensationPhase } from "../../kernel/compensate.ts";
10
11
  import type { AnyFlowDef } from "../../kernel/flow.ts";
11
12
  import { createFxContext, type CreateFxOptions, type Fx } from "../../kernel/fx.ts";
13
+ import { isFlowFailure } from "../../kernel/hooks.ts";
12
14
  import {
13
15
  createJournal,
14
16
  isJournalSuspend,
@@ -17,6 +19,7 @@ import {
17
19
  type JournalSession,
18
20
  type JournalStore,
19
21
  } from "../../kernel/journal.ts";
22
+
20
23
  /** Outcome of one durable attempt. */
21
24
  export type DurableResult<O = unknown> =
22
25
  | {
@@ -74,7 +77,21 @@ export async function runDurable<O = unknown>(
74
77
 
75
78
  if (options.runId) {
76
79
  const existing = await options.journalStore.get(options.runId);
77
- if (existing?.wakeAt !== undefined && now() < existing.wakeAt) {
80
+ if (!existing) {
81
+ return {
82
+ status: "failed",
83
+ runId: options.runId,
84
+ error: `journal: run "${options.runId}" not found`,
85
+ };
86
+ }
87
+ if (existing.status === "failed" || existing.status === "completed") {
88
+ return {
89
+ status: "failed",
90
+ runId: existing.id,
91
+ error: existing.error ?? `journal: run is already ${existing.status}`,
92
+ };
93
+ }
94
+ if (existing.wakeAt !== undefined && now() < existing.wakeAt) {
78
95
  const sleepEntry = [...existing.entries].reverse().find((e) => e.kind === "sleep");
79
96
  return {
80
97
  status: "sleeping",
@@ -102,6 +119,23 @@ export async function runDurable<O = unknown>(
102
119
  durable: true,
103
120
  });
104
121
 
122
+ if (session.run.status === "compensating") {
123
+ await rebindUndosFromDo(options.flow, options.input, fx, session);
124
+ const priorError = session.run.error ?? "Error";
125
+ await runCompensationPhase({
126
+ flow: options.flow,
127
+ input: options.input,
128
+ session,
129
+ fx,
130
+ error: new Error(priorError),
131
+ });
132
+ return {
133
+ status: "failed",
134
+ runId: session.runId,
135
+ error: session.run.error ?? priorError,
136
+ };
137
+ }
138
+
105
139
  try {
106
140
  const run = () => {
107
141
  // Same journal session across attempts — rewind so completed steps replay.
@@ -109,6 +143,20 @@ export async function runDurable<O = unknown>(
109
143
  return options.flow.do(options.input as never, fx);
110
144
  };
111
145
  const output = await (options.flow.retry ? fxRetry(run, options.flow.retry) : run());
146
+ if (isFlowFailure(output)) {
147
+ await runCompensationPhase({
148
+ flow: options.flow,
149
+ input: options.input,
150
+ session,
151
+ fx,
152
+ error: output,
153
+ });
154
+ return {
155
+ status: "failed",
156
+ runId: session.runId,
157
+ error: session.run.error ?? failureCodeOf(output),
158
+ };
159
+ }
112
160
  await session.commit("completed", { output });
113
161
  return {
114
162
  status: "completed",
@@ -124,12 +172,17 @@ export async function runDurable<O = unknown>(
124
172
  label: err.label,
125
173
  };
126
174
  }
127
- const message = err instanceof Error ? err.message : String(err);
128
- await session.commit("failed", { error: message });
175
+ await runCompensationPhase({
176
+ flow: options.flow,
177
+ input: options.input,
178
+ session,
179
+ fx,
180
+ error: err,
181
+ });
129
182
  return {
130
183
  status: "failed",
131
184
  runId: session.runId,
132
- error: message,
185
+ error: session.run.error ?? failureCodeOf(err),
133
186
  };
134
187
  }
135
188
  }
@@ -17,7 +17,11 @@ import { tmpdir } from "node:os";
17
17
  import { join } from "node:path";
18
18
 
19
19
  import { flow } from "../kernel/flow.ts";
20
- import { createFileJournalStore, createMemoryJournalStore } from "../kernel/journal.ts";
20
+ import {
21
+ createFileJournalStore,
22
+ createJournal,
23
+ createMemoryJournalStore,
24
+ } from "../kernel/journal.ts";
21
25
  import {
22
26
  clock,
23
27
  createClockRuntime,
@@ -65,7 +69,6 @@ describe("durable journal", () => {
65
69
  const calls: string[] = [];
66
70
  const journalStore = createMemoryJournalStore();
67
71
  const tt = createTimeTravel(0);
68
- let crashAfterFirstStep = true;
69
72
 
70
73
  const charge = flow("payments.charge", {
71
74
  durable: true,
@@ -74,9 +77,6 @@ describe("durable journal", () => {
74
77
  calls.push("create-intent");
75
78
  return { id: "pi_1" };
76
79
  });
77
- if (crashAfterFirstStep) {
78
- throw new Error("KILLED");
79
- }
80
80
  return fx.step("confirm", () => {
81
81
  calls.push("confirm");
82
82
  return intent.id === "pi_1";
@@ -84,17 +84,16 @@ describe("durable journal", () => {
84
84
  },
85
85
  });
86
86
 
87
- const first = await runDurable({
88
- flow: charge,
89
- input: { orderId: "o1" },
90
- journalStore,
91
- now: tt.now,
87
+ // Simulate crash after the first step: leave status `running` (orphans,
88
+ // not terminal `failed` — failed runs refuse resume).
89
+ const journal = createJournal({ store: journalStore, now: tt.now });
90
+ const crashed = await journal.start("payments.charge", { orderId: "o1" });
91
+ await crashed.step("create-intent", () => {
92
+ calls.push("create-intent");
93
+ return { id: "pi_1" };
92
94
  });
93
- expect(first.status).toBe("failed");
94
- expect(calls).toEqual(["create-intent"]);
95
+ const runId = crashed.runId;
95
96
 
96
- crashAfterFirstStep = false;
97
- const runId = first.status === "failed" ? first.runId : "";
98
97
  const second = await runDurable({
99
98
  flow: charge,
100
99
  input: { orderId: "o1" },