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.
- package/package.json +76 -10
- package/site/content/docs/ai/mcp.mdx +0 -1
- package/site/content/docs/deployment/docker-swarm.mdx +48 -111
- package/site/content/docs/deployment/docker.mdx +43 -36
- package/site/content/docs/deployment/index.mdx +9 -9
- package/site/content/docs/deployment/kubernetes.mdx +4 -4
- package/site/content/docs/elements/ai.mdx +7 -13
- package/site/content/docs/elements/channel.mdx +9 -13
- package/site/content/docs/elements/clock.mdx +8 -14
- package/site/content/docs/elements/flow.mdx +46 -54
- package/site/content/docs/elements/gate.mdx +6 -11
- package/site/content/docs/elements/signal.mdx +2 -8
- package/site/content/docs/elements/store.mdx +79 -93
- package/site/content/docs/elements/vault.mdx +89 -76
- package/site/content/docs/get-started/basic-usage.mdx +8 -14
- package/site/content/docs/get-started/installation.mdx +36 -36
- package/site/content/docs/get-started/introduction.mdx +11 -0
- package/site/content/docs/get-started/why.mdx +3 -4
- package/site/content/docs/index.mdx +0 -1
- package/site/content/docs/meta.json +0 -1
- package/site/content/docs/plugins/anonymous.mdx +1 -1
- package/site/content/docs/plugins/magic-link.mdx +11 -11
- package/site/content/docs/plugins/otp.mdx +2 -2
- package/site/content/docs/plugins/passkey.mdx +1 -1
- package/site/content/docs/plugins/two-factor.mdx +1 -1
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +1 -1
- package/site/content/docs/recipes/cockroachdb.mdx +2 -2
- package/site/content/docs/recipes/dragonfly.mdx +1 -1
- package/site/content/docs/recipes/index.mdx +1 -2
- package/site/content/docs/recipes/llama-cpp.mdx +21 -23
- package/site/content/docs/recipes/mailpit.mdx +11 -11
- package/site/content/docs/recipes/meilisearch.mdx +10 -10
- package/site/content/docs/recipes/meta.json +0 -1
- package/site/content/docs/recipes/ollama.mdx +1 -1
- package/site/content/docs/recipes/pgdog.mdx +12 -9
- package/site/content/docs/recipes/postgres.mdx +3 -3
- package/site/content/docs/recipes/redis.mdx +2 -2
- package/site/content/docs/recipes/rustfs.mdx +10 -7
- package/site/content/docs/recipes/sglang.mdx +1 -1
- package/site/content/docs/recipes/supabase-docker.mdx +1 -1
- package/site/content/docs/recipes/timescale.mdx +2 -2
- package/site/content/docs/recipes/valkey.mdx +1 -1
- package/site/content/docs/recipes/vllm.mdx +1 -1
- package/site/content/docs/recipes/yugabytedb.mdx +2 -2
- package/site/content/docs/reference/cli.md +61 -38
- package/site/content/docs/reference/configuration.mdx +145 -70
- package/site/content/docs/reference/environment-variables.mdx +36 -28
- package/site/content/docs/reference/errors.mdx +28 -29
- package/site/content/docs/reference/fx.mdx +14 -7
- package/site/content/docs/reference/i18n.mdx +17 -6
- package/site/content/docs/reference/index.mdx +5 -0
- package/site/content/docs/reference/meta.json +1 -0
- package/site/content/docs/reference/migrating-environments.mdx +158 -0
- package/site/content/docs/reference/plugins.mdx +2 -2
- package/src/auth/gate-auth.test.ts +12 -10
- package/src/cli/ai-setup/ai-setup.test.ts +30 -16
- package/src/cli/ai-setup/apply.ts +111 -15
- package/src/cli/ai-setup/catalog.ts +3 -3
- package/src/cli/ai-setup/index.ts +1 -1
- package/src/cli/ai-setup/prompts.ts +1 -1
- package/src/cli/ai-setup/recommend.test.ts +1 -1
- package/src/cli/ask-seed.test.ts +3 -3
- package/src/cli/build.ts +8 -9
- package/src/cli/db-auto-push.test.ts +6 -3
- package/src/cli/db-auto-push.ts +7 -5
- package/src/cli/db-seed.ts +7 -8
- package/src/cli/db.test.ts +70 -71
- package/src/cli/db.ts +86 -12
- package/src/cli/dev-controller.test.ts +35 -0
- package/src/cli/dev-controller.ts +298 -0
- package/src/cli/dev-controls.ts +11 -65
- package/src/cli/dev-db-push.test.ts +47 -15
- package/src/cli/dev-schema-sync.test.ts +11 -11
- package/src/cli/dev-schema-sync.ts +10 -10
- package/src/cli/dev-session-lock.test.ts +106 -0
- package/src/cli/dev-session-lock.ts +209 -0
- package/src/cli/dev.test.ts +246 -114
- package/src/cli/dev.ts +153 -205
- package/src/cli/doc-staleness.test.ts +3 -6
- package/src/cli/docker-clean.test.ts +33 -1
- package/src/cli/docker-clean.ts +28 -33
- package/src/cli/docker-cli.test.ts +12 -11
- package/src/cli/docker.ts +95 -20
- package/src/cli/doctor.test.ts +40 -0
- package/src/cli/doctor.ts +142 -9
- package/src/cli/drizzle-env.test.ts +11 -13
- package/src/cli/drizzle-env.ts +18 -11
- package/src/cli/ensure-drizzle-config.ts +11 -14
- package/src/cli/hero-meta.test.ts +15 -15
- package/src/cli/hero-meta.ts +10 -9
- package/src/cli/index.ts +36 -6
- package/src/cli/load-config.images.test.ts +44 -10
- package/src/cli/load-config.source.test.ts +50 -0
- package/src/cli/load-config.ts +35 -29
- package/src/cli/meilisearch-local.ts +1 -2
- package/src/cli/mode.ts +7 -58
- package/src/cli/registry.ts +175 -19
- package/src/cli/resolve-dev-sql-env.test.ts +8 -16
- package/src/cli/resolve-dev-sql-env.ts +13 -15
- package/src/cli/safe-defaults.test.ts +29 -11
- package/src/cli/schema.ts +65 -16
- package/src/cli/test.ts +58 -0
- package/src/cli/tui/App.tsx +252 -0
- package/src/cli/tui/DevLive.tsx +116 -0
- package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
- package/src/cli/tui/components/HeaderBar.tsx +67 -0
- package/src/cli/tui/components/LogStream.tsx +47 -0
- package/src/cli/tui/components/PanelCard.tsx +39 -0
- package/src/cli/tui/components/SelectRow.tsx +37 -0
- package/src/cli/tui/components/SlashPalette.tsx +172 -0
- package/src/cli/tui/components/Spinner.tsx +32 -0
- package/src/cli/tui/components/StatusBar.tsx +51 -0
- package/src/cli/tui/components/StatusDot.tsx +37 -0
- package/src/cli/tui/components/TabBar.tsx +49 -0
- package/src/cli/tui/components/Wordmark.tsx +43 -0
- package/src/cli/tui/format.ts +38 -0
- package/src/cli/tui/keys.test.ts +18 -0
- package/src/cli/tui/keys.ts +26 -0
- package/src/cli/tui/launch.test.ts +18 -0
- package/src/cli/tui/launch.ts +57 -0
- package/src/cli/tui/panels/Dashboard.tsx +201 -0
- package/src/cli/tui/panels/Database.tsx +198 -0
- package/src/cli/tui/panels/Dev.tsx +257 -0
- package/src/cli/tui/panels/Docker.tsx +357 -0
- package/src/cli/tui/panels/Navigator.test.ts +22 -0
- package/src/cli/tui/panels/Navigator.tsx +213 -0
- package/src/cli/tui/slash-catalog.test.ts +31 -0
- package/src/cli/tui/slash-catalog.ts +184 -0
- package/src/cli/tui/slash-run.ts +54 -0
- package/src/cli/tui/state/ports.ts +95 -0
- package/src/cli/tui/theme.ts +24 -0
- package/src/cli/vault-cmd.test.ts +402 -0
- package/src/cli/vault-cmd.ts +727 -69
- package/src/cli/vault-secure-input.test.ts +56 -0
- package/src/cli/vault-secure-input.ts +124 -0
- package/src/compiler/effects-infer.ts +7 -0
- package/src/compiler/extract.ts +12 -0
- package/src/compiler/fixtures/skyport/oke.config.ts +7 -5
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
- package/src/compiler/fixtures/skyport.expected.json +1 -1
- package/src/compiler/generate-adopt.ts +27 -1
- package/src/compiler-entry.ts +7 -0
- package/src/config/define-config.test.ts +111 -47
- package/src/config/driver-defaults.test.ts +115 -0
- package/src/config/driver-defaults.ts +156 -0
- package/src/config/index.ts +246 -131
- package/src/config/resolve-driver.test.ts +14 -19
- package/src/console/server/app.ts +4 -2
- package/src/console/server/console.test.ts +58 -1
- package/src/console/server/flows.ts +22 -2
- package/src/console/server/index.ts +3 -0
- package/src/console/server/operator-db.test.ts +55 -13
- package/src/console/server/operator-db.ts +305 -263
- package/src/console/server/serve.ts +16 -7
- package/src/console/server/state.ts +31 -9
- package/src/console/server/vault.test.ts +120 -5
- package/src/console/server/vault.ts +208 -12
- package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
- package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
- package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
- package/src/console/ui/dist/index.html +2 -2
- package/src/console/ui/shell/client.ts +16 -0
- package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
- package/src/console/ui/vault/VaultA11yView.tsx +33 -1
- package/src/console/ui/vault/backend.test.ts +75 -0
- package/src/console/ui/vault/backend.ts +117 -0
- package/src/console/ui/vault/fixture.ts +21 -2
- package/src/console/ui/vault/index.ts +11 -1
- package/src/console/ui/vault/types.ts +26 -0
- package/src/docker/compose.ts +164 -61
- package/src/docker/derive.ts +106 -12
- package/src/docker/docker.test.ts +156 -110
- package/src/docker/helpers.ts +98 -0
- package/src/docker/images-config.test.ts +163 -0
- package/src/docker/index.ts +30 -5
- package/src/docker/recipes/index.ts +0 -3
- package/src/docker/recipes/llama-cpp.ts +4 -4
- package/src/docker/recipes/pgdog.ts +9 -2
- package/src/docker/resources.test.ts +71 -0
- package/src/docker/resources.ts +176 -0
- package/src/docker/stack-id.test.ts +11 -4
- package/src/docker/stack-id.ts +42 -32
- package/src/docker/stack.integration.test.ts +4 -15
- package/src/docker/types.ts +35 -3
- package/src/drivers/ai-ollama.integration.test.ts +2 -2
- package/src/drivers/conformance.test.ts +11 -12
- package/src/drivers/drizzle-dialect.test.ts +0 -4
- package/src/drivers/drizzle-dialect.ts +7 -13
- package/src/drivers/index.ts +24 -7
- package/src/drivers/journal-postgres.test.ts +3 -1
- package/src/drivers/journal-postgres.ts +4 -2
- package/src/drivers/meilisearch.ts +1 -1
- package/src/drivers/pglite.ts +28 -5
- package/src/drivers/types.ts +9 -11
- package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
- package/src/drivers/vault-aws-secrets-manager.ts +333 -0
- package/src/drivers/vault-builtin.test.ts +110 -0
- package/src/drivers/vault-builtin.ts +184 -0
- package/src/drivers/vault-driver-removal.test.ts +13 -0
- package/src/drivers/vault-managed.test.ts +95 -0
- package/src/drivers/vault-managed.ts +101 -25
- package/src/drivers/vault-types.ts +25 -5
- package/src/elements/ai/runtime.ts +1 -1
- package/src/elements/clock/chaos.test.ts +4 -1
- package/src/elements/clock/durable.ts +57 -4
- package/src/elements/clock.test.ts +13 -14
- package/src/elements/gate/constants.ts +18 -0
- package/src/elements/gate.test.ts +1 -1
- package/src/elements/gate.ts +2 -2
- package/src/elements/store/domain-ddl.test.ts +5 -12
- package/src/elements/store/emit-drizzle.ts +44 -19
- package/src/elements/store/index-boot.test.ts +15 -116
- package/src/elements/store/load-plugin-tables.ts +1 -1
- package/src/elements/store/resource-list-docs.fixture.ts +12 -6
- package/src/elements/store/resource-list-docs.test.ts +64 -44
- package/src/elements/store/resource.test.ts +17 -10
- package/src/elements/store/runtime.ts +9 -12
- package/src/elements/store/schema-decl.test.ts +16 -32
- package/src/elements/store/seed.test.ts +1 -2
- package/src/elements/store/seed.ts +7 -8
- package/src/elements/store/sql-condition.test.ts +2 -2
- package/src/elements/store/sql-session.test.ts +8 -10
- package/src/elements/store/sql-session.ts +10 -2
- package/src/elements/store.test.ts +13 -13
- package/src/elements/vault/audit.ts +233 -0
- package/src/elements/vault/boot-chain.test.ts +78 -0
- package/src/elements/vault/boot-chain.ts +43 -39
- package/src/elements/vault/builtin-adapter.test.ts +479 -0
- package/src/elements/vault/builtin-adapter.ts +1184 -0
- package/src/elements/vault/crypto.test.ts +237 -0
- package/src/elements/vault/crypto.ts +431 -0
- package/src/elements/vault/declare.ts +164 -3
- package/src/elements/vault/errors.ts +56 -0
- package/src/elements/vault/kms-unseal.test.ts +76 -0
- package/src/elements/vault/kms-unseal.ts +181 -0
- package/src/elements/vault/path.ts +90 -0
- package/src/elements/vault/resilience.test.ts +102 -0
- package/src/elements/vault/resilience.ts +107 -0
- package/src/elements/vault/runtime.ts +36 -4
- package/src/elements/vault/security-checklist.test.ts +130 -0
- package/src/elements/vault/storage.ts +448 -0
- package/src/elements/vault/types.ts +288 -0
- package/src/elements/vault/unseal.ts +150 -0
- package/src/elements/vault.test.ts +72 -4
- package/src/elements/vault.ts +91 -2
- package/src/full.ts +248 -0
- package/src/http.ts +41 -0
- package/src/i18n/interpolate-simple.ts +37 -0
- package/src/i18n/messages.ts +51 -4
- package/src/i18n-entry.ts +7 -0
- package/src/index.ts +35 -145
- package/src/journal-entry.ts +20 -0
- package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
- package/src/kernel/app.ts +40 -42
- package/src/kernel/auto-registry.test.ts +2 -2
- package/src/kernel/boot-bind/ai.test.ts +5 -5
- package/src/kernel/boot-bind/ai.ts +2 -1
- package/src/kernel/boot-bind/channel.test.ts +6 -6
- package/src/kernel/boot-bind/channel.ts +5 -4
- package/src/kernel/boot-bind/clock.ts +5 -5
- package/src/kernel/boot-bind/gate.ts +6 -7
- package/src/kernel/boot-bind/honor-config.test.ts +42 -42
- package/src/kernel/boot-bind/journal.ts +5 -5
- package/src/kernel/boot-bind/signal.ts +3 -1
- package/src/kernel/boot-bind/store.test.ts +55 -37
- package/src/kernel/boot-bind/store.ts +24 -34
- package/src/kernel/boot-bind/vault.ts +19 -4
- package/src/kernel/boot.test.ts +12 -4
- package/src/kernel/boot.ts +31 -19
- package/src/kernel/budget-entry.ts +1 -1
- package/src/kernel/compensate.test.ts +288 -1
- package/src/kernel/compensate.ts +126 -0
- package/src/kernel/concurrency.test.ts +10 -9
- package/src/kernel/effects-stamping.test.ts +27 -2
- package/src/kernel/element-registries.ts +5 -3
- package/src/kernel/flow.ts +4 -2
- package/src/kernel/fx.test.ts +119 -3
- package/src/kernel/fx.ts +215 -40
- package/src/kernel/index.ts +14 -0
- package/src/kernel/journal.ts +130 -7
- package/src/kernel/pipeline.test.ts +20 -6
- package/src/kernel/redacted.test.ts +35 -0
- package/src/kernel/redacted.ts +24 -2
- package/src/kernel/router/create-default.ts +16 -0
- package/src/kernel/router/create-edge.ts +18 -0
- package/src/kernel/router/create.ts +23 -0
- package/src/kernel/router/index.ts +23 -0
- package/src/kernel/router/linear.ts +88 -0
- package/src/kernel/router/regexp.ts +188 -0
- package/src/kernel/router/smart.ts +120 -0
- package/src/kernel/router/trie.ts +108 -0
- package/src/kernel/router/types.ts +56 -0
- package/src/kernel/router.ts +18 -532
- package/src/kernel-entry.ts +43 -0
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/fixtures/skyport.manifest.json +1 -1
- package/src/plugins/config-source.test.ts +7 -7
- package/src/release/build-lib.ts +58 -0
- package/src/release/exports.test.ts +4 -2
- package/src/release/exports.ts +30 -4
- package/src/release/measure.ts +101 -22
- package/src/runs/duckdb.ts +7 -2
- package/src/shared/optional-peer.ts +41 -0
- package/src/term.test.ts +3 -44
- package/src/test/create-test-app.ts +12 -0
- package/src/test/provisions.integration.test.ts +1 -1
- package/src/test/reset-element-registries.ts +2 -0
- package/src/upgrade/codemods.test.ts +7 -20
- package/src/upgrade/codemods.ts +93 -60
- package/site/content/docs/console/access.mdx +0 -29
- package/site/content/docs/console/ai.mdx +0 -35
- package/site/content/docs/console/architecture.mdx +0 -35
- package/site/content/docs/console/channels.mdx +0 -37
- package/site/content/docs/console/clock.mdx +0 -51
- package/site/content/docs/console/flows.mdx +0 -31
- package/site/content/docs/console/gates.mdx +0 -73
- package/site/content/docs/console/index.mdx +0 -54
- package/site/content/docs/console/manifest-diff.mdx +0 -34
- package/site/content/docs/console/meta.json +0 -24
- package/site/content/docs/console/overview.mdx +0 -40
- package/site/content/docs/console/plugins.mdx +0 -41
- package/site/content/docs/console/privacy.mdx +0 -32
- package/site/content/docs/console/runs.mdx +0 -50
- package/site/content/docs/console/signals.mdx +0 -40
- package/site/content/docs/console/store.mdx +0 -32
- package/site/content/docs/console/tenancy.mdx +0 -32
- package/site/content/docs/console/traces.mdx +0 -38
- package/site/content/docs/console/vault.mdx +0 -41
- package/site/content/docs/recipes/openbao.mdx +0 -141
- package/src/cli/ask-dev-mode.ts +0 -40
- package/src/cli/dev-mode.test.ts +0 -45
- package/src/cli/dev-mode.ts +0 -78
- package/src/cli/openbao-bootstrap.test.ts +0 -150
- package/src/cli/openbao-bootstrap.ts +0 -321
- package/src/cli/openbao-restart.integration.test.ts +0 -145
- package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
- package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
- package/src/docker/recipes/openbao.ts +0 -47
- package/src/drivers/libsql.ts +0 -179
- package/src/drivers/sqlite.ts +0 -44
- package/src/drivers/vault-openbao.test.ts +0 -97
- package/src/drivers/vault-openbao.ts +0 -143
|
@@ -40,8 +40,8 @@ export interface ServeConsoleOptions extends CreateConsoleAppOptions {
|
|
|
40
40
|
/** Boot environment — use `"dev"` / `"prod"` so Bearer auth is production-like. */
|
|
41
41
|
readonly env?: "dev" | "prod" | "test";
|
|
42
42
|
/**
|
|
43
|
-
* Persist operators
|
|
44
|
-
* Set false for ephemeral test servers.
|
|
43
|
+
* Persist operators in Postgres `oke_console` (+ `.oke/console.secret`).
|
|
44
|
+
* Default true. Set false for ephemeral test servers.
|
|
45
45
|
*/
|
|
46
46
|
readonly persist?: boolean;
|
|
47
47
|
}
|
|
@@ -83,9 +83,18 @@ export async function serveConsole(
|
|
|
83
83
|
: {}),
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
// Console serve `
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
// Console serve defaults to `test` drivers when compose isn't up; pass
|
|
87
|
+
// `env: "dev"` only with compose URLs (or memory overrides) available.
|
|
88
|
+
// Never inherit `OKE_DOCKER=1` from `oke dev` into capability minting —
|
|
89
|
+
// Console operator flows don't stamp Manifest effects, and docker+dev
|
|
90
|
+
// would otherwise hard-fail with OKE1008 on the first undeclared flow
|
|
91
|
+
// (`console.setup.status`). Compose URLs for Manifest panels bind via
|
|
92
|
+
// `bindManifest*` below, not this boot's driver map.
|
|
93
|
+
const env = options.env ?? "test";
|
|
94
|
+
await handle.app.boot({
|
|
95
|
+
env: env === "dev" || env === "test" || env === "prod" ? env : "test",
|
|
96
|
+
docker: false,
|
|
97
|
+
});
|
|
89
98
|
handle.state.listRuns = async () => {
|
|
90
99
|
const runs = handle.app.bootResult?.runs;
|
|
91
100
|
if (!runs) return [];
|
|
@@ -182,7 +191,7 @@ export async function serveConsole(
|
|
|
182
191
|
stop(closeActive = false) {
|
|
183
192
|
server.stop(closeActive);
|
|
184
193
|
void handle.app.stop();
|
|
185
|
-
persistence?.close();
|
|
194
|
+
void persistence?.close();
|
|
186
195
|
},
|
|
187
196
|
};
|
|
188
197
|
}
|
|
@@ -195,7 +204,7 @@ export async function serveConsole(
|
|
|
195
204
|
export async function startConsoleApp(
|
|
196
205
|
options: CreateConsoleAppOptions & {
|
|
197
206
|
readonly env?: "dev" | "prod" | "test";
|
|
198
|
-
/** Opt-in durable operators
|
|
207
|
+
/** Opt-in durable operators in `oke_console` (default false for unit tests). */
|
|
199
208
|
readonly persist?: boolean;
|
|
200
209
|
} = {},
|
|
201
210
|
): Promise<ConsoleAppHandle> {
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
type SimulateGatesInput,
|
|
59
59
|
type SimulateGatesResult,
|
|
60
60
|
} from "./gates.ts";
|
|
61
|
-
import type { ConsoleVaultRow, VaultWriteInput } from "./vault.ts";
|
|
61
|
+
import type { ConsoleVaultBackend, ConsoleVaultRow, VaultWriteInput } from "./vault.ts";
|
|
62
62
|
import type { ChannelInbox } from "../../drivers/channel-types.ts";
|
|
63
63
|
import type { ChannelRuntime, TemplateCatalog } from "../../elements/channel.ts";
|
|
64
64
|
import type { ConsoleChannelPreview, ConsoleChannelsList, EmailAuthResult } from "./channels.ts";
|
|
@@ -68,6 +68,9 @@ import type { ConsolePluginsList } from "./plugins.ts";
|
|
|
68
68
|
import type { ConsoleDiffProjection } from "./diff.ts";
|
|
69
69
|
import { loadConsolePanel, type ConsolePanelId } from "./panel-load.ts";
|
|
70
70
|
|
|
71
|
+
/** How long a built-in Vault backend probe is reused across list polls. */
|
|
72
|
+
const VAULT_BACKEND_TTL_MS = 15_000;
|
|
73
|
+
|
|
71
74
|
/** User-plane identity row for the Flows invoke-as picker. */
|
|
72
75
|
export interface ConsoleIdentity {
|
|
73
76
|
readonly id: string;
|
|
@@ -251,6 +254,7 @@ export interface ConsoleState {
|
|
|
251
254
|
listVault: () => Promise<{
|
|
252
255
|
readonly secrets: readonly ConsoleVaultRow[];
|
|
253
256
|
readonly env: string;
|
|
257
|
+
readonly backend: ConsoleVaultBackend | null;
|
|
254
258
|
}>;
|
|
255
259
|
/** Set a vault value (write-only). */
|
|
256
260
|
setVault: (
|
|
@@ -328,15 +332,15 @@ export interface ConsoleState {
|
|
|
328
332
|
/** Whether first operator exists (wizard permanently closed). */
|
|
329
333
|
get setupClosed(): boolean;
|
|
330
334
|
/**
|
|
331
|
-
* Persist an operator after claim/create (
|
|
335
|
+
* Persist an operator after claim/create (`oke_console` schema).
|
|
332
336
|
* No-op when persistence is disabled (tests / memory-only).
|
|
333
337
|
*/
|
|
334
|
-
persistOperator: (operatorId: string) => void
|
|
338
|
+
persistOperator: (operatorId: string) => void | Promise<void>;
|
|
335
339
|
/**
|
|
336
|
-
* Persist sessions after issue / revoke (
|
|
340
|
+
* Persist sessions after issue / revoke (`oke_console` schema).
|
|
337
341
|
* No-op when persistence is disabled (tests / memory-only).
|
|
338
342
|
*/
|
|
339
|
-
persistSessions: () => void
|
|
343
|
+
persistSessions: () => void | Promise<void>;
|
|
340
344
|
/**
|
|
341
345
|
* Per-email login attempt timestamps for credential-check rate limiting
|
|
342
346
|
* (console §10.4 — same 5/60s strategy as setup-claim).
|
|
@@ -390,14 +394,14 @@ export interface CreateConsoleStateOptions {
|
|
|
390
394
|
readonly okeConfig?: OkeConfig | null;
|
|
391
395
|
/** Host plugin registry for scopes / capabilities. */
|
|
392
396
|
readonly pluginRegistry?: PluginRegistry | null;
|
|
393
|
-
/** Pre-hydrated operator store (from
|
|
397
|
+
/** Pre-hydrated operator store (from `oke_console` / PGlite fallback). */
|
|
394
398
|
readonly operators?: OperatorStore;
|
|
395
|
-
/** Pre-hydrated session store (from
|
|
399
|
+
/** Pre-hydrated session store (from `oke_console` / PGlite fallback). */
|
|
396
400
|
readonly sessions?: SessionStore;
|
|
397
401
|
/** Persist hook after claim/create. */
|
|
398
|
-
readonly persistOperator?: (operatorId: string) => void
|
|
402
|
+
readonly persistOperator?: (operatorId: string) => void | Promise<void>;
|
|
399
403
|
/** Persist hook after session issue / revoke. */
|
|
400
|
-
readonly persistSessions?: () => void
|
|
404
|
+
readonly persistSessions?: () => void | Promise<void>;
|
|
401
405
|
}
|
|
402
406
|
|
|
403
407
|
/**
|
|
@@ -427,6 +431,23 @@ export function createConsoleState(options: CreateConsoleStateOptions = {}): Con
|
|
|
427
431
|
return loadConsolePanel<T>(id);
|
|
428
432
|
};
|
|
429
433
|
|
|
434
|
+
// The Vault panel polls; probing the built-in backend opens a SQL
|
|
435
|
+
// connection, so hold the answer for a beat rather than per refresh.
|
|
436
|
+
let backendMemo: { readonly at: number; readonly value: ConsoleVaultBackend | null } | null =
|
|
437
|
+
null;
|
|
438
|
+
const vaultBackend = async (
|
|
439
|
+
vault: typeof import("./vault.ts"),
|
|
440
|
+
): Promise<ConsoleVaultBackend | null> => {
|
|
441
|
+
const at = state.now();
|
|
442
|
+
if (backendMemo && at - backendMemo.at < VAULT_BACKEND_TTL_MS) return backendMemo.value;
|
|
443
|
+
const value = await vault.probeVaultBackend({
|
|
444
|
+
config: state.okeConfig,
|
|
445
|
+
env: state.production ? "prod" : "dev",
|
|
446
|
+
});
|
|
447
|
+
backendMemo = { at, value };
|
|
448
|
+
return value;
|
|
449
|
+
};
|
|
450
|
+
|
|
430
451
|
const state: ConsoleState = {
|
|
431
452
|
operators,
|
|
432
453
|
sessions,
|
|
@@ -733,6 +754,7 @@ export function createConsoleState(options: CreateConsoleStateOptions = {}): Con
|
|
|
733
754
|
journal: state.journalStore,
|
|
734
755
|
env: state.vaultEnv,
|
|
735
756
|
now: state.now,
|
|
757
|
+
backend: await vaultBackend(vault),
|
|
736
758
|
});
|
|
737
759
|
},
|
|
738
760
|
setVault: async (input) => {
|
|
@@ -10,11 +10,15 @@ import type { Manifest } from "../../manifest/types.ts";
|
|
|
10
10
|
import {
|
|
11
11
|
assertNoSecretLeak,
|
|
12
12
|
blastRadiusOf,
|
|
13
|
+
probeVaultBackend,
|
|
13
14
|
projectVaultList,
|
|
14
15
|
readersOf,
|
|
16
|
+
resolveVaultDriverId,
|
|
15
17
|
rotateVaultValue,
|
|
16
18
|
setVaultValue,
|
|
19
|
+
toConsoleVaultStatus,
|
|
17
20
|
} from "./vault.ts";
|
|
21
|
+
import type { VaultStatus } from "../../elements/vault/types.ts";
|
|
18
22
|
|
|
19
23
|
const SECRET = "sk_live_console_vault_test_do_not_leak";
|
|
20
24
|
|
|
@@ -75,13 +79,13 @@ describe("projectVaultList", () => {
|
|
|
75
79
|
const { secrets, env } = await projectVaultList({
|
|
76
80
|
manifest: manifest(),
|
|
77
81
|
runtime: rt,
|
|
78
|
-
env: "
|
|
82
|
+
env: "test",
|
|
79
83
|
peerFingerprints: {
|
|
80
84
|
STRIPE_KEY: { staging: fingerprintSecretSync(SECRET) },
|
|
81
85
|
},
|
|
82
86
|
});
|
|
83
87
|
|
|
84
|
-
expect(env).toBe("
|
|
88
|
+
expect(env).toBe("test");
|
|
85
89
|
const stripe = secrets.find((s) => s.name === "STRIPE_KEY");
|
|
86
90
|
const pub = secrets.find((s) => s.name === "PUBLIC_APP_URL");
|
|
87
91
|
expect(stripe?.sensitive).toBe(true);
|
|
@@ -124,7 +128,7 @@ describe("projectVaultList", () => {
|
|
|
124
128
|
const { secrets } = await projectVaultList({
|
|
125
129
|
manifest: null,
|
|
126
130
|
runtime: rt,
|
|
127
|
-
env: "
|
|
131
|
+
env: "test",
|
|
128
132
|
});
|
|
129
133
|
const row = secrets.find((s) => s.name === "KEY");
|
|
130
134
|
expect(row?.winner).toBe(".env.local");
|
|
@@ -141,7 +145,7 @@ describe("projectVaultList", () => {
|
|
|
141
145
|
const { secrets } = await projectVaultList({
|
|
142
146
|
manifest: manifest(),
|
|
143
147
|
runtime: rt,
|
|
144
|
-
env: "
|
|
148
|
+
env: "test",
|
|
145
149
|
});
|
|
146
150
|
expect(secrets.find((s) => s.name === "STRIPE_KEY")?.lastReadAt).toBe(1_700_000_000_000);
|
|
147
151
|
});
|
|
@@ -202,13 +206,124 @@ describe("projectVaultList", () => {
|
|
|
202
206
|
manifest: manifest(),
|
|
203
207
|
runtime: rt,
|
|
204
208
|
journal,
|
|
205
|
-
env: "
|
|
209
|
+
env: "test",
|
|
206
210
|
now: () => now,
|
|
207
211
|
});
|
|
208
212
|
expect(secrets.find((s) => s.name === "STRIPE_KEY")?.blastRadius.count).toBe(2);
|
|
209
213
|
});
|
|
210
214
|
});
|
|
211
215
|
|
|
216
|
+
describe("backend badge", () => {
|
|
217
|
+
const builtinStatus: VaultStatus = {
|
|
218
|
+
sealed: true,
|
|
219
|
+
initialized: true,
|
|
220
|
+
masterKeyPresent: true,
|
|
221
|
+
kekVersion: 2,
|
|
222
|
+
secretCount: 3,
|
|
223
|
+
sealCount: 1,
|
|
224
|
+
lastSealedAt: new Date(1_700_000_100_000),
|
|
225
|
+
lastUnsealedAt: new Date(1_700_000_000_000),
|
|
226
|
+
rewrapTargetKekVersion: 3,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
test("driver id comes from config, falling back to framework defaults", () => {
|
|
230
|
+
expect(resolveVaultDriverId(null, "dev")).toBe("env");
|
|
231
|
+
expect(resolveVaultDriverId(null, "test")).toBe("memory");
|
|
232
|
+
expect(resolveVaultDriverId(null, "prod")).toBe("vault");
|
|
233
|
+
expect(resolveVaultDriverId({ drivers: { vault: { dev: "managed" } } }, "dev")).toBe("managed");
|
|
234
|
+
expect(resolveVaultDriverId({ drivers: { vault: { dev: "builtin" } } }, "dev")).toBe("vault");
|
|
235
|
+
// An unknown id is a boot concern — the Console still renders.
|
|
236
|
+
expect(resolveVaultDriverId({ drivers: { vault: { dev: "nope" } } }, "dev")).toBe("env");
|
|
237
|
+
expect(
|
|
238
|
+
resolveVaultDriverId({ drivers: { vault: { dev: ["open", "bao"].join("") } } }, "dev"),
|
|
239
|
+
).toBe("env");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("non-builtin driver reports its id and no status", async () => {
|
|
243
|
+
const backend = await probeVaultBackend({
|
|
244
|
+
config: { drivers: { vault: { dev: "managed" } } },
|
|
245
|
+
env: "dev",
|
|
246
|
+
processEnv: {},
|
|
247
|
+
});
|
|
248
|
+
expect(backend).toEqual({
|
|
249
|
+
driverId: "managed",
|
|
250
|
+
builtin: false,
|
|
251
|
+
status: null,
|
|
252
|
+
unavailable: null,
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("builtin status is JSON-safe and sealed follows the master key", async () => {
|
|
257
|
+
const sealed = await probeVaultBackend({
|
|
258
|
+
config: { drivers: { vault: { dev: "vault" } } },
|
|
259
|
+
env: "dev",
|
|
260
|
+
processEnv: {},
|
|
261
|
+
loadStatus: async () => builtinStatus,
|
|
262
|
+
});
|
|
263
|
+
expect(sealed.builtin).toBe(true);
|
|
264
|
+
expect(sealed.status).toEqual({
|
|
265
|
+
...toConsoleVaultStatus(builtinStatus),
|
|
266
|
+
sealed: true,
|
|
267
|
+
});
|
|
268
|
+
expect(sealed.status?.lastUnsealedAt).toBe(1_700_000_000_000);
|
|
269
|
+
expect(sealed.status?.rewrapTargetKekVersion).toBe(3);
|
|
270
|
+
|
|
271
|
+
const unsealed = await probeVaultBackend({
|
|
272
|
+
config: { drivers: { vault: { dev: "vault" } } },
|
|
273
|
+
env: "dev",
|
|
274
|
+
processEnv: { OKE_VAULT_MASTER_KEY: "a-base64-key" },
|
|
275
|
+
loadStatus: async () => builtinStatus,
|
|
276
|
+
});
|
|
277
|
+
expect(unsealed.status?.sealed).toBe(false);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("an unreachable builtin backend degrades to a reason, never a throw", async () => {
|
|
281
|
+
const backend = await probeVaultBackend({
|
|
282
|
+
config: { drivers: { vault: { dev: "vault" } } },
|
|
283
|
+
env: "dev",
|
|
284
|
+
processEnv: {},
|
|
285
|
+
loadStatus: async () => {
|
|
286
|
+
throw new Error("relation oke_vault_status does not exist");
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
expect(backend.status).toBeNull();
|
|
290
|
+
expect(backend.unavailable).toContain("oke_vault_status");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("builtin backend with no SQL configured says so instead of spinning PGlite", async () => {
|
|
294
|
+
const backend = await probeVaultBackend({
|
|
295
|
+
config: { drivers: { vault: { dev: "vault" } } },
|
|
296
|
+
env: "dev",
|
|
297
|
+
processEnv: {},
|
|
298
|
+
});
|
|
299
|
+
expect(backend.status).toBeNull();
|
|
300
|
+
expect(backend.unavailable).toContain("DATABASE_URL");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("the projection passes the backend through untouched", async () => {
|
|
304
|
+
const rt = await runtime();
|
|
305
|
+
const backend = await probeVaultBackend({
|
|
306
|
+
config: { drivers: { vault: { dev: "memory" } } },
|
|
307
|
+
env: "dev",
|
|
308
|
+
processEnv: {},
|
|
309
|
+
});
|
|
310
|
+
const listed = await projectVaultList({
|
|
311
|
+
manifest: manifest(),
|
|
312
|
+
runtime: rt,
|
|
313
|
+
env: "test",
|
|
314
|
+
backend,
|
|
315
|
+
});
|
|
316
|
+
expect(listed.backend).toEqual(backend);
|
|
317
|
+
|
|
318
|
+
const unprobed = await projectVaultList({
|
|
319
|
+
manifest: manifest(),
|
|
320
|
+
runtime: rt,
|
|
321
|
+
env: "test",
|
|
322
|
+
});
|
|
323
|
+
expect(unprobed.backend).toBeNull();
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
212
327
|
describe("set / rotate", () => {
|
|
213
328
|
test("set updates fingerprint and never returns value", async () => {
|
|
214
329
|
const rt = await runtime();
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* rotation blast radius are derived — never reimplemented in the UI.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type
|
|
10
|
-
import {
|
|
9
|
+
import { resolveDriverId, type ConfigEnv, type OkeConfig } from "../../config/index.ts";
|
|
10
|
+
import { VAULT_DEFAULTS } from "../../config/driver-defaults.ts";
|
|
11
|
+
import type { VaultDriverId } from "../../drivers/vault-types.ts";
|
|
12
|
+
import { buildVaultBootChain, normalizeVaultDriverId } from "../../elements/vault/boot-chain.ts";
|
|
11
13
|
import {
|
|
12
14
|
createVaultRuntime,
|
|
13
15
|
vault as declareVault,
|
|
@@ -16,6 +18,7 @@ import {
|
|
|
16
18
|
type VaultRuntime,
|
|
17
19
|
type VaultSecretDecl,
|
|
18
20
|
} from "../../elements/vault.ts";
|
|
21
|
+
import type { VaultStatus } from "../../elements/vault/types.ts";
|
|
19
22
|
import type { JournalRun, JournalStore } from "../../kernel/journal.ts";
|
|
20
23
|
import type { Manifest } from "../../manifest/types.ts";
|
|
21
24
|
|
|
@@ -58,11 +61,11 @@ export interface ConsoleVaultRow {
|
|
|
58
61
|
readonly winner: VaultResolutionSource | null;
|
|
59
62
|
/** Full resolution chain for the current environment. */
|
|
60
63
|
readonly resolution: readonly VaultResolutionStep[];
|
|
61
|
-
/** Flow ids that declare `fx.vault(name)` in Manifest effects. */
|
|
64
|
+
/** Flow ids that declare `fx.vault.get(name)` in Manifest effects. */
|
|
62
65
|
readonly readers: readonly string[];
|
|
63
66
|
/** In-flight durable runs that will wake holding a new key after rotate. */
|
|
64
67
|
readonly blastRadius: ConsoleVaultBlastRadius;
|
|
65
|
-
/** Epoch-ms of last `fx.vault` / runtime read; `null` if never read. */
|
|
68
|
+
/** Epoch-ms of last `fx.vault.get` / runtime read; `null` if never read. */
|
|
66
69
|
readonly lastReadAt: number | null;
|
|
67
70
|
/**
|
|
68
71
|
* When the current fingerprint equals another environment's — warning,
|
|
@@ -71,12 +74,63 @@ export interface ConsoleVaultRow {
|
|
|
71
74
|
readonly sharedFingerprintEnvs: readonly VaultEnvLabel[];
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Built-in Vault backend state, JSON-safe (dates become epoch-ms).
|
|
79
|
+
*
|
|
80
|
+
* Mirrors {@link VaultStatus} minus the resume cursor, which is an internal
|
|
81
|
+
* rewrap detail with no operator meaning.
|
|
82
|
+
*/
|
|
83
|
+
export interface ConsoleVaultBuiltinStatus {
|
|
84
|
+
/** Whether `oke vault init` has run against this backend. */
|
|
85
|
+
readonly initialized: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Whether the Console process can open secrets. The built-in store keeps
|
|
88
|
+
* the master key in memory per process, so this is `true` whenever
|
|
89
|
+
* `OKE_VAULT_MASTER_KEY` is absent — even if another process is unsealed.
|
|
90
|
+
*/
|
|
91
|
+
readonly sealed: boolean;
|
|
92
|
+
/** Whether a master-key record exists in the backend. */
|
|
93
|
+
readonly masterKeyPresent: boolean;
|
|
94
|
+
/** KEK generation new writes are wrapped under. */
|
|
95
|
+
readonly kekVersion: number;
|
|
96
|
+
/** Live (non-deleted) secret paths. */
|
|
97
|
+
readonly secretCount: number;
|
|
98
|
+
/** Seals since initialization. */
|
|
99
|
+
readonly sealCount: number;
|
|
100
|
+
/** Epoch-ms of the last seal, or `null`. */
|
|
101
|
+
readonly lastSealedAt: number | null;
|
|
102
|
+
/** Epoch-ms of the last unseal, or `null`. */
|
|
103
|
+
readonly lastUnsealedAt: number | null;
|
|
104
|
+
/** KEK generation an in-flight master rotation is migrating toward. */
|
|
105
|
+
readonly rewrapTargetKekVersion: number | null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Which backend terminates the resolution chain, and its state.
|
|
110
|
+
*
|
|
111
|
+
* Only the built-in `vault` driver has a seal lifecycle to report; every
|
|
112
|
+
* other driver surfaces its id alone so the operator still knows where the
|
|
113
|
+
* last layer reads from.
|
|
114
|
+
*/
|
|
115
|
+
export interface ConsoleVaultBackend {
|
|
116
|
+
/** `drivers.vault` id resolved for the current environment. */
|
|
117
|
+
readonly driverId: VaultDriverId;
|
|
118
|
+
/** Whether {@link driverId} is okengine's own encrypted-at-rest store. */
|
|
119
|
+
readonly builtin: boolean;
|
|
120
|
+
/** Backend status — `null` for non-builtin drivers or when unreachable. */
|
|
121
|
+
readonly status: ConsoleVaultBuiltinStatus | null;
|
|
122
|
+
/** Why {@link status} is missing for a builtin backend; `null` otherwise. */
|
|
123
|
+
readonly unavailable: string | null;
|
|
124
|
+
}
|
|
125
|
+
|
|
74
126
|
/** Options when projecting the vault list. */
|
|
75
127
|
export interface ProjectVaultOptions {
|
|
76
128
|
readonly manifest: Manifest | null;
|
|
77
129
|
readonly runtime: VaultRuntime | null;
|
|
78
130
|
/** Current process environment label. */
|
|
79
131
|
readonly env?: VaultEnvLabel;
|
|
132
|
+
/** Backend badge from {@link probeVaultBackend}; `null` when unprobed. */
|
|
133
|
+
readonly backend?: ConsoleVaultBackend | null;
|
|
80
134
|
/**
|
|
81
135
|
* Optional peer-environment fingerprints (e.g. staging from a remote
|
|
82
136
|
* probe). Never values — fingerprints only.
|
|
@@ -95,8 +149,9 @@ export interface ProjectVaultOptions {
|
|
|
95
149
|
export async function projectVaultList(options: ProjectVaultOptions): Promise<{
|
|
96
150
|
readonly secrets: readonly ConsoleVaultRow[];
|
|
97
151
|
readonly env: VaultEnvLabel;
|
|
152
|
+
readonly backend: ConsoleVaultBackend | null;
|
|
98
153
|
}> {
|
|
99
|
-
const env = options.env ?? "
|
|
154
|
+
const env = options.env ?? "dev";
|
|
100
155
|
const now = options.now ?? (() => Date.now());
|
|
101
156
|
const names = collectNames(options.manifest, options.runtime);
|
|
102
157
|
const journalRuns = options.journal
|
|
@@ -150,7 +205,144 @@ export async function projectVaultList(options: ProjectVaultOptions): Promise<{
|
|
|
150
205
|
});
|
|
151
206
|
}
|
|
152
207
|
|
|
153
|
-
return { secrets, env };
|
|
208
|
+
return { secrets, env, backend: options.backend ?? null };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Environment variable holding the base64 master key. Mirrors the driver's
|
|
213
|
+
* own constant so the probe stays free of a static `drivers/` import.
|
|
214
|
+
*/
|
|
215
|
+
const VAULT_MASTER_KEY_ENV = "OKE_VAULT_MASTER_KEY";
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Resolve `drivers.vault` for an environment, falling back to the framework
|
|
219
|
+
* defaults (`env` in dev, `memory` in test, the built-in store in prod).
|
|
220
|
+
*
|
|
221
|
+
* @param config - Loaded `oke.config`, when available
|
|
222
|
+
* @param env - Active config environment
|
|
223
|
+
*/
|
|
224
|
+
export function resolveVaultDriverId(
|
|
225
|
+
config: OkeConfig | null | undefined,
|
|
226
|
+
env: ConfigEnv,
|
|
227
|
+
): VaultDriverId {
|
|
228
|
+
const raw = resolveDriverId(config?.drivers?.vault, env, VAULT_DEFAULTS);
|
|
229
|
+
if (raw === undefined) return "env";
|
|
230
|
+
try {
|
|
231
|
+
return normalizeVaultDriverId(raw);
|
|
232
|
+
} catch {
|
|
233
|
+
// An unknown id is a boot concern; the Console still renders the list.
|
|
234
|
+
return "env";
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Options for {@link probeVaultBackend}. */
|
|
239
|
+
export interface ProbeVaultBackendOptions {
|
|
240
|
+
/** Loaded `oke.config` — supplies `drivers.vault`. */
|
|
241
|
+
readonly config?: OkeConfig | null;
|
|
242
|
+
/** Active config environment (default `dev`). */
|
|
243
|
+
readonly env?: ConfigEnv;
|
|
244
|
+
/** Process environment for SQL URL / master-key capability (default `process.env`). */
|
|
245
|
+
readonly processEnv?: Readonly<Record<string, string | undefined>>;
|
|
246
|
+
/**
|
|
247
|
+
* Status source. Injected by tests; by default the built-in adapter is
|
|
248
|
+
* opened *sealed* over the configured SQL URL, so the probe never unseals
|
|
249
|
+
* and never writes an unseal audit row.
|
|
250
|
+
*/
|
|
251
|
+
readonly loadStatus?: () => Promise<VaultStatus>;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Report which backend serves the terminal chain layer, plus its state when
|
|
256
|
+
* that backend is the built-in store.
|
|
257
|
+
*
|
|
258
|
+
* The Console never holds the master key on the operator's behalf: the probe
|
|
259
|
+
* reads backend facts only, and reports `sealed` from whether this process
|
|
260
|
+
* has `OKE_VAULT_MASTER_KEY` at all.
|
|
261
|
+
*
|
|
262
|
+
* @param options - Config / env / status source
|
|
263
|
+
*/
|
|
264
|
+
export async function probeVaultBackend(
|
|
265
|
+
options: ProbeVaultBackendOptions = {},
|
|
266
|
+
): Promise<ConsoleVaultBackend> {
|
|
267
|
+
const env = options.env ?? "dev";
|
|
268
|
+
const driverId = resolveVaultDriverId(options.config, env);
|
|
269
|
+
if (driverId !== "vault") {
|
|
270
|
+
return { driverId, builtin: false, status: null, unavailable: null };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const processEnv = options.processEnv ?? process.env;
|
|
274
|
+
const keyHeld = (processEnv[VAULT_MASTER_KEY_ENV] ?? "").trim().length > 0;
|
|
275
|
+
|
|
276
|
+
const load = options.loadStatus ?? defaultBuiltinStatusLoader(processEnv);
|
|
277
|
+
if (!load) {
|
|
278
|
+
return {
|
|
279
|
+
driverId,
|
|
280
|
+
builtin: true,
|
|
281
|
+
status: null,
|
|
282
|
+
unavailable: "No SQL URL configured — set DATABASE_URL or OKE_STORE_SQL_URL",
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const status = await load();
|
|
288
|
+
return {
|
|
289
|
+
driverId,
|
|
290
|
+
builtin: true,
|
|
291
|
+
status: { ...toConsoleVaultStatus(status), sealed: !keyHeld },
|
|
292
|
+
unavailable: null,
|
|
293
|
+
};
|
|
294
|
+
} catch (error) {
|
|
295
|
+
// Uninitialized / unreachable is an ordinary operator state, not a crash.
|
|
296
|
+
return {
|
|
297
|
+
driverId,
|
|
298
|
+
builtin: true,
|
|
299
|
+
status: null,
|
|
300
|
+
unavailable: error instanceof Error ? error.message : String(error),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Convert an adapter {@link VaultStatus} into the JSON-safe Console shape.
|
|
307
|
+
*
|
|
308
|
+
* @param status - Adapter status
|
|
309
|
+
*/
|
|
310
|
+
export function toConsoleVaultStatus(status: VaultStatus): ConsoleVaultBuiltinStatus {
|
|
311
|
+
return {
|
|
312
|
+
initialized: status.initialized,
|
|
313
|
+
sealed: status.sealed,
|
|
314
|
+
masterKeyPresent: status.masterKeyPresent,
|
|
315
|
+
kekVersion: status.kekVersion,
|
|
316
|
+
secretCount: status.secretCount,
|
|
317
|
+
sealCount: status.sealCount,
|
|
318
|
+
lastSealedAt: status.lastSealedAt?.getTime() ?? null,
|
|
319
|
+
lastUnsealedAt: status.lastUnsealedAt?.getTime() ?? null,
|
|
320
|
+
rewrapTargetKekVersion: status.rewrapTargetKekVersion ?? null,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Build the default status loader: open the built-in adapter sealed over the
|
|
326
|
+
* configured SQL URL. Returns `null` when no SQL is configured — spinning up
|
|
327
|
+
* a throwaway PGlite instance would report an empty vault that does not exist.
|
|
328
|
+
*
|
|
329
|
+
* @param processEnv - Environment supplying the SQL URL
|
|
330
|
+
*/
|
|
331
|
+
function defaultBuiltinStatusLoader(
|
|
332
|
+
processEnv: Readonly<Record<string, string | undefined>>,
|
|
333
|
+
): (() => Promise<VaultStatus>) | null {
|
|
334
|
+
const url = processEnv.DATABASE_URL ?? processEnv.OKE_STORE_SQL_URL;
|
|
335
|
+
if (url === undefined || url.trim().length === 0) return null;
|
|
336
|
+
return async () => {
|
|
337
|
+
const { openBuiltinVaultAdapter } = await import("../../drivers/vault-builtin.ts");
|
|
338
|
+
// `masterKey: ""` keeps the adapter sealed — status needs no unseal.
|
|
339
|
+
const opened = await openBuiltinVaultAdapter({ url, env: processEnv, masterKey: "" });
|
|
340
|
+
try {
|
|
341
|
+
return await opened.adapter.status();
|
|
342
|
+
} finally {
|
|
343
|
+
await opened.close();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
154
346
|
}
|
|
155
347
|
|
|
156
348
|
/** Input for set / rotate. */
|
|
@@ -206,6 +398,11 @@ export async function createManifestVaultRuntime(
|
|
|
206
398
|
readonly allowDevFallbacks?: boolean;
|
|
207
399
|
readonly seed?: Readonly<Record<string, string>>;
|
|
208
400
|
readonly now?: () => number;
|
|
401
|
+
/**
|
|
402
|
+
* Backend driver for the terminal layer. Defaults to the built-in
|
|
403
|
+
* `vault` store when `drivers.vault` is not consulted.
|
|
404
|
+
*/
|
|
405
|
+
readonly driverId?: VaultDriverId;
|
|
209
406
|
} = {},
|
|
210
407
|
): Promise<VaultRuntime | null> {
|
|
211
408
|
const entries = Object.entries(manifest.vault ?? {});
|
|
@@ -231,19 +428,18 @@ export async function createManifestVaultRuntime(
|
|
|
231
428
|
});
|
|
232
429
|
|
|
233
430
|
const seed = options.seed ?? {};
|
|
234
|
-
const env = options.env ?? "
|
|
235
|
-
//
|
|
236
|
-
//
|
|
431
|
+
const env = options.env ?? "dev";
|
|
432
|
+
// Soft-fallback to memory when the backend is unreachable — the Console
|
|
433
|
+
// must never fail to list contracts because a vault is missing.
|
|
237
434
|
const runtime = createVaultRuntime({
|
|
238
435
|
secrets,
|
|
239
436
|
allowDevFallbacks: options.allowDevFallbacks ?? env !== "prod",
|
|
240
437
|
now: options.now,
|
|
241
438
|
chain: buildVaultBootChain({
|
|
242
439
|
cwd,
|
|
243
|
-
driverId: "
|
|
244
|
-
env,
|
|
440
|
+
driverId: options.driverId ?? "vault",
|
|
441
|
+
env: env === "dev" || env === "test" || env === "prod" ? env : "dev",
|
|
245
442
|
seed,
|
|
246
|
-
missingOpenbao: "memory",
|
|
247
443
|
}),
|
|
248
444
|
});
|
|
249
445
|
await runtime.boot();
|