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,402 @@
1
+ /**
2
+ * `oke vault` builtin loop — init · status · seal/unseal · rotate · audit ·
3
+ * backup/restore.
4
+ *
5
+ * Every case runs against a real PGlite instance shared across the
6
+ * subcommands of one scenario, which is what a live vault looks like: each
7
+ * `oke vault …` is a fresh process that must re-supply the master key.
8
+ */
9
+
10
+ import { describe, expect, test } from "bun:test";
11
+ import { mkdtemp } from "node:fs/promises";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ import { connectPglite } from "../drivers/pglite.ts";
15
+ import type { SqlConnection } from "../drivers/types.ts";
16
+ import {
17
+ createBuiltinVaultAdapter,
18
+ sqlConnectionAsExec,
19
+ type BuiltinVaultAdapter,
20
+ } from "../elements/vault/builtin-adapter.ts";
21
+ import { vaultCli } from "./vault-cmd.ts";
22
+
23
+ /** A CLI bound to one in-memory vault, plus its captured stdout. */
24
+ interface Harness {
25
+ readonly sql: SqlConnection;
26
+ /** Run `oke vault <args>`; returns the exit code. */
27
+ run(args: readonly string[]): Promise<number>;
28
+ /** Everything written to stdout since the last {@link reset}. */
29
+ output(): string;
30
+ reset(): void;
31
+ close(): Promise<void>;
32
+ }
33
+
34
+ /**
35
+ * Open a CLI harness over a fresh PGlite instance.
36
+ *
37
+ * @param cwd - Project root for relative file arguments
38
+ */
39
+ async function harness(cwd?: string): Promise<Harness> {
40
+ const sql = await connectPglite({ url: "memory://vault-cli-test" });
41
+ let captured = "";
42
+ return {
43
+ sql,
44
+ run: (args) =>
45
+ vaultCli(args, {
46
+ sql,
47
+ // Never inherit a real OKE_VAULT_MASTER_KEY from the developer's shell.
48
+ env: {},
49
+ ...(cwd === undefined ? {} : { cwd }),
50
+ write: (text) => {
51
+ captured += text;
52
+ },
53
+ }),
54
+ output: () => captured,
55
+ reset: () => {
56
+ captured = "";
57
+ },
58
+ close: () => sql.close(),
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Extract the base64 master key printed once by `init` / `rotate-master`.
64
+ *
65
+ * @param output - Captured stdout
66
+ */
67
+ function masterKeyFrom(output: string): string {
68
+ const match = /shown once[^\n]*\n {2}(\S+)\n/.exec(output);
69
+ if (!match?.[1]) throw new Error(`no master key in output:\n${output}`);
70
+ return match[1];
71
+ }
72
+
73
+ /**
74
+ * Attach a direct adapter to the harness connection (test-side seeding).
75
+ *
76
+ * @param sql - Shared connection
77
+ * @param masterKey - Base64 master key
78
+ */
79
+ async function attach(sql: SqlConnection, masterKey: string): Promise<BuiltinVaultAdapter> {
80
+ const adapter = createBuiltinVaultAdapter({ db: sqlConnectionAsExec(sql) });
81
+ await adapter.unseal(masterKey);
82
+ return adapter;
83
+ }
84
+
85
+ describe("oke vault — builtin lifecycle", () => {
86
+ test("init prints the master key once and status reflects seal state", async () => {
87
+ const h = await harness();
88
+ try {
89
+ // A never-initialized vault reports rather than crashes.
90
+ expect(await h.run(["status"])).toBe(0);
91
+ expect(h.output()).toMatch(/initialized\s+no/);
92
+ expect(h.output()).toContain("oke vault init");
93
+
94
+ h.reset();
95
+ expect(await h.run(["init"])).toBe(0);
96
+ const masterKey = masterKeyFrom(h.output());
97
+ expect(h.output()).toContain("initialized (kek v1)");
98
+
99
+ h.reset();
100
+ expect(await h.run(["status", "--json"])).toBe(0);
101
+ const sealed = JSON.parse(h.output()) as { initialized: boolean; sealed: boolean };
102
+ expect(sealed.initialized).toBe(true);
103
+ // No key in this invocation — the vault cannot be read.
104
+ expect(sealed.sealed).toBe(true);
105
+
106
+ h.reset();
107
+ expect(await h.run(["status", "--json", "--key", masterKey])).toBe(0);
108
+ const unsealed = JSON.parse(h.output()) as { sealed: boolean; kekVersion: number };
109
+ expect(unsealed.sealed).toBe(false);
110
+ expect(unsealed.kekVersion).toBe(1);
111
+
112
+ h.reset();
113
+ expect(await h.run(["status"])).toBe(0);
114
+ expect(h.output()).toMatch(/initialized\s+yes/);
115
+
116
+ // A second init must not silently re-key a live vault.
117
+ expect(await h.run(["init"])).toBe(1);
118
+ } finally {
119
+ await h.close();
120
+ }
121
+ });
122
+
123
+ test("unseal requires a key and seal records the transition", async () => {
124
+ const h = await harness();
125
+ try {
126
+ await h.run(["init"]);
127
+ const masterKey = masterKeyFrom(h.output());
128
+
129
+ expect(await h.run(["unseal"])).toBe(1);
130
+ expect(await h.run(["unseal", "--key", "not-a-real-key"])).toBe(1);
131
+
132
+ h.reset();
133
+ expect(await h.run(["unseal", `--key=${masterKey}`])).toBe(0);
134
+ expect(h.output()).toContain("unsealed (kek v1");
135
+
136
+ h.reset();
137
+ expect(await h.run(["seal"])).toBe(0);
138
+ expect(h.output()).toContain("sealed");
139
+
140
+ h.reset();
141
+ expect(await h.run(["status", "--json", "--key", masterKey])).toBe(0);
142
+ const status = JSON.parse(h.output()) as { sealCount: number };
143
+ expect(status.sealCount).toBe(1);
144
+ } finally {
145
+ await h.close();
146
+ }
147
+ });
148
+ });
149
+
150
+ describe("oke vault rotate", () => {
151
+ test("rotate re-encrypts the current value under a new version", async () => {
152
+ const h = await harness();
153
+ try {
154
+ await h.run(["init"]);
155
+ const masterKey = masterKeyFrom(h.output());
156
+ const adapter = await attach(h.sql, masterKey);
157
+ await adapter.set("app/token", "s3cr3t");
158
+
159
+ h.reset();
160
+ expect(await h.run(["rotate", "app/token", "--key", masterKey])).toBe(0);
161
+ expect(h.output()).toContain("rotated app/token → v2");
162
+ // The value is never echoed back to the operator.
163
+ expect(h.output()).not.toContain("s3cr3t");
164
+
165
+ const rotated = await adapter.get("app/token");
166
+ expect(rotated?.version).toBe(2);
167
+ expect(rotated?.value).toBe("s3cr3t");
168
+
169
+ h.reset();
170
+ expect(await h.run(["rotate", "app/token", "next-value", "--key", masterKey])).toBe(0);
171
+ expect((await adapter.get("app/token"))?.value).toBe("next-value");
172
+
173
+ expect(await h.run(["rotate", "app/missing", "--key", masterKey])).toBe(1);
174
+ expect(await h.run(["rotate", "--key", masterKey])).toBe(1);
175
+ expect(await h.run(["rotate", "app/token"])).toBe(1);
176
+ } finally {
177
+ await h.close();
178
+ }
179
+ });
180
+
181
+ test("rotate-master issues a new key and retires the old one", async () => {
182
+ const h = await harness();
183
+ try {
184
+ await h.run(["init"]);
185
+ const first = masterKeyFrom(h.output());
186
+ const adapter = await attach(h.sql, first);
187
+ await adapter.set("app/token", "s3cr3t");
188
+
189
+ h.reset();
190
+ expect(await h.run(["rotate-master", "--key", first])).toBe(0);
191
+ expect(h.output()).toContain("master rotated → kek v2");
192
+ const second = masterKeyFrom(h.output());
193
+ expect(second).not.toBe(first);
194
+
195
+ h.reset();
196
+ expect(await h.run(["status", "--json", "--key", second])).toBe(0);
197
+ const status = JSON.parse(h.output()) as { sealed: boolean; kekVersion: number };
198
+ expect(status.sealed).toBe(false);
199
+ expect(status.kekVersion).toBe(2);
200
+
201
+ // The retired key no longer opens the vault.
202
+ expect(await h.run(["unseal", "--key", first])).toBe(1);
203
+
204
+ const reopened = await attach(h.sql, second);
205
+ expect((await reopened.get("app/token"))?.value).toBe("s3cr3t");
206
+ } finally {
207
+ await h.close();
208
+ }
209
+ });
210
+ });
211
+
212
+ describe("oke vault audit", () => {
213
+ test("lists rows, verifies the chain, and purges by date", async () => {
214
+ const h = await harness();
215
+ try {
216
+ await h.run(["init"]);
217
+ const masterKey = masterKeyFrom(h.output());
218
+ const adapter = await attach(h.sql, masterKey);
219
+ await adapter.set("app/token", "s3cr3t");
220
+
221
+ h.reset();
222
+ expect(await h.run(["audit"])).toBe(0);
223
+ expect(h.output()).toContain("initialize");
224
+ expect(h.output()).toContain("app/token");
225
+ expect(h.output()).not.toContain("s3cr3t");
226
+
227
+ h.reset();
228
+ expect(await h.run(["audit", "--path", "app/token", "--json"])).toBe(0);
229
+ const rows = JSON.parse(h.output()) as { path: string; action: string }[];
230
+ expect(rows.length).toBeGreaterThan(0);
231
+ expect(rows.every((r) => r.path === "app/token")).toBe(true);
232
+
233
+ h.reset();
234
+ expect(await h.run(["audit", "verify"])).toBe(0);
235
+ expect(h.output()).toContain("audit chain intact");
236
+
237
+ h.reset();
238
+ const future = new Date(Date.now() + 3_600_000).toISOString();
239
+ expect(await h.run(["audit", "purge", "--before", future])).toBe(0);
240
+ expect(h.output()).toContain("purged");
241
+
242
+ h.reset();
243
+ expect(await h.run(["audit"])).toBe(0);
244
+ // `purge` itself is auditable, so the chain restarts rather than empties.
245
+ expect(h.output()).toContain("purge");
246
+
247
+ expect(await h.run(["audit", "purge"])).toBe(1);
248
+ expect(await h.run(["audit", "purge", "--before", "not-a-date"])).toBe(1);
249
+ expect(await h.run(["audit", "nonsense"])).toBe(1);
250
+ } finally {
251
+ await h.close();
252
+ }
253
+ });
254
+ });
255
+
256
+ describe("oke vault backup / restore", () => {
257
+ test("round-trips every live secret through an encrypted bundle", async () => {
258
+ const dir = await mkdtemp(join(tmpdir(), "oke-vault-cli-"));
259
+ const h = await harness(dir);
260
+ try {
261
+ await h.run(["init"]);
262
+ const masterKey = masterKeyFrom(h.output());
263
+ const adapter = await attach(h.sql, masterKey);
264
+ await adapter.set("app/token", "s3cr3t");
265
+ await adapter.set("app/other", "another");
266
+
267
+ h.reset();
268
+ expect(await h.run(["backup", "vault.bundle", "--key", masterKey])).toBe(0);
269
+ expect(h.output()).toContain("encrypted byte(s) to vault.bundle");
270
+
271
+ const bundle = await Bun.file(join(dir, "vault.bundle")).text();
272
+ expect(bundle.startsWith("oke-vault-backup-v1")).toBe(true);
273
+ expect(bundle).not.toContain("s3cr3t");
274
+
275
+ h.reset();
276
+ expect(await h.run(["restore", "vault.bundle", "--key", masterKey])).toBe(0);
277
+ expect(h.output()).toContain("2 live secret(s)");
278
+
279
+ // Restore replays through the write path: same value, new version.
280
+ const restored = await adapter.get("app/token");
281
+ expect(restored?.value).toBe("s3cr3t");
282
+ expect(restored?.version).toBe(2);
283
+
284
+ expect(await h.run(["restore", "missing.bundle", "--key", masterKey])).toBe(1);
285
+ expect(await h.run(["backup"])).toBe(1);
286
+ expect(await h.run(["backup", "vault.bundle"])).toBe(1);
287
+ } finally {
288
+ await h.close();
289
+ }
290
+ });
291
+ });
292
+
293
+ describe("oke vault help", () => {
294
+ test("covers both the env loop and the builtin loop", async () => {
295
+ let out = "";
296
+ const code = await vaultCli([], {
297
+ env: {},
298
+ write: (t) => {
299
+ out += t;
300
+ },
301
+ });
302
+ expect(code).toBe(1);
303
+ for (const sub of [
304
+ "set",
305
+ "import",
306
+ "init",
307
+ "unseal",
308
+ "rotate-master",
309
+ "audit",
310
+ "purge-expired",
311
+ "restore",
312
+ ]) {
313
+ expect(out).toContain(`oke vault ${sub}`);
314
+ }
315
+ expect(out).toContain("OKE_VAULT_MASTER_KEY");
316
+ expect(out).toContain("Never pass master keys as CLI arguments");
317
+ });
318
+ });
319
+
320
+ describe("oke vault secure master-key input", () => {
321
+ test("unseal accepts --key - via injected stdin", async () => {
322
+ const h = await harness();
323
+ try {
324
+ await h.run(["init"]);
325
+ const masterKey = masterKeyFrom(h.output());
326
+ h.reset();
327
+ const code = await vaultCli(["unseal", "--key", "-"], {
328
+ sql: h.sql,
329
+ env: {},
330
+ readStdin: async () => masterKey,
331
+ write: (t) => {
332
+ /* capture unused */
333
+ void t;
334
+ },
335
+ });
336
+ expect(code).toBe(0);
337
+ } finally {
338
+ await h.close();
339
+ }
340
+ });
341
+
342
+ test("unseal accepts an injected interactive prompt", async () => {
343
+ const h = await harness();
344
+ try {
345
+ await h.run(["init"]);
346
+ const masterKey = masterKeyFrom(h.output());
347
+ h.reset();
348
+ let prompted = "";
349
+ const code = await vaultCli(["unseal"], {
350
+ sql: h.sql,
351
+ env: {},
352
+ readSecret: async (prompt) => {
353
+ prompted = prompt;
354
+ return masterKey;
355
+ },
356
+ write: (t) => {
357
+ void t;
358
+ },
359
+ });
360
+ expect(code).toBe(0);
361
+ expect(prompted).toContain("master key");
362
+ } finally {
363
+ await h.close();
364
+ }
365
+ });
366
+ });
367
+
368
+ describe("oke vault purge-expired", () => {
369
+ test("dry-run counts without deleting; live purge removes expired rows", async () => {
370
+ const h = await harness();
371
+ try {
372
+ await h.run(["init"]);
373
+ const masterKey = masterKeyFrom(h.output());
374
+ const adapter = await attach(h.sql, masterKey);
375
+ await adapter.set("app/live", "keep");
376
+ await adapter.set("app/stale", "gone", { expiresAt: new Date(Date.now() - 1_000) });
377
+
378
+ h.reset();
379
+ expect(await h.run(["purge-expired", "--dry-run", "--key", masterKey])).toBe(0);
380
+ expect(h.output()).toContain("would purge 1 expired");
381
+
382
+ const stillThere = (await h.sql.query(
383
+ `SELECT COUNT(*)::text AS n FROM oke_vault_secrets WHERE path = $1`,
384
+ ["app/stale"],
385
+ )) as unknown as readonly { readonly n: string }[];
386
+ expect(stillThere[0]?.n).toBe("1");
387
+
388
+ h.reset();
389
+ expect(await h.run(["purge-expired", "--key", masterKey])).toBe(0);
390
+ expect(h.output()).toContain("purged 1 expired");
391
+
392
+ const gone = (await h.sql.query(
393
+ `SELECT COUNT(*)::text AS n FROM oke_vault_secrets WHERE path = $1`,
394
+ ["app/stale"],
395
+ )) as unknown as readonly { readonly n: string }[];
396
+ expect(gone[0]?.n).toBe("0");
397
+ expect((await adapter.get("app/live"))?.value).toBe("keep");
398
+ } finally {
399
+ await h.close();
400
+ }
401
+ });
402
+ });