okengine 0.18.4 → 0.19.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 +18 -10
- package/README.md +2 -2
- package/package.json +51 -50
- package/site/content/docs/ai/index.mdx +25 -6
- package/site/content/docs/ai/llms-txt.mdx +1 -1
- package/site/content/docs/ai/meta.json +1 -1
- package/site/content/docs/ai/skills.mdx +4 -1
- package/site/content/docs/ai/try-it.mdx +58 -0
- package/site/content/docs/client/auth.mdx +208 -0
- package/site/content/docs/client/calling.mdx +451 -0
- package/site/content/docs/client/index.mdx +209 -0
- package/site/content/docs/client/live.mdx +234 -0
- package/site/content/docs/client/meta.json +5 -0
- package/site/content/docs/client/react.mdx +249 -0
- package/site/content/docs/elements/ai/agents.mdx +232 -0
- package/site/content/docs/elements/ai/index.mdx +341 -0
- package/site/content/docs/elements/ai/mcp.mdx +275 -0
- package/site/content/docs/elements/ai/meta.json +5 -0
- package/site/content/docs/elements/ai/models.mdx +283 -0
- package/site/content/docs/elements/ai/prompts.mdx +256 -0
- package/site/content/docs/elements/channel/email.mdx +266 -0
- package/site/content/docs/elements/channel/index.mdx +344 -0
- package/site/content/docs/elements/channel/meta.json +5 -0
- package/site/content/docs/elements/channel/push.mdx +218 -0
- package/site/content/docs/elements/channel/receipts.mdx +206 -0
- package/site/content/docs/elements/channel/sms.mdx +264 -0
- package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
- package/site/content/docs/elements/clock/index.mdx +300 -0
- package/site/content/docs/elements/clock/meta.json +5 -0
- package/site/content/docs/elements/clock/schedules.mdx +508 -0
- package/site/content/docs/elements/clock/sleep.mdx +445 -0
- package/site/content/docs/elements/flow/consumers.mdx +707 -0
- package/site/content/docs/elements/flow/http.mdx +1080 -0
- package/site/content/docs/elements/flow/index.mdx +618 -0
- package/site/content/docs/elements/flow/meta.json +5 -0
- package/site/content/docs/elements/flow/routing.mdx +559 -0
- package/site/content/docs/elements/flow/workflows.mdx +640 -0
- package/site/content/docs/elements/gate/auth.mdx +371 -0
- package/site/content/docs/elements/gate/authorization.mdx +298 -0
- package/site/content/docs/elements/gate/index.mdx +418 -0
- package/site/content/docs/elements/gate/meta.json +5 -0
- package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
- package/site/content/docs/elements/gate/rls.mdx +303 -0
- package/site/content/docs/elements/gate/tenancy.mdx +372 -0
- package/site/content/docs/elements/index.mdx +52 -11
- package/site/content/docs/elements/signal/broadcast.mdx +517 -0
- package/site/content/docs/elements/signal/index.mdx +352 -0
- package/site/content/docs/elements/signal/live.mdx +590 -0
- package/site/content/docs/elements/signal/meta.json +5 -0
- package/site/content/docs/elements/signal/once.mdx +596 -0
- package/site/content/docs/elements/store/files.mdx +659 -0
- package/site/content/docs/elements/store/index.mdx +310 -0
- package/site/content/docs/elements/store/kv.mdx +629 -0
- package/site/content/docs/elements/store/meta.json +5 -0
- package/site/content/docs/elements/store/search.mdx +946 -0
- package/site/content/docs/elements/store/sql.mdx +937 -0
- package/site/content/docs/elements/vault/config.mdx +256 -0
- package/site/content/docs/elements/vault/index.mdx +356 -0
- package/site/content/docs/elements/vault/meta.json +5 -0
- package/site/content/docs/elements/vault/rotation.mdx +273 -0
- package/site/content/docs/elements/vault/secrets.mdx +285 -0
- package/site/content/docs/index.mdx +78 -30
- package/site/content/docs/meta.json +11 -8
- package/site/content/docs/plugins/cors.mdx +2 -1
- package/site/content/docs/plugins/csrf.mdx +4 -1
- package/site/content/docs/plugins/headers.mdx +2 -1
- package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
- package/site/content/docs/plugins/magic-link.mdx +6 -0
- package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
- package/site/content/docs/plugins/oauth.mdx +1 -1
- package/site/content/docs/plugins/otp.mdx +12 -1
- package/site/content/docs/plugins/passkey.mdx +39 -15
- package/site/content/docs/plugins/two-factor.mdx +91 -34
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +3 -3
- package/site/content/docs/recipes/index.mdx +18 -28
- package/site/content/docs/recipes/mailpit.mdx +1 -1
- package/site/content/docs/recipes/meilisearch.mdx +3 -3
- package/site/content/docs/recipes/meta.json +1 -4
- package/site/content/docs/recipes/nginx.mdx +2 -2
- package/site/content/docs/recipes/openrouter.mdx +283 -0
- package/site/content/docs/recipes/pgdog.mdx +3 -3
- package/site/content/docs/recipes/postgres.mdx +1 -2
- package/site/content/docs/recipes/rustfs.mdx +2 -2
- package/site/content/docs/recipes/traefik.mdx +3 -3
- package/site/content/docs/reference/cli.mdx +231 -0
- package/site/content/docs/reference/configuration.mdx +30 -10
- package/site/content/docs/reference/environment-variables.mdx +93 -20
- package/site/content/docs/reference/errors.mdx +132 -29
- package/site/content/docs/reference/fx.mdx +137 -73
- package/site/content/docs/reference/index.mdx +13 -21
- package/site/content/docs/reference/meta.json +4 -5
- package/site/content/docs/reference/okid.mdx +42 -17
- package/site/content/docs/reference/plugins.mdx +4 -3
- package/site/content/docs/reference/security.mdx +197 -0
- package/site/content/docs/understand/meta.json +5 -0
- package/site/content/docs/understand/the-anatomy.mdx +132 -0
- package/site/content/docs/understand/the-model.mdx +32 -0
- package/site/content/docs/understand/the-problem.mdx +74 -0
- package/site/content/docs/understand/the-vocabulary.mdx +26 -0
- package/src/auth/api-keys.ts +2 -1
- package/src/auth/bindings.ts +64 -16
- package/src/auth/gate-auth.test.ts +6 -1
- package/src/auth/identity.ts +152 -5
- package/src/auth/index.ts +29 -0
- package/src/auth/invites.ts +2 -1
- package/src/auth/method-context.ts +41 -0
- package/src/auth/oauth-as/crypto.ts +2 -1
- package/src/auth/oauth-as/stores.ts +2 -1
- package/src/auth/operator.ts +2 -1
- package/src/auth/sessions-jwt.test.ts +77 -0
- package/src/auth/sessions.ts +20 -4
- package/src/auth/tenants.ts +3 -2
- package/src/auth/two-factor-challenge.test.ts +61 -0
- package/src/auth/two-factor-challenge.ts +284 -0
- package/src/auth/verification.ts +5 -0
- package/src/bench/REPORT.md +49 -0
- package/src/bench/g03-signal-once.bench.ts +1 -1
- package/src/bench/g10-observability-contention.bench.ts +1 -1
- package/src/bench/g17-hybrid-search.bench.ts +572 -0
- package/src/bench/load-app.ts +2 -10
- package/src/cli/ai-setup/ai-setup.test.ts +331 -42
- package/src/cli/ai-setup/apply.ts +294 -52
- package/src/cli/ai-setup/catalog.ts +238 -1342
- package/src/cli/ai-setup/index.ts +14 -99
- package/src/cli/ai-setup/prompts.ts +60 -443
- package/src/cli/ask-seed.test.ts +96 -0
- package/src/cli/ask-seed.ts +82 -0
- package/src/cli/ask-vault-gaps.test.ts +84 -0
- package/src/cli/ask-vault-gaps.ts +146 -0
- package/src/cli/build.ts +2 -2
- package/src/cli/client-add.test.ts +26 -1
- package/src/cli/client-add.ts +106 -27
- package/src/cli/competitor-mention-removal.test.ts +1 -1
- package/src/cli/db-seed.ts +2 -0
- package/src/cli/db.ts +150 -11
- package/src/cli/dev.ts +119 -211
- package/src/cli/docker-clean.ts +2 -2
- package/src/cli/docker-cli.test.ts +1 -1
- package/src/cli/doctor-diff.ts +4 -2
- package/src/cli/doctor-pii.test.ts +1 -1
- package/src/cli/doctor.ts +14 -1
- package/src/cli/hero-meta.test.ts +4 -2
- package/src/cli/load-config.images.test.ts +8 -10
- package/src/cli/load-config.ts +1 -1
- package/src/cli/registry.ts +22 -10
- package/src/cli/replay.ts +3 -1
- package/src/cli/start.ts +1 -1
- package/src/cli/tui/keys.ts +2 -3
- package/src/client/auth/cookies.ts +74 -0
- package/src/client/auth/create-auth-client.ts +636 -0
- package/src/client/auth/denials.ts +99 -0
- package/src/client/auth/session.ts +283 -0
- package/src/client/auth.test.ts +251 -0
- package/src/client/auth.ts +39 -114
- package/src/client/create-with-session.ts +245 -0
- package/src/client/create.ts +51 -5
- package/src/client/index.ts +11 -1
- package/src/client/live.ts +11 -79
- package/src/client/notes-contract.test.ts +5 -10
- package/src/client/sse.ts +137 -0
- package/src/client/stream.ts +147 -0
- package/src/client/transport.test.ts +32 -0
- package/src/client/transport.ts +123 -26
- package/src/client/types.ts +118 -14
- package/src/client-react/index.ts +185 -24
- package/src/client-react/use-live-query.ts +13 -3
- package/src/compiler/aot.test.ts +7 -4
- package/src/compiler/effects-embed.test.ts +56 -0
- package/src/compiler/effects-fetch.test.ts +43 -0
- package/src/compiler/effects-infer.ts +58 -2
- package/src/compiler/extract.test.ts +258 -44
- package/src/compiler/extract.ts +566 -48
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
- package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
- package/src/compiler/fixtures/skyport.expected.json +4 -4
- package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
- package/src/compiler/response.ts +2 -2
- package/src/compiler/search-writer-isolation.test.ts +40 -0
- package/src/config/index.ts +11 -0
- package/src/console/server/app.ts +17 -23
- package/src/console/server/bind.ts +4 -0
- package/src/console/server/console.test.ts +2 -2
- package/src/console/server/flows-invoke.test.ts +50 -34
- package/src/console/server/flows.ts +436 -255
- package/src/console/server/runs-ingest.test.ts +6 -3
- package/src/console/server/serve.ts +1 -1
- package/src/console/server/signals.test.ts +1 -5
- package/src/console/server/signals.ts +8 -6
- package/src/console/server/state.ts +6 -1
- package/src/console/server/store.ts +1 -1
- package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
- package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
- package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
- package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
- package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
- package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
- package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
- package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
- package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
- package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
- package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
- package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
- package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
- package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
- package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
- package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
- package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
- package/src/console/ui-next/dist/index.html +6 -13
- package/src/console/ui-next/seed-invoke-host.ts +21 -18
- package/src/console/ui-next/src/client.ts +7 -2
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
- package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
- package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
- package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
- package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
- package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
- package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
- package/src/docker/ai-model-status.test.ts +1 -12
- package/src/docker/ai-model-status.ts +10 -38
- package/src/docker/compose-up.test.ts +100 -0
- package/src/docker/compose-up.ts +292 -0
- package/src/docker/compose.ts +4 -14
- package/src/docker/derive.ts +9 -34
- package/src/docker/docker.test.ts +16 -261
- package/src/docker/images-config.test.ts +13 -25
- package/src/docker/index.ts +1 -24
- package/src/docker/recipes/index.ts +0 -23
- package/src/docker/recipes/pgdog.ts +6 -4
- package/src/docker/stack-id.test.ts +2 -2
- package/src/docker/stack-id.ts +0 -1
- package/src/docker/types.ts +2 -2
- package/src/drivers/ai-anthropic.ts +10 -0
- package/src/drivers/ai-openai-compatible.ts +34 -7
- package/src/drivers/ai-providers.test.ts +0 -107
- package/src/drivers/ai-stream.test.ts +0 -34
- package/src/drivers/ai-types.ts +17 -8
- package/src/drivers/channel-fcm.ts +6 -1
- package/src/drivers/channel-msegat.ts +6 -1
- package/src/drivers/channel-resend.ts +5 -1
- package/src/drivers/channel-smtp.ts +5 -1
- package/src/drivers/channel-sndr.ts +10 -1
- package/src/drivers/channel-taqnyat-mail.ts +5 -1
- package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
- package/src/drivers/channel-taqnyat.ts +6 -1
- package/src/drivers/channel-types.ts +10 -0
- package/src/drivers/channel-unifonic.ts +6 -1
- package/src/drivers/channel-wa-cloud.ts +6 -1
- package/src/drivers/channel-webpush.ts +6 -1
- package/src/drivers/external.ts +33 -0
- package/src/drivers/index.ts +0 -10
- package/src/drivers/meilisearch.ts +6 -0
- package/src/drivers/oauth-types.ts +4 -0
- package/src/drivers/postgres.test.ts +74 -3
- package/src/drivers/postgres.ts +175 -6
- package/src/drivers/signal-types.ts +5 -5
- package/src/drivers/types.ts +7 -0
- package/src/drivers/vault-types.ts +5 -0
- package/src/elements/ai/declare.ts +17 -4
- package/src/elements/ai/eval.ts +1 -1
- package/src/elements/ai/mcp-http.ts +1 -1
- package/src/elements/ai/pii.ts +1 -1
- package/src/elements/ai/providers.test.ts +289 -0
- package/src/elements/ai/providers.ts +169 -0
- package/src/elements/ai/runtime.ts +68 -5
- package/src/elements/ai/schema.ts +2 -2
- package/src/elements/ai.test.ts +7 -7
- package/src/elements/ai.ts +17 -1
- package/src/elements/channel/runtime.ts +105 -12
- package/src/elements/clock/cron-fields.test.ts +144 -0
- package/src/elements/clock/cron-fields.ts +185 -0
- package/src/elements/clock/declare.ts +247 -3
- package/src/elements/clock.ts +21 -1
- package/src/elements/index.ts +15 -0
- package/src/elements/signal/chaos-child.ts +1 -2
- package/src/elements/signal/declare.ts +65 -37
- package/src/elements/signal/delivery-modes.test.ts +11 -31
- package/src/elements/signal/dry-run-replay.test.ts +1 -7
- package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
- package/src/elements/signal/key-ordering.test.ts +6 -26
- package/src/elements/signal/lease-reclaim.test.ts +2 -6
- package/src/elements/signal/optional-emit.test.ts +6 -9
- package/src/elements/signal/order-lifecycle.test.ts +4 -17
- package/src/elements/signal/orphan-messages.test.ts +4 -15
- package/src/elements/signal/reconcile.test.ts +2 -2
- package/src/elements/signal/runtime.ts +1 -1
- package/src/elements/signal/schema-emit.test.ts +4 -8
- package/src/elements/signal.test.ts +24 -49
- package/src/elements/signal.ts +9 -2
- package/src/elements/store/domain-ddl.test.ts +1 -1
- package/src/elements/store/live-http.test.ts +9 -4
- package/src/elements/store/prepare-row.test.ts +123 -0
- package/src/elements/store/resource.ts +43 -12
- package/src/elements/store/schema-decl.ts +117 -0
- package/src/elements/store/search-backfill.ts +205 -0
- package/src/elements/store/search-bind.ts +66 -0
- package/src/elements/store/search-bm25.ts +78 -0
- package/src/elements/store/search-ddl.ts +156 -0
- package/src/elements/store/search-embed-flow.ts +141 -0
- package/src/elements/store/search-errors.ts +34 -0
- package/src/elements/store/search-fusion.ts +112 -0
- package/src/elements/store/search-lsh.ts +179 -0
- package/src/elements/store/search-runtime.ts +300 -0
- package/src/elements/store/search.test.ts +144 -0
- package/src/elements/store/sql-session.test.ts +1 -0
- package/src/elements/store/sql-session.ts +83 -3
- package/src/elements/store/table.ts +23 -2
- package/src/elements/store/upsert-app.test.ts +1 -3
- package/src/elements/store.ts +50 -0
- package/src/full.ts +4 -2
- package/src/http.ts +4 -1
- package/src/i18n/catalogs/ar.ts +14 -6
- package/src/i18n/catalogs/en.ts +14 -6
- package/src/index.ts +6 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
- package/src/kernel/adopt-routes.ts +9 -1
- package/src/kernel/app-auth.ts +5 -0
- package/src/kernel/app.ts +102 -9
- package/src/kernel/auto-cache.test.ts +6 -12
- package/src/kernel/auto-registry.test.ts +3 -3
- package/src/kernel/boot-bind/ai.test.ts +22 -19
- package/src/kernel/boot-bind/ai.ts +5 -43
- package/src/kernel/boot-bind/clock.ts +23 -5
- package/src/kernel/boot-bind/honor-config.test.ts +4 -4
- package/src/kernel/boot.test.ts +11 -6
- package/src/kernel/boot.ts +66 -16
- package/src/kernel/boundary-contract.ts +93 -0
- package/src/kernel/budget.test.ts +1 -1
- package/src/kernel/call.ts +89 -0
- package/src/kernel/capability.ts +6 -0
- package/src/kernel/client-descriptor.ts +123 -0
- package/src/kernel/clock-timezone.test.ts +80 -0
- package/src/kernel/correlation.test.ts +1 -1
- package/src/kernel/dry-run.ts +11 -8
- package/src/kernel/effects-stamping.test.ts +42 -9
- package/src/kernel/effects.test.ts +4 -2
- package/src/kernel/effects.ts +51 -9
- package/src/kernel/errors-channel.ts +17 -0
- package/src/kernel/errors-live-resume.ts +3 -2
- package/src/kernel/errors-tenant.ts +7 -4
- package/src/kernel/errors.registry-helpers.ts +96 -0
- package/src/kernel/errors.registry.test.ts +139 -25
- package/src/kernel/errors.ts +102 -23
- package/src/kernel/external-effects.test.ts +196 -0
- package/src/kernel/flow.test.ts +13 -4
- package/src/kernel/flow.ts +69 -71
- package/src/kernel/fx-ask-telemetry.test.ts +2 -2
- package/src/kernel/fx-dead-letters.test.ts +3 -3
- package/src/kernel/fx-fetch.ts +55 -0
- package/src/kernel/fx-live-stream.ts +2 -2
- package/src/kernel/fx-live.test.ts +8 -8
- package/src/kernel/fx.test.ts +17 -11
- package/src/kernel/fx.ts +180 -57
- package/src/kernel/horizontal-child.ts +1 -1
- package/src/kernel/http-query.test.ts +3 -6
- package/src/kernel/index.ts +13 -3
- package/src/kernel/instance-id.ts +1 -1
- package/src/kernel/instances.test.ts +40 -0
- package/src/kernel/instances.ts +47 -9
- package/src/kernel/live-http.test.ts +6 -7
- package/src/kernel/live-http.ts +6 -2
- package/src/kernel/live-resume.test.ts +1 -1
- package/src/kernel/mcp-tool.test.ts +3 -3
- package/src/kernel/on.ts +98 -4
- package/src/kernel/pipeline.test.ts +12 -15
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/stamp-http.test.ts +3 -3
- package/src/kernel/triggers.ts +210 -95
- package/src/kernel-entry.ts +0 -1
- package/src/manifest/diff.ts +11 -1
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/types.ts +46 -2
- package/src/mcp/docs-index.ts +3 -3
- package/src/mcp/docs-mcp.test.ts +2 -2
- package/src/mcp/docs-tools.ts +1 -1
- package/src/okid.test.ts +56 -0
- package/src/okid.ts +55 -11
- package/src/plugins/anonymous.ts +8 -4
- package/src/plugins/auth/shared.ts +27 -3
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
- package/src/plugins/auth-methods.security.test.ts +561 -23
- package/src/plugins/config-source.ts +1 -1
- package/src/plugins/csrf.test.ts +65 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/magic-link.ts +36 -16
- package/src/plugins/oauth/shared.ts +3 -1
- package/src/plugins/oauth.ts +22 -14
- package/src/plugins/otp.ts +37 -21
- package/src/plugins/passkey-webauthn.ts +9 -6
- package/src/plugins/passkey.ts +98 -26
- package/src/plugins/pre-account-hijack.test.ts +223 -0
- package/src/plugins/two-factor.ts +466 -37
- package/src/plugins/username.ts +19 -7
- package/src/release/build-lib.ts +1 -0
- package/src/release/limits.ts +2 -2
- package/src/release/measure.ts +1 -1
- package/src/runs/collect.test.ts +0 -2
- package/src/runtime/json-code-block.test.ts +250 -20
- package/src/runtime/json-code-block.ts +1261 -41
- package/src/term.test.ts +1 -1
- package/src/test/create-test-app.test.ts +12 -12
- package/src/test/live-signals.test.ts +6 -6
- package/src/test/provisions.integration.test.ts +10 -14
- package/src/test/reset-element-registries.ts +1 -1
- package/src/test/tenant-isolation.test.ts +12 -6
- package/site/content/docs/deployment/docker-swarm.mdx +0 -164
- package/site/content/docs/deployment/docker.mdx +0 -227
- package/site/content/docs/deployment/index.mdx +0 -83
- package/site/content/docs/deployment/kubernetes.mdx +0 -176
- package/site/content/docs/deployment/meta.json +0 -5
- package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
- package/site/content/docs/elements/ai.mdx +0 -385
- package/site/content/docs/elements/channel.mdx +0 -346
- package/site/content/docs/elements/clock.mdx +0 -244
- package/site/content/docs/elements/flow.mdx +0 -420
- package/site/content/docs/elements/gate.mdx +0 -436
- package/site/content/docs/elements/signal.mdx +0 -380
- package/site/content/docs/elements/store.mdx +0 -1099
- package/site/content/docs/elements/vault.mdx +0 -405
- package/site/content/docs/get-started/basic-usage.mdx +0 -173
- package/site/content/docs/get-started/index.mdx +0 -43
- package/site/content/docs/get-started/installation.mdx +0 -220
- package/site/content/docs/get-started/introduction.mdx +0 -132
- package/site/content/docs/get-started/meta.json +0 -13
- package/site/content/docs/get-started/project-structure.mdx +0 -925
- package/site/content/docs/get-started/testing.mdx +0 -328
- package/site/content/docs/get-started/why.mdx +0 -162
- package/site/content/docs/recipes/llama-cpp.mdx +0 -151
- package/site/content/docs/recipes/ollama.mdx +0 -142
- package/site/content/docs/recipes/sglang.mdx +0 -105
- package/site/content/docs/recipes/vllm.mdx +0 -106
- package/site/content/docs/reference/cli.md +0 -232
- package/site/content/docs/reference/client.mdx +0 -469
- package/site/content/docs/reference/security.md +0 -74
- package/src/cli/ai-setup/detect-ollama.ts +0 -213
- package/src/cli/ai-setup/recommend.test.ts +0 -225
- package/src/cli/ai-setup/recommend.ts +0 -225
- package/src/cli/dev-controls.test.ts +0 -122
- package/src/cli/dev-controls.ts +0 -164
- package/src/cli/tui/DevLive.tsx +0 -126
- package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
- package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
- package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
- package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
- package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
- package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
- package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
- package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
- package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
- package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
- package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
- package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
- package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
- package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
- package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
- package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
- package/src/docker/ollama-pull.ts +0 -232
- package/src/docker/recipes/llama-cpp.ts +0 -298
- package/src/docker/recipes/ollama.ts +0 -44
- package/src/docker/recipes/sglang.ts +0 -55
- package/src/docker/recipes/vllm.ts +0 -44
- package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
- package/src/drivers/ai-ollama.integration.test.ts +0 -184
- package/src/drivers/ai-ollama.ts +0 -389
- package/src/drivers/ollama.ts +0 -14
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Rate Limits"
|
|
3
|
+
description: "gate.rate — KV-backed throttles with max, per, and keyBy; five strategies, sliding-window-counter by default."
|
|
4
|
+
icon: "Gauge"
|
|
5
|
+
source: "docs/spec/unified-theory.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
`gate.rate` protects Flows from abuse and runaway clients. Limits run as atomic takes on the KV
|
|
9
|
+
driver before `do`. Compose rates with policies via `gate.all` or list them on `.gate(...)`.
|
|
10
|
+
|
|
11
|
+
For developers throttling login, writes, and expensive reads on okengine — declare the window,
|
|
12
|
+
key the subject, attach on the trigger.
|
|
13
|
+
|
|
14
|
+
<Callout title="The one rule">
|
|
15
|
+
Pass options as one object: `{ max, per, keyBy? }`. There is no `gate.rate(name, { limit, window })`
|
|
16
|
+
form — the runtime names the gate from strategy and window.
|
|
17
|
+
</Callout>
|
|
18
|
+
|
|
19
|
+
## Smallest Example
|
|
20
|
+
|
|
21
|
+
<Steps>
|
|
22
|
+
|
|
23
|
+
<Step>
|
|
24
|
+
### Declare a rate gate
|
|
25
|
+
|
|
26
|
+
```typescript title="src/core/gate.ts"
|
|
27
|
+
import { gate } from "okengine";
|
|
28
|
+
|
|
29
|
+
export const loginThrottle = gate.rate({
|
|
30
|
+
strategy: "sliding-window-counter", // default when omitted
|
|
31
|
+
max: 5,
|
|
32
|
+
per: "1m",
|
|
33
|
+
keyBy: "ip",
|
|
34
|
+
description: "Login attempts per IP",
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The runtime name is `rate:sliding-window-counter:5/1m` (strategy · max · per).
|
|
39
|
+
|
|
40
|
+
</Step>
|
|
41
|
+
|
|
42
|
+
<Step>
|
|
43
|
+
### Attach on the trigger
|
|
44
|
+
|
|
45
|
+
```typescript title="src/flows/auth/login.ts"
|
|
46
|
+
import { on, flow, http, gate } from "okengine";
|
|
47
|
+
import { loginThrottle } from "@/core/gate";
|
|
48
|
+
|
|
49
|
+
export const login = on(
|
|
50
|
+
http.post("/auth/sign-in/email").gate(gate.public, loginThrottle),
|
|
51
|
+
flow({
|
|
52
|
+
do: async (input, fx) => {
|
|
53
|
+
// At most 5 takes per IP per minute
|
|
54
|
+
return input;
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
</Step>
|
|
61
|
+
|
|
62
|
+
<Step>
|
|
63
|
+
### See a burned quota
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"data": null,
|
|
68
|
+
"error": {
|
|
69
|
+
"code": "RateLimited",
|
|
70
|
+
"message": "Too many requests. Try again later.",
|
|
71
|
+
"data": { "retryAfterMs": 42000 }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Status is `429 Too Many Requests`. Clients should wait `retryAfterMs` before retrying.
|
|
77
|
+
|
|
78
|
+
</Step>
|
|
79
|
+
|
|
80
|
+
</Steps>
|
|
81
|
+
|
|
82
|
+
## Progressive Patterns
|
|
83
|
+
|
|
84
|
+
From the default counter to bursty buckets and subject keys:
|
|
85
|
+
|
|
86
|
+
<Tabs items={["Default", "Token bucket", "keyBy", "Compose"]}>
|
|
87
|
+
|
|
88
|
+
<Tab value="Default">
|
|
89
|
+
|
|
90
|
+
Omit `strategy` for `sliding-window-counter` — best accuracy-to-cost ratio (two KV keys, no
|
|
91
|
+
boundary bursts):
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
export const fair = gate.rate({ max: 60, per: "1m", keyBy: "user" });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
</Tab>
|
|
98
|
+
|
|
99
|
+
<Tab value="Token bucket">
|
|
100
|
+
|
|
101
|
+
Allow short bursts that refill over `per`:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
export const bursty = gate.rate({
|
|
105
|
+
strategy: "token-bucket",
|
|
106
|
+
max: 20,
|
|
107
|
+
per: "1m",
|
|
108
|
+
keyBy: "user",
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Use `leaky-bucket` when you need a smooth outbound rate instead of burst capacity.
|
|
113
|
+
|
|
114
|
+
</Tab>
|
|
115
|
+
|
|
116
|
+
<Tab value="keyBy">
|
|
117
|
+
|
|
118
|
+
Subject dimension for the take key:
|
|
119
|
+
|
|
120
|
+
| `keyBy` | Subject |
|
|
121
|
+
| -------------------- | ------------------------------------------------- |
|
|
122
|
+
| `"user"` | `auth.userId` (else `meta.userId`, else `"anon"`) |
|
|
123
|
+
| `"ip"` | `meta.ip` (else `"0.0.0.0"`) |
|
|
124
|
+
| `"operator"` | `operator.id` (else `"anon"`) |
|
|
125
|
+
| `"global"` / omitted | Shared `"global"` bucket |
|
|
126
|
+
| other string | `meta[keyBy]` when present, else the literal |
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
gate.rate({ max: 100, per: "1h", keyBy: "ip" });
|
|
130
|
+
gate.rate({ max: 10, per: "1s" }); // global
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
</Tab>
|
|
134
|
+
|
|
135
|
+
<Tab value="Compose">
|
|
136
|
+
|
|
137
|
+
Rates sit on the same chain as policies — first denial wins:
|
|
138
|
+
|
|
139
|
+
```typescript title="src/flows/notes/create.ts"
|
|
140
|
+
import { on, flow, http, gate } from "okengine";
|
|
141
|
+
|
|
142
|
+
export const notesMutate = gate.all(
|
|
143
|
+
gate.policy("member", ({ auth }) => !!auth.verified),
|
|
144
|
+
gate.scope("notes:write"),
|
|
145
|
+
gate.rate({ max: 60, per: "1m", keyBy: "user" }),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
export const create = on(
|
|
149
|
+
http.post().gate(notesMutate),
|
|
150
|
+
flow({
|
|
151
|
+
do: async (input, fx) => fx.json.create({ id: fx.id(), ...input }),
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Put identity policies **before** rates when anonymous traffic should 401 instead of burning
|
|
157
|
+
quota under `"anon"`.
|
|
158
|
+
|
|
159
|
+
</Tab>
|
|
160
|
+
|
|
161
|
+
</Tabs>
|
|
162
|
+
|
|
163
|
+
## Strategies
|
|
164
|
+
|
|
165
|
+
| Strategy | Best for | Cost shape |
|
|
166
|
+
| ------------------------ | -------------------------- | --------------------------- |
|
|
167
|
+
| `sliding-window-counter` | Default — accuracy vs cost | Two keys, weighted estimate |
|
|
168
|
+
| `token-bucket` | Bursty traffic with refill | One hash (`tokens`, `ts`) |
|
|
169
|
+
| `leaky-bucket` | Smooth outbound rate | One hash (`level`, `ts`) |
|
|
170
|
+
| `fixed-window` | Simple reset intervals | One counter per bucket |
|
|
171
|
+
| `sliding-log` | Strict precision | ZSET of event timestamps |
|
|
172
|
+
|
|
173
|
+
All five run as atomic Lua on the KV driver (memory / redis EVAL).
|
|
174
|
+
|
|
175
|
+
<Callout title="Detailed section">
|
|
176
|
+
Pick the strategy from product physics, not fashion. The default covers most HTTP write paths;
|
|
177
|
+
switch only when you need bursts, smooth leak, or exact event logs.
|
|
178
|
+
</Callout>
|
|
179
|
+
|
|
180
|
+
<Tabs items={["sliding-window-counter", "token-bucket", "leaky-bucket", "fixed-window", "sliding-log"]}>
|
|
181
|
+
|
|
182
|
+
<Tab value="sliding-window-counter">
|
|
183
|
+
|
|
184
|
+
Near-exact rolling window without boundary spikes of a naive fixed window:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
gate.rate({
|
|
188
|
+
strategy: "sliding-window-counter", // or omit — this is the default
|
|
189
|
+
max: 100,
|
|
190
|
+
per: "1m",
|
|
191
|
+
keyBy: "user",
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Uses current + previous window counters with a time-weighted estimate.
|
|
196
|
+
|
|
197
|
+
</Tab>
|
|
198
|
+
|
|
199
|
+
<Tab value="token-bucket">
|
|
200
|
+
|
|
201
|
+
Burst up to `max`, then refill smoothly across `per`:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
gate.rate({
|
|
205
|
+
strategy: "token-bucket",
|
|
206
|
+
max: 20,
|
|
207
|
+
per: "1m",
|
|
208
|
+
keyBy: "ip",
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Consequence:** a quiet client can spend its full burst immediately after idle time.
|
|
213
|
+
|
|
214
|
+
</Tab>
|
|
215
|
+
|
|
216
|
+
<Tab value="leaky-bucket">
|
|
217
|
+
|
|
218
|
+
Smooth outbound rate — rejects when the “bucket level” would exceed `max`:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
gate.rate({
|
|
222
|
+
strategy: "leaky-bucket",
|
|
223
|
+
max: 50,
|
|
224
|
+
per: "1m",
|
|
225
|
+
keyBy: "user",
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Prefer this for upstream APIs that punish spikes more than averages.
|
|
230
|
+
|
|
231
|
+
</Tab>
|
|
232
|
+
|
|
233
|
+
<Tab value="fixed-window">
|
|
234
|
+
|
|
235
|
+
Simple counter that resets each `per` bucket:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
gate.rate({
|
|
239
|
+
strategy: "fixed-window",
|
|
240
|
+
max: 1000,
|
|
241
|
+
per: "1h",
|
|
242
|
+
keyBy: "global",
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Cheapest layout; allows a double-burst at the bucket boundary.
|
|
247
|
+
|
|
248
|
+
</Tab>
|
|
249
|
+
|
|
250
|
+
<Tab value="sliding-log">
|
|
251
|
+
|
|
252
|
+
Exact event log (ZSET of timestamps) — strict precision at higher cost:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
gate.rate({
|
|
256
|
+
strategy: "sliding-log",
|
|
257
|
+
max: 10,
|
|
258
|
+
per: "1m",
|
|
259
|
+
keyBy: "ip",
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Use for sensitive endpoints where approximate counters are not enough.
|
|
264
|
+
|
|
265
|
+
</Tab>
|
|
266
|
+
|
|
267
|
+
</Tabs>
|
|
268
|
+
|
|
269
|
+
## Options
|
|
270
|
+
|
|
271
|
+
| Option | Type | Default | Meaning |
|
|
272
|
+
| ------------- | -------------- | ------------------------ | -------------------------------------------- |
|
|
273
|
+
| `max` | `number` | required | Takes allowed within `per` (`> 0`) |
|
|
274
|
+
| `per` | `string` | required | Window / refill (`"1m"`, `"60s"`, `"1h"`, …) |
|
|
275
|
+
| `strategy` | `RateStrategy` | `sliding-window-counter` | Algorithm id |
|
|
276
|
+
| `keyBy` | `string` | global | Subject dim (`"ip"`, `"user"`, …) |
|
|
277
|
+
| `overridable` | `boolean` | `false` | Console may override `max` / `per` in Store |
|
|
278
|
+
| `description` | `string` | — | Human label for Console / docs |
|
|
279
|
+
|
|
280
|
+
**Consequence:** rate gates need a KV driver in the app. Without KV, evaluation denies with
|
|
281
|
+
telemetry reason `"rate gate requires kv"` and the typed code is still `RateLimited`.
|
|
282
|
+
|
|
283
|
+
Declare-time guards:
|
|
284
|
+
|
|
285
|
+
- `gate.rate: max must be a positive number`
|
|
286
|
+
- `gate.rate: per is required`
|
|
287
|
+
|
|
288
|
+
Invalid `per` at evaluation → deny with `reason: "invalid per: …"`.
|
|
289
|
+
|
|
290
|
+
## Naming & Overrides
|
|
291
|
+
|
|
292
|
+
The runtime name is always `rate:{strategy}:{max}/{per}`:
|
|
293
|
+
|
|
294
|
+
```text
|
|
295
|
+
rate:sliding-window-counter:60/1m
|
|
296
|
+
rate:token-bucket:20/1m
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
When `overridable: true`, Console may tune `max` / `per` in Store without a code deploy. Leave
|
|
300
|
+
it `false` (default) for hard product limits.
|
|
301
|
+
|
|
302
|
+
`oke({ gate: { rateLimit: { enabled } } })` controls whether **auth path** Flows attach
|
|
303
|
+
stricter presets. Default is `true` when `gate.auth` is on, `false` otherwise — it does not
|
|
304
|
+
replace your own `gate.rate` declarations.
|
|
305
|
+
|
|
306
|
+
## Troubleshooting
|
|
307
|
+
|
|
308
|
+
<Accordions>
|
|
309
|
+
|
|
310
|
+
<Accordion title="429 RateLimited immediately">
|
|
311
|
+
Quota burned for that subject. Inspect `retryAfterMs`, widen `per`, raise `max`, or change `keyBy`
|
|
312
|
+
so clients do not share one global bucket unintentionally.
|
|
313
|
+
</Accordion>
|
|
314
|
+
|
|
315
|
+
<Accordion title='Denial reason "rate gate requires kv"'>
|
|
316
|
+
Configure a KV driver (`memory` for local, `redis` for multi-instance). Policy-only apps can omit
|
|
317
|
+
KV; any `gate.rate` on a hot path cannot. The HTTP envelope is still `RateLimited`.
|
|
318
|
+
</Accordion>
|
|
319
|
+
|
|
320
|
+
<Accordion title="Anonymous traffic burns user quotas">
|
|
321
|
+
With `keyBy: "user"`, missing `userId` maps to `"anon"` — shared. Put a member policy before the
|
|
322
|
+
rate, or key by `"ip"` for public endpoints.
|
|
323
|
+
</Accordion>
|
|
324
|
+
|
|
325
|
+
<Accordion title="TypeError: gate.rate: max must be a positive number">
|
|
326
|
+
`max` must be `> 0`. Zero and negatives fail at declare.
|
|
327
|
+
</Accordion>
|
|
328
|
+
|
|
329
|
+
<Accordion title="TypeError: gate.rate: per is required">
|
|
330
|
+
Pass a duration string (`"1m"`, `"30s"`, …). Empty / missing `per` fails at declare.
|
|
331
|
+
</Accordion>
|
|
332
|
+
|
|
333
|
+
<Accordion title='Denial reason "invalid per: …"'>
|
|
334
|
+
The duration string did not parse to a positive window. Use `ms|s|m|h|d` style values the Clock
|
|
335
|
+
duration parser accepts (`"1m"`, `"60s"`, `"1h"`).
|
|
336
|
+
</Accordion>
|
|
337
|
+
|
|
338
|
+
</Accordions>
|
|
339
|
+
|
|
340
|
+
## Learn more
|
|
341
|
+
|
|
342
|
+
- [Gate Overview](/docs/elements/gate) — chain order and denial mapping
|
|
343
|
+
- [Authorization](/docs/elements/gate/authorization) — policies on the same `.gate(...)`
|
|
344
|
+
- [Store · KV](/docs/elements/store/kv) — KV facet drivers
|
|
345
|
+
- [HTTP](/docs/elements/flow/http) — attach rates on REST routes
|
|
346
|
+
|
|
347
|
+
## Next
|
|
348
|
+
|
|
349
|
+
<Cards>
|
|
350
|
+
<Card
|
|
351
|
+
title="Tenancy"
|
|
352
|
+
description="Resolve fx.tenant.id for B2B isolation."
|
|
353
|
+
href="/docs/elements/gate/tenancy"
|
|
354
|
+
/>
|
|
355
|
+
<Card
|
|
356
|
+
title="RLS"
|
|
357
|
+
description="Stamp Gate identity into SQL row policies."
|
|
358
|
+
href="/docs/elements/gate/rls"
|
|
359
|
+
/>
|
|
360
|
+
<Card
|
|
361
|
+
title="Vault Element"
|
|
362
|
+
description="Secrets and protected configuration."
|
|
363
|
+
href="/docs/elements/vault"
|
|
364
|
+
/>
|
|
365
|
+
</Cards>
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "RLS"
|
|
3
|
+
description: "Row-level security — Gate identity stamped into SQL so store.schema.policy helpers filter every fx.store query."
|
|
4
|
+
icon: "Rows3"
|
|
5
|
+
source: "docs/spec/unified-theory.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Gate decides whether a caller may run a Flow. **RLS** decides which SQL rows that
|
|
9
|
+
caller may see or change.
|
|
10
|
+
|
|
11
|
+
After `.gate(...)` passes, `fx.store` stamps the principal into Postgres session GUCs
|
|
12
|
+
(`oke.gate` · `oke.user` · `oke.scopes` · `oke.tenant`) so table policies enforce the
|
|
13
|
+
same identity.
|
|
14
|
+
|
|
15
|
+
For developers shipping multi-user SQL on okengine — declare Gate policies, attach them
|
|
16
|
+
on the trigger, and mirror them on the table with `store.schema.policy.*`.
|
|
17
|
+
|
|
18
|
+
<Callout title="The one rule">
|
|
19
|
+
Put row predicates on the **table** (`store.schema.policy.gate` / `owner` / `scope` / `tenant`).
|
|
20
|
+
Put permission to **act** on the **trigger** (`.gate(...)`). Never invent a separate middleware
|
|
21
|
+
layer — the stamp rides every `fx.store` call.
|
|
22
|
+
</Callout>
|
|
23
|
+
|
|
24
|
+
## Smallest Example
|
|
25
|
+
|
|
26
|
+
<Steps>
|
|
27
|
+
|
|
28
|
+
<Step>
|
|
29
|
+
### Declare a Gate policy and an RLS table
|
|
30
|
+
|
|
31
|
+
```typescript title="src/core/gate.ts"
|
|
32
|
+
import { gate } from "okengine";
|
|
33
|
+
|
|
34
|
+
export const member = gate.policy("member", ({ auth }) => !!auth.verified);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```typescript title="src/db/schema.decl.ts"
|
|
38
|
+
import { store, field } from "okengine";
|
|
39
|
+
|
|
40
|
+
export const tasks = store.schema.table(
|
|
41
|
+
"tasks",
|
|
42
|
+
{
|
|
43
|
+
id: field.id().primaryKey(),
|
|
44
|
+
owner: field.text().notNull(),
|
|
45
|
+
title: field.text().notNull(),
|
|
46
|
+
},
|
|
47
|
+
[
|
|
48
|
+
store.schema.policy.gate("member", { for: "select" }),
|
|
49
|
+
store.schema.policy.owner("owner", { for: "all" }),
|
|
50
|
+
],
|
|
51
|
+
);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
</Step>
|
|
55
|
+
|
|
56
|
+
<Step>
|
|
57
|
+
### Gate the route and query through `fx.store`
|
|
58
|
+
|
|
59
|
+
```typescript title="src/flows/tasks/list.ts"
|
|
60
|
+
import { on, flow, http } from "okengine";
|
|
61
|
+
import { member } from "@/core/gate";
|
|
62
|
+
import { db, tasks } from "@/schema";
|
|
63
|
+
|
|
64
|
+
export const list = on(
|
|
65
|
+
http.get().gate(member),
|
|
66
|
+
flow({
|
|
67
|
+
do: async (_, fx) => fx.store(db).select().from(tasks),
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
</Step>
|
|
73
|
+
|
|
74
|
+
<Step>
|
|
75
|
+
### What the caller sees
|
|
76
|
+
|
|
77
|
+
Alice’s session stamps `oke.gate = 'member'` and `oke.user = '<alice>'`. SELECT
|
|
78
|
+
policies that require `oke.gate() = 'member'` and `owner = oke.user()` return only
|
|
79
|
+
Alice’s rows — even if the Flow’s `where` clause is empty.
|
|
80
|
+
|
|
81
|
+
Use an RLS-capable SQL driver (`postgres` / `pglite`). The `memory` SQL driver does
|
|
82
|
+
not enforce row policies.
|
|
83
|
+
|
|
84
|
+
</Step>
|
|
85
|
+
|
|
86
|
+
</Steps>
|
|
87
|
+
|
|
88
|
+
## Progressive Patterns
|
|
89
|
+
|
|
90
|
+
From a gate-name check to owner columns, scopes, and tenants:
|
|
91
|
+
|
|
92
|
+
<Tabs items={["Gate", "Owner", "Scope", "Tenant"]}>
|
|
93
|
+
|
|
94
|
+
<Tab value="Gate">
|
|
95
|
+
|
|
96
|
+
`store.schema.policy.gate(name)` stamps `oke.gate() = '…'` (default `for: "select"`).
|
|
97
|
+
The name must match a Gate policy on the trigger (rate gates are skipped when picking
|
|
98
|
+
the stamp):
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
store.schema.policy.gate("member", { for: "select" });
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Consequence:** the first non-rate gate on `.gate(...)` becomes `oke.gate` for that
|
|
105
|
+
request. Put the policy you want stamped **first** when the chain mixes policies and
|
|
106
|
+
rates.
|
|
107
|
+
|
|
108
|
+
</Tab>
|
|
109
|
+
|
|
110
|
+
<Tab value="Owner">
|
|
111
|
+
|
|
112
|
+
`store.schema.policy.owner(column)` stamps `column = oke.user()` (default `for: "all"`):
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
store.schema.policy.owner("owner", { for: "all" });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`oke.user()` is the stamped `fx.auth.userId` (empty string when anonymous / public).
|
|
119
|
+
|
|
120
|
+
</Tab>
|
|
121
|
+
|
|
122
|
+
<Tab value="Scope">
|
|
123
|
+
|
|
124
|
+
`store.schema.policy.scope(scope)` stamps `oke.has_scope('…')` (default `for: "insert"`).
|
|
125
|
+
Pass a string or a `gate.scope(...)` handle so the scope stays single-sourced:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { gate } from "okengine";
|
|
129
|
+
|
|
130
|
+
export const bookingCreate = gate.scope("booking:create");
|
|
131
|
+
|
|
132
|
+
// On the table:
|
|
133
|
+
store.schema.policy.scope(bookingCreate, { for: "insert" });
|
|
134
|
+
// or: store.schema.policy.scope("booking:create", { for: "insert" });
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
</Tab>
|
|
138
|
+
|
|
139
|
+
<Tab value="Tenant">
|
|
140
|
+
|
|
141
|
+
When `gate.auth.tenant` is on, every table needs a tenant policy or an explicit opt-out:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
store.schema.policy.tenant("tenantId"); // default for: "all" → tenantId = oke.tenant()
|
|
145
|
+
// or shared catalog tables:
|
|
146
|
+
store.schema.unscoped();
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
See [Tenancy](/docs/elements/gate/tenancy). Extract fails without one:
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
extract: table "{store}.{table}" needs store.schema.policy.tenant(...) or store.schema.unscoped() when gate.auth.tenant is on
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
</Tab>
|
|
156
|
+
|
|
157
|
+
</Tabs>
|
|
158
|
+
|
|
159
|
+
## Identity Stamp
|
|
160
|
+
|
|
161
|
+
After Gate allows, the SQL session prelude sets:
|
|
162
|
+
|
|
163
|
+
| GUC / helper | Source | Used by |
|
|
164
|
+
| ------------------ | ---------------------------------- | --------------- |
|
|
165
|
+
| `oke.gate()` | First non-rate gate on the trigger | `policy.gate` |
|
|
166
|
+
| `oke.user()` | `fx.auth.userId` | `policy.owner` |
|
|
167
|
+
| `oke.has_scope(…)` | `fx.auth.scopes` (JSON) | `policy.scope` |
|
|
168
|
+
| `oke.tenant()` | `fx.tenant.id` when tenancy is on | `policy.tenant` |
|
|
169
|
+
|
|
170
|
+
The stamp `SET LOCAL ROLE oke_app` and turns `row_security` on so table owners cannot
|
|
171
|
+
bypass policies on the hot path.
|
|
172
|
+
|
|
173
|
+
| Plane / mode | Stamp? |
|
|
174
|
+
| --------------------------- | -------------------------- |
|
|
175
|
+
| User-plane Flow, gated | Yes |
|
|
176
|
+
| `plane: "operator"` | No — operator bypasses RLS |
|
|
177
|
+
| Console / Call API `bypass` | No |
|
|
178
|
+
|
|
179
|
+
## Policy Helpers
|
|
180
|
+
|
|
181
|
+
Extras are the **third argument** of `store.schema.table(name, cols, extras)`:
|
|
182
|
+
|
|
183
|
+
| Helper | Default `for` | Predicate |
|
|
184
|
+
| ---------------------------------- | ------------- | --------------------------------------- |
|
|
185
|
+
| `store.schema.policy.gate(name)` | `select` | `oke.gate() = '…'` |
|
|
186
|
+
| `store.schema.policy.owner(col)` | `all` | `col = oke.user()` |
|
|
187
|
+
| `store.schema.policy.scope(scope)` | `insert` | `oke.has_scope('…')` |
|
|
188
|
+
| `store.schema.policy.tenant(col)` | `all` | `col = oke.tenant()` |
|
|
189
|
+
| `store.schema.rls()` | — | Enable RLS with no policies yet |
|
|
190
|
+
| `store.schema.unscoped()` | — | Opt out of tenant requirement |
|
|
191
|
+
| `store.schema.policy(name, opts)` | — | Raw `using` / `withCheck` / `as` / `to` |
|
|
192
|
+
|
|
193
|
+
`for` accepts `select` · `insert` · `update` · `delete` · `all`. Optional `as`:
|
|
194
|
+
`"permissive"` (default) or `"restrictive"`. Optional `to` limits Postgres roles.
|
|
195
|
+
|
|
196
|
+
```typescript title="src/db/schema.decl.ts"
|
|
197
|
+
import { store, field } from "okengine";
|
|
198
|
+
import { member, bookingCreate } from "@/core/gate";
|
|
199
|
+
|
|
200
|
+
export const bookings = store.schema.table(
|
|
201
|
+
"bookings",
|
|
202
|
+
{
|
|
203
|
+
id: field.id().primaryKey(),
|
|
204
|
+
owner: field.text().notNull(),
|
|
205
|
+
tenantId: field.text().notNull(),
|
|
206
|
+
},
|
|
207
|
+
[
|
|
208
|
+
store.schema.policy.gate("member", { for: "select" }),
|
|
209
|
+
store.schema.policy.owner("owner", { for: "all" }),
|
|
210
|
+
store.schema.policy.scope(bookingCreate, { for: "insert" }),
|
|
211
|
+
store.schema.policy.tenant("tenantId"),
|
|
212
|
+
],
|
|
213
|
+
);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
<Callout title="Detailed section">
|
|
217
|
+
Schema extras, relations, and emit live under [Store · SQL · Schema Extras &
|
|
218
|
+
RLS](/docs/elements/store/sql#schema-extras--rls). This page is the Gate ↔ row identity contract.
|
|
219
|
+
</Callout>
|
|
220
|
+
|
|
221
|
+
## Composition
|
|
222
|
+
|
|
223
|
+
Multiple **PERMISSIVE** policies for the same command OR together — a row is visible if
|
|
224
|
+
any permissive policy passes. **RESTRICTIVE** policies AND with the rest (`as: "restrictive"`).
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
[
|
|
228
|
+
store.schema.policy.gate("member", { for: "select" }),
|
|
229
|
+
store.schema.policy.owner("owner", { for: "select" }),
|
|
230
|
+
];
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
A `member` principal sees rows that pass the gate predicate **or** the owner predicate
|
|
234
|
+
(for SELECT). Tighten with restrictive policies or a single compound raw `using`.
|
|
235
|
+
|
|
236
|
+
## Live Queries & Resources
|
|
237
|
+
|
|
238
|
+
Resource live (`store.resource({ live: true })`) and handwritten `liveQuery` classify
|
|
239
|
+
CDC events through the same RLS stamp. Needs `postgres` / `pglite` and a gated identity.
|
|
240
|
+
|
|
241
|
+
See [HTTP · Resource Live](/docs/elements/flow/http#resources) and
|
|
242
|
+
[Store · SQL](/docs/elements/store/sql).
|
|
243
|
+
|
|
244
|
+
## Troubleshooting
|
|
245
|
+
|
|
246
|
+
<Accordions>
|
|
247
|
+
|
|
248
|
+
<Accordion title="Every query returns zero rows">
|
|
249
|
+
Stamp or policies are wrong. Confirm `.gate(member)` (or matching name), that `owner` / `tenantId`
|
|
250
|
+
columns match `oke.user()` / `oke.tenant()`, and that the driver is `postgres` or `pglite` — not
|
|
251
|
+
`memory`.
|
|
252
|
+
</Accordion>
|
|
253
|
+
|
|
254
|
+
<Accordion title="Extract: needs store.schema.policy.tenant or unscoped">
|
|
255
|
+
Cause: `extract: table "{store}.{table}" needs store.schema.policy.tenant(...) or
|
|
256
|
+
store.schema.unscoped() when gate.auth.tenant is on`. Add a tenant column policy or mark the table
|
|
257
|
+
shared.
|
|
258
|
+
</Accordion>
|
|
259
|
+
|
|
260
|
+
<Accordion title="live query requires an RLS-capable SQL driver">
|
|
261
|
+
Cause: `live query for "…" requires an RLS-capable SQL driver (postgres / pglite)`. Switch the SQL
|
|
262
|
+
driver and attach a Gate chain so the stamp has a principal.
|
|
263
|
+
</Accordion>
|
|
264
|
+
|
|
265
|
+
<Accordion title="Operator / Console sees all rows">
|
|
266
|
+
`plane: "operator"` and Call API `bypass` skip the RLS stamp by design. User-plane Flows always
|
|
267
|
+
stamp when gated.
|
|
268
|
+
</Accordion>
|
|
269
|
+
|
|
270
|
+
<Accordion title="Wrong gate stamped on a long chain">
|
|
271
|
+
The first non-rate gate name wins for `oke.gate()`. Reorder `.gate(member, write, rate)` so the
|
|
272
|
+
policy you want in SQL predicates comes before rates and secondary scopes when those scopes use
|
|
273
|
+
`oke.has_scope` instead of `oke.gate`.
|
|
274
|
+
</Accordion>
|
|
275
|
+
|
|
276
|
+
</Accordions>
|
|
277
|
+
|
|
278
|
+
## Learn more
|
|
279
|
+
|
|
280
|
+
- [Authorization](/docs/elements/gate/authorization) — `gate.policy` / `gate.scope` on triggers
|
|
281
|
+
- [Tenancy](/docs/elements/gate/tenancy) — `fx.tenant.id` and `policy.tenant`
|
|
282
|
+
- [Store · SQL](/docs/elements/store/sql) — schema extras, session handle, drivers
|
|
283
|
+
- [HTTP](/docs/elements/flow/http) — resource live + RLS
|
|
284
|
+
|
|
285
|
+
## Next
|
|
286
|
+
|
|
287
|
+
<Cards>
|
|
288
|
+
<Card
|
|
289
|
+
title="Rate Limits"
|
|
290
|
+
description="Throttle with gate.rate on the same chain."
|
|
291
|
+
href="/docs/elements/gate/rate-limits"
|
|
292
|
+
/>
|
|
293
|
+
<Card
|
|
294
|
+
title="Tenancy"
|
|
295
|
+
description="Resolve fx.tenant.id and policy.tenant."
|
|
296
|
+
href="/docs/elements/gate/tenancy"
|
|
297
|
+
/>
|
|
298
|
+
<Card
|
|
299
|
+
title="Store · SQL"
|
|
300
|
+
description="Schema extras, fx.store, and RLS-capable drivers."
|
|
301
|
+
href="/docs/elements/store/sql"
|
|
302
|
+
/>
|
|
303
|
+
</Cards>
|