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,479 @@
1
+ /**
2
+ * Built-in Vault adapter — seal physics, versioning, rotation, audit chain.
3
+ *
4
+ * Runs against a real PGlite instance so the SQL (DDL, `bytea` round-trip,
5
+ * `DISTINCT ON`, `jsonb`) is exercised rather than mocked.
6
+ */
7
+
8
+ import { describe, expect, test } from "bun:test";
9
+ import { connectPglite } from "../../drivers/pglite.ts";
10
+ import type { SqlConnection } from "../../drivers/types.ts";
11
+ import {
12
+ createBuiltinVaultAdapter,
13
+ sqlConnectionAsExec,
14
+ type BuiltinVaultAdapter,
15
+ } from "./builtin-adapter.ts";
16
+ import { generateMasterKey, masterKeyToBase64 } from "./crypto.ts";
17
+ import { isVaultError, VaultError } from "./errors.ts";
18
+ import type { SqlExec } from "./storage.ts";
19
+
20
+ const PATH = "prod/api/stripe";
21
+ const VALUE = "sk_live_do_not_log_me";
22
+
23
+ /**
24
+ * Adapt a driver connection to the Vault's {@link SqlExec} surface.
25
+ *
26
+ * @param conn - Open PGlite connection
27
+ */
28
+ function asExec(conn: SqlConnection): SqlExec {
29
+ return sqlConnectionAsExec(conn);
30
+ }
31
+
32
+ /** An adapter over a fresh in-memory PGlite, plus its teardown. */
33
+ interface Harness {
34
+ readonly adapter: BuiltinVaultAdapter;
35
+ readonly db: SqlExec;
36
+ readonly masterKey: string;
37
+ close(): Promise<void>;
38
+ }
39
+
40
+ /**
41
+ * Boot an initialized, unsealed adapter over a fresh PGlite instance.
42
+ *
43
+ * @param options - Rewrap batch size for rotation tests
44
+ */
45
+ async function harness(options: { batchSize?: number } = {}): Promise<Harness> {
46
+ const conn = await connectPglite({ url: "memory://vault-test" });
47
+ const db = asExec(conn);
48
+ const adapter = createBuiltinVaultAdapter({
49
+ db,
50
+ ...(options.batchSize === undefined ? {} : { kekRewrapBatchSize: options.batchSize }),
51
+ });
52
+ const init = await adapter.initialize();
53
+ await adapter.unseal(init.masterKey);
54
+ return {
55
+ adapter,
56
+ db,
57
+ masterKey: init.masterKey,
58
+ close: () => conn.close(),
59
+ };
60
+ }
61
+
62
+ describe("builtin vault adapter — lifecycle", () => {
63
+ test("initialize returns the master key once and refuses a second run", async () => {
64
+ const conn = await connectPglite({ url: "memory://vault-test" });
65
+ try {
66
+ const adapter = createBuiltinVaultAdapter({ db: asExec(conn) });
67
+ const init = await adapter.initialize();
68
+
69
+ expect(init.masterKey).toMatch(/^[A-Za-z0-9+/]+=*$/);
70
+ expect(init.verifyHash).toMatch(/^[0-9a-f]{64}$/);
71
+ expect(init.kekVersion).toBe(1);
72
+
73
+ const status = await adapter.status();
74
+ expect(status.initialized).toBe(true);
75
+ expect(status.sealed).toBe(true);
76
+ expect(status.masterKeyPresent).toBe(true);
77
+
78
+ const failure = await adapter.initialize().catch((e: unknown) => e);
79
+ expect(isVaultError(failure, "ALREADY_INITIALIZED")).toBe(true);
80
+ } finally {
81
+ await conn.close();
82
+ }
83
+ });
84
+
85
+ test("unseal + set/get round-trips a secret", async () => {
86
+ const h = await harness();
87
+ try {
88
+ const written = await h.adapter.set(PATH, VALUE, { metadata: { owner: "billing" } });
89
+ expect(written.version).toBe(1);
90
+ expect(written.kekVersion).toBe(1);
91
+ expect(written.metadata).toEqual({ owner: "billing" });
92
+
93
+ const read = await h.adapter.get(PATH);
94
+ expect(read?.value).toBe(VALUE);
95
+ expect(read?.version).toBe(1);
96
+ expect(read?.metadata).toEqual({ owner: "billing" });
97
+ expect(read?.algorithm).toBe("aes-256-gcm");
98
+ } finally {
99
+ await h.close();
100
+ }
101
+ });
102
+
103
+ test("nothing readable is stored in cleartext", async () => {
104
+ const h = await harness();
105
+ try {
106
+ await h.adapter.set(PATH, VALUE);
107
+ const rows = await h.db.query<{ encrypted_value: Uint8Array }>(
108
+ `SELECT encrypted_value FROM oke_vault_secrets`,
109
+ );
110
+ const stored = Buffer.from(rows[0]!.encrypted_value).toString("utf8");
111
+ expect(stored).not.toContain("sk_live");
112
+ } finally {
113
+ await h.close();
114
+ }
115
+ });
116
+
117
+ test("a sealed vault rejects reads and writes", async () => {
118
+ const h = await harness();
119
+ try {
120
+ await h.adapter.set(PATH, VALUE);
121
+ await h.adapter.seal();
122
+
123
+ expect((await h.adapter.status()).sealed).toBe(true);
124
+ expect(h.adapter.getUnsealer()).toBeNull();
125
+
126
+ const read = await h.adapter.get(PATH).catch((e: unknown) => e);
127
+ expect(isVaultError(read, "SEALED")).toBe(true);
128
+ const write = await h.adapter.set(PATH, "x").catch((e: unknown) => e);
129
+ expect(isVaultError(write, "SEALED")).toBe(true);
130
+
131
+ await h.adapter.unseal(h.masterKey);
132
+ expect((await h.adapter.get(PATH))?.value).toBe(VALUE);
133
+ } finally {
134
+ await h.close();
135
+ }
136
+ });
137
+
138
+ test("the wrong master key cannot unseal", async () => {
139
+ const h = await harness();
140
+ try {
141
+ await h.adapter.seal();
142
+ const wrong = masterKeyToBase64(generateMasterKey());
143
+ const failure = await h.adapter.unseal(wrong).catch((e: unknown) => e);
144
+ expect(isVaultError(failure, "INVALID_KEY")).toBe(true);
145
+ expect((failure as VaultError).message).not.toContain(wrong);
146
+ expect(h.adapter.getUnsealer()).toBeNull();
147
+ } finally {
148
+ await h.close();
149
+ }
150
+ });
151
+ });
152
+
153
+ describe("builtin vault adapter — versions and listing", () => {
154
+ test("each write adds a version and reads return the newest", async () => {
155
+ const h = await harness();
156
+ try {
157
+ await h.adapter.set(PATH, "v1");
158
+ const second = await h.adapter.set(PATH, "v2");
159
+ const third = await h.adapter.rotate(PATH, "v3");
160
+
161
+ expect(second.version).toBe(2);
162
+ expect(third.version).toBe(3);
163
+ expect((await h.adapter.get(PATH))?.value).toBe("v3");
164
+ expect((await h.adapter.get(PATH, { version: 1 }))?.value).toBe("v1");
165
+ } finally {
166
+ await h.close();
167
+ }
168
+ });
169
+
170
+ test("soft delete hides every version and a later write keeps counting up", async () => {
171
+ const h = await harness();
172
+ try {
173
+ await h.adapter.set(PATH, "v1");
174
+ await h.adapter.set(PATH, "v2");
175
+
176
+ expect(await h.adapter.delete(PATH)).toBe(true);
177
+ expect(await h.adapter.delete(PATH)).toBe(false);
178
+ expect(await h.adapter.get(PATH)).toBeUndefined();
179
+ expect((await h.adapter.list()).map((e) => e.path)).toEqual([]);
180
+ expect((await h.adapter.list({ includeDeleted: true })).map((e) => e.path)).toEqual([PATH]);
181
+
182
+ const revived = await h.adapter.set(PATH, "v3");
183
+ expect(revived.version).toBe(3);
184
+ expect((await h.adapter.get(PATH))?.value).toBe("v3");
185
+ } finally {
186
+ await h.close();
187
+ }
188
+ });
189
+
190
+ test("list reports one entry per path and honours a prefix", async () => {
191
+ const h = await harness();
192
+ try {
193
+ await h.adapter.set("prod/api/stripe", "a");
194
+ await h.adapter.set("prod/api/stripe", "b");
195
+ await h.adapter.set("prod/db/main", "c");
196
+ await h.adapter.set("staging/api/stripe", "d");
197
+
198
+ const all = await h.adapter.list();
199
+ expect(all.map((e) => e.path)).toEqual([
200
+ "prod/api/stripe",
201
+ "prod/db/main",
202
+ "staging/api/stripe",
203
+ ]);
204
+ expect(all[0]?.version).toBe(2);
205
+
206
+ const scoped = await h.adapter.list({ prefix: "prod/api" });
207
+ expect(scoped.map((e) => e.path)).toEqual(["prod/api/stripe"]);
208
+
209
+ expect((await h.adapter.status()).secretCount).toBe(3);
210
+ } finally {
211
+ await h.close();
212
+ }
213
+ });
214
+
215
+ test("an expired secret fails closed unless the caller opts in", async () => {
216
+ const h = await harness();
217
+ try {
218
+ await h.adapter.set(PATH, VALUE, { expiresAt: new Date(Date.now() - 1_000) });
219
+
220
+ const failure = await h.adapter.get(PATH).catch((e: unknown) => e);
221
+ expect(isVaultError(failure, "EXPIRED")).toBe(true);
222
+ expect((await h.adapter.get(PATH, { allowExpired: true }))?.value).toBe(VALUE);
223
+ } finally {
224
+ await h.close();
225
+ }
226
+ });
227
+
228
+ test("paths are canonicalized and traversal is rejected", async () => {
229
+ const h = await harness();
230
+ try {
231
+ await h.adapter.set("/prod//api///stripe/", VALUE);
232
+ expect((await h.adapter.get(PATH))?.value).toBe(VALUE);
233
+ await expect(h.adapter.get("prod/../etc")).rejects.toThrow(VaultError);
234
+ } finally {
235
+ await h.close();
236
+ }
237
+ });
238
+ });
239
+
240
+ describe("builtin vault adapter — master rotation", () => {
241
+ test("rotateMaster re-wraps every DEK and secrets stay readable", async () => {
242
+ const h = await harness({ batchSize: 2 });
243
+ try {
244
+ for (let i = 0; i < 5; i += 1) {
245
+ await h.adapter.set(`prod/api/key-${i}`, `value-${i}`);
246
+ }
247
+
248
+ const next = generateMasterKey();
249
+ let progress = await h.adapter.rotateMaster(next);
250
+ expect(progress.kekVersion).toBe(2);
251
+ expect(progress.remaining).toBe(3);
252
+
253
+ // Mid-rewrap the vault dual-reads both KEK generations.
254
+ expect((await h.adapter.get("prod/api/key-0"))?.value).toBe("value-0");
255
+ expect((await h.adapter.get("prod/api/key-4"))?.value).toBe("value-4");
256
+
257
+ while (progress.remaining > 0) {
258
+ progress = await h.adapter.continueRotateMaster();
259
+ }
260
+ expect(progress.remaining).toBe(0);
261
+ expect((await h.adapter.status()).kekVersion).toBe(2);
262
+
263
+ for (let i = 0; i < 5; i += 1) {
264
+ const secret = await h.adapter.get(`prod/api/key-${i}`);
265
+ expect(secret?.value).toBe(`value-${i}`);
266
+ expect(secret?.kekVersion).toBe(2);
267
+ }
268
+
269
+ // The old master key is retired; only the new one unseals.
270
+ await h.adapter.seal();
271
+ const stale = await h.adapter.unseal(h.masterKey).catch((e: unknown) => e);
272
+ expect(isVaultError(stale, "INVALID_KEY")).toBe(true);
273
+ await h.adapter.unseal(masterKeyToBase64(next));
274
+ expect((await h.adapter.get("prod/api/key-2"))?.value).toBe("value-2");
275
+ } finally {
276
+ await h.close();
277
+ }
278
+ });
279
+
280
+ test("rotateMaster without a key returns the generated one exactly once", async () => {
281
+ const h = await harness();
282
+ try {
283
+ await h.adapter.set(PATH, VALUE);
284
+ const progress = await h.adapter.rotateMaster();
285
+ expect(progress.remaining).toBe(0);
286
+ expect(progress.masterKey).toBeString();
287
+
288
+ await h.adapter.seal();
289
+ await h.adapter.unseal(progress.masterKey!);
290
+ expect((await h.adapter.get(PATH))?.value).toBe(VALUE);
291
+ } finally {
292
+ await h.close();
293
+ }
294
+ });
295
+
296
+ test("continueRotateMaster without a rotation in flight is rejected", async () => {
297
+ const h = await harness();
298
+ try {
299
+ const failure = await h.adapter.continueRotateMaster().catch((e: unknown) => e);
300
+ expect(isVaultError(failure, "UNSUPPORTED")).toBe(true);
301
+ } finally {
302
+ await h.close();
303
+ }
304
+ });
305
+
306
+ test("continueRotateMaster cold-resumes from rewrap_key_hash after process death", async () => {
307
+ const conn = await connectPglite({ url: "memory://vault-cold-resume" });
308
+ const db = asExec(conn);
309
+ try {
310
+ const first = createBuiltinVaultAdapter({ db, kekRewrapBatchSize: 2 });
311
+ const init = await first.initialize();
312
+ await first.unseal(init.masterKey);
313
+ for (let i = 0; i < 5; i += 1) {
314
+ await first.set(`prod/api/key-${i}`, `value-${i}`);
315
+ }
316
+
317
+ const next = generateMasterKey();
318
+ const nextB64 = masterKeyToBase64(next);
319
+ let progress = await first.rotateMaster(next);
320
+ expect(progress.remaining).toBe(3);
321
+ expect((await first.status()).rewrapTargetKekVersion).toBe(2);
322
+
323
+ // Simulate process death: drop in-memory pending, reopen over the same DB.
324
+ const second = createBuiltinVaultAdapter({ db, kekRewrapBatchSize: 2 });
325
+ await second.unseal(init.masterKey);
326
+ expect((await second.status()).rewrapTargetKekVersion).toBe(2);
327
+
328
+ const wrong = await second.continueRotateMaster().catch((e: unknown) => e);
329
+ expect(isVaultError(wrong, "INVALID_KEY")).toBe(true);
330
+
331
+ progress = await second.continueRotateMaster(nextB64);
332
+ while (progress.remaining > 0) {
333
+ progress = await second.continueRotateMaster(nextB64);
334
+ }
335
+ expect(progress.remaining).toBe(0);
336
+ expect((await second.status()).kekVersion).toBe(2);
337
+ expect((await second.status()).rewrapTargetKekVersion).toBeUndefined();
338
+
339
+ for (let i = 0; i < 5; i += 1) {
340
+ expect((await second.get(`prod/api/key-${i}`))?.value).toBe(`value-${i}`);
341
+ }
342
+
343
+ await second.seal();
344
+ const stale = await second.unseal(init.masterKey).catch((e: unknown) => e);
345
+ expect(isVaultError(stale, "INVALID_KEY")).toBe(true);
346
+ await second.unseal(nextB64);
347
+ expect((await second.get("prod/api/key-0"))?.value).toBe("value-0");
348
+ } finally {
349
+ await conn.close();
350
+ }
351
+ });
352
+ });
353
+
354
+ describe("builtin vault adapter — audit and backup", () => {
355
+ test("the audit chain verifies after a full run of operations", async () => {
356
+ const h = await harness();
357
+ try {
358
+ await h.adapter.set(PATH, VALUE);
359
+ await h.adapter.get(PATH);
360
+ await h.adapter.rotate(PATH, "v2");
361
+ await h.adapter.list();
362
+ await h.adapter.delete(PATH);
363
+ await h.adapter.seal();
364
+ await h.adapter.unseal(h.masterKey);
365
+
366
+ expect(await h.adapter.verifyAudit()).toEqual({ ok: true });
367
+
368
+ const rows = await h.db.query<{ action: string; path: string | null }>(
369
+ `SELECT action, path FROM oke_vault_audit ORDER BY seq ASC`,
370
+ );
371
+ expect(rows.map((r) => r.action)).toContain("initialize");
372
+ expect(rows.map((r) => r.action)).toContain("rotate");
373
+ expect(rows.every((r) => r.path === null || !r.path.includes("sk_live"))).toBe(true);
374
+ } finally {
375
+ await h.close();
376
+ }
377
+ });
378
+
379
+ test("a tampered audit row is reported with its id", async () => {
380
+ const h = await harness();
381
+ try {
382
+ await h.adapter.set(PATH, VALUE);
383
+ await h.adapter.get(PATH);
384
+ const rows = await h.db.query<{ id: string }>(
385
+ `SELECT id FROM oke_vault_audit ORDER BY seq ASC`,
386
+ );
387
+ const victim = rows[1]!.id;
388
+ await h.db.execute(`UPDATE oke_vault_audit SET action = 'delete' WHERE id = $1`, [victim]);
389
+
390
+ const result = await h.adapter.verifyAudit();
391
+ expect(result.ok).toBe(false);
392
+ expect(result.brokenAt).toBe(victim);
393
+ } finally {
394
+ await h.close();
395
+ }
396
+ });
397
+
398
+ test("purgeAuditBefore drops old rows and the retained chain still verifies", async () => {
399
+ const h = await harness();
400
+ try {
401
+ await h.adapter.set(PATH, VALUE);
402
+ const removed = await h.adapter.purgeAuditBefore(new Date(Date.now() + 60_000));
403
+ expect(removed).toBeGreaterThan(0);
404
+ expect(await h.adapter.verifyAudit()).toEqual({ ok: true });
405
+ } finally {
406
+ await h.close();
407
+ }
408
+ });
409
+
410
+ test("purgeExpired dry-run counts then hard-deletes expired rows", async () => {
411
+ const h = await harness();
412
+ try {
413
+ await h.adapter.set("app/live", "keep-me");
414
+ await h.adapter.set("app/stale", "drop-me", {
415
+ expiresAt: new Date(Date.now() - 60_000),
416
+ });
417
+
418
+ const dry = await h.adapter.purgeExpired({ dryRun: true });
419
+ expect(dry.count).toBe(1);
420
+ expect(dry.paths).toEqual(["app/stale"]);
421
+ // Dry-run must not remove rows.
422
+ expect(
423
+ (
424
+ await h.db.query<{ n: string }>(
425
+ `SELECT COUNT(*)::text AS n FROM oke_vault_secrets WHERE path = $1`,
426
+ ["app/stale"],
427
+ )
428
+ )[0]?.n,
429
+ ).toBe("1");
430
+
431
+ const live = await h.adapter.purgeExpired();
432
+ expect(live.count).toBe(1);
433
+ expect(
434
+ (
435
+ await h.db.query<{ n: string }>(
436
+ `SELECT COUNT(*)::text AS n FROM oke_vault_secrets WHERE path = $1`,
437
+ ["app/stale"],
438
+ )
439
+ )[0]?.n,
440
+ ).toBe("0");
441
+ expect((await h.adapter.get("app/live"))?.value).toBe("keep-me");
442
+
443
+ const audit = await h.adapter.listAudit({ limit: 5 });
444
+ const purge = audit.find((r) => r.action === "purge" && r.errorMessage?.includes("expired"));
445
+ expect(purge?.errorMessage).toBe("expired count=1");
446
+ // No per-path audit bloat for the purge itself.
447
+ expect(purge?.path).toBeNull();
448
+ } finally {
449
+ await h.close();
450
+ }
451
+ });
452
+
453
+ test("backup round-trips through the backup KEK and rejects foreign bytes", async () => {
454
+ const h = await harness();
455
+ try {
456
+ await h.adapter.set("prod/api/stripe", "a");
457
+ await h.adapter.set("prod/db/main", "b", { metadata: { tier: "primary" } });
458
+
459
+ const blob = await h.adapter.exportBackup();
460
+ expect(Buffer.from(blob).toString("utf8")).toStartWith("oke-vault-backup-v1\n");
461
+ expect(Buffer.from(blob).toString("utf8")).not.toContain("prod/db/main");
462
+
463
+ await h.adapter.delete("prod/api/stripe");
464
+ await h.adapter.delete("prod/db/main");
465
+ expect(await h.adapter.list()).toHaveLength(0);
466
+
467
+ await h.adapter.importBackup(blob);
468
+ expect((await h.adapter.get("prod/api/stripe"))?.value).toBe("a");
469
+ const restored = await h.adapter.get("prod/db/main");
470
+ expect(restored?.value).toBe("b");
471
+ expect(restored?.metadata).toEqual({ tier: "primary" });
472
+
473
+ const junk = await h.adapter.importBackup(new Uint8Array([1, 2, 3])).catch((e: unknown) => e);
474
+ expect(isVaultError(junk, "UNSUPPORTED")).toBe(true);
475
+ } finally {
476
+ await h.close();
477
+ }
478
+ });
479
+ });