okengine 0.21.1 → 0.23.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 +11 -4
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +15 -9
- package/manifest.v1.schema.json +54 -0
- package/package.json +53 -38
- package/site/content/docs/ai/mcp.mdx +13 -9
- package/site/content/docs/ai/skills.mdx +2 -2
- package/site/content/docs/client/calling.mdx +27 -19
- package/site/content/docs/client/index.mdx +1 -1
- package/site/content/docs/elements/ai/agents.mdx +61 -13
- package/site/content/docs/elements/ai/decide.mdx +536 -0
- package/site/content/docs/elements/ai/events.mdx +373 -0
- package/site/content/docs/elements/ai/index.mdx +9 -3
- package/site/content/docs/elements/ai/meta.json +1 -1
- package/site/content/docs/elements/ai/prompts.mdx +4 -2
- package/site/content/docs/elements/channel/email.mdx +1 -1
- package/site/content/docs/elements/channel/index.mdx +1 -1
- package/site/content/docs/elements/flow/http.mdx +4 -4
- package/site/content/docs/elements/flow/index.mdx +3 -3
- package/site/content/docs/elements/flow/meta.json +1 -1
- package/site/content/docs/elements/gate/auth.mdx +4 -1
- package/site/content/docs/elements/gate/tenancy.mdx +1 -1
- package/site/content/docs/elements/store/index.mdx +1 -1
- package/site/content/docs/elements/store/kv.mdx +5 -5
- package/site/content/docs/elements/vault/rotation.mdx +15 -0
- package/site/content/docs/plugins/otp.mdx +31 -31
- package/site/content/docs/recipes/dragonfly.mdx +5 -5
- package/site/content/docs/recipes/index.mdx +6 -6
- package/site/content/docs/recipes/mailpit.mdx +1 -1
- package/site/content/docs/recipes/meilisearch.mdx +1 -1
- package/site/content/docs/recipes/pgdog.mdx +1 -1
- package/site/content/docs/recipes/postgres.mdx +12 -8
- package/site/content/docs/recipes/redis.mdx +4 -4
- package/site/content/docs/reference/cli.mdx +2 -1
- package/site/content/docs/reference/configuration.mdx +11 -9
- package/site/content/docs/reference/errors.mdx +12 -2
- package/site/content/docs/reference/fx.mdx +35 -21
- package/site/content/docs/reference/idempotency.mdx +192 -0
- package/site/content/docs/reference/index.mdx +6 -1
- package/site/content/docs/reference/meta.json +2 -1
- package/site/content/docs/understand/meta.json +1 -1
- package/site/content/docs/{elements/flow → understand}/routing.mdx +11 -10
- package/site/content/docs/understand/the-architecture.mdx +14 -12
- package/site/content/docs/understand/try-it.mdx +3 -3
- package/src/auth/api-key-sql.test.ts +44 -0
- package/src/auth/api-key-sql.ts +1 -0
- package/src/auth/api-keys.ts +30 -12
- package/src/auth/config.ts +6 -2
- package/src/auth/gate-auth.test.ts +20 -0
- package/src/cli/agents-md.test.ts +25 -0
- package/src/cli/ask-vault-gaps.test.ts +38 -1
- package/src/cli/ask-vault-gaps.ts +35 -2
- package/src/cli/competitor-mention-removal.test.ts +3 -3
- package/src/cli/decide.ts +174 -0
- package/src/cli/decision-lock-watch.test.ts +45 -0
- package/src/cli/decision-lock-watch.ts +51 -0
- package/src/cli/dev-app-runner.ts +34 -8
- package/src/cli/dev-auth-secret.test.ts +49 -0
- package/src/cli/dev-auth-secret.ts +49 -0
- package/src/cli/dev-hot.test.ts +41 -0
- package/src/cli/dev-hot.ts +48 -0
- package/src/cli/dev.test.ts +1 -1
- package/src/cli/dev.ts +24 -6
- package/src/cli/docker-cli.test.ts +1 -1
- package/src/cli/eval.ts +142 -1
- package/src/cli/index.ts +14 -1
- package/src/cli/load-config.images.test.ts +12 -12
- package/src/cli/load-config.ts +2 -2
- package/src/cli/mcp-from-console.ts +48 -0
- package/src/cli/registry.ts +35 -1
- package/src/cli/vault-cmd.test.ts +23 -0
- package/src/cli/vault-cmd.ts +7 -0
- package/src/client/agent.ts +307 -0
- package/src/client/create.ts +18 -1
- package/src/client/transport.test.ts +245 -0
- package/src/client/transport.ts +133 -19
- package/src/client/types.ts +29 -3
- package/src/client-react/index.ts +7 -1
- package/src/client-react/use-agent-run.test.ts +271 -0
- package/src/client-react/use-agent-run.ts +193 -0
- package/src/compiler/ai-approval.test.ts +36 -0
- package/src/compiler/ai-repair.test.ts +22 -0
- package/src/compiler/decisions.extract.test.ts +164 -0
- package/src/compiler/effects-infer.ts +753 -17
- package/src/compiler/extract.test.ts +37 -0
- package/src/compiler/extract.ts +381 -24
- package/src/compiler/fixtures/skyport.expected.json +24 -0
- package/src/compiler/fx-follow.test.ts +164 -0
- package/src/compiler/fx-index.ts +399 -0
- package/src/compiler/response.ts +35 -2
- package/src/config/index.ts +21 -3
- package/src/console/server/ai-runs-flows.ts +269 -0
- package/src/console/server/ai-runs.test.ts +323 -0
- package/src/console/server/ai-runs.ts +562 -0
- package/src/console/server/ai.ts +12 -1
- package/src/console/server/app.ts +10 -1
- package/src/console/server/decisions-flows.ts +157 -0
- package/src/console/server/decisions.test.ts +66 -0
- package/src/console/server/decisions.ts +229 -0
- package/src/console/server/flows-invoke.test.ts +2 -2
- package/src/console/server/flows.ts +50 -2
- package/src/console/server/invoke-user-flow.ts +9 -0
- package/src/console/server/runs-ingest.ts +16 -0
- package/src/console/server/serve.ts +5 -0
- package/src/console/server/state.ts +25 -1
- package/src/console/server/store-stats.test.ts +47 -0
- package/src/console/server/store-stats.ts +92 -18
- package/src/console/ui-next/dist/assets/access-page-DDKkhT9v.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-62Xts8Xi.js +1 -0
- package/src/console/ui-next/dist/assets/{cache-glyph-92uM5MO7.js → cache-glyph-DyeoKHKA.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-DtGVPtcs.js → call-pii-button-DvfpLXsU.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-DN7l6zmC.js → collapsible-BY03SeCg.js} +1 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-DZfuJXP4.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-BTjPVjRT.js +1 -0
- package/src/console/ui-next/dist/assets/decisions-page-CgkKlA56.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-Dk5LipF6.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-CRGrj5JU.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-Y6HLjyJ5.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-C6t8Vlml.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-chrome-Dslh2yE8.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CIVg7Jgb.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-DLPfNy-_.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-BlAEVgNW.js → highlighted-json-D7nNzpgB.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-BDf7OAHv.js → http-method-DdL19zzh.js} +1 -1
- package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
- package/src/console/ui-next/dist/assets/index-C0lc_s9d.js +58 -0
- package/src/console/ui-next/dist/assets/observability-page-CRlcyLCC.js +8 -0
- package/src/console/ui-next/dist/assets/{replica-lag-B3GLNVfF.js → replica-lag-Bb3FWUu9.js} +7 -7
- package/src/console/ui-next/dist/assets/request-meta-DYBMWhX8.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-DTE1sjTC.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-CcE-SXC8.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CKMSEQ4s.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-QOfk0M0H.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-DKC1CDDd.js +1 -0
- package/src/console/ui-next/dist/assets/use-vault-list-BAyQG8Re.js +1 -0
- package/src/console/ui-next/dist/assets/vault-page-B54v4Yit.js +2 -0
- package/src/console/ui-next/dist/index.html +4 -3
- package/src/console/ui-next/seed-parked-approval.ts +126 -0
- package/src/console/ui-next/src/client.ts +224 -1
- package/src/console/ui-next/src/components/explorer/detail-header.tsx +9 -0
- package/src/console/ui-next/src/components/explorer/explorer-start-toggle.tsx +5 -1
- package/src/console/ui-next/src/components/ui/sheet-form.tsx +21 -12
- package/src/console/ui-next/src/features/flows/decisions/decisions-page.tsx +216 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.ts +111 -0
- package/src/console/ui-next/src/features/flows/flows-page.tsx +10 -0
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +1 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +6 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.test.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-status.ts +58 -0
- package/src/console/ui-next/src/features/flows/traces/request-client-card.tsx +355 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.test.ts +114 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.ts +423 -0
- package/src/console/ui-next/src/features/flows/traces/request-input-view.test.ts +52 -6
- package/src/console/ui-next/src/features/flows/traces/request-input-view.ts +85 -19
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +505 -80
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.test.ts +61 -0
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.ts +78 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.ts +57 -0
- package/src/console/ui-next/src/features/flows/traces/trace-request-section.tsx +275 -96
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +16 -2
- package/src/console/ui-next/src/features/observability/detail/ai-runs.tsx +369 -0
- package/src/console/ui-next/src/features/observability/observability-page.tsx +31 -1
- package/src/console/ui-next/src/features/observability/state/observability-selection.ts +18 -3
- package/src/console/ui-next/src/features/store/performance/kv-performance-panel.tsx +12 -1
- package/src/console/ui-next/src/features/store/performance/performance-panel.tsx +14 -1
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +1 -1
- package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +15 -2
- package/src/console/ui-next/src/features/units/explorer/units-tree.tsx +73 -18
- package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +21 -0
- package/src/console/ui-next/src/features/units/lib/unit-tree.ts +82 -0
- package/src/console/ui-next/src/features/units/units-page.tsx +25 -7
- package/src/console/ui-next/src/router.tsx +17 -0
- package/src/console/ui-next/ui-next-seed-runs.ts +23 -0
- package/src/console/ui-next/vite-console-kernel-plugin.ts +43 -3
- package/src/console/ui-next/vite.config.ts +9 -0
- package/src/docker/docker.test.ts +13 -13
- package/src/docker/images-config.test.ts +12 -12
- package/src/docker/recipes/dragonfly.ts +3 -2
- package/src/docker/recipes/redis.ts +1 -1
- package/src/docker/stack-id.test.ts +1 -1
- package/src/drivers/ai-anthropic.ts +212 -21
- package/src/drivers/ai-mock.ts +34 -1
- package/src/drivers/ai-openai-compatible.ts +67 -26
- package/src/drivers/ai-preconnect.ts +18 -0
- package/src/drivers/ai-stream.test.ts +62 -0
- package/src/drivers/ai-types.ts +20 -0
- package/src/drivers/journal-postgres.ts +455 -3
- package/src/drivers/meilisearch.ts +2 -0
- package/src/drivers/memory-ddl.test.ts +18 -0
- package/src/drivers/memory.ts +21 -1
- package/src/drivers/pg-rls.test.ts +31 -0
- package/src/drivers/pg-rls.ts +15 -0
- package/src/drivers/postgres.test.ts +9 -0
- package/src/drivers/postgres.ts +49 -11
- package/src/drivers/vault-builtin.test.ts +10 -0
- package/src/drivers/vault-builtin.ts +13 -1
- package/src/drivers/vault-remote-bag.ts +5 -1
- package/src/drivers/vault-types.ts +6 -1
- package/src/elements/ai/agent-event-slot.ts +27 -0
- package/src/elements/ai/approval-http.ts +207 -0
- package/src/elements/ai/approval.test.ts +741 -0
- package/src/elements/ai/approval.ts +228 -0
- package/src/elements/ai/decisions/bind.ts +163 -0
- package/src/elements/ai/decisions/certificate.test.ts +291 -0
- package/src/elements/ai/decisions/certificate.ts +377 -0
- package/src/elements/ai/decisions/certify.ts +314 -0
- package/src/elements/ai/decisions/decide.test.ts +953 -0
- package/src/elements/ai/decisions/e2e.test.ts +399 -0
- package/src/elements/ai/decisions/export.test.ts +120 -0
- package/src/elements/ai/decisions/export.ts +146 -0
- package/src/elements/ai/decisions/fixtures/openrouter-request.json +24 -0
- package/src/elements/ai/decisions/fixtures/openrouter-response.json +22 -0
- package/src/elements/ai/decisions/fixtures/typesafe-request.json +19 -0
- package/src/elements/ai/decisions/fixtures/typesafe-response.json +13 -0
- package/src/elements/ai/decisions/http.test.ts +154 -0
- package/src/elements/ai/decisions/http.ts +240 -0
- package/src/elements/ai/decisions/labels.test.ts +282 -0
- package/src/elements/ai/decisions/labels.ts +257 -0
- package/src/elements/ai/decisions/openrouter.live.test.ts +136 -0
- package/src/elements/ai/decisions/openrouter.ts +36 -0
- package/src/elements/ai/decisions/provider.ts +102 -0
- package/src/elements/ai/decisions/typesafe.ts +36 -0
- package/src/elements/ai/declare.ts +252 -1
- package/src/elements/ai/events.test.ts +379 -0
- package/src/elements/ai/events.ts +138 -0
- package/src/elements/ai/run-events.test.ts +461 -0
- package/src/elements/ai/run-events.ts +522 -0
- package/src/elements/ai/runtime.ts +1186 -105
- package/src/elements/ai/stream-turn.ts +214 -0
- package/src/elements/ai/stream.live.test.ts +76 -0
- package/src/elements/ai/subagent.test.ts +136 -0
- package/src/elements/ai.test.ts +497 -3
- package/src/elements/ai.ts +16 -1
- package/src/elements/clock/durable.ts +27 -1
- package/src/elements/store/emit-drizzle.ts +2 -2
- package/src/elements/store/live-default.test.ts +2 -0
- package/src/elements/store/live-http.test.ts +1 -0
- package/src/elements/store/resource-list-docs.test.ts +2 -2
- package/src/elements/store/resource.test.ts +3 -3
- package/src/elements/store/schema-decl.test.ts +1 -0
- package/src/elements/store/sql-condition.ts +24 -7
- package/src/elements/store/sql-session.test.ts +51 -0
- package/src/elements/store/sql-session.ts +111 -1
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/vault/audit.test.ts +137 -0
- package/src/elements/vault/audit.ts +106 -13
- package/src/elements/vault/boot-chain.ts +9 -1
- package/src/elements/vault/builtin-adapter.ts +33 -3
- package/src/i18n/catalogs/ar.ts +9 -0
- package/src/i18n/catalogs/en.ts +9 -0
- package/src/index.ts +2 -0
- package/src/kernel/abort-scope.ts +33 -2
- package/src/kernel/agent-event-store.ts +448 -0
- package/src/kernel/app.ts +294 -19
- package/src/kernel/auto-cache.test.ts +6 -6
- package/src/kernel/boot-bind/ai.ts +3 -0
- package/src/kernel/boot-bind/store.test.ts +26 -0
- package/src/kernel/boot-bind/store.ts +82 -6
- package/src/kernel/boot-bind/vault.ts +1 -0
- package/src/kernel/boot.ts +34 -5
- package/src/kernel/builtin-errors.ts +10 -0
- package/src/kernel/capability.ts +3 -0
- package/src/kernel/client-descriptor.ts +1 -0
- package/src/kernel/concurrency.test.ts +33 -0
- package/src/kernel/decision-label-store.ts +441 -0
- package/src/kernel/effects-stamping.test.ts +2 -2
- package/src/kernel/effects.test.ts +2 -1
- package/src/kernel/effects.ts +13 -1
- package/src/kernel/element-registries.ts +3 -0
- package/src/kernel/errors-compiler.ts +20 -0
- package/src/kernel/errors-text.ts +4 -0
- package/src/kernel/errors.ts +2 -0
- package/src/kernel/external-effects.test.ts +36 -0
- package/src/kernel/flow.test.ts +2 -3
- package/src/kernel/flow.ts +24 -0
- package/src/kernel/fx-decide.ts +761 -0
- package/src/kernel/fx-fetch.ts +5 -1
- package/src/kernel/fx.test.ts +12 -5
- package/src/kernel/fx.ts +316 -29
- package/src/kernel/http-frame.test.ts +94 -0
- package/src/kernel/http-frame.ts +203 -0
- package/src/kernel/idempotency-store.ts +578 -0
- package/src/kernel/idempotency.test.ts +556 -0
- package/src/kernel/idempotency.ts +304 -0
- package/src/kernel/index.ts +1 -0
- package/src/kernel/journal.ts +160 -0
- package/src/kernel/json-result.ts +10 -0
- package/src/kernel/pipeline.test.ts +45 -0
- package/src/kernel/sse-id.ts +32 -0
- package/src/manifest/diff.ts +1 -0
- package/src/manifest/types.ts +37 -2
- package/src/mcp/ai-tools.test.ts +202 -0
- package/src/mcp/authorization.ts +66 -0
- package/src/mcp/session.ts +3 -0
- package/src/mcp/tools.ts +168 -0
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
- package/src/plugins/otp.test.ts +65 -1
- package/src/plugins/otp.ts +59 -7
- package/src/release/absolute-regression.test.ts +63 -0
- package/src/release/http-graph.test.ts +27 -0
- package/src/release/http-graph.ts +95 -0
- package/src/release/index.ts +3 -0
- package/src/release/limits.ts +17 -2
- package/src/release/measure.ts +144 -28
- package/src/runs/collect.ts +4 -1
- package/src/runs/parquet.test.ts +27 -1
- package/src/runs/parquet.ts +19 -0
- package/src/runs/types.ts +31 -0
- package/src/runtime/json-code-block.test.ts +106 -47
- package/src/runtime/json-code-block.ts +1209 -46
- package/src/term.test.ts +13 -0
- package/src/term.ts +81 -49
- package/src/test/create-test-app.test.ts +3 -1
- package/src/test/create-test-app.ts +40 -0
- package/src/test/live-signals.test.ts +1 -0
- package/src/test/provisions.integration.test.ts +1 -0
- package/src/test/tenant-isolation.test.ts +1 -0
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
- package/src/console/ui-next/dist/assets/access-page-Bgt9bq2r.js +0 -4
- package/src/console/ui-next/dist/assets/agent-disclosure-B43CXZZR.js +0 -1
- package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-BmIR9FV8.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-BMHs-IzK.js +0 -1
- package/src/console/ui-next/dist/assets/index-BKpaes3n.js +0 -63
- package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
- package/src/console/ui-next/dist/assets/observability-page-WnVLI-0j.js +0 -4
- package/src/console/ui-next/dist/assets/request-meta-DMbnAe3f.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-KvFDingJ.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-Htk8Cm9t.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CFMPWX4f.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-C4NdNuxP.js +0 -1
- package/src/console/ui-next/dist/assets/use-vault-list-CT4-gajj.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-CBYl0LW_.js +0 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Reference"
|
|
3
3
|
description: "Lookup pages — CLI, config, fx, errors, security, and plugins."
|
|
4
|
-
icon: "
|
|
4
|
+
icon: "BookBookmark"
|
|
5
5
|
source: "docs/spec/unified-theory.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -23,6 +23,11 @@ Dense tables and command lists. Reach for these when you already know what you a
|
|
|
23
23
|
/>
|
|
24
24
|
<Card title="fx" description="The complete fx surface and effects." href="/docs/reference/fx" />
|
|
25
25
|
<Card title="Errors" description="OKE codes, denials, fixes." href="/docs/reference/errors" />
|
|
26
|
+
<Card
|
|
27
|
+
title="Idempotency"
|
|
28
|
+
description="One Idempotency-Key, one execution."
|
|
29
|
+
href="/docs/reference/idempotency"
|
|
30
|
+
/>
|
|
26
31
|
<Card
|
|
27
32
|
title="Security"
|
|
28
33
|
description="Host, Origin, planes, MCP posture."
|
|
@@ -588,28 +588,29 @@ plus a handwritten `http.get("/links")`.
|
|
|
588
588
|
|
|
589
589
|
## Learn more
|
|
590
590
|
|
|
591
|
+
- [The Architecture](/docs/understand/the-architecture) — derived routes, no hand-written table
|
|
592
|
+
- [Try it](/docs/understand/try-it) — scaffold, run, and hit a stamped path
|
|
591
593
|
- [HTTP](/docs/elements/flow/http) — verbs, envelopes, `http.resource`, live SSE
|
|
592
594
|
- [Client](/docs/client/calling) — `api.links.get`, REST vs RPC, `$routes`
|
|
593
595
|
- [Errors](/docs/reference/errors) — OKE1040 · OKE1030 · OKE1041 · OKE1045 · OKE1070 · OKE1072
|
|
594
|
-
- [The Architecture](/docs/understand/the-architecture) — derived routes, no hand-written table
|
|
595
596
|
- [Gate](/docs/elements/gate) — `.gate(...)` / `.public()` on the same trigger
|
|
596
597
|
|
|
597
598
|
## Next
|
|
598
599
|
|
|
599
600
|
<Cards>
|
|
600
601
|
<Card
|
|
601
|
-
title="
|
|
602
|
-
description="
|
|
603
|
-
href="/docs/
|
|
602
|
+
title="The Architecture"
|
|
603
|
+
description="One law, eight elements, derived Manifest — where routes come from."
|
|
604
|
+
href="/docs/understand/the-architecture"
|
|
604
605
|
/>
|
|
605
606
|
<Card
|
|
606
|
-
title="
|
|
607
|
-
description="
|
|
608
|
-
href="/docs/
|
|
607
|
+
title="Try it"
|
|
608
|
+
description="Scaffold shorter, run oke dev, and call a stamped path."
|
|
609
|
+
href="/docs/understand/try-it"
|
|
609
610
|
/>
|
|
610
611
|
<Card
|
|
611
|
-
title="
|
|
612
|
-
description="
|
|
613
|
-
href="/docs/elements/flow/
|
|
612
|
+
title="HTTP"
|
|
613
|
+
description="REST verbs, RFC 10008 QUERY, CRUD mounts, and live SSE on Flow."
|
|
614
|
+
href="/docs/elements/flow/http"
|
|
614
615
|
/>
|
|
615
616
|
</Cards>
|
|
@@ -27,11 +27,11 @@ app.post("/signup", async (req, res) => {
|
|
|
27
27
|
|
|
28
28
|
It works. It ships. Then a traffic spike, a silent failure, a compliance question, and a double-submit turn those four lines into six files — endpoint, queue, worker, Redis connection, mail client, audit table — that never agreed with each other about anything.
|
|
29
29
|
|
|
30
|
-
**A payment webhook.** A provider confirms a charge. Mark the order paid.
|
|
30
|
+
**A payment webhook.** A provider confirms a charge. Mark the order paid. The provider retries the same event during a hiccup. That attempt is a second run, visible in `fx.runs`. The fix is one line: a unique constraint or upsert on the provider's event id.
|
|
31
31
|
|
|
32
32
|
**A nightly report.** Summarize yesterday's activity and email it to managers. Trivial — until a manager's access gets revoked at 11:58pm and the report that runs at midnight has no idea the permission it checked when the feature was built isn't the permission that holds right now.
|
|
33
33
|
|
|
34
|
-
Three teams. Three domains. Nobody on any of them talked to the other two. And all three land on the identical fork: **something has to happen later,
|
|
34
|
+
Three teams. Three domains. Nobody on any of them talked to the other two. And all three land on the identical fork: **something has to happen later, and the original four lines never said where the retry, the receipt, or the duplicate would be visible.** The door makes that visible. The fix is one line.
|
|
35
35
|
|
|
36
36
|
### Follow one all the way through
|
|
37
37
|
|
|
@@ -49,7 +49,7 @@ The endpoint is fast again. It's also no longer one system — it's an endpoint,
|
|
|
49
49
|
|
|
50
50
|
<SixSystemsDrift />
|
|
51
51
|
|
|
52
|
-
From here the same pattern repeats on a longer clock. A support ticket reveals a job failed silently — nobody had configured retries, so you add them, and now a specific number (3? 5? with what backoff?) lives in a file that nobody will remember the reasoning for in six weeks. Compliance asks for proof of every email sent — you add a table written to from inside the worker, and now that worker has two jobs instead of one, quietly capable of disagreeing with itself if the second write fails. Someone double-clicks submit — two jobs enqueue, two emails send
|
|
52
|
+
From here the same pattern repeats on a longer clock. A support ticket reveals a job failed silently — nobody had configured retries, so you add them, and now a specific number (3? 5? with what backoff?) lives in a file that nobody will remember the reasoning for in six weeks. Compliance asks for proof of every email sent — you add a table written to from inside the worker, and now that worker has two jobs instead of one, quietly capable of disagreeing with itself if the second write fails. Someone double-clicks submit — two jobs enqueue, two emails send. The duplicate is two runs. The fix is one line, a unique constraint or upsert, not a second protocol shared by a queue and a database.
|
|
53
53
|
|
|
54
54
|
None of these were mistakes. Each one was the correct call, made by a competent engineer, in direct response to something that actually happened.
|
|
55
55
|
|
|
@@ -74,7 +74,7 @@ OKE removes the disagreement by removing the choice. It's one rule, in two parts
|
|
|
74
74
|
|
|
75
75
|
**First: every trigger reduces to the same shape.** An HTTP request, a scheduled tick, a queue message, a database change — whatever wakes the code up, what follows has one identical anatomy: `on(Trigger) → Effects`. Not four systems that happen to look similar. One system, with four ways to wake it up.
|
|
76
76
|
|
|
77
|
-
**Second: every effect passes through one door.** Nothing is allowed to touch a database, send an email, check a permission, or read the clock on its own — all of it goes through a single surface.
|
|
77
|
+
**Second: every effect passes through one door.** Nothing is allowed to touch a database, send an email, check a permission, or read the clock on its own — all of it goes through a single surface. The Manifest lists every effect. Least privilege is enforced at runtime. Every run is recorded in `fx.runs`. Durability is one line in a known place — `signal.once` retries, or `durable` plus `fx.step` — not a new system.
|
|
78
78
|
|
|
79
79
|
<FlowShape />
|
|
80
80
|
|
|
@@ -193,7 +193,9 @@ do: async (input, fx) => {
|
|
|
193
193
|
};
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
-
This one rule is what made the month-8 drift
|
|
196
|
+
This one rule is what made the month-8 drift visible: every effect is in the Manifest, every run is in `fx.runs`, and a duplicate is a second run you can see.
|
|
197
|
+
A retried HTTP call can send one `Idempotency-Key`, so that retry runs once.
|
|
198
|
+
A provider event still needs a unique constraint, because the provider does not send that header.
|
|
197
199
|
|
|
198
200
|
### Putting the five pieces together
|
|
199
201
|
|
|
@@ -223,14 +225,14 @@ export const signup = on(
|
|
|
223
225
|
|
|
224
226
|
Nothing about the code above looks more complicated than the four lines that started the drift — because it isn't. The difference only shows up when the same pressure from that timeline hits it. Every fork was really the same question — _is this safe to retry, safe to audit, safe to run twice?_ — and now it has one home:
|
|
225
227
|
|
|
226
|
-
| Then: a new system per incident | Now:
|
|
227
|
-
| -------------------------------------------------------------------- |
|
|
228
|
-
| **Week 2 spike** — hand-build a queue + worker to run the send later |
|
|
229
|
-
| **Month 2 silence** — a retry count in a worker nobody remembers |
|
|
230
|
-
| **Month 4 audit** — a `sent_emails` table written by hand from a job |
|
|
231
|
-
| **Month 6 double-submit** — dedup split across a queue and a DB |
|
|
228
|
+
| Then: a new system per incident | Now: the door makes it visible, and the fix is one line |
|
|
229
|
+
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
230
|
+
| **Week 2 spike** — hand-build a queue + worker to run the send later | The send is a Signal consumer. The Manifest lists it. The run is in `fx.runs`. |
|
|
231
|
+
| **Month 2 silence** — a retry count in a worker nobody remembers | The retry count lives on `signal.once` (default 3) or on `durable` + `fx.step`, which skips a step already journaled. A failed HTTP request is not retried by `fx`. |
|
|
232
|
+
| **Month 4 audit** — a `sent_emails` table written by hand from a job | The send is an effect in the Manifest and a run in `fx.runs`. Channel records a receipt. The default receipt ledger is process-local memory. |
|
|
233
|
+
| **Month 6 double-submit** — dedup split across a queue and a DB | A retry of one call runs once when it sends `Idempotency-Key`. Two submits are two calls unless the app passes the same key. Provider webhooks still need a unique constraint on the provider's event id, because providers do not send that header. `{ key }` serializes `once` per key. It does not deduplicate. |
|
|
232
234
|
|
|
233
|
-
|
|
235
|
+
The door makes each of those problems visible, and the fix is one line in a known place. The signup sample is safe to run twice once the user row has a unique constraint or an upsert. The second attempt is a second run in `fx.runs`.
|
|
234
236
|
|
|
235
237
|
### Five kinds of trigger
|
|
236
238
|
|
|
@@ -405,7 +405,7 @@ test("creates a user", async () => {
|
|
|
405
405
|
bun test # [!code focus]
|
|
406
406
|
```
|
|
407
407
|
|
|
408
|
-
`createTestApp` swaps every driver behind `fx` for a deterministic one.
|
|
408
|
+
`createTestApp` swaps every driver behind `fx` for a deterministic one. It stamps Manifest tokens. Inline flows that are not an app pass `capability: "open"`.
|
|
409
409
|
|
|
410
410
|
</Step>
|
|
411
411
|
|
|
@@ -762,7 +762,7 @@ return await fx.ask(triage, { message }); // [!code focus]
|
|
|
762
762
|
|
|
763
763
|
## Learn more
|
|
764
764
|
|
|
765
|
-
- [Routing](/docs/
|
|
765
|
+
- [Routing](/docs/understand/routing) — when to omit the path, `list` / `create` / `get` filenames
|
|
766
766
|
- [Flow](/docs/elements/flow) — one species; `fx` is the door
|
|
767
767
|
- [Signal](/docs/elements/signal) — `once` / `broadcast` / `live`
|
|
768
768
|
- [Store](/docs/elements/store) — SQL, KV, files, search
|
|
@@ -775,7 +775,7 @@ return await fx.ask(triage, { message }); // [!code focus]
|
|
|
775
775
|
<Card
|
|
776
776
|
title="Routing"
|
|
777
777
|
description="File-tree stamps, many-routes files, and reserved leaves."
|
|
778
|
-
href="/docs/
|
|
778
|
+
href="/docs/understand/routing"
|
|
779
779
|
/>
|
|
780
780
|
<Card title="Flow" description="on(trigger) → do through fx." href="/docs/elements/flow" />
|
|
781
781
|
<Card title="Signal" description="Move work off the request." href="/docs/elements/signal" />
|
|
@@ -44,4 +44,48 @@ describe("api-key SQL persist", () => {
|
|
|
44
44
|
expect(other.keys.get("key_sql")?.creatorId).toBe("u1");
|
|
45
45
|
expect(other.keys.get("key_sql")?.hash).toBe(created.row.hash);
|
|
46
46
|
});
|
|
47
|
+
|
|
48
|
+
test("authenticate reloads an oke_ secret missing from memory", async () => {
|
|
49
|
+
const { authenticateApiKey } = await import("./api-keys.ts");
|
|
50
|
+
const rows = new Map<string, Record<string, unknown>>();
|
|
51
|
+
const sql: ApiKeySqlExec = {
|
|
52
|
+
async execute(_q, params) {
|
|
53
|
+
const id = String(params[0]);
|
|
54
|
+
rows.set(id, {
|
|
55
|
+
id,
|
|
56
|
+
plane: params[1],
|
|
57
|
+
hash: params[2],
|
|
58
|
+
name: params[3],
|
|
59
|
+
scopes: params[4],
|
|
60
|
+
expires_at: params[5],
|
|
61
|
+
rate_limit: params[6],
|
|
62
|
+
ip_allowlist: params[7],
|
|
63
|
+
creator_id: params[8],
|
|
64
|
+
creator_scopes: params[9],
|
|
65
|
+
created_at: params[10],
|
|
66
|
+
last_used_at: params[11],
|
|
67
|
+
revoked_at: params[12],
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
async all() {
|
|
71
|
+
return [...rows.values()];
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const writer = createApiKeyStore({ pepper: "shared" });
|
|
75
|
+
bindApiKeySqlPersist(writer, sql);
|
|
76
|
+
const created = await createApiKey(writer, {
|
|
77
|
+
plane: "user",
|
|
78
|
+
name: "Auth Test",
|
|
79
|
+
scopes: ["member"],
|
|
80
|
+
creatorId: "user_admin",
|
|
81
|
+
creatorScopes: ["member"],
|
|
82
|
+
id: "key_live",
|
|
83
|
+
});
|
|
84
|
+
const reader = createApiKeyStore({ pepper: "shared" });
|
|
85
|
+
bindApiKeySqlPersist(reader, sql);
|
|
86
|
+
expect(reader.keys.size).toBe(0);
|
|
87
|
+
const row = await authenticateApiKey(reader, created.secret);
|
|
88
|
+
expect(row?.id).toBe("key_live");
|
|
89
|
+
expect(reader.keys.has("key_live")).toBe(true);
|
|
90
|
+
});
|
|
47
91
|
});
|
package/src/auth/api-key-sql.ts
CHANGED
|
@@ -97,6 +97,7 @@ export function bindApiKeySqlPersist(
|
|
|
97
97
|
table: string = AUTH_TABLES.apiKeys,
|
|
98
98
|
): void {
|
|
99
99
|
store.persist = (row) => persistApiKeyRow(sql, row, table);
|
|
100
|
+
store.reload = () => hydrateApiKeyStore(sql, store, table);
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
/** Runtime that can open the app's shared primary SQL connection. */
|
package/src/auth/api-keys.ts
CHANGED
|
@@ -85,6 +85,11 @@ export interface ApiKeyStore {
|
|
|
85
85
|
pepper?: string;
|
|
86
86
|
/** Optional write-through persist (SQL). */
|
|
87
87
|
persist?: (row: ApiKeyRow) => void | Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Reload rows from the durable table. `oke dev` keeps Console and the app
|
|
90
|
+
* in separate processes; a miss on an `oke_` secret refreshes this cache.
|
|
91
|
+
*/
|
|
92
|
+
reload?: () => void | Promise<void>;
|
|
88
93
|
/** Sliding-window hits for per-key rateLimit (subject = key id). */
|
|
89
94
|
rateHits?: Map<string, number[]>;
|
|
90
95
|
}
|
|
@@ -202,20 +207,33 @@ export async function authenticateApiKey(
|
|
|
202
207
|
const now = options.now ?? (() => Date.now());
|
|
203
208
|
const pepper = store.pepper ?? DEFAULT_PEPPER;
|
|
204
209
|
const hash = await hashApiKeySecret(secret, pepper);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (!allowed) return null;
|
|
210
|
+
let row = matchApiKeyHash(store, hash);
|
|
211
|
+
if (!row && secret.startsWith("oke_") && store.reload) {
|
|
212
|
+
try {
|
|
213
|
+
await store.reload();
|
|
214
|
+
} catch {
|
|
215
|
+
// Durable read missed — the in-memory miss still stands.
|
|
212
216
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
+
row = matchApiKeyHash(store, hash);
|
|
218
|
+
}
|
|
219
|
+
if (!row) return null;
|
|
220
|
+
if (row.revokedAt !== null) return null;
|
|
221
|
+
if (row.expiresAt !== null && row.expiresAt <= now()) return null;
|
|
222
|
+
if (row.ipAllowlist.length > 0) {
|
|
223
|
+
const allowed = await allowlistAllowsIp(row.ipAllowlist, options.ip, options.lookup);
|
|
224
|
+
if (!allowed) return null;
|
|
225
|
+
}
|
|
226
|
+
if (row.rateLimit && !takeKeyRate(store, row.id, row.rateLimit, now())) return null;
|
|
227
|
+
row.lastUsedAt = now();
|
|
228
|
+
await persistRow(store, row);
|
|
229
|
+
return row;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function matchApiKeyHash(store: ApiKeyStore, hash: string): ApiKeyRow | undefined {
|
|
233
|
+
for (const row of store.keys.values()) {
|
|
234
|
+
if (row.hash === hash) return row;
|
|
217
235
|
}
|
|
218
|
-
return
|
|
236
|
+
return undefined;
|
|
219
237
|
}
|
|
220
238
|
|
|
221
239
|
/**
|
package/src/auth/config.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Better Auth is inspiration for shape only; this is OKE-native.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { createEnv } from "../runtime/primitives.ts";
|
|
7
8
|
import type { PasswordHashOptions } from "../runtime/types.ts";
|
|
8
9
|
import type { SessionStore } from "./sessions.ts";
|
|
9
10
|
import { createApiKeyStore, type ApiKeyStore } from "./api-keys.ts";
|
|
@@ -226,9 +227,12 @@ export function mintDevAuthSecret(): string {
|
|
|
226
227
|
export function resolveGateAuth(options: ResolveGateAuthOptions): ResolvedGateAuth {
|
|
227
228
|
const { auth, env } = options;
|
|
228
229
|
const isProd = env === "prod" || env === "production";
|
|
229
|
-
let secret = auth.secret;
|
|
230
|
+
let secret = auth.secret?.trim() ?? "";
|
|
230
231
|
let secretMinted = false;
|
|
231
|
-
if (
|
|
232
|
+
if (secret.length === 0) {
|
|
233
|
+
secret = createEnv().get("OKE_AUTH_SECRET")?.trim() ?? "";
|
|
234
|
+
}
|
|
235
|
+
if (secret.length === 0) {
|
|
232
236
|
if (isProd) {
|
|
233
237
|
throw new Error(
|
|
234
238
|
"gate.auth: secret is required in production (set gate.auth.secret or OKE_AUTH_SECRET)",
|
|
@@ -21,13 +21,33 @@ afterEach(() => {
|
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
describe("gate.auth — config", () => {
|
|
24
|
+
const prevAuthSecret = process.env.OKE_AUTH_SECRET;
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
if (prevAuthSecret === undefined) delete process.env.OKE_AUTH_SECRET;
|
|
28
|
+
else process.env.OKE_AUTH_SECRET = prevAuthSecret;
|
|
29
|
+
});
|
|
30
|
+
|
|
24
31
|
test("prod without secret fails", () => {
|
|
32
|
+
delete process.env.OKE_AUTH_SECRET;
|
|
25
33
|
expect(() =>
|
|
26
34
|
resolveGateAuth({ auth: { emailAndPassword: { enabled: true } }, env: "prod" }),
|
|
27
35
|
).toThrow(/secret is required/);
|
|
28
36
|
});
|
|
29
37
|
|
|
38
|
+
test("OKE_AUTH_SECRET is the pepper when gate.auth.secret is omitted", () => {
|
|
39
|
+
process.env.OKE_AUTH_SECRET = "shared-dev-pepper";
|
|
40
|
+
const resolved = resolveGateAuth({
|
|
41
|
+
auth: { emailAndPassword: { enabled: true } },
|
|
42
|
+
env: "dev",
|
|
43
|
+
});
|
|
44
|
+
expect(resolved.secret).toBe("shared-dev-pepper");
|
|
45
|
+
expect(resolved.secretMinted).toBe(false);
|
|
46
|
+
expect(resolved.apiKeyStore.pepper).toBe("shared-dev-pepper");
|
|
47
|
+
});
|
|
48
|
+
|
|
30
49
|
test("dev mints secret", () => {
|
|
50
|
+
delete process.env.OKE_AUTH_SECRET;
|
|
31
51
|
const resolved = resolveGateAuth({
|
|
32
52
|
auth: { emailAndPassword: { enabled: true } },
|
|
33
53
|
env: "dev",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commands named in the agent contract must exist on the CLI.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { describe, expect, test } from "bun:test";
|
|
7
|
+
import { commandNames } from "./registry.ts";
|
|
8
|
+
|
|
9
|
+
describe("AGENTS.md commands", () => {
|
|
10
|
+
test("every oke command mentioned in AGENTS.md is registered", () => {
|
|
11
|
+
const text = readFileSync(new URL("../../AGENTS.md", import.meta.url), "utf8");
|
|
12
|
+
const registered = new Set(commandNames());
|
|
13
|
+
const mentioned = new Set<string>();
|
|
14
|
+
for (const match of text.matchAll(/`oke ([a-z][a-z0-9-]*)/g)) {
|
|
15
|
+
const name = match[1];
|
|
16
|
+
if (name) mentioned.add(name);
|
|
17
|
+
}
|
|
18
|
+
expect(mentioned.has("eval")).toBe(true);
|
|
19
|
+
expect(mentioned.has("decide")).toBe(true);
|
|
20
|
+
expect(text).not.toContain("oke decide certify");
|
|
21
|
+
for (const name of mentioned) {
|
|
22
|
+
expect(registered.has(name)).toBe(true);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
6
|
+
import { EventEmitter } from "node:events";
|
|
6
7
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7
8
|
import { tmpdir } from "node:os";
|
|
8
9
|
import { join, resolve } from "node:path";
|
|
9
10
|
import { resetRequiredEnvNames, resetSecrets } from "../elements/vault/declare.ts";
|
|
10
11
|
import { resetBindings } from "../kernel/on.ts";
|
|
11
|
-
import { maybeAskVaultGaps, probeVaultGaps } from "./ask-vault-gaps.ts";
|
|
12
|
+
import { maybeAskVaultGaps, probeVaultGaps, secretPromptWrite } from "./ask-vault-gaps.ts";
|
|
13
|
+
import { promptHidden } from "./vault-secure-input.ts";
|
|
12
14
|
|
|
13
15
|
const OKE = resolve(import.meta.dir, "../index.ts");
|
|
14
16
|
|
|
@@ -93,6 +95,41 @@ describe("maybeAskVaultGaps", () => {
|
|
|
93
95
|
});
|
|
94
96
|
});
|
|
95
97
|
|
|
98
|
+
describe("secretPromptWrite", () => {
|
|
99
|
+
test("keeps the mask on the prompt line", async () => {
|
|
100
|
+
const stdin = new EventEmitter() as EventEmitter & {
|
|
101
|
+
setRawMode?: (mode: boolean) => void;
|
|
102
|
+
off?: (event: string, listener: (...args: unknown[]) => void) => EventEmitter;
|
|
103
|
+
};
|
|
104
|
+
stdin.setRawMode = () => undefined;
|
|
105
|
+
stdin.off = (event: string | symbol, listener: (...args: unknown[]) => void) => {
|
|
106
|
+
stdin.removeListener(event, listener);
|
|
107
|
+
return stdin;
|
|
108
|
+
};
|
|
109
|
+
const shown: string[] = [];
|
|
110
|
+
const echoed: string[] = [];
|
|
111
|
+
const pending = promptHidden("Enter value for OPENROUTER_API_KEY: ", {
|
|
112
|
+
stdin,
|
|
113
|
+
write: secretPromptWrite(
|
|
114
|
+
(text) => shown.push(text),
|
|
115
|
+
(text) => echoed.push(text),
|
|
116
|
+
),
|
|
117
|
+
exit: () => {
|
|
118
|
+
throw new Error("exit should not run");
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
await Promise.resolve();
|
|
122
|
+
stdin.emit("data", "sk");
|
|
123
|
+
stdin.emit("data", "\r");
|
|
124
|
+
expect(await pending).toBe("sk");
|
|
125
|
+
expect(shown).toHaveLength(1);
|
|
126
|
+
expect(shown[0]).toContain("Enter value for OPENROUTER_API_KEY: ");
|
|
127
|
+
expect(shown[0]?.endsWith("\n")).toBe(false);
|
|
128
|
+
expect(echoed.join("")).toBe("**\n");
|
|
129
|
+
expect(shown.join("")).not.toContain("*");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
96
133
|
describe("probeVaultGaps", () => {
|
|
97
134
|
test("sees secrets on oke({ secrets }) after consume drains the registry", async () => {
|
|
98
135
|
const dir = mkdtempSync(join(tmpdir(), "probe-vault-"));
|
|
@@ -14,7 +14,7 @@ import { buildVaultBootChain, normalizeVaultDriverId } from "../elements/vault/b
|
|
|
14
14
|
import { createVaultRuntime, VaultBootError, type VaultGap } from "../elements/vault/runtime.ts";
|
|
15
15
|
import type { OkeOptions } from "../kernel/app.ts";
|
|
16
16
|
import { requiredEnvRegistry, secretRegistry } from "../kernel/element-registries.ts";
|
|
17
|
-
import { formatCliChrome } from "../term.ts";
|
|
17
|
+
import { formatCliChrome, formatStatusLine } from "../term.ts";
|
|
18
18
|
import { loadOkeConfig } from "./load-config.ts";
|
|
19
19
|
import { openEnvStore } from "./vault-cmd.ts";
|
|
20
20
|
import { promptHidden } from "./vault-secure-input.ts";
|
|
@@ -147,6 +147,35 @@ function unionSecrets(
|
|
|
147
147
|
return out;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Writer for {@link promptHidden} during `oke dev`.
|
|
152
|
+
*
|
|
153
|
+
* The question is one chrome row with no trailing newline, so mask
|
|
154
|
+
* characters stay on that row. Echo (`*`, backspace, Enter) goes straight
|
|
155
|
+
* to stdout: `formatCliChrome` would turn each `*` into its own `│` line,
|
|
156
|
+
* and the anchored board would count each of those writes as a new row.
|
|
157
|
+
*
|
|
158
|
+
* @param sink - Line-oriented stdout (the dev board wrapper)
|
|
159
|
+
* @param echo - Raw echo. Defaults to `process.stdout.write`
|
|
160
|
+
*/
|
|
161
|
+
export function secretPromptWrite(
|
|
162
|
+
sink: (text: string) => void,
|
|
163
|
+
echo: (text: string) => void = (text) => {
|
|
164
|
+
process.stdout.write(text);
|
|
165
|
+
},
|
|
166
|
+
): (text: string) => void {
|
|
167
|
+
let opened = false;
|
|
168
|
+
return (text: string) => {
|
|
169
|
+
if (!opened) {
|
|
170
|
+
opened = true;
|
|
171
|
+
const line = formatStatusLine(text);
|
|
172
|
+
sink(line.endsWith("\n") ? line.slice(0, -1) : line);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
echo(text);
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
150
179
|
/**
|
|
151
180
|
* After compose hydrate (and usually schema push), fill Vault boot gaps
|
|
152
181
|
* one-by-one into `.env.local`. Skips when non-TTY or no gaps.
|
|
@@ -175,7 +204,11 @@ export async function maybeAskVaultGaps(options: AskVaultGapsOptions): Promise<n
|
|
|
175
204
|
const envPath = resolve(cwd, options.envFile ?? ".env.local");
|
|
176
205
|
const store = await openEnvStore(envPath);
|
|
177
206
|
const read =
|
|
178
|
-
options.readSecret ??
|
|
207
|
+
options.readSecret ??
|
|
208
|
+
((prompt: string) =>
|
|
209
|
+
promptHidden(prompt, {
|
|
210
|
+
write: secretPromptWrite(write),
|
|
211
|
+
}));
|
|
179
212
|
|
|
180
213
|
const remaining: VaultGap[] = [];
|
|
181
214
|
for (const gap of gaps) {
|
|
@@ -11,8 +11,8 @@ import { join } from "node:path";
|
|
|
11
11
|
|
|
12
12
|
const ROOT = join(import.meta.dir, "../..");
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
const FORBIDDEN_HONO = ["Ho", "no"].join("");
|
|
14
|
+
/** Word-boundary router peer — `-F` false-positives on `Honored` / `canHonor`. */
|
|
15
|
+
const FORBIDDEN_HONO = ["\\b", "Ho", "no", "\\b"].join("");
|
|
16
16
|
/** Forbidden peer name. */
|
|
17
17
|
const FORBIDDEN_ELYSIA = ["Ely", "sia"].join("");
|
|
18
18
|
/** Forbidden peer name (covers Enc + ore.ts). */
|
|
@@ -104,7 +104,7 @@ function assertZeroGitGrepRegex(pattern: string, ignorePrefixes: readonly string
|
|
|
104
104
|
|
|
105
105
|
describe("named competitor mention removal gate", () => {
|
|
106
106
|
test("tracked tree has zero mentions of the thin multi-runtime router peer", () => {
|
|
107
|
-
|
|
107
|
+
assertZeroGitGrepRegex(FORBIDDEN_HONO);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
test("tracked tree has zero mentions of the Bun-first peer framework", () => {
|