@iislee/opencodex 2.11.0 → 2.31.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_INSTALL.md +109 -0
- package/README.md +114 -19
- package/bin/ocx.mjs +164 -36
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-BF38heuV.js +104 -0
- package/gui/dist/assets/index-DMiI18Kv.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +19 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +376 -52
- package/src/adapters/base.ts +54 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +129 -6
- package/src/adapters/cursor/discovery.ts +61 -4
- package/src/adapters/cursor/effort-map.ts +27 -3
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +174 -58
- package/src/adapters/cursor/live-transport.ts +609 -170
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/native-exec.ts +101 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +383 -65
- package/src/adapters/cursor/request-builder.ts +237 -31
- package/src/adapters/cursor/tool-definitions.ts +242 -12
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +28 -1
- package/src/adapters/cursor.ts +133 -7
- package/src/adapters/google-antigravity-replay.ts +558 -34
- package/src/adapters/google-antigravity-wire.ts +43 -10
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +618 -74
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +92 -54
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1311 -336
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +830 -56
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +104 -21
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +428 -61
- package/src/chat/inbound.ts +43 -19
- package/src/chat/outbound.ts +82 -26
- package/src/claude/agents-inject.ts +32 -9
- package/src/claude/context-windows.ts +21 -5
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +70 -16
- package/src/cli/account-api.ts +35 -1
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +389 -2
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +30 -6
- package/src/cli/agent.ts +45 -1
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +29 -4
- package/src/cli/claude.ts +103 -34
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +593 -0
- package/src/cli/doctor.ts +315 -43
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +20 -271
- package/src/cli/index.ts +378 -519
- package/src/cli/init.ts +4 -17
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +35 -2
- package/src/cli/models.ts +100 -14
- package/src/cli/observe.ts +92 -3
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/provider.ts +24 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +437 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +10 -3
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/v2.ts +105 -10
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1358 -21
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +493 -106
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +849 -242
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +345 -32
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +68 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +121 -30
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +529 -45
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +224 -30
- package/src/codex/catalog/provider-fetch.ts +1460 -134
- package/src/codex/catalog/sync.ts +1449 -186
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +26 -20
- package/src/codex/history-provider.ts +231 -28
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1073 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +353 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +492 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +682 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +175 -13
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +505 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1009 -28
- package/src/codex/subagent-model-fallback.ts +350 -35
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +20 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +81 -9
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +1762 -140
- package/src/generated/compatibility-version.json +3164 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +152 -29
- package/src/images/plan.ts +23 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +153 -9
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +6 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +44 -2
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +398 -66
- package/src/oauth/key-providers.ts +16 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/store.ts +119 -21
- package/src/oauth/token-guardian.ts +9 -3
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +22 -5
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +253 -6
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +1319 -38
- package/src/providers/registry.ts +1429 -111
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +42 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +11 -4
- package/src/reasoning-effort.ts +49 -1
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +220 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +7 -1
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +565 -27
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +366 -30
- package/src/routing/analytics.ts +378 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +267 -46
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +130 -56
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +159 -43
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +865 -181
- package/src/server/lifecycle.ts +284 -13
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +398 -103
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/combo-routes.ts +62 -24
- package/src/server/management/config-routes.ts +464 -51
- package/src/server/management/context.ts +80 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +101 -32
- package/src/server/management/model-routes.ts +189 -131
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +80 -4
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +764 -157
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +288 -32
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +115 -14
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +132 -5
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +236 -76
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log.ts +235 -22
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/collaboration.ts +204 -35
- package/src/server/responses/compact.ts +442 -55
- package/src/server/responses/core.ts +2872 -331
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +79 -4
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +39 -38
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1111 -90
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +845 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +104 -1236
- package/src/update/index.ts +32 -19
- package/src/update/job.ts +442 -67
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +268 -16
- package/src/usage/log.ts +606 -41
- package/src/usage/summary.ts +177 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +9 -3
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +11 -3
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +279 -50
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
import { assessUrlDestination, resolvePublicAddresses } from "../../lib/destination-policy";
|
|
3
|
+
import { localFingerprint } from "../digest";
|
|
4
|
+
import { readInstallationSalt } from "../subject/installation-salt";
|
|
5
|
+
import { TransportError } from "./transport";
|
|
6
|
+
import type { DnsResolver, LabDestinationV1 } from "./types";
|
|
7
|
+
|
|
8
|
+
export class LabDestinationError extends Error {
|
|
9
|
+
override readonly name = "LabDestinationError";
|
|
10
|
+
constructor(message: string, readonly code: string) { super(message); }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeBasePath(pathname: string): string {
|
|
14
|
+
if (!pathname || pathname === "/") return "";
|
|
15
|
+
const trimmed = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
16
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function addressUrl(scheme: "http" | "https", address: string): string {
|
|
20
|
+
return `${scheme}://${isIP(address) === 6 ? `[${address}]` : address}/`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function validateResolvedAddress(
|
|
24
|
+
scheme: "http" | "https",
|
|
25
|
+
address: { address: string; family: 4 | 6 },
|
|
26
|
+
allowPrivateNetwork: boolean,
|
|
27
|
+
labRunApproval: boolean,
|
|
28
|
+
): boolean {
|
|
29
|
+
const actualFamily = isIP(address.address);
|
|
30
|
+
if ((actualFamily !== 4 && actualFamily !== 6) || actualFamily !== address.family) {
|
|
31
|
+
throw new LabDestinationError("resolver returned an invalid address/family", "harness_failure");
|
|
32
|
+
}
|
|
33
|
+
const assessment = assessUrlDestination(addressUrl(scheme, address.address));
|
|
34
|
+
if (!assessment) throw new LabDestinationError("resolver address could not be classified", "harness_failure");
|
|
35
|
+
if (["metadata", "link-local", "unspecified"].includes(assessment.kind)) {
|
|
36
|
+
throw new LabDestinationError("blocked resolved destination", "harness_failure");
|
|
37
|
+
}
|
|
38
|
+
const privateAnswer = ["private", "loopback", "localhost"].includes(assessment.kind);
|
|
39
|
+
if (privateAnswer && (!allowPrivateNetwork || !labRunApproval)) {
|
|
40
|
+
throw new LabDestinationError("resolved private network requires allowPrivateNetwork and labRunApproval", "harness_failure");
|
|
41
|
+
}
|
|
42
|
+
return privateAnswer;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function canonicalAddresses(addresses: ReadonlyArray<{ address: string; family: 4 | 6 }>): Array<{ address: string; family: 4 | 6 }> {
|
|
46
|
+
const unique = new Map<string, { address: string; family: 4 | 6 }>();
|
|
47
|
+
for (const row of addresses) unique.set(`${row.family}:${row.address}`, { ...row });
|
|
48
|
+
return [...unique.values()].sort((a, b) => a.family - b.family || (a.address < b.address ? -1 : a.address > b.address ? 1 : 0));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function addressSetKey(addresses: ReadonlyArray<{ address: string; family: 4 | 6 }>): string {
|
|
52
|
+
return canonicalAddresses(addresses).map((row) => `${row.family}:${row.address}`).join(",");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function withResolutionTimeout<T>(timeoutMs: number, operation: () => Promise<T>): Promise<T> {
|
|
56
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
57
|
+
try {
|
|
58
|
+
return await Promise.race([
|
|
59
|
+
operation(),
|
|
60
|
+
new Promise<T>((_, reject) => {
|
|
61
|
+
timer = setTimeout(() => reject(new TransportError("connect_timeout", "destination resolution timed out")), timeoutMs);
|
|
62
|
+
}),
|
|
63
|
+
]);
|
|
64
|
+
} finally {
|
|
65
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface CreateDestinationOptions {
|
|
70
|
+
baseUrl: string;
|
|
71
|
+
allowPrivateNetwork?: boolean;
|
|
72
|
+
labRunApproval?: boolean;
|
|
73
|
+
resolve?: DnsResolver;
|
|
74
|
+
connectTimeoutMs?: number;
|
|
75
|
+
configDir?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Resolve once, policy-check every answer, then freeze the exact snapshot used by all later stages. */
|
|
79
|
+
export async function createLabDestination(opts: CreateDestinationOptions): Promise<LabDestinationV1> {
|
|
80
|
+
let parsed: URL;
|
|
81
|
+
try { parsed = new URL(opts.baseUrl); } catch { throw new LabDestinationError("invalid provider baseUrl", "harness_failure"); }
|
|
82
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new LabDestinationError("unsupported URL scheme", "harness_failure");
|
|
83
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash) {
|
|
84
|
+
throw new LabDestinationError("provider destination may not contain userinfo, query, or fragment", "harness_failure");
|
|
85
|
+
}
|
|
86
|
+
const scheme = parsed.protocol === "https:" ? "https" as const : "http" as const;
|
|
87
|
+
const literal = assessUrlDestination(parsed.toString());
|
|
88
|
+
if (literal && ["metadata", "link-local", "unspecified"].includes(literal.kind)) {
|
|
89
|
+
throw new LabDestinationError("blocked destination", "harness_failure");
|
|
90
|
+
}
|
|
91
|
+
const allowPrivate = opts.allowPrivateNetwork === true;
|
|
92
|
+
const approved = opts.labRunApproval === true;
|
|
93
|
+
const literalPrivate = Boolean(literal && ["private", "loopback", "localhost"].includes(literal.kind));
|
|
94
|
+
if (literalPrivate && (!allowPrivate || !approved)) {
|
|
95
|
+
throw new LabDestinationError("private network requires allowPrivateNetwork and labRunApproval", "harness_failure");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const connectTimeoutMs = opts.connectTimeoutMs ?? 10_000;
|
|
99
|
+
if (!Number.isInteger(connectTimeoutMs) || connectTimeoutMs <= 0) {
|
|
100
|
+
throw new LabDestinationError("invalid connect timeout", "harness_failure");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let addresses: Array<{ address: string; family: 4 | 6 }>;
|
|
104
|
+
let privateNetwork = literalPrivate;
|
|
105
|
+
if (opts.resolve) {
|
|
106
|
+
try { addresses = await withResolutionTimeout(connectTimeoutMs, () => opts.resolve!(parsed.hostname)); }
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (error instanceof TransportError || error instanceof LabDestinationError) throw error;
|
|
109
|
+
throw new LabDestinationError("DNS resolution failed", "network_blocked");
|
|
110
|
+
}
|
|
111
|
+
if (addresses.length === 0) throw new LabDestinationError("DNS resolution returned no addresses", "network_blocked");
|
|
112
|
+
for (const row of addresses) privateNetwork = validateResolvedAddress(scheme, row, allowPrivate, approved) || privateNetwork;
|
|
113
|
+
} else {
|
|
114
|
+
try {
|
|
115
|
+
const resolved = await withResolutionTimeout(connectTimeoutMs, () => resolvePublicAddresses(parsed.toString(), {
|
|
116
|
+
context: "Lab provider destination",
|
|
117
|
+
allowPrivateNetwork: allowPrivate && approved,
|
|
118
|
+
}));
|
|
119
|
+
addresses = resolved.addresses.map((row) => ({ address: row.address, family: row.family === 6 ? 6 as const : 4 as const }));
|
|
120
|
+
privateNetwork = resolved.privateNetwork || privateNetwork;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error instanceof TransportError || error instanceof LabDestinationError) throw error;
|
|
123
|
+
throw new LabDestinationError("DNS destination policy failed", "network_blocked");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const frozenAddresses = Object.freeze(canonicalAddresses(addresses).map((row) => Object.freeze(row)));
|
|
127
|
+
const port = parsed.port ? Number(parsed.port) : scheme === "https" ? 443 : 80;
|
|
128
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) throw new LabDestinationError("invalid destination port", "harness_failure");
|
|
129
|
+
const snapshot = {
|
|
130
|
+
scheme,
|
|
131
|
+
host: parsed.hostname.toLowerCase(),
|
|
132
|
+
port,
|
|
133
|
+
basePath: normalizeBasePath(parsed.pathname),
|
|
134
|
+
sniHost: parsed.hostname.toLowerCase(),
|
|
135
|
+
addresses: frozenAddresses,
|
|
136
|
+
privateNetwork,
|
|
137
|
+
};
|
|
138
|
+
const salt = readInstallationSalt(opts.configDir);
|
|
139
|
+
const fingerprint = localFingerprint("endpoint", {
|
|
140
|
+
scheme: snapshot.scheme, host: snapshot.host, port: snapshot.port, basePath: snapshot.basePath,
|
|
141
|
+
}, salt);
|
|
142
|
+
return Object.freeze({ ...snapshot, fingerprint });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Detect any attempted replacement/re-resolution of the approved immutable address set. */
|
|
146
|
+
export function assertDestinationAddressSet(destination: LabDestinationV1, addresses: Array<{ address: string; family: 4 | 6 }>): void {
|
|
147
|
+
if (addressSetKey(destination.addresses) !== addressSetKey(addresses)) {
|
|
148
|
+
throw new LabDestinationError("destination address set mismatch", "destination_mismatch");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function assertHostSniMatch(destination: LabDestinationV1, host: string, sni?: string): void {
|
|
153
|
+
if (host.toLowerCase() !== destination.host) throw new LabDestinationError("host mismatch", "host_sni_mismatch");
|
|
154
|
+
if (sni !== undefined && sni.toLowerCase() !== destination.sniHost) throw new LabDestinationError("SNI mismatch", "host_sni_mismatch");
|
|
155
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { evaluateAssertions } from "../conformance/assertion";
|
|
2
|
+
import { emptyObservation, finalizeObservation, setClientResponse } from "../conformance/observation";
|
|
3
|
+
import { normalizeSseBytes } from "../conformance/sse-normalize";
|
|
4
|
+
import type { CaseAuthority, CaseRecord, FailureClassification, FailureRule, NormalizedEvent, NormalizedObservation } from "../conformance/types";
|
|
5
|
+
import { domainHash, jcsStringify, scenarioManifestDigest, subjectIdForSubject, suiteManifestDigest } from "../digest";
|
|
6
|
+
import type { RouteSubjectV1 } from "../events/types";
|
|
7
|
+
import { isTrustedLabRouteExecutor } from "../../lib/lab-live-execution-authority";
|
|
8
|
+
import { buildRouteSubjectV1 } from "../subject/route-subject";
|
|
9
|
+
import { createLabDestination, LabDestinationError } from "./destination";
|
|
10
|
+
import { expandLiveScenario, loadLiveCaseAuthority } from "./manifest";
|
|
11
|
+
import { createSandboxResourceState, enforceSandboxLimits, LabSandboxError, prepareLiveSandbox } from "./sandbox";
|
|
12
|
+
import { liveSuiteManifestObjectForCase } from "./suite-manifest";
|
|
13
|
+
import { classifyTransportError, TransportError } from "./transport";
|
|
14
|
+
import type { DnsResolver, LabRouteContext, LabTransport, LiveExecutionAuthority, LiveRunConfig, LiveScenarioRunResult, TrustedLabRouteExecutor } from "./types";
|
|
15
|
+
|
|
16
|
+
export interface LiveExecutorOptions {
|
|
17
|
+
/** Host-issued exact-route capability for evidence-eligible production execution. */
|
|
18
|
+
routeExecutor?: TrustedLabRouteExecutor;
|
|
19
|
+
/** Test-only byte transport. Results are deliberately not evidence-eligible. */
|
|
20
|
+
transport?: LabTransport;
|
|
21
|
+
resolve?: DnsResolver;
|
|
22
|
+
configDir?: string;
|
|
23
|
+
env?: NodeJS.ProcessEnv;
|
|
24
|
+
/** Orchestration cancellation — must not produce route incompatibility evidence. */
|
|
25
|
+
cancelSignal?: AbortSignal;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface TrustedLiveResultReceipt {
|
|
29
|
+
authorityDigest: string;
|
|
30
|
+
scenarioId: string;
|
|
31
|
+
suiteId: string;
|
|
32
|
+
scenarioManifestDigest: string;
|
|
33
|
+
suiteManifestDigest: string;
|
|
34
|
+
routeSubjectId: string;
|
|
35
|
+
resultDigest: string;
|
|
36
|
+
retryPolicy: FailureRule["retry"] | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const TRUSTED_RESULT_RECEIPTS = new WeakMap<object, TrustedLiveResultReceipt>();
|
|
40
|
+
const LIVE_AUTHORITY_DOMAIN = "ocx-lab:live-authority:v1";
|
|
41
|
+
const TRUSTED_LIVE_RESULT_DOMAIN = "ocx-lab:trusted-live-result:v1";
|
|
42
|
+
|
|
43
|
+
function trustedResultDigest(result: LiveScenarioRunResult): string {
|
|
44
|
+
const payload = {
|
|
45
|
+
scenarioId: result.scenarioId,
|
|
46
|
+
suite: result.suite,
|
|
47
|
+
startedAt: result.startedAt,
|
|
48
|
+
completedAt: result.completedAt,
|
|
49
|
+
passed: result.passed,
|
|
50
|
+
classification: result.classification,
|
|
51
|
+
secondaryCode: result.secondaryCode ?? null,
|
|
52
|
+
assertionResults: result.assertionResults.map((row) => ({
|
|
53
|
+
id: row.id,
|
|
54
|
+
operator: row.operator,
|
|
55
|
+
required: row.required,
|
|
56
|
+
passed: row.passed,
|
|
57
|
+
observedSummary: row.observedSummary,
|
|
58
|
+
reason: row.reason ?? null,
|
|
59
|
+
})),
|
|
60
|
+
diagnostics: [...result.diagnostics],
|
|
61
|
+
routeSubject: result.routeSubject ?? null,
|
|
62
|
+
transportError: result.transportError ?? null,
|
|
63
|
+
executionAuthority: result.executionAuthority,
|
|
64
|
+
};
|
|
65
|
+
return domainHash(TRUSTED_LIVE_RESULT_DOMAIN, jcsStringify(payload));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function receiptFor(result: LiveScenarioRunResult, caseRecord: CaseRecord, authority: CaseAuthority, retryPolicy: FailureRule["retry"] | null): TrustedLiveResultReceipt {
|
|
69
|
+
if (!result.routeSubject) throw new Error("trusted live result has no route subject");
|
|
70
|
+
return {
|
|
71
|
+
authorityDigest: domainHash(LIVE_AUTHORITY_DOMAIN, jcsStringify(authority)),
|
|
72
|
+
scenarioId: caseRecord.id,
|
|
73
|
+
suiteId: caseRecord.suite,
|
|
74
|
+
scenarioManifestDigest: scenarioManifestDigest(expandLiveScenario(caseRecord, authority)),
|
|
75
|
+
suiteManifestDigest: suiteManifestDigest(liveSuiteManifestObjectForCase(caseRecord, authority)),
|
|
76
|
+
routeSubjectId: subjectIdForSubject(result.routeSubject),
|
|
77
|
+
resultDigest: trustedResultDigest(result),
|
|
78
|
+
retryPolicy,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function sealTrustedLiveResult(result: LiveScenarioRunResult, caseRecord: CaseRecord, authority: CaseAuthority, retryPolicy: FailureRule["retry"] | null): void {
|
|
83
|
+
TRUSTED_RESULT_RECEIPTS.set(result, receiptFor(result, caseRecord, authority, retryPolicy));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Verify the module-private execution receipt before any live result enters persistence. */
|
|
87
|
+
export function assertTrustedLiveResultReceipt(result: LiveScenarioRunResult, caseRecord: CaseRecord, authority: CaseAuthority): void {
|
|
88
|
+
const actual = TRUSTED_RESULT_RECEIPTS.get(result);
|
|
89
|
+
if (!actual || result.executionAuthority !== "trusted_route") {
|
|
90
|
+
throw new Error("live result lacks trusted execution receipt");
|
|
91
|
+
}
|
|
92
|
+
const expected = receiptFor(result, caseRecord, authority, actual.retryPolicy);
|
|
93
|
+
for (const key of Object.keys(expected) as Array<keyof TrustedLiveResultReceipt>) {
|
|
94
|
+
if (actual[key] !== expected[key]) throw new Error("live result trusted execution receipt mismatch");
|
|
95
|
+
}
|
|
96
|
+
if (result.scenarioId !== caseRecord.id || result.suite !== caseRecord.suite) {
|
|
97
|
+
throw new Error("live result scenario identity mismatch");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Return retryability sealed by the executor after validating the trusted receipt. */
|
|
102
|
+
export function trustedLiveResultRetryable(result: LiveScenarioRunResult, caseRecord: CaseRecord, authority: CaseAuthority): boolean {
|
|
103
|
+
assertTrustedLiveResultReceipt(result, caseRecord, authority);
|
|
104
|
+
return TRUSTED_RESULT_RECEIPTS.get(result)?.retryPolicy === "bounded";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function liveLimitsFromAuthority(authorityLimits: Record<string, number>): LiveRunConfig {
|
|
108
|
+
return {
|
|
109
|
+
totalTimeoutMs: authorityLimits.totalTimeoutMs ?? 120_000, connectTimeoutMs: authorityLimits.connectTimeoutMs ?? 10_000,
|
|
110
|
+
firstByteTimeoutMs: authorityLimits.firstByteTimeoutMs ?? 30_000, inactivityTimeoutMs: authorityLimits.inactivityTimeoutMs ?? 30_000,
|
|
111
|
+
maxRequests: authorityLimits.maxRequests ?? 16, maxInputBytes: authorityLimits.maxInputBytes ?? 8 * 1024 * 1024,
|
|
112
|
+
maxOutputBytes: authorityLimits.maxOutputBytes ?? 16 * 1024 * 1024, maxOutputTokens: authorityLimits.maxOutputTokens ?? 32_768,
|
|
113
|
+
maxToolCalls: authorityLimits.maxToolCalls ?? 32, maxMemoryBytes: authorityLimits.maxMemoryBytes ?? 512 * 1024 * 1024,
|
|
114
|
+
maxChildProcesses: authorityLimits.maxChildProcesses ?? 0, maxArtifacts: authorityLimits.maxArtifacts ?? 16,
|
|
115
|
+
perArtifactBytes: authorityLimits.perArtifactBytes ?? 256 * 1024, aggregateArtifactBytes: authorityLimits.aggregateArtifactBytes ?? 1024 * 1024,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function isLiveCaseApplicableToRoute(caseRecord: CaseRecord, route: LabRouteContext): boolean {
|
|
120
|
+
const req = caseRecord.requirements;
|
|
121
|
+
if (!req.inboundProtocols.includes(route.inboundProtocol) || !req.upstreamProtocols.includes(route.upstreamProtocol) || !req.surfaces.includes(route.surface)) return false;
|
|
122
|
+
if (req.platforms.length > 0 && !req.platforms.includes(process.platform)) return false;
|
|
123
|
+
if (!req.requiredClaims.every((claim) => (route.requiredClaims ?? []).includes(claim))) return false;
|
|
124
|
+
if (!req.requiredHarnessFeatures.every((feature) => (route.availableHarnessFeatures ?? []).includes(feature))) return false;
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function routePreconditionFailure(route: LabRouteContext, caseRecord: CaseRecord): string | null {
|
|
129
|
+
for (const precondition of caseRecord.requirements.routePreconditions) {
|
|
130
|
+
if (precondition === "lab_run_approval" && route.labRunApproval !== true) return "route_precondition_unmet:lab_run_approval";
|
|
131
|
+
if (precondition === "allow_private_network" && route.allowPrivateNetwork !== true) return "route_precondition_unmet:allow_private_network";
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function classifyWithFailureRules(rules: FailureRule[], signal: string): { classification: FailureClassification; secondaryCode: string; retryPolicy: FailureRule["retry"] | null } {
|
|
137
|
+
const rule = rules.find((row) => row.match.includes(signal));
|
|
138
|
+
return rule ? { classification: rule.classification, secondaryCode: rule.secondaryCode ?? signal, retryPolicy: rule.retry }
|
|
139
|
+
: { classification: "inconclusive", secondaryCode: "unclassified", retryPolicy: null };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function pathForProtocol(protocol: string): string {
|
|
143
|
+
if (protocol === "openai-chat") return "/chat/completions";
|
|
144
|
+
if (protocol === "anthropic-messages") return "/messages";
|
|
145
|
+
return "/responses";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Upstream HTTP path for a trusted live-route request (CL-03 / CL-08 production dispatch). */
|
|
149
|
+
export function liveUpstreamRequestPath(protocol: string): string {
|
|
150
|
+
return pathForProtocol(protocol);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function chatObservation(body: string, status: number): NormalizedObservation {
|
|
154
|
+
const observation = emptyObservation();
|
|
155
|
+
const output: unknown[] = [];
|
|
156
|
+
let text = "";
|
|
157
|
+
let terminal = false;
|
|
158
|
+
const trimmed = body.trimStart();
|
|
159
|
+
if (trimmed.startsWith("data:")) {
|
|
160
|
+
const toolParts = new Map<number, { id: string; name: string; arguments: string }>();
|
|
161
|
+
for (const frame of body.split(/\r?\n\r?\n/)) {
|
|
162
|
+
const line = frame.split(/\r?\n/).find((row) => row.startsWith("data:"));
|
|
163
|
+
if (!line) continue;
|
|
164
|
+
const payload = line.slice(5).trim();
|
|
165
|
+
if (!payload || payload === "[DONE]") { if (payload === "[DONE]") terminal = true; continue; }
|
|
166
|
+
let json: any;
|
|
167
|
+
try { json = JSON.parse(payload); } catch { continue; }
|
|
168
|
+
for (const choice of Array.isArray(json.choices) ? json.choices : []) {
|
|
169
|
+
if (typeof choice?.delta?.content === "string") text += choice.delta.content;
|
|
170
|
+
if (choice?.finish_reason != null) terminal = true;
|
|
171
|
+
for (const call of Array.isArray(choice?.delta?.tool_calls) ? choice.delta.tool_calls : []) {
|
|
172
|
+
const idx = Number.isInteger(call?.index) ? call.index : toolParts.size;
|
|
173
|
+
const prior = toolParts.get(idx) ?? { id: "", name: "", arguments: "" };
|
|
174
|
+
if (typeof call?.id === "string") prior.id = call.id;
|
|
175
|
+
if (typeof call?.function?.name === "string") prior.name = call.function.name;
|
|
176
|
+
if (typeof call?.function?.arguments === "string") prior.arguments += call.function.arguments;
|
|
177
|
+
toolParts.set(idx, prior);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (const row of [...toolParts.entries()].sort((a, b) => a[0] - b[0]).map(([, value]) => value)) {
|
|
182
|
+
output.push({ type: "function_call", call_id: row.id, name: row.name, arguments: row.arguments });
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
const json = JSON.parse(body) as any;
|
|
186
|
+
const choice = Array.isArray(json.choices) ? json.choices[0] : undefined;
|
|
187
|
+
if (typeof choice?.message?.content === "string") text = choice.message.content;
|
|
188
|
+
terminal = choice?.finish_reason != null;
|
|
189
|
+
for (const call of Array.isArray(choice?.message?.tool_calls) ? choice.message.tool_calls : []) {
|
|
190
|
+
output.push({ type: "function_call", call_id: call.id, name: call.function?.name, arguments: call.function?.arguments });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (text) output.unshift({ type: "message", content: [{ type: "output_text", text }] });
|
|
194
|
+
const json = { status: terminal ? "completed" : "incomplete", output };
|
|
195
|
+
const events: NormalizedEvent[] = terminal ? [{ event: "response.completed", data: { response: json }, ordinal: 0 }] : [];
|
|
196
|
+
finalizeObservation(observation, events, json, status);
|
|
197
|
+
return observation;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function responsesObservation(body: string, status: number): NormalizedObservation {
|
|
201
|
+
const observation = emptyObservation();
|
|
202
|
+
const trimmed = body.trimStart();
|
|
203
|
+
if (trimmed.startsWith("data:") || trimmed.startsWith("event:")) {
|
|
204
|
+
const events = normalizeSseBytes(new TextEncoder().encode(body), "openai-responses");
|
|
205
|
+
finalizeObservation(observation, events, null, status);
|
|
206
|
+
return observation;
|
|
207
|
+
}
|
|
208
|
+
const json = JSON.parse(body) as Record<string, unknown>;
|
|
209
|
+
finalizeObservation(observation, [], json, status);
|
|
210
|
+
const state = typeof json.status === "string" ? json.status : undefined;
|
|
211
|
+
if (state === "completed" || state === "failed" || state === "incomplete") {
|
|
212
|
+
setClientResponse(observation, { terminal: state === "completed" ? "completed" : state });
|
|
213
|
+
}
|
|
214
|
+
return observation;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function normalizeTransportObservation(route: LabRouteContext, response: { status: number; body: string }): NormalizedObservation {
|
|
218
|
+
const observation = route.upstreamProtocol === "openai-chat" ? chatObservation(response.body, response.status) : responsesObservation(response.body, response.status);
|
|
219
|
+
if (route.surface.startsWith("anthropic-") && observation.client.response.terminal === "completed") {
|
|
220
|
+
setClientResponse(observation, { terminal: "message_stop" });
|
|
221
|
+
}
|
|
222
|
+
return observation;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Normalize a pinned provider response into a Lab observation (trusted-route production path). */
|
|
226
|
+
export function normalizeLabLiveTransportObservation(
|
|
227
|
+
route: LabRouteContext,
|
|
228
|
+
response: { status: number; body: string },
|
|
229
|
+
): NormalizedObservation {
|
|
230
|
+
return normalizeTransportObservation(route, response);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function withTotalTimeout<T>(
|
|
234
|
+
timeoutMs: number,
|
|
235
|
+
run: (signal: AbortSignal) => Promise<T>,
|
|
236
|
+
cancelSignal?: AbortSignal,
|
|
237
|
+
): Promise<T> {
|
|
238
|
+
if (cancelSignal?.aborted) throw cancelSignal.reason ?? new TransportError("harness_failure", "cancelled");
|
|
239
|
+
const controller = new AbortController();
|
|
240
|
+
const abortFromCancel = () => controller.abort(cancelSignal?.reason ?? new TransportError("harness_failure", "cancelled"));
|
|
241
|
+
if (cancelSignal) cancelSignal.addEventListener("abort", abortFromCancel, { once: true });
|
|
242
|
+
const timer = setTimeout(() => controller.abort(new TransportError("total_timeout", "live scenario total timeout")), timeoutMs);
|
|
243
|
+
try {
|
|
244
|
+
return await Promise.race([
|
|
245
|
+
run(controller.signal),
|
|
246
|
+
new Promise<T>((_, reject) => controller.signal.addEventListener("abort", () => reject(controller.signal.reason), { once: true })),
|
|
247
|
+
]);
|
|
248
|
+
} finally {
|
|
249
|
+
clearTimeout(timer);
|
|
250
|
+
if (cancelSignal) cancelSignal.removeEventListener("abort", abortFromCancel);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export async function runLiveScenario(caseRecord: CaseRecord, routeContext: LabRouteContext, opts: LiveExecutorOptions = {}): Promise<LiveScenarioRunResult> {
|
|
255
|
+
const diagnostics: string[] = [];
|
|
256
|
+
const startedAt = Date.now();
|
|
257
|
+
let routeSubject: RouteSubjectV1 | undefined;
|
|
258
|
+
let executionAuthority: LiveExecutionAuthority = "none";
|
|
259
|
+
let authority: CaseAuthority | undefined;
|
|
260
|
+
let activeCase = caseRecord;
|
|
261
|
+
let trustedExecutionStarted = false;
|
|
262
|
+
let failureRules: FailureRule[] = [];
|
|
263
|
+
const complete = (partial: Omit<LiveScenarioRunResult, "startedAt" | "completedAt" | "executionAuthority">, retryPolicy: FailureRule["retry"] | null = null): LiveScenarioRunResult => {
|
|
264
|
+
const result: LiveScenarioRunResult = { ...partial, executionAuthority, startedAt, completedAt: Math.max(startedAt, Date.now()) };
|
|
265
|
+
if (trustedExecutionStarted && authority && result.routeSubject) sealTrustedLiveResult(result, activeCase, authority, retryPolicy);
|
|
266
|
+
return result;
|
|
267
|
+
};
|
|
268
|
+
try {
|
|
269
|
+
const environment = prepareLiveSandbox(opts.env);
|
|
270
|
+
authority = loadLiveCaseAuthority();
|
|
271
|
+
const canonicalCase = authority.cases.find((row) => row.id === caseRecord.id);
|
|
272
|
+
if (!canonicalCase || jcsStringify(canonicalCase) !== jcsStringify(caseRecord)) {
|
|
273
|
+
throw new TransportError("harness_failure", "live scenario does not match canonical authority");
|
|
274
|
+
}
|
|
275
|
+
activeCase = canonicalCase;
|
|
276
|
+
if (!isLiveCaseApplicableToRoute(activeCase, routeContext)) {
|
|
277
|
+
return complete({ scenarioId: activeCase.id, suite: activeCase.suite, passed: false, classification: "inconclusive", secondaryCode: "scenario_inapplicable", assertionResults: [], diagnostics, routeSubject });
|
|
278
|
+
}
|
|
279
|
+
const preconditionFailure = routePreconditionFailure(routeContext, activeCase);
|
|
280
|
+
if (preconditionFailure) {
|
|
281
|
+
return complete({ scenarioId: activeCase.id, suite: activeCase.suite, passed: false, classification: "inconclusive", secondaryCode: preconditionFailure, assertionResults: [], diagnostics: [preconditionFailure], routeSubject });
|
|
282
|
+
}
|
|
283
|
+
const expanded = expandLiveScenario(activeCase, authority);
|
|
284
|
+
failureRules = expanded.failureRules as FailureRule[];
|
|
285
|
+
const limits = liveLimitsFromAuthority(expanded.executionLimits as Record<string, number>);
|
|
286
|
+
const destination = await createLabDestination({ baseUrl: routeContext.baseUrl, allowPrivateNetwork: routeContext.allowPrivateNetwork, labRunApproval: routeContext.labRunApproval, resolve: opts.resolve, configDir: opts.configDir });
|
|
287
|
+
routeSubject = buildRouteSubjectV1(routeContext, destination, opts.configDir);
|
|
288
|
+
const state = createSandboxResourceState();
|
|
289
|
+
let observation: NormalizedObservation;
|
|
290
|
+
if (opts.routeExecutor) {
|
|
291
|
+
if (!isTrustedLabRouteExecutor(opts.routeExecutor)) throw new TransportError("untrusted_route_executor", "untrusted route executor capability");
|
|
292
|
+
executionAuthority = "trusted_route";
|
|
293
|
+
trustedExecutionStarted = true;
|
|
294
|
+
observation = await withTotalTimeout(limits.totalTimeoutMs, (signal) => opts.routeExecutor!.execute({ routeContext, destination, routeSubject: routeSubject!, scenarioId: activeCase.id, initiatingRequest: activeCase.initiatingRequest?.bytesUtf8, limits, signal, environment }), opts.cancelSignal);
|
|
295
|
+
} else if (opts.transport && activeCase.initiatingRequest) {
|
|
296
|
+
executionAuthority = "test_transport";
|
|
297
|
+
const body = activeCase.initiatingRequest.bytesUtf8;
|
|
298
|
+
const inputBytes = new TextEncoder().encode(body).byteLength;
|
|
299
|
+
enforceSandboxLimits(state, limits, { requests: 1, inputBytes });
|
|
300
|
+
const response = await withTotalTimeout(limits.totalTimeoutMs, (signal) => opts.transport!.request({ method: "POST", path: pathForProtocol(routeContext.upstreamProtocol), body, signal }), opts.cancelSignal);
|
|
301
|
+
if (response.status === 401 || response.status === 403) throw new TransportError("auth_blocked", `HTTP ${response.status}`);
|
|
302
|
+
if (response.status === 429) throw new TransportError("quota_blocked", "HTTP 429");
|
|
303
|
+
if (response.status === 451) throw new TransportError("region_blocked", "HTTP 451");
|
|
304
|
+
if (response.status >= 500) throw new TransportError("provider_transient", `HTTP ${response.status}`);
|
|
305
|
+
const outputBytes = new TextEncoder().encode(response.body).byteLength;
|
|
306
|
+
enforceSandboxLimits(state, limits, { outputBytes, outputTokens: Math.ceil(outputBytes / 4) });
|
|
307
|
+
observation = normalizeTransportObservation(routeContext, response);
|
|
308
|
+
} else {
|
|
309
|
+
throw new TransportError("live_transport_required", activeCase.initiatingRequest ? "live transport or trusted route executor required" : "trusted route executor required");
|
|
310
|
+
}
|
|
311
|
+
const assertionResults = evaluateAssertions(activeCase.assertions, observation);
|
|
312
|
+
const requiredFailures = assertionResults.filter((row) => row.required && !row.passed);
|
|
313
|
+
if (activeCase.expectedFailure) {
|
|
314
|
+
const matched = activeCase.expectedFailure.assertionIds.every((id) => assertionResults.find((row) => row.id === id)?.passed === true) && requiredFailures.length === 0;
|
|
315
|
+
if (matched) {
|
|
316
|
+
return complete({ scenarioId: activeCase.id, suite: activeCase.suite, passed: true, classification: activeCase.expectedFailure.expectedClass, secondaryCode: activeCase.expectedFailure.expectedCode, assertionResults, diagnostics, routeSubject });
|
|
317
|
+
}
|
|
318
|
+
const mismatch = classifyWithFailureRules(failureRules, "required_assertion_failed");
|
|
319
|
+
return complete({ scenarioId: activeCase.id, suite: activeCase.suite, passed: false, classification: mismatch.classification, secondaryCode: mismatch.secondaryCode, assertionResults, diagnostics, routeSubject }, mismatch.retryPolicy);
|
|
320
|
+
}
|
|
321
|
+
const passed = requiredFailures.length === 0;
|
|
322
|
+
const classified = passed ? { classification: "inconclusive" as FailureClassification, secondaryCode: "pass", retryPolicy: null } : classifyWithFailureRules(failureRules, "required_assertion_failed");
|
|
323
|
+
return complete({ scenarioId: activeCase.id, suite: activeCase.suite, passed, classification: passed ? "inconclusive" : classified.classification, secondaryCode: passed ? undefined : classified.secondaryCode, assertionResults, diagnostics, routeSubject }, passed ? null : classified.retryPolicy);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
const diagnosticCode = error instanceof TransportError || error instanceof LabSandboxError || error instanceof LabDestinationError
|
|
326
|
+
? error.code
|
|
327
|
+
: "execution_error";
|
|
328
|
+
diagnostics.push(diagnosticCode);
|
|
329
|
+
let classified = classifyTransportError(error);
|
|
330
|
+
if (error instanceof LabSandboxError) classified = { classification: error.code === "harness_failure" ? "harness_failure" : "budget_exhausted", secondaryCode: error.code };
|
|
331
|
+
if (error instanceof LabDestinationError) classified = { classification: error.code === "network_blocked" ? "network_failure" : "harness_failure", secondaryCode: error.code };
|
|
332
|
+
const failureSignal = error instanceof TransportError || error instanceof LabSandboxError || error instanceof LabDestinationError ? error.code : "harness_failure";
|
|
333
|
+
const retryPolicy = failureRules.find((rule) => rule.match.includes(failureSignal))?.retry ?? null;
|
|
334
|
+
return complete({ scenarioId: activeCase.id, suite: activeCase.suite, passed: false, classification: classified.classification, secondaryCode: classified.secondaryCode, assertionResults: [], diagnostics, routeSubject, transportError: error instanceof TransportError ? error.code : undefined }, retryPolicy);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface InertToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
kind: "function" | "custom";
|
|
4
|
+
description?: string;
|
|
5
|
+
parameters?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface InertToolResult {
|
|
9
|
+
output: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const LAB_TOOLS: Record<string, InertToolDefinition & { staticResult: InertToolResult }> = {
|
|
13
|
+
lookup: {
|
|
14
|
+
name: "lookup",
|
|
15
|
+
kind: "function",
|
|
16
|
+
description: "Lab inert lookup tool",
|
|
17
|
+
parameters: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: { q: { type: "string" } },
|
|
20
|
+
required: ["q"],
|
|
21
|
+
},
|
|
22
|
+
staticResult: { output: "RESULT" },
|
|
23
|
+
},
|
|
24
|
+
apply_patch: {
|
|
25
|
+
name: "apply_patch",
|
|
26
|
+
kind: "custom",
|
|
27
|
+
description: "Lab inert patch tool",
|
|
28
|
+
staticResult: { output: "PATCHED" },
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function listInertToolDefinitions(): InertToolDefinition[] {
|
|
33
|
+
return Object.values(LAB_TOOLS).map(({ staticResult: _r, ...tool }) => tool);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function validateInertToolArgs(name: string, args: unknown): void {
|
|
37
|
+
const tool = LAB_TOOLS[name];
|
|
38
|
+
if (!tool) throw new Error(`unknown inert tool: ${name}`);
|
|
39
|
+
if (tool.kind === "function") {
|
|
40
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) {
|
|
41
|
+
throw new Error("invalid function tool arguments");
|
|
42
|
+
}
|
|
43
|
+
const record = args as Record<string, unknown>;
|
|
44
|
+
if (typeof record.q !== "string") {
|
|
45
|
+
throw new Error("lookup requires string q");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Execute an inert lab tool — validate args and return static results only. */
|
|
51
|
+
export function executeInertTool(name: string, args: unknown): InertToolResult {
|
|
52
|
+
validateInertToolArgs(name, args);
|
|
53
|
+
const tool = LAB_TOOLS[name];
|
|
54
|
+
if (!tool) throw new Error(`unknown inert tool: ${name}`);
|
|
55
|
+
return { ...tool.staticResult };
|
|
56
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { fixtureDigest } from "../digest";
|
|
5
|
+
import type { CaseAuthority, CaseRecord, FailureClassification, FailureRule } from "../conformance/types";
|
|
6
|
+
import { CL03_LIVE_SUITES, SYNTHETIC_MARKER } from "../conformance/types";
|
|
7
|
+
|
|
8
|
+
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const AUTHORITY_FILE = "024_live_v1_cases.json";
|
|
10
|
+
const RUNTIME_AUTHORITY = join(MODULE_DIR, "..", "conformance", "fixtures", "live-v1-cases.json");
|
|
11
|
+
const REPO_AUTHORITY = join(MODULE_DIR, "..", "..", "..", "devlog", "_fin", "260807_compatibility_lab", AUTHORITY_FILE);
|
|
12
|
+
|
|
13
|
+
function readAuthority(path: string): CaseAuthority { return JSON.parse(readFileSync(path, "utf8")) as CaseAuthority; }
|
|
14
|
+
|
|
15
|
+
/** During source-tree execution, fail closed unless the runtime copy is byte-identical to normative 024. */
|
|
16
|
+
export function assertLiveAuthorityMirror(): void {
|
|
17
|
+
if (!existsSync(REPO_AUTHORITY)) return;
|
|
18
|
+
const runtimeBytes = readFileSync(RUNTIME_AUTHORITY);
|
|
19
|
+
const normativeBytes = readFileSync(REPO_AUTHORITY);
|
|
20
|
+
if (!runtimeBytes.equals(normativeBytes)) throw new Error("harness_failure: live authority runtime copy drift");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function loadLiveCaseAuthority(): CaseAuthority {
|
|
24
|
+
assertLiveAuthorityMirror();
|
|
25
|
+
const raw = readAuthority(RUNTIME_AUTHORITY);
|
|
26
|
+
validateLiveAuthority(raw);
|
|
27
|
+
return raw;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function discoverLiveScenarios(authority: CaseAuthority, suites: readonly string[] = CL03_LIVE_SUITES): CaseRecord[] {
|
|
31
|
+
return authority.cases.filter((row) => suites.includes(row.suite));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function expandLiveScenario(caseRecord: CaseRecord, authority: CaseAuthority): Record<string, unknown> {
|
|
35
|
+
const defaults = authority.manifestDefaults;
|
|
36
|
+
const fixtures = caseRecord.initiatingRequest ? [fixtureRef(caseRecord.initiatingRequest, authority), fixtureRef(caseRecord.fixture, authority)] : [fixtureRef(caseRecord.fixture, authority)];
|
|
37
|
+
return {
|
|
38
|
+
schemaVersion: authority.schemaVersion, id: caseRecord.id, version: defaults.version,
|
|
39
|
+
suite: { id: caseRecord.suite, version: defaults.suiteVersion, evidenceLayer: defaults.evidenceLayer },
|
|
40
|
+
evidenceLayer: defaults.evidenceLayer, capability: caseRecord.capability,
|
|
41
|
+
verificationRole: caseRecord.verificationRole ?? defaults.verificationRole, requirements: caseRecord.requirements,
|
|
42
|
+
fixtures, executionLimits: defaults.executionLimits, executionMode: defaults.executionMode, assertions: caseRecord.assertions,
|
|
43
|
+
...(caseRecord.expectedFailure ? { expectedFailure: caseRecord.expectedFailure } : {}),
|
|
44
|
+
failureRules: expandLiveFailureRules(caseRecord, authority), artifactPolicy: defaults.artifactPolicy, freshness: defaults.freshness,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function fixtureRef(fixture: CaseRecord["fixture"], authority: CaseAuthority): Record<string, unknown> {
|
|
49
|
+
const bytes = new TextEncoder().encode(fixture.bytesUtf8);
|
|
50
|
+
return { id: fixture.id, role: fixture.role, mediaType: fixture.mediaType, digest: fixture.digest, byteLength: bytes.byteLength,
|
|
51
|
+
syntheticMarker: SYNTHETIC_MARKER, provenance: { kind: "lab_authored", authority: AUTHORITY_FILE, sourceCommit: authority.sourceCommit } };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function expandLiveFailureRules(caseRecord: CaseRecord, authority: CaseAuthority): FailureRule[] {
|
|
55
|
+
const setName = authority.manifestDefaults.failureRuleSet;
|
|
56
|
+
const ruleSet = authority.failureRuleSets[setName];
|
|
57
|
+
if (!Array.isArray(ruleSet)) throw new Error(`harness_failure: contract_integrity unknown failureRuleSet ${setName}`);
|
|
58
|
+
const base = [...ruleSet];
|
|
59
|
+
if (!caseRecord.expectedFailure) return base;
|
|
60
|
+
const template = authority.expectedFailureRuleTemplate;
|
|
61
|
+
const controlRule: FailureRule = { id: template.id, match: [...template.match], classification: caseRecord.expectedFailure.expectedClass as FailureClassification,
|
|
62
|
+
secondaryCode: caseRecord.expectedFailure.expectedCode, verdictEffect: caseRecord.expectedFailure.onMatch === "unsupported" ? "unsupported" : "none",
|
|
63
|
+
retry: template.retry, expected: template.expected };
|
|
64
|
+
const idx = base.findIndex((row) => row.id === "required-assertion");
|
|
65
|
+
if (idx >= 0) base.splice(idx, 0, controlRule); else base.push(controlRule);
|
|
66
|
+
return base;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function validateLiveAuthority(authority: CaseAuthority): void {
|
|
70
|
+
if (authority.schemaVersion !== 1 || authority.manifestDefaults.evidenceLayer !== "live_route_compatibility") throw new Error("invalid live authority");
|
|
71
|
+
if (!authority.sourceCommit || !Array.isArray(authority.cases) || authority.cases.length !== 10) throw new Error("invalid live authority case set");
|
|
72
|
+
const ids = new Set<string>();
|
|
73
|
+
for (const caseRecord of authority.cases) {
|
|
74
|
+
if (ids.has(caseRecord.id)) throw new Error(`duplicate live scenario ${caseRecord.id}`);
|
|
75
|
+
ids.add(caseRecord.id);
|
|
76
|
+
if (!caseRecord.id.split(".").includes("live")) throw new Error(`non-live scenario in CL-03 authority: ${caseRecord.id}`);
|
|
77
|
+
const bytes = new TextEncoder().encode(caseRecord.fixture.bytesUtf8);
|
|
78
|
+
if (fixtureDigest(bytes) !== caseRecord.fixture.digest) throw new Error(`${caseRecord.id}: fixture digest mismatch`);
|
|
79
|
+
if (caseRecord.initiatingRequest) {
|
|
80
|
+
const initBytes = new TextEncoder().encode(caseRecord.initiatingRequest.bytesUtf8);
|
|
81
|
+
if (fixtureDigest(initBytes) !== caseRecord.initiatingRequest.digest) throw new Error(`${caseRecord.id}: initiatingRequest digest mismatch`);
|
|
82
|
+
}
|
|
83
|
+
expandLiveScenario(caseRecord, authority);
|
|
84
|
+
}
|
|
85
|
+
}
|