okengine 0.18.4 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +18 -10
- package/README.md +2 -2
- package/package.json +51 -50
- package/site/content/docs/ai/index.mdx +25 -6
- package/site/content/docs/ai/llms-txt.mdx +1 -1
- package/site/content/docs/ai/meta.json +1 -1
- package/site/content/docs/ai/skills.mdx +4 -1
- package/site/content/docs/ai/try-it.mdx +58 -0
- package/site/content/docs/client/auth.mdx +208 -0
- package/site/content/docs/client/calling.mdx +451 -0
- package/site/content/docs/client/index.mdx +209 -0
- package/site/content/docs/client/live.mdx +234 -0
- package/site/content/docs/client/meta.json +5 -0
- package/site/content/docs/client/react.mdx +249 -0
- package/site/content/docs/elements/ai/agents.mdx +232 -0
- package/site/content/docs/elements/ai/index.mdx +341 -0
- package/site/content/docs/elements/ai/mcp.mdx +275 -0
- package/site/content/docs/elements/ai/meta.json +5 -0
- package/site/content/docs/elements/ai/models.mdx +283 -0
- package/site/content/docs/elements/ai/prompts.mdx +256 -0
- package/site/content/docs/elements/channel/email.mdx +266 -0
- package/site/content/docs/elements/channel/index.mdx +344 -0
- package/site/content/docs/elements/channel/meta.json +5 -0
- package/site/content/docs/elements/channel/push.mdx +218 -0
- package/site/content/docs/elements/channel/receipts.mdx +206 -0
- package/site/content/docs/elements/channel/sms.mdx +264 -0
- package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
- package/site/content/docs/elements/clock/index.mdx +300 -0
- package/site/content/docs/elements/clock/meta.json +5 -0
- package/site/content/docs/elements/clock/schedules.mdx +508 -0
- package/site/content/docs/elements/clock/sleep.mdx +445 -0
- package/site/content/docs/elements/flow/consumers.mdx +707 -0
- package/site/content/docs/elements/flow/http.mdx +1080 -0
- package/site/content/docs/elements/flow/index.mdx +618 -0
- package/site/content/docs/elements/flow/meta.json +5 -0
- package/site/content/docs/elements/flow/routing.mdx +559 -0
- package/site/content/docs/elements/flow/workflows.mdx +640 -0
- package/site/content/docs/elements/gate/auth.mdx +371 -0
- package/site/content/docs/elements/gate/authorization.mdx +298 -0
- package/site/content/docs/elements/gate/index.mdx +418 -0
- package/site/content/docs/elements/gate/meta.json +5 -0
- package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
- package/site/content/docs/elements/gate/rls.mdx +303 -0
- package/site/content/docs/elements/gate/tenancy.mdx +372 -0
- package/site/content/docs/elements/index.mdx +52 -11
- package/site/content/docs/elements/signal/broadcast.mdx +517 -0
- package/site/content/docs/elements/signal/index.mdx +352 -0
- package/site/content/docs/elements/signal/live.mdx +590 -0
- package/site/content/docs/elements/signal/meta.json +5 -0
- package/site/content/docs/elements/signal/once.mdx +596 -0
- package/site/content/docs/elements/store/files.mdx +659 -0
- package/site/content/docs/elements/store/index.mdx +310 -0
- package/site/content/docs/elements/store/kv.mdx +629 -0
- package/site/content/docs/elements/store/meta.json +5 -0
- package/site/content/docs/elements/store/search.mdx +946 -0
- package/site/content/docs/elements/store/sql.mdx +937 -0
- package/site/content/docs/elements/vault/config.mdx +256 -0
- package/site/content/docs/elements/vault/index.mdx +356 -0
- package/site/content/docs/elements/vault/meta.json +5 -0
- package/site/content/docs/elements/vault/rotation.mdx +273 -0
- package/site/content/docs/elements/vault/secrets.mdx +285 -0
- package/site/content/docs/index.mdx +78 -30
- package/site/content/docs/meta.json +11 -8
- package/site/content/docs/plugins/cors.mdx +2 -1
- package/site/content/docs/plugins/csrf.mdx +4 -1
- package/site/content/docs/plugins/headers.mdx +2 -1
- package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
- package/site/content/docs/plugins/magic-link.mdx +6 -0
- package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
- package/site/content/docs/plugins/oauth.mdx +1 -1
- package/site/content/docs/plugins/otp.mdx +12 -1
- package/site/content/docs/plugins/passkey.mdx +39 -15
- package/site/content/docs/plugins/two-factor.mdx +91 -34
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +3 -3
- package/site/content/docs/recipes/index.mdx +18 -28
- package/site/content/docs/recipes/mailpit.mdx +1 -1
- package/site/content/docs/recipes/meilisearch.mdx +3 -3
- package/site/content/docs/recipes/meta.json +1 -4
- package/site/content/docs/recipes/nginx.mdx +2 -2
- package/site/content/docs/recipes/openrouter.mdx +283 -0
- package/site/content/docs/recipes/pgdog.mdx +3 -3
- package/site/content/docs/recipes/postgres.mdx +1 -2
- package/site/content/docs/recipes/rustfs.mdx +2 -2
- package/site/content/docs/recipes/traefik.mdx +3 -3
- package/site/content/docs/reference/cli.mdx +231 -0
- package/site/content/docs/reference/configuration.mdx +30 -10
- package/site/content/docs/reference/environment-variables.mdx +93 -20
- package/site/content/docs/reference/errors.mdx +132 -29
- package/site/content/docs/reference/fx.mdx +137 -73
- package/site/content/docs/reference/index.mdx +13 -21
- package/site/content/docs/reference/meta.json +4 -5
- package/site/content/docs/reference/okid.mdx +42 -17
- package/site/content/docs/reference/plugins.mdx +4 -3
- package/site/content/docs/reference/security.mdx +197 -0
- package/site/content/docs/understand/meta.json +5 -0
- package/site/content/docs/understand/the-anatomy.mdx +132 -0
- package/site/content/docs/understand/the-model.mdx +32 -0
- package/site/content/docs/understand/the-problem.mdx +74 -0
- package/site/content/docs/understand/the-vocabulary.mdx +26 -0
- package/src/auth/api-keys.ts +2 -1
- package/src/auth/bindings.ts +64 -16
- package/src/auth/gate-auth.test.ts +6 -1
- package/src/auth/identity.ts +152 -5
- package/src/auth/index.ts +29 -0
- package/src/auth/invites.ts +2 -1
- package/src/auth/method-context.ts +41 -0
- package/src/auth/oauth-as/crypto.ts +2 -1
- package/src/auth/oauth-as/stores.ts +2 -1
- package/src/auth/operator.ts +2 -1
- package/src/auth/sessions-jwt.test.ts +77 -0
- package/src/auth/sessions.ts +20 -4
- package/src/auth/tenants.ts +3 -2
- package/src/auth/two-factor-challenge.test.ts +61 -0
- package/src/auth/two-factor-challenge.ts +284 -0
- package/src/auth/verification.ts +5 -0
- package/src/bench/REPORT.md +49 -0
- package/src/bench/g03-signal-once.bench.ts +1 -1
- package/src/bench/g10-observability-contention.bench.ts +1 -1
- package/src/bench/g17-hybrid-search.bench.ts +572 -0
- package/src/bench/load-app.ts +2 -10
- package/src/cli/ai-setup/ai-setup.test.ts +331 -42
- package/src/cli/ai-setup/apply.ts +294 -52
- package/src/cli/ai-setup/catalog.ts +238 -1342
- package/src/cli/ai-setup/index.ts +14 -99
- package/src/cli/ai-setup/prompts.ts +60 -443
- package/src/cli/ask-seed.test.ts +96 -0
- package/src/cli/ask-seed.ts +82 -0
- package/src/cli/ask-vault-gaps.test.ts +84 -0
- package/src/cli/ask-vault-gaps.ts +146 -0
- package/src/cli/build.ts +2 -2
- package/src/cli/client-add.test.ts +26 -1
- package/src/cli/client-add.ts +106 -27
- package/src/cli/competitor-mention-removal.test.ts +1 -1
- package/src/cli/db-seed.ts +2 -0
- package/src/cli/db.ts +150 -11
- package/src/cli/dev.ts +119 -211
- package/src/cli/docker-clean.ts +2 -2
- package/src/cli/docker-cli.test.ts +1 -1
- package/src/cli/doctor-diff.ts +4 -2
- package/src/cli/doctor-pii.test.ts +1 -1
- package/src/cli/doctor.ts +14 -1
- package/src/cli/hero-meta.test.ts +4 -2
- package/src/cli/load-config.images.test.ts +8 -10
- package/src/cli/load-config.ts +1 -1
- package/src/cli/registry.ts +22 -10
- package/src/cli/replay.ts +3 -1
- package/src/cli/start.ts +1 -1
- package/src/cli/tui/keys.ts +2 -3
- package/src/client/auth/cookies.ts +74 -0
- package/src/client/auth/create-auth-client.ts +636 -0
- package/src/client/auth/denials.ts +99 -0
- package/src/client/auth/session.ts +283 -0
- package/src/client/auth.test.ts +251 -0
- package/src/client/auth.ts +39 -114
- package/src/client/create-with-session.ts +245 -0
- package/src/client/create.ts +51 -5
- package/src/client/index.ts +11 -1
- package/src/client/live.ts +11 -79
- package/src/client/notes-contract.test.ts +5 -10
- package/src/client/sse.ts +137 -0
- package/src/client/stream.ts +147 -0
- package/src/client/transport.test.ts +32 -0
- package/src/client/transport.ts +123 -26
- package/src/client/types.ts +118 -14
- package/src/client-react/index.ts +185 -24
- package/src/client-react/use-live-query.ts +13 -3
- package/src/compiler/aot.test.ts +7 -4
- package/src/compiler/effects-embed.test.ts +56 -0
- package/src/compiler/effects-fetch.test.ts +43 -0
- package/src/compiler/effects-infer.ts +58 -2
- package/src/compiler/extract.test.ts +258 -44
- package/src/compiler/extract.ts +566 -48
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
- package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
- package/src/compiler/fixtures/skyport.expected.json +4 -4
- package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
- package/src/compiler/response.ts +2 -2
- package/src/compiler/search-writer-isolation.test.ts +40 -0
- package/src/config/index.ts +11 -0
- package/src/console/server/app.ts +17 -23
- package/src/console/server/bind.ts +4 -0
- package/src/console/server/console.test.ts +2 -2
- package/src/console/server/flows-invoke.test.ts +50 -34
- package/src/console/server/flows.ts +436 -255
- package/src/console/server/runs-ingest.test.ts +6 -3
- package/src/console/server/serve.ts +1 -1
- package/src/console/server/signals.test.ts +1 -5
- package/src/console/server/signals.ts +8 -6
- package/src/console/server/state.ts +6 -1
- package/src/console/server/store.ts +1 -1
- package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
- package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
- package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
- package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
- package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
- package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
- package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
- package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
- package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
- package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
- package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
- package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
- package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
- package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
- package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
- package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
- package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
- package/src/console/ui-next/dist/index.html +6 -13
- package/src/console/ui-next/seed-invoke-host.ts +21 -18
- package/src/console/ui-next/src/client.ts +7 -2
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
- package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
- package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
- package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
- package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
- package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
- package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
- package/src/docker/ai-model-status.test.ts +1 -12
- package/src/docker/ai-model-status.ts +10 -38
- package/src/docker/compose-up.test.ts +100 -0
- package/src/docker/compose-up.ts +292 -0
- package/src/docker/compose.ts +4 -14
- package/src/docker/derive.ts +9 -34
- package/src/docker/docker.test.ts +16 -261
- package/src/docker/images-config.test.ts +13 -25
- package/src/docker/index.ts +1 -24
- package/src/docker/recipes/index.ts +0 -23
- package/src/docker/recipes/pgdog.ts +6 -4
- package/src/docker/stack-id.test.ts +2 -2
- package/src/docker/stack-id.ts +0 -1
- package/src/docker/types.ts +2 -2
- package/src/drivers/ai-anthropic.ts +10 -0
- package/src/drivers/ai-openai-compatible.ts +34 -7
- package/src/drivers/ai-providers.test.ts +0 -107
- package/src/drivers/ai-stream.test.ts +0 -34
- package/src/drivers/ai-types.ts +17 -8
- package/src/drivers/channel-fcm.ts +6 -1
- package/src/drivers/channel-msegat.ts +6 -1
- package/src/drivers/channel-resend.ts +5 -1
- package/src/drivers/channel-smtp.ts +5 -1
- package/src/drivers/channel-sndr.ts +10 -1
- package/src/drivers/channel-taqnyat-mail.ts +5 -1
- package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
- package/src/drivers/channel-taqnyat.ts +6 -1
- package/src/drivers/channel-types.ts +10 -0
- package/src/drivers/channel-unifonic.ts +6 -1
- package/src/drivers/channel-wa-cloud.ts +6 -1
- package/src/drivers/channel-webpush.ts +6 -1
- package/src/drivers/external.ts +33 -0
- package/src/drivers/index.ts +0 -10
- package/src/drivers/meilisearch.ts +6 -0
- package/src/drivers/oauth-types.ts +4 -0
- package/src/drivers/postgres.test.ts +74 -3
- package/src/drivers/postgres.ts +175 -6
- package/src/drivers/signal-types.ts +5 -5
- package/src/drivers/types.ts +7 -0
- package/src/drivers/vault-types.ts +5 -0
- package/src/elements/ai/declare.ts +17 -4
- package/src/elements/ai/eval.ts +1 -1
- package/src/elements/ai/mcp-http.ts +1 -1
- package/src/elements/ai/pii.ts +1 -1
- package/src/elements/ai/providers.test.ts +289 -0
- package/src/elements/ai/providers.ts +169 -0
- package/src/elements/ai/runtime.ts +68 -5
- package/src/elements/ai/schema.ts +2 -2
- package/src/elements/ai.test.ts +7 -7
- package/src/elements/ai.ts +17 -1
- package/src/elements/channel/runtime.ts +105 -12
- package/src/elements/clock/cron-fields.test.ts +144 -0
- package/src/elements/clock/cron-fields.ts +185 -0
- package/src/elements/clock/declare.ts +247 -3
- package/src/elements/clock.ts +21 -1
- package/src/elements/index.ts +15 -0
- package/src/elements/signal/chaos-child.ts +1 -2
- package/src/elements/signal/declare.ts +65 -37
- package/src/elements/signal/delivery-modes.test.ts +11 -31
- package/src/elements/signal/dry-run-replay.test.ts +1 -7
- package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
- package/src/elements/signal/key-ordering.test.ts +6 -26
- package/src/elements/signal/lease-reclaim.test.ts +2 -6
- package/src/elements/signal/optional-emit.test.ts +6 -9
- package/src/elements/signal/order-lifecycle.test.ts +4 -17
- package/src/elements/signal/orphan-messages.test.ts +4 -15
- package/src/elements/signal/reconcile.test.ts +2 -2
- package/src/elements/signal/runtime.ts +1 -1
- package/src/elements/signal/schema-emit.test.ts +4 -8
- package/src/elements/signal.test.ts +24 -49
- package/src/elements/signal.ts +9 -2
- package/src/elements/store/domain-ddl.test.ts +1 -1
- package/src/elements/store/live-http.test.ts +9 -4
- package/src/elements/store/prepare-row.test.ts +123 -0
- package/src/elements/store/resource.ts +43 -12
- package/src/elements/store/schema-decl.ts +117 -0
- package/src/elements/store/search-backfill.ts +205 -0
- package/src/elements/store/search-bind.ts +66 -0
- package/src/elements/store/search-bm25.ts +78 -0
- package/src/elements/store/search-ddl.ts +156 -0
- package/src/elements/store/search-embed-flow.ts +141 -0
- package/src/elements/store/search-errors.ts +34 -0
- package/src/elements/store/search-fusion.ts +112 -0
- package/src/elements/store/search-lsh.ts +179 -0
- package/src/elements/store/search-runtime.ts +300 -0
- package/src/elements/store/search.test.ts +144 -0
- package/src/elements/store/sql-session.test.ts +1 -0
- package/src/elements/store/sql-session.ts +83 -3
- package/src/elements/store/table.ts +23 -2
- package/src/elements/store/upsert-app.test.ts +1 -3
- package/src/elements/store.ts +50 -0
- package/src/full.ts +4 -2
- package/src/http.ts +4 -1
- package/src/i18n/catalogs/ar.ts +14 -6
- package/src/i18n/catalogs/en.ts +14 -6
- package/src/index.ts +6 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
- package/src/kernel/adopt-routes.ts +9 -1
- package/src/kernel/app-auth.ts +5 -0
- package/src/kernel/app.ts +102 -9
- package/src/kernel/auto-cache.test.ts +6 -12
- package/src/kernel/auto-registry.test.ts +3 -3
- package/src/kernel/boot-bind/ai.test.ts +22 -19
- package/src/kernel/boot-bind/ai.ts +5 -43
- package/src/kernel/boot-bind/clock.ts +23 -5
- package/src/kernel/boot-bind/honor-config.test.ts +4 -4
- package/src/kernel/boot.test.ts +11 -6
- package/src/kernel/boot.ts +66 -16
- package/src/kernel/boundary-contract.ts +93 -0
- package/src/kernel/budget.test.ts +1 -1
- package/src/kernel/call.ts +89 -0
- package/src/kernel/capability.ts +6 -0
- package/src/kernel/client-descriptor.ts +123 -0
- package/src/kernel/clock-timezone.test.ts +80 -0
- package/src/kernel/correlation.test.ts +1 -1
- package/src/kernel/dry-run.ts +11 -8
- package/src/kernel/effects-stamping.test.ts +42 -9
- package/src/kernel/effects.test.ts +4 -2
- package/src/kernel/effects.ts +51 -9
- package/src/kernel/errors-channel.ts +17 -0
- package/src/kernel/errors-live-resume.ts +3 -2
- package/src/kernel/errors-tenant.ts +7 -4
- package/src/kernel/errors.registry-helpers.ts +96 -0
- package/src/kernel/errors.registry.test.ts +139 -25
- package/src/kernel/errors.ts +102 -23
- package/src/kernel/external-effects.test.ts +196 -0
- package/src/kernel/flow.test.ts +13 -4
- package/src/kernel/flow.ts +69 -71
- package/src/kernel/fx-ask-telemetry.test.ts +2 -2
- package/src/kernel/fx-dead-letters.test.ts +3 -3
- package/src/kernel/fx-fetch.ts +55 -0
- package/src/kernel/fx-live-stream.ts +2 -2
- package/src/kernel/fx-live.test.ts +8 -8
- package/src/kernel/fx.test.ts +17 -11
- package/src/kernel/fx.ts +180 -57
- package/src/kernel/horizontal-child.ts +1 -1
- package/src/kernel/http-query.test.ts +3 -6
- package/src/kernel/index.ts +13 -3
- package/src/kernel/instance-id.ts +1 -1
- package/src/kernel/instances.test.ts +40 -0
- package/src/kernel/instances.ts +47 -9
- package/src/kernel/live-http.test.ts +6 -7
- package/src/kernel/live-http.ts +6 -2
- package/src/kernel/live-resume.test.ts +1 -1
- package/src/kernel/mcp-tool.test.ts +3 -3
- package/src/kernel/on.ts +98 -4
- package/src/kernel/pipeline.test.ts +12 -15
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/stamp-http.test.ts +3 -3
- package/src/kernel/triggers.ts +210 -95
- package/src/kernel-entry.ts +0 -1
- package/src/manifest/diff.ts +11 -1
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/types.ts +46 -2
- package/src/mcp/docs-index.ts +3 -3
- package/src/mcp/docs-mcp.test.ts +2 -2
- package/src/mcp/docs-tools.ts +1 -1
- package/src/okid.test.ts +56 -0
- package/src/okid.ts +55 -11
- package/src/plugins/anonymous.ts +8 -4
- package/src/plugins/auth/shared.ts +27 -3
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
- package/src/plugins/auth-methods.security.test.ts +561 -23
- package/src/plugins/config-source.ts +1 -1
- package/src/plugins/csrf.test.ts +65 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/magic-link.ts +36 -16
- package/src/plugins/oauth/shared.ts +3 -1
- package/src/plugins/oauth.ts +22 -14
- package/src/plugins/otp.ts +37 -21
- package/src/plugins/passkey-webauthn.ts +9 -6
- package/src/plugins/passkey.ts +98 -26
- package/src/plugins/pre-account-hijack.test.ts +223 -0
- package/src/plugins/two-factor.ts +466 -37
- package/src/plugins/username.ts +19 -7
- package/src/release/build-lib.ts +1 -0
- package/src/release/limits.ts +2 -2
- package/src/release/measure.ts +1 -1
- package/src/runs/collect.test.ts +0 -2
- package/src/runtime/json-code-block.test.ts +250 -20
- package/src/runtime/json-code-block.ts +1261 -41
- package/src/term.test.ts +1 -1
- package/src/test/create-test-app.test.ts +12 -12
- package/src/test/live-signals.test.ts +6 -6
- package/src/test/provisions.integration.test.ts +10 -14
- package/src/test/reset-element-registries.ts +1 -1
- package/src/test/tenant-isolation.test.ts +12 -6
- package/site/content/docs/deployment/docker-swarm.mdx +0 -164
- package/site/content/docs/deployment/docker.mdx +0 -227
- package/site/content/docs/deployment/index.mdx +0 -83
- package/site/content/docs/deployment/kubernetes.mdx +0 -176
- package/site/content/docs/deployment/meta.json +0 -5
- package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
- package/site/content/docs/elements/ai.mdx +0 -385
- package/site/content/docs/elements/channel.mdx +0 -346
- package/site/content/docs/elements/clock.mdx +0 -244
- package/site/content/docs/elements/flow.mdx +0 -420
- package/site/content/docs/elements/gate.mdx +0 -436
- package/site/content/docs/elements/signal.mdx +0 -380
- package/site/content/docs/elements/store.mdx +0 -1099
- package/site/content/docs/elements/vault.mdx +0 -405
- package/site/content/docs/get-started/basic-usage.mdx +0 -173
- package/site/content/docs/get-started/index.mdx +0 -43
- package/site/content/docs/get-started/installation.mdx +0 -220
- package/site/content/docs/get-started/introduction.mdx +0 -132
- package/site/content/docs/get-started/meta.json +0 -13
- package/site/content/docs/get-started/project-structure.mdx +0 -925
- package/site/content/docs/get-started/testing.mdx +0 -328
- package/site/content/docs/get-started/why.mdx +0 -162
- package/site/content/docs/recipes/llama-cpp.mdx +0 -151
- package/site/content/docs/recipes/ollama.mdx +0 -142
- package/site/content/docs/recipes/sglang.mdx +0 -105
- package/site/content/docs/recipes/vllm.mdx +0 -106
- package/site/content/docs/reference/cli.md +0 -232
- package/site/content/docs/reference/client.mdx +0 -469
- package/site/content/docs/reference/security.md +0 -74
- package/src/cli/ai-setup/detect-ollama.ts +0 -213
- package/src/cli/ai-setup/recommend.test.ts +0 -225
- package/src/cli/ai-setup/recommend.ts +0 -225
- package/src/cli/dev-controls.test.ts +0 -122
- package/src/cli/dev-controls.ts +0 -164
- package/src/cli/tui/DevLive.tsx +0 -126
- package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
- package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
- package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
- package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
- package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
- package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
- package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
- package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
- package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
- package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
- package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
- package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
- package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
- package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
- package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
- package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
- package/src/docker/ollama-pull.ts +0 -232
- package/src/docker/recipes/llama-cpp.ts +0 -298
- package/src/docker/recipes/ollama.ts +0 -44
- package/src/docker/recipes/sglang.ts +0 -55
- package/src/docker/recipes/vllm.ts +0 -44
- package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
- package/src/drivers/ai-ollama.integration.test.ts +0 -184
- package/src/drivers/ai-ollama.ts +0 -389
- package/src/drivers/ollama.ts +0 -14
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G17 — Hybrid SQL search (BM25 / LSH / fusion) load + recall gate.
|
|
3
|
+
*
|
|
4
|
+
* Real Postgres only (`OKE_TEST_POSTGRES=1` + `DATABASE_URL`). PGlite is
|
|
5
|
+
* correctness-only — headline numbers come from a live instance.
|
|
6
|
+
*
|
|
7
|
+
* Run:
|
|
8
|
+
* OKE_BENCH=1 OKE_TEST_POSTGRES=1 DATABASE_URL=$DATABASE_URL \
|
|
9
|
+
* bun test src/bench/g17-hybrid-search.bench.ts --timeout 3600000
|
|
10
|
+
*
|
|
11
|
+
* OKE_BENCH_CAL=1 shrinks corpora to 1k·10k for a calibration pass.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import type { DeclaredColumn, Manifest } from "../manifest/types.ts";
|
|
17
|
+
import type { SqlConnection } from "../drivers/types.ts";
|
|
18
|
+
import { connectPostgres } from "../drivers/postgres.ts";
|
|
19
|
+
import { runSearchBackfill } from "../elements/store/search-backfill.ts";
|
|
20
|
+
import {
|
|
21
|
+
embColumn,
|
|
22
|
+
ensureHyperplaneInserts,
|
|
23
|
+
lshColumn,
|
|
24
|
+
OKE_SEARCH_PLANES,
|
|
25
|
+
OKE_TSV_COL,
|
|
26
|
+
searchDdlForTable,
|
|
27
|
+
} from "../elements/store/search-ddl.ts";
|
|
28
|
+
import { LSH_DEFAULT_K, RRF_DEFAULT_K } from "../elements/store/search-errors.ts";
|
|
29
|
+
import {
|
|
30
|
+
cosineSimilarity,
|
|
31
|
+
deserializePlanes,
|
|
32
|
+
lshBucket,
|
|
33
|
+
lshBucketToSql,
|
|
34
|
+
neighborBuckets,
|
|
35
|
+
} from "../elements/store/search-lsh.ts";
|
|
36
|
+
import { runSqlSearch, type SearchColumnMeta } from "../elements/store/search-runtime.ts";
|
|
37
|
+
import { resolveLivePg } from "./lib/infra.ts";
|
|
38
|
+
import { DISCLAIMER, HARDWARE, percentile, writeArtifact } from "./lib/report.ts";
|
|
39
|
+
|
|
40
|
+
const ENABLED = process.env.OKE_BENCH === "1";
|
|
41
|
+
const CAL = process.env.OKE_BENCH_CAL === "1";
|
|
42
|
+
const LIVE_URL = resolveLivePg();
|
|
43
|
+
|
|
44
|
+
/** Corpus sizes — full gate requires 1M. */
|
|
45
|
+
const CORPUS_SIZES = CAL ? [1_000, 10_000] : [1_000, 10_000, 100_000, 1_000_000];
|
|
46
|
+
/** Synthetic embedding dims (deterministic hash bag — not a production model). */
|
|
47
|
+
const DIMS = 32;
|
|
48
|
+
/** Latency samples per (size × mode). */
|
|
49
|
+
const LATENCY_ITERS = CAL ? 8 : 20;
|
|
50
|
+
/** Recall probes per size (vector / hybrid). */
|
|
51
|
+
const RECALL_QUERIES = CAL ? 5 : 12;
|
|
52
|
+
/** Exact baseline sample cap for 1M (full scan still runs; JS vector cache). */
|
|
53
|
+
const TABLE = "g17_docs";
|
|
54
|
+
const BACKFILL_TABLE = "g17_backfill";
|
|
55
|
+
/** Backfill interrupt after this many embedded column writes. */
|
|
56
|
+
const BACKFILL_KILL_AFTER = CAL ? 200 : 2_000;
|
|
57
|
+
const BACKFILL_ROWS = CAL ? 5_000 : 50_000;
|
|
58
|
+
|
|
59
|
+
const TOPICS = [
|
|
60
|
+
"refund policy shipping delay",
|
|
61
|
+
"password reset two factor auth",
|
|
62
|
+
"invoice payment stripe webhook",
|
|
63
|
+
"search ranking bm25 hybrid",
|
|
64
|
+
"postgres gin btree index plan",
|
|
65
|
+
"tenant isolation row level security",
|
|
66
|
+
"durable flow journal resume",
|
|
67
|
+
"live query fanout subscriber",
|
|
68
|
+
] as const;
|
|
69
|
+
|
|
70
|
+
interface LatencyPoint {
|
|
71
|
+
readonly n: number;
|
|
72
|
+
readonly mode: "text" | "vector" | "hybrid";
|
|
73
|
+
readonly p50Ms: number;
|
|
74
|
+
readonly p99Ms: number;
|
|
75
|
+
readonly opsPerSec: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface RecallPoint {
|
|
79
|
+
readonly n: number;
|
|
80
|
+
readonly mode: "vector" | "hybrid";
|
|
81
|
+
readonly precisionAt10: number;
|
|
82
|
+
readonly exactMsP50: number;
|
|
83
|
+
readonly approxMsP50: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let conn: SqlConnection;
|
|
87
|
+
const issues: string[] = [];
|
|
88
|
+
|
|
89
|
+
beforeAll(async () => {
|
|
90
|
+
if (!ENABLED) return;
|
|
91
|
+
if (!LIVE_URL) {
|
|
92
|
+
throw new Error("G17 requires OKE_TEST_POSTGRES=1 + DATABASE_URL (or OKE_TEST_POSTGRES_URL)");
|
|
93
|
+
}
|
|
94
|
+
conn = await connectPostgres({ url: LIVE_URL, pool: { max: 1 } });
|
|
95
|
+
}, 30_000);
|
|
96
|
+
|
|
97
|
+
afterAll(async () => {
|
|
98
|
+
if (conn) await conn.close();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Deterministic synthetic embedding — same text → same vector.
|
|
103
|
+
*
|
|
104
|
+
* @param text - Source text
|
|
105
|
+
* @param dims - Dimensionality
|
|
106
|
+
*/
|
|
107
|
+
function synthEmbed(text: string, dims: number = DIMS): number[] {
|
|
108
|
+
const v = new Array<number>(dims).fill(0);
|
|
109
|
+
const tokens = text.toLowerCase().split(/\W+/).filter(Boolean);
|
|
110
|
+
for (const t of tokens) {
|
|
111
|
+
const h = createHash("sha256").update(t).digest();
|
|
112
|
+
for (let i = 0; i < dims; i++) {
|
|
113
|
+
const byte = h[i % h.length]!;
|
|
114
|
+
v[i]! += (byte / 255) * 2 - 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// L2 normalize
|
|
118
|
+
let norm = 0;
|
|
119
|
+
for (const x of v) norm += x * x;
|
|
120
|
+
norm = Math.sqrt(norm) || 1;
|
|
121
|
+
return v.map((x) => x / norm);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Minimal Manifest slice for {@link runSearchBackfill}.
|
|
126
|
+
*
|
|
127
|
+
* @param table - Table name
|
|
128
|
+
* @param withEmbed - Include `.embed()` dims
|
|
129
|
+
*/
|
|
130
|
+
function benchManifest(table: string, withEmbed: boolean): Manifest {
|
|
131
|
+
const body: DeclaredColumn = {
|
|
132
|
+
type: "text",
|
|
133
|
+
sqlName: "body",
|
|
134
|
+
searchable: { weight: 1 },
|
|
135
|
+
...(withEmbed ? { embed: { dims: DIMS, model: "mock" } } : {}),
|
|
136
|
+
};
|
|
137
|
+
const title: DeclaredColumn = {
|
|
138
|
+
type: "text",
|
|
139
|
+
sqlName: "title",
|
|
140
|
+
searchable: { weight: 2 },
|
|
141
|
+
};
|
|
142
|
+
const id: DeclaredColumn = { type: "text", sqlName: "id", primaryKey: true };
|
|
143
|
+
return {
|
|
144
|
+
version: 1,
|
|
145
|
+
stores: {
|
|
146
|
+
app: {
|
|
147
|
+
facet: "sql",
|
|
148
|
+
tables: {
|
|
149
|
+
[table]: {
|
|
150
|
+
columns: { id, title, body },
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
} as unknown as Manifest;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const SEARCH_COLS: SearchColumnMeta[] = [
|
|
159
|
+
{ key: "title", sqlName: "title", weight: 2 },
|
|
160
|
+
{ key: "body", sqlName: "body", weight: 1, embed: { dims: DIMS, model: "mock" } },
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Drop + create table with search DDL + hyperplanes.
|
|
165
|
+
*
|
|
166
|
+
* @param table - Table name
|
|
167
|
+
* @param withEmbed - Embed shadow columns
|
|
168
|
+
*/
|
|
169
|
+
async function prepareTable(table: string, withEmbed: boolean): Promise<void> {
|
|
170
|
+
await conn.exec(`DROP TABLE IF EXISTS ${table} CASCADE`);
|
|
171
|
+
await conn.exec(`
|
|
172
|
+
CREATE TABLE ${table} (
|
|
173
|
+
id text PRIMARY KEY,
|
|
174
|
+
title text NOT NULL,
|
|
175
|
+
body text NOT NULL
|
|
176
|
+
)
|
|
177
|
+
`);
|
|
178
|
+
const cols: Record<string, DeclaredColumn> = {
|
|
179
|
+
id: { type: "text", sqlName: "id", primaryKey: true },
|
|
180
|
+
title: { type: "text", sqlName: "title", searchable: { weight: 2 } },
|
|
181
|
+
body: {
|
|
182
|
+
type: "text",
|
|
183
|
+
sqlName: "body",
|
|
184
|
+
searchable: { weight: 1 },
|
|
185
|
+
...(withEmbed ? { embed: { dims: DIMS, model: "mock" } } : {}),
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
for (const stmt of searchDdlForTable(table, cols)) {
|
|
189
|
+
await conn.exec(stmt);
|
|
190
|
+
}
|
|
191
|
+
if (withEmbed) {
|
|
192
|
+
for (const ins of ensureHyperplaneInserts(table, cols)) {
|
|
193
|
+
await conn.exec(ins.sql, ins.params);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Bulk-load N docs with precomputed embeddings + LSH buckets + DF/stats.
|
|
200
|
+
*
|
|
201
|
+
* @param n - Row count
|
|
202
|
+
*/
|
|
203
|
+
async function seedCorpus(n: number): Promise<{
|
|
204
|
+
readonly vectors: Float32Array[];
|
|
205
|
+
readonly ids: string[];
|
|
206
|
+
readonly titles: string[];
|
|
207
|
+
readonly bodies: string[];
|
|
208
|
+
}> {
|
|
209
|
+
await prepareTable(TABLE, true);
|
|
210
|
+
const planesRow = await conn.query(
|
|
211
|
+
`SELECT k, planes FROM ${OKE_SEARCH_PLANES} WHERE table_name = ? AND column_name = ?`,
|
|
212
|
+
[TABLE, "body"],
|
|
213
|
+
);
|
|
214
|
+
const prow = planesRow[0]!;
|
|
215
|
+
const k = Number(prow["k"] ?? LSH_DEFAULT_K);
|
|
216
|
+
const planes = deserializePlanes(Buffer.from(prow["planes"] as Buffer), k);
|
|
217
|
+
|
|
218
|
+
const vectors: Float32Array[] = new Array(n);
|
|
219
|
+
const ids: string[] = new Array(n);
|
|
220
|
+
const titles: string[] = new Array(n);
|
|
221
|
+
const bodies: string[] = new Array(n);
|
|
222
|
+
const batch = 500;
|
|
223
|
+
const t0 = performance.now();
|
|
224
|
+
|
|
225
|
+
for (let start = 0; start < n; start += batch) {
|
|
226
|
+
const end = Math.min(n, start + batch);
|
|
227
|
+
const values: unknown[] = [];
|
|
228
|
+
const placeholders: string[] = [];
|
|
229
|
+
for (let i = start; i < end; i++) {
|
|
230
|
+
const topic = TOPICS[i % TOPICS.length]!;
|
|
231
|
+
const id = `d${String(i).padStart(8, "0")}`;
|
|
232
|
+
const title = `${topic} item ${i}`;
|
|
233
|
+
const body = `${topic}. Document ${i} discusses ${topic} with enough tokens for BM25.`;
|
|
234
|
+
const vec = synthEmbed(body, DIMS);
|
|
235
|
+
const bucket = lshBucket(vec, planes);
|
|
236
|
+
ids[i] = id;
|
|
237
|
+
titles[i] = title;
|
|
238
|
+
bodies[i] = body;
|
|
239
|
+
vectors[i] = Float32Array.from(vec);
|
|
240
|
+
placeholders.push(`(?, ?, ?, ?, ?)`);
|
|
241
|
+
values.push(id, title, body, `{${vec.join(",")}}`, lshBucketToSql(bucket));
|
|
242
|
+
}
|
|
243
|
+
await conn.exec(
|
|
244
|
+
`INSERT INTO ${TABLE} (id, title, body, ${embColumn("body")}, ${lshColumn("body")})
|
|
245
|
+
VALUES ${placeholders.join(",")}`,
|
|
246
|
+
values,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Rebuild corpus stats via backfill path (embed already present — skip re-embed).
|
|
251
|
+
const result = await runSearchBackfill(conn, benchManifest(TABLE, true), {
|
|
252
|
+
table: TABLE,
|
|
253
|
+
batchSize: 1_000,
|
|
254
|
+
embedPauseMs: 0,
|
|
255
|
+
// No embed fn → stats/DF only; embeddings already bulk-loaded.
|
|
256
|
+
});
|
|
257
|
+
expect(result.rows).toBe(n);
|
|
258
|
+
const loadMs = performance.now() - t0;
|
|
259
|
+
console.log(`G17 seeded n=${n} in ${(loadMs / 1000).toFixed(1)}s`);
|
|
260
|
+
|
|
261
|
+
return { vectors, ids, titles, bodies };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Exact top-K by cosine against in-memory vectors.
|
|
266
|
+
*
|
|
267
|
+
* @param query - Query vector
|
|
268
|
+
* @param vectors - Corpus
|
|
269
|
+
* @param k - Top-k
|
|
270
|
+
*/
|
|
271
|
+
function exactTopK(
|
|
272
|
+
query: readonly number[],
|
|
273
|
+
vectors: readonly Float32Array[],
|
|
274
|
+
k: number,
|
|
275
|
+
): string[] {
|
|
276
|
+
const scored: Array<{ i: number; s: number }> = [];
|
|
277
|
+
for (let i = 0; i < vectors.length; i++) {
|
|
278
|
+
scored.push({ i, s: cosineSimilarity(query, Array.from(vectors[i]!)) });
|
|
279
|
+
}
|
|
280
|
+
scored.sort((a, b) => b.s - a.s);
|
|
281
|
+
return scored.slice(0, k).map((x) => `d${String(x.i).padStart(8, "0")}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Precision@K — fraction of approx top-K that appear in exact top-K.
|
|
286
|
+
*
|
|
287
|
+
* @param exact - Exact ids
|
|
288
|
+
* @param approx - Approx ids
|
|
289
|
+
* @param k - K
|
|
290
|
+
*/
|
|
291
|
+
function precisionAtK(exact: readonly string[], approx: readonly string[], k: number): number {
|
|
292
|
+
const set = new Set(exact.slice(0, k));
|
|
293
|
+
let hit = 0;
|
|
294
|
+
for (const id of approx.slice(0, k)) {
|
|
295
|
+
if (set.has(id)) hit += 1;
|
|
296
|
+
}
|
|
297
|
+
return hit / k;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
describe.skipIf(!ENABLED)("G17 hybrid search", () => {
|
|
301
|
+
test(
|
|
302
|
+
"corpus × mode latency, LSH recall vs exact cosine, EXPLAIN, backfill kill/resume",
|
|
303
|
+
async () => {
|
|
304
|
+
expect(LIVE_URL).toBeTruthy();
|
|
305
|
+
expect(RRF_DEFAULT_K).toBe(60);
|
|
306
|
+
|
|
307
|
+
const latency: LatencyPoint[] = [];
|
|
308
|
+
const recall: RecallPoint[] = [];
|
|
309
|
+
let explainText = "";
|
|
310
|
+
let backfill: Record<string, number | boolean | string> = {};
|
|
311
|
+
|
|
312
|
+
// --- Backfill timing + kill/resume on a pre-populated table ---
|
|
313
|
+
{
|
|
314
|
+
await prepareTable(BACKFILL_TABLE, true);
|
|
315
|
+
const bfBatch = 100;
|
|
316
|
+
for (let start = 0; start < BACKFILL_ROWS; start += bfBatch) {
|
|
317
|
+
const end = Math.min(BACKFILL_ROWS, start + bfBatch);
|
|
318
|
+
const values: unknown[] = [];
|
|
319
|
+
const ph: string[] = [];
|
|
320
|
+
for (let i = start; i < end; i++) {
|
|
321
|
+
const topic = TOPICS[i % TOPICS.length]!;
|
|
322
|
+
ph.push(`(?, ?, ?)`);
|
|
323
|
+
values.push(`b${i}`, `${topic} ${i}`, `${topic} body ${i}`);
|
|
324
|
+
}
|
|
325
|
+
await conn.exec(
|
|
326
|
+
`INSERT INTO ${BACKFILL_TABLE} (id, title, body) VALUES ${ph.join(",")}`,
|
|
327
|
+
values,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let embeddedBeforeKill = 0;
|
|
332
|
+
const killCtrl = new AbortController();
|
|
333
|
+
const tKill0 = performance.now();
|
|
334
|
+
try {
|
|
335
|
+
await runSearchBackfill(conn, benchManifest(BACKFILL_TABLE, true), {
|
|
336
|
+
table: BACKFILL_TABLE,
|
|
337
|
+
batchSize: 50,
|
|
338
|
+
embedPauseMs: 0,
|
|
339
|
+
signal: killCtrl.signal,
|
|
340
|
+
embed: async (_model, text, dims) => {
|
|
341
|
+
embeddedBeforeKill += 1;
|
|
342
|
+
if (embeddedBeforeKill >= BACKFILL_KILL_AFTER) killCtrl.abort();
|
|
343
|
+
return synthEmbed(text, dims);
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
issues.push("backfill kill did not interrupt before completion");
|
|
347
|
+
} catch (err) {
|
|
348
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
349
|
+
if (!/abort/i.test(msg) && killCtrl.signal.aborted !== true) {
|
|
350
|
+
throw err instanceof Error ? err : new Error(msg);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const killMs = performance.now() - tKill0;
|
|
354
|
+
const partial = await conn.query(
|
|
355
|
+
`SELECT count(*)::int AS c FROM ${BACKFILL_TABLE} WHERE ${embColumn("body")} IS NOT NULL`,
|
|
356
|
+
);
|
|
357
|
+
const partialCount = Number(partial[0]?.["c"] ?? 0);
|
|
358
|
+
if (partialCount === 0) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
`backfill kill left 0 embeddings (killAfter=${BACKFILL_KILL_AFTER}); see prior catch issues: ${issues.join("; ")}`,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
expect(partialCount).toBeGreaterThan(0);
|
|
364
|
+
expect(partialCount).toBeLessThan(BACKFILL_ROWS);
|
|
365
|
+
|
|
366
|
+
const tResume0 = performance.now();
|
|
367
|
+
const resumed = await runSearchBackfill(conn, benchManifest(BACKFILL_TABLE, true), {
|
|
368
|
+
table: BACKFILL_TABLE,
|
|
369
|
+
batchSize: 200,
|
|
370
|
+
embedPauseMs: 0,
|
|
371
|
+
embed: async (_model, text, dims) => synthEmbed(text, dims),
|
|
372
|
+
});
|
|
373
|
+
const resumeMs = performance.now() - tResume0;
|
|
374
|
+
expect(resumed.rows).toBe(BACKFILL_ROWS);
|
|
375
|
+
const full = await conn.query(
|
|
376
|
+
`SELECT count(*)::int AS c FROM ${BACKFILL_TABLE} WHERE ${embColumn("body")} IS NOT NULL`,
|
|
377
|
+
);
|
|
378
|
+
expect(Number(full[0]?.["c"] ?? 0)).toBe(BACKFILL_ROWS);
|
|
379
|
+
|
|
380
|
+
backfill = {
|
|
381
|
+
rows: BACKFILL_ROWS,
|
|
382
|
+
killAfterEmbeds: BACKFILL_KILL_AFTER,
|
|
383
|
+
embedsAtKill: partialCount,
|
|
384
|
+
killWallMs: Number(killMs.toFixed(1)),
|
|
385
|
+
resumeWallMs: Number(resumeMs.toFixed(1)),
|
|
386
|
+
resumeEmbedded: resumed.embedded,
|
|
387
|
+
resumable: true,
|
|
388
|
+
};
|
|
389
|
+
console.log("G17 backfill kill/resume", backfill);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// --- Per-corpus latency + recall ---
|
|
393
|
+
for (const n of CORPUS_SIZES) {
|
|
394
|
+
const corpus = await seedCorpus(n);
|
|
395
|
+
|
|
396
|
+
// EXPLAIN once at 100k (or largest CAL size) — real plan artifact.
|
|
397
|
+
if ((CAL && n === CORPUS_SIZES[CORPUS_SIZES.length - 1]) || n === 100_000) {
|
|
398
|
+
const qVec = synthEmbed(TOPICS[0]!, DIMS);
|
|
399
|
+
const planesRow = await conn.query(
|
|
400
|
+
`SELECT k, planes FROM ${OKE_SEARCH_PLANES} WHERE table_name = ? AND column_name = ?`,
|
|
401
|
+
[TABLE, "body"],
|
|
402
|
+
);
|
|
403
|
+
const prow = planesRow[0]!;
|
|
404
|
+
const kPlanes = Number(prow["k"] ?? LSH_DEFAULT_K);
|
|
405
|
+
const planes = deserializePlanes(Buffer.from(prow["planes"] as Buffer), kPlanes);
|
|
406
|
+
const bucket = lshBucket(qVec, planes);
|
|
407
|
+
const buckets = neighborBuckets(bucket, kPlanes).map((b) => lshBucketToSql(b));
|
|
408
|
+
const explainSql = `EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
|
|
409
|
+
SELECT id FROM ${TABLE}
|
|
410
|
+
WHERE ${OKE_TSV_COL} @@ plainto_tsquery('english', $1)
|
|
411
|
+
OR ${lshColumn("body")} = ANY($2::bigint[])
|
|
412
|
+
LIMIT 50`;
|
|
413
|
+
// Bun.SQL uses ? placeholders — fall back to interpolated literals for EXPLAIN only.
|
|
414
|
+
const safeQ = TOPICS[0]!.replaceAll("'", "''");
|
|
415
|
+
const bucketList = buckets.join(",");
|
|
416
|
+
const explainRows = await conn.query(
|
|
417
|
+
`EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
|
|
418
|
+
SELECT id FROM ${TABLE}
|
|
419
|
+
WHERE ${OKE_TSV_COL} @@ plainto_tsquery('english', '${safeQ}')
|
|
420
|
+
OR ${lshColumn("body")} = ANY(ARRAY[${bucketList}]::bigint[])
|
|
421
|
+
LIMIT 50`,
|
|
422
|
+
);
|
|
423
|
+
explainText = explainRows.map((r) => String(Object.values(r)[0] ?? "")).join("\n");
|
|
424
|
+
void explainSql;
|
|
425
|
+
console.log("G17 EXPLAIN (ANALYZE, BUFFERS):\n" + explainText);
|
|
426
|
+
if (!/Bitmap|Index|Seq Scan|BitmapOr|Bitmap Heap/i.test(explainText)) {
|
|
427
|
+
issues.push("EXPLAIN text lacked recognizable Postgres plan nodes");
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const modes = [
|
|
432
|
+
{
|
|
433
|
+
mode: "text" as const,
|
|
434
|
+
query: TOPICS[0]!,
|
|
435
|
+
embedQuery: undefined as undefined | ((text: string) => Promise<readonly number[]>),
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
mode: "vector" as const,
|
|
439
|
+
// Lexically rare token so GIN contributes little; LSH buckets carry the load.
|
|
440
|
+
query: "zxqwv semantic neighbor probe",
|
|
441
|
+
embedQuery: async (text: string) =>
|
|
442
|
+
synthEmbed(text.includes("zxqwv") ? TOPICS[3]! : text, DIMS),
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
mode: "hybrid" as const,
|
|
446
|
+
query: TOPICS[3]!,
|
|
447
|
+
embedQuery: async (text: string) => synthEmbed(text, DIMS),
|
|
448
|
+
},
|
|
449
|
+
];
|
|
450
|
+
|
|
451
|
+
for (const m of modes) {
|
|
452
|
+
const samples: number[] = [];
|
|
453
|
+
for (let i = 0; i < LATENCY_ITERS; i++) {
|
|
454
|
+
const t0 = performance.now();
|
|
455
|
+
await runSqlSearch({
|
|
456
|
+
conn,
|
|
457
|
+
table: TABLE,
|
|
458
|
+
columns: SEARCH_COLS,
|
|
459
|
+
pkSqlName: "id",
|
|
460
|
+
options: { query: m.query, limit: 10 },
|
|
461
|
+
...(m.embedQuery ? { embedQuery: m.embedQuery } : {}),
|
|
462
|
+
});
|
|
463
|
+
samples.push(performance.now() - t0);
|
|
464
|
+
}
|
|
465
|
+
const p50 = percentile(samples, 50);
|
|
466
|
+
const p99 = percentile(samples, 99);
|
|
467
|
+
const total = samples.reduce((a, b) => a + b, 0);
|
|
468
|
+
latency.push({
|
|
469
|
+
n,
|
|
470
|
+
mode: m.mode,
|
|
471
|
+
p50Ms: Number(p50.toFixed(2)),
|
|
472
|
+
p99Ms: Number(p99.toFixed(2)),
|
|
473
|
+
opsPerSec: Number(((samples.length / total) * 1000).toFixed(1)),
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Recall: LSH approximate path vs exact brute-force cosine on SAME vectors.
|
|
478
|
+
for (const mode of ["vector", "hybrid"] as const) {
|
|
479
|
+
const scores: number[] = [];
|
|
480
|
+
const exactMs: number[] = [];
|
|
481
|
+
const approxMs: number[] = [];
|
|
482
|
+
for (let q = 0; q < RECALL_QUERIES; q++) {
|
|
483
|
+
const topic = TOPICS[q % TOPICS.length]!;
|
|
484
|
+
const qVec = synthEmbed(topic, DIMS);
|
|
485
|
+
const te0 = performance.now();
|
|
486
|
+
const exact = exactTopK(qVec, corpus.vectors, 10);
|
|
487
|
+
exactMs.push(performance.now() - te0);
|
|
488
|
+
|
|
489
|
+
const ta0 = performance.now();
|
|
490
|
+
const result = await runSqlSearch({
|
|
491
|
+
conn,
|
|
492
|
+
table: TABLE,
|
|
493
|
+
columns: SEARCH_COLS,
|
|
494
|
+
pkSqlName: "id",
|
|
495
|
+
options: {
|
|
496
|
+
query: mode === "vector" ? `zxqwv ${topic}` : topic,
|
|
497
|
+
limit: 10,
|
|
498
|
+
},
|
|
499
|
+
embedQuery: async () => qVec,
|
|
500
|
+
});
|
|
501
|
+
approxMs.push(performance.now() - ta0);
|
|
502
|
+
const approxIds = result.data.map((r) => String(r["id"]));
|
|
503
|
+
scores.push(precisionAtK(exact, approxIds, 10));
|
|
504
|
+
}
|
|
505
|
+
const pAt10 = scores.reduce((a, b) => a + b, 0) / scores.length;
|
|
506
|
+
recall.push({
|
|
507
|
+
n,
|
|
508
|
+
mode,
|
|
509
|
+
precisionAt10: Number(pAt10.toFixed(3)),
|
|
510
|
+
exactMsP50: Number(percentile(exactMs, 50).toFixed(2)),
|
|
511
|
+
approxMsP50: Number(percentile(approxMs, 50).toFixed(2)),
|
|
512
|
+
});
|
|
513
|
+
if (pAt10 < 0.3) {
|
|
514
|
+
issues.push(
|
|
515
|
+
`honest low recall: n=${n} mode=${mode} precision@10=${pAt10.toFixed(3)} (LSH approx vs exact cosine)`,
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const metrics: Record<string, number> = {
|
|
522
|
+
rrfDefaultK: RRF_DEFAULT_K,
|
|
523
|
+
dims: DIMS,
|
|
524
|
+
backfillRows: Number(backfill.rows),
|
|
525
|
+
backfillResumeMs: Number(backfill.resumeWallMs),
|
|
526
|
+
backfillEmbedsAtKill: Number(backfill.embedsAtKill),
|
|
527
|
+
};
|
|
528
|
+
for (const p of latency) {
|
|
529
|
+
metrics[`n${p.n}_${p.mode}_p50Ms`] = p.p50Ms;
|
|
530
|
+
metrics[`n${p.n}_${p.mode}_p99Ms`] = p.p99Ms;
|
|
531
|
+
metrics[`n${p.n}_${p.mode}_opsPerSec`] = p.opsPerSec;
|
|
532
|
+
}
|
|
533
|
+
for (const r of recall) {
|
|
534
|
+
metrics[`n${r.n}_${r.mode}_precisionAt10`] = r.precisionAt10;
|
|
535
|
+
metrics[`n${r.n}_${r.mode}_exactMsP50`] = r.exactMsP50;
|
|
536
|
+
metrics[`n${r.n}_${r.mode}_approxMsP50`] = r.approxMsP50;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const path = await writeArtifact({
|
|
540
|
+
group: "g17-hybrid-search",
|
|
541
|
+
hardware: HARDWARE,
|
|
542
|
+
disclaimer: DISCLAIMER,
|
|
543
|
+
command:
|
|
544
|
+
"OKE_BENCH=1 OKE_TEST_POSTGRES=1 DATABASE_URL=$DATABASE_URL bun test src/bench/g17-hybrid-search.bench.ts --timeout 3600000",
|
|
545
|
+
metrics,
|
|
546
|
+
issues,
|
|
547
|
+
fixes: [],
|
|
548
|
+
remeasured: null,
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// Persist EXPLAIN + tables beside the JSON for REPORT.md authorship.
|
|
552
|
+
const reportSide = new URL("./results/", import.meta.url).pathname;
|
|
553
|
+
await Bun.write(
|
|
554
|
+
`${reportSide}g17-explain-${Date.now()}.txt`,
|
|
555
|
+
explainText || "(no EXPLAIN captured)\n",
|
|
556
|
+
);
|
|
557
|
+
await Bun.write(
|
|
558
|
+
`${reportSide}g17-summary-${Date.now()}.json`,
|
|
559
|
+
`${JSON.stringify({ latency, recall, backfill, explainText, issues }, null, 2)}\n`,
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
console.log(`G17 artifact → ${path}`);
|
|
563
|
+
console.table(latency);
|
|
564
|
+
console.table(recall);
|
|
565
|
+
expect(latency.length).toBe(CORPUS_SIZES.length * 3);
|
|
566
|
+
expect(recall.length).toBe(CORPUS_SIZES.length * 2);
|
|
567
|
+
expect(String(backfill.resumable)).toBe("true");
|
|
568
|
+
expect(explainText.length).toBeGreaterThan(20);
|
|
569
|
+
},
|
|
570
|
+
CAL ? 600_000 : 3_600_000,
|
|
571
|
+
);
|
|
572
|
+
});
|
package/src/bench/load-app.ts
CHANGED
|
@@ -81,16 +81,8 @@ clockRt.onCron(CRON, async () => {});
|
|
|
81
81
|
|
|
82
82
|
// --- Signal (once + live physics) -------------------------------------------
|
|
83
83
|
const signalPath = `/tmp/oke-bench-signal-${process.pid}.json`;
|
|
84
|
-
const BENCH_JOB = signal("bench-job", {
|
|
85
|
-
|
|
86
|
-
retries: 3,
|
|
87
|
-
deadLetter: true,
|
|
88
|
-
optional: true,
|
|
89
|
-
});
|
|
90
|
-
const BENCH_LIVE = signal("bench-live", {
|
|
91
|
-
delivery: "live",
|
|
92
|
-
retention: { maxCount: 100 },
|
|
93
|
-
});
|
|
84
|
+
const BENCH_JOB = signal.once("bench-job", { retries: 3, deadLetter: true, optional: true });
|
|
85
|
+
const BENCH_LIVE = signal.live("bench-live", { retention: { maxCount: 100 } });
|
|
94
86
|
const signalRt = createSignalRuntime({
|
|
95
87
|
driver: memorySignalDriver,
|
|
96
88
|
durablePath: signalPath,
|