okengine 0.13.0 → 0.15.2
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/manifest.v1.schema.json +7 -1
- package/package.json +4 -5
- package/site/content/docs/ai/mcp.mdx +27 -2
- package/site/content/docs/elements/ai.mdx +59 -11
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +30 -12
- package/site/content/docs/elements/flow.mdx +9 -3
- package/site/content/docs/elements/gate.mdx +25 -22
- package/site/content/docs/elements/signal.mdx +71 -25
- package/site/content/docs/elements/store.mdx +87 -28
- package/site/content/docs/elements/vault.mdx +1 -0
- package/site/content/docs/get-started/basic-usage.mdx +22 -13
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/get-started/installation.mdx +2 -2
- package/site/content/docs/get-started/introduction.mdx +11 -3
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/project-structure.mdx +932 -0
- package/site/content/docs/reference/cli.md +13 -10
- package/site/content/docs/reference/client.mdx +25 -21
- package/site/content/docs/reference/fx.mdx +27 -22
- package/site/content/docs/reference/security.md +1 -1
- package/src/auth/bindings.ts +14 -5
- package/src/auth/cross-plane.ts +10 -3
- package/src/cli/ai-setup/ai-setup.test.ts +62 -0
- package/src/cli/ai-setup/apply.ts +32 -1
- package/src/cli/ai-setup/recommend.test.ts +25 -0
- package/src/cli/ai-setup/recommend.ts +8 -3
- package/src/cli/attach-host-console.test.ts +3 -2
- package/src/cli/attach-host-console.ts +3 -0
- package/src/cli/build.test.ts +4 -2
- package/src/cli/db-seed.ts +48 -15
- package/src/cli/db.test.ts +104 -0
- package/src/cli/db.ts +122 -10
- package/src/cli/dev-controls.test.ts +20 -0
- package/src/cli/dev-controls.ts +13 -0
- package/src/cli/dev.test.ts +15 -4
- package/src/cli/dev.ts +102 -29
- package/src/cli/ensure-drizzle-config.ts +2 -1
- package/src/cli/tui/DevLive.tsx +12 -2
- package/src/cli/tui/keys.test.ts +2 -1
- package/src/cli/tui/keys.ts +3 -2
- package/src/compiler/aot.test.ts +1 -1
- package/src/compiler/effects-infer.ts +58 -3
- package/src/compiler/extract.test.ts +458 -29
- package/src/compiler/extract.ts +387 -69
- package/src/compiler/flow-path.test.ts +96 -0
- package/src/compiler/flow-path.ts +173 -0
- package/src/compiler/generate-adopt.test.ts +112 -3
- package/src/compiler/generate-adopt.ts +280 -26
- package/src/compiler/response.ts +45 -1
- package/src/compiler/schema-from-ast.ts +609 -0
- package/src/console/server/ai.ts +5 -2
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/flows.ts +121 -40
- package/src/console/server/invoke-user-flow.test.ts +3 -1
- package/src/console/server/invoke-user-flow.ts +17 -2
- package/src/console/server/iso-at.test.ts +9 -0
- package/src/console/server/iso-at.ts +11 -0
- package/src/console/server/security-headers.ts +6 -3
- package/src/console/server/serve.ts +17 -2
- package/src/console/server/spa-proxy.test.ts +1 -0
- package/src/console/server/sql-catalog.ts +147 -7
- package/src/console/server/sql-rls.test.ts +16 -0
- package/src/console/server/state.ts +8 -0
- package/src/console/server/store.test.ts +7 -4
- package/src/console/server/store.ts +142 -58
- package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +1 -0
- package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
- package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
- package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
- package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
- package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
- package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
- package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
- package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
- package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +4 -0
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
- package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
- package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
- package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
- package/src/console/ui-next/dist/index.html +6 -6
- package/src/console/ui-next/seed-invoke-host.ts +2 -0
- package/src/console/ui-next/src/client.ts +35 -7
- package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
- package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
- package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
- package/src/console/ui-next/src/features/flows/graph/build-flow-graph.test.ts +18 -0
- package/src/console/ui-next/src/features/flows/graph/build-flow-graph.ts +14 -1
- package/src/console/ui-next/src/features/flows/graph/neighborhood.test.ts +17 -0
- package/src/console/ui-next/src/features/flows/graph/neighborhood.ts +16 -3
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +3 -1
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +9 -3
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +29 -2
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- package/src/console/ui-next/src/features/observability/lib/ask-count.test.ts +25 -0
- package/src/console/ui-next/src/features/observability/lib/ask-count.ts +4 -1
- package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
- package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
- package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
- package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
- package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
- package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
- package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
- package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
- package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
- package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
- package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
- package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
- package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
- package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
- package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
- package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
- package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +12 -4
- package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
- package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
- package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
- package/src/docker/compose-health.test.ts +27 -0
- package/src/docker/compose-health.ts +19 -3
- package/src/docker/docker.test.ts +4 -1
- package/src/docker/dockerfile.ts +1 -1
- package/src/docker/recipes/pgdog.ts +2 -0
- package/src/drivers/ai-anthropic.ts +5 -0
- package/src/drivers/ai-ollama.ts +49 -30
- package/src/drivers/ai-openai-compatible.ts +57 -46
- package/src/drivers/ai-providers.test.ts +3 -0
- package/src/drivers/bun-native-completeness.test.ts +7 -9
- package/src/drivers/clock-postgres.ts +13 -9
- package/src/drivers/instances-postgres.ts +4 -6
- package/src/drivers/journal-postgres.ts +13 -9
- package/src/drivers/pg-rls.test.ts +87 -0
- package/src/drivers/pg-rls.ts +233 -7
- package/src/drivers/pglite.ts +26 -3
- package/src/drivers/postgres.test.ts +23 -0
- package/src/drivers/postgres.ts +133 -9
- package/src/drivers/redis.ts +11 -4
- package/src/drivers/signal-redis.ts +24 -14
- package/src/drivers/signal-types.ts +2 -1
- package/src/drivers/types.ts +10 -0
- package/src/elements/ai/declare.ts +109 -0
- package/src/elements/ai/errors.test.ts +5 -1
- package/src/elements/ai/errors.ts +30 -2
- package/src/elements/ai/eval.ts +4 -6
- package/src/elements/ai/mcp-client.test.ts +206 -0
- package/src/elements/ai/mcp-client.ts +362 -0
- package/src/elements/ai/mcp-http.ts +159 -0
- package/src/elements/ai/mcp-mock.ts +134 -0
- package/src/elements/ai/mcp-protocol.ts +234 -0
- package/src/elements/ai/mcp-stdio.test.ts +50 -0
- package/src/elements/ai/mcp-stdio.ts +212 -0
- package/src/elements/ai/mcp-transport.ts +70 -0
- package/src/elements/ai/runtime.ts +159 -29
- package/src/elements/ai.test.ts +139 -0
- package/src/elements/ai.ts +16 -0
- package/src/elements/clock/declare.ts +26 -1
- package/src/elements/clock/health.test.ts +43 -0
- package/src/elements/clock/runtime.ts +55 -2
- package/src/elements/clock/schedule.ts +71 -142
- package/src/elements/clock.test.ts +9 -40
- package/src/elements/clock.ts +6 -1
- package/src/elements/gate/boot.ts +5 -5
- package/src/elements/gate/declare.ts +36 -4
- package/src/elements/gate/flatten.ts +1 -1
- package/src/elements/gate/runtime.ts +1 -1
- package/src/elements/index.ts +2 -0
- package/src/elements/signal/declare.ts +2 -1
- package/src/elements/signal/runtime.ts +16 -1
- package/src/elements/signal.ts +1 -0
- package/src/elements/store/cache.test.ts +2 -0
- package/src/elements/store/cache.ts +3 -3
- package/src/elements/store/declare.ts +9 -6
- package/src/elements/store/emit-drizzle.ts +44 -8
- package/src/elements/store/resource.test.ts +1 -1
- package/src/elements/store/rls-identity.test.ts +95 -0
- package/src/elements/store/rls-identity.ts +116 -0
- package/src/elements/store/runtime.ts +4 -0
- package/src/elements/store/schema-decl.test.ts +37 -0
- package/src/elements/store/schema-decl.ts +161 -0
- package/src/elements/store/seed.ts +1 -1
- package/src/elements/store/sql-condition.test.ts +12 -0
- package/src/elements/store/sql-condition.ts +26 -2
- package/src/elements/store/sql-rls-isolation.test.ts +165 -0
- package/src/elements/store/sql-rls-stamp.test.ts +117 -0
- package/src/elements/store/sql-session.ts +81 -3
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/store.ts +14 -0
- package/src/elements/vault/builtin-adapter.ts +17 -0
- package/src/full.ts +2 -0
- package/src/http.ts +20 -3
- package/src/index.ts +14 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/app.ts +227 -77
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +54 -9
- package/src/kernel/boot-bind/ai.ts +24 -0
- package/src/kernel/boot-bind/clock.ts +2 -0
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/boot.ts +5 -3
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +15 -5
- package/src/kernel/errors.ts +24 -0
- package/src/kernel/flow-units.ts +64 -0
- package/src/kernel/flow.ts +29 -17
- package/src/kernel/fx-dead-letters.test.ts +77 -0
- package/src/kernel/fx.test.ts +22 -0
- package/src/kernel/fx.ts +135 -6
- package/src/kernel/horizontal-child.ts +4 -4
- package/src/kernel/http-path-pending.ts +20 -0
- package/src/kernel/http-resource.ts +4 -0
- package/src/kernel/http-stream.test.ts +174 -0
- package/src/kernel/index.ts +2 -0
- package/src/kernel/pipeline.test.ts +2 -2
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +2 -2
- package/src/kernel/resource-mount.test.ts +14 -0
- package/src/kernel/run-duration.test.ts +2 -2
- package/src/kernel/stamp-http.test.ts +79 -0
- package/src/kernel/stamp-http.ts +75 -0
- package/src/kernel/triggers.ts +52 -27
- package/src/manifest/mcp-ref.ts +88 -0
- package/src/manifest/types.ts +41 -2
- package/src/manifest/validate.test.ts +20 -0
- package/src/mcp/docs-server.ts +1 -1
- package/src/mcp/server.ts +1 -1
- package/src/plugins/compression.test.ts +21 -0
- package/src/plugins/compression.ts +1 -0
- package/src/release/measure.ts +2 -2
- package/src/runtime/bun.ts +41 -4
- package/src/runtime/json-code-block.test.ts +2 -2
- package/src/runtime/serve.test.ts +4 -4
- package/src/term.test.ts +52 -4
- package/src/term.ts +125 -17
- package/src/test/reset-element-registries.ts +10 -3
- package/src/cli/ask-seed.test.ts +0 -96
- package/src/cli/ask-seed.ts +0 -82
- package/src/console/ui-next/dist/assets/cache-glyph-CLPBqZeb.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-Dg8CTE29.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-BJ92Z_ke.js +0 -1
- package/src/console/ui-next/dist/assets/index-Bp-R7jtM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- package/src/console/ui-next/dist/assets/observability-page-DAnpEaq1.js +0 -4
- package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
- package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
- package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
- package/src/console/ui-next/dist/assets/trace-detail-sheet-D16lWQMt.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-BtQ0bqMe.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-Ca-MvcmJ.js +0 -2
package/manifest.v1.schema.json
CHANGED
|
@@ -111,6 +111,11 @@
|
|
|
111
111
|
"type": "string",
|
|
112
112
|
"pattern": "^(sql|kv|files|index):.+$"
|
|
113
113
|
},
|
|
114
|
+
"SignalResourceRef": {
|
|
115
|
+
"description": "Dead-letter read via fx.deadLetters — signal:<name>, not a store facet.",
|
|
116
|
+
"type": "string",
|
|
117
|
+
"pattern": "^signal:.+$"
|
|
118
|
+
},
|
|
114
119
|
"SignalRef": {
|
|
115
120
|
"type": "string",
|
|
116
121
|
"minLength": 1
|
|
@@ -145,7 +150,8 @@
|
|
|
145
150
|
{
|
|
146
151
|
"const": "runs",
|
|
147
152
|
"description": "Runs wide-event store via fx.runs (observability read)."
|
|
148
|
-
}
|
|
153
|
+
},
|
|
154
|
+
{ "$ref": "#/$defs/SignalResourceRef" }
|
|
149
155
|
]
|
|
150
156
|
},
|
|
151
157
|
"uniqueItems": true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okengine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "One law. Eight elements. Ten exports. One package. One manifest. Every backend need is derived, never added.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -174,10 +174,9 @@
|
|
|
174
174
|
"d3-array": "^3.2.4",
|
|
175
175
|
"d3-shape": "^3.2.0",
|
|
176
176
|
"date-fns": "^4.1.0",
|
|
177
|
-
"drizzle-kit": "1.0.0-rc.
|
|
178
|
-
"drizzle-orm": "1.0.0-rc.
|
|
177
|
+
"drizzle-kit": "1.0.0-rc.5-ab785fc",
|
|
178
|
+
"drizzle-orm": "1.0.0-rc.5-169397b",
|
|
179
179
|
"drizzle-seed": "^0.3.1",
|
|
180
|
-
"drizzle-zod": "^0.8.3",
|
|
181
180
|
"gflows": "^1.2.1",
|
|
182
181
|
"happy-dom": "^20.11.1",
|
|
183
182
|
"hast-util-to-jsx-runtime": "^2.3.6",
|
|
@@ -271,6 +270,6 @@
|
|
|
271
270
|
"react": "^19.2.8"
|
|
272
271
|
},
|
|
273
272
|
"engines": {
|
|
274
|
-
"bun": ">=1.
|
|
273
|
+
"bun": ">=1.4.0"
|
|
275
274
|
}
|
|
276
275
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "MCP"
|
|
3
|
-
description: "
|
|
3
|
+
description: "OKE serves MCP on :6535 so agents can operate a running app, and apps consume external MCP servers as allowlisted tools on the same fx.call path."
|
|
4
4
|
icon: "Plug"
|
|
5
5
|
source: "docs/spec/unified-theory.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Two directions, same protocol. **Serve:** OKE exposes your running app on port **6535** so an agent can read the Manifest and take confirmed actions. **Consume:** your Flows call _external_ MCP servers as tools — declared on `ai.mcpServer`, dispatched through `fx.call`.
|
|
9
|
+
|
|
10
|
+
The server on **6535** speaks JSON-RPC over HTTP (MCP protocol `2024-11-05`), requires a Bearer token **even on localhost**, and never forwards that token upstream — adapters receive structured operator ids instead.
|
|
9
11
|
|
|
10
12
|
<Callout title="The one rule">
|
|
11
13
|
MCP inherits the operator's capability and can never exceed it. Server-level controls alone are
|
|
@@ -96,6 +98,29 @@ Consumed tokens cannot be replayed; the next write needs a new confirmation. A m
|
|
|
96
98
|
|
|
97
99
|
The docs content ships inside the `okengine` package, so the index your agent searches is exactly the version you have installed. If the surface cannot boot (missing content, busy port), `oke dev` prints `Docs MCP skipped — …` and continues — docs search never takes your dev session down.
|
|
98
100
|
|
|
101
|
+
## Consume — `ai.mcpServer`
|
|
102
|
+
|
|
103
|
+
Your app can _call_ other MCP servers. Those tools are not a second loop — they join `fx.ask` / `ai.agent` the same way a Flow tool does.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
export const github = ai.mcpServer("github", {
|
|
107
|
+
url: "https://mcp.example/github",
|
|
108
|
+
auth: { bearer: githubToken },
|
|
109
|
+
tools: ["create_issue"], // required allowlist
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await fx.ask(triage, input, { tools: [github.tool("create_issue")] });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Rule | Meaning |
|
|
116
|
+
| ---------- | ------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| Allowlist | `tools` is required. Extra names from `tools/list` are dropped. |
|
|
118
|
+
| Capability | `mcp:<server>/<tool>` on `effects.calls` — undeclared throws **OKE1007**. |
|
|
119
|
+
| Transport | `url` (Streamable HTTP) **or** `command` + `args` (stdio). Not both. |
|
|
120
|
+
| Cancel | HTTP aborts the fetch / SSE stream. stdio sends `notifications/cancelled` then kills the process. |
|
|
121
|
+
|
|
122
|
+
Console draws each declared server as one **AI** node on the flow graph; Units chips read `Call github → create_issue`; traces label the effect **MCP call**. There is no separate MCP page or connect UI.
|
|
123
|
+
|
|
99
124
|
## Learn more
|
|
100
125
|
|
|
101
126
|
- [Agent contracts](/docs/ai/skills) — what agents are taught about the system they operate
|
|
@@ -68,14 +68,15 @@ export const triage = smart.prompt("ticket-triage", {
|
|
|
68
68
|
|
|
69
69
|
</Steps>
|
|
70
70
|
|
|
71
|
-
## The
|
|
71
|
+
## The building blocks
|
|
72
72
|
|
|
73
|
-
| Declaration | Produces
|
|
74
|
-
| -------------------------- |
|
|
75
|
-
| `ai.model(name, opts)` | Logical model binding — provider / tier /
|
|
76
|
-
| `model.prompt(name, opts)` | Versioned prompt with typed in/out, evals, budget
|
|
77
|
-
| `ai.embed(name, opts)` | Embedding pipeline into a `store.index` (searched via `fx.search`)
|
|
78
|
-
| `ai.agent(name, opts)` | Bounded agent whose tools are **your own flows**
|
|
73
|
+
| Declaration | Produces |
|
|
74
|
+
| -------------------------- | ------------------------------------------------------------------------------- |
|
|
75
|
+
| `ai.model(name, opts)` | Logical model binding — provider / tier / model id / optional `driverId` |
|
|
76
|
+
| `model.prompt(name, opts)` | Versioned prompt with typed in/out, evals, budget |
|
|
77
|
+
| `ai.embed(name, opts)` | Embedding pipeline into a `store.index` (searched via `fx.search`) |
|
|
78
|
+
| `ai.agent(name, opts)` | Bounded agent whose tools are **your own flows** |
|
|
79
|
+
| `ai.mcpServer(name, opts)` | External MCP server — allowlisted tools join `fx.call` as `mcp:<server>/<tool>` |
|
|
79
80
|
|
|
80
81
|
<AiBlocks />
|
|
81
82
|
|
|
@@ -105,9 +106,11 @@ const out = await fx.ask(summarizeNote, { title, body });
|
|
|
105
106
|
// out.via is the logical model that answered
|
|
106
107
|
```
|
|
107
108
|
|
|
108
|
-
Resolution: `fx.ask(…, { via })` overrides `prompt.via`, else the prompt’s bound model. On each model the runtime retries **once** for retryable failures (timeout / 429 / 5xx / network), then advances.
|
|
109
|
+
Resolution: `fx.ask(…, { via })` overrides `prompt.via`, else the prompt’s bound model. On each model the runtime retries **once** for retryable failures (timeout / 429 / 5xx / network), then advances.
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
Permanent failures (401 / other 4xx / schema invalid / client disconnect / `maxCostPerCall` exceeded) stop the chain. When every eligible attempt fails, `fx.ask` throws — map that to a typed error in the flow (no silent text excerpt).
|
|
112
|
+
|
|
113
|
+
`timeout` uses the same duration vocabulary as Clock (`"30s"`, `"2m"`, or a millisecond number). Ask-time `fx.ask(…, { timeout: "10s" })` overrides the prompt. Omit = no artificial cap. `budget.maxCostPerCall` is enforced at runtime (not just observed). Set `driverId` on a model to open a different protocol than the app default.
|
|
111
114
|
|
|
112
115
|
## Declared guardrails
|
|
113
116
|
|
|
@@ -126,6 +129,22 @@ const result = await fx.ask(triage, input, {
|
|
|
126
129
|
});
|
|
127
130
|
```
|
|
128
131
|
|
|
132
|
+
External MCP tools take the same path. Declare a server with a **required** allowlist, then pass `server.tool("…")` — never whatever `tools/list` happens to expose:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
export const github = ai.mcpServer("github", {
|
|
136
|
+
url: "https://mcp.example/github",
|
|
137
|
+
auth: { bearer: githubToken },
|
|
138
|
+
tools: ["create_issue"],
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await fx.ask(triage, input, {
|
|
142
|
+
tools: [github.tool("create_issue")],
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Capability refs are `mcp:<server>/<tool>`. An undeclared call throws **OKE1007**. See [MCP](/docs/ai/mcp).
|
|
147
|
+
|
|
129
148
|
## Agents with real guardrails
|
|
130
149
|
|
|
131
150
|
An agent's tools are your flows — each carrying its own gates, effects, and typed errors, so the agent can never do anything a flow couldn't:
|
|
@@ -139,7 +158,7 @@ export const support = ai.agent("support", {
|
|
|
139
158
|
});
|
|
140
159
|
```
|
|
141
160
|
|
|
142
|
-
`maxSteps` bounds the loop; `budget.maxCostPerRun`
|
|
161
|
+
`maxSteps` bounds the loop; `budget.maxCostPerRun` is enforced after the run. Both are declared, so "the agent ran away" is a violated contract, not a surprise. `fx.run(support, { message })` uses the same tool loop and the same `fx.call` dispatch as `fx.ask(…, { tools })`.
|
|
143
162
|
|
|
144
163
|
## Rate limits via Gate
|
|
145
164
|
|
|
@@ -157,7 +176,20 @@ Use `keyBy: "ip"` on public unauthenticated AI edges. Cost caps stay on prompt/a
|
|
|
157
176
|
|
|
158
177
|
## Streaming
|
|
159
178
|
|
|
160
|
-
`fx.stream(model, { prompt })` yields real provider tokens (Ollama NDJSON, OpenAI-compatible SSE). Cancel by aborting the ambient signal — the same one `fx.all` / `fx.race` already use. Drivers without `stream` fail loud (no stub echo).
|
|
179
|
+
`fx.stream(model, { prompt, via? })` yields real provider tokens (Ollama NDJSON, OpenAI-compatible SSE). Cancel by aborting the ambient signal — the same one `fx.all` / `fx.race` already use. Drivers without `stream` fail loud (no stub echo).
|
|
180
|
+
|
|
181
|
+
Return those chunks on HTTP with `fx.json.stream(...)`. The kernel answers `text/event-stream` (`data:` frames, then `data: [DONE]`). JSON flows stay buffered `{ data, error }`.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
on(
|
|
185
|
+
http.post("/complete").public(),
|
|
186
|
+
flow("chat.complete", {
|
|
187
|
+
do: (input, fx) => fx.json.stream(fx.stream(smart, { prompt: input.prompt })),
|
|
188
|
+
}),
|
|
189
|
+
);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Disconnect cancels the provider call automatically (`request.signal` → ambient abort). An `AbortError` from hang-up does not retry or advance `via`. Console Traces stay empty until the stream closes — then the run is one completed row whose duration covers the open stream, not just time-to-first-byte.
|
|
161
193
|
|
|
162
194
|
## PII cannot leak by accident
|
|
163
195
|
|
|
@@ -305,6 +337,11 @@ Capability pins and Call API pass `name@version`. `fx.ask` resolves the bare
|
|
|
305
337
|
prompt id. A pin that does not match `prompt.version` throws
|
|
306
338
|
`ai: unknown prompt "name@version"`.
|
|
307
339
|
|
|
340
|
+
</Accordion>
|
|
341
|
+
<Accordion title="OKE1007 when the model calls an MCP tool">
|
|
342
|
+
|
|
343
|
+
Add `mcp:<server>/<tool>` to that flow’s `effects.calls` — the same token as `fx.call`. The allowlist on `ai.mcpServer` is not a capability grant.
|
|
344
|
+
|
|
308
345
|
</Accordion>
|
|
309
346
|
<Accordion title="An agent looped and burned budget">
|
|
310
347
|
|
|
@@ -313,13 +350,24 @@ Bound it at declaration: `maxSteps` caps iterations, `budget.maxCostPerRun` caps
|
|
|
313
350
|
`fx.ask` stamps `promptVersion` on the run. Cost is recorded only when the
|
|
314
351
|
driver reports `usage.cost` — tokens land on the ask journal either way.
|
|
315
352
|
|
|
353
|
+
</Accordion>
|
|
354
|
+
<Accordion title="The client hung up but the model kept generating">
|
|
355
|
+
|
|
356
|
+
HTTP flows install `request.signal` as the ambient abort. `fx.ask` / `fx.stream` /
|
|
357
|
+
`fx.run` cancel the provider fetch. Return tokens with `fx.json.stream(fx.stream(...))`
|
|
358
|
+
so disconnect is observable.
|
|
359
|
+
|
|
360
|
+
A hang-up is `AbortError` — it does not retry or advance `via`.
|
|
361
|
+
|
|
316
362
|
</Accordion>
|
|
317
363
|
</Accordions>
|
|
318
364
|
|
|
319
365
|
## Learn more
|
|
320
366
|
|
|
367
|
+
- [Project structure](/docs/get-started/project-structure) — agent `tools` are Flow names (`notes.get`), not URLs
|
|
321
368
|
- [Flow](/docs/elements/flow) — `fx.ask` and `fx.search` inside `do`
|
|
322
369
|
- [Store](/docs/elements/store) — `store.index`, the home of embeddings
|
|
370
|
+
- [MCP](/docs/ai/mcp) — serve the app to agents, or consume external servers as tools
|
|
323
371
|
|
|
324
372
|
## Next
|
|
325
373
|
|
|
@@ -327,6 +327,7 @@ Confirm a visible OS notification titled `okengine webpush interop`.
|
|
|
327
327
|
|
|
328
328
|
## Learn more
|
|
329
329
|
|
|
330
|
+
- [Project structure](/docs/get-started/project-structure) — `flow({…})` names as `unit.export`; explicit `flow("unit.action")` still wins
|
|
330
331
|
- [Flow](/docs/elements/flow) — `fx.send` inside `do`
|
|
331
332
|
- [Environment Variables](/docs/reference/environment-variables) — Channel boot credentials
|
|
332
333
|
- [Configuration](/docs/reference/configuration) — `drivers.channel.*` maps
|
|
@@ -28,7 +28,7 @@ export const purgeOld = on(
|
|
|
28
28
|
every("1h"),
|
|
29
29
|
flow("links.purgeOld", {
|
|
30
30
|
do: async (_, fx) => {
|
|
31
|
-
const cutoff = fx.clock.
|
|
31
|
+
const cutoff = fx.clock.ago("30d");
|
|
32
32
|
await fx.store(db).delete(links).where(lt(links.createdAt, cutoff));
|
|
33
33
|
},
|
|
34
34
|
}),
|
|
@@ -52,6 +52,8 @@ export const dailyReport = clock("daily-report", {
|
|
|
52
52
|
});
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
Import the module before `oke()` — named clocks auto-register. No `oke({ clocks })` list.
|
|
56
|
+
|
|
55
57
|
```typescript title="src/flows/reports/daily.ts"
|
|
56
58
|
export const sendDaily = on(
|
|
57
59
|
dailyReport,
|
|
@@ -68,12 +70,15 @@ export const sendDaily = on(
|
|
|
68
70
|
<Step>
|
|
69
71
|
### Ask for time inside flows
|
|
70
72
|
|
|
71
|
-
`fx.clock` is the only clock a flow knows
|
|
73
|
+
`fx.clock` is the only clock a flow knows. Instants stay epoch-ms; spans use the same
|
|
74
|
+
duration strings as `every()` and `sleep()`:
|
|
72
75
|
|
|
73
76
|
```typescript
|
|
74
77
|
do: async (input, fx) => {
|
|
75
|
-
const
|
|
76
|
-
|
|
78
|
+
const cutoff = fx.clock.ago("30d");
|
|
79
|
+
const expiresAt = fx.clock.fromNow("14d");
|
|
80
|
+
const window = input.createdAt + fx.clock.duration("7d");
|
|
81
|
+
await fx.clock.sleep("wait-for-payment", "7d");
|
|
77
82
|
};
|
|
78
83
|
```
|
|
79
84
|
|
|
@@ -90,13 +95,25 @@ difference.
|
|
|
90
95
|
|
|
91
96
|
### `clock()` options
|
|
92
97
|
|
|
93
|
-
| Option | Type | Default | Meaning
|
|
94
|
-
| ------------- | ------- | ------- |
|
|
95
|
-
| `cron` | string | — |
|
|
96
|
-
| `every` | string | — | Fixed interval: `"30s"` · `"10m"` · `"1h"` · `"7d"`
|
|
97
|
-
| `timezone` | string | `"UTC"` | IANA timezone for cron evaluation
|
|
98
|
-
| `overridable` | boolean | `false` | Allow the Console to **edit** the schedule (pause is separate)
|
|
99
|
-
| `description` | string | — | Human title in the Console (falls back to the clock name)
|
|
98
|
+
| Option | Type | Default | Meaning |
|
|
99
|
+
| ------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
100
|
+
| `cron` | string | — | Five-field crontab (`m h dom mon dow`), plus steps, ranges, lists, names, `@hourly`, `@daily` (this or `every` required) |
|
|
101
|
+
| `every` | string | — | Fixed interval: `"30s"` · `"10m"` · `"1h"` · `"7d"` |
|
|
102
|
+
| `timezone` | string | `"UTC"` | IANA timezone for cron evaluation |
|
|
103
|
+
| `overridable` | boolean | `false` | Allow the Console to **edit** the schedule (pause is separate) |
|
|
104
|
+
| `description` | string | — | Human title in the Console (falls back to the clock name) |
|
|
105
|
+
|
|
106
|
+
### `fx.clock`
|
|
107
|
+
|
|
108
|
+
| Call | Returns | Meaning |
|
|
109
|
+
| -------------------- | ------- | ------------------------------------------------ |
|
|
110
|
+
| `now()` | instant | Injected epoch-ms |
|
|
111
|
+
| `ago("30d")` | instant | now − duration |
|
|
112
|
+
| `fromNow("14d")` | instant | now + duration |
|
|
113
|
+
| `duration("7d")` | span ms | Offset a stored instant (`createdAt + duration`) |
|
|
114
|
+
| `sleep(label, "7d")` | — | Durable sleep |
|
|
115
|
+
|
|
116
|
+
Same duration strings as `every()`: `"200ms"` · `"30s"` · `"2m"` · `"1h"` · `"7d"`. A `"d"` is 86_400_000 ms, not a calendar day. Unknown strings parse as `0`.
|
|
100
117
|
|
|
101
118
|
## Sleeping inside a flow
|
|
102
119
|
|
|
@@ -136,7 +153,7 @@ Simple daily crons (`M H * * *`) in a DST-observing zone can hit a **gap** (spri
|
|
|
136
153
|
|
|
137
154
|
Detection attaches `dstAmbiguity` on the cron row and the Console shows it. `oke doctor` does not check DST. The scheduler does not rewrite the expression.
|
|
138
155
|
|
|
139
|
-
**Consequence:**
|
|
156
|
+
**Consequence:** firing follows crontab. A spring gap runs at the next valid instant (shifted). A fall overlap fires **once** (first occurrence). Prefer UTC when a shifted spring fire would hurt.
|
|
140
157
|
|
|
141
158
|
## Per-environment drivers
|
|
142
159
|
|
|
@@ -203,6 +220,7 @@ The registry is off in `test` and when there is no shared SQL URL. `dev`/`prod`
|
|
|
203
220
|
|
|
204
221
|
## Learn more
|
|
205
222
|
|
|
223
|
+
- [Project structure](/docs/get-started/project-structure) — clock flows in the tree get a name, never a URL
|
|
206
224
|
- [Flow](/docs/elements/flow) — `on(trigger, flow)` and the `fx` surface
|
|
207
225
|
- [fx · Runs](/docs/reference/fx#runs-observability-read) — native SLO checkers via `on(every(…))` + `fx.runs`
|
|
208
226
|
- [Signal](/docs/elements/signal) — reacting to events instead of time
|
|
@@ -112,6 +112,8 @@ Built-in codes attach a localized `message` ([Errors](/docs/reference/errors));
|
|
|
112
112
|
|
|
113
113
|
`http.get` · `http.post` · `http.put` · `http.patch` · `http.delete` · `http.head` · `http.options` · `http.query`. QUERY (RFC 10008) is a safe, idempotent read with a JSON body.
|
|
114
114
|
|
|
115
|
+
`http.get()` with no path is filled from the file tree (`flows/notes/[id]/get.ts` → `/notes/:id`). `flow({ do })` is named `unit.export`. Explicit `http.get("/me/tasks")` and `flow("notes.get")` still win. Folder conventions live on [Project structure](/docs/get-started/project-structure).
|
|
116
|
+
|
|
115
117
|
`Content-Type: application/json` is required — missing → **400** `InvalidQuery`, anything else → **415** `UnsupportedMediaType` with `Accept-Query: "application/json"`. Invalid JSON is **400** (no sniffing). Responses advertise `Accept-Query`. okengine does not assign `Location` / `Content-Location` result URIs.
|
|
116
118
|
|
|
117
119
|
A browser `GET` (`Accept` prefers `text/html`) paints the `{ data, error }` envelope in traces chrome — status, latency, and cache, Routes rail (static GET links), copy, line numbers, Pretty / Raw, Console on **6533**. `curl`, `Accept: application/json`, and `?format=json` stay JSON.
|
|
@@ -130,7 +132,9 @@ export const findOrder = on(
|
|
|
130
132
|
|
|
131
133
|
### signal — another flow emits
|
|
132
134
|
|
|
133
|
-
The producer emits through `fx
|
|
135
|
+
The producer emits through `fx`. For `once` and `broadcast` the consumer is the same species —
|
|
136
|
+
`on(signal, flow)`, no `subscribe()` registration. `live` replay is the exception: `bus.live()` on
|
|
137
|
+
the server, not a Flow.
|
|
134
138
|
|
|
135
139
|
```typescript
|
|
136
140
|
await fx.emit(orderPlaced, { orderId: id }); // inside the producing flow
|
|
@@ -154,7 +158,7 @@ on(
|
|
|
154
158
|
every("1h"),
|
|
155
159
|
flow("sessions.sweepExpired", {
|
|
156
160
|
do: async (_, fx) => {
|
|
157
|
-
const cutoff = fx.clock.
|
|
161
|
+
const cutoff = fx.clock.ago("30d");
|
|
158
162
|
await fx.store(db).delete(sessions).where(lt(sessions.createdAt, cutoff));
|
|
159
163
|
},
|
|
160
164
|
}),
|
|
@@ -198,12 +202,13 @@ Everything a flow may touch, on one object:
|
|
|
198
202
|
| ------------------------------------------- | --------------- | -------------------------------------------------- |
|
|
199
203
|
| `fx.store(db).select/insert/…` | read / write | SQL, KV, files, index sessions |
|
|
200
204
|
| `fx.emit(signal, payload)` | emit | Publish a signal (transactional with writes) |
|
|
205
|
+
| `fx.deadLetters(signal)` | read | Dead-lettered messages for that signal |
|
|
201
206
|
| `fx.send(template, opts)` | send | Reach a human (email · SMS · …) |
|
|
202
207
|
| `fx.ask(prompt, input)` | ask | Call a versioned AI prompt |
|
|
203
208
|
| `fx.run(agent, input)` | ask | Run a bounded agent |
|
|
204
209
|
| `fx.call(flow, input)` | call | Invoke another flow |
|
|
205
210
|
| `fx.vault.get(contract)` | secret | Read a secret (`Redacted`; logs masked) |
|
|
206
|
-
| `fx.clock.now
|
|
211
|
+
| `fx.clock.now/ago/fromNow/duration/sleep` | — | Injected instants, spans, durable sleep |
|
|
207
212
|
| `fx.cache.get/set` | — | Shared cache with effect-aware invalidation |
|
|
208
213
|
| `fx.step(name, fn, { undo? })` | — | Named durable step — optional LIFO undo on failure |
|
|
209
214
|
| `fx.all` / `fx.race` / `fx.retry` | — | Structured concurrency + backoff retry |
|
|
@@ -366,6 +371,7 @@ a **hit**. A matching write turns the next read into a miss.
|
|
|
366
371
|
|
|
367
372
|
## Learn more
|
|
368
373
|
|
|
374
|
+
- [Project structure](/docs/get-started/project-structure) — folders are the URL; `unit.export` is the name
|
|
369
375
|
- [fx](/docs/reference/fx) — full `fx` surface, including ICU `fx.t` / `fx.locale`
|
|
370
376
|
- [Signal](/docs/elements/signal) — delivery physics (`once` · `broadcast` · `live`)
|
|
371
377
|
- [Clock](/docs/elements/clock) — schedules and durable sleep
|
|
@@ -10,7 +10,7 @@ burned its minute quota? Attach gates to the HTTP trigger — the pipeline check
|
|
|
10
10
|
store write, emit, or channel send runs.
|
|
11
11
|
|
|
12
12
|
<Callout title="The one rule">
|
|
13
|
-
Every HTTP trigger declares posture: attach a real gate, or attach `.
|
|
13
|
+
Every HTTP trigger declares posture: attach a real gate, or attach `.public()`. Omitting both
|
|
14
14
|
fails boot. Denial is a typed error value — never a thrown exception mid-`do`.
|
|
15
15
|
</Callout>
|
|
16
16
|
|
|
@@ -19,10 +19,10 @@ store write, emit, or channel send runs.
|
|
|
19
19
|
<Steps>
|
|
20
20
|
|
|
21
21
|
<Step>
|
|
22
|
-
### Declare policies
|
|
22
|
+
### Declare policies
|
|
23
23
|
|
|
24
|
-
Policies are named predicates; rate limits are declarative budgets.
|
|
25
|
-
`oke({ gate: { policies } })
|
|
24
|
+
Policies are named predicates; rate limits are declarative budgets. Import the module before
|
|
25
|
+
`oke()` — they auto-register. `oke({ gate: { policies } })` is still valid and additive.
|
|
26
26
|
|
|
27
27
|
```typescript title="src/gates.ts"
|
|
28
28
|
import { gate } from "okengine";
|
|
@@ -43,14 +43,10 @@ export const fair = gate.rate({
|
|
|
43
43
|
the scope checked.
|
|
44
44
|
|
|
45
45
|
```typescript title="src/app.ts"
|
|
46
|
+
import "./gates";
|
|
46
47
|
import { oke } from "okengine";
|
|
47
|
-
import { member, canBook, fair } from "./gates";
|
|
48
48
|
|
|
49
|
-
export const app = oke({
|
|
50
|
-
name: "shop",
|
|
51
|
-
env: "dev",
|
|
52
|
-
gate: { policies: [member, canBook, fair] },
|
|
53
|
-
});
|
|
49
|
+
export const app = oke({ name: "shop", env: "dev" });
|
|
54
50
|
```
|
|
55
51
|
|
|
56
52
|
</Step>
|
|
@@ -58,14 +54,14 @@ export const app = oke({
|
|
|
58
54
|
<Step>
|
|
59
55
|
### Attach them to triggers
|
|
60
56
|
|
|
61
|
-
`.gate(...)` composes left to right — first denial wins. Public routes must say so explicitly
|
|
57
|
+
`.gate(...)` composes left to right — first denial wins. Public routes must say so explicitly. Path and name can come from the file tree — see [Project structure](/docs/get-started/project-structure).
|
|
62
58
|
|
|
63
59
|
```typescript title="src/flows/links/shorten.ts"
|
|
64
60
|
import { on, flow, http, gate } from "okengine";
|
|
65
61
|
import { member, canBook, fair } from "../../gates";
|
|
66
62
|
|
|
67
63
|
export const health = on(
|
|
68
|
-
http.get("/health").
|
|
64
|
+
http.get("/health").public(),
|
|
69
65
|
flow("health.check", {
|
|
70
66
|
out: HealthOut,
|
|
71
67
|
do: () => ({ ok: true as const }),
|
|
@@ -88,8 +84,8 @@ export const shorten = on(
|
|
|
88
84
|
```
|
|
89
85
|
|
|
90
86
|
A single gate stays `.gate(member)`. Reuse two or more with `gate.all` — `.gate(write)`
|
|
91
|
-
flattens. `http.resource(path, ops).gate(...)` stamps every verb.
|
|
92
|
-
`gate.public` in `gate.policies
|
|
87
|
+
flattens. `http.resource(path, ops).gate(...)` stamps every verb. Public routes use
|
|
88
|
+
`.public()` — do **not** put `gate.public` in `gate.policies`.
|
|
93
89
|
|
|
94
90
|
</Step>
|
|
95
91
|
|
|
@@ -131,16 +127,16 @@ gates are skipped. `do` runs only when every gate passed.
|
|
|
131
127
|
| ------------------------------------- | ------------------------------------------- | ---------------------------------- |
|
|
132
128
|
| `gate.policy(name, check \| options)` | Is this principal allowed? (ABAC) | auth / operator / request metadata |
|
|
133
129
|
| `gate.scope(name)` | Does `auth.scopes` contain this exact name? | `auth.scopes` |
|
|
134
|
-
| `gate.public` / `.
|
|
130
|
+
| `gate.public` / `.public()` | Intentionally unauthenticated surface | always allows |
|
|
135
131
|
| `gate.rate(options)` | Is there budget left for this subject? | an atomic counter on the kv driver |
|
|
136
132
|
| `gate.all(...members)` | Reuse this chain on many triggers | flattens; each member is evaluated |
|
|
137
133
|
|
|
138
134
|
Pass `{ check, description }` on `gate.policy` when you want a human title in the Console (falls
|
|
139
|
-
back to the policy name). The name `"public"` is reserved —
|
|
140
|
-
`gate.policy("public", …)`.
|
|
135
|
+
back to the policy name). The name `"public"` is reserved — mark the trigger with
|
|
136
|
+
`.public()`, not `gate.policy("public", …)`.
|
|
141
137
|
|
|
142
138
|
`gate.all()` with no members throws. Nested `all` handles flatten.
|
|
143
|
-
`oke({ gate: { policies: [write] } })`
|
|
139
|
+
Members auto-register when declared; `oke({ gate: { policies: [write] } })` is additive.
|
|
144
140
|
|
|
145
141
|
### Policy context
|
|
146
142
|
|
|
@@ -178,11 +174,11 @@ driver in tests. Boot opens a dedicated `oke:gates` namespace on **`drivers.stor
|
|
|
178
174
|
|
|
179
175
|
## Auth posture at boot
|
|
180
176
|
|
|
181
|
-
Every HTTP trigger must carry a gate or `.
|
|
177
|
+
Every HTTP trigger must carry a gate or `.public()`. Omitting both fails boot with
|
|
182
178
|
`GateBootError` — message starts with `gate boot failed —` and lists every gap.
|
|
183
179
|
|
|
184
180
|
```typescript
|
|
185
|
-
on(http.get("/health").
|
|
181
|
+
on(http.get("/health").public(), flow("health.check", {/* … */}));
|
|
186
182
|
on(http.post("/bookings").gate(member, canBook), flow("bookings.create", {/* … */}));
|
|
187
183
|
```
|
|
188
184
|
|
|
@@ -191,7 +187,7 @@ on(http.post("/bookings").gate(member, canBook), flow("bookings.create", {/* …
|
|
|
191
187
|
posture but does **not** authenticate.
|
|
192
188
|
|
|
193
189
|
`gate: { unguardedHttp: "allow" }` skips the audit **only** when `env === "test"`.
|
|
194
|
-
In `dev` / `prod` migrate with per-trigger `.
|
|
190
|
+
In `dev` / `prod` migrate with per-trigger `.public()`.
|
|
195
191
|
|
|
196
192
|
## Built-in auth (`gate.auth`)
|
|
197
193
|
|
|
@@ -270,12 +266,18 @@ Pass **and** deny land on the run's `gates` dimension. Console (`:6533` → `/fl
|
|
|
270
266
|
chain on the flow contract. Rate `overridable` is a catalog flag only — Clock schedules are what the Console can
|
|
271
267
|
edit live today.
|
|
272
268
|
|
|
269
|
+
<Callout title="RLS reads the stamped principal">
|
|
270
|
+
After HTTP Gate passes, user-plane `fx.store` stamps `oke.gate()`, `oke.user()`, and
|
|
271
|
+
`oke.has_scope()`. Rate, cron, CDC, and signal stay unstamped. Helpers:
|
|
272
|
+
[Store](/docs/elements/store).
|
|
273
|
+
</Callout>
|
|
274
|
+
|
|
273
275
|
## Troubleshooting
|
|
274
276
|
|
|
275
277
|
<Accordions>
|
|
276
278
|
<Accordion title="Boot: gate boot failed — N HTTP trigger(s) missing auth posture">
|
|
277
279
|
|
|
278
|
-
Empty `.gate(...)` chain. Attach a policy/rate gate, or `.
|
|
280
|
+
Empty `.gate(...)` chain. Attach a policy/rate gate, or `.public()`. First `fetch`
|
|
279
281
|
auto-boots — do not rely on `gate.unguardedHttp: "allow"` outside `env: "test"`.
|
|
280
282
|
|
|
281
283
|
</Accordion>
|
|
@@ -313,6 +315,7 @@ Keying an authenticated endpoint by IP punishes shared NAT.
|
|
|
313
315
|
|
|
314
316
|
## Learn more
|
|
315
317
|
|
|
318
|
+
- [Project structure](/docs/get-started/project-structure) — `http.get()` and `flow({…})` fill path and name from the file tree
|
|
316
319
|
- [Flow](/docs/elements/flow) — the trigger pipeline gates plug into
|
|
317
320
|
- [fx](/docs/reference/fx) — `fx.auth`, `fx.operator`, `fx.principal`
|
|
318
321
|
- [Client](/docs/reference/client) — Bearer `createClient` + `okengine/client/auth`
|