okengine 0.21.1 → 0.23.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/AGENTS.md +11 -4
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +15 -9
- package/manifest.v1.schema.json +54 -0
- package/package.json +53 -38
- package/site/content/docs/ai/mcp.mdx +13 -9
- package/site/content/docs/ai/skills.mdx +2 -2
- package/site/content/docs/client/calling.mdx +27 -19
- package/site/content/docs/client/index.mdx +1 -1
- package/site/content/docs/elements/ai/agents.mdx +61 -13
- package/site/content/docs/elements/ai/decide.mdx +536 -0
- package/site/content/docs/elements/ai/events.mdx +373 -0
- package/site/content/docs/elements/ai/index.mdx +9 -3
- package/site/content/docs/elements/ai/meta.json +1 -1
- package/site/content/docs/elements/ai/prompts.mdx +4 -2
- package/site/content/docs/elements/channel/email.mdx +1 -1
- package/site/content/docs/elements/channel/index.mdx +1 -1
- package/site/content/docs/elements/flow/http.mdx +4 -4
- package/site/content/docs/elements/flow/index.mdx +3 -3
- package/site/content/docs/elements/flow/meta.json +1 -1
- package/site/content/docs/elements/gate/auth.mdx +4 -1
- package/site/content/docs/elements/gate/tenancy.mdx +1 -1
- package/site/content/docs/elements/store/index.mdx +1 -1
- package/site/content/docs/elements/store/kv.mdx +5 -5
- package/site/content/docs/elements/vault/rotation.mdx +15 -0
- package/site/content/docs/plugins/otp.mdx +31 -31
- package/site/content/docs/recipes/dragonfly.mdx +5 -5
- package/site/content/docs/recipes/index.mdx +6 -6
- package/site/content/docs/recipes/mailpit.mdx +1 -1
- package/site/content/docs/recipes/meilisearch.mdx +1 -1
- package/site/content/docs/recipes/pgdog.mdx +1 -1
- package/site/content/docs/recipes/postgres.mdx +12 -8
- package/site/content/docs/recipes/redis.mdx +4 -4
- package/site/content/docs/reference/cli.mdx +2 -1
- package/site/content/docs/reference/configuration.mdx +11 -9
- package/site/content/docs/reference/errors.mdx +12 -2
- package/site/content/docs/reference/fx.mdx +35 -21
- package/site/content/docs/reference/idempotency.mdx +192 -0
- package/site/content/docs/reference/index.mdx +6 -1
- package/site/content/docs/reference/meta.json +2 -1
- package/site/content/docs/understand/meta.json +1 -1
- package/site/content/docs/{elements/flow → understand}/routing.mdx +11 -10
- package/site/content/docs/understand/the-architecture.mdx +14 -12
- package/site/content/docs/understand/try-it.mdx +3 -3
- package/src/auth/api-key-sql.test.ts +44 -0
- package/src/auth/api-key-sql.ts +1 -0
- package/src/auth/api-keys.ts +30 -12
- package/src/auth/config.ts +6 -2
- package/src/auth/gate-auth.test.ts +20 -0
- package/src/cli/agents-md.test.ts +25 -0
- package/src/cli/ask-vault-gaps.test.ts +38 -1
- package/src/cli/ask-vault-gaps.ts +35 -2
- package/src/cli/competitor-mention-removal.test.ts +3 -3
- package/src/cli/decide.ts +174 -0
- package/src/cli/decision-lock-watch.test.ts +45 -0
- package/src/cli/decision-lock-watch.ts +51 -0
- package/src/cli/dev-app-runner.ts +34 -8
- package/src/cli/dev-auth-secret.test.ts +49 -0
- package/src/cli/dev-auth-secret.ts +49 -0
- package/src/cli/dev-hot.test.ts +41 -0
- package/src/cli/dev-hot.ts +48 -0
- package/src/cli/dev.test.ts +1 -1
- package/src/cli/dev.ts +24 -6
- package/src/cli/docker-cli.test.ts +1 -1
- package/src/cli/eval.ts +142 -1
- package/src/cli/index.ts +14 -1
- package/src/cli/load-config.images.test.ts +12 -12
- package/src/cli/load-config.ts +2 -2
- package/src/cli/mcp-from-console.ts +48 -0
- package/src/cli/registry.ts +35 -1
- package/src/cli/vault-cmd.test.ts +23 -0
- package/src/cli/vault-cmd.ts +7 -0
- package/src/client/agent.ts +307 -0
- package/src/client/create.ts +18 -1
- package/src/client/transport.test.ts +245 -0
- package/src/client/transport.ts +133 -19
- package/src/client/types.ts +29 -3
- package/src/client-react/index.ts +7 -1
- package/src/client-react/use-agent-run.test.ts +271 -0
- package/src/client-react/use-agent-run.ts +193 -0
- package/src/compiler/ai-approval.test.ts +36 -0
- package/src/compiler/ai-repair.test.ts +22 -0
- package/src/compiler/decisions.extract.test.ts +164 -0
- package/src/compiler/effects-infer.ts +753 -17
- package/src/compiler/extract.test.ts +37 -0
- package/src/compiler/extract.ts +381 -24
- package/src/compiler/fixtures/skyport.expected.json +24 -0
- package/src/compiler/fx-follow.test.ts +164 -0
- package/src/compiler/fx-index.ts +399 -0
- package/src/compiler/response.ts +35 -2
- package/src/config/index.ts +21 -3
- package/src/console/server/ai-runs-flows.ts +269 -0
- package/src/console/server/ai-runs.test.ts +323 -0
- package/src/console/server/ai-runs.ts +562 -0
- package/src/console/server/ai.ts +12 -1
- package/src/console/server/app.ts +10 -1
- package/src/console/server/decisions-flows.ts +157 -0
- package/src/console/server/decisions.test.ts +66 -0
- package/src/console/server/decisions.ts +229 -0
- package/src/console/server/flows-invoke.test.ts +2 -2
- package/src/console/server/flows.ts +50 -2
- package/src/console/server/invoke-user-flow.ts +9 -0
- package/src/console/server/runs-ingest.ts +16 -0
- package/src/console/server/serve.ts +5 -0
- package/src/console/server/state.ts +25 -1
- package/src/console/server/store-stats.test.ts +47 -0
- package/src/console/server/store-stats.ts +92 -18
- package/src/console/ui-next/dist/assets/access-page-DDKkhT9v.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-62Xts8Xi.js +1 -0
- package/src/console/ui-next/dist/assets/{cache-glyph-92uM5MO7.js → cache-glyph-DyeoKHKA.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-DtGVPtcs.js → call-pii-button-DvfpLXsU.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-DN7l6zmC.js → collapsible-BY03SeCg.js} +1 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-DZfuJXP4.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-BTjPVjRT.js +1 -0
- package/src/console/ui-next/dist/assets/decisions-page-CgkKlA56.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-Dk5LipF6.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-CRGrj5JU.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-Y6HLjyJ5.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-C6t8Vlml.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-chrome-Dslh2yE8.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CIVg7Jgb.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-DLPfNy-_.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-BlAEVgNW.js → highlighted-json-D7nNzpgB.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-BDf7OAHv.js → http-method-DdL19zzh.js} +1 -1
- package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
- package/src/console/ui-next/dist/assets/index-C0lc_s9d.js +58 -0
- package/src/console/ui-next/dist/assets/observability-page-CRlcyLCC.js +8 -0
- package/src/console/ui-next/dist/assets/{replica-lag-B3GLNVfF.js → replica-lag-Bb3FWUu9.js} +7 -7
- package/src/console/ui-next/dist/assets/request-meta-DYBMWhX8.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-DTE1sjTC.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-CcE-SXC8.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CKMSEQ4s.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-QOfk0M0H.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-DKC1CDDd.js +1 -0
- package/src/console/ui-next/dist/assets/use-vault-list-BAyQG8Re.js +1 -0
- package/src/console/ui-next/dist/assets/vault-page-B54v4Yit.js +2 -0
- package/src/console/ui-next/dist/index.html +4 -3
- package/src/console/ui-next/seed-parked-approval.ts +126 -0
- package/src/console/ui-next/src/client.ts +224 -1
- package/src/console/ui-next/src/components/explorer/detail-header.tsx +9 -0
- package/src/console/ui-next/src/components/explorer/explorer-start-toggle.tsx +5 -1
- package/src/console/ui-next/src/components/ui/sheet-form.tsx +21 -12
- package/src/console/ui-next/src/features/flows/decisions/decisions-page.tsx +216 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.ts +111 -0
- package/src/console/ui-next/src/features/flows/flows-page.tsx +10 -0
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +1 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +6 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.test.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-status.ts +58 -0
- package/src/console/ui-next/src/features/flows/traces/request-client-card.tsx +355 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.test.ts +114 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.ts +423 -0
- package/src/console/ui-next/src/features/flows/traces/request-input-view.test.ts +52 -6
- package/src/console/ui-next/src/features/flows/traces/request-input-view.ts +85 -19
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +505 -80
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.test.ts +61 -0
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.ts +78 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.ts +57 -0
- package/src/console/ui-next/src/features/flows/traces/trace-request-section.tsx +275 -96
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +16 -2
- package/src/console/ui-next/src/features/observability/detail/ai-runs.tsx +369 -0
- package/src/console/ui-next/src/features/observability/observability-page.tsx +31 -1
- package/src/console/ui-next/src/features/observability/state/observability-selection.ts +18 -3
- package/src/console/ui-next/src/features/store/performance/kv-performance-panel.tsx +12 -1
- package/src/console/ui-next/src/features/store/performance/performance-panel.tsx +14 -1
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +1 -1
- package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +15 -2
- package/src/console/ui-next/src/features/units/explorer/units-tree.tsx +73 -18
- package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +21 -0
- package/src/console/ui-next/src/features/units/lib/unit-tree.ts +82 -0
- package/src/console/ui-next/src/features/units/units-page.tsx +25 -7
- package/src/console/ui-next/src/router.tsx +17 -0
- package/src/console/ui-next/ui-next-seed-runs.ts +23 -0
- package/src/console/ui-next/vite-console-kernel-plugin.ts +43 -3
- package/src/console/ui-next/vite.config.ts +9 -0
- package/src/docker/docker.test.ts +13 -13
- package/src/docker/images-config.test.ts +12 -12
- package/src/docker/recipes/dragonfly.ts +3 -2
- package/src/docker/recipes/redis.ts +1 -1
- package/src/docker/stack-id.test.ts +1 -1
- package/src/drivers/ai-anthropic.ts +212 -21
- package/src/drivers/ai-mock.ts +34 -1
- package/src/drivers/ai-openai-compatible.ts +67 -26
- package/src/drivers/ai-preconnect.ts +18 -0
- package/src/drivers/ai-stream.test.ts +62 -0
- package/src/drivers/ai-types.ts +20 -0
- package/src/drivers/journal-postgres.ts +455 -3
- package/src/drivers/meilisearch.ts +2 -0
- package/src/drivers/memory-ddl.test.ts +18 -0
- package/src/drivers/memory.ts +21 -1
- package/src/drivers/pg-rls.test.ts +31 -0
- package/src/drivers/pg-rls.ts +15 -0
- package/src/drivers/postgres.test.ts +9 -0
- package/src/drivers/postgres.ts +49 -11
- package/src/drivers/vault-builtin.test.ts +10 -0
- package/src/drivers/vault-builtin.ts +13 -1
- package/src/drivers/vault-remote-bag.ts +5 -1
- package/src/drivers/vault-types.ts +6 -1
- package/src/elements/ai/agent-event-slot.ts +27 -0
- package/src/elements/ai/approval-http.ts +207 -0
- package/src/elements/ai/approval.test.ts +741 -0
- package/src/elements/ai/approval.ts +228 -0
- package/src/elements/ai/decisions/bind.ts +163 -0
- package/src/elements/ai/decisions/certificate.test.ts +291 -0
- package/src/elements/ai/decisions/certificate.ts +377 -0
- package/src/elements/ai/decisions/certify.ts +314 -0
- package/src/elements/ai/decisions/decide.test.ts +953 -0
- package/src/elements/ai/decisions/e2e.test.ts +399 -0
- package/src/elements/ai/decisions/export.test.ts +120 -0
- package/src/elements/ai/decisions/export.ts +146 -0
- package/src/elements/ai/decisions/fixtures/openrouter-request.json +24 -0
- package/src/elements/ai/decisions/fixtures/openrouter-response.json +22 -0
- package/src/elements/ai/decisions/fixtures/typesafe-request.json +19 -0
- package/src/elements/ai/decisions/fixtures/typesafe-response.json +13 -0
- package/src/elements/ai/decisions/http.test.ts +154 -0
- package/src/elements/ai/decisions/http.ts +240 -0
- package/src/elements/ai/decisions/labels.test.ts +282 -0
- package/src/elements/ai/decisions/labels.ts +257 -0
- package/src/elements/ai/decisions/openrouter.live.test.ts +136 -0
- package/src/elements/ai/decisions/openrouter.ts +36 -0
- package/src/elements/ai/decisions/provider.ts +102 -0
- package/src/elements/ai/decisions/typesafe.ts +36 -0
- package/src/elements/ai/declare.ts +252 -1
- package/src/elements/ai/events.test.ts +379 -0
- package/src/elements/ai/events.ts +138 -0
- package/src/elements/ai/run-events.test.ts +461 -0
- package/src/elements/ai/run-events.ts +522 -0
- package/src/elements/ai/runtime.ts +1186 -105
- package/src/elements/ai/stream-turn.ts +214 -0
- package/src/elements/ai/stream.live.test.ts +76 -0
- package/src/elements/ai/subagent.test.ts +136 -0
- package/src/elements/ai.test.ts +497 -3
- package/src/elements/ai.ts +16 -1
- package/src/elements/clock/durable.ts +27 -1
- package/src/elements/store/emit-drizzle.ts +2 -2
- package/src/elements/store/live-default.test.ts +2 -0
- package/src/elements/store/live-http.test.ts +1 -0
- package/src/elements/store/resource-list-docs.test.ts +2 -2
- package/src/elements/store/resource.test.ts +3 -3
- package/src/elements/store/schema-decl.test.ts +1 -0
- package/src/elements/store/sql-condition.ts +24 -7
- package/src/elements/store/sql-session.test.ts +51 -0
- package/src/elements/store/sql-session.ts +111 -1
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/vault/audit.test.ts +137 -0
- package/src/elements/vault/audit.ts +106 -13
- package/src/elements/vault/boot-chain.ts +9 -1
- package/src/elements/vault/builtin-adapter.ts +33 -3
- package/src/i18n/catalogs/ar.ts +9 -0
- package/src/i18n/catalogs/en.ts +9 -0
- package/src/index.ts +2 -0
- package/src/kernel/abort-scope.ts +33 -2
- package/src/kernel/agent-event-store.ts +448 -0
- package/src/kernel/app.ts +294 -19
- package/src/kernel/auto-cache.test.ts +6 -6
- package/src/kernel/boot-bind/ai.ts +3 -0
- package/src/kernel/boot-bind/store.test.ts +26 -0
- package/src/kernel/boot-bind/store.ts +82 -6
- package/src/kernel/boot-bind/vault.ts +1 -0
- package/src/kernel/boot.ts +34 -5
- package/src/kernel/builtin-errors.ts +10 -0
- package/src/kernel/capability.ts +3 -0
- package/src/kernel/client-descriptor.ts +1 -0
- package/src/kernel/concurrency.test.ts +33 -0
- package/src/kernel/decision-label-store.ts +441 -0
- package/src/kernel/effects-stamping.test.ts +2 -2
- package/src/kernel/effects.test.ts +2 -1
- package/src/kernel/effects.ts +13 -1
- package/src/kernel/element-registries.ts +3 -0
- package/src/kernel/errors-compiler.ts +20 -0
- package/src/kernel/errors-text.ts +4 -0
- package/src/kernel/errors.ts +2 -0
- package/src/kernel/external-effects.test.ts +36 -0
- package/src/kernel/flow.test.ts +2 -3
- package/src/kernel/flow.ts +24 -0
- package/src/kernel/fx-decide.ts +761 -0
- package/src/kernel/fx-fetch.ts +5 -1
- package/src/kernel/fx.test.ts +12 -5
- package/src/kernel/fx.ts +316 -29
- package/src/kernel/http-frame.test.ts +94 -0
- package/src/kernel/http-frame.ts +203 -0
- package/src/kernel/idempotency-store.ts +578 -0
- package/src/kernel/idempotency.test.ts +556 -0
- package/src/kernel/idempotency.ts +304 -0
- package/src/kernel/index.ts +1 -0
- package/src/kernel/journal.ts +160 -0
- package/src/kernel/json-result.ts +10 -0
- package/src/kernel/pipeline.test.ts +45 -0
- package/src/kernel/sse-id.ts +32 -0
- package/src/manifest/diff.ts +1 -0
- package/src/manifest/types.ts +37 -2
- package/src/mcp/ai-tools.test.ts +202 -0
- package/src/mcp/authorization.ts +66 -0
- package/src/mcp/session.ts +3 -0
- package/src/mcp/tools.ts +168 -0
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
- package/src/plugins/otp.test.ts +65 -1
- package/src/plugins/otp.ts +59 -7
- package/src/release/absolute-regression.test.ts +63 -0
- package/src/release/http-graph.test.ts +27 -0
- package/src/release/http-graph.ts +95 -0
- package/src/release/index.ts +3 -0
- package/src/release/limits.ts +17 -2
- package/src/release/measure.ts +144 -28
- package/src/runs/collect.ts +4 -1
- package/src/runs/parquet.test.ts +27 -1
- package/src/runs/parquet.ts +19 -0
- package/src/runs/types.ts +31 -0
- package/src/runtime/json-code-block.test.ts +106 -47
- package/src/runtime/json-code-block.ts +1209 -46
- package/src/term.test.ts +13 -0
- package/src/term.ts +81 -49
- package/src/test/create-test-app.test.ts +3 -1
- package/src/test/create-test-app.ts +40 -0
- package/src/test/live-signals.test.ts +1 -0
- package/src/test/provisions.integration.test.ts +1 -0
- package/src/test/tenant-isolation.test.ts +1 -0
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
- package/src/console/ui-next/dist/assets/access-page-Bgt9bq2r.js +0 -4
- package/src/console/ui-next/dist/assets/agent-disclosure-B43CXZZR.js +0 -1
- package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-BmIR9FV8.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-BMHs-IzK.js +0 -1
- package/src/console/ui-next/dist/assets/index-BKpaes3n.js +0 -63
- package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
- package/src/console/ui-next/dist/assets/observability-page-WnVLI-0j.js +0 -4
- package/src/console/ui-next/dist/assets/request-meta-DMbnAe3f.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-KvFDingJ.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-Htk8Cm9t.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CFMPWX4f.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-C4NdNuxP.js +0 -1
- package/src/console/ui-next/dist/assets/use-vault-list-CT4-gajj.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-CBYl0LW_.js +0 -2
package/src/kernel/app.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* pipeline, and wires `fx.call` to untriggered (and triggered) flows.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { bindAgentApprovalFlows } from "../elements/ai/approval-http.ts";
|
|
9
10
|
import { compileRoute } from "../compiler/dynamic.ts";
|
|
10
11
|
import type { CompiledRoute } from "../compiler/aot.ts";
|
|
11
12
|
import { encodeExecuteResult, encodeFailure } from "../compiler/response.ts";
|
|
@@ -43,7 +44,7 @@ import { parseAcceptLanguage } from "../elements/channel/locale.ts";
|
|
|
43
44
|
import { runWithLocale } from "../i18n/locale-context.ts";
|
|
44
45
|
import { isFlow, type AnyFlowDef } from "./flow.ts";
|
|
45
46
|
import { failureFromUnknown, runCompensationPhase } from "./compensate.ts";
|
|
46
|
-
import { withAbortSignal } from "./abort-scope.ts";
|
|
47
|
+
import { runDetached, withAbortSignal } from "./abort-scope.ts";
|
|
47
48
|
import { withCdcMutationId } from "../elements/store/sql-session.ts";
|
|
48
49
|
import { MUTATION_ID_HEADER } from "./mutation-id.ts";
|
|
49
50
|
import { fxRetry } from "./concurrency.ts";
|
|
@@ -67,6 +68,7 @@ import {
|
|
|
67
68
|
import { GATE_PUBLIC_NAME } from "../elements/gate/flatten.ts";
|
|
68
69
|
import type { JsonCodeAuth } from "../runtime/json-code-block.ts";
|
|
69
70
|
import { resolveDurationMs } from "./elapsed.ts";
|
|
71
|
+
import { captureHttpFrame } from "./http-frame.ts";
|
|
70
72
|
import { fail, throwOke, OkeError } from "./errors.ts";
|
|
71
73
|
import { FLOW_NAME_DUPLICATE, FLOW_UNNAMED } from "./errors-flow-name.ts";
|
|
72
74
|
import { ONCE_SIGNAL_MULTI_FLOW } from "./errors-once-signal.ts";
|
|
@@ -83,6 +85,7 @@ import {
|
|
|
83
85
|
} from "./hooks.ts";
|
|
84
86
|
import {
|
|
85
87
|
createJournal,
|
|
88
|
+
createJournalSlot,
|
|
86
89
|
createMemoryJournalStore,
|
|
87
90
|
hasJournalLease,
|
|
88
91
|
isJournalLeaseBusy,
|
|
@@ -91,6 +94,7 @@ import {
|
|
|
91
94
|
type JournalSession,
|
|
92
95
|
type JournalStore,
|
|
93
96
|
} from "./journal.ts";
|
|
97
|
+
import type { IdempotencyAttempt } from "./idempotency.ts";
|
|
94
98
|
import { releaseInstanceLeases } from "./graceful-shutdown.ts";
|
|
95
99
|
import { mintInstanceId } from "./instance-id.ts";
|
|
96
100
|
import type { JournalRuntime } from "./boot-bind/journal.ts";
|
|
@@ -181,6 +185,11 @@ function loadFx(): {
|
|
|
181
185
|
return lazyRequire(import.meta.dir, ["fx", "runtime"].join("-"));
|
|
182
186
|
}
|
|
183
187
|
|
|
188
|
+
/** Idempotency policy — off the edge graph until a request actually claims. */
|
|
189
|
+
function loadIdempotency(): typeof import("./idempotency.ts") {
|
|
190
|
+
return lazyRequire(import.meta.dir, ["idem", "potency"].join(""));
|
|
191
|
+
}
|
|
192
|
+
|
|
184
193
|
/** App-shell tenancy wiring — loaded only when `gate.auth.tenant` is on. */
|
|
185
194
|
function loadAppTenant(): { w: (kind: number, ...args: unknown[]) => unknown } {
|
|
186
195
|
return lazyRequire(import.meta.dir, ["app", "tenant"].join("-"));
|
|
@@ -210,6 +219,39 @@ function loadClientDescriptor(): typeof import("./client-descriptor.ts") {
|
|
|
210
219
|
return lazyRequire(import.meta.dir, ["client", "descriptor"].join("-"));
|
|
211
220
|
}
|
|
212
221
|
|
|
222
|
+
/** Decision HTTP flows. A static `import()` still lands on every HTTP bundle. */
|
|
223
|
+
function loadDecisionBind(): typeof import("../elements/ai/decisions/bind.ts") {
|
|
224
|
+
return lazyRequire(`${import.meta.dir}/../elements/ai/decisions`, ["bi", "nd"].join(""));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Decision lockfile. Loaded only when the manifest declares decisions. */
|
|
228
|
+
function loadDecisionCertificate(): typeof import("../elements/ai/decisions/certificate.ts") {
|
|
229
|
+
return lazyRequire(`${import.meta.dir}/../elements/ai/decisions`, ["certificate"].join(""));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Decision label opener. Loaded only when the manifest declares decisions. */
|
|
233
|
+
function loadDecisionLabels(): typeof import("../elements/ai/decisions/labels.ts") {
|
|
234
|
+
return lazyRequire(`${import.meta.dir}/../elements/ai/decisions`, ["lab", "els"].join(""));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Drift-flag registration. Loaded only when the manifest declares decisions. */
|
|
238
|
+
function loadDecisionLabelStore(): typeof import("./decision-label-store.ts") {
|
|
239
|
+
return lazyRequire(import.meta.dir, ["decision", "label", "store"].join("-"));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** `fx.decide` gate hook. Loaded only when the manifest declares decisions. */
|
|
243
|
+
function loadFxDecideModule(): typeof import("./fx-decide.ts") {
|
|
244
|
+
return lazyRequire(import.meta.dir, ["fx", "decide"].join("-"));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Full boot graph (drivers, elements). A static `import()` string is still
|
|
249
|
+
* bundled into every HTTP app; the stem stays computed.
|
|
250
|
+
*/
|
|
251
|
+
function loadBoot(): typeof import("./boot.ts") {
|
|
252
|
+
return lazyRequire(import.meta.dir, ["bo", "ot"].join(""));
|
|
253
|
+
}
|
|
254
|
+
|
|
213
255
|
/** Options for {@link oke}. */
|
|
214
256
|
export interface OkeOptions {
|
|
215
257
|
/** Application name (Manifest `app`). */
|
|
@@ -1088,6 +1130,8 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1088
1130
|
// contribute auth-method Bindings before traffic (plan Phase 2).
|
|
1089
1131
|
const router: Router<Binding> = smart;
|
|
1090
1132
|
|
|
1133
|
+
bindAgentApprovalFlows(adoptBinding);
|
|
1134
|
+
|
|
1091
1135
|
function adoptBinding(b: Binding): void {
|
|
1092
1136
|
adopted.push(b);
|
|
1093
1137
|
registerFlow(b.flow);
|
|
@@ -1108,6 +1152,24 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1108
1152
|
|
|
1109
1153
|
/** Once-per-app: system `_oke_search_embed_*` CDC bindings from Manifest. */
|
|
1110
1154
|
let searchEmbedFlowsBound = false;
|
|
1155
|
+
let decisionFlowsBound = false;
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Register the decision aggregate, drift monitor, and candidate route
|
|
1159
|
+
* when any decision declares autonomy.
|
|
1160
|
+
*
|
|
1161
|
+
* @param manifest - Boot manifest
|
|
1162
|
+
*/
|
|
1163
|
+
async function ensureDecisionFlows(manifest: BootOptions["manifest"] | undefined): Promise<void> {
|
|
1164
|
+
if (decisionFlowsBound || !manifest) return;
|
|
1165
|
+
const { bindDecisionFlows, manifestHasDecisionAutonomy } = loadDecisionBind();
|
|
1166
|
+
if (!manifestHasDecisionAutonomy(manifest)) {
|
|
1167
|
+
decisionFlowsBound = true;
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
bindDecisionFlows(adoptBinding, manifest);
|
|
1171
|
+
decisionFlowsBound = true;
|
|
1172
|
+
}
|
|
1111
1173
|
/**
|
|
1112
1174
|
* Auto-register hybrid-search embed CDC flows when any `.embed()` column
|
|
1113
1175
|
* exists. Lazy-loads search-bind so edge graphs without search stay lean.
|
|
@@ -1332,6 +1394,19 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1332
1394
|
...durableResumeFx(),
|
|
1333
1395
|
...(gateConfig.auth?.tenant ? (loadAppTenant().w(5, gateConfig.auth) as object) : {}),
|
|
1334
1396
|
...(existing?.tenant ? { tenant: { id: existing.tenant } } : {}),
|
|
1397
|
+
callHandler: async (name, callInput) => {
|
|
1398
|
+
const target = flowsByName.get(name);
|
|
1399
|
+
if (!target) return undefined;
|
|
1400
|
+
const inner = await execute(
|
|
1401
|
+
target,
|
|
1402
|
+
callInput,
|
|
1403
|
+
{ kind: "internal" } satisfies InternalTrigger,
|
|
1404
|
+
{ parentId: runId },
|
|
1405
|
+
);
|
|
1406
|
+
if (inner.failure) return inner.failure;
|
|
1407
|
+
if (inner.ctx.error !== undefined) throw inner.ctx.error;
|
|
1408
|
+
return inner.output;
|
|
1409
|
+
},
|
|
1335
1410
|
},
|
|
1336
1411
|
});
|
|
1337
1412
|
} catch (err) {
|
|
@@ -1360,7 +1435,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1360
1435
|
}
|
|
1361
1436
|
|
|
1362
1437
|
async function doBoot(overrides?: Partial<BootOptions>): Promise<BootResult> {
|
|
1363
|
-
const { bootApplication, resolveElementNeeds } =
|
|
1438
|
+
const { bootApplication, resolveElementNeeds } = loadBoot();
|
|
1364
1439
|
const { assertHttpGatePosture } = await import("../elements/gate/boot.ts");
|
|
1365
1440
|
const { assertPluginNeeds, buildAvailableNeedTokens } = await import("./plugin-needs.ts");
|
|
1366
1441
|
const dockerFlag =
|
|
@@ -1381,6 +1456,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1381
1456
|
}
|
|
1382
1457
|
|
|
1383
1458
|
const unguardedHttp = overrides?.unguardedHttp ?? gateConfig.unguardedHttp ?? "deny";
|
|
1459
|
+
await ensureDecisionFlows(overrides?.manifest ?? options.manifest);
|
|
1384
1460
|
// Auth bindings are in `adopted` — missing posture fails with GateBootError.
|
|
1385
1461
|
assertHttpGatePosture(adopted, { unguardedHttp, env: bootEnv });
|
|
1386
1462
|
|
|
@@ -1550,6 +1626,26 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1550
1626
|
// Built-in hybrid search — auto-register durable embed CDC flows when the
|
|
1551
1627
|
// Manifest declares any `.embed()` column (writer flows stay embed-free).
|
|
1552
1628
|
await ensureSearchEmbedFlows(overrides?.manifest ?? options.manifest);
|
|
1629
|
+
const decisionManifest = overrides?.manifest ?? options.manifest;
|
|
1630
|
+
const declaredDecisions = decisionManifest?.ai?.decisions;
|
|
1631
|
+
if (declaredDecisions && Object.keys(declaredDecisions).length > 0) {
|
|
1632
|
+
const { loadDecisionLockfile, setDecisionDrift } = loadDecisionCertificate();
|
|
1633
|
+
const { openDecisionLabelStore } = loadDecisionLabels();
|
|
1634
|
+
const { setDeclaredDriftDecisions } = loadDecisionLabelStore();
|
|
1635
|
+
const { setDecisionGateAllow } = loadFxDecideModule();
|
|
1636
|
+
const root = overrides?.rootDir ?? options.rootDir ?? process.env["OKE_ROOT_DIR"];
|
|
1637
|
+
if (!root) {
|
|
1638
|
+
throw new Error(
|
|
1639
|
+
"oke boot: decisions are declared but rootDir and OKE_ROOT_DIR are unset, so the decision lockfile cannot load.",
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
await loadDecisionLockfile(root);
|
|
1643
|
+
setDeclaredDriftDecisions(Object.keys(declaredDecisions));
|
|
1644
|
+
if (result.journal && result.journal.store.decisions) {
|
|
1645
|
+
await openDecisionLabelStore(result.journal.store, setDecisionDrift);
|
|
1646
|
+
}
|
|
1647
|
+
if (result.gate) setDecisionGateAllow((names, ctx) => result.gate!.allow(names, ctx));
|
|
1648
|
+
}
|
|
1553
1649
|
// otp() provider / app mode capability — fail loud at boot, never silent downgrade.
|
|
1554
1650
|
if (result.channel) {
|
|
1555
1651
|
const { assertOtpPluginCapability } = await import("../auth/otp-capability.ts");
|
|
@@ -1837,23 +1933,61 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1837
1933
|
);
|
|
1838
1934
|
|
|
1839
1935
|
capability = booted.capabilities.get(flowDef.name);
|
|
1840
|
-
|
|
1841
|
-
if (flowDef.durable) {
|
|
1842
|
-
const { store, instanceId, leaseMs } = activeJournal();
|
|
1843
|
-
const journal = createJournal({
|
|
1844
|
-
store,
|
|
1845
|
-
now,
|
|
1846
|
-
id: () => runId,
|
|
1847
|
-
// Hold the run lease for the request's lifetime — a crash mid-run
|
|
1848
|
-
// leaves an expired lease another instance can reclaim and resume.
|
|
1849
|
-
...(hasJournalLease(store) ? { lease: { instanceId, leaseMs } } : {}),
|
|
1850
|
-
});
|
|
1851
|
-
journalSession = await journal.start(flowDef.name, input);
|
|
1852
|
-
inflightRuns.add(journalSession.runId);
|
|
1853
|
-
}
|
|
1854
1936
|
}
|
|
1855
1937
|
|
|
1856
1938
|
const effects = flowDef.effects ?? capability?.declared;
|
|
1939
|
+
const requestForIdem = extras?.request;
|
|
1940
|
+
const rawIdemHeader = requestForIdem?.headers.get("idempotency-key") ?? null;
|
|
1941
|
+
const idemRequired =
|
|
1942
|
+
flowDef.idempotency === "required" ||
|
|
1943
|
+
(typeof flowDef.idempotency === "object" && flowDef.idempotency.required === true) ||
|
|
1944
|
+
flowDef.resolvedIdempotency?.mode === "required";
|
|
1945
|
+
let idemAttempt: IdempotencyAttempt | undefined;
|
|
1946
|
+
if (requestForIdem && (rawIdemHeader !== null || idemRequired)) {
|
|
1947
|
+
const method = trigger.kind === "http" ? trigger.method : requestForIdem.method;
|
|
1948
|
+
const live =
|
|
1949
|
+
flowDef.live !== undefined || (trigger.kind === "http" && trigger.liveSignal !== undefined);
|
|
1950
|
+
idemAttempt = loadIdempotency().prepareIdempotencyAttempt({
|
|
1951
|
+
hasRequest: true,
|
|
1952
|
+
method,
|
|
1953
|
+
stream: flowDef.stream === true,
|
|
1954
|
+
live,
|
|
1955
|
+
effects,
|
|
1956
|
+
usesRaw: flowDef.usesRaw === true,
|
|
1957
|
+
option: flowDef.idempotency,
|
|
1958
|
+
resolved: flowDef.resolvedIdempotency,
|
|
1959
|
+
header: rawIdemHeader,
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
const claiming = idemAttempt?.claiming === true;
|
|
1963
|
+
|
|
1964
|
+
const openJournal = (): ReturnType<typeof createJournal> => {
|
|
1965
|
+
const { store, instanceId, leaseMs } = activeJournal();
|
|
1966
|
+
return createJournal({
|
|
1967
|
+
store,
|
|
1968
|
+
now,
|
|
1969
|
+
id: () => runId,
|
|
1970
|
+
// Hold the run lease for the request's lifetime — a crash mid-run
|
|
1971
|
+
// leaves an expired lease another instance can reclaim and resume.
|
|
1972
|
+
...(hasJournalLease(store) ? { lease: { instanceId, leaseMs } } : {}),
|
|
1973
|
+
});
|
|
1974
|
+
};
|
|
1975
|
+
|
|
1976
|
+
// A claim starts the journal only after it wins, so a replay or a 409
|
|
1977
|
+
// does not open a run. Auto with no header keeps the start here.
|
|
1978
|
+
if (booted && flowDef.durable && !claiming) {
|
|
1979
|
+
journalSession = await openJournal().start(flowDef.name, input);
|
|
1980
|
+
inflightRuns.add(journalSession.runId);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
let journalSlot: { session?: JournalSession } | undefined;
|
|
1984
|
+
let journalFacade: JournalSession | undefined;
|
|
1985
|
+
if (booted && flowDef.durable && claiming) {
|
|
1986
|
+
const created = createJournalSlot();
|
|
1987
|
+
journalSlot = created.slot;
|
|
1988
|
+
journalFacade = created.facade;
|
|
1989
|
+
}
|
|
1990
|
+
const journalForFx = journalSession ?? journalFacade;
|
|
1857
1991
|
const { fx, ledger } = loadFx().createFxContext({
|
|
1858
1992
|
...options.fx,
|
|
1859
1993
|
flow: flowDef.name,
|
|
@@ -1906,7 +2040,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1906
2040
|
}
|
|
1907
2041
|
: {}),
|
|
1908
2042
|
runsRuntime: booted?.runs ?? (isRunsRuntimeLike(options.runs) ? options.runs : undefined),
|
|
1909
|
-
...(
|
|
2043
|
+
...(journalForFx ? { journal: journalForFx, durable: true } : {}),
|
|
1910
2044
|
callHandler: async (name, callInput) => {
|
|
1911
2045
|
const target = flowsByName.get(name);
|
|
1912
2046
|
if (!target) {
|
|
@@ -2038,13 +2172,141 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
2038
2172
|
}
|
|
2039
2173
|
return output;
|
|
2040
2174
|
};
|
|
2175
|
+
const runClaimed = async (validated: unknown): Promise<unknown> => {
|
|
2176
|
+
if (!claiming || idemAttempt === undefined) return invoke(validated);
|
|
2177
|
+
const policy = loadIdempotency();
|
|
2178
|
+
const classified = policy.classifyIdempotencyKey(rawIdemHeader);
|
|
2179
|
+
if (classified === "missing") {
|
|
2180
|
+
return policy.idempotencyErrorResponse("IdempotencyKeyMissing");
|
|
2181
|
+
}
|
|
2182
|
+
if (classified === "invalid") {
|
|
2183
|
+
return policy.idempotencyErrorResponse("IdempotencyKeyInvalid");
|
|
2184
|
+
}
|
|
2185
|
+
const idemStore = activeJournal().store.idempotency;
|
|
2186
|
+
if (!idemStore) return invoke(validated);
|
|
2187
|
+
const scope = {
|
|
2188
|
+
tenant: fx.tenant.id ?? "",
|
|
2189
|
+
principal: policy.idempotencyPrincipal(fx.auth),
|
|
2190
|
+
flow: flowDef.name,
|
|
2191
|
+
key: classified,
|
|
2192
|
+
};
|
|
2193
|
+
const claimToken = crypto.randomUUID();
|
|
2194
|
+
const leaseMs = activeJournal().leaseMs;
|
|
2195
|
+
const claimed = await idemStore.claim({
|
|
2196
|
+
scope,
|
|
2197
|
+
fingerprint: policy.idempotencyFingerprint(flowDef.name, validated),
|
|
2198
|
+
claimToken,
|
|
2199
|
+
now: now(),
|
|
2200
|
+
leaseMs,
|
|
2201
|
+
ttlMs: idemAttempt.ttlMs,
|
|
2202
|
+
});
|
|
2203
|
+
if (claimed.kind === "mismatch") {
|
|
2204
|
+
return policy.idempotencyErrorResponse("IdempotencyKeyReused");
|
|
2205
|
+
}
|
|
2206
|
+
if (claimed.kind === "in_progress") {
|
|
2207
|
+
return policy.idempotencyErrorResponse(
|
|
2208
|
+
"IdempotencyInProgress",
|
|
2209
|
+
claimed.retryAfterSeconds,
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
if (claimed.kind === "replay") return policy.replayResponse(claimed.row);
|
|
2213
|
+
|
|
2214
|
+
if (booted && flowDef.durable) {
|
|
2215
|
+
const journal = openJournal();
|
|
2216
|
+
const resumeId = claimed.kind === "reclaimed" ? claimed.row.runId : undefined;
|
|
2217
|
+
try {
|
|
2218
|
+
const session = resumeId
|
|
2219
|
+
? await journal.resume(resumeId)
|
|
2220
|
+
: await journal.start(flowDef.name, validated);
|
|
2221
|
+
if (journalSlot) journalSlot.session = session;
|
|
2222
|
+
journalSession = session;
|
|
2223
|
+
inflightRuns.add(session.runId);
|
|
2224
|
+
const tenantId = principals?.tenant.id ?? fx.tenant.id;
|
|
2225
|
+
if (tenantId) await session.stampTenant(tenantId);
|
|
2226
|
+
await idemStore.attachRun(scope, claimToken, session.runId);
|
|
2227
|
+
} catch (err) {
|
|
2228
|
+
if (isJournalLeaseBusy(err)) {
|
|
2229
|
+
await idemStore.forfeit(scope, claimToken);
|
|
2230
|
+
return policy.idempotencyErrorResponse("IdempotencyInProgress", 1);
|
|
2231
|
+
}
|
|
2232
|
+
await idemStore.remove(scope, claimToken);
|
|
2233
|
+
throw err;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
const timer = setInterval(
|
|
2238
|
+
() => {
|
|
2239
|
+
void idemStore.renew(scope, claimToken, now() + leaseMs);
|
|
2240
|
+
},
|
|
2241
|
+
Math.max(5, Math.floor(leaseMs / 3)),
|
|
2242
|
+
);
|
|
2243
|
+
if (typeof timer === "object" && timer !== null && "unref" in timer) timer.unref();
|
|
2244
|
+
|
|
2245
|
+
const storeEncoded = async (encoded: {
|
|
2246
|
+
readonly status: number;
|
|
2247
|
+
readonly headers: { get(name: string): string | null };
|
|
2248
|
+
text(): Promise<string>;
|
|
2249
|
+
}): Promise<void> => {
|
|
2250
|
+
const stored = await policy.storedFromResponse(encoded);
|
|
2251
|
+
if (!stored) {
|
|
2252
|
+
await idemStore.remove(scope, claimToken);
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
await idemStore.complete(scope, claimToken, stored, journalSession?.runId);
|
|
2256
|
+
};
|
|
2257
|
+
try {
|
|
2258
|
+
const output = await runDetached(() => invoke(validated));
|
|
2259
|
+
if (loadFx().isJsonStreamResult(output)) {
|
|
2260
|
+
await idemStore.remove(scope, claimToken);
|
|
2261
|
+
return output;
|
|
2262
|
+
}
|
|
2263
|
+
if (output instanceof Response) {
|
|
2264
|
+
const contentType = output.headers.get("content-type") ?? "";
|
|
2265
|
+
const buffered =
|
|
2266
|
+
output.status === 204 ||
|
|
2267
|
+
contentType.includes("json") ||
|
|
2268
|
+
contentType.startsWith("text/");
|
|
2269
|
+
if (!buffered || contentType.includes("text/event-stream")) {
|
|
2270
|
+
await idemStore.remove(scope, claimToken);
|
|
2271
|
+
return output;
|
|
2272
|
+
}
|
|
2273
|
+
await storeEncoded(output.clone());
|
|
2274
|
+
return output;
|
|
2275
|
+
}
|
|
2276
|
+
await storeEncoded(
|
|
2277
|
+
await encodeExecuteResult({
|
|
2278
|
+
output: isFlowFailure(output) ? undefined : output,
|
|
2279
|
+
failure: isFlowFailure(output) ? output : undefined,
|
|
2280
|
+
}),
|
|
2281
|
+
);
|
|
2282
|
+
return output;
|
|
2283
|
+
} catch (err) {
|
|
2284
|
+
if (flowDef.durable && isJournalSuspend(err)) {
|
|
2285
|
+
await idemStore.complete(
|
|
2286
|
+
scope,
|
|
2287
|
+
claimToken,
|
|
2288
|
+
{ status: 204, body: "" },
|
|
2289
|
+
journalSession?.runId,
|
|
2290
|
+
);
|
|
2291
|
+
throw err;
|
|
2292
|
+
}
|
|
2293
|
+
if (policy.attemptedMutation(ledger.entries) || idemAttempt.usesRaw) {
|
|
2294
|
+
await storeEncoded(encodeFailure(fail("InternalError", {})));
|
|
2295
|
+
} else {
|
|
2296
|
+
await idemStore.remove(scope, claimToken);
|
|
2297
|
+
}
|
|
2298
|
+
throw err;
|
|
2299
|
+
} finally {
|
|
2300
|
+
clearInterval(timer);
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2041
2303
|
if (!alreadyValidated) {
|
|
2042
2304
|
const parsed = await validate(flowDef.in, ctx.input);
|
|
2043
2305
|
if (!parsed.ok) return parsed.failure;
|
|
2044
2306
|
ctx.input = parsed.value;
|
|
2045
|
-
return await
|
|
2307
|
+
return await runClaimed(parsed.value);
|
|
2046
2308
|
}
|
|
2047
|
-
return await
|
|
2309
|
+
return await runClaimed(ctx.input);
|
|
2048
2310
|
} catch (err) {
|
|
2049
2311
|
if (isFlowFailure(err)) return err;
|
|
2050
2312
|
// A durable sleep that has not yet elapsed suspends the run — this
|
|
@@ -2084,6 +2346,14 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
2084
2346
|
: durationMs;
|
|
2085
2347
|
if (journalSession) {
|
|
2086
2348
|
inflightRuns.delete(journalSession.runId);
|
|
2349
|
+
const parked = streamOut?.parked;
|
|
2350
|
+
if (parked) {
|
|
2351
|
+
ctx.state.sleeping = {
|
|
2352
|
+
wakeAt: parked.wakeAt,
|
|
2353
|
+
label: parked.label,
|
|
2354
|
+
runId: journalSession.runId,
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2087
2357
|
const sleeping = ctx.state.sleeping as
|
|
2088
2358
|
| { readonly wakeAt: number; readonly label: string; readonly runId: string }
|
|
2089
2359
|
| undefined;
|
|
@@ -2104,6 +2374,8 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
2104
2374
|
fx,
|
|
2105
2375
|
error: terminalErr,
|
|
2106
2376
|
});
|
|
2377
|
+
} else if (parked && sleeping) {
|
|
2378
|
+
await journalSession.commit("sleeping", { wakeAt: sleeping.wakeAt });
|
|
2107
2379
|
} else if (!sleeping) {
|
|
2108
2380
|
await journalSession.commit("completed", {
|
|
2109
2381
|
output: streamOut ? { streamed: true } : result.output,
|
|
@@ -2136,6 +2408,9 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
2136
2408
|
? { output: streamOut ? { streamed: true } : result.output }
|
|
2137
2409
|
: {}),
|
|
2138
2410
|
...(parentId !== undefined ? { parentId } : {}),
|
|
2411
|
+
...(trigger.kind === "http" && extras?.request
|
|
2412
|
+
? { http: captureHttpFrame(extras.request, result.response) }
|
|
2413
|
+
: {}),
|
|
2139
2414
|
},
|
|
2140
2415
|
archiveCleartext,
|
|
2141
2416
|
);
|
|
@@ -58,7 +58,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
58
58
|
stores: [db],
|
|
59
59
|
gate: { policies: [gate.public] },
|
|
60
60
|
}).adopt({ list });
|
|
61
|
-
const t = await createTestApp(app);
|
|
61
|
+
const t = await createTestApp(app, { capability: "open" });
|
|
62
62
|
const notesApi = requireNotesApi(t);
|
|
63
63
|
|
|
64
64
|
const first = await notesApi.list({});
|
|
@@ -90,7 +90,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
90
90
|
stores: [db],
|
|
91
91
|
gate: { policies: [gate.public] },
|
|
92
92
|
}).adopt({ list });
|
|
93
|
-
const t = await createTestApp(app);
|
|
93
|
+
const t = await createTestApp(app, { capability: "open" });
|
|
94
94
|
const trigger = list.triggers[0];
|
|
95
95
|
if (trigger === undefined) throw new Error("notes.list has no trigger");
|
|
96
96
|
|
|
@@ -128,7 +128,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
128
128
|
stores: [db],
|
|
129
129
|
gate: { policies: [gate.public] },
|
|
130
130
|
}).adopt({ list });
|
|
131
|
-
const t = await createTestApp(app);
|
|
131
|
+
const t = await createTestApp(app, { capability: "open" });
|
|
132
132
|
const notesApi = requireNotesApi(t);
|
|
133
133
|
await notesApi.list({});
|
|
134
134
|
await notesApi.list({});
|
|
@@ -158,7 +158,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
158
158
|
stores: [db],
|
|
159
159
|
gate: { policies: [gate.public] },
|
|
160
160
|
}).adopt({ list });
|
|
161
|
-
const t = await createTestApp(app);
|
|
161
|
+
const t = await createTestApp(app, { capability: "open" });
|
|
162
162
|
const notesApi = requireNotesApi(t);
|
|
163
163
|
|
|
164
164
|
await notesApi.list({});
|
|
@@ -209,7 +209,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
209
209
|
stores: [db],
|
|
210
210
|
gate: { policies: [gate.public] },
|
|
211
211
|
}).adopt({ list, create });
|
|
212
|
-
const t = await createTestApp(app);
|
|
212
|
+
const t = await createTestApp(app, { capability: "open" });
|
|
213
213
|
const notesApi = requireNotesApi(t);
|
|
214
214
|
|
|
215
215
|
await notesApi.list({});
|
|
@@ -251,7 +251,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
251
251
|
stores: [db],
|
|
252
252
|
gate: { policies: [gate.public] },
|
|
253
253
|
}).adopt({ list, create });
|
|
254
|
-
const t = await createTestApp(app);
|
|
254
|
+
const t = await createTestApp(app, { capability: "open" });
|
|
255
255
|
const notesApi = requireNotesApi(t);
|
|
256
256
|
|
|
257
257
|
await notesApi.list({});
|
|
@@ -14,6 +14,7 @@ import type { AiDriver, AiOpenOptions } from "../../drivers/ai-types.ts";
|
|
|
14
14
|
import { createAiRuntime, type AiRuntime } from "../../elements/ai.ts";
|
|
15
15
|
import type { GateRuntime } from "../../elements/gate.ts";
|
|
16
16
|
import type { VaultRuntime } from "../../elements/vault.ts";
|
|
17
|
+
import type { JournalStore } from "../journal.ts";
|
|
17
18
|
import type { BootOptions } from "../boot.ts";
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -33,6 +34,7 @@ export function bindAi(
|
|
|
33
34
|
env: ConfigEnv = "test",
|
|
34
35
|
docker = false,
|
|
35
36
|
vault?: VaultRuntime,
|
|
37
|
+
journalStore?: JournalStore,
|
|
36
38
|
): AiRuntime {
|
|
37
39
|
const id = resolveAiDriverId(options, env, docker);
|
|
38
40
|
const driver =
|
|
@@ -48,6 +50,7 @@ export function bindAi(
|
|
|
48
50
|
},
|
|
49
51
|
gates: options.ai?.gates ?? gate,
|
|
50
52
|
now,
|
|
53
|
+
...(journalStore ? { journalStore } : {}),
|
|
51
54
|
});
|
|
52
55
|
}
|
|
53
56
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
resolveIndexDriverId,
|
|
14
14
|
resolveKvDriverId,
|
|
15
15
|
resolveSqlDriverId,
|
|
16
|
+
sqlBindingForDecl,
|
|
16
17
|
} from "./store.ts";
|
|
17
18
|
|
|
18
19
|
describe("bindStore driver resolution", () => {
|
|
@@ -99,6 +100,31 @@ describe("bindStore driver resolution", () => {
|
|
|
99
100
|
});
|
|
100
101
|
});
|
|
101
102
|
|
|
103
|
+
describe("sqlBindingForDecl", () => {
|
|
104
|
+
test("pool.max and replica URLs land on the binding; min is ignored", () => {
|
|
105
|
+
const binding = sqlBindingForDecl("db", "postgres://primary/db", {
|
|
106
|
+
driver: "postgres",
|
|
107
|
+
pool: { max: 20, min: 2 },
|
|
108
|
+
replicas: ["postgres://ro-1/db", "postgres://ro-2/db"],
|
|
109
|
+
});
|
|
110
|
+
expect(binding).toEqual({
|
|
111
|
+
name: "db",
|
|
112
|
+
primary: { url: "postgres://primary/db", pool: { max: 20 } },
|
|
113
|
+
replicas: [
|
|
114
|
+
{ url: "postgres://ro-1/db", pool: { max: 20 } },
|
|
115
|
+
{ url: "postgres://ro-2/db", pool: { max: 20 } },
|
|
116
|
+
],
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("a string pin keeps the URL and omits pool and replicas", () => {
|
|
121
|
+
expect(sqlBindingForDecl("db", "memory://", "memory")).toEqual({
|
|
122
|
+
name: "db",
|
|
123
|
+
primary: { url: "memory://" },
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
102
128
|
describe("bindStore durable KV routing", () => {
|
|
103
129
|
const prev = {
|
|
104
130
|
redis: process.env.REDIS_URL,
|
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
* Lazy store binder — loaded only when Store is declared.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
driverRefId,
|
|
7
|
+
resolveDomainDdlMode,
|
|
8
|
+
resolveDriverId,
|
|
9
|
+
resolveDriverRef,
|
|
10
|
+
type ConfigEnv,
|
|
11
|
+
type DriverRef,
|
|
12
|
+
} from "../../config/index.ts";
|
|
6
13
|
import {
|
|
7
14
|
STORE_FILES_DEFAULTS,
|
|
8
15
|
STORE_KV_DEFAULTS,
|
|
@@ -18,6 +25,7 @@ import { redisDriver } from "../../drivers/redis.ts";
|
|
|
18
25
|
import { s3Driver } from "../../drivers/s3.ts";
|
|
19
26
|
import type { FilesDriver, IndexDriver, KvDriver, SqlDriver } from "../../drivers/types.ts";
|
|
20
27
|
import { createStoreRuntime, type StoreRuntime } from "../../elements/store.ts";
|
|
28
|
+
import type { SqlRuntimeBinding } from "../../elements/store/runtime.ts";
|
|
21
29
|
import type { StoreDecl } from "../../elements/store/declare.ts";
|
|
22
30
|
import { emitBootWarn } from "../../runtime/boot-warn.ts";
|
|
23
31
|
import type { BootOptions } from "../boot.ts"; // type-only — no cycle at runtime
|
|
@@ -129,17 +137,15 @@ export function bindStore(
|
|
|
129
137
|
warnKvMemoryDurable(durableKvDecls.map((d) => d.name));
|
|
130
138
|
}
|
|
131
139
|
|
|
132
|
-
const
|
|
140
|
+
const sqlRef = resolveSqlDriverRef(options, env, docker);
|
|
141
|
+
const sqlBindings: Record<string, SqlRuntimeBinding> = {};
|
|
133
142
|
const kvBindings: Record<string, { url?: string }> = {};
|
|
134
143
|
const filesBindings: Record<string, { root?: string }> = {};
|
|
135
144
|
const indexBindings: Record<string, { url?: string; apiKey?: string }> = {};
|
|
136
145
|
|
|
137
146
|
for (const decl of options.stores ?? []) {
|
|
138
147
|
if (isSqlDecl(decl)) {
|
|
139
|
-
sqlBindings[decl.name] =
|
|
140
|
-
name: decl.name,
|
|
141
|
-
primary: { url: sqlUrl },
|
|
142
|
-
};
|
|
148
|
+
sqlBindings[decl.name] = sqlBindingForDecl(decl.name, sqlUrl, sqlRef);
|
|
143
149
|
} else if (isKvDecl(decl)) {
|
|
144
150
|
if (decl.durable === true) {
|
|
145
151
|
kvBindings[decl.name] = {};
|
|
@@ -210,6 +216,76 @@ export function resolveSqlDriverId(options: BootOptions, env: ConfigEnv, docker:
|
|
|
210
216
|
return resolveDriverId(options.config?.drivers?.store?.sql, env, STORE_SQL_DEFAULTS)!;
|
|
211
217
|
}
|
|
212
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Rich SQL driver ref for the active env. A docker `OKE_SQL_DRIVER` override
|
|
221
|
+
* that changes the protocol drops `pool` / `replicas` — those belong to the
|
|
222
|
+
* configured driver, not the override.
|
|
223
|
+
*
|
|
224
|
+
* @param options - Boot options
|
|
225
|
+
* @param env - Active environment
|
|
226
|
+
* @param docker - Docker mode
|
|
227
|
+
*/
|
|
228
|
+
function resolveSqlDriverRef(
|
|
229
|
+
options: BootOptions,
|
|
230
|
+
env: ConfigEnv,
|
|
231
|
+
docker: boolean,
|
|
232
|
+
): DriverRef | undefined {
|
|
233
|
+
const configured = resolveDriverRef(options.config?.drivers?.store?.sql, env, STORE_SQL_DEFAULTS);
|
|
234
|
+
const fromEnv = process.env.OKE_SQL_DRIVER?.trim();
|
|
235
|
+
if (docker && fromEnv && fromEnv !== driverRefId(configured)) return fromEnv;
|
|
236
|
+
return configured;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Primary URL plus documented `pool.max` and read-replica URLs.
|
|
241
|
+
*
|
|
242
|
+
* `pool.min` is ignored — Bun.SQL has no minimum pool size. Unset `max`
|
|
243
|
+
* leaves pool sizing to the postgres driver default of 8.
|
|
244
|
+
*
|
|
245
|
+
* @param name - Store declaration name
|
|
246
|
+
* @param url - Primary connection URL
|
|
247
|
+
* @param ref - Active driver pin (string or rich object)
|
|
248
|
+
*/
|
|
249
|
+
export function sqlBindingForDecl(
|
|
250
|
+
name: string,
|
|
251
|
+
url: string,
|
|
252
|
+
ref: DriverRef | undefined,
|
|
253
|
+
): SqlRuntimeBinding {
|
|
254
|
+
const max = driverPoolMax(ref);
|
|
255
|
+
const pool = max !== undefined ? { pool: { max } } : {};
|
|
256
|
+
const replicas = driverReplicaUrls(ref).map((replicaUrl) => ({
|
|
257
|
+
url: replicaUrl,
|
|
258
|
+
...pool,
|
|
259
|
+
}));
|
|
260
|
+
return {
|
|
261
|
+
name,
|
|
262
|
+
primary: { url, ...pool },
|
|
263
|
+
...(replicas.length > 0 ? { replicas } : {}),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Positive finite `pool.max` from a rich driver ref.
|
|
269
|
+
*
|
|
270
|
+
* @param ref - Driver pin
|
|
271
|
+
*/
|
|
272
|
+
function driverPoolMax(ref: DriverRef | undefined): number | undefined {
|
|
273
|
+
if (ref === undefined || typeof ref === "string") return undefined;
|
|
274
|
+
const max = ref.pool?.max;
|
|
275
|
+
if (typeof max !== "number" || !Number.isFinite(max) || max < 1) return undefined;
|
|
276
|
+
return Math.floor(max);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Replica connection strings from a rich driver ref. Non-strings are dropped.
|
|
281
|
+
*
|
|
282
|
+
* @param ref - Driver pin
|
|
283
|
+
*/
|
|
284
|
+
function driverReplicaUrls(ref: DriverRef | undefined): readonly string[] {
|
|
285
|
+
if (ref === undefined || typeof ref === "string" || ref.replicas === undefined) return [];
|
|
286
|
+
return ref.replicas.filter((item): item is string => typeof item === "string" && item.length > 0);
|
|
287
|
+
}
|
|
288
|
+
|
|
213
289
|
/**
|
|
214
290
|
* @param options - Boot options
|
|
215
291
|
* @param env - Active env
|