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,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry wrapper for Vault SQL — connection blips, not business errors.
|
|
3
|
+
*
|
|
4
|
+
* Audit writes must use the raw {@link SqlExec}: retrying an append can
|
|
5
|
+
* duplicate rows and break the hash chain.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SqlExec } from "./storage.ts";
|
|
9
|
+
|
|
10
|
+
/** Tunables for {@link withResilience}. */
|
|
11
|
+
export interface RetryConfig {
|
|
12
|
+
/** Attempts after the first failure. */
|
|
13
|
+
readonly maxRetries: number;
|
|
14
|
+
/** Initial backoff in milliseconds. */
|
|
15
|
+
readonly baseDelayMs: number;
|
|
16
|
+
/** Cap on exponential backoff. */
|
|
17
|
+
readonly maxDelayMs: number;
|
|
18
|
+
/** Substrings matched against `error.code` or `error.message`. */
|
|
19
|
+
readonly retryableErrors: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Default Postgres / network blip codes. */
|
|
23
|
+
export const DEFAULT_RETRY: RetryConfig = {
|
|
24
|
+
maxRetries: 3,
|
|
25
|
+
baseDelayMs: 500,
|
|
26
|
+
maxDelayMs: 5000,
|
|
27
|
+
retryableErrors: ["ECONNRESET", "ETIMEDOUT", "08006", "08003", "57P01"],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether `error` looks like a transient connection failure.
|
|
32
|
+
*
|
|
33
|
+
* @param error - Caught value
|
|
34
|
+
* @param config - Retry tunables
|
|
35
|
+
*/
|
|
36
|
+
export function isRetryableError(error: unknown, config: RetryConfig = DEFAULT_RETRY): boolean {
|
|
37
|
+
if (!error || typeof error !== "object") return false;
|
|
38
|
+
const record = error as { code?: string | number; message?: string };
|
|
39
|
+
const code =
|
|
40
|
+
typeof record.code === "string" || typeof record.code === "number" ? String(record.code) : "";
|
|
41
|
+
const message =
|
|
42
|
+
typeof record.message === "string"
|
|
43
|
+
? record.message
|
|
44
|
+
: error instanceof Error
|
|
45
|
+
? error.message
|
|
46
|
+
: "";
|
|
47
|
+
return config.retryableErrors.some(
|
|
48
|
+
(retryable) => code.includes(retryable) || message.includes(retryable),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Sleep without journaling (CLI / adapter path, not a durable Flow step).
|
|
54
|
+
*
|
|
55
|
+
* @param ms - Delay
|
|
56
|
+
*/
|
|
57
|
+
export async function sleep(ms: number): Promise<void> {
|
|
58
|
+
await new Promise<void>((resolve) => {
|
|
59
|
+
setTimeout(resolve, ms);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Run `operation` with exponential backoff on retryable failures.
|
|
65
|
+
*
|
|
66
|
+
* @param operation - Thunk to retry
|
|
67
|
+
* @param config - Retry tunables
|
|
68
|
+
*/
|
|
69
|
+
export async function withResilience<T>(
|
|
70
|
+
operation: () => Promise<T>,
|
|
71
|
+
config: RetryConfig = DEFAULT_RETRY,
|
|
72
|
+
): Promise<T> {
|
|
73
|
+
let lastError: unknown;
|
|
74
|
+
for (let attempt = 0; attempt <= config.maxRetries; attempt += 1) {
|
|
75
|
+
try {
|
|
76
|
+
return await operation();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
lastError = error;
|
|
79
|
+
if (attempt === config.maxRetries) break;
|
|
80
|
+
if (!isRetryableError(error, config)) throw error;
|
|
81
|
+
const delay = Math.min(config.baseDelayMs * 2 ** attempt, config.maxDelayMs);
|
|
82
|
+
console.warn(`[vault] Query failed (attempt ${attempt + 1}), retrying in ${delay}ms...`);
|
|
83
|
+
await sleep(delay);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
throw lastError;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Wrap a {@link SqlExec} so every `query` / `execute` retries on blips.
|
|
91
|
+
*
|
|
92
|
+
* Do **not** pass the result to {@link createSqlAuditWriter} — audit must
|
|
93
|
+
* fail loudly or be best-effort without duplicate appends.
|
|
94
|
+
*
|
|
95
|
+
* @param db - Underlying SQL surface
|
|
96
|
+
* @param config - Retry tunables
|
|
97
|
+
*/
|
|
98
|
+
export function createResilientSqlExec(db: SqlExec, config: RetryConfig = DEFAULT_RETRY): SqlExec {
|
|
99
|
+
return {
|
|
100
|
+
query<T>(sql: string, params?: unknown[]): Promise<T[]> {
|
|
101
|
+
return withResilience(() => db.query<T>(sql, params), config);
|
|
102
|
+
},
|
|
103
|
+
execute(sql: string, params?: unknown[]): Promise<void> {
|
|
104
|
+
return withResilience(() => db.execute(sql, params), config);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { VaultBag, VaultDriver } from "../../drivers/vault-types.ts";
|
|
6
|
-
import { fromDockerRole, isFromDocker } from "./declare.ts";
|
|
6
|
+
import { fromDockerRole, isFromDocker, readEnv } from "./declare.ts";
|
|
7
7
|
import type { VaultSecretDecl } from "./declare.ts";
|
|
8
8
|
import { fingerprintSecretSync } from "./fingerprint.ts";
|
|
9
9
|
import { createSecretRedactor, SECRET_MASK } from "./redact.ts";
|
|
@@ -69,9 +69,15 @@ export interface VaultResolutionStep {
|
|
|
69
69
|
export interface CreateVaultRuntimeOptions {
|
|
70
70
|
/** Declared secret / config contracts. */
|
|
71
71
|
readonly secrets?: readonly VaultSecretDecl[];
|
|
72
|
+
/**
|
|
73
|
+
* Environment variables declared through `vault.env.required`. Missing
|
|
74
|
+
* ones are reported as {@link VaultGap}s in the same boot pass as secrets,
|
|
75
|
+
* so one run lists every hole in the configuration.
|
|
76
|
+
*/
|
|
77
|
+
readonly requiredEnv?: readonly string[];
|
|
72
78
|
/**
|
|
73
79
|
* Resolution chain — first hit wins.
|
|
74
|
-
* Callers pass the env-appropriate layers (env /
|
|
80
|
+
* Callers pass the env-appropriate layers (env / vault / memory / managed).
|
|
75
81
|
*/
|
|
76
82
|
readonly chain?: readonly VaultChainLayer[];
|
|
77
83
|
/**
|
|
@@ -88,7 +94,7 @@ export interface VaultRuntime {
|
|
|
88
94
|
/** Declared contracts by name. */
|
|
89
95
|
readonly contracts: ReadonlyMap<string, VaultSecretDecl>;
|
|
90
96
|
/**
|
|
91
|
-
* Driver ids of each chain layer in order (`env` · `
|
|
97
|
+
* Driver ids of each chain layer in order (`env` · `vault` · `memory` · `managed`).
|
|
92
98
|
* Available before {@link boot}.
|
|
93
99
|
*/
|
|
94
100
|
readonly chainDriverIds: readonly string[];
|
|
@@ -100,7 +106,7 @@ export interface VaultRuntime {
|
|
|
100
106
|
/** Whether {@link boot} has succeeded. */
|
|
101
107
|
readonly booted: boolean;
|
|
102
108
|
/**
|
|
103
|
-
* Read a secret (capability is enforced by `fx.vault`).
|
|
109
|
+
* Read a secret (capability is enforced by `fx.vault.get`).
|
|
104
110
|
* Records {@link lastReadAt}.
|
|
105
111
|
*
|
|
106
112
|
* @param name - Secret name
|
|
@@ -238,6 +244,7 @@ export function createVaultRuntime(options: CreateVaultRuntimeOptions = {}): Vau
|
|
|
238
244
|
});
|
|
239
245
|
}
|
|
240
246
|
}
|
|
247
|
+
gaps.push(...requiredEnvGaps(options.requiredEnv ?? [], new Set(contracts.keys())));
|
|
241
248
|
if (gaps.length > 0) {
|
|
242
249
|
throw new VaultBootError(gaps);
|
|
243
250
|
}
|
|
@@ -378,6 +385,31 @@ export function createVaultRuntime(options: CreateVaultRuntimeOptions = {}): Vau
|
|
|
378
385
|
};
|
|
379
386
|
}
|
|
380
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Gaps for every `vault.env.required` name absent from the process
|
|
390
|
+
* environment. Names that are also declared contracts are skipped — those
|
|
391
|
+
* resolve through the boot chain (`.env.local`, driver, dev fallback), so
|
|
392
|
+
* `process.env` alone would report a false gap.
|
|
393
|
+
*
|
|
394
|
+
* @param names - Required environment variable names
|
|
395
|
+
* @param declared - Contract names already validated by the boot chain
|
|
396
|
+
*/
|
|
397
|
+
export function requiredEnvGaps(
|
|
398
|
+
names: readonly string[],
|
|
399
|
+
declared: ReadonlySet<string> = new Set(),
|
|
400
|
+
): VaultGap[] {
|
|
401
|
+
const gaps: VaultGap[] = [];
|
|
402
|
+
const seen = new Set<string>();
|
|
403
|
+
for (const name of names) {
|
|
404
|
+
if (declared.has(name) || seen.has(name)) continue;
|
|
405
|
+
seen.add(name);
|
|
406
|
+
if (readEnv(name) === undefined) {
|
|
407
|
+
gaps.push({ name, description: "required environment variable (vault.env.required)" });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return gaps;
|
|
411
|
+
}
|
|
412
|
+
|
|
381
413
|
/**
|
|
382
414
|
* Infer the Console source id for a chain layer.
|
|
383
415
|
*
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 10 security checklist — threat-model assertions for the built-in vault.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { connectPglite } from "../../drivers/pglite.ts";
|
|
7
|
+
import type { SqlConnection } from "../../drivers/types.ts";
|
|
8
|
+
import {
|
|
9
|
+
createBuiltinVaultAdapter,
|
|
10
|
+
sqlConnectionAsExec,
|
|
11
|
+
type BuiltinVaultAdapter,
|
|
12
|
+
} from "./builtin-adapter.ts";
|
|
13
|
+
import {
|
|
14
|
+
ALGORITHM,
|
|
15
|
+
buildAad,
|
|
16
|
+
decryptSecret,
|
|
17
|
+
encryptSecret,
|
|
18
|
+
generateDek,
|
|
19
|
+
importAesKey,
|
|
20
|
+
unwrapDek,
|
|
21
|
+
wrapDek,
|
|
22
|
+
} from "./crypto.ts";
|
|
23
|
+
import { VaultError } from "./errors.ts";
|
|
24
|
+
import { canonicalizePath } from "./path.ts";
|
|
25
|
+
import type { SqlExec } from "./storage.ts";
|
|
26
|
+
import { createMemoryUnsealer } from "./unseal.ts";
|
|
27
|
+
|
|
28
|
+
/** Fresh initialized + unsealed adapter. */
|
|
29
|
+
async function harness(): Promise<{
|
|
30
|
+
adapter: BuiltinVaultAdapter;
|
|
31
|
+
db: SqlExec;
|
|
32
|
+
conn: SqlConnection;
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
}> {
|
|
35
|
+
const conn = await connectPglite({ url: `memory://vault-sec-${crypto.randomUUID()}` });
|
|
36
|
+
const db = sqlConnectionAsExec(conn);
|
|
37
|
+
const adapter = createBuiltinVaultAdapter({ db });
|
|
38
|
+
const init = await adapter.initialize!();
|
|
39
|
+
await adapter.unseal!(init.masterKey);
|
|
40
|
+
return {
|
|
41
|
+
adapter,
|
|
42
|
+
db,
|
|
43
|
+
conn,
|
|
44
|
+
async close() {
|
|
45
|
+
await conn.close();
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe("vault security checklist", () => {
|
|
51
|
+
test("DB rows never contain the cleartext secret", async () => {
|
|
52
|
+
const h = await harness();
|
|
53
|
+
try {
|
|
54
|
+
const secret = `sk_live_${crypto.randomUUID()}`;
|
|
55
|
+
await h.adapter.set("prod/api/stripe", secret);
|
|
56
|
+
const rows = await h.conn.query(
|
|
57
|
+
"select encode(encrypted_value, 'escape') as blob from oke_vault_secrets",
|
|
58
|
+
);
|
|
59
|
+
for (const row of rows) {
|
|
60
|
+
const blob = String((row as { blob?: unknown }).blob ?? "");
|
|
61
|
+
expect(blob.includes(secret)).toBe(false);
|
|
62
|
+
}
|
|
63
|
+
} finally {
|
|
64
|
+
await h.close();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("sealed vault fails closed on get and set", async () => {
|
|
69
|
+
const h = await harness();
|
|
70
|
+
try {
|
|
71
|
+
await h.adapter.set("a/b", "v1");
|
|
72
|
+
await h.adapter.seal!();
|
|
73
|
+
await expect(h.adapter.get("a/b")).rejects.toBeInstanceOf(VaultError);
|
|
74
|
+
await expect(h.adapter.set("a/b", "v2")).rejects.toBeInstanceOf(VaultError);
|
|
75
|
+
} finally {
|
|
76
|
+
await h.close();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("canonical path is what enters AAD — non-canonical input is rejected before store", () => {
|
|
81
|
+
expect(() => canonicalizePath("../etc/passwd")).toThrow(VaultError);
|
|
82
|
+
expect(() => canonicalizePath("a\\b")).toThrow(VaultError);
|
|
83
|
+
expect(() => canonicalizePath("a\0b")).toThrow(VaultError);
|
|
84
|
+
expect(canonicalizePath("/prod//api/stripe/")).toBe("prod/api/stripe");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("AAD path mismatch fails decrypt even with the correct DEK", async () => {
|
|
88
|
+
const dekBytes = generateDek();
|
|
89
|
+
const dek = await importAesKey(dekBytes);
|
|
90
|
+
const aad = buildAad("prod/api/stripe", 1, ALGORITHM, 1);
|
|
91
|
+
const sealed = await encryptSecret(dek, "plain", aad);
|
|
92
|
+
const wrongAad = buildAad("prod/api/other", 1, ALGORITHM, 1);
|
|
93
|
+
await expect(decryptSecret(dek, sealed, wrongAad)).rejects.toBeInstanceOf(VaultError);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("VaultError from unwrap paths never exposes a cause chain", async () => {
|
|
97
|
+
const unsealer = createMemoryUnsealer(crypto.getRandomValues(new Uint8Array(32)));
|
|
98
|
+
const kek = await unsealer.unwrapKek();
|
|
99
|
+
const dekBytes = generateDek();
|
|
100
|
+
const aad = buildAad("p", 1, ALGORITHM, 1);
|
|
101
|
+
const wrapped = await wrapDek(kek, dekBytes, aad);
|
|
102
|
+
const other = createMemoryUnsealer(crypto.getRandomValues(new Uint8Array(32)));
|
|
103
|
+
const otherKek = await other.unwrapKek();
|
|
104
|
+
try {
|
|
105
|
+
await unwrapDek(otherKek, wrapped, aad);
|
|
106
|
+
expect.unreachable();
|
|
107
|
+
} catch (err) {
|
|
108
|
+
expect(err).toBeInstanceOf(VaultError);
|
|
109
|
+
expect((err as Error & { cause?: unknown }).cause).toBeUndefined();
|
|
110
|
+
expect(String(err)).not.toContain(Buffer.from(dekBytes).toString("base64"));
|
|
111
|
+
}
|
|
112
|
+
unsealer.seal();
|
|
113
|
+
other.seal();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("audit verify detects a tampered row_hash", async () => {
|
|
117
|
+
const h = await harness();
|
|
118
|
+
try {
|
|
119
|
+
await h.adapter.set("x/y", "one");
|
|
120
|
+
await h.conn.exec(
|
|
121
|
+
"update oke_vault_audit set row_hash = $1 where seq = (select max(seq) from oke_vault_audit)",
|
|
122
|
+
["0".repeat(64)],
|
|
123
|
+
);
|
|
124
|
+
const result = await h.adapter.verifyAudit();
|
|
125
|
+
expect(result.ok).toBe(false);
|
|
126
|
+
} finally {
|
|
127
|
+
await h.close();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|