okengine 0.10.3 → 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 (318) 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/constants.ts +18 -0
  204. package/src/elements/gate.test.ts +1 -1
  205. package/src/elements/gate.ts +2 -2
  206. package/src/elements/store/domain-ddl.test.ts +5 -12
  207. package/src/elements/store/emit-drizzle.ts +44 -19
  208. package/src/elements/store/index-boot.test.ts +15 -116
  209. package/src/elements/store/load-plugin-tables.ts +1 -1
  210. package/src/elements/store/resource-list-docs.fixture.ts +12 -6
  211. package/src/elements/store/resource-list-docs.test.ts +64 -44
  212. package/src/elements/store/resource.test.ts +17 -10
  213. package/src/elements/store/runtime.ts +9 -12
  214. package/src/elements/store/schema-decl.test.ts +16 -32
  215. package/src/elements/store/seed.test.ts +1 -2
  216. package/src/elements/store/seed.ts +7 -8
  217. package/src/elements/store/sql-condition.test.ts +2 -2
  218. package/src/elements/store/sql-session.test.ts +8 -10
  219. package/src/elements/store/sql-session.ts +10 -2
  220. package/src/elements/store.test.ts +13 -13
  221. package/src/elements/vault/audit.ts +233 -0
  222. package/src/elements/vault/boot-chain.test.ts +78 -0
  223. package/src/elements/vault/boot-chain.ts +43 -39
  224. package/src/elements/vault/builtin-adapter.test.ts +479 -0
  225. package/src/elements/vault/builtin-adapter.ts +1184 -0
  226. package/src/elements/vault/crypto.test.ts +237 -0
  227. package/src/elements/vault/crypto.ts +431 -0
  228. package/src/elements/vault/declare.ts +164 -3
  229. package/src/elements/vault/errors.ts +56 -0
  230. package/src/elements/vault/kms-unseal.test.ts +76 -0
  231. package/src/elements/vault/kms-unseal.ts +181 -0
  232. package/src/elements/vault/path.ts +90 -0
  233. package/src/elements/vault/resilience.test.ts +102 -0
  234. package/src/elements/vault/resilience.ts +107 -0
  235. package/src/elements/vault/runtime.ts +36 -4
  236. package/src/elements/vault/security-checklist.test.ts +130 -0
  237. package/src/elements/vault/storage.ts +448 -0
  238. package/src/elements/vault/types.ts +288 -0
  239. package/src/elements/vault/unseal.ts +150 -0
  240. package/src/elements/vault.test.ts +72 -4
  241. package/src/elements/vault.ts +91 -2
  242. package/src/full.ts +248 -0
  243. package/src/http.ts +41 -0
  244. package/src/i18n/interpolate-simple.ts +37 -0
  245. package/src/i18n/messages.ts +51 -4
  246. package/src/i18n-entry.ts +7 -0
  247. package/src/index.ts +35 -145
  248. package/src/journal-entry.ts +20 -0
  249. package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
  250. package/src/kernel/app.ts +40 -42
  251. package/src/kernel/auto-registry.test.ts +2 -2
  252. package/src/kernel/boot-bind/ai.test.ts +5 -5
  253. package/src/kernel/boot-bind/ai.ts +2 -1
  254. package/src/kernel/boot-bind/channel.test.ts +6 -6
  255. package/src/kernel/boot-bind/channel.ts +3 -10
  256. package/src/kernel/boot-bind/clock.ts +2 -2
  257. package/src/kernel/boot-bind/gate.ts +5 -9
  258. package/src/kernel/boot-bind/honor-config.test.ts +42 -42
  259. package/src/kernel/boot-bind/journal.ts +2 -2
  260. package/src/kernel/boot-bind/store.test.ts +43 -44
  261. package/src/kernel/boot-bind/store.ts +16 -41
  262. package/src/kernel/boot-bind/vault.ts +16 -1
  263. package/src/kernel/boot.test.ts +12 -4
  264. package/src/kernel/boot.ts +31 -19
  265. package/src/kernel/budget-entry.ts +1 -1
  266. package/src/kernel/compensate.test.ts +288 -1
  267. package/src/kernel/compensate.ts +126 -0
  268. package/src/kernel/concurrency.test.ts +10 -9
  269. package/src/kernel/effects-stamping.test.ts +27 -2
  270. package/src/kernel/element-registries.ts +5 -3
  271. package/src/kernel/flow.ts +4 -2
  272. package/src/kernel/fx.test.ts +119 -3
  273. package/src/kernel/fx.ts +215 -40
  274. package/src/kernel/index.ts +14 -0
  275. package/src/kernel/journal.ts +130 -7
  276. package/src/kernel/pipeline.test.ts +20 -6
  277. package/src/kernel/redacted.test.ts +35 -0
  278. package/src/kernel/redacted.ts +24 -2
  279. package/src/kernel/router/create-default.ts +16 -0
  280. package/src/kernel/router/create-edge.ts +18 -0
  281. package/src/kernel/router/create.ts +23 -0
  282. package/src/kernel/router/index.ts +23 -0
  283. package/src/kernel/router/linear.ts +88 -0
  284. package/src/kernel/router/regexp.ts +188 -0
  285. package/src/kernel/router/smart.ts +120 -0
  286. package/src/kernel/router/trie.ts +108 -0
  287. package/src/kernel/router/types.ts +56 -0
  288. package/src/kernel/router.ts +18 -532
  289. package/src/kernel-entry.ts +43 -0
  290. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  291. package/src/manifest/fixtures/skyport.manifest.json +1 -1
  292. package/src/plugins/config-source.test.ts +7 -7
  293. package/src/release/build-lib.ts +58 -0
  294. package/src/release/exports.test.ts +4 -2
  295. package/src/release/exports.ts +30 -4
  296. package/src/release/measure.ts +101 -22
  297. package/src/runs/duckdb.ts +7 -2
  298. package/src/shared/optional-peer.ts +41 -0
  299. package/src/term.test.ts +3 -44
  300. package/src/test/create-test-app.ts +12 -0
  301. package/src/test/provisions.integration.test.ts +1 -1
  302. package/src/test/reset-element-registries.ts +2 -0
  303. package/src/upgrade/codemods.test.ts +7 -20
  304. package/src/upgrade/codemods.ts +93 -60
  305. package/site/content/docs/recipes/openbao.mdx +0 -141
  306. package/src/cli/ask-dev-mode.ts +0 -40
  307. package/src/cli/dev-mode.test.ts +0 -45
  308. package/src/cli/dev-mode.ts +0 -78
  309. package/src/cli/openbao-bootstrap.test.ts +0 -150
  310. package/src/cli/openbao-bootstrap.ts +0 -321
  311. package/src/cli/openbao-restart.integration.test.ts +0 -145
  312. package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
  313. package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
  314. package/src/docker/recipes/openbao.ts +0 -47
  315. package/src/drivers/libsql.ts +0 -179
  316. package/src/drivers/sqlite.ts +0 -44
  317. package/src/drivers/vault-openbao.test.ts +0 -97
  318. package/src/drivers/vault-openbao.ts +0 -143
@@ -4,6 +4,10 @@
4
4
  * `oke upgrade` runs every applicable transform and prints a diff.
5
5
  * Adding a breaking change without a codemod is a process defect; the
6
6
  * registry is the checklist.
7
+ *
8
+ * Environment-model migrations (`local`/`docker` → `dev`, sqlite removal)
9
+ * are documented only — see site docs `reference/migrating-environments`.
10
+ * No soft-compat rewriter for those.
7
11
  */
8
12
 
9
13
  import { readdir } from "node:fs/promises";
@@ -38,49 +42,7 @@ export interface Codemod {
38
42
  }
39
43
 
40
44
  /**
41
- * Rewrite driver-map keys `dev:` `local:` and `stack:` `docker:`.
42
- *
43
- * Vault `dev:` fallbacks are intentionally excluded — callers pass only
44
- * `oke.config.*` paths into this rewriter.
45
- *
46
- * @param source - File contents
47
- */
48
- export function rewriteConfigEnvKeys(source: string): string {
49
- return source
50
- .replace(/(^|[^\w.])dev(\s*:)/gm, "$1local$2")
51
- .replace(/(^|[^\w.])stack(\s*:)/gm, "$1docker$2");
52
- }
53
-
54
- /**
55
- * Rewrite removed vault stack markers to docker equivalents.
56
- *
57
- * @param source - File contents
58
- */
59
- export function rewriteFromStackMarkers(source: string): string {
60
- return source
61
- .replace(/\bfromStack\b/g, "fromDocker")
62
- .replace(/\bFROM_STACK_PREFIX\b/g, "FROM_DOCKER_PREFIX")
63
- .replace(/\bisFromStack\b/g, "isFromDocker")
64
- .replace(/\bfromStackRole\b/g, "fromDockerRole")
65
- .replace(/__oke_from_stack__/g, "__oke_from_docker__");
66
- }
67
-
68
- /**
69
- * Collect `oke.config.*` files under a project root.
70
- *
71
- * @param cwd - Project root
72
- */
73
- async function collectConfigCandidates(cwd: string): Promise<string[]> {
74
- const out: string[] = [];
75
- for (const name of ["oke.config.ts", "oke.config.mts", "oke.config.js"]) {
76
- const path = resolve(cwd, name);
77
- if (await Bun.file(path).exists()) out.push(path);
78
- }
79
- return out;
80
- }
81
-
82
- /**
83
- * Collect TypeScript sources that may reference `fromStack`.
45
+ * Collect TypeScript sources under `src` / `app`.
84
46
  *
85
47
  * @param cwd - Project root
86
48
  */
@@ -116,30 +78,101 @@ async function walkTs(dir: string, out: string[]): Promise<void> {
116
78
  }
117
79
 
118
80
  /**
119
- * Built-in transforms.
81
+ * Rewrite deep `okengine` imports that left the thin root to subpaths.
82
+ *
83
+ * @param source - File contents
120
84
  */
85
+ export function rewriteThinRootImports(source: string): string {
86
+ const movers: readonly { readonly names: readonly string[]; readonly from: string }[] = [
87
+ {
88
+ names: [
89
+ "createRunsRuntime",
90
+ "collectWideEvent",
91
+ "createRunTelemetry",
92
+ "explainOutliers",
93
+ "filesRunsDriver",
94
+ "memoryRunsDriver",
95
+ "postgresRunsDriver",
96
+ "clickhouseRunsDriver",
97
+ "openDuckDB",
98
+ "duckQuery",
99
+ ],
100
+ from: "okengine/runs",
101
+ },
102
+ {
103
+ names: [
104
+ "createJournal",
105
+ "createMemoryJournalStore",
106
+ "createFileJournalStore",
107
+ "hasJournalLease",
108
+ "JOURNAL_DEFAULT_LEASE_MS",
109
+ ],
110
+ from: "okengine/journal",
111
+ },
112
+ {
113
+ names: ["compileAot", "compileDynamic", "compileRoute", "sucrose", "extractManifest"],
114
+ from: "okengine/compiler",
115
+ },
116
+ {
117
+ names: ["formatMessage", "clearMessageFormatCache", "resolveFailureMessage"],
118
+ from: "okengine/i18n",
119
+ },
120
+ {
121
+ names: [
122
+ "bootApplication",
123
+ "mintCapabilities",
124
+ "createFx",
125
+ "createRouter",
126
+ "CONSOLE_PORT",
127
+ "MCP_PORT",
128
+ ],
129
+ from: "okengine/full",
130
+ },
131
+ ];
132
+
133
+ let text = source;
134
+ for (const mover of movers) {
135
+ const re = new RegExp(`import\\s*\\{([^}]*)\\}\\s*from\\s*(["'])okengine\\2`, "g");
136
+ text = text.replace(re, (full, body: string, quote: string) => {
137
+ const parts = body
138
+ .split(",")
139
+ .map((p) => p.trim())
140
+ .filter(Boolean);
141
+ const keep: string[] = [];
142
+ const move: string[] = [];
143
+ for (const part of parts) {
144
+ const base =
145
+ part
146
+ .replace(/^type\s+/, "")
147
+ .split(/\s+as\s+/)[0]
148
+ ?.trim() ?? part;
149
+ if (mover.names.includes(base)) move.push(part);
150
+ else keep.push(part);
151
+ }
152
+ if (move.length === 0) return full;
153
+ const lines: string[] = [];
154
+ if (keep.length > 0) {
155
+ lines.push(`import { ${keep.join(", ")} } from ${quote}okengine${quote}`);
156
+ }
157
+ lines.push(`import { ${move.join(", ")} } from ${quote}${mover.from}${quote}`);
158
+ return lines.join(";\n");
159
+ });
160
+ }
161
+ return text;
162
+ }
163
+
121
164
  export const CODEMODS: readonly Codemod[] = [
122
165
  {
123
- id: "0.2.7-config-env-local-docker",
124
- from: "0.2.6",
125
- to: "0.2.7",
126
- description: "Rename driver-map keys dev→local, stack→docker; fromStack→fromDocker",
166
+ id: "0.10.3-thin-root-subpaths",
167
+ from: "0.10.2",
168
+ to: "0.10.3",
169
+ description:
170
+ "Move deep okengine imports (runs/journal/compiler/i18n/boot helpers) to okengine/* subpaths after thin root",
127
171
  async apply(cwd) {
128
172
  const changes: CodemodChange[] = [];
129
- for (const path of await collectConfigCandidates(cwd)) {
130
- const before = await Bun.file(path).text();
131
- const after = rewriteConfigEnvKeys(before);
132
- if (after !== before) {
133
- changes.push({
134
- path: relative(cwd, path) || path,
135
- before,
136
- after,
137
- });
138
- }
139
- }
140
173
  for (const path of await collectVaultCandidates(cwd)) {
141
174
  const before = await Bun.file(path).text();
142
- const after = rewriteFromStackMarkers(before);
175
+ const after = rewriteThinRootImports(before);
143
176
  if (after !== before) {
144
177
  changes.push({
145
178
  path: relative(cwd, path) || path,
@@ -1,141 +0,0 @@
1
- ---
2
- title: "OpenBao"
3
- description: "Durable single-node vault — Raft under /openbao/file, OKE_VAULT_URL / OKE_VAULT_TOKEN, host unseal key SPOF."
4
- icon: "KeyRound"
5
- source: "docs/spec/unified-theory.md"
6
- ---
7
-
8
- OpenBao is the docker/prod Vault driver target — a real secrets service with Raft
9
- storage, **not** an in-memory `-dev` server. Templates pin
10
- `vault: "openbao/openbao:2.6.1"` and `drivers.vault.docker/prod: "openbao"`.
11
-
12
- <Callout title="The one rule">
13
- Never run OpenBao `-dev` in docker/prod. Data belongs on the Raft volume; init + unseal stay on
14
- the host under `.oke/openbao/`.
15
- </Callout>
16
-
17
- ## Quick start
18
-
19
- <Steps>
20
-
21
- <Step>
22
- ### Pin driver + image
23
-
24
- ```typescript title="oke.config.ts"
25
- drivers: {
26
- vault: { local: "env", docker: "openbao", test: "memory", prod: "openbao" },
27
- },
28
- images: {
29
- vault: "openbao/openbao:2.6.1",
30
- },
31
- ```
32
-
33
- </Step>
34
-
35
- <Step>
36
- ### First boot is automatic
37
-
38
- `oke dev --docker` initializes OpenBao (Shamir 1-of-1), unseals it, writes a
39
- least-privilege policy for **declared** secrets only, and mints an app token. You do not
40
- run raw `bao` commands.
41
-
42
- </Step>
43
-
44
- <Step>
45
- ### App sees only the least-privilege token
46
-
47
- ```bash title="docker/.env.docker"
48
- OKE_VAULT_URL=http://127.0.0.1:8200
49
- OKE_VAULT_TOKEN=… # app token — not the root token
50
- OKE_VAULT_MOUNT=secret # KV v2 mount (default)
51
- ```
52
-
53
- </Step>
54
-
55
- </Steps>
56
-
57
- ## Required env
58
-
59
- | Variable | Who holds it | Meaning |
60
- | ------------------ | ------------------- | ---------------------------------------- |
61
- | `OKE_VAULT_URL` | App / stack | OpenBao base URL |
62
- | `OKE_VAULT_TOKEN` | App (`.env.docker`) | Least-privilege app token |
63
- | `OKE_VAULT_MOUNT` | Optional | KV v2 mount path (default `"secret"`) |
64
- | `BAO_LOCAL_CONFIG` | Container (recipe) | Listener + Raft JSON — set by the recipe |
65
-
66
- Host-only material (mode `0600`, gitignored) — **not** in compose YAML:
67
-
68
- | File | Holds |
69
- | ------------------------- | ---------------------------------------- |
70
- | `.oke/openbao/unseal.key` | Single unseal share |
71
- | `.oke/openbao/root.token` | Root token (CLI policy sync / mint only) |
72
- | `.oke/openbao/app.token` | Same least-privilege token the app uses |
73
-
74
- ## Data and backup
75
-
76
- | Volume | Path | What it stores |
77
- | ------------ | --------------- | ---------------------------- |
78
- | `vault-data` | `/openbao/file` | Raft storage (node id `oke`) |
79
-
80
- Recipe note: Raft path is `/openbao/file` (the stock entrypoint chowns that directory).
81
- Healthcheck hits `/v1/sys/init` — **200 even while sealed**; bootstrap owns unseal.
82
-
83
- **Backup means two things:**
84
-
85
- 1. The Raft volume (`/openbao/file`) — encrypted secret store
86
- 2. `.oke/openbao/unseal.key` on the host — without it the volume is unrecoverable
87
-
88
- ## Production note
89
-
90
- <Callout title="Single point of failure — read this once" type="warn">
91
- Back up `.oke/openbao/unseal.key` to a separate safe location. Losing it means losing every secret
92
- permanently, with no recovery. Restated from [Vault ·
93
- OpenBao](/docs/elements/vault#openbao-in-docker-and-prod).
94
- </Callout>
95
-
96
- Self-hosted single-node, Shamir 1-of-1, no cloud KMS — by design. If OpenBao is
97
- initialized but the key is gone, boot fails with an explicit permanent-loss error; it
98
- never silently starts empty.
99
-
100
- ## What the recipe configures
101
-
102
- | Field | Value |
103
- | -------------- | -------------------------------------------------------------------------------------- |
104
- | Container port | `8200` |
105
- | Command | `server` (with `BAO_LOCAL_CONFIG`) |
106
- | TLS | Disabled on the listener for compose (`tls_disable: true`) — rely on network isolation |
107
- | Healthcheck | `wget` `/v1/sys/init`, every 5s, 12 retries |
108
-
109
- ## Troubleshooting
110
-
111
- <Accordions>
112
- <Accordion title="openbao vault: GET … failed (… ) — sealed or unauthorized?">
113
-
114
- `OpenBaoUnavailableError` — vault sealed, wrong token, or unreachable. Re-run
115
- `oke dev --docker` so bootstrap unseals with the host key. If the vault is initialized
116
- but `.oke/openbao/unseal.key` is missing, restore the key from backup.
117
-
118
- </Accordion>
119
- <Accordion title="Permanent-loss / initialized but key missing">
120
-
121
- Starting empty would look like “no secrets.” The CLI refuses that path. Restore
122
- `unseal.key` or treat the Raft volume as destroyed and re-init only if you accept wiping
123
- secrets.
124
-
125
- </Accordion>
126
- </Accordions>
127
-
128
- ## Learn more
129
-
130
- - [Vault · OpenBao in docker and prod](/docs/elements/vault#openbao-in-docker-and-prod) —
131
- full bootstrap, key table, SPOF callout
132
- - [Errors](/docs/reference/errors) — `OpenBaoUnavailableError`, `OpenBaoBootstrapError`
133
- - [CLI · oke vault](/docs/reference/cli) — `set` / `list` / `import`
134
-
135
- ## Next
136
-
137
- <Cards>
138
- <Card title="Vault" description="Resolution chain and rotation." href="/docs/elements/vault" />
139
- <Card title="Mailpit" description="Local email catcher." href="/docs/recipes/mailpit" />
140
- <Card title="RustFS" description="S3-compatible files." href="/docs/recipes/rustfs" />
141
- </Cards>
@@ -1,40 +0,0 @@
1
- /**
2
- * One-time interactive `oke dev` mode choice via `@clack/prompts`.
3
- */
4
-
5
- import { isCancel, select } from "@clack/prompts";
6
- import type { DevMode } from "./dev-mode.ts";
7
-
8
- /**
9
- * Injectable ask step — production uses {@link askDevMode}.
10
- *
11
- * @returns Chosen mode, or `null` if the user cancelled
12
- */
13
- export type AskDevModeFn = () => Promise<DevMode | null>;
14
-
15
- /**
16
- * Prompt once: local vs docker.
17
- *
18
- * @returns Chosen mode, or `null` on cancel
19
- */
20
- export async function askDevMode(): Promise<DevMode | null> {
21
- const value = await select({
22
- message: "Run against",
23
- options: [
24
- {
25
- value: "local",
26
- label: "local",
27
- hint: "sqlite file, instant, no Docker needed",
28
- },
29
- {
30
- value: "docker",
31
- label: "docker",
32
- hint: "real Postgres/Redis via compose, closer to production",
33
- },
34
- ],
35
- initialValue: "local",
36
- });
37
- if (isCancel(value)) return null;
38
- if (value === "local" || value === "docker") return value;
39
- return null;
40
- }
@@ -1,45 +0,0 @@
1
- /**
2
- * `.oke/mode` persistence and prompt gating.
3
- */
4
-
5
- import { afterEach, describe, expect, test } from "bun:test";
6
- import { mkdtemp, rm } from "node:fs/promises";
7
- import { tmpdir } from "node:os";
8
- import { join } from "node:path";
9
- import { parseDevMode, readDevMode, shouldAskDevMode, writeDevMode } from "./dev-mode.ts";
10
-
11
- const dirs: string[] = [];
12
-
13
- afterEach(async () => {
14
- await Promise.all(dirs.splice(0).map((d) => rm(d, { recursive: true, force: true })));
15
- });
16
-
17
- async function tempDir(): Promise<string> {
18
- const dir = await mkdtemp(join(tmpdir(), "oke-mode-"));
19
- dirs.push(dir);
20
- return dir;
21
- }
22
-
23
- describe("dev-mode", () => {
24
- test("parseDevMode accepts local|docker only", () => {
25
- expect(parseDevMode("local")).toBe("local");
26
- expect(parseDevMode(" docker \n")).toBe("docker");
27
- expect(parseDevMode("stack")).toBeNull();
28
- expect(parseDevMode(undefined)).toBeNull();
29
- });
30
-
31
- test("read/write round-trip", async () => {
32
- const dir = await tempDir();
33
- expect(await readDevMode(dir)).toBeNull();
34
- await writeDevMode(dir, "docker");
35
- expect(await readDevMode(dir)).toBe("docker");
36
- });
37
-
38
- test("shouldAskDevMode: TTY + unset → ask; non-TTY → never", () => {
39
- expect(shouldAskDevMode({ saved: null, explicit: false, stdinIsTTY: true })).toBe(true);
40
- expect(shouldAskDevMode({ saved: null, explicit: false, stdinIsTTY: false })).toBe(false);
41
- expect(shouldAskDevMode({ saved: null, explicit: false, stdinIsTTY: undefined })).toBe(false);
42
- expect(shouldAskDevMode({ saved: "local", explicit: false, stdinIsTTY: true })).toBe(false);
43
- expect(shouldAskDevMode({ saved: null, explicit: true, stdinIsTTY: true })).toBe(false);
44
- });
45
- });
@@ -1,78 +0,0 @@
1
- /**
2
- * Persisted `oke dev` run mode — `local` vs `docker`.
3
- */
4
-
5
- import { mkdir } from "node:fs/promises";
6
- import { join, resolve } from "node:path";
7
-
8
- /** Saved / selectable `oke dev` infrastructure mode. */
9
- export type DevMode = "local" | "docker";
10
-
11
- /** Relative path under the project root. */
12
- export const DEV_MODE_RELATIVE_PATH = ".oke/mode";
13
-
14
- /**
15
- * Absolute path to the mode preference file.
16
- *
17
- * @param cwd - Project root
18
- */
19
- export function devModePath(cwd: string): string {
20
- return resolve(cwd, DEV_MODE_RELATIVE_PATH);
21
- }
22
-
23
- /**
24
- * Parse a raw mode string.
25
- *
26
- * @param raw - File contents or CLI arg
27
- */
28
- export function parseDevMode(raw: string | undefined | null): DevMode | null {
29
- if (raw === undefined || raw === null) return null;
30
- const value = raw.trim().toLowerCase();
31
- if (value === "local" || value === "docker") return value;
32
- return null;
33
- }
34
-
35
- /**
36
- * Read the saved mode from `.oke/mode`, or `null` when unset / invalid.
37
- *
38
- * @param cwd - Project root
39
- */
40
- export async function readDevMode(cwd: string): Promise<DevMode | null> {
41
- const path = devModePath(cwd);
42
- const file = Bun.file(path);
43
- if (!(await file.exists())) return null;
44
- try {
45
- return parseDevMode(await file.text());
46
- } catch {
47
- return null;
48
- }
49
- }
50
-
51
- /**
52
- * Persist the preferred mode for future `oke dev` invocations.
53
- *
54
- * @param cwd - Project root
55
- * @param mode - Mode to save
56
- */
57
- export async function writeDevMode(cwd: string, mode: DevMode): Promise<void> {
58
- const path = devModePath(cwd);
59
- await mkdir(join(cwd, ".oke"), { recursive: true });
60
- await Bun.write(path, `${mode}\n`);
61
- }
62
-
63
- /**
64
- * Whether `oke dev` should open the one-time mode prompt.
65
- *
66
- * Mirrors create-oke: never prompt when stdin is not a TTY.
67
- *
68
- * @param options - Preference + TTY + explicit flag
69
- */
70
- export function shouldAskDevMode(options: {
71
- readonly saved: DevMode | null;
72
- readonly explicit: boolean;
73
- readonly stdinIsTTY: boolean | undefined;
74
- }): boolean {
75
- if (options.explicit) return false;
76
- if (options.saved !== null) return false;
77
- return Boolean(options.stdinIsTTY);
78
- }
@@ -1,150 +0,0 @@
1
- /**
2
- * OpenBao bootstrap — material durability gates (`.oke/` ignore + real
3
- * `0600`, init-then-write-fail must not report success, initialized-without-
4
- * key fails loud).
5
- */
6
-
7
- import { afterEach, describe, expect, test } from "bun:test";
8
- import { mkdtemp, rm } from "node:fs/promises";
9
- import { statSync } from "node:fs";
10
- import { tmpdir } from "node:os";
11
- import { join } from "node:path";
12
- import {
13
- assertMode,
14
- ensureOpenBao,
15
- OpenBaoBootstrapError,
16
- OPENBAO_STATE_DIR_REL,
17
- } from "./openbao-bootstrap.ts";
18
-
19
- let dirs: string[] = [];
20
-
21
- async function tempDir(): Promise<string> {
22
- const dir = await mkdtemp(join(tmpdir(), "oke-openbao-boot-"));
23
- dirs.push(dir);
24
- return dir;
25
- }
26
-
27
- afterEach(async () => {
28
- for (const dir of dirs) await rm(dir, { recursive: true, force: true });
29
- dirs = [];
30
- });
31
-
32
- /** Scripted fake OpenBao (seal-status → init → unseal → policy → token). */
33
- function fakeOpenBao(calls: { url: string; method: string; body?: string }[]) {
34
- const routes: Record<string, () => Response> = {
35
- "GET /v1/sys/seal-status": () =>
36
- Response.json({ sealed: false, initialized: false, t: 1, n: 1, progress: 0 }),
37
- "GET /v1/sys/health": () =>
38
- Response.json({ initialized: true, sealed: false, standby: false }, { status: 200 }),
39
- "POST /v1/sys/init": () =>
40
- Response.json({ keys: ["unseal-key-1"], keys_base64: ["dW5zZWFs"], root_token: "root-tok" }),
41
- "POST /v1/sys/unseal": () => Response.json({ sealed: false, t: 1, n: 1, progress: 0 }),
42
- "POST /v1/sys/mounts/secret": () => Response.json({}),
43
- "POST /v1/sys/policy/oke-app": () => new Response(null, { status: 204 }),
44
- "POST /v1/auth/token/create": () =>
45
- Response.json({ auth: { client_token: "app-tok", policies: ["oke-app"] } }),
46
- };
47
- const fetchFn = (async (input: string | URL | Request, init?: RequestInit) => {
48
- const url = String(input);
49
- const method = init?.method ?? "GET";
50
- calls.push({ url, method, body: typeof init?.body === "string" ? init.body : undefined });
51
- const path = new URL(url).pathname;
52
- const handler = routes[`${method} ${path}`];
53
- if (!handler) return new Response("not found", { status: 404 });
54
- return handler();
55
- }) as typeof globalThis.fetch;
56
- return fetchFn;
57
- }
58
-
59
- describe("openbao bootstrap material gates", () => {
60
- test("`.oke/` is gitignored (check-ignore, not inference)", () => {
61
- const root = join(import.meta.dir, "../..");
62
- for (const p of [
63
- `${OPENBAO_STATE_DIR_REL}/unseal.key`,
64
- `${OPENBAO_STATE_DIR_REL}/root.token`,
65
- ]) {
66
- const proc = Bun.spawnSync(["git", "check-ignore", "-v", p], {
67
- cwd: root,
68
- stdout: "pipe",
69
- stderr: "pipe",
70
- });
71
- expect(proc.exitCode).toBe(0);
72
- expect(proc.stdout.toString()).toContain(".gitignore");
73
- }
74
- });
75
-
76
- test("init writes 0600 files + 0700 dir (real mode) before success", async () => {
77
- const cwd = await tempDir();
78
- const calls: { url: string; method: string; body?: string }[] = [];
79
- const result = await ensureOpenBao({
80
- cwd,
81
- url: "http://127.0.0.1:8200",
82
- names: ["STRIPE_KEY", "DATABASE_URL"],
83
- fetch: fakeOpenBao(calls),
84
- });
85
- expect(result.initializedNow).toBe(true);
86
- expect(result.appToken).toBe("app-tok");
87
-
88
- const stateDir = join(cwd, OPENBAO_STATE_DIR_REL);
89
- expect(statSync(stateDir).mode & 0o777).toBe(0o700);
90
- expect(statSync(join(stateDir, "unseal.key")).mode & 0o777).toBe(0o600);
91
- expect(statSync(join(stateDir, "root.token")).mode & 0o777).toBe(0o600);
92
- expect(statSync(join(stateDir, "app.token")).mode & 0o777).toBe(0o600);
93
- assertMode(join(stateDir, "unseal.key"), 0o600);
94
-
95
- // Policy is scoped to the two declared paths only.
96
- const policyCall = calls.find((c) => c.url.endsWith("/v1/sys/policy/oke-app"));
97
- expect(policyCall?.body).toContain("secret/data/STRIPE_KEY");
98
- expect(policyCall?.body).toContain("secret/data/DATABASE_URL");
99
- expect(policyCall?.body).not.toContain("secret/data/*");
100
- });
101
-
102
- test("post-init material write failure does NOT report success", async () => {
103
- const cwd = await tempDir();
104
- // Block the state dir with a *file* so mkdir/write fails after init.
105
- const blocker = join(cwd, ".oke");
106
- await Bun.write(blocker, "not-a-dir");
107
-
108
- const calls: { url: string; method: string; body?: string }[] = [];
109
- let failed: unknown;
110
- try {
111
- await ensureOpenBao({
112
- cwd,
113
- url: "http://127.0.0.1:8200",
114
- names: ["STRIPE_KEY"],
115
- fetch: fakeOpenBao(calls),
116
- });
117
- } catch (err) {
118
- failed = err;
119
- }
120
- expect(failed).toBeDefined();
121
- // init happened (vault now initialized server-side) but we never returned
122
- // success without durable material.
123
- expect(calls.some((c) => c.url.endsWith("/v1/sys/init"))).toBe(true);
124
- expect(calls.some((c) => c.url.endsWith("/v1/auth/token/create"))).toBe(false);
125
- });
126
-
127
- test("initialized but host unseal.key missing → loud permanent-loss error", async () => {
128
- const cwd = await tempDir();
129
- const calls: { url: string; method: string; body?: string }[] = [];
130
- const fetchFn = fakeOpenBao(calls);
131
- // Seal-status says initialized (data exists) but the host has no material.
132
- const initialized = (async (input: string | URL | Request, init?: RequestInit) => {
133
- const url = String(input);
134
- if (url.endsWith("/v1/sys/seal-status")) {
135
- return Response.json({ sealed: true, initialized: true });
136
- }
137
- return fetchFn(input as string, init);
138
- }) as typeof globalThis.fetch;
139
-
140
- let failed: unknown;
141
- try {
142
- await ensureOpenBao({ cwd, url: "http://127.0.0.1:8200", fetch: initialized });
143
- } catch (err) {
144
- failed = err;
145
- }
146
- expect(failed).toBeInstanceOf(OpenBaoBootstrapError);
147
- expect(String(failed)).toContain("unrecoverable");
148
- expect(String(failed)).toContain("compose.vault.yml down -v");
149
- });
150
- });