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
@@ -56,7 +56,7 @@ describe("store.schema.table + field.*", () => {
56
56
  const decl = store.sql("notes", { schema: { notes } });
57
57
  const runtime = createStoreRuntime({
58
58
  drivers: {},
59
- sql: { notes: { name: "notes", primary: { url: ":memory:" } } },
59
+ sql: { notes: { name: "notes", primary: {} } },
60
60
  });
61
61
  // Register so classificationsFor merges table columns.
62
62
  runtime.register(decl);
@@ -68,10 +68,10 @@ describe("store.schema.table + field.*", () => {
68
68
  expect(masked[0]!.email).toBe(PII_MASK);
69
69
 
70
70
  // Runtime path: open handle and query — ensure classificationsFromTable feeds mask.
71
- const { sqliteDriver } = await import("../../drivers/index.ts");
71
+ const { memorySqlDriver } = await import("../../drivers/index.ts");
72
72
  const rt = createStoreRuntime({
73
- drivers: { sql: sqliteDriver },
74
- sql: { notes: { name: "notes", primary: { url: ":memory:" } } },
73
+ drivers: { sql: memorySqlDriver },
74
+ sql: { notes: { name: "notes", primary: {} } },
75
75
  });
76
76
  rt.register(decl);
77
77
  const handle = (await rt.open(decl, {
@@ -84,7 +84,7 @@ describe("store.schema.table + field.*", () => {
84
84
  });
85
85
  });
86
86
 
87
- describe("emitDrizzleSource — both dialects from one declaration", () => {
87
+ describe("emitDrizzleSource — postgres pgTable from one declaration", () => {
88
88
  const notes = store.schema.table("notes", {
89
89
  id: field.text().primaryKey().defaultFn(id),
90
90
  title: field.text().notNull(),
@@ -92,24 +92,17 @@ describe("emitDrizzleSource — both dialects from one declaration", () => {
92
92
  createdAt: field.integer().notNull().defaultFn(now),
93
93
  });
94
94
 
95
- test("sqlite emits sqliteTable", () => {
96
- const src = emitDrizzleSource([notes], "sqlite");
97
- expect(src.startsWith(GENERATED_SCHEMA_HEADER)).toBe(true);
98
- expect(src).toContain('from "drizzle-orm/sqlite-core"');
99
- expect(src).toContain("sqliteTable");
100
- expect(src).not.toContain("pgTable");
101
- expect(src).toContain('text("id").primaryKey().$defaultFn(id)');
102
- expect(src).toContain('integer("created_at").notNull().$defaultFn(now)');
103
- expect(src).toContain('text("body").notNull()');
104
- });
105
-
106
- test("postgres emits pgTable from the same decl", () => {
95
+ test("postgres emits pgTable", () => {
107
96
  const src = emitDrizzleSource([notes], "postgres");
97
+ expect(src.startsWith(GENERATED_SCHEMA_HEADER)).toBe(true);
108
98
  expect(src).toContain('from "drizzle-orm/pg-core"');
109
99
  expect(src).toContain("pgTable");
110
100
  expect(src).not.toContain("sqliteTable");
101
+ expect(src).not.toContain("drizzle-orm/sqlite-core");
111
102
  expect(src).toContain('text("id").primaryKey().$defaultFn(id)');
112
- expect(src).toContain('integer("created_at").notNull().$defaultFn(now)');
103
+ // Postgres INTEGER is 32-bit — abstract integer maps to bigint for ms clocks.
104
+ expect(src).toContain('bigint("created_at", { mode: "number" }).notNull().$defaultFn(now)');
105
+ expect(src).toContain('text("body").notNull()');
113
106
  });
114
107
  });
115
108
 
@@ -142,9 +135,9 @@ describe("plugin table columns in generated schema", () => {
142
135
  expect(pluginTables[0]!.columns.actorId!.classification?.pii).toBe(true);
143
136
 
144
137
  const merged = mergeSchemaTables([notes], pluginTables);
145
- const src = emitDrizzleSource(merged, "sqlite");
146
- expect(src).toContain('sqliteTable("notes"');
147
- expect(src).toContain('sqliteTable("audit_events"');
138
+ const src = emitDrizzleSource(merged, "postgres");
139
+ expect(src).toContain('pgTable("notes"');
140
+ expect(src).toContain('pgTable("audit_events"');
148
141
  expect(src).toContain('text("actor_id").notNull()');
149
142
  });
150
143
 
@@ -195,21 +188,12 @@ describe("emitDrizzleSource — references + relations (Linkly-shaped)", () => {
195
188
  },
196
189
  }));
197
190
 
198
- test("sqlite emit includes FK + defineRelations", () => {
199
- const src = emitDrizzleSource([links, daily], "sqlite", { relations: [relations] });
200
- expect(src).toContain("sqliteTable");
201
- expect(src).toContain(".references(() => links.code)");
202
- expect(src).toContain('import { defineRelations } from "drizzle-orm"');
203
- expect(src).toContain("defineRelations({ links, daily }");
204
- expect(src).toContain("r.many.daily({ from: r.links.code, to: r.daily.code })");
205
- expect(src).toContain("r.one.links({ from: r.daily.code, to: r.links.code, optional: false })");
206
- });
207
-
208
- test("postgres emit includes same FK + defineRelations from one decl", () => {
191
+ test("postgres emit includes FK + defineRelations", () => {
209
192
  const src = emitDrizzleSource([links, daily], "postgres", { relations: [relations] });
210
193
  expect(src).toContain("pgTable");
211
194
  expect(src).not.toContain("sqliteTable");
212
195
  expect(src).toContain(".references(() => links.code)");
196
+ expect(src).toContain('import { defineRelations } from "drizzle-orm"');
213
197
  expect(src).toContain("defineRelations({ links, daily }");
214
198
  expect(src).toContain("r.many.daily({ from: r.links.code, to: r.daily.code })");
215
199
  expect(src).toContain("r.one.links({ from: r.daily.code, to: r.links.code, optional: false })");
@@ -19,8 +19,7 @@ describe("defineSeed / resolveSeedCategory", () => {
19
19
  });
20
20
 
21
21
  test("resolveSeedCategory matches the env matrix", () => {
22
- expect(resolveSeedCategory("local")).toBe("dev");
23
- expect(resolveSeedCategory("docker")).toBe("dev");
22
+ expect(resolveSeedCategory("dev")).toBe("dev");
24
23
  expect(resolveSeedCategory("prod")).toBe("prod");
25
24
  expect(resolveSeedCategory("test")).toBe(null);
26
25
  });
@@ -17,7 +17,7 @@ export type SeedFns = SeedFn | readonly SeedFn[];
17
17
  * Seed categories for {@link defineSeed}.
18
18
  *
19
19
  * - `essential` — every environment, always
20
- * - `dev` — `local` or `docker` only
20
+ * - `dev` — `dev` ConfigEnv only (Docker Compose laptop)
21
21
  * - `prod` — `prod` only
22
22
  */
23
23
  export interface SeedDef {
@@ -52,17 +52,16 @@ export function normalizeSeedFns(fns?: SeedFns): SeedFn[] {
52
52
  /**
53
53
  * Which optional category runs for `env` (single source of truth).
54
54
  *
55
- * | env | category |
56
- * | ------ | -------- |
57
- * | local | dev |
58
- * | docker | dev |
59
- * | test | (none) |
60
- * | prod | prod |
55
+ * | env | category |
56
+ * | ---- | -------- |
57
+ * | dev | dev |
58
+ * | test | (none) |
59
+ * | prod | prod |
61
60
  *
62
61
  * @param env - Resolved {@link ConfigEnv}
63
62
  */
64
63
  export function resolveSeedCategory(env: ConfigEnv): SeedCategory | null {
65
- if (env === "local" || env === "docker") return "dev";
64
+ if (env === "dev") return "dev";
66
65
  if (env === "prod") return "prod";
67
66
  return null;
68
67
  }
@@ -20,10 +20,10 @@ import {
20
20
  or,
21
21
  sql,
22
22
  } from "drizzle-orm";
23
- import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
23
+ import { integer, pgTable, text } from "drizzle-orm/pg-core";
24
24
  import { compileOrderBy, compileWhere } from "./sql-condition.ts";
25
25
 
26
- const notes = sqliteTable("notes", {
26
+ const notes = pgTable("notes", {
27
27
  id: text("id").primaryKey(),
28
28
  title: text("title").notNull(),
29
29
  createdAt: integer("created_at").notNull(),
@@ -7,22 +7,21 @@
7
7
  */
8
8
 
9
9
  import { describe, expect, test } from "bun:test";
10
- import { Database } from "bun:sqlite";
11
10
  import { and, desc, eq, like, lt, or } from "drizzle-orm";
12
- import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
13
- import { sqliteDriver } from "../../drivers/sqlite.ts";
11
+ import { integer, pgTable, text } from "drizzle-orm/pg-core";
12
+ import { pgliteDriver } from "../../drivers/pglite.ts";
14
13
  import type { SqlConnection } from "../../drivers/types.ts";
15
14
  import { createSqlStoreHandle, type SqlStoreHandle } from "./sql-session.ts";
16
15
 
17
- const posts = sqliteTable("posts", {
16
+ const posts = pgTable("posts", {
18
17
  id: text("id").primaryKey(),
19
18
  title: text("title").notNull(),
20
19
  createdAt: integer("created_at").notNull(),
21
20
  });
22
21
 
23
22
  async function openHandle(): Promise<{ handle: SqlStoreHandle; conn: SqlConnection }> {
24
- const conn = await sqliteDriver.connect({
25
- client: new Database(":memory:"),
23
+ const conn = await pgliteDriver.connect({
24
+ url: "memory://",
26
25
  role: "primary",
27
26
  });
28
27
  const handle = createSqlStoreHandle("sql:app", {
@@ -36,8 +35,7 @@ async function openHandle(): Promise<{ handle: SqlStoreHandle; conn: SqlConnecti
36
35
 
37
36
  describe("SqlStoreHandle — no relational query surface (path b)", () => {
38
37
  test("created handle exposes exactly the single-table surface", async () => {
39
- const db = new Database(":memory:");
40
- const conn = await sqliteDriver.connect({ client: db, role: "primary" });
38
+ const conn = await pgliteDriver.connect({ url: "memory://", role: "primary" });
41
39
  const handle = createSqlStoreHandle("sql:app", {
42
40
  connection: conn,
43
41
  classifications: new Map(),
@@ -124,12 +122,12 @@ describe("SqlStoreHandle — orderBy / limit select chain", () => {
124
122
  await conn.close();
125
123
  });
126
124
 
127
- test("like filters rows through the session (sqlite ASCII-insensitive)", async () => {
125
+ test("like filters rows through the session (postgres case-sensitive)", async () => {
128
126
  const { handle, conn } = await openHandle();
129
127
  await handle.insert(posts).values({ id: "p1", title: "alpha", createdAt: 100 });
130
128
  await handle.insert(posts).values({ id: "p2", title: "bravo", createdAt: 200 });
131
129
 
132
- const rows = await handle.select().from(posts).where(like(posts.title, "A%"));
130
+ const rows = await handle.select().from(posts).where(like(posts.title, "a%"));
133
131
  expect(rows.map((r) => r.id)).toEqual(["p1"]);
134
132
  await conn.close();
135
133
  });
@@ -363,9 +363,13 @@ export function createSqlStoreHandle(
363
363
  if (cols.length === 0) return;
364
364
  const name = resolveTableName(table);
365
365
  const pk = cols.find((c) => c.primary)?.sqlName;
366
+ // Postgres/PGLite INTEGER is 32-bit — abstract `integer` (ms timestamps via
367
+ // `now`) must map to BIGINT. SQLite INTEGER is flexible up to 64-bit.
368
+ const pgWideInt = connection.driverId === "postgres" || connection.driverId === "pglite";
366
369
  const colSql = cols
367
370
  .map((c) => {
368
- const typ = pk && c.sqlName === pk ? `${c.sqlType} PRIMARY KEY` : c.sqlType;
371
+ const base = pgWideInt && c.sqlType === "INTEGER" ? "BIGINT" : c.sqlType;
372
+ const typ = pk && c.sqlName === pk ? `${base} PRIMARY KEY` : base;
369
373
  return `${quoteIdent(c.sqlName)} ${typ}`;
370
374
  })
371
375
  .join(", ");
@@ -734,6 +738,10 @@ export function createSqlStoreHandle(
734
738
  async ensureTable(table) {
735
739
  const cols = Object.values(table.columns);
736
740
  const pk = resolvePkColumn(table);
741
+ const intType =
742
+ connection.driverId === "postgres" || connection.driverId === "pglite"
743
+ ? "BIGINT"
744
+ : "INTEGER";
737
745
  const colSql = cols
738
746
  .map((c) => {
739
747
  const typ =
@@ -743,7 +751,7 @@ export function createSqlStoreHandle(
743
751
  c.name === "qty" ||
744
752
  c.name === "createdAt" ||
745
753
  c.name === "created_at"
746
- ? "INTEGER"
754
+ ? intType
747
755
  : "TEXT";
748
756
  return `${quoteIdent(c.name)} ${typ}`;
749
757
  })
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Store element acceptance:
3
- * - same flow runs unchanged against sqlite and postgres
3
+ * - same flow runs unchanged against memory and postgres
4
4
  * - read-only flow provably hits a replica
5
5
  * - auto-invalidation fires on exactly the writes touching the read's keys
6
6
  * - SELECT * masks classified columns
7
7
  */
8
8
 
9
9
  import { describe, expect, test } from "bun:test";
10
- import { createPostgresFakeClient, postgresDriver, sqliteDriver } from "../drivers/index.ts";
10
+ import { createPostgresFakeClient, memorySqlDriver, postgresDriver } from "../drivers/index.ts";
11
11
  import type { Effects } from "../manifest/types.ts";
12
12
  import {
13
13
  classify,
@@ -73,7 +73,7 @@ describe("store facets", () => {
73
73
  });
74
74
  });
75
75
 
76
- describe("same flow against sqlite and postgres", () => {
76
+ describe("same flow against memory and postgres", () => {
77
77
  test("insert / select / findById unchanged across drivers", async () => {
78
78
  const decl = store.sql("notes", {
79
79
  schema: { notes },
@@ -81,7 +81,7 @@ describe("same flow against sqlite and postgres", () => {
81
81
  });
82
82
 
83
83
  for (const [label, driver, client] of [
84
- ["sqlite", sqliteDriver, undefined],
84
+ ["memory", memorySqlDriver, undefined],
85
85
  ["postgres", postgresDriver, createPostgresFakeClient()],
86
86
  ] as const) {
87
87
  const runtime = createStoreRuntime({
@@ -89,7 +89,7 @@ describe("same flow against sqlite and postgres", () => {
89
89
  sql: {
90
90
  notes: {
91
91
  name: "notes",
92
- primary: { url: ":memory:", client },
92
+ primary: { client },
93
93
  },
94
94
  },
95
95
  });
@@ -218,8 +218,8 @@ describe("tier-1 cache invalidation from effects", () => {
218
218
 
219
219
  test("runtime putTier1 + onWriteEffects", async () => {
220
220
  const runtime = createStoreRuntime({
221
- drivers: { sql: sqliteDriver },
222
- sql: { notes: { name: "notes", primary: { url: ":memory:" } } },
221
+ drivers: { sql: memorySqlDriver },
222
+ sql: { notes: { name: "notes", primary: {} } },
223
223
  });
224
224
  const keys = runtime.putTier1({ reads: ["sql:notes"] }, ["cached"]);
225
225
  expect(runtime.cache.get<string[]>(keys[0]!)).toEqual(["cached"]);
@@ -237,7 +237,7 @@ describe("exists and increment helpers", () => {
237
237
 
238
238
  test("exists returns correctly for present / absent rows across sqlite and postgres", async () => {
239
239
  for (const [label, driver, client] of [
240
- ["sqlite", sqliteDriver, undefined],
240
+ ["memory", memorySqlDriver, undefined],
241
241
  ["postgres", postgresDriver, createPostgresFakeClient()],
242
242
  ] as const) {
243
243
  const decl = store.sql("counters");
@@ -246,7 +246,7 @@ describe("exists and increment helpers", () => {
246
246
  sql: {
247
247
  counters: {
248
248
  name: "counters",
249
- primary: { url: ":memory:", client },
249
+ primary: { client },
250
250
  },
251
251
  },
252
252
  });
@@ -267,7 +267,7 @@ describe("exists and increment helpers", () => {
267
267
 
268
268
  test("increment under 100 concurrent callers lands on the correct final value", async () => {
269
269
  for (const [label, driver, client] of [
270
- ["sqlite", sqliteDriver, undefined],
270
+ ["memory", memorySqlDriver, undefined],
271
271
  ["postgres", postgresDriver, createPostgresFakeClient()],
272
272
  ] as const) {
273
273
  const decl = store.sql("counters");
@@ -276,7 +276,7 @@ describe("exists and increment helpers", () => {
276
276
  sql: {
277
277
  counters: {
278
278
  name: "counters",
279
- primary: { url: ":memory:", client },
279
+ primary: { client },
280
280
  },
281
281
  },
282
282
  });
@@ -308,8 +308,8 @@ describe("PII masking at the driver boundary", () => {
308
308
  classify: { notes: { email: classify({ pii: true }) } },
309
309
  });
310
310
  const runtime = createStoreRuntime({
311
- drivers: { sql: sqliteDriver },
312
- sql: { notes: { name: "notes", primary: { url: ":memory:" } } },
311
+ drivers: { sql: memorySqlDriver },
312
+ sql: { notes: { name: "notes", primary: {} } },
313
313
  });
314
314
  runtime.register(decl);
315
315
  const handle = asSql(
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Vault audit trail — a tamper-evident hash chain of *operations*.
3
+ *
4
+ * An audit row records that something happened to a path, never what the
5
+ * value was. Each row hashes its own payload together with the previous
6
+ * row's hash, so removing or editing a row breaks every hash after it
7
+ * (`verifyAuditChain` in `storage.ts` walks the chain).
8
+ */
9
+
10
+ import { VaultError } from "./errors.ts";
11
+ import type { VaultErrorCode } from "./errors.ts";
12
+ import type { VaultActorType } from "./types.ts";
13
+ import type { VaultAuditSinkKind } from "./types.ts";
14
+
15
+ /** Operations recorded in the audit chain. */
16
+ export type AuditAction =
17
+ | "get"
18
+ | "set"
19
+ | "delete"
20
+ | "rotate"
21
+ | "list"
22
+ | "seal"
23
+ | "unseal"
24
+ | "initialize"
25
+ | "rewrap"
26
+ | "purge";
27
+
28
+ /** Hash-chain genesis marker used for the first row. */
29
+ export const AUDIT_GENESIS_HASH = "genesis";
30
+
31
+ /** One operation to append to the chain. */
32
+ export interface AuditEntry {
33
+ /** What happened. */
34
+ readonly action: AuditAction;
35
+ /** Canonical path, when the operation targets one. */
36
+ readonly path?: string;
37
+ /** Actor class. */
38
+ readonly actorType: VaultActorType;
39
+ /** Actor id within its class. */
40
+ readonly actorId?: string;
41
+ /** Whether the operation succeeded. */
42
+ readonly success: boolean;
43
+ /** Failure reason when `success` is `false`. */
44
+ readonly errorCode?: VaultErrorCode;
45
+ /** Safe, secret-free failure description. */
46
+ readonly errorMessage?: string;
47
+ /** Correlation id for the enclosing request/run. */
48
+ readonly requestId?: string;
49
+ /** Event time. Defaults to now at append time. */
50
+ readonly at?: Date;
51
+ }
52
+
53
+ /**
54
+ * Exact field set covered by a row hash.
55
+ *
56
+ * Normalized (`null` rather than `undefined`, ISO-8601 timestamps) so a row
57
+ * read back from SQL hashes identically to the row that was written.
58
+ */
59
+ export interface AuditHashPayload {
60
+ /** What happened. */
61
+ readonly action: AuditAction;
62
+ /** Canonical path or `null`. */
63
+ readonly path: string | null;
64
+ /** Actor class. */
65
+ readonly actorType: VaultActorType;
66
+ /** Actor id or `null`. */
67
+ readonly actorId: string | null;
68
+ /** Whether the operation succeeded. */
69
+ readonly success: boolean;
70
+ /** Failure reason or `null`. */
71
+ readonly errorCode: VaultErrorCode | null;
72
+ /** Failure description or `null`. */
73
+ readonly errorMessage: string | null;
74
+ /** Correlation id or `null`. */
75
+ readonly requestId: string | null;
76
+ /** ISO-8601 event time. */
77
+ readonly createdAt: string;
78
+ }
79
+
80
+ /** Destination for audit rows. */
81
+ export interface AuditSink {
82
+ /** Sink kind, mirroring the configured `audit.sink`. */
83
+ readonly kind: VaultAuditSinkKind;
84
+ /**
85
+ * Append one operation.
86
+ *
87
+ * @param entry - Operation to record
88
+ */
89
+ append(entry: AuditEntry): Promise<void>;
90
+ /** Release sink resources, if any. */
91
+ close?(): Promise<void>;
92
+ }
93
+
94
+ /**
95
+ * Backend-side audit writer.
96
+ *
97
+ * `storage.ts` implements this over `oke_vault_audit`; the `db` sink is a
98
+ * thin adapter so the audit module never imports SQL.
99
+ */
100
+ export interface AuditWriter {
101
+ /**
102
+ * Persist one operation, computing and linking its row hash.
103
+ *
104
+ * @param entry - Operation to record
105
+ */
106
+ appendAuditEntry(entry: AuditEntry): Promise<void>;
107
+ }
108
+
109
+ /** Options accepted by {@link createAuditSink}. */
110
+ export interface CreateAuditSinkOptions {
111
+ /** Required for the `db` sink. */
112
+ readonly writer?: AuditWriter;
113
+ /** Required for the `webhook` sink (not yet implemented). */
114
+ readonly webhookUrl?: string;
115
+ /** Line writer for the `stdout` sink (tests). */
116
+ readonly write?: (line: string) => void;
117
+ /** Clock for `stdout` timestamps (tests). */
118
+ readonly now?: () => Date;
119
+ }
120
+
121
+ /**
122
+ * Normalize an entry into the exact fields a row hash covers.
123
+ *
124
+ * @param entry - Operation to record
125
+ * @param createdAt - Event time
126
+ */
127
+ export function toAuditHashPayload(entry: AuditEntry, createdAt: Date): AuditHashPayload {
128
+ return {
129
+ action: entry.action,
130
+ path: entry.path ?? null,
131
+ actorType: entry.actorType,
132
+ actorId: entry.actorId ?? null,
133
+ success: entry.success,
134
+ errorCode: entry.errorCode ?? null,
135
+ errorMessage: entry.errorMessage ?? null,
136
+ requestId: entry.requestId ?? null,
137
+ createdAt: createdAt.toISOString(),
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Hash one audit row into the chain.
143
+ *
144
+ * Fields are joined with NUL in a fixed order — no JSON key-order or
145
+ * whitespace ambiguity — and prefixed with the previous row's hash.
146
+ *
147
+ * @param prevHash - Previous row hash, or `null` / {@link AUDIT_GENESIS_HASH} for the first row
148
+ * @param payload - Normalized row fields
149
+ * @returns Lowercase hex SHA-256 digest
150
+ */
151
+ export async function computeAuditRowHash(
152
+ prevHash: string | null,
153
+ payload: AuditHashPayload,
154
+ ): Promise<string> {
155
+ const parts = [
156
+ "oke-vault-audit-v1",
157
+ prevHash ?? AUDIT_GENESIS_HASH,
158
+ payload.action,
159
+ payload.path ?? "",
160
+ payload.actorType,
161
+ payload.actorId ?? "",
162
+ payload.success ? "1" : "0",
163
+ payload.errorCode ?? "",
164
+ payload.errorMessage ?? "",
165
+ payload.requestId ?? "",
166
+ payload.createdAt,
167
+ ];
168
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(parts.join("\0")));
169
+ return Buffer.from(new Uint8Array(digest)).toString("hex");
170
+ }
171
+
172
+ /**
173
+ * Build an audit sink.
174
+ *
175
+ * - `db` delegates to an {@link AuditWriter} supplied by storage.
176
+ * - `stdout` writes one secret-free JSON line per operation.
177
+ * - `webhook` is not implemented yet and throws `UNSUPPORTED`.
178
+ *
179
+ * @param kind - Sink kind from `audit.sink`
180
+ * @param opts - Writer / webhook URL / test seams
181
+ * @throws VaultError `UNSUPPORTED` for `webhook`, or `MISSING_PEER` when `db` has no writer
182
+ */
183
+ export function createAuditSink(
184
+ kind: VaultAuditSinkKind,
185
+ opts: CreateAuditSinkOptions = {},
186
+ ): AuditSink {
187
+ if (kind === "webhook") {
188
+ throw new VaultError("UNSUPPORTED", "vault: webhook audit sink is not implemented");
189
+ }
190
+
191
+ if (kind === "db") {
192
+ const writer = opts.writer;
193
+ if (!writer) {
194
+ throw new VaultError("MISSING_PEER", "vault: db audit sink requires a storage writer");
195
+ }
196
+ return {
197
+ kind: "db",
198
+ append(entry) {
199
+ return writer.appendAuditEntry(entry);
200
+ },
201
+ };
202
+ }
203
+
204
+ const write = opts.write ?? ((line: string) => void process.stdout.write(`${line}\n`));
205
+ const now = opts.now ?? (() => new Date());
206
+ return {
207
+ kind: "stdout",
208
+ async append(entry) {
209
+ // Only normalized metadata is serialized — an entry never holds a value.
210
+ write(
211
+ JSON.stringify({
212
+ sink: "oke.vault.audit",
213
+ ...toAuditHashPayload(entry, entry.at ?? now()),
214
+ }),
215
+ );
216
+ },
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Sink that drops every entry (`audit.enabled: false`).
222
+ *
223
+ * Reports `kind: "stdout"` because {@link VaultAuditSinkKind} has no
224
+ * "off" member — callers gate on config, not on this field.
225
+ */
226
+ export function createNullAuditSink(): AuditSink {
227
+ return {
228
+ kind: "stdout",
229
+ async append() {
230
+ // Intentionally empty — auditing is disabled.
231
+ },
232
+ };
233
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Vault boot chain — managed provider wiring and driver-id normalization.
3
+ */
4
+
5
+ import { afterEach, describe, expect, test } from "bun:test";
6
+ import type { VaultOpenOptions } from "../../drivers/vault-types.ts";
7
+ import { buildVaultBootChain, normalizeVaultDriverId } from "./boot-chain.ts";
8
+
9
+ /** Env keys these tests own. */
10
+ const OWNED = [
11
+ "OKE_VAULT_PROVIDER",
12
+ "OKE_VAULT_URL",
13
+ "OKE_VAULT_TOKEN",
14
+ "OKE_VAULT_MOUNT",
15
+ "OKE_VAULT_REGION",
16
+ ] as const;
17
+
18
+ afterEach(() => {
19
+ for (const key of OWNED) delete process.env[key];
20
+ });
21
+
22
+ describe("managed vault boot chain", () => {
23
+ test("no provider stays a single platform-injected layer", () => {
24
+ const chain = buildVaultBootChain({ driverId: "managed", env: "test", seed: { A: "1" } });
25
+ expect(chain).toHaveLength(1);
26
+ expect(chain[0]?.driver.id).toBe("managed");
27
+ });
28
+
29
+ test("a provider puts the env layers in front of the managed layer", () => {
30
+ process.env.OKE_VAULT_PROVIDER = "aws-secrets-manager";
31
+ process.env.OKE_VAULT_REGION = "eu-central-1";
32
+ process.env.OKE_VAULT_MOUNT = "oke/prod/";
33
+
34
+ const chain = buildVaultBootChain({ driverId: "managed", env: "prod" });
35
+ expect(chain[0]?.driver.id).toBe("env");
36
+ const last = chain.at(-1);
37
+ expect(last?.driver.id).toBe("managed");
38
+ const options = last?.options as VaultOpenOptions;
39
+ expect(options.provider).toBe("aws-secrets-manager");
40
+ expect(options.region).toBe("eu-central-1");
41
+ expect(options.mount).toBe("oke/prod/");
42
+ });
43
+
44
+ test("aws-secrets-manager carries region and prefix without inventing a mount", () => {
45
+ process.env.OKE_VAULT_PROVIDER = "aws-secrets-manager";
46
+ process.env.OKE_VAULT_REGION = "eu-central-1";
47
+
48
+ const chain = buildVaultBootChain({ driverId: "managed", env: "prod" });
49
+ const options = chain.at(-1)?.options as VaultOpenOptions;
50
+ expect(options.region).toBe("eu-central-1");
51
+ expect(options.mount).toBeUndefined();
52
+ });
53
+
54
+ test("built-in vault sits behind env layers", () => {
55
+ const chain = buildVaultBootChain({ driverId: "vault", env: "prod" });
56
+ expect(chain.map((l) => l.driver.id)).toContain("env");
57
+ expect(chain.at(-1)?.driver.id).toBe("vault");
58
+ });
59
+ });
60
+
61
+ describe("normalizeVaultDriverId", () => {
62
+ test("maps legacy labels onto protocol ids", () => {
63
+ expect(normalizeVaultDriverId("dotenv")).toBe("env");
64
+ expect(normalizeVaultDriverId("builtin")).toBe("vault");
65
+ expect(normalizeVaultDriverId("managed")).toBe("managed");
66
+ expect(normalizeVaultDriverId("vault")).toBe("vault");
67
+ });
68
+
69
+ test("rejects an unknown label", () => {
70
+ expect(() => normalizeVaultDriverId("acme")).toThrow(/unknown vault driver/);
71
+ });
72
+
73
+ test("rejects removed external vault driver id", () => {
74
+ const removed = ["open", "bao"].join("");
75
+ expect(() => normalizeVaultDriverId(removed)).toThrow(/unknown vault driver/);
76
+ expect(() => normalizeVaultDriverId(removed)).toThrow(/aws-secrets-manager/);
77
+ });
78
+ });