okengine 0.18.5 → 0.19.1
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/llms-txt.mdx +1 -1
- package/site/content/docs/ai/skills.mdx +4 -1
- 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 +208 -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 +235 -0
- package/site/content/docs/elements/ai/index.mdx +342 -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 +257 -0
- package/site/content/docs/elements/channel/email.mdx +269 -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 +301 -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 +448 -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 +375 -0
- package/site/content/docs/elements/index.mdx +20 -10
- package/site/content/docs/elements/signal/broadcast.mdx +519 -0
- package/site/content/docs/elements/signal/index.mdx +352 -0
- package/site/content/docs/elements/signal/live.mdx +591 -0
- package/site/content/docs/elements/signal/meta.json +5 -0
- package/site/content/docs/elements/signal/once.mdx +597 -0
- package/site/content/docs/elements/store/files.mdx +667 -0
- package/site/content/docs/elements/store/index.mdx +313 -0
- package/site/content/docs/elements/store/kv.mdx +637 -0
- package/site/content/docs/elements/store/meta.json +5 -0
- package/site/content/docs/elements/store/search.mdx +950 -0
- package/site/content/docs/elements/store/sql.mdx +941 -0
- package/site/content/docs/elements/vault/config.mdx +259 -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 +276 -0
- package/site/content/docs/elements/vault/secrets.mdx +288 -0
- package/site/content/docs/index.mdx +52 -40
- package/site/content/docs/meta.json +8 -7
- 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 +138 -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-architecture.mdx +264 -0
- package/site/content/docs/understand/try-it.mdx +58 -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 +80 -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/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 +257 -43
- 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-C_qLDhTq.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-BHVqr3TN.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-CKe92lRQ.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-DEwTl8ZX.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-BCBtDrCt.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-JroqeuCp.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-CVHa0RTt.js +1 -0
- package/src/console/ui-next/dist/assets/highlighted-json-DjJW6hqe.js +154 -0
- package/src/console/ui-next/dist/assets/http-method-DC5HBdLU.js +1 -0
- package/src/console/ui-next/dist/assets/index-CYjiZ3WO.js +63 -0
- package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
- package/src/console/ui-next/dist/assets/observability-page-CAYMyKb3.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-yAQYLv75.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-D0yusGxJ.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-BTKJeJ02.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-Bp-Yygs5.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-DoaVDfAM.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-BRz7xyYL.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-3jQt-bOJ.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.test.ts +1 -1
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +5 -3
- 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/trace-detail.test.ts +4 -5
- package/src/console/ui-next/src/features/flows/traces/trace-gates.ts +3 -6
- 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/gate/declare.ts +1 -1
- package/src/elements/gate.ts +1 -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 +214 -0
- package/src/elements/store/search-runtime.pglite.test.ts +186 -0
- package/src/elements/store/search-runtime.ts +314 -0
- package/src/elements/store/search.test.ts +226 -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 +52 -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 +2 -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 -144
- 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 -114
- 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-3-EFj-2G.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-cVFnA4HH.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-CMIgUbD0.js +0 -66
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-CQ3p34ip.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-l8FeKfnP.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-CL-d_mLE.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
package/src/auth/sessions.ts
CHANGED
|
@@ -407,17 +407,23 @@ export class SessionError extends Error {
|
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
/** Compact JWS algorithm for session access tokens (HMAC-SHA256). */
|
|
411
|
+
const ACCESS_TOKEN_ALG = "HS256" as const;
|
|
412
|
+
|
|
410
413
|
async function hashToken(raw: string): Promise<string> {
|
|
411
414
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(raw));
|
|
412
415
|
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
413
416
|
}
|
|
414
417
|
|
|
415
418
|
function cryptoRandomId(): string {
|
|
416
|
-
|
|
419
|
+
// Refresh / opaque session secrets — not application ids (those use OKID).
|
|
420
|
+
const bytes = new Uint8Array(32);
|
|
421
|
+
crypto.getRandomValues(bytes);
|
|
422
|
+
return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
417
423
|
}
|
|
418
424
|
|
|
419
425
|
async function signAccess(secret: string, claims: AccessClaims): Promise<string> {
|
|
420
|
-
const header = b64url(JSON.stringify({ alg:
|
|
426
|
+
const header = b64url(JSON.stringify({ alg: ACCESS_TOKEN_ALG, typ: "JWT" }));
|
|
421
427
|
const payload = b64url(JSON.stringify(claims));
|
|
422
428
|
const data = `${header}.${payload}`;
|
|
423
429
|
const sig = await hmac(secret, data);
|
|
@@ -426,9 +432,19 @@ async function signAccess(secret: string, claims: AccessClaims): Promise<string>
|
|
|
426
432
|
|
|
427
433
|
async function verifyAccessSignature(secret: string, token: string): Promise<AccessClaims> {
|
|
428
434
|
const parts = token.split(".");
|
|
435
|
+
// JWS compact = 3 parts; JWE compact = 5 — reject anything else before crypto.
|
|
429
436
|
if (parts.length !== 3) throw new SessionError("malformed access token");
|
|
430
|
-
const [
|
|
431
|
-
|
|
437
|
+
const [headerPart, payload, sig] = parts as [string, string, string];
|
|
438
|
+
let header: { alg?: string };
|
|
439
|
+
try {
|
|
440
|
+
header = JSON.parse(b64urlDecode(headerPart)) as { alg?: string };
|
|
441
|
+
} catch {
|
|
442
|
+
throw new SessionError("malformed access token header");
|
|
443
|
+
}
|
|
444
|
+
if (header.alg !== ACCESS_TOKEN_ALG) {
|
|
445
|
+
throw new SessionError("unsupported access token alg");
|
|
446
|
+
}
|
|
447
|
+
const data = `${headerPart}.${payload}`;
|
|
432
448
|
const expected = await hmac(secret, data);
|
|
433
449
|
if (sig !== expected) throw new SessionError("invalid access token signature");
|
|
434
450
|
return JSON.parse(b64urlDecode(payload)) as AccessClaims;
|
package/src/auth/tenants.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { isApplicationScope } from "../elements/gate/permissions.ts";
|
|
9
9
|
import type { TenantMemberRow, TenantRoleRow, TenantRow } from "./tables.ts";
|
|
10
|
+
import { okid } from "../okid.ts";
|
|
10
11
|
|
|
11
12
|
export type { TenantMemberRow, TenantRoleRow, TenantRow };
|
|
12
13
|
|
|
@@ -99,7 +100,7 @@ export async function createTenant(
|
|
|
99
100
|
): Promise<TenantRow> {
|
|
100
101
|
const now = options.now ?? (() => Date.now());
|
|
101
102
|
const t = now();
|
|
102
|
-
const id = options.id ??
|
|
103
|
+
const id = options.id ?? okid();
|
|
103
104
|
if (store.tenants.has(id)) {
|
|
104
105
|
throw new TenantError("tenant_exists", `tenant already exists: ${id}`);
|
|
105
106
|
}
|
|
@@ -180,7 +181,7 @@ export function addMember(store: TenantStore, options: AddMemberOptions): Tenant
|
|
|
180
181
|
}
|
|
181
182
|
const now = options.now ?? (() => Date.now());
|
|
182
183
|
const row: TenantMemberRow = {
|
|
183
|
-
id: options.id ??
|
|
184
|
+
id: options.id ?? okid(),
|
|
184
185
|
tenantId: options.tenantId,
|
|
185
186
|
userId: options.userId,
|
|
186
187
|
role: options.role ?? DEFAULT_TENANT_ROLE,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending 2FA challenge + step-up grant store.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import {
|
|
7
|
+
consumeChallenge,
|
|
8
|
+
consumeStepUp,
|
|
9
|
+
createPendingTwoFactorStore,
|
|
10
|
+
createStepUpStore,
|
|
11
|
+
findActiveForUser,
|
|
12
|
+
getChallenge,
|
|
13
|
+
grantStepUp,
|
|
14
|
+
hasActiveChallenge,
|
|
15
|
+
issueChallenge,
|
|
16
|
+
twoFactorEmailOtpIdentifier,
|
|
17
|
+
} from "./two-factor-challenge.ts";
|
|
18
|
+
|
|
19
|
+
describe("pending two-factor challenge store", () => {
|
|
20
|
+
test("issue locks method and replaces prior active challenge", () => {
|
|
21
|
+
const store = createPendingTwoFactorStore();
|
|
22
|
+
const first = issueChallenge(store, {
|
|
23
|
+
userId: "u1",
|
|
24
|
+
method: "email_otp",
|
|
25
|
+
now: 1_000,
|
|
26
|
+
ttlMs: 60_000,
|
|
27
|
+
});
|
|
28
|
+
expect(first.method).toBe("email_otp");
|
|
29
|
+
expect(hasActiveChallenge(store, "u1", 1_000)).toBe(true);
|
|
30
|
+
|
|
31
|
+
const second = issueChallenge(store, {
|
|
32
|
+
userId: "u1",
|
|
33
|
+
method: "totp",
|
|
34
|
+
now: 2_000,
|
|
35
|
+
ttlMs: 60_000,
|
|
36
|
+
});
|
|
37
|
+
expect(second.id).not.toBe(first.id);
|
|
38
|
+
expect(getChallenge(store, first.id, 2_000)).toBeUndefined();
|
|
39
|
+
expect(findActiveForUser(store, "u1", 2_000)?.method).toBe("totp");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("consume clears active pointer", () => {
|
|
43
|
+
const store = createPendingTwoFactorStore();
|
|
44
|
+
const row = issueChallenge(store, { userId: "u1", method: "totp", now: 1_000 });
|
|
45
|
+
consumeChallenge(store, row, 1_500);
|
|
46
|
+
expect(hasActiveChallenge(store, "u1", 1_500)).toBe(false);
|
|
47
|
+
expect(getChallenge(store, row.id, 1_500)).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("step-up grant is single-use and purpose-scoped", () => {
|
|
51
|
+
const stepUp = createStepUpStore();
|
|
52
|
+
grantStepUp(stepUp, "u1", "change", 1_000, 60_000);
|
|
53
|
+
expect(consumeStepUp(stepUp, "u1", "enroll", 1_100)).toBe(false);
|
|
54
|
+
expect(consumeStepUp(stepUp, "u1", ["change", "enroll"], 1_100)).toBe(true);
|
|
55
|
+
expect(consumeStepUp(stepUp, "u1", "change", 1_200)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("twoFactorEmailOtpIdentifier namespaces by user", () => {
|
|
59
|
+
expect(twoFactorEmailOtpIdentifier("abc")).toBe("2fa:abc");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { okid } from "../okid.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Pending login 2FA challenges + short-lived step-up grants.
|
|
4
|
+
*
|
|
5
|
+
* One active unresolved challenge per user; method is locked at issue time
|
|
6
|
+
* so enrollment / method-switch cannot bypass the configured factor.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Locked second-factor method for a login challenge. */
|
|
10
|
+
export type TwoFactorMethod = "totp" | "email_otp";
|
|
11
|
+
|
|
12
|
+
/** Default login-challenge TTL (align with OTP). */
|
|
13
|
+
export const DEFAULT_TWO_FACTOR_CHALLENGE_TTL_MS = 10 * 60 * 1000;
|
|
14
|
+
|
|
15
|
+
/** Default step-up grant TTL for privileged 2FA ops. */
|
|
16
|
+
export const DEFAULT_STEP_UP_TTL_MS = 5 * 60 * 1000;
|
|
17
|
+
|
|
18
|
+
/** One pending login 2FA challenge. */
|
|
19
|
+
export interface PendingTwoFactorChallenge {
|
|
20
|
+
id: string;
|
|
21
|
+
userId: string;
|
|
22
|
+
method: TwoFactorMethod;
|
|
23
|
+
createdAt: number;
|
|
24
|
+
expiresAt: number;
|
|
25
|
+
consumedAt: number | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** In-memory pending 2FA challenge store. */
|
|
29
|
+
export interface PendingTwoFactorStore {
|
|
30
|
+
readonly byId: Map<string, PendingTwoFactorChallenge>;
|
|
31
|
+
/** userId → active challenge id (at most one). */
|
|
32
|
+
readonly byUserId: Map<string, string>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create an empty pending 2FA challenge store.
|
|
37
|
+
*/
|
|
38
|
+
export function createPendingTwoFactorStore(): PendingTwoFactorStore {
|
|
39
|
+
return { byId: new Map(), byUserId: new Map() };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Options for {@link issueChallenge}. */
|
|
43
|
+
export interface IssueChallengeOptions {
|
|
44
|
+
readonly userId: string;
|
|
45
|
+
readonly method: TwoFactorMethod;
|
|
46
|
+
readonly now?: number;
|
|
47
|
+
readonly ttlMs?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Invalidate any prior active challenge for the user and issue a new locked one.
|
|
52
|
+
*
|
|
53
|
+
* @param store - Pending store
|
|
54
|
+
* @param options - User, method, clock
|
|
55
|
+
*/
|
|
56
|
+
export function issueChallenge(
|
|
57
|
+
store: PendingTwoFactorStore,
|
|
58
|
+
options: IssueChallengeOptions,
|
|
59
|
+
): PendingTwoFactorChallenge {
|
|
60
|
+
const now = options.now ?? Date.now();
|
|
61
|
+
const ttlMs = options.ttlMs ?? DEFAULT_TWO_FACTOR_CHALLENGE_TTL_MS;
|
|
62
|
+
invalidateActiveForUser(store, options.userId, now);
|
|
63
|
+
const row: PendingTwoFactorChallenge = {
|
|
64
|
+
id: okid(),
|
|
65
|
+
userId: options.userId,
|
|
66
|
+
method: options.method,
|
|
67
|
+
createdAt: now,
|
|
68
|
+
expiresAt: now + ttlMs,
|
|
69
|
+
consumedAt: null,
|
|
70
|
+
};
|
|
71
|
+
store.byId.set(row.id, row);
|
|
72
|
+
store.byUserId.set(options.userId, row.id);
|
|
73
|
+
return row;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Find a non-consumed, non-expired challenge by id.
|
|
78
|
+
*
|
|
79
|
+
* @param store - Pending store
|
|
80
|
+
* @param challengeId - Challenge id
|
|
81
|
+
* @param now - Clock
|
|
82
|
+
*/
|
|
83
|
+
export function getChallenge(
|
|
84
|
+
store: PendingTwoFactorStore,
|
|
85
|
+
challengeId: string,
|
|
86
|
+
now: number = Date.now(),
|
|
87
|
+
): PendingTwoFactorChallenge | undefined {
|
|
88
|
+
const row = store.byId.get(challengeId);
|
|
89
|
+
if (!row) return undefined;
|
|
90
|
+
if (row.consumedAt !== null) return undefined;
|
|
91
|
+
if (row.expiresAt <= now) {
|
|
92
|
+
clearUserPointer(store, row);
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
return row;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Find the active unresolved challenge for a user, if any.
|
|
100
|
+
*
|
|
101
|
+
* @param store - Pending store
|
|
102
|
+
* @param userId - User id
|
|
103
|
+
* @param now - Clock
|
|
104
|
+
*/
|
|
105
|
+
export function findActiveForUser(
|
|
106
|
+
store: PendingTwoFactorStore,
|
|
107
|
+
userId: string,
|
|
108
|
+
now: number = Date.now(),
|
|
109
|
+
): PendingTwoFactorChallenge | undefined {
|
|
110
|
+
const id = store.byUserId.get(userId);
|
|
111
|
+
if (!id) return undefined;
|
|
112
|
+
const row = getChallenge(store, id, now);
|
|
113
|
+
if (!row) {
|
|
114
|
+
store.byUserId.delete(userId);
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return row;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* True when the user has an active unresolved login 2FA challenge.
|
|
122
|
+
*
|
|
123
|
+
* @param store - Pending store
|
|
124
|
+
* @param userId - User id
|
|
125
|
+
* @param now - Clock
|
|
126
|
+
*/
|
|
127
|
+
export function hasActiveChallenge(
|
|
128
|
+
store: PendingTwoFactorStore,
|
|
129
|
+
userId: string,
|
|
130
|
+
now: number = Date.now(),
|
|
131
|
+
): boolean {
|
|
132
|
+
return findActiveForUser(store, userId, now) !== undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Consume a challenge after successful verification.
|
|
137
|
+
*
|
|
138
|
+
* @param store - Pending store
|
|
139
|
+
* @param row - Active challenge
|
|
140
|
+
* @param now - Clock
|
|
141
|
+
*/
|
|
142
|
+
export function consumeChallenge(
|
|
143
|
+
store: PendingTwoFactorStore,
|
|
144
|
+
row: PendingTwoFactorChallenge,
|
|
145
|
+
now: number = Date.now(),
|
|
146
|
+
): void {
|
|
147
|
+
row.consumedAt = now;
|
|
148
|
+
clearUserPointer(store, row);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Invalidate the active challenge for a user (fresh sign-in / abort).
|
|
153
|
+
*
|
|
154
|
+
* @param store - Pending store
|
|
155
|
+
* @param userId - User id
|
|
156
|
+
* @param now - Clock
|
|
157
|
+
*/
|
|
158
|
+
export function invalidateActiveForUser(
|
|
159
|
+
store: PendingTwoFactorStore,
|
|
160
|
+
userId: string,
|
|
161
|
+
now: number = Date.now(),
|
|
162
|
+
): void {
|
|
163
|
+
const id = store.byUserId.get(userId);
|
|
164
|
+
if (!id) return;
|
|
165
|
+
const row = store.byId.get(id);
|
|
166
|
+
if (row && row.consumedAt === null) {
|
|
167
|
+
row.consumedAt = now;
|
|
168
|
+
}
|
|
169
|
+
store.byUserId.delete(userId);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function clearUserPointer(store: PendingTwoFactorStore, row: PendingTwoFactorChallenge): void {
|
|
173
|
+
if (store.byUserId.get(row.userId) === row.id) {
|
|
174
|
+
store.byUserId.delete(row.userId);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Privileged step-up grant after verifying the current 2FA method. */
|
|
179
|
+
export interface StepUpGrant {
|
|
180
|
+
userId: string;
|
|
181
|
+
expiresAt: number;
|
|
182
|
+
purpose: "enroll" | "change" | "disable";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** In-memory step-up grant store. */
|
|
186
|
+
export interface StepUpStore {
|
|
187
|
+
readonly byUserId: Map<string, StepUpGrant>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Create an empty step-up grant store.
|
|
192
|
+
*/
|
|
193
|
+
export function createStepUpStore(): StepUpStore {
|
|
194
|
+
return { byUserId: new Map() };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Record a short-lived step-up grant for privileged 2FA operations.
|
|
199
|
+
*
|
|
200
|
+
* @param store - Step-up store
|
|
201
|
+
* @param userId - User id
|
|
202
|
+
* @param purpose - Intended privileged op
|
|
203
|
+
* @param now - Clock
|
|
204
|
+
* @param ttlMs - Grant TTL
|
|
205
|
+
*/
|
|
206
|
+
export function grantStepUp(
|
|
207
|
+
store: StepUpStore,
|
|
208
|
+
userId: string,
|
|
209
|
+
purpose: StepUpGrant["purpose"],
|
|
210
|
+
now: number = Date.now(),
|
|
211
|
+
ttlMs: number = DEFAULT_STEP_UP_TTL_MS,
|
|
212
|
+
): StepUpGrant {
|
|
213
|
+
const grant: StepUpGrant = { userId, purpose, expiresAt: now + ttlMs };
|
|
214
|
+
store.byUserId.set(userId, grant);
|
|
215
|
+
return grant;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Consume a valid step-up grant (single-use). Returns false if missing/expired/wrong purpose.
|
|
220
|
+
*
|
|
221
|
+
* @param store - Step-up store
|
|
222
|
+
* @param userId - User id
|
|
223
|
+
* @param purpose - Required purpose (or any of the listed)
|
|
224
|
+
* @param now - Clock
|
|
225
|
+
*/
|
|
226
|
+
export function consumeStepUp(
|
|
227
|
+
store: StepUpStore,
|
|
228
|
+
userId: string,
|
|
229
|
+
purpose: StepUpGrant["purpose"] | readonly StepUpGrant["purpose"][],
|
|
230
|
+
now: number = Date.now(),
|
|
231
|
+
): boolean {
|
|
232
|
+
const grant = store.byUserId.get(userId);
|
|
233
|
+
if (!grant) return false;
|
|
234
|
+
if (grant.expiresAt <= now) {
|
|
235
|
+
store.byUserId.delete(userId);
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
const allowed = typeof purpose === "string" ? [purpose] : purpose;
|
|
239
|
+
if (!allowed.includes(grant.purpose)) return false;
|
|
240
|
+
store.byUserId.delete(userId);
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Peek whether a valid step-up grant exists (does not consume).
|
|
246
|
+
*
|
|
247
|
+
* @param store - Step-up store
|
|
248
|
+
* @param userId - User id
|
|
249
|
+
* @param purpose - Required purpose
|
|
250
|
+
* @param now - Clock
|
|
251
|
+
*/
|
|
252
|
+
export function hasStepUp(
|
|
253
|
+
store: StepUpStore,
|
|
254
|
+
userId: string,
|
|
255
|
+
purpose: StepUpGrant["purpose"] | readonly StepUpGrant["purpose"][],
|
|
256
|
+
now: number = Date.now(),
|
|
257
|
+
): boolean {
|
|
258
|
+
const grant = store.byUserId.get(userId);
|
|
259
|
+
if (!grant) return false;
|
|
260
|
+
if (grant.expiresAt <= now) {
|
|
261
|
+
store.byUserId.delete(userId);
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
const allowed = typeof purpose === "string" ? [purpose] : purpose;
|
|
265
|
+
return allowed.includes(grant.purpose);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** Sign-in response when 2FA must complete before session issue. */
|
|
269
|
+
export interface TwoFactorRequiredOut {
|
|
270
|
+
readonly twoFactorRequired: true;
|
|
271
|
+
readonly challengeId: string;
|
|
272
|
+
readonly method: TwoFactorMethod;
|
|
273
|
+
readonly userId: string;
|
|
274
|
+
readonly devOtp?: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Verification-store identifier for email OTP used as a second factor.
|
|
279
|
+
*
|
|
280
|
+
* @param userId - User id
|
|
281
|
+
*/
|
|
282
|
+
export function twoFactorEmailOtpIdentifier(userId: string): string {
|
|
283
|
+
return `2fa:${userId}`;
|
|
284
|
+
}
|
package/src/auth/verification.ts
CHANGED
|
@@ -27,6 +27,11 @@ export interface VerificationRow {
|
|
|
27
27
|
email?: string | null;
|
|
28
28
|
/** E.164 phone captured at request. */
|
|
29
29
|
phone?: string | null;
|
|
30
|
+
/**
|
|
31
|
+
* Ceremony session id (passkey WebAuthn) — challenge is bound to this id.
|
|
32
|
+
* Other auth methods leave it unset.
|
|
33
|
+
*/
|
|
34
|
+
sessionId?: string | null;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
/** In-memory verification store. */
|
package/src/bench/REPORT.md
CHANGED
|
@@ -174,3 +174,83 @@ Artifact: `G15-postgres-degradation-…json` (+ extra stress-level artifacts)
|
|
|
174
174
|
2. G3b wave-2 steady-state sat below the ~500-subscriber target; fd-per-subscriber trend unaffected.
|
|
175
175
|
3. Channel bulk throughput now bounded by per-transport serialization (correctness over throughput); revisit if fan-out email volume matters.
|
|
176
176
|
4. G15 is in-process backpressure only (see honesty note).
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## G17 — Hybrid SQL search (BM25 / LSH / fusion)
|
|
181
|
+
|
|
182
|
+
**Command:** `OKE_BENCH=1 OKE_TEST_POSTGRES=1 DATABASE_URL=$DATABASE_URL bun test ./src/bench/g17-hybrid-search.bench.ts --timeout 3600000`
|
|
183
|
+
**Hardware:** Apple M4, 24 GB · trend-analysis only (not SLA; macOS ≠ Linux prod)
|
|
184
|
+
|
|
185
|
+
Synthetic 32-dim embeddings (deterministic hash bag — not a production model). Modes: text-only (BM25/GIN), vector-only (LSH + cosine), hybrid (RRF k=60). Recall = precision@10 of the LSH/hybrid path vs **exact brute-force cosine** on the same in-memory vectors.
|
|
186
|
+
|
|
187
|
+
### Before — v0.19.0 (2026-09-10)
|
|
188
|
+
|
|
189
|
+
**Postgres:** 16.15 · **Artifact:** `g17-hybrid-search-1789039639500.json`
|
|
190
|
+
Query path: K=64 bucket equality plus Hamming-1 (`= ANY(65 buckets)`), oversample ≤50.
|
|
191
|
+
|
|
192
|
+
#### Latency (p50 / p99 ms)
|
|
193
|
+
|
|
194
|
+
| N | text p50 | text p99 | vector p50 | vector p99 | hybrid p50 | hybrid p99 |
|
|
195
|
+
| ---- | -------- | -------- | ---------- | ---------- | ---------- | ---------- |
|
|
196
|
+
| 1k | 1.32 | 3.6 | 1.35 | 2.19 | 1.43 | 2.04 |
|
|
197
|
+
| 10k | 2.77 | 3.85 | 3.74 | 4.38 | 2.99 | 5.97 |
|
|
198
|
+
| 100k | 39.86 | 59.73 | 57.05 | 126.14 | 52.08 | 288.09 |
|
|
199
|
+
| 1M | 877.57 | 2243.68 | 1165.5 | 3370.19 | 649.14 | 1861.91 |
|
|
200
|
+
|
|
201
|
+
#### Recall — LSH approx vs exact cosine (precision@10)
|
|
202
|
+
|
|
203
|
+
| N | vector P@10 | hybrid P@10 | exact cosine p50 (ms) | approx p50 (ms) |
|
|
204
|
+
| ---- | ----------- | ----------- | --------------------- | --------------- |
|
|
205
|
+
| 1k | **0.000** | 0.067 | 0.16 | 1.41 / 1.6 |
|
|
206
|
+
| 10k | **0.000** | **0.000** | ~1.6 | ~3.5 |
|
|
207
|
+
| 100k | **0.000** | **0.000** | ~36 | ~54 |
|
|
208
|
+
| 1M | 0.008 | **0.000** | ~445 | ~600 |
|
|
209
|
+
|
|
210
|
+
**Root cause (not an LSH-vs-HNSW tradeoff):** write-time and query-time buckets were byte-identical; `neighborBuckets` did emit 65 probes; `= ANY(?::bigint[])` matched stored rows. Hamming-1 at K=64 cannot see true neighbors. On this generator, same-topic docs sit at cosine ≈0.96 / Hamming ~5; a topic-string query vs those docs is cosine ≈0.82 / expected Hamming ~12. P(Hamming ≤ 1) at K=64 is ~10⁻⁵. A 40-row hand case recovered **0/10** exact neighbors via Hamming-1 and **10/10** via Hamming-rank + cosine. The hash-bag embedding is clustered (8 topics, near-duplicate bodies) — that makes exact top-10 at large N a lottery among the cluster, but it does **not** explain the zero collapse at N=40.
|
|
211
|
+
|
|
212
|
+
The 2026-09-10 EXPLAIN (Seq Scan, `tsv @@ q OR lsh = ANY(…)`) was a separate planner finding on that query shape.
|
|
213
|
+
|
|
214
|
+
### After — Hamming-rank candidate path (2026-09-11)
|
|
215
|
+
|
|
216
|
+
**Date:** 2026-09-11 · **Engine:** Bun 1.4.2 · **Postgres:** 16 (dedicated live instance, not PGlite)
|
|
217
|
+
**Artifact:** `g17-hybrid-search-1789135622349.json`
|
|
218
|
+
Fix: UNION of GIN lexical `LIMIT` and SimHash k-NN (`ORDER BY bit_count((lsh # query)::bit(64)) LIMIT oversample`). Same K=64, same oversample formula `max(limit×5, 50)` capped at 500. Not a K/oversample paper-over.
|
|
219
|
+
|
|
220
|
+
#### Latency (p50 / p99 ms)
|
|
221
|
+
|
|
222
|
+
| N | text p50 | text p99 | vector p50 | vector p99 | hybrid p50 | hybrid p99 |
|
|
223
|
+
| ---- | -------- | -------- | ---------- | ---------- | ---------- | ---------- |
|
|
224
|
+
| 1k | 0.88 | 2.8 | 1.27 | 2.37 | 1.28 | 1.54 |
|
|
225
|
+
| 10k | 4.5 | 17.21 | 9.36 | 29.03 | 6.88 | 32.48 |
|
|
226
|
+
| 100k | 21.03 | 27.61 | 30.61 | 49.8 | 34.08 | 89.99 |
|
|
227
|
+
| 1M | 305.61 | 377.7 | 388.98 | 548.36 | 391.96 | 469.76 |
|
|
228
|
+
|
|
229
|
+
#### Recall — LSH approx vs exact cosine (precision@10)
|
|
230
|
+
|
|
231
|
+
| N | vector P@10 | hybrid P@10 | exact cosine p50 (ms) | approx p50 (ms) |
|
|
232
|
+
| ---- | ----------- | ----------- | --------------------- | --------------- |
|
|
233
|
+
| 1k | 0.167 | 0.117 | 0.6 / 0.41 | 1.25 / 1.3 |
|
|
234
|
+
| 10k | 0.100 | 0.017 | ~6 | ~9 |
|
|
235
|
+
| 100k | 0.017 | 0.008 | ~45 | ~32 |
|
|
236
|
+
| 1M | 0.017 | 0.000 | ~535 | ~363 |
|
|
237
|
+
|
|
238
|
+
**Honest verdict after the fix:** the near-total collapse at 10k/100k is gone on the vector path (smooth 0.17 → 0.10 → 0.017). Remaining low precision@10 vs exact cosine is the real K=64 SimHash + 50-candidate oversample tradeoff on this near-duplicate hash-bag corpus — not a hashing mismatch. Still **not** a drop-in for HNSW/pgvector. Prefer BM25-only or an external `store.index` (`pgvector` / Meilisearch) when semantic recall matters.
|
|
239
|
+
|
|
240
|
+
### EXPLAIN (ANALYZE, BUFFERS) at N=100k (after)
|
|
241
|
+
|
|
242
|
+
UNION Append (~14.7 ms): **Bitmap Index Scan** on the GIN tsvector (50 lexical rows) plus **Parallel Seq Scan** + top-N heapsort on `bit_count((lsh # query)::bit(64))` (50 SimHash-nearest). Hamming-rank cannot use the LSH B-tree (equality index). Do not claim BitmapOr from design alone — re-check `EXPLAIN` on production data volumes and `ANALYZE`d tables.
|
|
243
|
+
|
|
244
|
+
### Backfill kill / resume (50k pre-populated rows)
|
|
245
|
+
|
|
246
|
+
| Metric | Before (2026-09-10) | After (2026-09-11) |
|
|
247
|
+
| ---------------------------------- | ------------------- | ------------------ |
|
|
248
|
+
| Kill after embeds | 2,000 | 2,000 |
|
|
249
|
+
| Embeds at kill | 2,000 | 2,000 |
|
|
250
|
+
| Kill wall | 726 ms | 732 ms |
|
|
251
|
+
| Resume wall (re-run to completion) | 29.3 s | 28.3 s |
|
|
252
|
+
| Final embedded rows | 50,000 | 50,000 |
|
|
253
|
+
|
|
254
|
+
Re-running `oke db search-backfill` after interrupt completed idempotently (safe re-entry; DF/stats rebuilt).
|
|
255
|
+
|
|
256
|
+
**Issues recorded in after artifact:** eight low-recall notes (P@10 still < 0.3 at every size — honest SimHash-vs-exact remainder, not the Hamming-1 zero collapse).
|
|
@@ -33,7 +33,7 @@ let bus: SignalBus;
|
|
|
33
33
|
beforeAll(async () => {
|
|
34
34
|
if (!LIVE_PG) throw new Error("G3 needs live Postgres: set OKE_TEST_POSTGRES=1 + DATABASE_URL");
|
|
35
35
|
sql = createBunSignalSql(LIVE_PG);
|
|
36
|
-
const decl = signal(SIGNAL_NAME, {
|
|
36
|
+
const decl = signal.once(SIGNAL_NAME, { retries: 2, deadLetter: true });
|
|
37
37
|
bus = await openPostgresSignal({
|
|
38
38
|
signals: new Map([[decl.name, decl]]),
|
|
39
39
|
sql,
|
|
@@ -137,7 +137,7 @@ async function runArm(
|
|
|
137
137
|
const obsLats: number[] = [];
|
|
138
138
|
let obsQueries = 0;
|
|
139
139
|
if (observers && sql) {
|
|
140
|
-
const decl = signal("g10-watch", {
|
|
140
|
+
const decl = signal.once("g10-watch", { optional: true });
|
|
141
141
|
const consoleBus: SignalBus = await openPostgresSignal({
|
|
142
142
|
signals: new Map([[decl.name, decl]]),
|
|
143
143
|
sql,
|