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
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault SQL storage — DDL, audit retention, and chain verification.
|
|
3
|
+
*
|
|
4
|
+
* Statements are raw SQL strings against a minimal {@link SqlExec} surface
|
|
5
|
+
* so the same DDL runs on `postgres.js`, PGlite, or any thin wrapper. All
|
|
6
|
+
* DDL is `IF NOT EXISTS` and therefore safe to run on every boot.
|
|
7
|
+
*
|
|
8
|
+
* Ciphertext columns are `bytea`; nothing in this module ever sees a
|
|
9
|
+
* cleartext value or a key.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { AUDIT_GENESIS_HASH, computeAuditRowHash, toAuditHashPayload } from "./audit.ts";
|
|
13
|
+
import type { AuditAction, AuditEntry, AuditWriter } from "./audit.ts";
|
|
14
|
+
import { VaultError } from "./errors.ts";
|
|
15
|
+
import type { VaultErrorCode } from "./errors.ts";
|
|
16
|
+
import type { VaultActorType } from "./types.ts";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Minimal SQL surface the Vault needs.
|
|
20
|
+
*
|
|
21
|
+
* Placeholders are Postgres-style (`$1`, `$2`, …).
|
|
22
|
+
*/
|
|
23
|
+
export interface SqlExec {
|
|
24
|
+
/**
|
|
25
|
+
* Run a statement and return its rows.
|
|
26
|
+
*
|
|
27
|
+
* @param sql - Statement text
|
|
28
|
+
* @param params - Positional parameters
|
|
29
|
+
*/
|
|
30
|
+
query<T>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Run a statement, discarding rows.
|
|
33
|
+
*
|
|
34
|
+
* @param sql - Statement text
|
|
35
|
+
* @param params - Positional parameters
|
|
36
|
+
*/
|
|
37
|
+
execute(sql: string, params?: unknown[]): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Encrypted secret versions. One row per `(path, version)`. */
|
|
41
|
+
export const CREATE_SECRETS_TABLE = `
|
|
42
|
+
CREATE TABLE IF NOT EXISTS oke_vault_secrets (
|
|
43
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
44
|
+
path text NOT NULL,
|
|
45
|
+
encrypted_value bytea NOT NULL,
|
|
46
|
+
iv bytea NOT NULL,
|
|
47
|
+
auth_tag bytea NOT NULL,
|
|
48
|
+
version integer NOT NULL DEFAULT 1,
|
|
49
|
+
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
50
|
+
algorithm text NOT NULL DEFAULT 'aes-256-gcm',
|
|
51
|
+
kek_version integer NOT NULL DEFAULT 1,
|
|
52
|
+
key_context jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
53
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
54
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
55
|
+
expires_at timestamptz,
|
|
56
|
+
deleted_at timestamptz,
|
|
57
|
+
CONSTRAINT oke_vault_secrets_path_version_key UNIQUE (path, version)
|
|
58
|
+
)`;
|
|
59
|
+
|
|
60
|
+
/** Per-secret wrapped DEKs. One live row per secret version. */
|
|
61
|
+
export const CREATE_KEYS_TABLE = `
|
|
62
|
+
CREATE TABLE IF NOT EXISTS oke_vault_keys (
|
|
63
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
64
|
+
secret_id uuid NOT NULL REFERENCES oke_vault_secrets(id) ON DELETE CASCADE,
|
|
65
|
+
encrypted_dek bytea NOT NULL,
|
|
66
|
+
dek_iv bytea NOT NULL,
|
|
67
|
+
dek_auth_tag bytea NOT NULL,
|
|
68
|
+
dek_salt bytea,
|
|
69
|
+
algorithm text NOT NULL DEFAULT 'aes-256-gcm',
|
|
70
|
+
kek_version integer NOT NULL DEFAULT 1,
|
|
71
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
72
|
+
rotated_at timestamptz
|
|
73
|
+
)`;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Tamper-evident operation log. Never holds a secret value.
|
|
77
|
+
*
|
|
78
|
+
* `seq` — not `created_at` — defines chain order. Two appends inside the
|
|
79
|
+
* same millisecond share a timestamp, and `id` is a random uuid, so any
|
|
80
|
+
* `(created_at, id)` ordering can disagree with insertion order and make an
|
|
81
|
+
* intact chain look broken.
|
|
82
|
+
*/
|
|
83
|
+
export const CREATE_AUDIT_TABLE = `
|
|
84
|
+
CREATE TABLE IF NOT EXISTS oke_vault_audit (
|
|
85
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
86
|
+
seq bigserial NOT NULL,
|
|
87
|
+
action text NOT NULL,
|
|
88
|
+
path text,
|
|
89
|
+
actor_type text NOT NULL DEFAULT 'unknown',
|
|
90
|
+
actor_id text,
|
|
91
|
+
success boolean NOT NULL DEFAULT true,
|
|
92
|
+
error_code text,
|
|
93
|
+
error_message text,
|
|
94
|
+
request_id text,
|
|
95
|
+
prev_hash text,
|
|
96
|
+
row_hash text NOT NULL,
|
|
97
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
98
|
+
)`;
|
|
99
|
+
|
|
100
|
+
/** Master-key record. Holds a verification hash, never the key. */
|
|
101
|
+
export const CREATE_MASTER_TABLE = `
|
|
102
|
+
CREATE TABLE IF NOT EXISTS oke_vault_master (
|
|
103
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
104
|
+
key_hash text NOT NULL,
|
|
105
|
+
wrapped_master bytea,
|
|
106
|
+
kms_key_id text,
|
|
107
|
+
kek_version integer NOT NULL DEFAULT 1,
|
|
108
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
109
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
110
|
+
)`;
|
|
111
|
+
|
|
112
|
+
/** Singleton operational state (`id = 1`). */
|
|
113
|
+
export const CREATE_STATUS_TABLE = `
|
|
114
|
+
CREATE TABLE IF NOT EXISTS oke_vault_status (
|
|
115
|
+
id integer PRIMARY KEY DEFAULT 1,
|
|
116
|
+
sealed boolean NOT NULL DEFAULT true,
|
|
117
|
+
initialized boolean NOT NULL DEFAULT false,
|
|
118
|
+
master_key_present boolean NOT NULL DEFAULT false,
|
|
119
|
+
last_sealed_at timestamptz,
|
|
120
|
+
last_unsealed_at timestamptz,
|
|
121
|
+
seal_count integer NOT NULL DEFAULT 0,
|
|
122
|
+
rewrap_checkpoint text,
|
|
123
|
+
rewrap_target_kek_version integer,
|
|
124
|
+
rewrap_key_hash text,
|
|
125
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
126
|
+
CONSTRAINT oke_vault_status_singleton CHECK (id = 1)
|
|
127
|
+
)`;
|
|
128
|
+
|
|
129
|
+
/** Index / seed statements applied after the tables exist. */
|
|
130
|
+
const POST_DDL_STATEMENTS: readonly string[] = [
|
|
131
|
+
`CREATE INDEX IF NOT EXISTS oke_vault_secrets_path_idx ON oke_vault_secrets (path) WHERE deleted_at IS NULL`,
|
|
132
|
+
`CREATE INDEX IF NOT EXISTS oke_vault_secrets_expires_idx ON oke_vault_secrets (expires_at) WHERE expires_at IS NOT NULL`,
|
|
133
|
+
`CREATE INDEX IF NOT EXISTS oke_vault_keys_secret_idx ON oke_vault_keys (secret_id)`,
|
|
134
|
+
`CREATE INDEX IF NOT EXISTS oke_vault_keys_kek_version_idx ON oke_vault_keys (kek_version)`,
|
|
135
|
+
`ALTER TABLE oke_vault_audit ADD COLUMN IF NOT EXISTS seq bigserial NOT NULL`,
|
|
136
|
+
`ALTER TABLE oke_vault_status ADD COLUMN IF NOT EXISTS rewrap_key_hash text`,
|
|
137
|
+
`CREATE INDEX IF NOT EXISTS oke_vault_audit_seq_idx ON oke_vault_audit (seq)`,
|
|
138
|
+
`CREATE INDEX IF NOT EXISTS oke_vault_audit_created_idx ON oke_vault_audit (created_at, id)`,
|
|
139
|
+
`CREATE INDEX IF NOT EXISTS oke_vault_audit_path_idx ON oke_vault_audit (path)`,
|
|
140
|
+
`INSERT INTO oke_vault_status (id) VALUES (1) ON CONFLICT (id) DO NOTHING`,
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
/** Every Vault table in dependency order. */
|
|
144
|
+
export const VAULT_DDL_STATEMENTS: readonly string[] = [
|
|
145
|
+
CREATE_SECRETS_TABLE,
|
|
146
|
+
CREATE_KEYS_TABLE,
|
|
147
|
+
CREATE_AUDIT_TABLE,
|
|
148
|
+
CREATE_MASTER_TABLE,
|
|
149
|
+
CREATE_STATUS_TABLE,
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Create every Vault table, index, and the status singleton.
|
|
154
|
+
*
|
|
155
|
+
* Idempotent — safe to call on every boot.
|
|
156
|
+
*
|
|
157
|
+
* @param db - SQL surface
|
|
158
|
+
* @throws VaultError `BACKEND_ERROR` when the backend rejects the DDL
|
|
159
|
+
*/
|
|
160
|
+
export async function ensureVaultTables(db: SqlExec): Promise<void> {
|
|
161
|
+
for (const sql of [...VAULT_DDL_STATEMENTS, ...POST_DDL_STATEMENTS]) {
|
|
162
|
+
try {
|
|
163
|
+
await db.execute(sql);
|
|
164
|
+
} catch {
|
|
165
|
+
// Never surface the driver error verbatim: it can echo row contents.
|
|
166
|
+
throw new VaultError("BACKEND_ERROR", "vault: failed to ensure vault tables");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Delete audit rows older than `before`.
|
|
173
|
+
*
|
|
174
|
+
* Purging is a deliberate break in the hash chain: the oldest retained row
|
|
175
|
+
* keeps a `prev_hash` that no longer resolves. {@link verifyAuditChain}
|
|
176
|
+
* treats the first retained row as a chain anchor for this reason.
|
|
177
|
+
*
|
|
178
|
+
* @param db - SQL surface
|
|
179
|
+
* @param before - Exclusive upper bound on `created_at`
|
|
180
|
+
* @returns Number of rows removed
|
|
181
|
+
*/
|
|
182
|
+
export async function purgeAuditBefore(db: SqlExec, before: Date): Promise<number> {
|
|
183
|
+
try {
|
|
184
|
+
const rows = await db.query<{ id: string }>(
|
|
185
|
+
`DELETE FROM oke_vault_audit WHERE created_at < $1 RETURNING id`,
|
|
186
|
+
[before],
|
|
187
|
+
);
|
|
188
|
+
return rows.length;
|
|
189
|
+
} catch {
|
|
190
|
+
throw new VaultError("BACKEND_ERROR", "vault: failed to purge audit rows");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Result of {@link purgeExpiredSecrets}. */
|
|
195
|
+
export interface PurgeExpiredResult {
|
|
196
|
+
/** Rows matched (deleted, or counted on dry-run). */
|
|
197
|
+
readonly count: number;
|
|
198
|
+
/** Distinct secret paths among those rows. */
|
|
199
|
+
readonly paths: readonly string[];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Hard-delete secret versions whose `expires_at` is strictly before `before`.
|
|
204
|
+
*
|
|
205
|
+
* Independent of soft-delete (`deleted_at`). Wrapped DEK rows cascade via
|
|
206
|
+
* `oke_vault_keys.secret_id ON DELETE CASCADE`.
|
|
207
|
+
*
|
|
208
|
+
* @param db - SQL surface
|
|
209
|
+
* @param before - Exclusive upper bound on `expires_at`
|
|
210
|
+
* @param dryRun - When true, count only
|
|
211
|
+
*/
|
|
212
|
+
export async function purgeExpiredSecrets(
|
|
213
|
+
db: SqlExec,
|
|
214
|
+
before: Date,
|
|
215
|
+
dryRun: boolean,
|
|
216
|
+
): Promise<PurgeExpiredResult> {
|
|
217
|
+
try {
|
|
218
|
+
const rows = dryRun
|
|
219
|
+
? await db.query<{ path: string }>(
|
|
220
|
+
`SELECT path FROM oke_vault_secrets
|
|
221
|
+
WHERE expires_at IS NOT NULL AND expires_at < $1`,
|
|
222
|
+
[before],
|
|
223
|
+
)
|
|
224
|
+
: await db.query<{ path: string }>(
|
|
225
|
+
`DELETE FROM oke_vault_secrets
|
|
226
|
+
WHERE expires_at IS NOT NULL AND expires_at < $1
|
|
227
|
+
RETURNING path`,
|
|
228
|
+
[before],
|
|
229
|
+
);
|
|
230
|
+
const paths = [...new Set(rows.map((r) => r.path))].sort();
|
|
231
|
+
return { count: rows.length, paths };
|
|
232
|
+
} catch {
|
|
233
|
+
throw new VaultError("BACKEND_ERROR", "vault: failed to purge expired secrets");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Filters for {@link readAuditPage}. */
|
|
238
|
+
export interface AuditPageOptions {
|
|
239
|
+
/** Maximum rows to return, newest first. Defaults to 50. */
|
|
240
|
+
readonly limit?: number;
|
|
241
|
+
/** Restrict to one canonical path. */
|
|
242
|
+
readonly path?: string;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* One audit row as surfaced to an operator.
|
|
247
|
+
*
|
|
248
|
+
* Mirrors the stored columns minus the chain hashes — a row records that
|
|
249
|
+
* something happened to a path, never what the value was.
|
|
250
|
+
*/
|
|
251
|
+
export interface VaultAuditRecord {
|
|
252
|
+
/** Row id. */
|
|
253
|
+
readonly id: string;
|
|
254
|
+
/** Insertion order. */
|
|
255
|
+
readonly seq: number;
|
|
256
|
+
/** What happened. */
|
|
257
|
+
readonly action: AuditAction;
|
|
258
|
+
/** Canonical path, when the operation targeted one. */
|
|
259
|
+
readonly path: string | null;
|
|
260
|
+
/** Actor class. */
|
|
261
|
+
readonly actorType: VaultActorType;
|
|
262
|
+
/** Actor id within its class. */
|
|
263
|
+
readonly actorId: string | null;
|
|
264
|
+
/** Whether the operation succeeded. */
|
|
265
|
+
readonly success: boolean;
|
|
266
|
+
/** Failure reason when `success` is `false`. */
|
|
267
|
+
readonly errorCode: VaultErrorCode | null;
|
|
268
|
+
/** Secret-free failure description. */
|
|
269
|
+
readonly errorMessage: string | null;
|
|
270
|
+
/** Correlation id for the enclosing request/run. */
|
|
271
|
+
readonly requestId: string | null;
|
|
272
|
+
/** Event time. */
|
|
273
|
+
readonly createdAt: Date;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Read the newest audit rows.
|
|
278
|
+
*
|
|
279
|
+
* @param db - SQL surface
|
|
280
|
+
* @param options - Limit / path filter
|
|
281
|
+
*/
|
|
282
|
+
export async function readAuditPage(
|
|
283
|
+
db: SqlExec,
|
|
284
|
+
options: AuditPageOptions = {},
|
|
285
|
+
): Promise<readonly VaultAuditRecord[]> {
|
|
286
|
+
const params: unknown[] = [];
|
|
287
|
+
let where = "";
|
|
288
|
+
if (options.path !== undefined) {
|
|
289
|
+
params.push(options.path);
|
|
290
|
+
where = `WHERE path = $${params.length}`;
|
|
291
|
+
}
|
|
292
|
+
params.push(Math.max(1, Math.floor(options.limit ?? 50)));
|
|
293
|
+
|
|
294
|
+
let rows: (AuditDbRow & { seq: number | string | bigint })[];
|
|
295
|
+
try {
|
|
296
|
+
rows = await db.query(
|
|
297
|
+
`SELECT id, seq, action, path, actor_type, actor_id, success, error_code, error_message,
|
|
298
|
+
request_id, prev_hash, row_hash, created_at
|
|
299
|
+
FROM oke_vault_audit ${where}
|
|
300
|
+
ORDER BY seq DESC LIMIT $${params.length}`,
|
|
301
|
+
params,
|
|
302
|
+
);
|
|
303
|
+
} catch {
|
|
304
|
+
throw new VaultError("BACKEND_ERROR", "vault: failed to read audit rows");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return rows.map((row) => ({
|
|
308
|
+
id: row.id,
|
|
309
|
+
seq: typeof row.seq === "number" ? row.seq : Number(row.seq),
|
|
310
|
+
action: row.action as AuditAction,
|
|
311
|
+
path: row.path,
|
|
312
|
+
actorType: row.actor_type as VaultActorType,
|
|
313
|
+
actorId: row.actor_id,
|
|
314
|
+
success: row.success === true || row.success === 1,
|
|
315
|
+
errorCode: row.error_code as VaultErrorCode | null,
|
|
316
|
+
errorMessage: row.error_message,
|
|
317
|
+
requestId: row.request_id,
|
|
318
|
+
createdAt: row.created_at instanceof Date ? row.created_at : new Date(row.created_at),
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Result of {@link verifyAuditChain}. */
|
|
323
|
+
export interface AuditChainResult {
|
|
324
|
+
/** Whether every retained row hashes and links correctly. */
|
|
325
|
+
readonly ok: boolean;
|
|
326
|
+
/** Id of the first row that failed verification. */
|
|
327
|
+
readonly brokenAt?: string;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Raw `oke_vault_audit` row shape. */
|
|
331
|
+
interface AuditDbRow {
|
|
332
|
+
id: string;
|
|
333
|
+
action: string;
|
|
334
|
+
path: string | null;
|
|
335
|
+
actor_type: string;
|
|
336
|
+
actor_id: string | null;
|
|
337
|
+
success: boolean | number;
|
|
338
|
+
error_code: string | null;
|
|
339
|
+
error_message: string | null;
|
|
340
|
+
request_id: string | null;
|
|
341
|
+
prev_hash: string | null;
|
|
342
|
+
row_hash: string;
|
|
343
|
+
created_at: Date | string;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Walk the audit chain and report the first broken row.
|
|
348
|
+
*
|
|
349
|
+
* Each row is re-hashed from its stored `prev_hash` and payload; the result
|
|
350
|
+
* must equal the stored `row_hash`. Consecutive rows must additionally link
|
|
351
|
+
* (`row[n].prev_hash === row[n-1].row_hash`). The oldest retained row is an
|
|
352
|
+
* anchor — its `prev_hash` may point at a purged predecessor.
|
|
353
|
+
*
|
|
354
|
+
* Rows are walked in `seq` order, which is insertion order even when several
|
|
355
|
+
* appends share a `created_at`.
|
|
356
|
+
*
|
|
357
|
+
* @param db - SQL surface
|
|
358
|
+
*/
|
|
359
|
+
export async function verifyAuditChain(db: SqlExec): Promise<AuditChainResult> {
|
|
360
|
+
let rows: AuditDbRow[];
|
|
361
|
+
try {
|
|
362
|
+
rows = await db.query<AuditDbRow>(
|
|
363
|
+
`SELECT id, action, path, actor_type, actor_id, success, error_code, error_message, request_id, prev_hash, row_hash, created_at
|
|
364
|
+
FROM oke_vault_audit ORDER BY seq ASC`,
|
|
365
|
+
);
|
|
366
|
+
} catch {
|
|
367
|
+
throw new VaultError("BACKEND_ERROR", "vault: failed to read audit rows");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
let previousHash: string | undefined;
|
|
371
|
+
for (const row of rows) {
|
|
372
|
+
if (previousHash !== undefined && row.prev_hash !== previousHash) {
|
|
373
|
+
return { ok: false, brokenAt: row.id };
|
|
374
|
+
}
|
|
375
|
+
const expected = await computeAuditRowHash(row.prev_hash, {
|
|
376
|
+
action: row.action as AuditAction,
|
|
377
|
+
path: row.path,
|
|
378
|
+
actorType: row.actor_type as VaultActorType,
|
|
379
|
+
actorId: row.actor_id,
|
|
380
|
+
success: row.success === true || row.success === 1,
|
|
381
|
+
errorCode: row.error_code as VaultErrorCode | null,
|
|
382
|
+
errorMessage: row.error_message,
|
|
383
|
+
requestId: row.request_id,
|
|
384
|
+
createdAt: toIso(row.created_at),
|
|
385
|
+
});
|
|
386
|
+
if (expected !== row.row_hash) {
|
|
387
|
+
return { ok: false, brokenAt: row.id };
|
|
388
|
+
}
|
|
389
|
+
previousHash = row.row_hash;
|
|
390
|
+
}
|
|
391
|
+
return { ok: true };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Backend-side {@link AuditWriter} over `oke_vault_audit`.
|
|
396
|
+
*
|
|
397
|
+
* Reads the current chain head, links the new row to it, and inserts.
|
|
398
|
+
* Concurrent appends must be serialized by the caller (a transaction with
|
|
399
|
+
* `SELECT … FOR UPDATE` on the head, or a single writer) — two racing
|
|
400
|
+
* appends would otherwise share a `prev_hash`.
|
|
401
|
+
*
|
|
402
|
+
* @param db - SQL surface
|
|
403
|
+
*/
|
|
404
|
+
export function createSqlAuditWriter(db: SqlExec): AuditWriter {
|
|
405
|
+
return {
|
|
406
|
+
async appendAuditEntry(entry: AuditEntry): Promise<void> {
|
|
407
|
+
const createdAt = entry.at ?? new Date();
|
|
408
|
+
const payload = toAuditHashPayload(entry, createdAt);
|
|
409
|
+
try {
|
|
410
|
+
const head = await db.query<{ row_hash: string }>(
|
|
411
|
+
`SELECT row_hash FROM oke_vault_audit ORDER BY seq DESC LIMIT 1`,
|
|
412
|
+
);
|
|
413
|
+
const prevHash = head[0]?.row_hash ?? AUDIT_GENESIS_HASH;
|
|
414
|
+
const rowHash = await computeAuditRowHash(prevHash, payload);
|
|
415
|
+
await db.execute(
|
|
416
|
+
`INSERT INTO oke_vault_audit
|
|
417
|
+
(action, path, actor_type, actor_id, success, error_code, error_message, request_id, prev_hash, row_hash, created_at)
|
|
418
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
|
|
419
|
+
[
|
|
420
|
+
payload.action,
|
|
421
|
+
payload.path,
|
|
422
|
+
payload.actorType,
|
|
423
|
+
payload.actorId,
|
|
424
|
+
payload.success,
|
|
425
|
+
payload.errorCode,
|
|
426
|
+
payload.errorMessage,
|
|
427
|
+
payload.requestId,
|
|
428
|
+
prevHash,
|
|
429
|
+
rowHash,
|
|
430
|
+
createdAt,
|
|
431
|
+
],
|
|
432
|
+
);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
if (error instanceof VaultError) throw error;
|
|
435
|
+
throw new VaultError("BACKEND_ERROR", "vault: failed to append audit row");
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Normalize a timestamp column to ISO-8601.
|
|
443
|
+
*
|
|
444
|
+
* @param value - `Date` or driver-supplied string
|
|
445
|
+
*/
|
|
446
|
+
function toIso(value: Date | string): string {
|
|
447
|
+
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
448
|
+
}
|