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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import { currentAbortSignal, isAbortError, withAbortSignal } from "./abort-scope.ts";
|
|
4
|
-
import { createMemoryJournalStore } from "./journal.ts";
|
|
4
|
+
import { createJournal, createMemoryJournalStore } from "./journal.ts";
|
|
5
5
|
import { createFx, createFxContext } from "./fx.ts";
|
|
6
6
|
import { flow } from "./flow.ts";
|
|
7
7
|
import { runDurable } from "../elements/clock/durable.ts";
|
|
@@ -216,12 +216,11 @@ describe("retry + durable journal", () => {
|
|
|
216
216
|
test("fx.retry inside fx.step does not re-run completed step on resume", async () => {
|
|
217
217
|
const journalStore = createMemoryJournalStore();
|
|
218
218
|
let attempts = 0;
|
|
219
|
-
let crash = true;
|
|
220
219
|
|
|
221
220
|
const f = flow("pay.retry", {
|
|
222
221
|
durable: true,
|
|
223
222
|
do: async (_input, fx) => {
|
|
224
|
-
|
|
223
|
+
return fx.step("charge", () =>
|
|
225
224
|
fx.retry(
|
|
226
225
|
async () => {
|
|
227
226
|
attempts += 1;
|
|
@@ -230,20 +229,22 @@ describe("retry + durable journal", () => {
|
|
|
230
229
|
{ retries: 2, delay: 0 },
|
|
231
230
|
),
|
|
232
231
|
);
|
|
233
|
-
if (crash) throw new Error("KILLED");
|
|
234
|
-
return charged;
|
|
235
232
|
},
|
|
236
233
|
});
|
|
237
234
|
|
|
238
|
-
|
|
239
|
-
|
|
235
|
+
// Simulate crash after the journaled step: leave status `running`.
|
|
236
|
+
const journal = createJournal({ store: journalStore });
|
|
237
|
+
const crashed = await journal.start("pay.retry");
|
|
238
|
+
await crashed.step("charge", () => {
|
|
239
|
+
attempts += 1;
|
|
240
|
+
return { id: "ch_1" };
|
|
241
|
+
});
|
|
240
242
|
expect(attempts).toBe(1);
|
|
241
243
|
|
|
242
|
-
crash = false;
|
|
243
244
|
const second = await runDurable({
|
|
244
245
|
flow: f,
|
|
245
246
|
journalStore,
|
|
246
|
-
runId:
|
|
247
|
+
runId: crashed.runId,
|
|
247
248
|
});
|
|
248
249
|
expect(second.status).toBe("completed");
|
|
249
250
|
expect(attempts).toBe(1);
|
|
@@ -91,8 +91,22 @@ describe("boot-level: undeclared-effects flow, no manifest / rootDir", () => {
|
|
|
91
91
|
const create = buildUnannotatedCreateFlow(db);
|
|
92
92
|
const app = oke({ name: "stamp-docker", gate: { policies: [gate.public] } }).adopt({ create });
|
|
93
93
|
|
|
94
|
+
const memoryDrivers = {
|
|
95
|
+
store: {
|
|
96
|
+
sql: { dev: "memory", prod: "memory" },
|
|
97
|
+
kv: { dev: "memory", prod: "memory" },
|
|
98
|
+
},
|
|
99
|
+
channel: { email: { dev: "console", prod: "console" } },
|
|
100
|
+
} as const;
|
|
94
101
|
await expect(
|
|
95
|
-
app.boot({
|
|
102
|
+
app.boot({
|
|
103
|
+
env: "dev",
|
|
104
|
+
docker: true,
|
|
105
|
+
stores: [db],
|
|
106
|
+
unguardedHttp: "allow",
|
|
107
|
+
startScheduler: false,
|
|
108
|
+
config: { drivers: memoryDrivers },
|
|
109
|
+
}),
|
|
96
110
|
).rejects.toThrow(/OKE1008/);
|
|
97
111
|
});
|
|
98
112
|
|
|
@@ -104,7 +118,18 @@ describe("boot-level: undeclared-effects flow, no manifest / rootDir", () => {
|
|
|
104
118
|
const app = oke({ name: "stamp-prod", gate: { policies: [gate.public] } }).adopt({ create });
|
|
105
119
|
|
|
106
120
|
await expect(
|
|
107
|
-
app.boot({
|
|
121
|
+
app.boot({
|
|
122
|
+
env: "prod",
|
|
123
|
+
stores: [db],
|
|
124
|
+
unguardedHttp: "allow",
|
|
125
|
+
startScheduler: false,
|
|
126
|
+
config: {
|
|
127
|
+
drivers: {
|
|
128
|
+
store: { sql: { prod: "memory" }, kv: { prod: "memory" } },
|
|
129
|
+
channel: { email: { prod: "console" } },
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
108
133
|
).rejects.toThrow(/OKE1008/);
|
|
109
134
|
});
|
|
110
135
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module-evaluation registries for `store.sql` / `store.files`, `vault.secret`,
|
|
3
|
-
* `signal()`, and `channel.<medium>().template()` — the
|
|
4
|
-
* each declare module's `listX()` / `resetX()` pair
|
|
5
|
-
* trigger-drain `bindings` array).
|
|
3
|
+
* `vault.env.required`, `signal()`, and `channel.<medium>().template()` — the
|
|
4
|
+
* plain arrays behind each declare module's `listX()` / `resetX()` pair
|
|
5
|
+
* (mirrors `on.ts`'s trigger-drain `bindings` array).
|
|
6
6
|
*
|
|
7
7
|
* Lives here, not in the declare modules themselves, so {@link oke} can read
|
|
8
8
|
* every registry with one lightweight import instead of statically pulling
|
|
@@ -21,6 +21,8 @@ import type { ChannelTemplateDecl } from "../elements/channel/declare.ts";
|
|
|
21
21
|
export const storeRegistry: StoreDecl[] = [];
|
|
22
22
|
/** `vault.secret` declarations since the last reset. */
|
|
23
23
|
export const secretRegistry: VaultSecretDecl[] = [];
|
|
24
|
+
/** `vault.env.required(name)` environment variables since the last reset. */
|
|
25
|
+
export const requiredEnvRegistry: string[] = [];
|
|
24
26
|
/** `signal()` declarations since the last reset. */
|
|
25
27
|
export const signalRegistry: SignalDecl[] = [];
|
|
26
28
|
/** Medium-binder `.template()` declarations since the last reset. */
|
package/src/kernel/flow.ts
CHANGED
|
@@ -71,8 +71,10 @@ export interface FlowOptions<I = unknown, O = unknown, E extends FlowErrorMap =
|
|
|
71
71
|
readonly breaking?: boolean;
|
|
72
72
|
/**
|
|
73
73
|
* Compensation phase after terminal failure on a durable flow.
|
|
74
|
-
* Runs under the same journal session
|
|
75
|
-
*
|
|
74
|
+
* Runs under the same journal session after auto per-step `{ undo }`
|
|
75
|
+
* handlers and before `commit("failed")`. Prefer `fx.step(name, fn, { undo })`
|
|
76
|
+
* for step-local reverse work; use this hook for cross-cutting cleanup.
|
|
77
|
+
* Manual bodies must use distinct `fx.step("undo:…")` names — never reuse
|
|
76
78
|
* forward step names. Never called on retryable attempts or sleep park.
|
|
77
79
|
*/
|
|
78
80
|
readonly compensate?: (
|
package/src/kernel/fx.test.ts
CHANGED
|
@@ -100,7 +100,7 @@ describe("fx — effect ledger", () => {
|
|
|
100
100
|
await fx.emit("order-placed", {});
|
|
101
101
|
await fx.send("order-confirmed", { to: "u1" });
|
|
102
102
|
await fx.ask("triage@1", {});
|
|
103
|
-
expect(fx.vault("STRIPE_KEY").reveal()).toBe("sk_test");
|
|
103
|
+
expect((await fx.vault.get("STRIPE_KEY")).reveal()).toBe("sk_test");
|
|
104
104
|
await fx.call("payments.charge", {});
|
|
105
105
|
|
|
106
106
|
expect(ledger.entries).toHaveLength(7);
|
|
@@ -112,6 +112,105 @@ describe("fx — effect ledger", () => {
|
|
|
112
112
|
});
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
+
describe("fx.vault — object surface", () => {
|
|
116
|
+
/** Minimal in-memory adapter covering the surface `fx.vault` uses. */
|
|
117
|
+
function memoryAdapter() {
|
|
118
|
+
const rows = new Map<string, { value: string; version: number }>();
|
|
119
|
+
const actors: string[] = [];
|
|
120
|
+
return {
|
|
121
|
+
rows,
|
|
122
|
+
actors,
|
|
123
|
+
adapter: {
|
|
124
|
+
id: "memory",
|
|
125
|
+
async get(path: string) {
|
|
126
|
+
const row = rows.get(path);
|
|
127
|
+
return row === undefined ? undefined : { path, ...row };
|
|
128
|
+
},
|
|
129
|
+
async set(path: string, value: string, opts?: { actor?: { id?: string } }) {
|
|
130
|
+
if (opts?.actor?.id) actors.push(opts.actor.id);
|
|
131
|
+
const version = (rows.get(path)?.version ?? 0) + 1;
|
|
132
|
+
rows.set(path, { value, version });
|
|
133
|
+
return { path, value, version };
|
|
134
|
+
},
|
|
135
|
+
async rotate(path: string, value: string) {
|
|
136
|
+
const version = (rows.get(path)?.version ?? 0) + 1;
|
|
137
|
+
rows.set(path, { value, version });
|
|
138
|
+
return { path, value, version };
|
|
139
|
+
},
|
|
140
|
+
async delete(path: string) {
|
|
141
|
+
return rows.delete(path);
|
|
142
|
+
},
|
|
143
|
+
async list(opts?: { prefix?: string }) {
|
|
144
|
+
return [...rows.keys()]
|
|
145
|
+
.filter((p) => opts?.prefix === undefined || p.startsWith(opts.prefix))
|
|
146
|
+
.map((path) => ({ path, version: rows.get(path)!.version }));
|
|
147
|
+
},
|
|
148
|
+
async status() {
|
|
149
|
+
return { sealed: false, initialized: true, secretCount: rows.size };
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
test("mutations run through the adapter, ledger `secret`, and carry the flow actor", async () => {
|
|
156
|
+
const { adapter, actors, rows } = memoryAdapter();
|
|
157
|
+
const ledger = createEffectLedger();
|
|
158
|
+
const fx = createFx({
|
|
159
|
+
flow: "ops.rotateKey",
|
|
160
|
+
effects: { secrets: ["prod/api/stripe"] },
|
|
161
|
+
ledger,
|
|
162
|
+
// The adapter's structural shape is all `fx.vault` consumes.
|
|
163
|
+
vaultAdapter: adapter as never,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(await fx.vault.set("prod/api/stripe", "sk_1")).toEqual({
|
|
167
|
+
path: "prod/api/stripe",
|
|
168
|
+
version: 1,
|
|
169
|
+
});
|
|
170
|
+
expect(await fx.vault.rotate("prod/api/stripe", "sk_2")).toEqual({
|
|
171
|
+
path: "prod/api/stripe",
|
|
172
|
+
version: 2,
|
|
173
|
+
});
|
|
174
|
+
expect(await fx.vault.list("prod/")).toEqual(["prod/api/stripe"]);
|
|
175
|
+
expect(await fx.vault.status()).toEqual({
|
|
176
|
+
sealed: false,
|
|
177
|
+
initialized: true,
|
|
178
|
+
backend: "memory",
|
|
179
|
+
});
|
|
180
|
+
expect(await fx.vault.delete("prod/api/stripe")).toBe(true);
|
|
181
|
+
|
|
182
|
+
expect(rows.size).toBe(0);
|
|
183
|
+
expect(actors).toEqual(["ops.rotateKey"]);
|
|
184
|
+
expect(ledger.entries.map((e) => e.kind)).toEqual(["secret", "secret", "secret"]);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("an undeclared path is refused before the adapter is touched", async () => {
|
|
188
|
+
const { adapter, rows } = memoryAdapter();
|
|
189
|
+
const fx = createFx({
|
|
190
|
+
flow: "ops.rotateKey",
|
|
191
|
+
effects: { secrets: ["prod/api/stripe"] },
|
|
192
|
+
vaultAdapter: adapter as never,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await expect(fx.vault.set("prod/api/other", "sk_1")).rejects.toThrow(/OKE1006/);
|
|
196
|
+
expect(rows.size).toBe(0);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("mutations without a bound adapter name the missing backend", async () => {
|
|
200
|
+
const fx = createFx({
|
|
201
|
+
flow: "ops.rotateKey",
|
|
202
|
+
effects: { secrets: ["prod/api/stripe"] },
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
await expect(fx.vault.set("prod/api/stripe", "sk_1")).rejects.toThrow(
|
|
206
|
+
/fx\.vault\.set needs a bound Vault backend/,
|
|
207
|
+
);
|
|
208
|
+
await expect(fx.vault.status()).rejects.toThrow(
|
|
209
|
+
/fx\.vault\.status needs a bound Vault backend/,
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
115
214
|
describe("fx — wholesale swap", () => {
|
|
116
215
|
test("fx is swappable wholesale (plain object interface)", async () => {
|
|
117
216
|
const calls: string[] = [];
|
|
@@ -144,8 +243,25 @@ describe("fx — wholesale swap", () => {
|
|
|
144
243
|
now: () => 0,
|
|
145
244
|
sleep: async () => undefined,
|
|
146
245
|
},
|
|
147
|
-
vault
|
|
148
|
-
|
|
246
|
+
vault: {
|
|
247
|
+
async get() {
|
|
248
|
+
return new Redacted("");
|
|
249
|
+
},
|
|
250
|
+
async set() {
|
|
251
|
+
throw new Error("vault.set must not be used");
|
|
252
|
+
},
|
|
253
|
+
async rotate() {
|
|
254
|
+
throw new Error("vault.rotate must not be used");
|
|
255
|
+
},
|
|
256
|
+
async delete() {
|
|
257
|
+
throw new Error("vault.delete must not be used");
|
|
258
|
+
},
|
|
259
|
+
async list() {
|
|
260
|
+
return [];
|
|
261
|
+
},
|
|
262
|
+
async status() {
|
|
263
|
+
return { sealed: false, initialized: true, backend: "memory" };
|
|
264
|
+
},
|
|
149
265
|
},
|
|
150
266
|
cache: {
|
|
151
267
|
get: async () => undefined,
|
package/src/kernel/fx.ts
CHANGED
|
@@ -28,7 +28,7 @@ import type {
|
|
|
28
28
|
import type { SqlRow } from "../drivers/types.ts";
|
|
29
29
|
import type { SignalRuntime } from "../elements/signal.ts";
|
|
30
30
|
import type { SignalEmitOptions } from "../drivers/signal-types.ts";
|
|
31
|
-
import type { VaultRuntime } from "../elements/vault.ts";
|
|
31
|
+
import type { VaultActor, VaultAdapter, VaultRuntime } from "../elements/vault.ts";
|
|
32
32
|
import type { ChannelRuntime } from "../elements/channel.ts";
|
|
33
33
|
import type { AiRuntime } from "../elements/ai.ts";
|
|
34
34
|
import { parseDurationMs } from "../elements/clock/duration.ts";
|
|
@@ -51,19 +51,22 @@ import {
|
|
|
51
51
|
type FxThunk,
|
|
52
52
|
} from "./concurrency.ts";
|
|
53
53
|
import { maskRedactedDeep, Redacted } from "./redacted.ts";
|
|
54
|
-
import type { JournalSession } from "./journal.ts";
|
|
54
|
+
import type { JournalSession, JournalStepOptions } from "./journal.ts";
|
|
55
|
+
|
|
56
|
+
/** Options for {@link Fx.step} — see {@link JournalStepOptions}. */
|
|
57
|
+
export type StepOptions<T> = JournalStepOptions<T>;
|
|
55
58
|
import type { RunTelemetry } from "./run-telemetry.ts";
|
|
56
59
|
import type { RunsRuntime } from "../runs/runtime.ts";
|
|
57
60
|
import type { RunsRow, WideEvent } from "../runs/types.ts";
|
|
58
|
-
import {
|
|
59
|
-
evaluateSloBreaches,
|
|
60
|
-
windowStatsForFlow,
|
|
61
|
-
type RunWindowStats,
|
|
62
|
-
type SloBreach,
|
|
63
|
-
} from "../runs/window.ts";
|
|
61
|
+
import type { RunWindowStats, SloBreach } from "../runs/window.ts";
|
|
64
62
|
import { translate, type MessageCatalogs } from "../i18n/messages.ts";
|
|
65
63
|
import type { AppMessageKey, MessageValues } from "../i18n/types.ts";
|
|
66
64
|
|
|
65
|
+
/** Lazy runs/window helpers — kept off the cold `oke` static graph. */
|
|
66
|
+
async function loadRunsWindow(): Promise<typeof import("../runs/window.ts")> {
|
|
67
|
+
return import("../runs/window.ts");
|
|
68
|
+
}
|
|
69
|
+
|
|
67
70
|
/** Resource ref Flows declare to read the Runs store via {@link Fx.runs}. */
|
|
68
71
|
export const RUNS_RESOURCE = "runs";
|
|
69
72
|
|
|
@@ -339,6 +342,83 @@ export interface FxJson {
|
|
|
339
342
|
with<T>(data: T, meta: Record<string, unknown>): JsonResult<T>;
|
|
340
343
|
}
|
|
341
344
|
|
|
345
|
+
/** Options for {@link FxVault.set}. */
|
|
346
|
+
export interface FxVaultSetOptions {
|
|
347
|
+
/** Relative expiry from now, in milliseconds. */
|
|
348
|
+
readonly ttlMs?: number;
|
|
349
|
+
/** Non-sensitive metadata stored beside the ciphertext. */
|
|
350
|
+
readonly metadata?: Record<string, unknown>;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Identity of a written secret version. */
|
|
354
|
+
export interface FxVaultWriteResult {
|
|
355
|
+
/** Canonical path that was written. */
|
|
356
|
+
readonly path: string;
|
|
357
|
+
/** Monotonic version of the new value. */
|
|
358
|
+
readonly version: number;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Operational state of the bound Vault backend. */
|
|
362
|
+
export interface FxVaultStatus {
|
|
363
|
+
/** Whether the master key is currently unavailable. */
|
|
364
|
+
readonly sealed: boolean;
|
|
365
|
+
/** Whether the backend has been initialized. */
|
|
366
|
+
readonly initialized: boolean;
|
|
367
|
+
/** Backend id (`sql`, `memory`, `vault`, …). */
|
|
368
|
+
readonly backend: string;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Vault surface on `fx`.
|
|
373
|
+
*
|
|
374
|
+
* `get` reads through the boot-time resolution chain and is the only method
|
|
375
|
+
* every app has: the mutation and introspection methods need a bound
|
|
376
|
+
* {@link CreateFxOptions.vaultAdapter} (the encrypted-at-rest backend) and
|
|
377
|
+
* throw without one.
|
|
378
|
+
*/
|
|
379
|
+
export interface FxVault {
|
|
380
|
+
/**
|
|
381
|
+
* Read a vault secret (records `secret`).
|
|
382
|
+
*
|
|
383
|
+
* Returns a {@link Redacted} — printing / logging / serializing it yields a
|
|
384
|
+
* placeholder, never the value. Call `.reveal()` at the one boundary that
|
|
385
|
+
* needs the real value (e.g. passing a credential to a driver).
|
|
386
|
+
*
|
|
387
|
+
* @param secret - Secret name or handle
|
|
388
|
+
*/
|
|
389
|
+
get(secret: NamedRef): Promise<Redacted<string>>;
|
|
390
|
+
/**
|
|
391
|
+
* Write a new version of a path (records `secret`).
|
|
392
|
+
*
|
|
393
|
+
* @param path - Secret path or handle
|
|
394
|
+
* @param value - Cleartext value
|
|
395
|
+
* @param options - TTL / metadata
|
|
396
|
+
*/
|
|
397
|
+
set(path: NamedRef, value: string, options?: FxVaultSetOptions): Promise<FxVaultWriteResult>;
|
|
398
|
+
/**
|
|
399
|
+
* Write a new version with a fresh data key, retiring the previous one
|
|
400
|
+
* (records `secret`).
|
|
401
|
+
*
|
|
402
|
+
* @param path - Secret path or handle
|
|
403
|
+
* @param value - New cleartext value
|
|
404
|
+
*/
|
|
405
|
+
rotate(path: NamedRef, value: string): Promise<FxVaultWriteResult>;
|
|
406
|
+
/**
|
|
407
|
+
* Crypto-shred a path (records `secret`). Returns whether anything went.
|
|
408
|
+
*
|
|
409
|
+
* @param path - Secret path or handle
|
|
410
|
+
*/
|
|
411
|
+
delete(path: NamedRef): Promise<boolean>;
|
|
412
|
+
/**
|
|
413
|
+
* Enumerate secret paths (never values).
|
|
414
|
+
*
|
|
415
|
+
* @param prefix - Canonical path prefix filter
|
|
416
|
+
*/
|
|
417
|
+
list(prefix?: string): Promise<readonly string[]>;
|
|
418
|
+
/** Seal / initialization state of the backend. */
|
|
419
|
+
status(): Promise<FxVaultStatus>;
|
|
420
|
+
}
|
|
421
|
+
|
|
342
422
|
/**
|
|
343
423
|
* Flow-facing read door to the Runs wide-event store.
|
|
344
424
|
*
|
|
@@ -420,16 +500,8 @@ export interface Fx {
|
|
|
420
500
|
readonly runs: FxRuns;
|
|
421
501
|
/** Clock surface. */
|
|
422
502
|
readonly clock: FxClock;
|
|
423
|
-
/**
|
|
424
|
-
|
|
425
|
-
*
|
|
426
|
-
* Returns a {@link Redacted} — printing / logging / serializing it yields a
|
|
427
|
-
* placeholder, never the value. Call `.reveal()` at the one boundary that
|
|
428
|
-
* needs the real value (e.g. passing a credential to a driver).
|
|
429
|
-
*
|
|
430
|
-
* @param secret - Secret name or handle
|
|
431
|
-
*/
|
|
432
|
-
vault(secret: NamedRef): Redacted<string>;
|
|
503
|
+
/** Vault surface — `fx.vault.get(secret)` and the adapter-backed mutations. */
|
|
504
|
+
readonly vault: FxVault;
|
|
433
505
|
/** Cache surface. */
|
|
434
506
|
readonly cache: FxCache;
|
|
435
507
|
/**
|
|
@@ -544,8 +616,9 @@ export interface Fx {
|
|
|
544
616
|
*
|
|
545
617
|
* @param name - Step name
|
|
546
618
|
* @param fn - Step body
|
|
619
|
+
* @param opts - Optional `{ undo }` — durable compensation on terminal failure
|
|
547
620
|
*/
|
|
548
|
-
step<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
|
|
621
|
+
step<T>(name: string, fn: () => T | Promise<T>, opts?: StepOptions<T>): Promise<T>;
|
|
549
622
|
/**
|
|
550
623
|
* Ambient abort signal for the current structured-concurrency branch.
|
|
551
624
|
* Outside `all` / `race`, a never-aborted signal.
|
|
@@ -619,7 +692,7 @@ export interface CreateFxOptions {
|
|
|
619
692
|
readonly principal?: FxPrincipal;
|
|
620
693
|
/** Tenant. */
|
|
621
694
|
readonly tenant?: FxTenant;
|
|
622
|
-
/** Secret name → value map for `fx.vault`. */
|
|
695
|
+
/** Secret name → value map for `fx.vault.get`. */
|
|
623
696
|
readonly secrets?: Readonly<Record<string, string>>;
|
|
624
697
|
/** Seed data for in-memory stores: ref → key → value. */
|
|
625
698
|
readonly storeData?: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
@@ -651,10 +724,16 @@ export interface CreateFxOptions {
|
|
|
651
724
|
*/
|
|
652
725
|
readonly runsRuntime?: RunsRuntime;
|
|
653
726
|
/**
|
|
654
|
-
* Optional vault runtime. When set, `fx.vault` reads through it and
|
|
727
|
+
* Optional vault runtime. When set, `fx.vault.get` reads through it and
|
|
655
728
|
* `fx.log` redacts loaded secret values automatically.
|
|
656
729
|
*/
|
|
657
730
|
readonly vaultRuntime?: VaultRuntime;
|
|
731
|
+
/**
|
|
732
|
+
* Optional encrypted-at-rest Vault backend. Required by the `fx.vault`
|
|
733
|
+
* mutation / introspection methods (`set`, `rotate`, `delete`, `list`,
|
|
734
|
+
* `status`); reads never need it.
|
|
735
|
+
*/
|
|
736
|
+
readonly vaultAdapter?: VaultAdapter;
|
|
658
737
|
/**
|
|
659
738
|
* Optional channel runtime. When set, `fx.send` delivers through it
|
|
660
739
|
* (templates, consent, fallback chains, receipts).
|
|
@@ -1229,6 +1308,116 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1229
1308
|
},
|
|
1230
1309
|
};
|
|
1231
1310
|
|
|
1311
|
+
const vaultActor: VaultActor = {
|
|
1312
|
+
type: "flow",
|
|
1313
|
+
id: options.flow,
|
|
1314
|
+
...(options.runId !== undefined ? { requestId: options.runId } : {}),
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* Capability-check a vault path and ledger the `secret` effect around it.
|
|
1319
|
+
*
|
|
1320
|
+
* Kept off {@link gated} deliberately: secret access is never journaled,
|
|
1321
|
+
* so a durable replay re-reads the live value instead of resurrecting a
|
|
1322
|
+
* rotated one from the journal.
|
|
1323
|
+
*
|
|
1324
|
+
* @param name - Secret path
|
|
1325
|
+
* @param body - Work to run under the gate
|
|
1326
|
+
*/
|
|
1327
|
+
async function gatedSecret<T>(name: string, body: () => T | Promise<T>): Promise<T> {
|
|
1328
|
+
capability.assert("secret", name);
|
|
1329
|
+
const timestamp = now();
|
|
1330
|
+
try {
|
|
1331
|
+
return await body();
|
|
1332
|
+
} finally {
|
|
1333
|
+
ledger.record({
|
|
1334
|
+
kind: "secret",
|
|
1335
|
+
resource: name,
|
|
1336
|
+
timestamp,
|
|
1337
|
+
duration: Math.max(0, now() - timestamp),
|
|
1338
|
+
reversibility: reversibilityOf("secret"),
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* @param op - Method name for the error message
|
|
1345
|
+
*/
|
|
1346
|
+
function vaultAdapterFor(op: string): VaultAdapter {
|
|
1347
|
+
const adapter = options.vaultAdapter;
|
|
1348
|
+
if (!adapter) {
|
|
1349
|
+
throw new Error(
|
|
1350
|
+
`fx.vault.${op} needs a bound Vault backend — configure the vault element (drivers.vault = "vault") or pass vaultAdapter to createFx`,
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
return adapter;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* @param op - Method name for the error message
|
|
1358
|
+
* @param path - Secret path
|
|
1359
|
+
*/
|
|
1360
|
+
function refuseDryRunVaultWrite(op: string, path: string): void {
|
|
1361
|
+
if (isDryRun()) {
|
|
1362
|
+
throw new DryRunWriteIsolationError(
|
|
1363
|
+
`fx.vault.${op}("${path}") cannot isolate writes during dry-run; dry-run refused rather than risk mutating a live secret.`,
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
const vaultSurface: FxVault = {
|
|
1369
|
+
get(secret) {
|
|
1370
|
+
const name = resolveName(secret);
|
|
1371
|
+
return gatedSecret(name, () => {
|
|
1372
|
+
const value = options.vaultRuntime
|
|
1373
|
+
? options.vaultRuntime.read(name)
|
|
1374
|
+
: (secrets[name] ?? `[secret:${name}]`);
|
|
1375
|
+
return new Redacted(value);
|
|
1376
|
+
});
|
|
1377
|
+
},
|
|
1378
|
+
set(path, value, setOptions) {
|
|
1379
|
+
const name = resolveName(path);
|
|
1380
|
+
return gatedSecret(name, async () => {
|
|
1381
|
+
refuseDryRunVaultWrite("set", name);
|
|
1382
|
+
const written = await vaultAdapterFor("set").set(name, value, {
|
|
1383
|
+
...(setOptions?.ttlMs !== undefined ? { ttlMs: setOptions.ttlMs } : {}),
|
|
1384
|
+
...(setOptions?.metadata !== undefined ? { metadata: setOptions.metadata } : {}),
|
|
1385
|
+
actor: vaultActor,
|
|
1386
|
+
});
|
|
1387
|
+
return { path: written.path, version: written.version };
|
|
1388
|
+
});
|
|
1389
|
+
},
|
|
1390
|
+
rotate(path, value) {
|
|
1391
|
+
const name = resolveName(path);
|
|
1392
|
+
return gatedSecret(name, async () => {
|
|
1393
|
+
refuseDryRunVaultWrite("rotate", name);
|
|
1394
|
+
const written = await vaultAdapterFor("rotate").rotate(name, value, {
|
|
1395
|
+
actor: vaultActor,
|
|
1396
|
+
});
|
|
1397
|
+
return { path: written.path, version: written.version };
|
|
1398
|
+
});
|
|
1399
|
+
},
|
|
1400
|
+
delete(path) {
|
|
1401
|
+
const name = resolveName(path);
|
|
1402
|
+
return gatedSecret(name, async () => {
|
|
1403
|
+
refuseDryRunVaultWrite("delete", name);
|
|
1404
|
+
return vaultAdapterFor("delete").delete(name, { actor: vaultActor });
|
|
1405
|
+
});
|
|
1406
|
+
},
|
|
1407
|
+
async list(prefix) {
|
|
1408
|
+
const entries = await vaultAdapterFor("list").list({
|
|
1409
|
+
...(prefix !== undefined ? { prefix } : {}),
|
|
1410
|
+
actor: vaultActor,
|
|
1411
|
+
});
|
|
1412
|
+
return entries.map((entry) => entry.path);
|
|
1413
|
+
},
|
|
1414
|
+
async status() {
|
|
1415
|
+
const adapter = vaultAdapterFor("status");
|
|
1416
|
+
const state = await adapter.status();
|
|
1417
|
+
return { sealed: state.sealed, initialized: state.initialized, backend: adapter.id };
|
|
1418
|
+
},
|
|
1419
|
+
};
|
|
1420
|
+
|
|
1232
1421
|
const runsSurface: FxRuns = {
|
|
1233
1422
|
query(sql) {
|
|
1234
1423
|
return gated("read", RUNS_RESOURCE, async () => {
|
|
@@ -1251,6 +1440,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1251
1440
|
if (!options.runsRuntime) {
|
|
1252
1441
|
throw new Error("fx.runs.window requires a bound runs runtime (oke({ runs }))");
|
|
1253
1442
|
}
|
|
1443
|
+
const { windowStatsForFlow } = await loadRunsWindow();
|
|
1254
1444
|
const events = await options.runsRuntime.all();
|
|
1255
1445
|
return windowStatsForFlow(events, flowName, now(), windowMs);
|
|
1256
1446
|
});
|
|
@@ -1260,6 +1450,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1260
1450
|
if (!options.runsRuntime) {
|
|
1261
1451
|
throw new Error("fx.runs.checkSlo requires a bound runs runtime (oke({ runs }))");
|
|
1262
1452
|
}
|
|
1453
|
+
const { windowStatsForFlow, evaluateSloBreaches } = await loadRunsWindow();
|
|
1263
1454
|
const events = await options.runsRuntime.all();
|
|
1264
1455
|
const stats = windowStatsForFlow(events, flowName, now(), windowMs);
|
|
1265
1456
|
return evaluateSloBreaches(stats, slo);
|
|
@@ -1294,23 +1485,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1294
1485
|
});
|
|
1295
1486
|
},
|
|
1296
1487
|
clock,
|
|
1297
|
-
vault
|
|
1298
|
-
const name = resolveName(secret);
|
|
1299
|
-
// vault is sync in the public examples; check + record synchronously
|
|
1300
|
-
capability.assert("secret", name);
|
|
1301
|
-
const timestamp = now();
|
|
1302
|
-
const value = options.vaultRuntime
|
|
1303
|
-
? options.vaultRuntime.read(name)
|
|
1304
|
-
: (secrets[name] ?? `[secret:${name}]`);
|
|
1305
|
-
ledger.record({
|
|
1306
|
-
kind: "secret",
|
|
1307
|
-
resource: name,
|
|
1308
|
-
timestamp,
|
|
1309
|
-
duration: Math.max(0, now() - timestamp),
|
|
1310
|
-
reversibility: reversibilityOf("secret"),
|
|
1311
|
-
});
|
|
1312
|
-
return new Redacted(value);
|
|
1313
|
-
},
|
|
1488
|
+
vault: vaultSurface,
|
|
1314
1489
|
cache,
|
|
1315
1490
|
send(template, opts) {
|
|
1316
1491
|
const name = resolveName(template);
|
|
@@ -1538,9 +1713,9 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1538
1713
|
return { [jsonResultBrand]: true, status: 200, value: data, meta } as JsonResult<T>;
|
|
1539
1714
|
},
|
|
1540
1715
|
},
|
|
1541
|
-
async step<T>(name: string, fn: () => T | Promise<T>): Promise<T> {
|
|
1716
|
+
async step<T>(name: string, fn: () => T | Promise<T>, opts?: StepOptions<T>): Promise<T> {
|
|
1542
1717
|
if (journal) {
|
|
1543
|
-
return journal.step(name, fn);
|
|
1718
|
+
return journal.step(name, fn, opts);
|
|
1544
1719
|
}
|
|
1545
1720
|
return await fn();
|
|
1546
1721
|
},
|
package/src/kernel/index.ts
CHANGED
|
@@ -136,6 +136,7 @@ export {
|
|
|
136
136
|
type FxThunk,
|
|
137
137
|
type JsonResult,
|
|
138
138
|
type NamedRef,
|
|
139
|
+
type StepOptions,
|
|
139
140
|
} from "./fx.ts";
|
|
140
141
|
|
|
141
142
|
export {
|
|
@@ -177,10 +178,13 @@ export {
|
|
|
177
178
|
createFileJournalStore,
|
|
178
179
|
hasJournalLease,
|
|
179
180
|
isJournalLeaseBusy,
|
|
181
|
+
isJournalRegistrationComplete,
|
|
180
182
|
isJournalSuspend,
|
|
181
183
|
JournalLeaseBusy,
|
|
184
|
+
JournalRegistrationComplete,
|
|
182
185
|
JournalSuspend,
|
|
183
186
|
JOURNAL_DEFAULT_LEASE_MS,
|
|
187
|
+
JOURNAL_UNDO_PREFIX,
|
|
184
188
|
type Journal,
|
|
185
189
|
type JournalEntry,
|
|
186
190
|
type JournalLeaseOptions,
|
|
@@ -188,10 +192,20 @@ export {
|
|
|
188
192
|
type JournalRun,
|
|
189
193
|
type JournalRunStatus,
|
|
190
194
|
type JournalSession,
|
|
195
|
+
type JournalStepOptions,
|
|
191
196
|
type JournalStore,
|
|
197
|
+
type JournalUndoFrame,
|
|
192
198
|
type CreateJournalOptions,
|
|
193
199
|
} from "./journal.ts";
|
|
194
200
|
|
|
201
|
+
export {
|
|
202
|
+
failureCodeOf,
|
|
203
|
+
forwardCompletedSteps,
|
|
204
|
+
rebindUndosFromDo,
|
|
205
|
+
runCompensationPhase,
|
|
206
|
+
type RunCompensationPhaseOptions,
|
|
207
|
+
} from "./compensate.ts";
|
|
208
|
+
|
|
195
209
|
export {
|
|
196
210
|
AFTER_HANDLER_STAGES,
|
|
197
211
|
BEFORE_HANDLER_STAGES,
|