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
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,52 @@ 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
|
+
**Date:** 2026-09-10 · **Engine:** Bun 1.4.2 · **Postgres:** 16.15 (dedicated live instance, not PGlite)
|
|
184
|
+
**Hardware:** Apple M4, 24 GB · trend-analysis only (not SLA; macOS ≠ Linux prod)
|
|
185
|
+
**Artifact:** `g17-hybrid-search-1789039639500.json`
|
|
186
|
+
|
|
187
|
+
Synthetic 32-dim embeddings (deterministic hash bag — not a production model). Modes: text-only (BM25/GIN), vector-only (LSH buckets + cosine), hybrid (RRF k=60). Recall = precision@10 of the LSH/hybrid path vs **exact brute-force cosine** on the same in-memory vectors.
|
|
188
|
+
|
|
189
|
+
### Latency (p50 / p99 ms)
|
|
190
|
+
|
|
191
|
+
| N | text p50 | text p99 | vector p50 | vector p99 | hybrid p50 | hybrid p99 |
|
|
192
|
+
| ---- | -------- | -------- | ---------- | ---------- | ---------- | ---------- |
|
|
193
|
+
| 1k | 1.32 | 3.6 | 1.35 | 2.19 | 1.43 | 2.04 |
|
|
194
|
+
| 10k | 2.77 | 3.85 | 3.74 | 4.38 | 2.99 | 5.97 |
|
|
195
|
+
| 100k | 39.86 | 59.73 | 57.05 | 126.14 | 52.08 | 288.09 |
|
|
196
|
+
| 1M | 877.57 | 2243.68 | 1165.5 | 3370.19 | 649.14 | 1861.91 |
|
|
197
|
+
|
|
198
|
+
### Recall — LSH approx vs exact cosine (precision@10)
|
|
199
|
+
|
|
200
|
+
| N | vector P@10 | hybrid P@10 | exact cosine p50 (ms) | approx p50 (ms) |
|
|
201
|
+
| ---- | ----------- | ----------- | --------------------- | --------------- |
|
|
202
|
+
| 1k | **0.000** | 0.067 | 0.16 | 1.41 / 1.6 |
|
|
203
|
+
| 10k | **0.000** | **0.000** | ~1.6 | ~3.5 |
|
|
204
|
+
| 100k | **0.000** | **0.000** | ~36 | ~54 |
|
|
205
|
+
| 1M | 0.008 | **0.000** | ~445 | ~600 |
|
|
206
|
+
|
|
207
|
+
**Honest verdict:** on this corpus the random-hyperplane LSH path (K=64, Hamming-1 neighbors, candidate oversample ≤50) does **not** recover the exact cosine top-10. Treat built-in LSH as a cheap candidate generator for hybrid fusion — **not** a drop-in for HNSW/pgvector recall. Prefer BM25-only or an external `store.index` (`pgvector` / Meilisearch) when semantic recall matters.
|
|
208
|
+
|
|
209
|
+
### EXPLAIN (ANALYZE, BUFFERS) at N=100k
|
|
210
|
+
|
|
211
|
+
Postgres 16 chose a **Seq Scan** with a Filter on `__oke_tsv @@ plainto_tsquery(…) OR __oke_lsh_body = ANY(…)` (Limit 50; execution ~0.27 ms; shared hit=27). GIN + LSH B-tree indexes exist; the planner did **not** emit BitmapOr on this hardware/stats for the measured query. Do not claim BitmapOr from design alone — re-check `EXPLAIN` on production data volumes and `ANALYZE`d tables.
|
|
212
|
+
|
|
213
|
+
### Backfill kill / resume (50k pre-populated rows)
|
|
214
|
+
|
|
215
|
+
| Metric | Value |
|
|
216
|
+
| ---------------------------------- | ------ |
|
|
217
|
+
| Kill after embeds | 2,000 |
|
|
218
|
+
| Embeds at kill | 2,000 |
|
|
219
|
+
| Kill wall | 726 ms |
|
|
220
|
+
| Resume wall (re-run to completion) | 29.3 s |
|
|
221
|
+
| Final embedded rows | 50,000 |
|
|
222
|
+
|
|
223
|
+
Re-running `oke db search-backfill` after interrupt completed idempotently (safe re-entry; DF/stats rebuilt).
|
|
224
|
+
|
|
225
|
+
**Issues recorded in artifact:** eight low-recall notes (all sizes). No product fix applied mid-bench — numbers stand as measured.
|
|
@@ -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,
|