diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 629cacfa..270ea6c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,7 +126,7 @@ jobs: - name: Check release configuration run: node scripts/key_data_preflight.mjs --check # Bundle-only checks cannot attest remote deployment prerequisites. The - # normal Wrangler build enforces those before a real upload. + # normal Wrangler build command enforces those before a real upload. - name: Validate Knowledge Worker bundle env: WRANGLER_SEND_METRICS: "false" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c94246d9..d5a8a627 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -111,6 +111,7 @@ jobs: with: ref: ${{ needs.gate.outputs.sha }} persist-credentials: false + fetch-depth: 0 - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.11" @@ -161,7 +162,9 @@ jobs: env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - run: pnpm exec wrangler deploy --config .release-wrangler-actions.jsonc --tag "$SHA" --message "$DEPLOY_MESSAGE" + run: | + pnpm exec wrangler deploy --config .release-wrangler-actions.jsonc --containers-rollout=none --tag "$SHA" --message "$DEPLOY_MESSAGE" + pnpm exec wrangler deploy --config .release-wrangler-actions.jsonc --tag "$SHA" --message "$DEPLOY_MESSAGE" - name: Verify actual Container rollout if: vars.DEPLOY_MAIN_WORKER == 'true' env: diff --git a/README.md b/README.md index db492430..ba4f2efb 100644 --- a/README.md +++ b/README.md @@ -278,6 +278,8 @@ SQLite or PostgreSQL elsewhere). Each key can have daily and monthly dollar budg a model allowlist, an expiry time and client address ranges; the dashboard's Usage page and `GET /v1/usage` show spend and remaining budget, and request telemetry can be exported over OTLP. See [usage, budgets and key controls](docs/usage-and-budgets.md). +Keys can also restrict provider data classes and enforce request preferences; see +[key data controls and usage details](docs/key-data-guards.md). Administrators can query `GET /admin/providers/usage` from an authenticated dashboard session to combine provider-account allowances with local request, diff --git a/config.py b/config.py index b4cc27ee..e563c0bf 100644 --- a/config.py +++ b/config.py @@ -66,6 +66,7 @@ def load_env_choice( class Config: + GEMINI_DATA_TIER = os.environ.get("GEMINI_DATA_TIER", "free").strip().lower() FREE_ROUTE_EXTRA_PROVIDERS = os.environ.get('FREE_ROUTE_EXTRA_PROVIDERS', '') FREE_ROUTE_WORKERSAI_ACCOUNT_ID = os.environ.get('FREE_ROUTE_WORKERSAI_ACCOUNT_ID', '') FREE_ROUTE_FREE_TIER_PROVIDERS = os.environ.get('FREE_ROUTE_FREE_TIER_PROVIDERS', '') diff --git a/docs/deployment-cloudflare.md b/docs/deployment-cloudflare.md index 74236e4a..752eee4d 100644 --- a/docs/deployment-cloudflare.md +++ b/docs/deployment-cloudflare.md @@ -308,11 +308,12 @@ python -m pytest -q npm run test:worker npm run test:integration npm run cf:migrations:check -npx wrangler deploy --dry-run +node scripts/key_data_preflight.mjs --check +node scripts/check_worker_bundle.mjs --config wrangler.jsonc --outdir /tmp/main-worker ``` Pushing to `main` deploys; see [How deploys work](#how-deploys-work). Never deploy -with a bare `wrangler deploy`, which skips the migrations. +with a bare `wrangler deploy`: its preflight verifies prerequisites but does not apply migrations. ## How deploys work @@ -332,7 +333,10 @@ A push to `main` runs the `ci` workflow. When it passes, the `deploy` workflow first because the main Worker binds it (`KNOWLEDGE_SERVICE`) and may call what the new version adds. The main Worker and its Container go last, and only when the repository variable `DEPLOY_MAIN_WORKER` is `true` (see [Workers Builds](#workers-builds)). - Each version is tagged with the full commit SHA, and its message links to the run. + Before upload, the main build verifies the migrated account schema, required bindings + and secret names, and the private Worker's tagged expansion capability. Main activation uses + `--containers-rollout=none` first, followed by the Container rollout. Each version + is tagged with the full commit SHA, and its message links to the run. 4. **Verify.** `scripts/deploy_status.mjs --verify` retries for up to ten minutes until no migration is pending, `GET /ready` answers 200 (every D1 table exists and the Container is healthy), the Knowledge Worker's live version carries the commit, @@ -349,8 +353,9 @@ SHA. ## Migrations -Migrations are applied before the code that uses them, and with Workers Builds the new -code can briefly run before them, so the schema must work for the old and the new code: +Migrations are applied before the code that uses them. The main build refuses upload +until they are recorded and the required account columns exist. The schema must +still support old and new code during rollout: - Make only additive changes: new tables, new nullable or defaulted columns, new indexes, using `IF NOT EXISTS`. Add each new table to `REQUIRED_D1_TABLES` in @@ -442,7 +447,8 @@ Set `D1_DATABASE` explicitly to the database in both selected Worker configurati then run `npm run deploy` (`scripts/deploy.sh`). For another environment set `MAIN_WORKER_CONFIG`, `KNOWLEDGE_WORKER_CONFIG` and `DEPLOY_STATUS_URL` as well. The script checks migrations, applies them, deploys the private Worker, then the main -Worker and Container. From a clean checkout it tags both versions with the commit. +Worker with Container rollout disabled, then the Container. Preflight runs before +each main upload. From a clean checkout it tags both versions with the commit. Extra arguments go to the final deploy. Never run it concurrently with another deploy. Each release uses a fresh build-only image identifier. The command does not finish diff --git a/docs/key-controls-rollout.md b/docs/key-controls-rollout.md index 1efa694f..fcaedf4d 100644 --- a/docs/key-controls-rollout.md +++ b/docs/key-controls-rollout.md @@ -40,6 +40,20 @@ Rollback from enforcement to expansion leaves policy columns intact and refuses controlled accounts. Restore enforcement-capable components to resume those accounts; never clear restrictions as an automatic rollback step. +Enforcement advertises `key_controls: {protocol: 2, enforcement: true}`. A controlled +request probes the actual Container `/healthz` or private Knowledge `/ready` before +sending its body. Missing, stale, malformed or unreadable capability refuses with 503. +The Container independently checks the main Worker's account-RPC capability when +admitting controlled provider dispatch. The private Worker checks both current durable controls and +the sender's enforcement protocol. NULL-policy calls need no enforcement negotiation. + +Deployment preflight verifies configuration, migration ledger, policy columns, required +secret names and a fully active private deployment whose tagged source supports +expansion. The tagged commit must be available in the checkout; a shallow build must +fetch that history before release. It reads no secret values. Runtime peer readiness, +rather than the tag, governs controlled dispatch. Bundle dry runs use isolated configs +and never stand in for release admission. + ## Staging evidence and management compatibility Real staging of the earlier re-land reproduced both incident errors with mixed Worker @@ -112,4 +126,6 @@ See [Container deployment semantics](https://developers.cloudflare.com/container and [rollout controls](https://developers.cloudflare.com/containers/configuration/rollouts/). -Account reads negotiate the additive row protocol only after a successful capability probe. Old Containers receive the original account fields on list reads and NULL-policy username reads, including when a controlled neighbour exists, so dashboard cache reloads and unrestricted sign-in remain available. Legacy username and prefix authentication refuse controlled rows, since either can also feed deferred provider work. Provider forwarding and private outbound admission remain separate from management projection; an old Container cannot use a projected row to dispatch for a controlled account. A Containers request the full policy from compatible peers and retain their dormant refusal fence. A failed or malformed capability probe never downgrades to a legacy row; only the old peer's explicit unsupported-operation response permits that fallback. +In expansion, account reads negotiate the additive row protocol only after a successful capability probe. Old Containers receive the original account fields on list reads and NULL-policy username reads, including when a controlled neighbour exists, so dashboard cache reloads and unrestricted sign-in remain available. Legacy username and prefix authentication refuse controlled rows, since either can also feed deferred provider work. Provider forwarding and private outbound admission remain separate from management projection; an old Container cannot use a projected row to dispatch for a controlled account. A Containers request the full policy from compatible peers and retain their dormant refusal fence. A failed or malformed capability probe never downgrades to a legacy row; only the old peer's explicit unsupported-operation response permits that fallback. + +Contract Containers announce the expanded account-row protocol directly: every supported A/B peer accepts it. Management reads validate their policy envelope without requiring enforcement capability; the current capability is checked separately before controlled provider dispatch. diff --git a/docs/key-data-guards.md b/docs/key-data-guards.md new file mode 100644 index 00000000..bd01f690 --- /dev/null +++ b/docs/key-data-guards.md @@ -0,0 +1,201 @@ +# Key data controls + +For an app whose requests carry personal data, set controls through the existing +administrator-only `PUT /users/{username}/controls` endpoint or the user's Limits +form: + +```json +{ + "official_only": true, + "forced_provider_preferences": {"data_collection": "deny"} +} +``` + +`official_only` defaults to false. A restricted key receives HTTP 403 with +`provider_class_not_allowed` for a direct non-official provider. Auto, intelligence +and cascade routes filter concrete candidates before dispatch, including fallback +and background submissions. No eligible candidate means refusal. Model allowlists +still apply independently. Disabling a control requires an administrator; request +headers and routing overrides cannot disable it. + +`forced_provider_preferences` is an optional JSON object, limited to 4 KiB. It is +merged recursively into the `provider` object on every OpenRouter request made for +the key, including raw passthrough and internally routed requests. Administrator +values win; a caller's `data_collection: "deny"` stays `deny` even if the stored +value says `allow`. Other caller preferences remain unless overridden. See +[provider routing](https://openrouter.ai/docs/guides/routing/provider-selection) +for the supported preference fields. This control does not change another +provider's request format. + +Realtime ticket issuance and socket admission re-read current key data controls. +Restricted keys cannot open non-official models. Realtime refuses any key with +forced preferences, including an empty object, because those preferences cannot +be enforced throughout the socket session. Existing tickets do not bypass a +subsequent control change. + +Requests from keys with either data control are excluded from shadow sampling. +Stored samples recheck their source key before replay and before every candidate +or judge dispatch, including three-arm comparisons. Controlled, inactive, missing +or unverifiable source accounts are skipped; existing samples expire normally. + +Restricted Gemini requests reject caller credential overrides in query parameters, +provider headers or a separate Authorization header. Only the operator's current +`GEMINI_API_KEY` binding is covered by `GEMINI_DATA_TIER=paid`; a missing binding +fails closed, without a cached or legacy-alias fallback. The same restriction +applies to automatic fallback and background dispatch. + +## Provider classes + +`providers/data-classes.json` is shared by Flask and the Worker. Registry adapters +expose `data_class`; classification follows the configured provider namespace, +not a claim in a request or a model's name. Unknown namespaces are conservatively +`reseller`, and unknown `ce-*` namespaces are `subscription`. + +| Class | Provider namespaces | Evidence and scope | +| --- | --- | --- | +| official | openrouter, openai, anthropic, azure | Direct API endpoints in `config.py` and `providers/registry.py`; [router API](https://openrouter.ai/docs/quickstart), [direct model API](https://platform.openai.com/docs/api-reference), [Messages API](https://platform.claude.com/docs/en/api/overview), [Azure inference](https://learn.microsoft.com/en-us/azure/ai-foundry/model-inference/how-to/use-chat-completions). | +| official | groq, together, cerebras, xai | Direct inference service endpoints; [Groq](https://console.groq.com/docs/overview), [Together](https://docs.together.ai/docs/quickstart), [Cerebras](https://inference-docs.cerebras.ai/quickstart), [xAI](https://docs.x.ai/docs/overview). | +| official | scaleway, sambanova, hyperbolic | Configured service APIs; [Scaleway](https://www.scaleway.com/en/docs/generative-apis/quickstart/), [SambaNova](https://docs.sambanova.ai/docs/en/get-started/quickstart), [Hyperbolic](https://docs.hyperbolic.xyz/docs/getting-started). The current Hyperbolic overview emphasizes compute; the historical inference endpoint's availability needs operator confirmation. | +| official | googleai, cloudflare | Cloud-hosted inference endpoints; [Google Cloud](https://cloud.google.com/vertex-ai/docs/start/introduction-unified-platform), [Workers AI](https://developers.cloudflare.com/workers-ai/). | +| official or free | gemini | Defaults to `free`. Set `GEMINI_DATA_TIER=paid` only after confirming every credential used for this namespace belongs to the paid tier. [Pricing and data-use distinctions](https://ai.google.dev/gemini-api/docs/pricing). The proxy cannot discover an account's tier. | +| subscription | ce-gpt-plus, ce-gpt-pro, ce-grok-heavy, ce-claude-kiro, ce-claude-kiro-cheap, ce-claude-max, ce-image, codex-easy | Subscription/pool adapters in `providers/codex_everywhere.py`, `providers/image_relays.py` and the existing README. Pool provenance and current vendor terms remain unverified; these are excluded from restricted keys. | +| subscription | opencode, cline-pass, kimi-code, mimo | Configured coding-plan endpoints, rather than ordinary pay-as-you-go endpoints: [Go](https://opencode.ai/docs/go/), [Cline Pass](https://docs.cline.bot/getting-started/clinepass), [Kimi Code membership](https://www.kimi.com/code/docs/en/kimi-code/membership), [MiMo plan distinction](https://platform.xiaomimimo.com/static/docs/price/pay-as-you-go.md). | +| reseller | aihubmix, navyai, linkapi, nanogpt, a6api, aimlapi, ephone, gguu, gguu-grok, latix | Relay endpoints in `config.py`, `providers/image_relays.py` and the existing README. [Navy's multi-provider API](https://api.navy/docs) also uses subscription billing; the reseller tag takes precedence here. The other relays' current sourcing and data terms remain unverified. | +| reseller | chutes, nineteen | Conservative classification of distributed inference networks: [Chutes](https://chutes.ai/docs/guides/starter-guide), [Nineteen](https://nineteen.ai/app). Whether a separately reviewed deployment should qualify as official remains an operator decision. | +| free | free, gemma, palm | Explicit free pools and conservatively excluded legacy Google aliases; the latter's credential tier is not established by the adapters. | +| free | ollama, vllm, lmstudio, llamacpp, sglang | Operator-configured local inference adapters in `providers/local_inference.py`. This is a conservative exclusion within the four-class vocabulary, not a claim about hosting cost or local data handling. | + +`FREE_ROUTE_FREE_TIER_PROVIDERS` takes precedence over the table, including the +Gemini paid setting. A class describes the integration; it does not certify +retention, training policy, a data-processing agreement, or a custom base URL. +Review operator-supplied endpoints and credentials before admitting traffic. + +## Migration and usage + +Use the [expansion and enforcement rollout](key-controls-rollout.md). Apply migrations +0041–0043, deploy expansion to all three components and verify protocol 1 everywhere. +Then deploy enforcement independently, verify protocol 2 with enforcement on every +component, and only then enable restrictions. Expansion peers preserve NULL-policy +traffic and refuse controlled accounts during rollback. Repeat the tracked migration +command rather than replaying bare ALTER statements. + +The version-1 `data_policy` envelope requires `version`, `official_only` and +`forced_provider_preferences`, with explicit nulls for unrestricted values. +Bootstrap administrative recovery remains available for management, but cannot +authorise provider dispatch without a current durable account and verified policy. + +`services/provider_dispatch.py` and `worker/provider-dispatch.mjs` are the outbound +provider boundaries. HTTP adapters and SDK/binding operations reread the account +at every dispatch, including retries, fallbacks, video polls, background work, +Realtime, roleplay and Knowledge/MCP. Deferred work carries only identity; it does +not carry a policy snapshot. Stored Knowledge sources and ingestion jobs retain +their source account identity across scheduling, retries and reconciliation. +Imported-skill watches also retain the importing account; legacy imports without +that identity refuse upstream checks until explicitly imported again. +Restricted requests replace cached credentials with +an operator credential freshly resolved for an allowlisted provider API origin, +and reject caller Gemini credentials. Vertex refreshes the current service-account +binding (JSON or file) or the currently selected gcloud account, without using the +legacy process token cache. Missing, changed or malformed bindings refuse dispatch. +Public video assets still require policy admission but never receive API credentials. +Video relay and storage downloads follow at most five HTTPS redirects, rechecking +current policy at each hop. An origin change permanently strips credential headers; +unresolved redirects are errors rather than successful content. +Forced routing preferences merge at the final JSON submission. Realtime and +roleplay refuse forced preferences because their full transports cannot enforce +them. Missing accounts, missing fields, malformed envelopes and store outages +refuse dispatch. The static outbound inventory pins every runtime source module, +independently of HTTP client spelling. Suite-wide runtime guards require an active +network capability at Python's `socket.connect` audit event and Worker fetch/socket +calls, even when clients use the extension socket or tests replace fetch. Provider +SDK and index binding doubles use the shared `providerBinding` fixture, which checks +every method call, nested handle and replacement method. Admission issues a capability only for the transport +operation and revokes it on completion or failure. Mutation tests cover urllib3, +fetch-based adapters, Node HTTP, raw extension sockets and omitted SDK admission wrappers. +Private persistence, storage management, authentication, telemetry, public capability +metadata and webhook delivery are separate control traffic. Their tested network +scopes are destination-bound; local D1 fixtures register only their own disposable +loopback ports. Test gateway clients likewise scope only their local server. + +New columns hold +the two controls and provider-reported `cached_input_tokens` / `reasoning_tokens`. +Existing keys remain unrestricted and existing event details remain null; daily +totals start at zero. Flask adds the same columns to its SQLite or PostgreSQL +ledger. Missing or malformed durable controls fail closed on dispatch. + +Chat Completions and Responses usage-detail objects, including final streaming +usage, populate the counters. Native Messages cache-read and cache-creation input +counts are included in total input, while only cache reads count as cache hits. +Reasoning is a detail of output usage and is not added to output a second time. +Absent details remain unknown on individual events. Recording these counters does +not depend on the optional prompt-cache price-bucket feature. See the router's +[usage accounting](https://openrouter.ai/docs/cookbook/administration/usage-accounting). + +The Usage page shows cache-hit rate and reasoning tokens per key and model. +Cache-hit rate is reported cached input divided by all ledger input in that +group, multiplied by 100; upstreams that omit cache details lower the observed +rate. This is not a request-level cache-hit percentage. No webhook budget alert +is included. + +## Account contract and release preflight + +The Container requests `data_policy_version: 1` on private account RPC operations. +The main Worker owns `intelligence.internal/v1/users` and reads `INTELLIGENCE_DB` +directly. This path does not use `KNOWLEDGE_SERVICE` and needs no additional +policy-signing secret. Both `CONFIG_REVISION_SYNC_ENABLED` and +`STREAM_COST_BREAKER_ENABLED` default off when unset, and the Container receives +explicitly configured flag values through `collectContainerEnv`. + +A pre-expansion Container checks the exact account field set. Sending the three new +policy fields to it produces `account_storage_unavailable`. Version negotiation +therefore preserves its earlier row shape only for accounts whose current policy +is verified and unrestricted. An expansion reader receives controlled rows and refuses their provider dispatch. +Legacy writes preserve policy columns atomically and cannot erase a concurrent restriction. A current Container still refuses missing, +malformed or unsupported policy envelopes; it never negotiates down to an older +Worker's unverifiable policy. + +The integration test applies the real migrations through 0043, inserts an account +with realistic metadata and defaulted policy columns, and runs `/v1/models` and +`/v1/chat/completions` through registered Flask routes over HTTP to the main +Worker's real account handler. Only the external provider is mocked. It also +checks current controls, revocation and legacy reads/writes. Matching current +runtimes succeed with both flags unset. Subsequent real staging reproduced both +incident errors as mixed-version failures: the original new Worker with an old +Container returned `account_storage_unavailable`, and the reverse mix returned +`key_data_controls_unavailable`. Matched original runtimes were healthy on 45 of +46 probes. The first re-land fixed only the forward mix; its reverse rollback +still interrupted chat for roughly 50–55 seconds. See the expanded A/B matrix and +[staging/rollback guidance](key-controls-rollout.md#staging-evidence-and-management-compatibility). + +Reading an account for login, session refresh or listing is management, not provider +dispatch. These reads decode both expansion/enforcement policies without requiring +main-Worker enforcement capability. A restricted row cannot disable everyone else's +sign-in by poisoning a full account-cache reload. The Container checks the current +main account-RPC capability only when admitting a controlled provider dispatch; +management access never grants generation authority. + +The default Wrangler build runs `scripts/key_data_preflight.mjs --deploy` before +upload. It checks the D1 binding and migration ledger, actual policy columns, +Container binding, required secret **names**, and a fully active private Worker +whose tagged source advertises expansion support or enforcement. Runtime delegation +probes the actual peer readiness before controlled content; a release tag alone never +grants enforcement authority. Missing or unreadable prerequisites stop deployment. +It does not fetch secret values. The orchestrated deploy also runs `--prepare` +after migrations and before updating the private Worker, so missing main bindings +stop that update too. `--check` validates local configuration only; +CI uses `scripts/check_worker_bundle.mjs` after that check. The wrapper removes +the release build command only from a temporary config and always passes +`--dry-run`; it cannot upload or attest live readiness. + +Disconnect automatic promotion in Workers Builds, or use `wrangler versions upload`. +Its token permissions for the preflight remain untested. Workers Builds can deploy +the main Worker and Container without waiting for the +GitHub deploy workflow, which owns the private Knowledge Worker. First deploy +expansion everywhere and verify each component's capability. Enforcement can then +roll independently: NULL-policy traffic keeps working with expansion peers, while +controlled traffic refuses any peer below enforcement capability. Enable restrictions +only after all three report protocol 2 and enforcement. The manual script and workflow +retain the migrations, private Worker, main Worker, Container sequence; the protocol +also handles independent builds and rollback to expansion. A rollback never clears +policy columns. Already-applied migrations stay recorded and are not replayed as bare +ALTER statements. Test an ordinary durable key before enabling controls. diff --git a/package.json b/package.json index 43a630cb..830b7785 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "ruff check . --select E9,F63,F7,F82", "typecheck": "mypy --ignore-missing-imports --follow-imports=skip --disable-error-code import-untyped --disable-error-code var-annotated --disable-error-code arg-type --disable-error-code assignment --disable-error-code operator app.py routes services providers streaming", "test": "python -m pytest -q", - "test:worker": "node --import ./tests/helpers/network_guard.mjs --test tests/test_cloudflare_worker.mjs tests/test_child_safety_worker.mjs tests/test_child_safety_dashboard.mjs tests/test_runtime_feature_flags_worker.mjs tests/test_anthropic_worker.mjs tests/test_haiku_intelligence_worker.mjs tests/test_cascades_worker.mjs tests/test_cascades_dashboard.mjs tests/test_image_quality_worker.mjs tests/test_secret_scan_worker.mjs tests/test_secret_firewall_worker.mjs tests/test_secret_firewall_coverage_worker.mjs tests/test_roleplay_compaction_budget_worker.mjs tests/test_roleplay_compaction_model_worker.mjs tests/test_roleplay_stream_validation_worker.mjs tests/test_aihubmix_worker.mjs tests/test_gpt_image_moderation_worker.mjs tests/test_dashboard_scripts.mjs tests/test_operations_dashboard.mjs tests/test_opencode_reasoning_worker.mjs tests/test_opencode_thinking_worker.mjs tests/test_nanogpt_reasoning_worker.mjs tests/test_nanogpt_speed_routing_worker.mjs tests/test_sse_heartbeat_worker.mjs tests/test_roleplay_worker.mjs tests/test_roleplay_config_worker.mjs tests/test_roleplay_glm_failover_worker.mjs tests/test_roleplay_intelligence_worker.mjs tests/test_roleplay_memory_worker.mjs tests/test_roleplay_compatibility_worker.mjs tests/test_roleplay_history_reconciliation_worker.mjs tests/test_roleplay_output_contract_analysis.mjs tests/test_roleplay_output_contract_cleanup_worker.mjs tests/test_roleplay_output_contract_nonstream_worker.mjs tests/test_roleplay_output_contract_worker.mjs tests/test_roleplay_performance_worker.mjs tests/test_roleplay_quality_routing_worker.mjs tests/test_roleplay_reasoning_worker.mjs tests/test_roleplay_session_storage_worker.mjs tests/test_roleplay_transport_failure_worker.mjs tests/test_roleplay_unlimited_worker.mjs tests/test_roleplay_workbench_worker.mjs tests/test_roleplay_candidate_context_worker.mjs tests/test_roleplay_operator_worker.mjs tests/test_workbench_stream.mjs tests/test_intelligence_worker.mjs tests/test_intelligence_d1_worker.mjs tests/test_intelligence_auth_worker.mjs tests/test_intelligence_outbound_worker.mjs tests/test_intelligence_operator.mjs tests/test_control_users_worker.mjs tests/test_control_audit_worker.mjs tests/test_access_sso_worker.mjs tests/test_d1_schema_worker.mjs tests/test_shadow_eval_worker.mjs tests/test_auto_routes_worker.mjs tests/test_ai_outbound_worker.mjs tests/test_knowledge_*.mjs tests/test_control_state_worker.mjs tests/test_route_health_worker.mjs tests/test_gateway_mcp_worker.mjs tests/test_usage_ledger_worker.mjs tests/test_media_outbound_worker.mjs tests/test_media_files_worker.mjs tests/test_media_jobs_worker.mjs tests/test_tool_repair_worker.mjs tests/test_prometheus_worker.mjs tests/test_evaluation_noise_floor_worker.mjs tests/test_prompt_templates_worker.mjs tests/test_config_snapshots_worker.mjs tests/test_managed_idempotency_worker.mjs tests/test_wave3_hub_worker.mjs tests/test_wave3_transport_worker.mjs tests/test_admission_leases_worker.mjs tests/test_edge_request_metrics_worker.mjs tests/test_request_cancellation_worker.mjs tests/test_mcp_contract_drift_worker.mjs tests/test_zero_content_retention_worker.mjs tests/test_prompt_cache_cost_worker.mjs tests/test_config_revision_sync_worker.mjs tests/test_gateway_alerts_worker.mjs tests/test_session_tiers_worker.mjs tests/test_usage_reservations_worker.mjs tests/test_shared_generation_cache_worker.mjs tests/test_deferred_tools_worker.mjs tests/test_generation_deadline_worker.mjs tests/test_wave4_worker.mjs tests/test_responses_state_worker.mjs tests/test_gateway_batches_worker.mjs tests/test_semantic_generation_cache_worker.mjs tests/test_canary_traffic_worker.mjs tests/test_context_pages_worker.mjs tests/test_observability_exports_worker.mjs tests/test_prompt_injection_worker.mjs tests/test_pii_rehydration_worker.mjs tests/test_wave5_worker.mjs tests/test_usage_receipts_worker.mjs tests/test_learned_cooldown_worker.mjs tests/test_stream_cost_breaker_worker.mjs tests/test_realtime_worker.mjs tests/test_latency_slo_worker.mjs tests/test_context_canary_worker.mjs tests/test_enterprise_contracts_worker.mjs tests/test_wave6_worker.mjs tests/test_wave7_worker.mjs tests/test_payment_billing_worker.mjs tests/test_credits_ledger_worker.mjs tests/test_scim_provisioning_worker.mjs tests/test_saml_federation_worker.mjs tests/test_tenant_governance_worker.mjs tests/test_tenant_hierarchy_worker.mjs tests/test_frontend_build.mjs tests/test_deploy_pipeline.mjs tests/test_free_routes_worker.mjs tests/test_client_headers_worker.mjs tests/test_key_controls_expand_worker.mjs tests/test_key_data_preflight.mjs", + "test:worker": "node --import ./tests/helpers/network_guard.mjs --test tests/test_cloudflare_worker.mjs tests/test_child_safety_worker.mjs tests/test_child_safety_dashboard.mjs tests/test_runtime_feature_flags_worker.mjs tests/test_anthropic_worker.mjs tests/test_haiku_intelligence_worker.mjs tests/test_cascades_worker.mjs tests/test_cascades_dashboard.mjs tests/test_image_quality_worker.mjs tests/test_secret_scan_worker.mjs tests/test_secret_firewall_worker.mjs tests/test_secret_firewall_coverage_worker.mjs tests/test_roleplay_compaction_budget_worker.mjs tests/test_roleplay_compaction_model_worker.mjs tests/test_roleplay_stream_validation_worker.mjs tests/test_aihubmix_worker.mjs tests/test_gpt_image_moderation_worker.mjs tests/test_dashboard_scripts.mjs tests/test_operations_dashboard.mjs tests/test_opencode_reasoning_worker.mjs tests/test_opencode_thinking_worker.mjs tests/test_nanogpt_reasoning_worker.mjs tests/test_nanogpt_speed_routing_worker.mjs tests/test_sse_heartbeat_worker.mjs tests/test_roleplay_worker.mjs tests/test_roleplay_config_worker.mjs tests/test_roleplay_glm_failover_worker.mjs tests/test_roleplay_intelligence_worker.mjs tests/test_roleplay_memory_worker.mjs tests/test_roleplay_compatibility_worker.mjs tests/test_roleplay_history_reconciliation_worker.mjs tests/test_roleplay_output_contract_analysis.mjs tests/test_roleplay_output_contract_cleanup_worker.mjs tests/test_roleplay_output_contract_nonstream_worker.mjs tests/test_roleplay_output_contract_worker.mjs tests/test_roleplay_performance_worker.mjs tests/test_roleplay_quality_routing_worker.mjs tests/test_roleplay_reasoning_worker.mjs tests/test_roleplay_session_storage_worker.mjs tests/test_roleplay_transport_failure_worker.mjs tests/test_roleplay_unlimited_worker.mjs tests/test_roleplay_workbench_worker.mjs tests/test_roleplay_candidate_context_worker.mjs tests/test_roleplay_operator_worker.mjs tests/test_workbench_stream.mjs tests/test_intelligence_worker.mjs tests/test_intelligence_d1_worker.mjs tests/test_intelligence_auth_worker.mjs tests/test_intelligence_outbound_worker.mjs tests/test_intelligence_operator.mjs tests/test_control_users_worker.mjs tests/test_control_audit_worker.mjs tests/test_access_sso_worker.mjs tests/test_d1_schema_worker.mjs tests/test_shadow_eval_worker.mjs tests/test_auto_routes_worker.mjs tests/test_ai_outbound_worker.mjs tests/test_knowledge_*.mjs tests/test_control_state_worker.mjs tests/test_route_health_worker.mjs tests/test_gateway_mcp_worker.mjs tests/test_usage_ledger_worker.mjs tests/test_media_outbound_worker.mjs tests/test_media_files_worker.mjs tests/test_media_jobs_worker.mjs tests/test_tool_repair_worker.mjs tests/test_prometheus_worker.mjs tests/test_evaluation_noise_floor_worker.mjs tests/test_prompt_templates_worker.mjs tests/test_config_snapshots_worker.mjs tests/test_managed_idempotency_worker.mjs tests/test_wave3_hub_worker.mjs tests/test_wave3_transport_worker.mjs tests/test_admission_leases_worker.mjs tests/test_edge_request_metrics_worker.mjs tests/test_request_cancellation_worker.mjs tests/test_mcp_contract_drift_worker.mjs tests/test_zero_content_retention_worker.mjs tests/test_prompt_cache_cost_worker.mjs tests/test_config_revision_sync_worker.mjs tests/test_gateway_alerts_worker.mjs tests/test_session_tiers_worker.mjs tests/test_usage_reservations_worker.mjs tests/test_shared_generation_cache_worker.mjs tests/test_deferred_tools_worker.mjs tests/test_generation_deadline_worker.mjs tests/test_wave4_worker.mjs tests/test_responses_state_worker.mjs tests/test_gateway_batches_worker.mjs tests/test_semantic_generation_cache_worker.mjs tests/test_canary_traffic_worker.mjs tests/test_context_pages_worker.mjs tests/test_observability_exports_worker.mjs tests/test_prompt_injection_worker.mjs tests/test_pii_rehydration_worker.mjs tests/test_wave5_worker.mjs tests/test_usage_receipts_worker.mjs tests/test_learned_cooldown_worker.mjs tests/test_stream_cost_breaker_worker.mjs tests/test_realtime_worker.mjs tests/test_latency_slo_worker.mjs tests/test_context_canary_worker.mjs tests/test_enterprise_contracts_worker.mjs tests/test_wave6_worker.mjs tests/test_wave7_worker.mjs tests/test_payment_billing_worker.mjs tests/test_credits_ledger_worker.mjs tests/test_scim_provisioning_worker.mjs tests/test_saml_federation_worker.mjs tests/test_tenant_governance_worker.mjs tests/test_tenant_hierarchy_worker.mjs tests/test_frontend_build.mjs tests/test_deploy_pipeline.mjs tests/test_free_routes_worker.mjs tests/test_client_headers_worker.mjs tests/test_key_controls_expand_worker.mjs tests/test_key_data_guards_worker.mjs tests/test_usage_token_details_worker.mjs tests/test_provider_dispatch_worker.mjs tests/test_dispatch_network_worker.mjs tests/test_key_data_preflight.mjs tests/test_account_contract_worker.mjs tests/test_key_controls_contract_worker.mjs", "db:migrate": "python scripts/validate_sqlite_schema.py --apply", "db:validate": "python scripts/validate_sqlite_schema.py", "security:secrets": "python scripts/check_static_secrets.py", @@ -17,8 +17,8 @@ "cf:deploy": "bash scripts/deploy.sh", "cf:dry-run": "wrangler deploy --dry-run", "cf:tail": "wrangler tail", - "test:knowledge": "node --test tests/test_knowledge_*.mjs", - "test:integration": "node --import ./tests/helpers/network_guard.mjs --test tests/test_d1_integration.mjs tests/test_config_revision_sync_integration.mjs tests/test_key_controls_expand_worker.mjs", + "test:knowledge": "node --import ./tests/helpers/network_guard.mjs --test tests/test_knowledge_*.mjs", + "test:integration": "node --import ./tests/helpers/network_guard.mjs --test tests/test_d1_integration.mjs tests/test_config_revision_sync_integration.mjs tests/test_key_controls_expand_worker.mjs tests/test_account_envelope_integration.mjs tests/test_key_controls_contract_worker.mjs", "cf:knowledge:dry-run": "wrangler deploy --config wrangler.knowledge.jsonc --dry-run", "cf:knowledge:deploy": "wrangler deploy --config wrangler.knowledge.jsonc", "cf:migrations:check": "node scripts/verify_d1_migrations.mjs --check", diff --git a/providers/openai_compatible.py b/providers/openai_compatible.py index ae98ac4d..838aaa85 100644 --- a/providers/openai_compatible.py +++ b/providers/openai_compatible.py @@ -21,6 +21,11 @@ class OpenAICompatibleAdapter: chat_path: str = "v1/chat/completions" provider_capabilities: ProviderCapabilities = field(default_factory=ProviderCapabilities) + @property + def data_class(self) -> str: + from services.key_data_guards import provider_data_class + return provider_data_class(self.name) + def chat_completions_url(self) -> str: return f"{self.base_url.rstrip('/')}/{self.chat_path.lstrip('/')}" diff --git a/routes/auto_routes.py b/routes/auto_routes.py index 69f42372..026c4eab 100644 --- a/routes/auto_routes.py +++ b/routes/auto_routes.py @@ -179,6 +179,8 @@ def dispatch_auto_route( canary = canary_traffic.prepare_request(route, payload) candidates = canary.dispatch_order if canary is not None else route.candidates + from services.key_data_guards import eligible_models + candidates = tuple(eligible_models(candidates, getattr(g, "authenticated_user", None) or {})) order = RouteHealth.order(route.id, candidates, circuit_state=_circuit_state) if canary is not None: canary.observe(order.candidates) diff --git a/routes/cascades.py b/routes/cascades.py index a4f91df9..69baee10 100644 --- a/routes/cascades.py +++ b/routes/cascades.py @@ -177,6 +177,11 @@ def dispatch_cascade(payload, dispatch, *, timeout=120): user = getattr(g, "authenticated_user", None) or {} if not model_allowed(user, config["name"]): raise APIError("This API key is not allowed to use this cascade", 403, {"error": "model_not_allowed"}) + from services.key_data_guards import model_permitted + tiers = [tier for tier in config["tiers"] if model_permitted(user, tier["model"])] + if not tiers: + raise APIError("No eligible provider data class remains", 403, {"error": "provider_class_not_allowed"}) + config = {**config, "tiers": tiers} if "routing" in payload: raise APIError("Cascades do not accept intelligence routing overrides", 400) if payload.get("n", 1) != 1: @@ -284,7 +289,13 @@ def validate_cascade_target(app, auth, model, proxy): config = CascadeService.get_route(model) if config is None: raise APIError(f"Cascade not found: {model}", 404) - for tier in config["tiers"]: + from services.key_data_guards import has_data_controls, model_permitted + user = getattr(g, "authenticated_user", None) or {} + tiers = [tier for tier in config["tiers"] if model_permitted(user, tier["model"])] + if not tiers: + raise APIError("No eligible provider data class remains", 403, {"error": "provider_class_not_allowed"}) + denied = 0 + for tier in tiers: try: if tier["model"] == "auto:intelligence": from routes.intelligence import load_policy @@ -292,6 +303,7 @@ def validate_cascade_target(app, auth, model, proxy): if any(model_allowed(getattr(g, "authenticated_user", {}) or {}, candidate["model"]) for candidate in policy["candidates"]): return "cascade" + denied += int(has_data_controls(user)) continue if tier["model"].startswith("free:"): from services.free_model_policy import FREE_MODELS @@ -300,8 +312,13 @@ def validate_cascade_target(app, auth, model, proxy): continue validate_unified_chat_target(app, auth, tier["model"], proxy) return "cascade" - except (APIError, ValueError, GatewayError): + except APIError as error: + denied += int((error.payload or {}).get("error") == "provider_class_not_allowed") + continue + except (ValueError, GatewayError): continue + if denied == len(tiers): + raise APIError("No eligible provider data class remains", 403, {"error": "provider_class_not_allowed"}) raise APIError(f"No configured provider is available for cascade: {model}", 503) diff --git a/routes/intelligence.py b/routes/intelligence.py index 8059dffb..41ed6474 100644 --- a/routes/intelligence.py +++ b/routes/intelligence.py @@ -168,6 +168,13 @@ def dispatch_intelligence_chat(app, auth, metrics, proxy, payload): policy = {**policy, "candidates": [candidate for candidate in policy["candidates"] if judge_candidate_allowed(candidate["model"])]} + from services.key_data_guards import has_data_controls, model_permitted + user = getattr(g, "authenticated_user", None) or {} + if has_data_controls(user): + policy["candidates"] = [candidate for candidate in policy["candidates"] + if model_permitted(user, candidate["model"])] + if not policy["candidates"]: + raise GatewayError("provider_class_not_allowed", "No eligible provider data class remains.", 403) cascade_deadline = getattr(g, "cascade_deadline", None) if cascade_deadline is not None: from services.key_controls import model_allowed diff --git a/routes/proxy.py b/routes/proxy.py index c3a20a64..8ff05fd6 100644 --- a/routes/proxy.py +++ b/routes/proxy.py @@ -140,6 +140,8 @@ def proxy(api_provider: str, path: str = ""): ): api_provider = "kimi-code" raise APIError("Invalid Kimi Code path", status_code=400) + from services.key_data_guards import require_provider + require_provider(api_provider) if api_provider not in app.config["API_BASE_URLS"]: raise APIError(f"Unsupported API provider: {api_provider}", status_code=400) if api_provider == "linkapi" and any(delimiter in path for delimiter in "?#"): diff --git a/routes/unified.py b/routes/unified.py index 662cbd30..529821af 100644 --- a/routes/unified.py +++ b/routes/unified.py @@ -101,6 +101,8 @@ def _resolve_enabled_model(app, model_id: str): + from services.key_data_guards import require_provider + require_provider(model_id.split(":", 1)[0]) check_managed_candidate(model_id) provider, provider_model = ModelRegistry.parse_model_id(model_id) adapter = get_adapter(provider, app.config["API_BASE_URLS"]) @@ -269,7 +271,9 @@ def validate_unified_chat_target( route = AutoRouteService.get_route(model_id) if route is None: raise APIError(f"Auto route not found: {model_id}", status_code=404) - for candidate in route.candidates: + from services.key_data_guards import eligible_models + eligible = eligible_models(route.candidates, getattr(g, "authenticated_user", None) or {}) + for candidate in eligible: try: _validate_direct_chat_target( app, diff --git a/routes/usage.py b/routes/usage.py index 8cd57ab3..e30bfda7 100644 --- a/routes/usage.py +++ b/routes/usage.py @@ -34,7 +34,7 @@ def _range(days: int) -> tuple[str, str]: def _combined(rows: list) -> dict: - totals = {name: 0 for name in usage_store.TOTAL_COLUMNS[:7]} + totals = {name: 0 for name in usage_store.SUMMARY_COLUMNS} buckets = [0] * len(usage_store.BUCKET_COLUMNS) for row in rows: for name in totals: @@ -80,7 +80,7 @@ def api_usage(): if workspace: scoped = workspace["workspace"] return jsonify({"object": "usage", "principal": principal, "key_prefix": user.get("api_key_prefix"), - "controls": {name: controls[name] for name in ("allowed_models", "allowed_ips", "expires_at")}, + "controls": {name: controls[name] for name in ("allowed_models", "allowed_ips", "expires_at", "official_only", "forced_provider_preferences")}, "budget": {"daily_budget_usd": user.get("daily_budget_usd"), "monthly_budget_usd": user.get("monthly_budget_usd")}, "history_available": True, "range": scoped["range"], "daily": scoped["daily"], "models": scoped["models"], @@ -92,7 +92,7 @@ def api_usage(): "principal": principal, "key_prefix": user.get("api_key_prefix"), "budget": BudgetService.status(user), - "controls": {name: controls[name] for name in ("allowed_models", "allowed_ips", "expires_at")}, + "controls": {name: controls[name] for name in ("allowed_models", "allowed_ips", "expires_at", "official_only", "forced_provider_preferences")}, **usage_history(principal, _days()), **workspace, }) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 02fd227b..82728b09 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -33,5 +33,6 @@ npx --no-install wrangler d1 migrations apply "$DATABASE" --remote --config "$MA node scripts/verify_d1_migrations.mjs "$DATABASE" node scripts/key_data_preflight.mjs --prepare npx --no-install wrangler deploy --config "$KNOWLEDGE_CONFIG" ${annotations[@]+"${annotations[@]}"} +npx --no-install wrangler deploy --config "$release_config" ${annotations[@]+"${annotations[@]}"} --containers-rollout=none npx --no-install wrangler deploy --config "$release_config" ${annotations[@]+"${annotations[@]}"} "$@" node scripts/container_rollout.mjs --verify --state "$state" --config "$MAIN_CONFIG" --knowledge-config "$KNOWLEDGE_CONFIG" diff --git a/services/auth_service.py b/services/auth_service.py index 61eca671..ff0efe43 100644 --- a/services/auth_service.py +++ b/services/auth_service.py @@ -285,7 +285,7 @@ def _backfill_user_metadata(cls, connection: sqlite3.Connection) -> None: def _row_to_user(cls, row: sqlite3.Row) -> Dict[str, Any]: from services.dispatch_policy import envelope, verify return { - "data_policy": verify(row["data_policy"]) if dict(row).get("data_policy") is not None else envelope({"official_only": None, "forced_provider_preferences": None, **dict(row)}), + "data_policy": verify(row["data_policy"]) if dict(row).get("data_policy") is not None else envelope(row), "username": row["username"], "api_key_hash": row["api_key_hash"], "api_key_prefix": row["api_key_prefix"], @@ -299,7 +299,9 @@ def _row_to_user(cls, row: sqlite3.Row) -> Dict[str, Any]: "rotated_at": deserialize_datetime(row["rotated_at"]), "revoked_at": deserialize_datetime(row["revoked_at"]), **stream_cost_breaker.storage_control(row), - **key_controls.from_storage(row), + **key_controls.from_storage( + {name: key_controls.row_value(row, name) for name in key_controls.CONTROL_FIELDS} + ), } @classmethod @@ -463,7 +465,9 @@ def _persist_user( allowed_ips = excluded.allowed_ips, expires_at = excluded.expires_at, secret_scan_mode = excluded.secret_scan_mode, - shadow_eval_rate = excluded.shadow_eval_rate + shadow_eval_rate = excluded.shadow_eval_rate, + official_only = excluded.official_only, + forced_provider_preferences = excluded.forced_provider_preferences """, ( username, @@ -479,7 +483,6 @@ def _persist_user( serialize_datetime(rotated_at), serialize_datetime(revoked_at), *(stored_controls[name] for name in key_controls.CONTROL_FIELDS), - None, None, ), ) stream_cost_breaker.persist_sql_cap(connection, username, stored_controls) @@ -920,6 +923,7 @@ def _public_user(cls, username: str, user: Dict[str, Any]) -> Dict[str, Any]: "rotated_at": serialize_datetime(user["rotated_at"]), "revoked_at": serialize_datetime(user["revoked_at"]), **key_controls.public(user), + "data_policy": user.get("data_policy"), } @staticmethod diff --git a/services/control_plane_backup.py b/services/control_plane_backup.py index 04bc1971..59848ca3 100644 --- a/services/control_plane_backup.py +++ b/services/control_plane_backup.py @@ -27,7 +27,7 @@ } ADDITIVE_TABLES = {"intelligence_policy", "intelligence_reservations"} # Columns added later; older backups omit them and restore them as NULL. -OPTIONAL_FIELDS = {"users": ("daily_budget_usd", "monthly_budget_usd", "allowed_models", "allowed_ips", "expires_at", "secret_scan_mode", "shadow_eval_rate")} +OPTIONAL_FIELDS = {"users": ("daily_budget_usd", "monthly_budget_usd", "allowed_models", "allowed_ips", "expires_at", "secret_scan_mode", "shadow_eval_rate", "official_only", "forced_provider_preferences")} STORES = { "auth": ("AUTH_DB_PATH", "auth.sqlite3"), "models": ("MODEL_REGISTRY_DB_PATH", "model_registry.sqlite3"), diff --git a/services/key_controls.py b/services/key_controls.py index 017ee8b0..921f9127 100644 --- a/services/key_controls.py +++ b/services/key_controls.py @@ -18,10 +18,10 @@ from werkzeug.security import check_password_hash from error_handlers import APIError -from services import user_store, stream_cost_breaker +from services import user_store, stream_cost_breaker, key_data_guards from services.auth_primitives import build_api_key_prefix, normalized_username -CONTROL_FIELDS = ("daily_budget_usd", "monthly_budget_usd", "allowed_models", "allowed_ips", "expires_at", "secret_scan_mode", "shadow_eval_rate") +CONTROL_FIELDS = ("daily_budget_usd", "monthly_budget_usd", "allowed_models", "allowed_ips", "expires_at", "secret_scan_mode", "shadow_eval_rate", "official_only", "forced_provider_preferences") MAX_BUDGET_USD = 1_000_000_000 MAX_PATTERNS = 64 MAX_RANGES = 64 @@ -148,6 +148,7 @@ def validate(payload: Mapping[str, Any]) -> dict[str, Any]: "secret_scan_mode": mode, "shadow_eval_rate": rate, **stream_cost_breaker.validated_control(payload), + **key_data_guards.validate_controls(payload), } @@ -164,6 +165,7 @@ def from_storage(row: Mapping[str, Any]) -> dict[str, Any]: rate = row.get("shadow_eval_rate") if hasattr(row, "get") else row_value(row, "shadow_eval_rate") if type(rate) in (int, float) and 0 <= rate <= 0.2: controls["shadow_eval_rate"] = float(rate) + controls.update(key_data_guards.stored_controls(row)) controls.update(stream_cost_breaker.storage_control(row)) return controls @@ -187,6 +189,8 @@ def public(controls: Mapping[str, Any]) -> dict[str, Any]: "secret_scan_mode": controls.get("secret_scan_mode"), "shadow_eval_rate": controls.get("shadow_eval_rate"), **stream_cost_breaker.public_control(controls), + "official_only": bool(controls.get("official_only")), + "forced_provider_preferences": key_data_guards.preferences(controls.get("forced_provider_preferences")), } @@ -210,6 +214,8 @@ def _glob(pattern: str) -> re.Pattern: def model_allowed(user: Mapping[str, Any], model: Any) -> bool: """Whether a model ID matches the key's allowlist; ``*`` matches any characters.""" + if not key_data_guards.model_permitted(user, model): + return False patterns = model_patterns(user) if patterns is None: allowed = True @@ -224,6 +230,8 @@ def model_allowed(user: Mapping[str, Any], model: Any) -> bool: def provider_allowed(user: Mapping[str, Any], provider: str) -> bool: """Whether a request that names no model may reach a provider namespace.""" + if not key_data_guards.model_permitted(user, provider + ":*"): + return False patterns = model_patterns(user) if patterns is None: allowed = True @@ -293,9 +301,11 @@ def client_ip(request) -> Optional[str]: def _security_user(auth, row): """Reject malformed authority records before permissive legacy decoding.""" + if not isinstance(row, dict) or not set(user_store.USER_FIELDS).issubset(row): + raise ValueError("Incomplete security account") + row = stream_cost_breaker.d1_row(row) if not isinstance(row, dict) or set(row) != set(user_store.USER_FIELDS) | ({stream_cost_breaker.CAP_FIELD} if stream_cost_breaker.enabled() else set()): raise ValueError("Incomplete security account") - stream_cost_breaker.d1_row(row) if normalized_username(row["username"]) != row["username"]: raise ValueError("Invalid security account identity") if not row["api_key_hash"] or not row["api_key_prefix"] or not re.fullmatch(r"[A-Za-z0-9:_.,-]{0,512}", row["scopes"]): @@ -308,7 +318,11 @@ def _security_user(auth, row): models = row["allowed_models"] if models is not None and (not models or not all(MODEL_PATTERN.fullmatch(item) for item in models.split(","))): raise ValueError("Invalid security model grants") - controls = validate({name: row[name] for name in CONTROL_FIELDS}) + payload = {name: row[name] for name in CONTROL_FIELDS} + if payload["official_only"] is not None: + payload["official_only"] = bool(payload["official_only"]) + payload["forced_provider_preferences"] = key_data_guards.preferences(payload["forced_provider_preferences"]) + controls = validate(payload) if any(row[name] is not None and controls[name] is None for name in CONTROL_FIELDS): raise ValueError("Invalid security account controls") return auth._row_to_user(row) diff --git a/services/key_controls_capability.py b/services/key_controls_capability.py index 09cd3e71..368d7a7a 100644 --- a/services/key_controls_capability.py +++ b/services/key_controls_capability.py @@ -1,5 +1,5 @@ """The code version advertises rollout support; configuration cannot turn it on.""" -CAPABILITY = {"protocol": 1, "enforcement": False} +CAPABILITY = {"protocol": 2, "enforcement": True} def policy_present(policy): diff --git a/services/key_data_guards.py b/services/key_data_guards.py index 3acad499..4dee0802 100644 --- a/services/key_data_guards.py +++ b/services/key_data_guards.py @@ -97,9 +97,7 @@ def allows_shadow_sampling(user): def model_permitted(user, model, config=None): - from services.key_controls_capability import expansion_fence policy = account_controls(user) if "data_policy" in user else user - expansion_fence(policy) if policy.get("official_only") not in (1, True): return True provider = str(model or "").split(":", 1)[0].lower() diff --git a/services/peer_capability.py b/services/peer_capability.py new file mode 100644 index 00000000..ed8ad1de --- /dev/null +++ b/services/peer_capability.py @@ -0,0 +1,8 @@ +"""Current peer code capability is required before controlled delegation.""" +from services.dispatch_policy import unavailable + + +def require_enforcement(capability): + if (not isinstance(capability, dict) or type(capability.get("protocol")) is not int + or capability["protocol"] < 2 or capability.get("enforcement") is not True): + raise unavailable() diff --git a/services/prompt_cache_cost.py b/services/prompt_cache_cost.py index ab78ea62..7fd9b7ec 100644 --- a/services/prompt_cache_cost.py +++ b/services/prompt_cache_cost.py @@ -89,12 +89,12 @@ def from_body(cls, value: Any) -> CacheObservation | None: key in usage for key in ("cache_read_input_tokens", "cache_creation_input_tokens")) details = usage.get("prompt_tokens_details", usage.get("input_tokens_details")) details = details if isinstance(details, dict) else {} - raw = count(typed.input_tokens) + raw = count(usage.get("input_tokens")) if anthropic else count(typed.input_tokens) read = count(usage.get("cache_read_input_tokens")) if anthropic else count(details.get("cached_tokens")) write = count(usage.get("cache_creation_input_tokens")) if anthropic else count(details.get("cache_write_tokens", 0 if raw is not None or details else None)) source = "anthropic" if anthropic else "openai" if raw is not None or details else "unknown" total = _sum((raw, read, write)) if anthropic else raw - return cls(total, count(typed.output_tokens), typed.basis, typed.provenance, raw, read, write, source) + return cls(total, count(typed.output_tokens), typed.basis, typed.provenance, raw, read, write, source, cached_input_tokens=typed.cached_input_tokens, reasoning_tokens=typed.reasoning_tokens) def merge(self, later: UsageObservation) -> CacheObservation: if not isinstance(later, CacheObservation): @@ -110,7 +110,8 @@ def latest(old, new): output = latest(self.output_tokens, later.output_tokens) known = any(v is not None for v in (raw, read, write, output)) return CacheObservation(total, output, "provider" if known else "unknown", ("provider",) if known else (), - raw, read, write, source) + raw, read, write, source, cached_input_tokens=read, + reasoning_tokens=later.reasoning_tokens if later.reasoning_tokens is not None else self.reasoning_tokens) def buckets_from(usage: UsageObservation | None) -> dict: diff --git a/services/provider_assets.py b/services/provider_assets.py index 3f0698b4..da5836b2 100644 --- a/services/provider_assets.py +++ b/services/provider_assets.py @@ -9,9 +9,6 @@ def asset_http(provider, url, *, headers=None, reference=None, **options): - from services.key_controls_capability import CAPABILITY - if not CAPABILITY["enforcement"]: - return provider_http(provider, "GET", url, headers=dict(headers or {}), reference=reference, **options) # Only documented authenticated content APIs may start with credentials. # Status-returned xAI and private-binding assets are always public downloads. authenticated = provider in {"openai", "gemini"} and api_origin(provider, url) diff --git a/services/provider_dispatch.py b/services/provider_dispatch.py index b9bc0281..06dfabfe 100644 --- a/services/provider_dispatch.py +++ b/services/provider_dispatch.py @@ -17,7 +17,6 @@ from services.provider_credentials import api_origin, operator_credential, strip_credentials, unavailable_credential from services.dispatch_network import admitted_network from services.dispatch_policy import unavailable, verify -from services.key_controls_capability import CAPABILITY, expansion_fence from services.key_data_guards import has_data_controls, merge_preferences, model_permitted, preferences _reference = ContextVar("provider_principal", default=None) @@ -51,25 +50,28 @@ def current_principal(reference=None): raise unavailable() if str(reference["username"]).startswith("integration:"): from services.intelligence_d1_store import request_private_intelligence - response = request_private_intelligence({"version": 1, "operation": "lookup", - "keyPrefix": reference.get("api_key_prefix")}, endpoint="auth") - row = response.get("principal") if isinstance(response, dict) else None - if not row or row.get("id") != reference["username"] or row.get("revokedAt"): + response = request_private_intelligence({"version": 1, "operation": "dispatch_policy", + "principalId": reference["username"]}, endpoint="auth") + if not isinstance(response, dict) or response.get("version") != 1 or not {"version", "principal"}.issubset(response): raise unavailable() - user = {"username": row["id"], "api_key_prefix": row["keyPrefix"], - "data_policy": {"version": 1, "official_only": None, "forced_provider_preferences": None}} - + user = response["principal"] + from services.peer_capability import require_enforcement + if user and has_data_controls(verify(user.get("data_policy"))): + require_enforcement(response.get("key_controls")) else: user = AuthService._load_user_by_username(reference["username"]) - if not user or CAPABILITY["enforcement"] and (user.get("revoked_at") or expired(user)): + if not user or user.get("revoked_at") or expired(user): raise unavailable() if reference.get("api_key_prefix") and reference["api_key_prefix"] != user.get("api_key_prefix"): raise unavailable() policy = verify(user.get("data_policy")) - expansion_fence(policy) + if has_data_controls(policy) and not str(reference["username"]).startswith("integration:"): + from services import user_store + if user_store.using_d1(): + from services.peer_capability import require_enforcement + capability = user_store.request_private_intelligence({"operation": "capabilities"}, endpoint="users") + require_enforcement(capability.get("key_controls")) return {**user, **policy} - except APIError: - raise except Exception: raise unavailable() from None @@ -123,8 +125,6 @@ def _credential(provider, user, url, options, *, public_asset=False): def prepare_dispatch(provider, url, options, *, reference=None, binding=False, public_asset=False): user = current_principal(reference) - if not CAPABILITY["enforcement"]: - return dict(options) if not binding and not model_permitted(user, provider + ":*"): raise APIError("This API key cannot use this provider data class", 403, {"error": "provider_class_not_allowed"}) options = _credential(provider, user, url, dict(options), public_asset=public_asset) @@ -139,9 +139,10 @@ def prepare_dispatch(provider, url, options, *, reference=None, binding=False, p body = {**body, "provider": merge_preferences(body.get("provider"), forced)} options = {**options, **({"json": body} if "json" in options else {"data": json.dumps(body).encode()})} headers = dict(options.get("headers") or {}) - headers = {name: value for name, value in headers.items() if name.lower() not in {"content-length", "x-multillm-dispatch-principal"}} - if binding and CAPABILITY["enforcement"]: + headers = {name: value for name, value in headers.items() if name.lower() not in {"content-length", "x-multillm-dispatch-principal", "x-multillm-key-controls"}} + if binding: headers["X-MultiLLM-Dispatch-Principal"] = json.dumps({"version": 1, "username": user["username"]}) + headers["X-MultiLLM-Key-Controls"] = "2" return {**options, "headers": headers} @@ -149,7 +150,6 @@ def provider_http(provider, method, url, *, transport=None, reference=None, bind options = prepare_dispatch(provider, url, options, reference=reference, binding=binding, public_asset=public_asset) # Redirects are separate dispatches and must never forward an attested credential # to a provider-selected host without another admission. - if CAPABILITY["enforcement"]: - options["allow_redirects"] = False + options["allow_redirects"] = False with admitted_network(): return (transport or requests.request)(method=method, url=url, **options) diff --git a/services/request_accounting.py b/services/request_accounting.py index e026251e..a638320b 100644 --- a/services/request_accounting.py +++ b/services/request_accounting.py @@ -271,6 +271,9 @@ def begin() -> Optional[Response]: return None payload = _payload() models, provider = _requested_models(kind, payload) + from services.key_data_guards import model_permitted + if any(not model_permitted(user, model) for model in models) or (provider and not model_permitted(user, provider + ":*")): + return _error(403, "provider_class_not_allowed", "This API key cannot use this provider data class.") for model in models: if not key_controls.model_allowed(user, model): return _error(403, "model_not_allowed", @@ -511,8 +514,7 @@ def _record(context: UsageContext, status: int, usage: Optional[UsageObservation def _row(context: UsageContext, status: int, usage: Optional[UsageObservation], units: Optional[int]) -> dict: if usage is not None and context.path == "/v1/embeddings": - usage = (replace(usage, output_tokens=0) if isinstance(usage, prompt_cache_cost.CacheObservation) - else UsageObservation(usage.input_tokens, 0, "provider", ("provider",))) + usage = replace(usage, output_tokens=0) qualified = [_qualified(model, context.provider) for model in context.models] requested = qualified[0] if len(qualified) == 1 else ("mixed" if qualified else None) selected = context.selected or (requested if requested and requested != "mixed" @@ -550,6 +552,8 @@ def _row(context: UsageContext, status: int, usage: Optional[UsageObservation], "latency_ms": min(86_400_000, max(0, round((time.perf_counter() - context.started) * 1000))), "input_tokens": usage.input_tokens if usage is not None else None, "output_tokens": usage.output_tokens if usage is not None else None, + "cached_input_tokens": usage.cached_input_tokens if usage is not None else None, + "reasoning_tokens": usage.reasoning_tokens if usage is not None else None, "cost_usd": cost, "cost_basis": basis, "request_id": context.request_id, diff --git a/services/shadow_eval_data_policy.py b/services/shadow_eval_data_policy.py index 8d073c82..f5849715 100644 --- a/services/shadow_eval_data_policy.py +++ b/services/shadow_eval_data_policy.py @@ -6,12 +6,14 @@ def source_allows_replay(sample): from services.auth_service import AuthService + from services.key_controls import expired try: user = AuthService._load_user_by_username(sample["key_id"]) from services.dispatch_policy import verify policy = verify(user.get("data_policy") if user else None) - return bool(user and not has_data_controls(policy)) + return bool(user and not user.get("revoked_at") and not expired(user) + and not has_data_controls(policy)) except Exception: # Missing, malformed or unavailable authority cannot authorize content replay. return False @@ -20,7 +22,7 @@ def source_allows_replay(sample): def guarded_dispatch(sample, dispatch): def guarded(payload): if not source_allows_replay(sample): - raise APIError("Source key does not permit shadow replay", 503, + raise APIError("Source key does not permit shadow replay", 403, {"error": "shadow_source_not_allowed"}) from services.provider_dispatch import dispatch_as with dispatch_as({"username": sample["key_id"]}): diff --git a/services/usage_store.py b/services/usage_store.py index b98c134a..ed7a3ba9 100644 --- a/services/usage_store.py +++ b/services/usage_store.py @@ -23,10 +23,13 @@ LATENCY_BUCKETS = (250, 500, 1000, 2000, 4000, 8000, 15000, 30000, 60000, 120000) BUCKET_COLUMNS = tuple(f"lat_le_{bound}" for bound in LATENCY_BUCKETS) + ("lat_gt_120000",) ROW_FIELDS = ("at", "principal", "key_prefix", "kind", "endpoint", "requested_model", "selected_model", - "status", "latency_ms", "input_tokens", "output_tokens", "cost_usd", "cost_basis", "request_id") + "status", "latency_ms", "input_tokens", "output_tokens", "cost_usd", "cost_basis", "request_id", "cached_input_tokens", "reasoning_tokens") EVENT_COLUMNS = ("id",) + ROW_FIELDS TOTAL_COLUMNS = ("requests", "errors", "input_tokens", "output_tokens", "cost_usd", "priced_requests", "latency_ms_total") + BUCKET_COLUMNS +DETAIL_COLUMNS = ("cached_input_tokens", "reasoning_tokens") +TOTAL_COLUMNS += DETAIL_COLUMNS +SUMMARY_COLUMNS = TOTAL_COLUMNS[:7] + DETAIL_COLUMNS GROUPS = ("day", "model", "principal") MAX_PAGE = 500 _BATCH_ID = re.compile(r"[0-9a-f]{32}\Z") @@ -76,6 +79,7 @@ def summarize(row: dict) -> dict: latency_avg_ms=round(int(row.get("latency_ms_total") or 0) / requests) if requests else None, latency_p50_ms=percentile(buckets, 0.5), latency_p95_ms=percentile(buckets, 0.95), + cache_hit_rate=round(100 * int(row.get("cached_input_tokens") or 0) / int(row.get("input_tokens") or 1), 1), ) return result @@ -131,7 +135,7 @@ def totals(self, principal: str, day: str, month_start: str) -> dict: def summary(self, group: str, since: str, until: str, principal: Optional[str], limit: int) -> list[dict]: response = self._call("summary", group=group, since=since, until=until, principal=principal, limit=limit) - fields = {group, *TOTAL_COLUMNS[:7], "latency_buckets"} + fields = {group, *SUMMARY_COLUMNS, "latency_buckets"} return _checked_rows(response.get("rows") if set(response) == {"version", "rows"} else None, fields) def recent(self, since: str, principal: Optional[str], before: Optional[int], limit: int) -> list[dict]: @@ -237,9 +241,24 @@ def ensure(connection) -> None: """ ) connection.execute("CREATE INDEX IF NOT EXISTS idx_usage_daily_principal ON usage_daily(principal, day)") + SqlUsageStore.ensure_details(connection) if prompt_cache_cost.enabled(): SqlUsageStore.ensure_buckets(connection) + @staticmethod + def ensure_details(connection) -> None: + for table in ("usage_events", "usage_daily"): + if getattr(connection, "dialect", None) == "postgresql": + columns = {row["name"] for row in connection.execute( + "SELECT column_name AS name FROM information_schema.columns " + "WHERE table_schema = 'multillm' AND table_name = ?", (table,))} + else: + columns = {row[1] for row in connection.execute(f"PRAGMA table_info({table})")} # nosec B608 + for name in DETAIL_COLUMNS: + if name not in columns: + suffix = " NOT NULL DEFAULT 0" if table == "usage_daily" else "" + connection.execute(f"ALTER TABLE {table} ADD COLUMN {name} INTEGER{suffix}") # nosec B608 + def record(self, batch_id: str, rows: list[dict]) -> int: if not _BATCH_ID.fullmatch(batch_id): raise UsageStoreError("invalid batch") @@ -255,6 +274,8 @@ def record(self, batch_id: str, rows: list[dict]) -> int: totals[5] += int(row["cost_usd"] is not None) totals[6] += row["latency_ms"] totals[7 + latency_bucket(row["latency_ms"])] += 1 + for name in DETAIL_COLUMNS: + totals[TOTAL_COLUMNS.index(name)] += row.get(name) or 0 row_fields = ROW_FIELDS + (prompt_cache_cost.ROW_FIELDS if prompt_cache_cost.enabled() else ()) columns = ", ".join(TOTAL_COLUMNS) updates = ", ".join(f"{name} = usage_daily.{name} + excluded.{name}" for name in TOTAL_COLUMNS) @@ -264,7 +285,7 @@ def record(self, batch_id: str, rows: list[dict]) -> int: connection.executemany( f"INSERT INTO usage_events (day, {', '.join(row_fields)}) " # nosec B608 f"VALUES ({', '.join('?' for _ in range(len(row_fields) + 1))})", - [(row["at"][:10], *(row[name] if name in ROW_FIELDS else row.get(name) for name in row_fields)) for row in rows], + [(row["at"][:10], *(row.get(name) for name in row_fields)) for row in rows], ) # Column names come only from the fixed TOTAL_COLUMNS definition. connection.executemany( @@ -311,7 +332,7 @@ def summary(self, group: str, since: str, until: str, principal: Optional[str], f"GROUP BY {group} ORDER BY {order}, {group} LIMIT ?", parameters, ) - return [{group: row[group], **{name: row[name] or 0 for name in TOTAL_COLUMNS[:7]}, + return [{group: row[group], **{name: row[name] or 0 for name in SUMMARY_COLUMNS}, "latency_buckets": [int(row[name] or 0) for name in BUCKET_COLUMNS]} for row in rows] def recent(self, since: str, principal: Optional[str], before: Optional[int], limit: int) -> list[dict]: diff --git a/services/usage_types.py b/services/usage_types.py index 1ec3936b..32d32f7a 100644 --- a/services/usage_types.py +++ b/services/usage_types.py @@ -3,7 +3,7 @@ from __future__ import annotations import json -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Any, Literal UsageBasis = Literal["provider", "estimated", "unknown"] @@ -19,6 +19,8 @@ class UsageObservation: output_tokens: int | None = None basis: UsageBasis = "unknown" provenance: tuple[str, ...] = () + cached_input_tokens: int | None = field(default=None, kw_only=True) + reasoning_tokens: int | None = field(default=None, kw_only=True) @classmethod def from_body(cls, value: Any) -> UsageObservation | None: @@ -35,12 +37,21 @@ def from_body(cls, value: Any) -> UsageObservation | None: return None input_tokens = _component(usage.get("prompt_tokens", usage.get("input_tokens"))) output_tokens = _component(usage.get("completion_tokens", usage.get("output_tokens"))) + input_details = usage.get("prompt_tokens_details", usage.get("input_tokens_details")) + output_details = usage.get("completion_tokens_details", usage.get("output_tokens_details")) + cached = _component(input_details.get("cached_tokens")) if isinstance(input_details, dict) else None + reasoning = _component(output_details.get("reasoning_tokens")) if isinstance(output_details, dict) else None + if "cache_read_input_tokens" in usage or "cache_creation_input_tokens" in usage: + cached = _component(usage.get("cache_read_input_tokens")) + written = _component(usage.get("cache_creation_input_tokens")) + if input_tokens is not None: + input_tokens += (cached or 0) + (written or 0) data = value.get("data") if isinstance(data, list) and any(isinstance(item, dict) and "embedding" in item for item in data): output_tokens = 0 known = input_tokens is not None or output_tokens is not None return cls(input_tokens, output_tokens, "provider" if known else "unknown", - ("provider",) if known else ()) + ("provider",) if known else (), cached_input_tokens=cached, reasoning_tokens=reasoning) def merge(self, later: UsageObservation) -> UsageObservation: """Use the latest valid value for each component; null never erases a count.""" @@ -49,7 +60,9 @@ def merge(self, later: UsageObservation) -> UsageObservation: provenance = tuple(dict.fromkeys((*self.provenance, *later.provenance))) basis: UsageBasis = "estimated" if "request_estimate" in provenance else ( "provider" if input_tokens is not None or output_tokens is not None else "unknown") - return UsageObservation(input_tokens, output_tokens, basis, provenance) + return UsageObservation(input_tokens, output_tokens, basis, provenance, + cached_input_tokens=later.cached_input_tokens if later.cached_input_tokens is not None else self.cached_input_tokens, + reasoning_tokens=later.reasoning_tokens if later.reasoning_tokens is not None else self.reasoning_tokens) def with_estimates(self, input_tokens: int | None, output_tokens: int | None) -> UsageObservation: """Fill missing components explicitly, retaining measured zero and provenance.""" @@ -59,7 +72,8 @@ def with_estimates(self, input_tokens: int | None, output_tokens: int | None) -> return self return UsageObservation(self.input_tokens if self.input_tokens is not None else estimated_input, self.output_tokens if self.output_tokens is not None else estimated_output, - "estimated", tuple(dict.fromkeys((*self.provenance, "request_estimate")))) + "estimated", tuple(dict.fromkeys((*self.provenance, "request_estimate"))), + cached_input_tokens=self.cached_input_tokens, reasoning_tokens=self.reasoning_tokens) def storage_basis(self, cost: float | None) -> str | None: """Map internal provenance to the existing strict SQL/D1 cost-basis vocabulary.""" diff --git a/services/user_store.py b/services/user_store.py index e9198289..80215752 100644 --- a/services/user_store.py +++ b/services/user_store.py @@ -20,11 +20,11 @@ "last_login", "last_used_at", "last_used_ip", "created_by", "rotated_at", "revoked_at", # Per-key controls (migration 0007); NULL means no budget, every model, no expiry, any address. "daily_budget_usd", "monthly_budget_usd", "allowed_models", "allowed_ips", "expires_at", "secret_scan_mode", - "shadow_eval_rate", + "shadow_eval_rate", "official_only", "forced_provider_preferences", ) _REQUIRED_TEXT = ("username", "api_key_hash", "api_key_prefix", "scopes", "created_at") _OPTIONAL_TEXT = ("last_login", "last_used_at", "last_used_ip", "created_by", "rotated_at", "revoked_at", - "allowed_models", "allowed_ips", "expires_at", "secret_scan_mode") + "allowed_models", "allowed_ips", "expires_at", "secret_scan_mode", "forced_provider_preferences") _OPTIONAL_NUMBER = ("daily_budget_usd", "monthly_budget_usd", "shadow_eval_rate") PAGE_SIZE = 200 READ_OPERATIONS = frozenset({"list", "get", "by_prefix"}) @@ -52,33 +52,16 @@ def _transient(error): return not isinstance(error, PrivateIntelligenceError) or error.status >= 500 -def _policy_protocol(): - """Send policy extensions only after the peer advertises their decoder.""" - try: - capability = request_private_intelligence({"operation": "capabilities"}, endpoint="users").get("key_controls") - except PrivateIntelligenceError as error: - if error.status in (400, 404): - return {} # Old peers reject this operation, without seeing an extended row request. - raise unavailable() from None - except Exception: - raise unavailable() from None - if (not isinstance(capability, dict) or type(capability.get("protocol")) is not int - or capability["protocol"] not in (1, 2)): - raise unavailable() - return {"data_policy_version": 1} - - def _call(operation, **values): """One private call; reads are retried once after a transient failure. Reads are idempotent, so a single retry absorbs a slow first call after the Container or D1 was idle. Writes are sent once. """ - protocol = _policy_protocol() if operation in READ_OPERATIONS else {} attempts = 2 if operation in READ_OPERATIONS else 1 for attempt in range(1, attempts + 1): try: - return request_private_intelligence({"operation": operation, **values, **protocol}, endpoint="users") + return request_private_intelligence({"operation": operation, "data_policy_version": 1, **values}, endpoint="users") except Exception as error: cause = (f"{error.status} {error.code}" if isinstance(error, PrivateIntelligenceError) else getattr(error, "code", None) or type(error).__name__) @@ -94,16 +77,13 @@ def _call(operation, **values): def _row(value): - policy = None if isinstance(value, dict): # A Worker released before migrations 0011 and 0013 returns rows without these columns. - from services.dispatch_policy import verify - if value.get("data_policy") is not None: - policy = verify(value["data_policy"]) - if "official_only" in value or "forced_provider_preferences" in value: - from services.key_data_guards import stored_controls - policy = {"version": 1, **stored_controls(value)} + from services.dispatch_policy import envelope, verify + policy = verify(value.get("data_policy")) value = {name: item for name, item in value.items() if name in USER_FIELDS} + if envelope(value) != policy: + raise unavailable() value = {"secret_scan_mode": None, "shadow_eval_rate": None, **value} # nosec B105 - column defaults if ( not isinstance(value, dict) @@ -112,11 +92,12 @@ def _row(value): or any(value[name] is not None and not isinstance(value[name], str) for name in _OPTIONAL_TEXT) or any(value[name] is not None and (isinstance(value[name], bool) or not isinstance(value[name], (int, float))) for name in _OPTIONAL_NUMBER) + or (value["official_only"] is not None and (type(value["official_only"]) is not int or value["official_only"] not in (0, 1))) or type(value["is_admin"]) is not int or value["is_admin"] not in (0, 1) ): raise unavailable() - return {**value, **({"data_policy": policy} if policy is not None else {})} + return value def _rows(value, limit=PAGE_SIZE): diff --git a/static/js/key-controls.js b/static/js/key-controls.js index 4cf9c788..70a79916 100644 --- a/static/js/key-controls.js +++ b/static/js/key-controls.js @@ -5,6 +5,8 @@ /** The PUT /users//controls body for the dialog's field values. */ function controlsPayload(fields) { return { + official_only: fields.official_only === "on" || fields.official_only === true, + forced_provider_preferences: fields.forced_provider_preferences ? JSON.parse(fields.forced_provider_preferences) : null, secret_scan_mode: fields.secret_scan_mode || null, shadow_eval_rate: amount(fields.shadow_eval_rate), daily_budget_usd: amount(fields.daily_budget_usd), @@ -43,6 +45,8 @@ controls = {}; } document.getElementById('key-controls-user').textContent = username; + field('official_only').checked = controls.official_only === true; + field('forced_provider_preferences').value = controls.forced_provider_preferences ? JSON.stringify(controls.forced_provider_preferences, null, 2) : ''; field('daily_budget_usd').value = controls.daily_budget_usd ?? ''; field('monthly_budget_usd').value = controls.monthly_budget_usd ?? ''; field('allowed_models').value = (controls.allowed_models || []).join('\n'); diff --git a/static/js/usage.js b/static/js/usage.js index 80ed6135..b66739f4 100644 --- a/static/js/usage.js +++ b/static/js/usage.js @@ -113,7 +113,8 @@ ], 'No billable requests in this range.'); fillTable('usage-models', data.models || [], [ (row) => row.model, (row) => formatNumber(row.requests), formatErrors, - (row) => `${formatNumber(row.input_tokens)} / ${formatNumber(row.output_tokens)}`, (row) => formatCost(row.cost_usd), ...latency, + (row) => `${formatNumber(row.input_tokens)} / ${formatNumber(row.output_tokens)}`, + (row) => `${Number(row.cache_hit_rate || 0).toFixed(1)}%`, (row) => formatNumber(row.reasoning_tokens), (row) => formatCost(row.cost_usd), ...latency, ], 'No billable requests in this range.'); if (isAdmin) { const keysPanel = document.getElementById('usage-keys-panel'); @@ -122,7 +123,8 @@ } const principals = data.principals || []; fillTable('usage-keys', principals, [ - (row) => keyButton(row.principal), (row) => formatNumber(row.requests), formatErrors, (row) => formatCost(row.cost_usd), ...latency, + (row) => keyButton(row.principal), (row) => `${Number(row.cache_hit_rate || 0).toFixed(1)}%`, + (row) => formatNumber(row.reasoning_tokens), (row) => formatNumber(row.requests), formatErrors, (row) => formatCost(row.cost_usd), ...latency, ], 'No billable requests in this range.'); principals.forEach((row) => { if (principalSelect && !knownPrincipals.has(row.principal)) { diff --git a/templates/usage.html b/templates/usage.html index ad1f9f55..3ef70ca5 100644 --- a/templates/usage.html +++ b/templates/usage.html @@ -83,7 +83,7 @@

Filters

By model

The model that served each request; automatic routes show their selected candidate.

- +
ModelRequestsErrorsTokens in / outCost (USD)p50 msp95 ms
ModelRequestsErrorsTokens in / outCache hit %Reasoning tokensCost (USD)p50 msp95 ms
@@ -94,7 +94,7 @@

Filters

By key

Select a key to see its budget and controls.

- +
KeyRequestsErrorsCost (USD)p50 msp95 ms
KeyCache hit %Reasoning tokensRequestsErrorsCost (USD)p50 msp95 ms
@@ -104,5 +104,5 @@

Filters

{% endblock %} {% block scripts %} - + {% endblock %} diff --git a/templates/users.html b/templates/users.html index a8c8a4c3..f3b9d58e 100644 --- a/templates/users.html +++ b/templates/users.html @@ -135,7 +135,7 @@

Accounts

+ +

Exclude subscription, reseller and free providers, including routed candidates.

+ @@ -216,5 +221,5 @@

Save this API key now

{% block scripts %} - + {% endblock %} diff --git a/tests/dispatch_fixture.py b/tests/dispatch_fixture.py index d0a5bf23..9fff8bfc 100644 --- a/tests/dispatch_fixture.py +++ b/tests/dispatch_fixture.py @@ -12,9 +12,13 @@ def verified_transport_principal(monkeypatch): # Exercise real admission with an explicitly unrestricted stored account. # Policy and authentication regressions use their own stores instead. - monkeypatch.setattr(AuthService, "_load_user_by_username", lambda username: { - "username": username, - "data_policy": envelope({"official_only": None, "forced_provider_preferences": None}), - }) + original = AuthService._load_user_by_username + + def load(username): + if username == "synthetic-transport": + return {"username": username, "data_policy": envelope({"official_only": None, "forced_provider_preferences": None})} + return original(username) + + monkeypatch.setattr(AuthService, "_load_user_by_username", load) with dispatch_as({"username": "synthetic-transport"}): yield diff --git a/tests/fixtures/secret_firewall_coverage.json b/tests/fixtures/secret_firewall_coverage.json index ba9bb67b..a83ec1e3 100644 --- a/tests/fixtures/secret_firewall_coverage.json +++ b/tests/fixtures/secret_firewall_coverage.json @@ -262,7 +262,7 @@ "guards": 3 }, "worker/roleplay/transport.mjs": { - "dispatches": 4, + "dispatches": 3, "guards": 1 }, "worker/knowledge-edge.mjs": { @@ -284,6 +284,7 @@ }, "edge_modules": [ "worker/access-sso.mjs", + "worker/account-contract.mjs", "worker/admission-do.mjs", "worker/ai-outbound.mjs", "worker/alert-delivery.mjs", @@ -390,6 +391,7 @@ "worker/opencode/reasoning-request.mjs", "worker/opencode/reasoning-response.mjs", "worker/payment-webhooks.mjs", + "worker/peer-capability.mjs", "worker/peer-controls.mjs", "worker/pii-redaction.mjs", "worker/pii-rehydration.mjs", diff --git a/tests/helpers/runtime_versions.mjs b/tests/helpers/runtime_versions.mjs index 16945384..80cf02d8 100644 --- a/tests/helpers/runtime_versions.mjs +++ b/tests/helpers/runtime_versions.mjs @@ -9,13 +9,15 @@ export async function runtimeBundle(contents, revision = "", platform = "node") const output = await build({ stdin: { resolveDir: root, contents }, bundle: true, format: "esm", platform, external: ["node:*"], write: false, plugins: [{ name: "runtime-version", setup(b) { + b.onResolve({ filter: /^@cloudflare\/containers$/ }, () => ({ path: "containers", namespace: "containers" })); + b.onLoad({ filter: /.*/, namespace: "containers" }, () => ({ contents: "export class Container {}; export class ContainerProxy {}; export const getContainer=(binding,name)=>binding.getByName(name);" })); b.onResolve({ filter: /^cloudflare:workers$/ }, () => ({ path: "runtime", namespace: "stub" })); b.onLoad({ filter: /.*/, namespace: "stub" }, () => ({ contents: "export class DurableObject {constructor(ctx,env){this.ctx=ctx;this.env=env}}; export class WorkflowEntrypoint {}" })); if (platform === "node") b.onResolve({ filter: /dispatch-network\.mjs$/ }, () => ({ path: new URL("../../worker/dispatch-network.mjs", import.meta.url).href, external: true, })); - if (revision) b.onLoad({ filter: /\/worker\/.*\.(mjs|json)$/ }, args => ({ + if (revision) b.onLoad({ filter: /(?:\/worker\/.*\.(mjs|json)|\/cloudflare-worker\.mjs)$/ }, args => ({ contents: execFileSync("git", ["show", `${revision}:${args.path.slice(root.length + 1)}`], { encoding: "utf8" }), loader: args.path.endsWith(".json") ? "json" : "js", })); diff --git a/tests/outbound_inventory.json b/tests/outbound_inventory.json new file mode 100644 index 00000000..f097df2c --- /dev/null +++ b/tests/outbound_inventory.json @@ -0,0 +1,2457 @@ +{ + "app.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "38480cbcaf363f62177f1449b73b12c6c1b379dbb938e0096d6ff3eebb65921e" + }, + "cloudflare-worker.mjs": { + "kind": "internal", + "reason": "Registered Container forwarding; provider I/O reenters guarded Flask or Worker adapters.", + "sha256": "fc9fae652c0a1e7ad37b31b27f2d350266303aefa27911772377a0957bfcf866" + }, + "config.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "98e753d318ac39e0d6e3b1c5c9feb561faee62b97515055b5c05859bd3c6953f" + }, + "env_loader.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "ddc4d2ed5a4acc6c93c57e35371119a6bda3965a8ba59ff4aa96522f623d621a" + }, + "error_handlers.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8c8009220a718dfc3d4b1032c395f1c6fe32e639f208d6f81b1588d88e867493" + }, + "index.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "83932c6dc1e6e3cf822217dba6a1a575bfc983ac53842d1bec9a1512a8332782" + }, + "postcss.config.js": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d96a4fef9d253f0526603b86935d8e41f0731cc21b9232dd80bd5eed08bd8dd6" + }, + "providers/__init__.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8fa961e52dc94a2923538ca3e639c04b01f1ffc955fcd8fe301e136ead81eadf" + }, + "providers/aihubmix.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "a061e555cab9b86da52910ee53d8bfe229a91f517f3617aaaa680a17be279b56" + }, + "providers/anthropic.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "28ec570d523c8326c073ad8fc0aa051719e0aaaf2e8a468efe9e1061cdd9c528" + }, + "providers/base.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a99d39fbbe56c002cb86ba0524748c98d1bf10e446118d40ae2bb2792db2e344" + }, + "providers/cline_pass.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7b208a408a4484c969f44681e0be5da2317f5359d664677d18408139f21a9072" + }, + "providers/codex_everywhere.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a1c14b9b39c3e65e3583766d98375031a923d312284b6f33da7f213a0c3d0787" + }, + "providers/gpt_image_moderation.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b21ca2efe3c94d39a4e89ddb6a485ad3bc909a4803485ca2c9a7d717b5a46036" + }, + "providers/image_relays.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "ffc5bf871bd44660aacd4b202ade999f99d15bbe5cccb206e0642964b6b96155" + }, + "providers/local_inference.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "d87cb7b407afba0ac15caa8b5dd290134b650fcaffd623cfa9a2e9136ccb30f9" + }, + "providers/nanogpt.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "21c97f8bf80f4003b87498ee999d2c248da031005037df4ce02e40ea1718d9ed" + }, + "providers/navyai.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "09d0016f9eabecaf2c444c9394762b2a5ed7f65a709c3fe2d9624ffbc91459d9" + }, + "providers/openai_compatible.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "9864daa6b33894f3cce5600243ac175b06221fb87ff31416ba96be332d7976e8" + }, + "providers/opencode_go.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "0986fe1f497fcc01a28585089565a8fa127d212638b06afe197fbbdb90f54a0a" + }, + "providers/protocols.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b546ee027579cca2e9a1ff17cfbfb446bb3d3f0abdae30baa183a03c4c7e055f" + }, + "providers/registry.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f0d3808a1f0696db085f465a629cf329cd9347c0b60785c369006d4c2ba0913d" + }, + "proxy.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "d111384804103ded22cbb014ec1341185d7394721724461a2a3f9a75b9bf392d" + }, + "request_validation.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "1c91cb634c91161b6e25917c1bd53c65c3b007c192a27b686affa698406056dd" + }, + "route_helpers.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "d0c3c361f9a98d015a82fe1a9a9069de3534911f409b022cb9a9dca50108c60b" + }, + "routes/__init__.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b" + }, + "routes/alerts.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "12c4e91ff8c23beb7b1c092ac70fc2c13806b67e1ba7afd4dcc89babd26570f6" + }, + "routes/auto_routes.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "05eb09d16ff8f001dd48a97a980c55e30777d3ec4294412db2a3b690b9283b1f" + }, + "routes/cascade_deadline.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f0d107c6552541d925817a554563c6ca08543e0d10de53eb15badcc235170f99" + }, + "routes/cascades.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7d398b0e40b90bc860081b7c4343b9073002a700cdd550af3ea171e0f74cd8ab" + }, + "routes/chat_cache.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6abb810dc1ee1ce4d7a14416c8b1618773d4a1ca3f6e9e77a3df8d970eb9392b" + }, + "routes/config_snapshots.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "c2a72bbaf92cffa6c7547c3a51678a0e7e230aa7d8d88f8a2593aba8803e81a0" + }, + "routes/context_pages.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "2664df690add3c758ef2e711bac604a2d215f3b3a22cfc123c77bf5731c3b8e7" + }, + "routes/core.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "494de6e313116bd173153f9164d7258ef8bafa7ca5c0fb661f0a3b435a38d000" + }, + "routes/credits.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "952f5d13da6c66e0838e4d85ad1c9affea5faf318984b1f3eed781dbd453ad26" + }, + "routes/csrf_errors.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "dcc9103be9eca895084d5dc0848fc367bc1c147bbfd973385254216f518c2c16" + }, + "routes/dashboard_chat.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "e4074f254cf32d43ee7de2ebd93f0c590454b246a23d558998c334ce7d28901f" + }, + "routes/dashboard_security.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "38f9c7b9262fa1275c02a7a1c95b25e3b67d8e16744f8573797514ff87be2239" + }, + "routes/documentation.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d10850bfcc5715a3e11b9601cd1b3c43cc52fdbfbd0e3e4436d1ba044fd5cdea" + }, + "routes/enterprise_preview.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "309e274dfb03e6a8dc9f7babb98f2fcc0a8951266661209687e9757bd4429448" + }, + "routes/free_response.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "34df91bc3650bb09fbce76589e5d2dacc2c3165fa75ba6bd9d9f7245eb466263" + }, + "routes/free_routes.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "14a413e5b92e94315833fe0fc06ab7fad2fd92568c788d74565b2ac1d607aaba" + }, + "routes/gateway_batches.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a9cc3bc8eb3d25eaab4efefbde57bc6506d4af6f7d4518394c92af861bd4ac77" + }, + "routes/gateway_mcp.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "24d514965b61712a9e027860bd2a2d7aa6f4ba5a5929ba0a9f73a005707192f7" + }, + "routes/image_quality.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7eca100834d2f02c896bd7775a170b805893a9d95f87752bf501fe2247b9bb11" + }, + "routes/intelligence.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "1085b546c4eefb475af30e077ffeaebb905a4bef40af4f4ccf550f12a7e6cf4f" + }, + "routes/intelligence_media.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "40072ff1b7b45b9216a85cc17625206a7255f329693fa8f581b744a700974090" + }, + "routes/knowledge.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "3bc186b4f812dc93c17756f4be0160c9d98d816956ade81f5bfb11161739fcfb" + }, + "routes/knowledge_alexandria.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b5cb8fe752664eebff7308de55c3283c2bb91c838c9a828f294d525d29a1d259" + }, + "routes/knowledge_handoffs.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7b67f7c3da4c963dff6388fabfa4ba93b477404eefa846dcd9c341ffb0b73da8" + }, + "routes/knowledge_management.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "55b14a47d162c272722c07082d1a263184cea73d8b7ff102b556c53f2ef675b8" + }, + "routes/knowledge_mcp.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "488818e6f67a4ab50547588fd1371b30b587f79e3cafd3e28d145bb8e38cc47b" + }, + "routes/knowledge_memos.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "de86c533b80a75d25fd5f3a77763e957a4308b39d2de33b381954c196eb5a4a9" + }, + "routes/knowledge_onboarding.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "5adf66ce4d4d733ef8ec551b2b92d0493164381c5537a3eab679b3e3c24f55b6" + }, + "routes/knowledge_skills.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "0f378fdaf735e7794496b4bc57d1d7815755cdce857936f60f84674a822496d7" + }, + "routes/media.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "14e989ce1fb678535043b5a08139f37f1e10bf98c8c11ab360550bb22fd0c37e" + }, + "routes/media_audio.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "ac1e69377343bdc41f19d66b756213571e6430bd28274146e9e25c2d60a11170" + }, + "routes/media_batches.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "c58b066a6b37681212e6b8b55be8b01400fd64a337d54546b3002e1c33d7e32a" + }, + "routes/media_edits.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "30ed0f0f18529df52f06f1fcee0a2186f75c9376915d0759a6b036df46455149" + }, + "routes/media_files.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "67aa19f99f3c7fddb4f5f2c282f1712d29c745748b3e3078eaff98b8a6c642bf" + }, + "routes/media_images.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "be9e194c9b2ad34cde76fb8367a735cc5d71f8b6141278fd02124840f26a3697" + }, + "routes/media_narration.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "ddef74e625989431fd9ddea741cb8315bc8b96db82e011d338c79b13668a8879" + }, + "routes/model_discovery.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6ee413d25736340b784e1d718a3c50e5d7c1a483a2f4228a3fef5beccdbeddda" + }, + "routes/moderations.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "11e5b53d8a37808f53adb1f8b8c0ea9d32ded688c74cebd4abf8e8ca115a729d" + }, + "routes/optimized.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d4c565cabfb124acbbf2253e5e1e7337fe889425dc8d486d75cef8ce24746e72" + }, + "routes/payments.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f7fd931e0bb1b807ee8182b54eb5b91f50866cdd0f47445020e4159eab4357b9" + }, + "routes/prompt_templates.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "ef3c1e67b43b98e64105ebc79e38f1a12fc245d6a8998db27f16c69072127343" + }, + "routes/protocol_bridge.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "afbb07e2876724ae67bf4f665b6651af29da6ee4d732d9dec031a17a622ef92d" + }, + "routes/provider_credentials.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "7ed444a62db36f68a4bffa9f4e74c762212114989c8a00efc9fbb0c2f059d1a4" + }, + "routes/proxy.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "265e183e46865675ea3963f49546f650b3d0a04f98742f2dec63c7da862868a7" + }, + "routes/public_pages.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "121a7c8c51b8d8a290fefbffd2a31d392385c13694759d94c39e19d73b98c285" + }, + "routes/responses_compat.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "5c3cb763f26dab741a57699fa20ccc1e5435057ddff852669773ee8318ffff64" + }, + "routes/responses_state.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "195d37cf285625fdde72dafd0d148325337dc46afdc6e683accd4b9e8b90f7cd" + }, + "routes/safety.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b650a8ef7a81674b384eb192fa0a7d610ddafe948eabb241d9a88c4e4bd1ce9e" + }, + "routes/saml_federation.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8a8c12d9da440382a5479a3cdea6fdb15a1f6c67e92db1c4db0b37ad84098a34" + }, + "routes/scim.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "20a69ef7eeb656377e73cd550ff2b247c970e49f72bd84b82654b33b4454bcce" + }, + "routes/shadow_eval.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "3c53c36a2869d5e3ad7093a7b95819ef04570f222f321660c45a35c459c64724" + }, + "routes/status_page.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "3e460f0ac27a7392b2552905a73211e13d5d71e7100cf5cf4193646eb3a72114" + }, + "routes/tenants.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e395d65ae1f590baa76bbd09e0eb273bcb7fd83aa2e1e66e3e30b5fe3cc89ca5" + }, + "routes/tool_repair.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "71f5d11cd649208d60b1508c6802760aaed18d2882d7ba4da2af832e9a89ebf9" + }, + "routes/unified.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "bcc073d8fc97c65c2b00b1d7b069284ddfcd1834ab610c640e0d1644a439415a" + }, + "routes/unified_bridge.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "15b84736a4e28cb594e41b0495c1c064182229c76e9aa6258392579c65b69999" + }, + "routes/unified_images.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f5de8bfacfb4d754f9c8e5d26f0833b6cc28de39aa3112aed94467f63942f550" + }, + "routes/unified_messages.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e0f1a70c70d2f1a0020448978d366e4770b260e5aa662ece18d9d4d7a5dfcf81" + }, + "routes/unified_transport.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "cae7cf3b9ee2e74f0fec5cab3c55d71fa71db63facdc5e98f275fde4f3cebe87" + }, + "routes/usage.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "c222884e9bfda52a2db4b99ed88c49de4be5d27496e8806758a5c2d34ff9e362" + }, + "routes/usage_receipts.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b4b8e72c0540be4e3c8241834918f3e535b36b0dea82c9d6da17df3a56ab02ac" + }, + "routes/workbench.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "0f5b2cf70441ada654990182353035db0bf8a1ae4af77abe87c2016e6860b9c8" + }, + "security_config.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "26d999fe08561d26d7cf69a46656f575bf22d7d357fb9e4eee37c03a0211f7f7" + }, + "services/accounted_dispatch.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "647d4233ca8d7927b71ee6c74aa414914cee1864bd979b4229741ab67d66bc35" + }, + "services/adaptive_context_service.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "c9b44d71b9d67d298c5c727b7e2f48fd10688c3f673cd181960ff40ce822d43d" + }, + "services/admission_leases.py": { + "kind": "internal", + "reason": "Private admission lease RPC.", + "sha256": "b6f26de1c81f38615f585b55d3585d2aa12ad74452b1677266af3a99979852ae" + }, + "services/admission_request.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b003b9c3bebaa1234b03ee956ddd21baf1a9f255f9d73d3278c96dcf71811443" + }, + "services/agent_setup_prompt.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e6dc38246069e30b9cf5b14cd2bba10bc73846049be5ccf6024086a5e761befd" + }, + "services/anthropic_catalog.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "818c1e657d2519390dd77843a483a64fc0252083a8e5572e94721a3898e8b6b9" + }, + "services/audio_codec.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "fa56ff06817eefe0cc33f4c87a6f85c52365129c724a8d331afcfb7729cabcf7" + }, + "services/audit_log.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7b841926ed623f0c94c8fe864b3f25b1a6a5cbd222e77ad8c9788d05fc778901" + }, + "services/auth_primitives.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "0236bdad078e24d37db938cabe1db820094f05605f7ebd87c89597b0232143b8" + }, + "services/auth_service.py": { + "kind": "non-provider", + "reason": "OAuth credential refresh authenticates the operator; provider generation is separately admitted.", + "sha256": "ddd35642bf94e75227fbf414add8807e5db9a72a66802d86244f040a32f7eda8" + }, + "services/auto_route_d1.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f944b899963d1cb63c339dc36ebe5c7f20e139106964a86a4dab3b63a6ac8355" + }, + "services/auto_route_service.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "8837d1dd009fc9bb8c16da383caa370cf3f89933cff9ec46b592448709bdb8d1" + }, + "services/bandit_recommendations.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "2b0b86ba442b0fb9c5e9f1bfa8c27a84b6560d27bbf1bab9f796d9067c4cb43e" + }, + "services/budget_service.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "2bce627b9d973dc4fa989d1aff7e2226dea6cace8592d89485c49a8ff4066eed" + }, + "services/cache_policy.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "8d905ec9f2da11ed397f08aed091091057adbab9082f8668354c7eef91c47fab" + }, + "services/cache_service.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b6e11421a1ec86a8e6b99f63a2ea71011a0cbe79b9773555f6839c527f581f0f" + }, + "services/canary_traffic.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "ab5e3f57b31e8afd6b1a3f2032094896cfd74ef9dd5381b092ef99f0dcad8dc5" + }, + "services/capability_probes.py": { + "kind": "non-provider", + "reason": "Standalone diagnostic client calls the authenticated gateway; it cannot dispatch directly to a provider.", + "sha256": "da5c7c9e93413dfbd26b63c3871e79a0d35f5cf046e5a0b10ef9395578ead3b0" + }, + "services/cascade_checks.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8eeb6a3cb749dd566d35dcbe8418ccdd9c66f4b9dd0d24f59587b0e4d6b4b043" + }, + "services/cascade_config.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "553bdfcb6eb61f20045be3dc245594a403a993fa45d9f45a139b23e582dc9bae" + }, + "services/cascade_d1.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "c232e46e097c2f36eb6eba36264070b330eb3a889ead2458738934e766b20b8e" + }, + "services/cascade_service.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "201e349af7de489aa26014f316ccb98d853694eb0604cd99b478245635ba8b7c" + }, + "services/child_safety.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "4ab665ab1feed38228d8ad05422fbf7efcaef914042381d2d24caa2fce36c05c" + }, + "services/child_safety_inputs.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e9ed64edc3ae20dc3e96dd5bea93850681b11ab9f70e50bf79c949243d231766" + }, + "services/child_safety_settings.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "cf0efe175e2cc70cc5d1e1bb9c0e075ed084b41af648eb95e2f62722b9eb2cf6" + }, + "services/client_headers.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d7b07da139924e4153869b51e43f4bd2a6e9e9a8ae64ddccce1631fd4ae7b704" + }, + "services/cloudflare_ai.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "614a13e4109ab76be440e3f3e6c4da562e2477bc937578f439539bb1cc0b202e" + }, + "services/comparison_lab.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "ea61b938ed812b1979e397b3c641bddc0ed91a0eecc1d47484633554fa4cf047" + }, + "services/config_revision_sync.py": { + "kind": "internal", + "reason": "Private durable configuration reads, never a provider transport.", + "sha256": "489a9573ba8c5c6de36af1b7d2fb53a687caf91b909584a9fc944f266c44c4c0" + }, + "services/config_snapshots.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "da52ed20947b939cd0b55d6759ce6d64d32011b6217e4ed3b1817e806fe4e051" + }, + "services/connection_profiles.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a30c7f4b48f8f0db2ea99fc7c5f95c32dc9acc1da1ceb4ca3bb42d19738a27ef" + }, + "services/context_analysis_cache.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "bfe23758188a1d40d720d4a4221b8da80f9776440f807cde1a1fb4bbe31fb84d" + }, + "services/context_canary.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "23ff3a3a73fde62f7ea835a05e9091ed4f8602d759ad1e252dc8552d8a8fb56c" + }, + "services/context_canary_hook.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "acb6e06daae2bed2773f93232f549517feb117369d2269f0617f14615ed2518b" + }, + "services/context_optimizer.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "152fc1433e2ff265bcbf40d5699089af1526ee53339d4fbeac11afb1180a5d00" + }, + "services/context_pages.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e6d95d1590ef50f525cda666c1be3782a9f5fd92d54a403ab371e93581dd3770" + }, + "services/control_plane_backup.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6ef334a76f33c90292c2857fd170b8c2d5fc1abbdcb61313edce23381cda5943" + }, + "services/control_state_d1.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "33045f2a165f333094c34e9985e77851aa94d4e0293ea0915d01a8f5500007fb" + }, + "services/conversion_diagnostics.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "de04c864969fc9b5b2a283775907562fb8aef80d9b425165249834967efe854a" + }, + "services/cost_service.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "2c51cf7c6558ea04123eb94361b942aa8958e465a76fceac06800ace1f09f4fd" + }, + "services/credential_context.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "d305faa4f30c9400f243639e0d775169c9f3adc4bef93669c517cedb6a331bdc" + }, + "services/credential_pool.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "aa6ca6ddfb0137452cc53ecff300d13f383c39836a3005a2a56e7481d6e90771" + }, + "services/credits_admission.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "2231471cfd1042e186d3e9af7c6f93fe0ab38dc363296acf977b24f82f55c2bb" + }, + "services/credits_ledger.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "18d1033bc5c4fbe76f027fa818c0a06ff4257a8d58e6d80a4cab046419177c39" + }, + "services/credits_payments.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d128d9cedca99ff187d064ccd88d70e4b0db1cef2f0e1867a45f83c3e0d8d0b6" + }, + "services/dashboard_sso.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "500ebc3e06213f94cd0bef8594709b6ce5ef50c6bb9a6082144033973549c61e" + }, + "services/deferred_tools.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b2a006acdaa5903e197610863f9576438e0a05f233986b9bc4bf1644a9270626" + }, + "services/dispatch_network.py": { + "kind": "guarded", + "reason": "Origin-bound credential and network capability boundary, with current admission for each asset redirect.", + "sha256": "3322243fe6fe13f5e8d1cfaf666a806fe97a95f9b6737722230f478f782004a9" + }, + "services/dispatch_policy.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "9ef501fd08282710e37a3c6c4f43a790c0472a9dbc925bd88680fe689f54d68b" + }, + "services/enterprise_contract.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "5ad07545aad25a83b2ff33757555a6cc05b61c8f6ea917ab80fa1cbc365998ee" + }, + "services/evaluation_noise_floor.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a15da85e3639e4a6ba061a919bd55dbd76719894c8ff8c8520c944c96ffcf198" + }, + "services/free_compatibility.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "fc0d0eea4d8d63c59334c3686f00535ee9764eb3c8881fbbd10732b20c663a36" + }, + "services/free_json_contract.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8bcd19b617b94cf006ba8a8d498c84b0fb17050b4af1df418d67e63d30a00cd0" + }, + "services/free_model_policy.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "0d0e2b03a2c68076b936e76e45498c2ff6365711019da0c6103722c8e89dc587" + }, + "services/free_provider_catalog.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "729133dcf51aa218ec583cb5bea3dc80cb23287296ae45fddd1d71103164f34a" + }, + "services/free_quota_d1.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "217b385ac0a82541acf4bb08d3cb1dabc6c0ababd1cd46ffc86a211f8990e74d" + }, + "services/free_quota_service.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "c376f7cca1e95771f873a9176989fe6b7aa6d3abc01ef6fb07bf8bdd3e9e5e9d" + }, + "services/free_route_diagnostics.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a7e9e2746f3c656edc203acf4404a131918b69a8a40fd75571b38a467d8fd33e" + }, + "services/free_tool_contract.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "fb689785e7ed1695c121d5bfafa50f53bd1aa42cc1f7a28c2b8ace91c34a37b6" + }, + "services/gateway_alerts.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "48dff8d277ed73105cdf912138e4dad6bb9fb750ad0b21e1b4ca5c8653344559" + }, + "services/gateway_batches.py": { + "kind": "internal", + "reason": "Private batch persistence RPC; managed execution reenters guarded Container.", + "sha256": "7fbccd11aa42b4864b32a745377fd067ea281aab0ff2711e7934c4f2e37ba854" + }, + "services/gateway_extensions.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6cbdd3e49c37f99c3d2c83d110db0bbc5c503dc495fcaf03577f655ac7e70cd5" + }, + "services/gemini_audio.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "cb701602a93793381683c4d73cf3cf8073e7962f7380ddc0ab5a14116720e4ac" + }, + "services/gemini_credentials.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "de43c25cb99302e18cf5ba72d3781985cdb371e09fe8ca02d3991115bdcc5410" + }, + "services/generation_deadline.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8c25159414aa80291d0d9794bdfa86a05c90d962d501ff81705063c21cd03cf5" + }, + "services/health_checks.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "93cf31cb1115a10b66d51a0c3784db7d20cf0c7132b15ff39bb583c37a3fae28" + }, + "services/hedged_requests.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6336d87f0ca70c0a17b252ec0eb2e93e47e3ac6031153e8d5df8cefb06a4aeb6" + }, + "services/idempotency_store.py": { + "kind": "internal", + "reason": "Private idempotency persistence RPC.", + "sha256": "8c88d77785a9d3180c83e564aff9feec540cd959b856bcbda17078b4cf809c41" + }, + "services/image_quality.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "49f210a603bcd4b63160b3a6128e0b1a91cd9e9649da982dddeed55d48ac5714" + }, + "services/image_relay_catalog.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "3ba2d17ed1234d08964fe1fd1e57a6e7d6e19f9ba7b9211f7a1e345c966b4f27" + }, + "services/intelligence_anthropic.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "92776b86633baddf56345bed0b49c7d53f03cd3b01f8d7e98f89271c5065f240" + }, + "services/intelligence_auth.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e512eb322e5e71e1a520a1db348a9e397d84f9957441ac335c4c9e9d28b9ad31" + }, + "services/intelligence_cancellation.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f197a9276f0281c17995fc5411a8dfff16e2a06dc960af9da5a9db7d891fb43d" + }, + "services/intelligence_contract.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "be470d53857d00571267c8537f58571f7e93de014d9c8b6cbb369b40f31db3b1" + }, + "services/intelligence_d1_store.py": { + "kind": "internal", + "reason": "Private durable account and policy authority RPC; required before admission.", + "sha256": "f6964a63f5cb996322ffedca041c9fac8a4b6cdeec2f96a69206fb930551b9e8" + }, + "services/intelligence_gateway.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "efac340e944c0444222a44a484c1abf2caf219458b4eaa5ed0f093ac143d0c91" + }, + "services/intelligence_output.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e7c8b68e4adaa2b3a87e8cf7ceb85843b4b021beb095d395bdfec689dbcdb096" + }, + "services/intelligence_policy.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "ea72592b6127f19c04411e8eb52eb5c1891c1c152e34b852c50d195472a8f2e3" + }, + "services/intelligence_route_policy.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d5f696a77cbd6388bae9a4e1b4b185fcacf69004c8c9fdeb3002a021c7aa33d3" + }, + "services/intelligence_store.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f925c22ed7bc51bcced0597f8c769072125d326047b02893c326fd8f7ea70590" + }, + "services/intelligence_stream.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7e812a2528b55ee943b4beef6cb4211894220eef368e4b6bb4316d741f4d520b" + }, + "services/intelligence_tool_repair.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d76e8aa07901d1e3846f07d2cbcab94e151661aa5afa5f12e59d1eeb5f954d50" + }, + "services/intelligence_transport.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "2c3e234e66159f49d9cb55fcf586f5034d6adab9a169d564ac10d66372defd66" + }, + "services/judge_routing.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f9e3ca391cb5e3172560cc7c32255f1f3a8f4d2e507bf0354997054846db0955" + }, + "services/key_controls.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "3d4b04334ba19c75a2921ebb93323a826e2c3d745e0bb3141f3d55108be06d25" + }, + "services/key_data_guards.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "71318c7015922b1fc3dbb0e6ab1e18c957a0046a7de7ae7fedd9ae8ee01c2221" + }, + "services/knowledge_client.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "915e9f2a8ae157ad0d87b08073f6198a70f3b00df778d00bc9d06fd943a7bf8c" + }, + "services/knowledge_native.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b0560510e50edc6935f10c3dd21584a0cf40e0f8f2802c691f8b5041c7e8f3b6" + }, + "services/latency_slo.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "c529c365464c4b66995c8339add1eb03dbc3f5b2dd8dde6e9ff59d284179b1b1" + }, + "services/latency_slo_candidates.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6cc84ea11dd2a034cda5a0735af7a08261a140144895ba535e8a14171aadf197" + }, + "services/learned_cooldown.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "3fb690e621ef2dd9a3a554f8bd6459f45631f16ea34be61f7a8b81e92e1c7c53" + }, + "services/login_attempt_d1.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "784600f2fd7e0108456baf6a534b951049e0e2f05a202283811f51ebd4a17276" + }, + "services/login_attempt_service.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8a2f75ada5cd6e377ce7419ec38d4595777fa7d42eea9f6c2562210c1a722c7e" + }, + "services/managed_dispatch.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "3c9967511d53066f740053ae04c8cfc123fdc44f77ca52e96fb68e40aedffb7e" + }, + "services/managed_turn.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "c619b5fd489667332193b797375f43eae84700cef42dd779b88bacac8f6c7f55" + }, + "services/mcp_contract_drift.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7b000cf59b897ed7ca96b246065c7b90f7aa62a13914c30c2b661d996fa0696c" + }, + "services/media_catalog.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "f0f12c531156ffc99becb9d607eb0efb18473e1ae58e0562f38333aa85b064f4" + }, + "services/media_jobs.py": { + "kind": "internal", + "reason": "Private job persistence and scheduling RPC; execution reenters guarded Container.", + "sha256": "b47be5492e36a219cd743b5dfb8c9467e172c678be588600495d5e1ab5118f13" + }, + "services/media_signing.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "cbea9c4db4aed6aabb7d46faf402568f511ee03c2aa4ba173e421d543f052f08" + }, + "services/media_storage.py": { + "kind": "guarded", + "reason": "External downloads/imports use provider_http; remaining methods manage private R2 objects.", + "sha256": "c7eafb0cd39349ff767b8fe1365f3651b4e616608005e72c21dd1def8929440e" + }, + "services/media_urls.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "27deaeb534d23e3622377742318173ead0b9f1a883a4c00f3a6be44cbddd8b78" + }, + "services/metrics_service.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d8894064ab2e8add6184c06c2ccf9303dc583ec9eecf0c0f7c5877fbdf58d880" + }, + "services/model_catalog_service.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "0fde859ed00e534a90c85891d8f182af6c7934c51e7c2c8c19a8dcd0be6a867b" + }, + "services/model_cooldown.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "57e22aebb9fe3b7fc6e59715a7d3a667bae8ae3dfb512192af70bd371331cecb" + }, + "services/model_override_d1.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "4df825212824d678e571da03983c5e3efba62b317e8cb0d0c382ab68b8708146" + }, + "services/model_precision.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6cf35af44a625de049e0610e276114f4e2df35e44147363475ed74422a0c8815" + }, + "services/model_registry.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a112631d7a4aecc391fe5f0951ab01d450c9690dc274c1f2d075e44b16f11847" + }, + "services/moderation_input.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "79d9e46bb2085859845153a084cbb912c2665222d2d1f78e2b5118dc0cc474de" + }, + "services/nanogpt_key_pool.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "78d38e691062bc61dc94d0d4d5bf2ad64025d410b3429e63daffc418b6de2a1d" + }, + "services/nanogpt_speed_breaker.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "2c0015cb90af00adbd074fd85158566afde15e8e142472f4321f0b04ab95f15d" + }, + "services/native_token_count.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "46cd0e4dff7d3d123fe710c1cc00dcdf39a57a56ce1008446c94c8f4975282ac" + }, + "services/observability_adapters.py": { + "kind": "non-provider", + "reason": "Explicit observability sink, not a provider generation transport.", + "sha256": "1bbeb8bb3c74a42e996dc6c57c489203451c0b73f7f8ef31c2ffbf53981e5676" + }, + "services/openai_safety.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8614fe5f305e4fa322869f5adfb1fbee6da6dc4eae72a98646577a68b487ab44" + }, + "services/openapi_spec.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "4aadeecb28e6b81deb0f65b2e720782a6d2b02fc5137eb7a4bc71a719c488929" + }, + "services/opencode_session.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "64a4b6f6458077825724549e07fff5709ff0d45bade57ca376abef0296bdcede" + }, + "services/output_schema_validation.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "03eb2a5a4887153c11001fbb107928031097470f6ee62a2704429a5ff2bae94c" + }, + "services/payment_billing.py": { + "kind": "non-provider", + "reason": "Payment checkout control API, not inference.", + "sha256": "b8ed7c0d67fb2d1d0341fbc56cef18e03c92098ad9abd83b2b53a5322f8c5da8" + }, + "services/pii_redaction.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "683ca6757e9d77f3032a4bb6507d7d026588ba20bcdc955d0be2c00981c19e0d" + }, + "services/pii_stream.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "ced61ac817e74cd035de03ae60e46889f7b8683398251aebcff88c2c45a9181d" + }, + "services/pool_reset_schedule.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7e76d2ca84c5f9e9934cca7a6fcf70fb89913e93cc65c0c7f347287356bae450" + }, + "services/postgres_store.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "455a1428798977d982b96f8d9a7cb4d66c80f2ca19f9e4c4d6f0e0dcc09d64a1" + }, + "services/probe_cost_forecast.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "15e5b3b8b896ea63faf2ca434734731e73f5ccf4bc7d45032a764d4e412600f1" + }, + "services/prometheus_export.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e6463d21307cae24a620756492c5ad93907fe6642a2a0b1463f1ed75f1dc9b24" + }, + "services/prompt_cache_affinity.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "0b6ba053a2c58e8fa0fbbe50a3315042b371da126de935f58ff0d0fc0519cb1d" + }, + "services/prompt_cache_cost.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "53ed7f57d5ed2ee3e13762f5686414b6a6e6f4b3a0d3f6c53f8ae1d37af274af" + }, + "services/prompt_injection_detection.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "5ccf79f9fffdc7d5877ffd36733fbfcaf9be9a67433481f3155b6ceb751c7633" + }, + "services/prompt_templates.py": { + "kind": "internal", + "reason": "Private prompt-template persistence RPC.", + "sha256": "9e785abea29bb7abb88162f146d5eafa9d02fe57b23dd8bf1ece71a957f26bd9" + }, + "services/protocol_extras.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7906366ac64f811f0bdf654312bf97ee4304da0f8365a3ff3557e84b9beaf2c1" + }, + "services/protocol_translation/__init__.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "376ae90cf48be50da1c0f774262b8a9ff09b3d4ec4f3b759858057c3868f2386" + }, + "services/protocol_translation/anthropic.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "cff4c368369114275cf11f830cc3b53391c64a4c3e58a06b628e5156ee82133c" + }, + "services/protocol_translation/anthropic_continuation.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "876a709fcf9661f9508d6c6e566362901c5133880a80b46c98ac67b678dff7bb" + }, + "services/protocol_translation/anthropic_request_policy.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "61bb336865809714a61f7af0cc620ee618ab805f441744697e8c5758d6d0e062" + }, + "services/protocol_translation/anthropic_stream.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b2709c2e9b6d1abd6996ef8ec9be59ab6cb1cbc291f3f4002c419edfa5c916f0" + }, + "services/protocol_translation/anthropic_usage.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "965da99f1fd40f9c2526d6113a0c213a445d13a580c2faa49b6a52133c71a375" + }, + "services/protocol_translation/common.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8e7824816c68a0ac6c5a9d0d0db7ee56561896f967e6ab8638e2f4ff546f9483" + }, + "services/protocol_translation/responses.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "340a027eb1cd6778b557f9596f49e74efa7a507c2a8a296d573c299260f5fd0d" + }, + "services/protocol_translation/responses_requests.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f1af21d6e83d3aa9dff063fd28a3a2ba17cf02ac4da813d3f0c617c92e1e48a3" + }, + "services/provider_access_policy.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "14a0ea1030daadc8c1c1992b2abdf90ee7c1746b649bb1d4ebad8430ea0fe606" + }, + "services/provider_assets.py": { + "kind": "guarded", + "reason": "Origin-bound credential and network capability boundary, with current admission for each asset redirect.", + "sha256": "7becf18c378881506f2a20e1eebeb0b00f1be38786730eb41268554c93f5ab80" + }, + "services/provider_authenticity.py": { + "kind": "non-provider", + "reason": "Standalone operator diagnostic client, never called by the generation runtime; the target gateway authenticates its caller.", + "sha256": "28422fd44abbbd29cfd0a0fe10900945db87caff4b2022b7731592598444e389" + }, + "services/provider_capability_discovery.py": { + "kind": "non-provider", + "reason": "Fixed public catalog metadata GET without account headers or user content.", + "sha256": "da1ab5cf7982f600d22afdc125dfdddd92e6b1f943a927ff14e84f825aad385c" + }, + "services/provider_catalog_d1.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "651d31ee1bdddb4dc5fe430a82a4c0cf1623ecaa72edeeb008e3cd2ebf9cfddd" + }, + "services/provider_catalog_metadata.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "c507e4d54a104fdeb3a06e211ab2ff4191424a764f14b09342dc134759dd6005" + }, + "services/provider_catalog_refresh.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "007a2ff0c9f7498a276c146efd8fab9feca00d16c6d9bd429936e0d8834651a2" + }, + "services/provider_catalog_service.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b1448a93e4602d1def01aecd20c3c3c9f00475e683d55d74effc56e2db495d01" + }, + "services/provider_credentials.py": { + "kind": "guarded", + "reason": "Origin-bound credential and network capability boundary, with current admission for each asset redirect.", + "sha256": "babc974be1d465cf350e9f5d67f0e692bdf9733f62d475832e78e5f0d106a769" + }, + "services/provider_dispatch.py": { + "kind": "guarded", + "reason": "Sole Flask provider HTTP boundary; fresh durable policy precedes requests.request or injected session.", + "sha256": "552818f7d912a67c2e82c6734a85625f71fc7f47aee2565cf2719dc086b98448" + }, + "services/provider_prompt_cache.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "537ae92b1ede73ae20f5b7a03353e5cdd6a3d7a1ce4c930dcada1465969e3c9f" + }, + "services/provider_request_headers.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d59b8e720d732d1d1934361afc82130453cff502e67317aa67d66be29c694205" + }, + "services/provider_usage_normalizers.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6b65f6f8367b47be6c882173820e4d8bc53936a3f2928dd4a6058e2916691c50" + }, + "services/provider_usage_service.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "86e844a9e00cd2fc3609131f527b60f853f0d868f9bcd12bfb3f9956e053fbb6" + }, + "services/proxy_documentation_service.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "66bb86aed9afbe5ad5ad4ae0cdf409a858804ae98a4fa0f0db261b7da269b413" + }, + "services/proxy_service.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "e4241056d6a511a101367ec27d46e6147ffd62f86a4feb91f6a133954f5ca505" + }, + "services/rate_limit_d1.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e9872676cc6bd9ea8ec9741c2b8eaf42d78e007394d898071c34ab3bd6e2491f" + }, + "services/rate_limit_service.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "cc2fa4360ea9227e383ca90871cd352083ca00366058b3e998007ea8b4f10193" + }, + "services/reasoning_policy.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "9ba417687b433147fe16b97238d94398d60bb782119536414aecc992fe108eec" + }, + "services/redaction.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "9b8bb2d47102cb59e278f1b3c986451b808be3d504e2256f332bcf9a470eeedf" + }, + "services/release_status.py": { + "kind": "non-provider", + "reason": "Operator release and health metadata, not generation.", + "sha256": "1ec73290f38b8896c0bbf179be435d5e55256cb257b97ea3bdf751e063db6ac8" + }, + "services/request_accounting.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "da61afcdb7e66f1f43a7f9c315fab0de50440c2d0760b90d78a555bbe3b569dc" + }, + "services/request_cancellation.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "efd3bb06aec60e0fc0621ad2e481dd7db5cf03bfbf613a061f8e3feb7f70f723" + }, + "services/reservation_store.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "fc98e355d1d52354219b05594b39d0f89412af753ab785debbf263124c975f63" + }, + "services/resilience_service.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "591efe8a9a33ed172866bed6960a4d84122563bde85bef297d62614534604d8f" + }, + "services/responses_state.py": { + "kind": "internal", + "reason": "Private Responses state persistence RPC.", + "sha256": "5f247c4f39f97bdf54eba1e91a4090a4d147d0a0501074be6f697b78a001895e" + }, + "services/retention_policy.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6de7def22c49e5e5017bc028bbc4340e8c84ade1870c6000c59a767a3443f953" + }, + "services/retry_advice.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "09fa87b2d572be6d1a800a2aeceb6c650243a834a4cc10184b72f4dfdda28e3c" + }, + "services/route_health.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "50d9c2477fa6c5f74a919d65bbaf45f4913e7e2311584dad4ce8529c0858ba09" + }, + "services/route_health_d1.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "0f9ad892a772b8fa2302fd78de68bce4d9712562009b9aead1b0d84fba1bad27" + }, + "services/route_health_sync.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "0e01629b69c639a80380c9835540ecfb68c5b5ad4ce2cdec9e9cfb5afb0bb91b" + }, + "services/saml_federation.py": { + "kind": "non-provider", + "reason": "SAML metadata and URL handling, not provider dispatch.", + "sha256": "5a7c15c202c8b85173cab71392e36f977a77acccbe6a11b38e33ad643d6b10c8" + }, + "services/scim_provisioning.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e7a534b4954db0ca053bea696ff14fe3237c997c1a1d453f7ac9341cbbea3a34" + }, + "services/secret_firewall.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "9ac96591c2fdaa4fb5e880f4a7aab5fa6b9d7604c299bcfba6b5d0de1d5bfbf1" + }, + "services/secret_scan.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "73a9d92bb572ef77a942505146f3ea2110d5dcdf5c2de62969f6fa1942236072" + }, + "services/semantic_generation_cache.py": { + "kind": "internal", + "reason": "Private semantic-cache persistence; embeddings use guarded ProxyService.", + "sha256": "4f1d3bfb79533399a8210b84f9f6411840f36a8073f65aae9f2e06f6f8125d5c" + }, + "services/session_tiers.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "69c717b783b39074f673ff6ca6c92487ea518cf5e7e0e3512de4520c53a36ae5" + }, + "services/shadow_eval_context.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e78eeb8c12885e2ee56d05d86aebbd87d665ddd145b4fd6d98821e205a72b8c8" + }, + "services/shadow_eval_contract.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d1edd785c7b1a717936e4ed3b843ba194fd4b3e25603d612c08fc37672ab9e25" + }, + "services/shadow_eval_data_policy.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "315cabecae855804deaeb033e1b0b3c9517306dc216acd6f51c03d1078bba3a2" + }, + "services/shadow_eval_judge.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f88c2c49d9b5e6a0efbcf19ffd5e48a81e73993dc284efd177676bbd044df13f" + }, + "services/shadow_eval_league.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "ebc2b5432ba341909a1adb106bbec44306ad88a1790698ee6f979cf00b9b0563" + }, + "services/shadow_eval_runner.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a6b6c7c27115c8fd7b318b2c2c9bc43df6af818217cc7e389a77bf1c4351f3c2" + }, + "services/shadow_eval_sampling.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "3c6d1ab4cb38b38daff5da8d40a4aa7060c0cf36652b2e57e20f9615a6047670" + }, + "services/shadow_eval_store.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "71c3664eb647f73cea18952c956851819877d9cb87e8923b9ec3e6fc68e4fda3" + }, + "services/shared_generation_cache.py": { + "kind": "internal", + "reason": "Private cache persistence RPC.", + "sha256": "58ab29b11f7783626ae5b37438fe60f888b2bab03b1dbf771db7fab2fc24052c" + }, + "services/sqlite_store.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "58c4a99a51395ee084aaec97a63df8b7ca76d09262a031ca75c437323dff2859" + }, + "services/status_snapshot.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "346b8abd8279302f3defefb5c73475a670ea8bc29e64d80eab55bbc893a12044" + }, + "services/stream_cost_breaker.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7b9b9155a0837671f4d159c694e558def292d3464daa77d26f0a37ad85f13894" + }, + "services/stream_preflight.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "f5afb2b2150c8048e45d9d008042fdf9d1ddd7bc33117681508d0768d067011a" + }, + "services/telemetry_export.py": { + "kind": "non-provider", + "reason": "Explicit telemetry sink, not a provider generation transport.", + "sha256": "07596f7c9628e83ac10a6aba05e61b80fb7792c9706a6a27a5704c5258ad9531" + }, + "services/tenant_governance.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "99bee5a2c5818507bca232e244909d956d6519d124ea2bd9a6a06c43ba6a448b" + }, + "services/tenant_hierarchy.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "2d3ed64f579d75766679d61fcc3128087fe17e10bc6251fa415797ac67968f14" + }, + "services/tool_argument_parser.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "06c5ec7b9c0f11340b33d94904c04d0fd800241945443faafe7225775e3c4673" + }, + "services/tool_argument_schema.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "3b3e806762952725199d34d01334c98c6c4e2095b45a8571f5df7dfe6007bc1d" + }, + "services/tool_call_repair.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "9cdff918fa6c59ccec78b7b48fc02564b1e4316e15fe51c0d699ec50b5f74b52" + }, + "services/tool_repair_runtime.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "1d2c42df65624c21abf9e8fd3e7f2ac00f779983e0f773b499208b53e919cb39" + }, + "services/tool_repair_stream.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "cc695d54de018b74ce2ea335f334951b8c8cc0089a40bd21bd99d0d61f95549e" + }, + "services/transport_policy.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "842411432b5ed2718d857a70c7cfde9137d00004c46434cb4ea186a1cd17e8bd" + }, + "services/upstream_errors.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "69acb80ee747e3ecd87c596c2a581de624915ac119d3f0ab6cffe217547a0ca9" + }, + "services/upstream_outcome.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d164bf54137031072198ee944a537de9d6f4d77c967d9f5083a7c812169316ab" + }, + "services/upstream_transport.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "c27e7589a1efbc44b8bdd546199f087c4ec08c852e811156c6be149f0fa0690c" + }, + "services/usage_ledger.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "3f5530de97b8947bcb949eecca11c4905153820d7297d3e683e7cbec92a93313" + }, + "services/usage_receipts.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "fc44eb84c4fdfdab36e27c5f747bad9453d8067c60f822d1215c3272ed05f5b6" + }, + "services/usage_store.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "1121631c97b778163533deba4bc523f5c6ae31a837ebb6d9a90cd5dfb21336af" + }, + "services/usage_types.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d7b74bd38f673925fa882dd4bcd3a34bb2f85adb9ae4e488bfce12e5df43fc2b" + }, + "services/user_provisioning.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "046c8ef1b03e93fe768494f4727f356f026b72b5eb550aeeb813e4d451d6f96e" + }, + "services/user_store.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6ce9dad31606745046116d9bb05cc5ac1bafa854905135aa781e8c3a220ab662" + }, + "services/video_generation.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "62e59dc4ad4c6192cec297178bed33ffd4a6a9f510d2daa16b9f8769ec079d0f" + }, + "services/workbench_d1.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "74f87a7ad83256f28ae3577456c6c6007b8bf33fb283d129fd22b72dce93301c" + }, + "services/workbench_gateway.py": { + "kind": "internal", + "reason": "Management RPC to the registered Worker; generation reenters guarded transport.", + "sha256": "3b6b3a64aab462fe93bf1952de3c456124bbf4ec73d598c073881d191ac0373d" + }, + "tailwind.config.js": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "75e7d622614663d473b1e97a237d2ddd0fb840195013ac402520adacf41d2464" + }, + "vercel.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7e04625f1b75e45a47d17879bf6d1ee03090403ee4aa499d2a657fe63883f4a7" + }, + "worker/access-sso.mjs": { + "kind": "non-provider", + "reason": "Authentication certificate discovery, not generation.", + "sha256": "1a1535e5b8b04051812e723fa808c7c731fcb24695ad6b282c636f76e5940a1f" + }, + "worker/account-contract.mjs": { + "kind": "internal", + "reason": "Negotiates private account RPC metadata; no provider dispatch.", + "sha256": "7d4e309ebafd2da9143d9bc19562173626466a2947428273c4686a04fc3c0f76" + }, + "worker/admission-do.mjs": { + "kind": "internal", + "reason": "Private durable admission RPC.", + "sha256": "e87b485c50391562216055760a6d5102c038d7d00da6ac6bfb894b473c4fe1a9" + }, + "worker/ai-outbound.mjs": { + "kind": "guarded", + "reason": "Inference SDK calls use providerOperation; external result downloads use providerFetch.", + "sha256": "b9bfbce10ceac9e665c270e4a42a54a06aecd709e35d5cde7594cd257668961f" + }, + "worker/alert-delivery.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "34849266d92a9fcd7eaccd323bdb552e0ff52640d69f5b7205c529e962b3ced4" + }, + "worker/api-paths.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "378a091ba1b4f13ce2e75516eaa425a01c1655624970aac5bfd59627e5cf4a54" + }, + "worker/auto-routes-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f250eb0fc1b0d59dccb0877a933e3af6cd6bfbd0f5d4d46e14f772becb6d5003" + }, + "worker/batch-files.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "4c89419f46d884f5fe0a34eb437d31ffd51644c1a3e8b56009e1c646a97dde9b" + }, + "worker/batch-jobs.mjs": { + "kind": "internal", + "reason": "Private Container batch dispatch; generation reenters guarded Flask transport.", + "sha256": "20feb9a3ea43d46c7b862709989007eaebb143f8ed4561dfc35a053bf441aca4" + }, + "worker/build-id.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "3f187f55a86a64c8b9dff2b84c0828cf3d3e6e7b538b79364c5f72090ebfcf24" + }, + "worker/canary-assignment.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "070d9969f6c5a17fa238c5c4eba2aefceb2cea99bf2c025563f19a7c479718db" + }, + "worker/cascades-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "386648e0c5cd2b24f4d2341f72a4ec01fe869badd630fc1e049b60d140590caa" + }, + "worker/child-safety-settings-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "2f8ce0df2b8f9582c8c9605c846f04e8e65bc420ed8742a35926e5d872b1355f" + }, + "worker/client-headers.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "0ee194888a0c5f97105110b4811790c68e24333492e03464594e086f47bde9be" + }, + "worker/config-revision.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "616e21f45629f3f1739c365a0c74cadae4a85814396d89dd98459a48da4d1671" + }, + "worker/container-env.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "56f07b1a9d2f6451be990e16038e30d55afbee1dbf5a5e5ea82b30235558d790" + }, + "worker/context-canary.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "db09f569ea16239cc45cae1b379ccc46f52ab3395e098825a607b0dd7d3605b6" + }, + "worker/context-pages-d1.mjs": { + "kind": "internal", + "reason": "Private durable context-page RPC.", + "sha256": "6ccd924a75e5f0cace2a4e6d02b180a08c57e88ec955beb3508f1467dba9f082" + }, + "worker/control-audit-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "3c4672f5836f29ffcdb259b8b727ab170bb9b515511999c53fb82be710c62790" + }, + "worker/control-state-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f846d913d2acca4ea289217821a2927aa6fe0cd5aa4654578a67d4ae073b803c" + }, + "worker/control-users-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "4f7a8ae40dd9fdf5997a1a28c348680faf509b1834030fa8d988e5468668956b" + }, + "worker/cors-policy.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "17ee44d44ae37d6bf63838775c1ce252704e48ba0d32aea3dcb7faa6a52fee9f" + }, + "worker/credits-admission.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a4f5d892df430d60ae4721b9e42fbc4615f000a35fe1d7da391c5dea2c4f2d19" + }, + "worker/credits-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "af2e83b617db442bb9acfccd6a006079addf3bc8f44e860f2626c2bff260e83a" + }, + "worker/d1-schema.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "97848f176dba14a172ea8f0983f9bf09fde185bfefdb9a4ce4bc9603ecfc478b" + }, + "worker/dispatch-network.mjs": { + "kind": "guarded", + "reason": "Origin-bound credential and network capability boundary, with current admission for each asset redirect.", + "sha256": "4df64b633e0800aae5d7228cc49df0980442e2983a87f19d5b6a629f7e4cb709" + }, + "worker/dispatch-policy.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "ac63e1f4a06eecdb5eb57a930ec2169bea2ae88cda709aee4198a52e44f4285b" + }, + "worker/enterprise-contract.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "deeaa3e515e758d2d1d84108738a29038f20b07f59e2bdd4fff68320d6b084b4" + }, + "worker/exact-generation-cache.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d137c0f8a5214b51a00421435c33a16b0731de679f2834be6dacc88600b891f9" + }, + "worker/fallback-page.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "4abf38c486ce083f1065d8104209c1845cda3cb53a07b09c961e41500379b615" + }, + "worker/gateway-extensions.mjs": { + "kind": "guarded", + "reason": "Wraps the native fetch delegate with providerFetch before middleware, embeddings, retries or fallback.", + "sha256": "51c1e1d303e3113b03f9ec872e40135c08d2a3ccb150eda12079bfad03c5cadf" + }, + "worker/gateway-lifecycle.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b3c2d635a55c6656c27468c914a70dda8601fef341cd72464606128802c25e01" + }, + "worker/generation-cache-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "5c7289bd174df4ffb9ea80e63b1f7a1dfad4ec95c305c9edf08ea8b589732f68" + }, + "worker/generation-deadline.mjs": { + "kind": "guarded", + "reason": "Deadline wrapper calls the admitted native transport delegate.", + "sha256": "03b7cbadf4c479c16e619f4a5bd0c8b18f33481b76d4580bb273401368fea8e8" + }, + "worker/gpt-image-moderation.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "93a7abc49644407f59b4f3d8a69b24f77b9089d52830eabf9f308c0c3114411f" + }, + "worker/health-schedule.mjs": { + "kind": "non-provider", + "reason": "Private Container health/readiness checks, no provider generation.", + "sha256": "05f0b9921e73ca05fcb5a670494794d2270aab2d1ef975a786ff0ced9b3c99fb" + }, + "worker/idempotency-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a846c6e7216aa2b4eb46614f254c2031a65da5e6666e4485a676414771a4f34e" + }, + "worker/intelligence-auth-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a4b9433cad6d971e280bf8be146273a49ebed611b0fe0c8249680e265006dbac" + }, + "worker/intelligence-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b0fc57c85159614e48fa82ec7f76c19cf73ba69f9fdd5a2823b106bcb5a2fc4c" + }, + "worker/intelligence-outbound.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b562bc9345fc1c2570954acc89ebfaafb4c5e056154a9068124a4795c2ba86fa" + }, + "worker/janitor-reasoning-response.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b9b8a35c3f764ebbd2f00a52e3845064457c8d588b0576827e75f4ec2f2c4a54" + }, + "worker/key-data-guards.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "98248fd1ee5be5aa7d99734a0d51b0a00032a57629bdac3abe76dd38631d14ba" + }, + "worker/knowledge-edge.mjs": { + "kind": "internal", + "reason": "Private Knowledge binding; projects authenticated identity for provider admission.", + "sha256": "bb3382c69682e4c5dc379855758560cdbb26bdd357903bd6ce4238d4da5198ee" + }, + "worker/knowledge-outbound.mjs": { + "kind": "internal", + "reason": "Private Knowledge binding; requires versioned identity for provider admission.", + "sha256": "bd8e9360dff0e4be9484c146616027f75383a48e5ac0fd04201a77e821d2966f" + }, + "worker/knowledge/alexandria/catalogue.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "37e2293923b39027b34c7e4489db73559601cbc5803950f0256abbb1b9c1ab14" + }, + "worker/knowledge/alexandria/contracts.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a9a00338aad163108446ae1bae62840da042540cb50989f5088245fc3fa46576" + }, + "worker/knowledge/alexandria/service.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "9abf7ac28c4dce81e98b393b06b3d0b58cb3ad87e53c6eec81ed812b53d333e3" + }, + "worker/knowledge/artifact-eligibility.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7fd2d2ceac178d84bbdb7e0cea54bf3513cba292d6a9cf9179e38af78e063171" + }, + "worker/knowledge/authority-client.mjs": { + "kind": "internal", + "reason": "Private catalogue persistence RPC.", + "sha256": "d6a1734c1d3e1b6c016db1699dd82b571e9fa8d0113733c22dfdf833a89cb411" + }, + "worker/knowledge/authority.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "1cb60b774b4a414ede0954a99323c25be254dc69d64949328fdc5b8371b9101b" + }, + "worker/knowledge/cache.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "9380eb3a1e7664bec6edcfed49f47b3b6574dd4208e7afa37452f982bb5a0a5b" + }, + "worker/knowledge/contract-drift.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "9193679ab2e6613cbd8446ba1d0b586d2df79467de39ef5d4b0b35b1a56794b3" + }, + "worker/knowledge/contracts.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6a99f8fd80cbe2302f6a1207341cf794994fdbe7a643b87a93fd3bd9ea08e398" + }, + "worker/knowledge/corpus.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "0a1a3fd8ee3d75a5e2e4cd46693de1d746c6f32e4efa3776896a62e8544bef91" + }, + "worker/knowledge/credentials.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f3a20a7b4e82e09e09295a7b3985be45cdffed95226ddd09c42a3b6c61bf9ce5" + }, + "worker/knowledge/deferred-tools.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a31a100364f912eaf926a503e8f32a6d22d85421d896ebc2d5b770ec5b110f19" + }, + "worker/knowledge/evidence.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "894510be603acd8bebeba30b2c01b80cd423206e1ff216a04782c243bd7dcb6d" + }, + "worker/knowledge/handoff-contracts.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "9444837e30380e38459f593642a69dc39d31895c10280955219837cfbc5c4c02" + }, + "worker/knowledge/handoff-store.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "4e499eb0d18a75ac239dbabebc3c28adc60faef8745700bd5a79e4e092b392fe" + }, + "worker/knowledge/handoffs.mjs": { + "kind": "internal", + "reason": "Private handoff persistence RPC.", + "sha256": "6a5aa961c7e36a989808d52a922efa983971d0e7fdb8025ea4a14580b0e6641a" + }, + "worker/knowledge/index.mjs": { + "kind": "internal", + "reason": "Private durable handlers and Workflow entry points bind identity before provider operations.", + "sha256": "8db6479ebb73e1f9fcfd321e15d4982f10802ffa41aba1a624b77d77fdd86c9b" + }, + "worker/knowledge/ingestion.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "966d0f64669bc6e2544b0c09a969c2ec16975916007f35f87c7eaea3ac08ba9c" + }, + "worker/knowledge/ledger.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "65afb28c5a155ac5057552c984ed98217a2343078d3ae0cd297c6bddec379788" + }, + "worker/knowledge/memo-store.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "9d725f49f93e218089276cc7acb1584c2b24fc5626681e788c7b3e6b72bf089a" + }, + "worker/knowledge/memo-validation.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "2bf294d83c3fcb38692b0d539f4ddf67297d0c38d27bc63a5e1bcfc9c11e9d0a" + }, + "worker/knowledge/memos.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "f604ea74e3720db6b4d20a47588a15470ba790b3ccaaffa3509499947c8c9e8a" + }, + "worker/knowledge/native.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "3ae2923e3f94544dfef4da4c21c7c9d101f4aff2ae3cb042a17d138eeb3244a1" + }, + "worker/knowledge/operations.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "1039c53b215b926079e995a9f1327dcfd1489420ea1ed208aef239373a4c938f" + }, + "worker/knowledge/policy.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "70f3bd62a5b33670b0c3f3e9e6a079495942d73e777b495524ec3391734ccc7f" + }, + "worker/knowledge/product-site-identities.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "44c1b51eef62735bfe7df380402898862f7de4faa1234738cb1326db004e466b" + }, + "worker/knowledge/product-sites.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "cc964879ca07a669f84fa24d42bc2921d58b0c0decaed543730219ec337e18b2" + }, + "worker/knowledge/provider-site-filter.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "1554ea597d9551dc0e767241dd0023a67392c3fba463c67cfb95cdb4a325d18c" + }, + "worker/knowledge/providers/context7.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7973b19f165b17e922e64abf4aaf7ae19db06f2ac77cd7994d4d41720b2c5012" + }, + "worker/knowledge/providers/errors.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8fcc775b14e92f2cf1ecdc870c7d0c32474e1a4f1f935be50289a2c1232b1e3f" + }, + "worker/knowledge/providers/exa.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6abdf22644fb6ccc8be0919711209f34beecf9b6c9bdb5adadf70897a4911812" + }, + "worker/knowledge/providers/firecrawl.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "940b5fc8e3cbbef6b67df416c1d6fe3e8f91786b1b88fa3c4498b0b6d15848b9" + }, + "worker/knowledge/providers/index.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "8bc1f67a0a75c6c3765a03b873dbe5dc768dd5e93388d265937153a89b55d3ee" + }, + "worker/knowledge/providers/keys.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "44ca0feda59869c91b6a52c5c46a67f9b7526106db7de4820dde57d322a181d1" + }, + "worker/knowledge/providers/mcp.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7a3b234117f8f3753e1731f991835a22a5d1c7c72f2ae617caa519e7aec77d8b" + }, + "worker/knowledge/providers/source-policy.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8064a15f5b74b26dd71573b3ad687d468c2ec7a014fd1c5cc06c8ebe9b32c477" + }, + "worker/knowledge/providers/transport.mjs": { + "kind": "guarded", + "reason": "Every provider HTTP call uses providerFetch immediately before its fetch delegate.", + "sha256": "be7f26bfc29523a999ae0fe3f33ee6d9083d5f044ff109b2ede4b7337bb9cc9e" + }, + "worker/knowledge/retrieval.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "aac6df5801182e564d13ed25aae79a202d25a4e430a069f87894fc2c166ba5a1" + }, + "worker/knowledge/service.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "3bb4d263841fa2f3edeaa2f00898b1ba5b5cdd4a955e68443f3f7858eb52d0e7" + }, + "worker/knowledge/skills-import.mjs": { + "kind": "guarded", + "reason": "Every marketplace/source read uses providerFetch immediately before fetch.", + "sha256": "45ecea27eb4bbcc83d459519d6a28579de55acc3e7b9d3cfbb883c6c63c2aefe" + }, + "worker/knowledge/skills-index.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e52215beb724eb3819ee05cdf2a53153506bede5b55949837951e06d4eb71ec4" + }, + "worker/knowledge/skills-ledger.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "4d718520cf80bf5f384ddf54de16834cba4f2be9a2bf2caf0f8144407cb976a5" + }, + "worker/knowledge/skills-market.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "c6f6cd1fa18d0caac5415cdf5ae1cdb672fde175285e1f8c8b933ea6c0e8f94a" + }, + "worker/knowledge/skills-store.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "4ae9c433aeff9afe2e9b42c001b7b908254ad510fef3924893c11ef1be237186" + }, + "worker/knowledge/skills-validation.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "9a9d5f9552297444e4800207a363c47271ce60e6c0f9641eab2a2669c96e660b" + }, + "worker/knowledge/skills.mjs": { + "kind": "internal", + "reason": "Private skills RPC propagates identity; marketplace HTTP uses guarded provider transport.", + "sha256": "9ad18ac58697e7ab03357cced91785c26efa9fb7f241376f07a93338a0e949ac" + }, + "worker/latency-slo.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "98375b156d17b704ed06dd6c78e227c10d2953fe1537bd0d91b6f7989cc2125c" + }, + "worker/learned-cooldown-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b60fa2f0641f3bf08d9206585527ee4e61e5a6793f2f41cc46443139e3682dde" + }, + "worker/log.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d276aaeff69d0c73f4e935e3bcc8aef2a5f3d045d5e6b57de86fa0704189f637" + }, + "worker/managed-state-dispatch.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "45a6141e259548b450e85375d5f26613d1b6f5efea563dbd890f8b319f5c3f7a" + }, + "worker/media-files.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "eb14b537492aa1cb60cc910d43d5cb87138a999d21b2f970a0eda6e9f789f08a" + }, + "worker/media-jobs.mjs": { + "kind": "non-provider", + "reason": "Private job execution callbacks and configured webhook delivery, not provider HTTP.", + "sha256": "3356956867ed1f3c58fdc4b125aabefc4419d0639422ab347509a8d763e1fa8b" + }, + "worker/media-outbound.mjs": { + "kind": "guarded", + "reason": "External assets use providerFetch; remaining operations manage private R2 objects.", + "sha256": "5f6e01800ca85e10076581b3a8a8fdf5a9e48f753f909e4ce1ff8cd2aa0bf1bf" + }, + "worker/media-signing.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8fec18f88b01b3947252489743856ea3c90a3a750455a91f918e34bd8f5cd756" + }, + "worker/media-workflow.mjs": { + "kind": "internal", + "reason": "Private Container job dispatch; generation reenters guarded Flask transport.", + "sha256": "32029d79c9b304b80ed9606a4680fa3517f8d4c856614e396ee70716f88afc30" + }, + "worker/native-config-sync.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "ff7115f5d9c88d92274808f9958568d60b7a5584a76771c62ca787048ed57cf9" + }, + "worker/observability-export.mjs": { + "kind": "non-provider", + "reason": "Explicit observability export, no provider generation.", + "sha256": "3c3ac74a0d718362a8fcdef1c7d1f6b0a171efe866796e950c80a2b83f063133" + }, + "worker/opencode-session.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "155251c715ca64be5bd04c3dea5405b9ae04fcda2910dada3a84119f5bb649e7" + }, + "worker/opencode/reasoning-policy.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "ae655230b797ec9589a5172b05249f9cece612e09e539cb0542c1839583cf034" + }, + "worker/opencode/reasoning-request.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "3be9b47cc17a2ca9f70ab03f9228887d4e8245659494b7b6f7dd62c2ccb38994" + }, + "worker/opencode/reasoning-response.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e8c45e0273c5fb2113289c81dae3802347cf9f77cac0b71447b084a86bb4db40" + }, + "worker/payment-webhooks.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "9a952e1abf42772625faf313ac16cb13bb87e8aa84e1cbb29214f5b532500659" + }, + "worker/pii-redaction.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "51d45b86ff66a1c26cfa959b097a593fe529406001acea65dde074d2689bfeb6" + }, + "worker/pii-rehydration.mjs": { + "kind": "guarded", + "reason": "Payload transformation wrapper calls the admitted native or roleplay transport delegate.", + "sha256": "919be3b88a68aeaddaba22c63c6e20e19d658964f3cb338dabea6eada0d702ed" + }, + "worker/prometheus.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "0726a06b13c968ac8ec13f61f59fd07591e7985e9223660f94afd7ee6eb97d1c" + }, + "worker/prompt-injection-detection.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "2a2844dee9774075926ec3ee3d0a67b17c7b531b390e0f5b240403ed1067c936" + }, + "worker/prompt-templates-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "266511dd14adee22d1828fd8c3e70a47bb5cff75268a535dfc64687567aa1e5d" + }, + "worker/provider-dispatch.mjs": { + "kind": "guarded", + "reason": "Sole Worker HTTP/SDK boundary; fresh durable policy precedes fetch or admitted callbacks.", + "sha256": "77e2ffe56e3f5bdd7fb76d6ea01478449c3111625652c96385a3fabf8c0218b9" + }, + "worker/realtime-metering.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "9ef22465b3c5034a5df87dcb2f2d9d0c209dbe6969cf5d7f4b02c9f88afc88ee" + }, + "worker/realtime-session.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "de5730ce8269079a7cf152e6eba01cdc688b4810d7786e4c7b0b468246fef18e" + }, + "worker/realtime.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "78f30da2e56ec8c552322fd6db11ba3d6d9f7c454cda8771e1db2c76c566e63b" + }, + "worker/request-telemetry.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b8a54faaaf40fdc957a933313b1718e09e25f707d63c2dedf6963a8560a9ae0f" + }, + "worker/reservations-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "ba1a1a5bc15fc58bca1c695a22fd0ec22411621e0a093d87c89406a75e410861" + }, + "worker/responses-state-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "cbca75eb921b8e029861867b6799df8ef9d2d9a644fe8418da311922f085c7a7" + }, + "worker/retention-policy.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "52a2bf7d5c44953eabcbc6b05519f8c96b51d9be786cca33d2edd7284428d6b1" + }, + "worker/roleplay/candidate-context.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "cccfbec402dc653d67d8bd340d3335296ad2bb72264ac8b9a7bea3bbac4a20c9" + }, + "worker/roleplay/capacity.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f8068b510268924b570e0277a097790898b861b75458e9cfa564f419d2d180a4" + }, + "worker/roleplay/checkpoint.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "60c194f4b523fc8fb9a1da5cca1113539bc97061802fcaefd2453088fc9c650d" + }, + "worker/roleplay/compaction-budget.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "1e0b66156c1f307d63ec632f055868a2c3b638710cce1930eb92ad9d4d328d13" + }, + "worker/roleplay/compaction-policy.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "34a81069dfed8e393ac2c3e3d0986da3ce4adac980e675676a012df6bfee87c7" + }, + "worker/roleplay/compatibility.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "45ed9b8a1e00b29f0d08de993434b0646aee396ab692cf79e44add3f9666ba99" + }, + "worker/roleplay/completion-result.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "924b5dd596ac8ef89f8bd13f04bc7e1067546ed092e47435e3417b66728f1761" + }, + "worker/roleplay/config.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "ea0007c314192ae4718fee801465e8678cf5ec8edd7ffd84e9a70480cc10d1ee" + }, + "worker/roleplay/continuation.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "0a3abcb3358db0112e4b6e22cf07695947f8bd2d4cde253de033b7f55c29e5b5" + }, + "worker/roleplay/credential-health.mjs": { + "kind": "guarded", + "reason": "Provider credential probes use providerFetch with current durable administrator controls.", + "sha256": "6bb21614ba698b8bb55edef2d1ebb475d7645ac1dbbf5a1269e2c7b6ca3745ca" + }, + "worker/roleplay/directives.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "159d5cb7aeafc3de73f9f03ea0bcf79bd929998b2f6b5ea777a19e3d1ca1edfe" + }, + "worker/roleplay/edge.mjs": { + "kind": "internal", + "reason": "Private session RPC; all generation enters guarded roleplay transport.", + "sha256": "3819cc2b142597537500e10a3426015688124cc2d02291069bd9a4efabc80a33" + }, + "worker/roleplay/endpoint.mjs": { + "kind": "internal", + "reason": "Private session handler; generation and compaction use guarded transport.", + "sha256": "9e460b4d4cd420c7692406a7ba03248b73413c830be54423fdb57fe208a6d3cb" + }, + "worker/roleplay/fallback-memory.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "af6e3d57a05edabfa704602c00b3d8e3e258a93462c19f88286127bfd86f494e" + }, + "worker/roleplay/history-reconciliation.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "0012faf5281555123f48e01caa0177913e82dc030daaed1b5a9ded4b233b5694" + }, + "worker/roleplay/memory.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "9466e85cb1186b33f98fa4ae7d863071788ffc41f3e5b4bad172b4668aae8b51" + }, + "worker/roleplay/message-fragments.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "677018d928dcf2f069f483c1e09bdb4fe76179c4788262c2584b0b52477c50fc" + }, + "worker/roleplay/message-validation.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "fc99aa511590f79f8b3e33a96cf679ace137a767f0c2a94dfcca2890038efdd0" + }, + "worker/roleplay/model-performance.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f13af718076e01ea366fd5c5baa9b2daf386e75ee88a8aaf76e301c30bbc8583" + }, + "worker/roleplay/model-selection.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "87e782e35c4ad4ee6e5df2c0264e9cb9b8118298bc8be0f418c270385b7ee576" + }, + "worker/roleplay/nonstream-repair.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "8d3166d81f7be6d0e6f25760a7c05866c4cd5c23eaad36300b8d7f2e3a03e196" + }, + "worker/roleplay/operator-edge.mjs": { + "kind": "internal", + "reason": "Private roleplay management/session RPC; generation enters guarded transport.", + "sha256": "24d1de1b071f8be482873ae11d3f2ce0d4eb4bfe2ff564691305e7521e718e7a" + }, + "worker/roleplay/operator-memory.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d03a800eb9fa7c74d1ed7f717d2f983a0fa70fe0521eef8998b667f68c76db20" + }, + "worker/roleplay/output-budget.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "ce1bafa71dfa0186b6685cefd0502b31fba339464d8072b1a6c36f42660c6217" + }, + "worker/roleplay/output-contract.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a1b2d7727840b0de8aa810c22584c7064954129aff17b1449b6000c124d1f17b" + }, + "worker/roleplay/profile-receipt.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f7214fe4b10d9f3d4deeee997b83f199e758e72d860586c3df21d8307db25dbd" + }, + "worker/roleplay/prompt-cache.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "aaae8c7fd84221d1cccc28896b2c25fdc3bc2393e1b10aaa836f2666357e376e" + }, + "worker/roleplay/provider-errors.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "28d78891411589354ab9f1d8a3157369dd018afaa9c7ff6875b1475afebedff1" + }, + "worker/roleplay/quality-routing.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "c3b37c7521d3813942ef7f80d94a18f8f27717bbf67a6655b23429334419db7a" + }, + "worker/roleplay/reasoning-output.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7c92345894b68d7c0fd93d7774ad4745e04b4197329fd872ab4930b6804a0591" + }, + "worker/roleplay/reasoning.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "65f296d79de04c79b0b5565a97a87b3c69111dc12c2f4b46f573c79a8c2f4dbe" + }, + "worker/roleplay/recovery.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "023fecbb3d0a4ca6310dfaeea67ccf8fa2bae5db2ac19549d7ef648535736460" + }, + "worker/roleplay/refusal-fallback.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a645bf4983ec4affddf33dff3c96b2ec14831e432b64510534dff0690cb606e0" + }, + "worker/roleplay/routing-policy.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "648f241ab460a37b70d26e1b9a1e2349d2fc03e6e61d2a953ea9a44ac1926dbf" + }, + "worker/roleplay/session-metrics.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "39735a84caf51e261ea91a3d881281d629c9419ffb39aa94cf823f078f75444d" + }, + "worker/roleplay/session-storage.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e09f9b499a3efb77fb29390aeefeb79ad2dfb18c40b08f406c3ea91bec641ba5" + }, + "worker/roleplay/session-tier-policy.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f3c3b3b11b567ba8ab57c976bd1d3a65a7eda31a9cefc27069dd30ad07300907" + }, + "worker/roleplay/sse-collector.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "d609605183dd3f8f2b90f2f7402ba2f45afb797f88297d11460de31ed36e9997" + }, + "worker/roleplay/state-runtime.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "e59cd25c50546c28c1a7b0aaa1301d4854d6d7ad55bc9c2837ebeaae3d9f0246" + }, + "worker/roleplay/stream-validation-gate.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "457c2a917a0cc43e16567a9d28e4e43503e133f1e1be15302fa7955276d4e77b" + }, + "worker/roleplay/streaming.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "cfa1ae35acfbddf5d6f8cd362b13c24247b38df78cbca3fd0bc545924a89b372" + }, + "worker/roleplay/transport.mjs": { + "kind": "guarded", + "reason": "Every candidate, compaction and fallback uses providerFetch, including Container egress.", + "sha256": "fb1f50e58dd75196d6920a542df1c262e811a1c4e86d701d21366aa1d1540eb3" + }, + "worker/roleplay/turn-runtime.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "309d852b4164430143e4fceb4c67ce9c9fe614e2fcb35819467aad8b2cbe65a9" + }, + "worker/roleplay/turn-trace.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "1cc028a2a7bef7e0e1540e1a99792e7651203b965b1562823e347c1d6474074e" + }, + "worker/roleplay/validation.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6383634dd3186799e0138897cb2023e888151062d52677d8cb89934d93583f02" + }, + "worker/route-health-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "bf916fee4d656378b95bd3f1c7239d5c7d8b2e8591ceaec64cb48a7f5b4f0772" + }, + "worker/runtime-feature-flags.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "acf379482beccbaa1faae4f1b872ee5ed613f3f3abfd600e60fb5fff1a4464fd" + }, + "worker/saml-identity.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "5f1de1406099bd3c199a89913d85948f3c303f0222521fc7a6d4d527a6f296b9" + }, + "worker/scheduled-maintenance.mjs": { + "kind": "non-provider", + "reason": "Explicit maintenance webhook delivery, no provider generation.", + "sha256": "7302ed3f218a47fa3afe7a65f5e2a4353a0be24847279be128782d6a0553129e" + }, + "worker/scim-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "438e925647db4851a088c4ebe83dcb6791dc06631a384b2057a52b24284b09d6" + }, + "worker/secret-digest.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "63572c2322a6a29e602964fe2b8136cb0e563cb34aee17956ac90779f8d93396" + }, + "worker/secret-firewall.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "bd79e15c3b2d4f29916c4c2c5601284f953ff142cad80198a5155985b2560203" + }, + "worker/secret-scan.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "31a08d14c47a58ec405505b45dd254edc48f06863e1d234b10db7fb611b06a02" + }, + "worker/semantic-cache-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "1acc71757514b0e73acea66000758f4cee55e261c19b11ba5f26f608004185ff" + }, + "worker/semantic-generation-cache.mjs": { + "kind": "guarded", + "reason": "Embedding delegate is the native providerFetch wrapper, including the embedding provider authority.", + "sha256": "ba3f547d794cb9b323c6927ca65e3b45e816e73a215e97218ab023584ca68a90" + }, + "worker/shadow-eval-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "2b1a2b3b7c6551683025c80050a0d36f961c8e20106a26ba578358a97bb56e29" + }, + "worker/shadow-eval-schedule.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "b83c12aee761dfa21a0b942b8c8187e48d2af3a7dc4d015495b74d9614f03741" + }, + "worker/sse-heartbeat.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "4eae26738d0e8d7768a829396a82d83ba65792a1a9119689fd9b6031be9db670" + }, + "worker/status-page.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "af6c1272b8537020da0e435e04b1f8ac94a1bbe3d9c40eb16a24433016f5bb51" + }, + "worker/stream-cost-breaker.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "5317e648a4dbb6fee35d45ae294ccee128d9296751ed1c941f578223d1be0640" + }, + "worker/tenant-governance-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "6b4f0d97520e9bfc07ee5b9879a017ccf94ca8b48a800793886f332c6933c451" + }, + "worker/tenants-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "a33aa142fea6154ce16983ad9dcdde9220ac5dbc954422abfea8d4e7a02fe50e" + }, + "worker/upstream-cancellation.mjs": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "44c67fd5404a2ed8acc3d74f4ccf35f1944e9bddacb44fd7177a5d829d4aef04" + }, + "worker/usage-buckets-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "7d35b0203ce30441977df2e458e825e0fb5dd0f26db81b963a0cc1c2606dc996" + }, + "worker/usage-ledger-d1.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "5430e78972b43f5257c3eddb1533586243b9e27092e0ce5517e1752232c5146e" + }, + "worker/usage-receipts.mjs": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "347edf7943143eec7bac78e79fd234a9a98940358f1839d9274abb5c7240c89b" + }, + "wsgi.py": { + "kind": "non-provider", + "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", + "sha256": "f07b32f2824b0b30479f475d276816223b4d4486fff067503c3ce054042e9053" + }, + "services/peer_capability.py": { + "kind": "internal", + "reason": "Capability negotiation and current-policy admission before controlled delegation.", + "sha256": "d1e05e277f9d765aa5ae5a75f5a5ac1148db5dbaf3aedf245dbb5037f6d49441" + }, + "services/key_controls_capability.py": { + "kind": "internal", + "reason": "Capability negotiation and current-policy admission before controlled delegation.", + "sha256": "8b68cc533d11a40d9a4477ab2c73f984cd7c96d30ce993918c4f26d765b80b2f" + }, + "worker/peer-capability.mjs": { + "kind": "internal", + "reason": "Capability negotiation and current-policy admission before controlled delegation.", + "sha256": "2bc4b2e584c7ff329c415869dfa0442211bb5743590c4b97f13f76730cb6ef85" + }, + "worker/peer-controls.mjs": { + "kind": "internal", + "reason": "Capability negotiation and current-policy admission before controlled delegation.", + "sha256": "fe3e617eb061d5219a82e571c77cba9b235b9be1f2ee0f7d1c68962528ae03a2" + }, + "worker/key-controls-capability.mjs": { + "kind": "internal", + "reason": "Capability negotiation and current-policy admission before controlled delegation.", + "sha256": "90591998960f0ac17f898e7be64b18bad6d0429d666c27894bbab3e42aae6925" + }, + "worker/legacy-binding-policy.mjs": { + "kind": "internal", + "reason": "Content-free capability probe of the actual fenced expansion Container before legacy private dispatch.", + "sha256": "1bc8f20ae715d46ceed6a32d90d549688d9479ee87fe25a110235d03c390059e" + } +} diff --git a/tests/support/account_envelope_driver.py b/tests/support/account_envelope_driver.py index 0d47914f..2f0670d2 100644 --- a/tests/support/account_envelope_driver.py +++ b/tests/support/account_envelope_driver.py @@ -107,6 +107,27 @@ def versioned_template(name): requests_to_run = json.loads(sys.argv[4]) if len(sys.argv) > 4 else [("/v1/models", "get"), ("/v1/chat/completions", "post")] for operation in requests_to_run: path, method = operation[:2] + if path == "/probe/integration": + from error_handlers import APIError + from services.provider_dispatch import provider_http + + def integration_send(**kwargs): + from services.dispatch_network import network_admitted + assert network_admitted() + seen.append(kwargs) + response = requests.Response() + response.status_code = 200 + return response + + try: + response = provider_http("openrouter", "POST", "https://openrouter.ai/api/v1/chat/completions", + reference={"username": "integration:synthetic", "api_key_prefix": "mllm_intelligence_aaaaaaaaaaaaaaaa"}, + transport=integration_send, json={"messages": []}) + status = response.status_code + except APIError as error: + status = error.status_code + print(json.dumps({"path": path, "status": status, "dispatches": len(seen)})) + continue if path == "/login": username = operation[2] client = app.test_client() @@ -122,6 +143,6 @@ def versioned_template(name): request_headers = {"Authorization": "Bearer " + os.environ["ADMIN_API_KEY"]} if len(operation) > 2 and operation[2] == "admin" else headers response = getattr(client, method)(path, headers=request_headers, **arguments) print(json.dumps({"path": path, "status": response.status_code, - "error": response.json.get("error") if isinstance(response.json, dict) else None, + "body": response.json, "error": response.json.get("error") if isinstance(response.json, dict) else None, "dispatches": len(seen), "preferences": seen[-1].get("provider") if seen else None})) scratch.cleanup() diff --git a/tests/support/config_revision_fixtures.mjs b/tests/support/config_revision_fixtures.mjs index dfe81b6e..d3c912a7 100644 --- a/tests/support/config_revision_fixtures.mjs +++ b/tests/support/config_revision_fixtures.mjs @@ -31,7 +31,7 @@ export const account = (changes = {}) => ({ username: "alice", api_key_hash: "sy expires_at: null, secret_scan_mode: null, shadow_eval_rate: null, official_only: null, forced_provider_preferences: null, ...changes }); const usersRequest = body => new Request("http://intelligence.internal/v1/users", { - method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ version: 1, ...body }), + method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ version: 1, data_policy_version: 1, ...body }), }); export async function usersCall(env, body) { diff --git a/tests/support/expand_snapshot_driver.py b/tests/support/expand_snapshot_driver.py new file mode 100644 index 00000000..6b82f114 --- /dev/null +++ b/tests/support/expand_snapshot_driver.py @@ -0,0 +1,46 @@ +"""Execute the unchanged expansion suite with its actual pinned runtime modules.""" +import json +import os +import sys +import tempfile +from pathlib import Path +root = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(root)) +revision = "b6b0e43" +if revision: + import importlib.abc + import importlib.util + import subprocess + + class RuntimeModules(importlib.abc.MetaPathFinder, importlib.abc.Loader): + def find_spec(self, fullname, path=None, target=None): + if fullname.split(".")[0] not in {"app", "config", "error_handlers", "route_helpers", "services", "routes", "providers", "streaming"}: + return None + name = fullname.replace(".", "/") + for relative, package in [(name + ".py", False), (name + "/__init__.py", True)]: + result = subprocess.run(["git", "show", revision + ":" + relative], cwd=root, capture_output=True, text=True) + if result.returncode == 0: + spec = importlib.util.spec_from_loader(fullname, self, origin=str(root) + "/" + relative, is_package=package) + spec.loader_state = (relative, result.stdout) + return spec + return None + + def create_module(self, spec): + return None + + def exec_module(self, module): + relative, source = module.__spec__.loader_state + module.__file__ = str(root) + "/" + relative + if module.__spec__.submodule_search_locations is not None: + module.__path__ = [str(root) + "/" + relative.rsplit("/", 1)[0]] + exec(compile(source, module.__file__, "exec"), module.__dict__) + + sys.meta_path.insert(0, RuntimeModules()) + +import subprocess +import pytest +source = subprocess.check_output(["git", "show", revision + ":tests/test_key_controls_expand.py"], cwd=root, text=True) +with tempfile.TemporaryDirectory(prefix="expand-contract-") as directory: + path = Path(directory) / "test_expand_snapshot.py" + path.write_text(source) + raise SystemExit(pytest.main(["-q", str(path)])) diff --git a/tests/test_account_contract_worker.mjs b/tests/test_account_contract_worker.mjs new file mode 100644 index 00000000..d9c23a9d --- /dev/null +++ b/tests/test_account_contract_worker.mjs @@ -0,0 +1,32 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { accountPolicyVersion, accountReply, accountWrite } from "../worker/account-contract.mjs"; + +test("account policy negotiation is explicit and rejects unknown versions", () => { + assert.equal(accountPolicyVersion({ version: 1 }), null); + const body = { version: 1, data_policy_version: 1 }; + assert.equal(accountPolicyVersion(body), 1); + assert.deepEqual(body, { version: 1 }); + for (const value of [null, false, "1", 0, 2]) assert.throws(() => accountPolicyVersion({ data_policy_version: value })); +}); +test("legacy account projection refuses missing or malformed verification", () => { + for (const data_policy of [undefined, null, {}, { version: 1, official_only: null }]) { + assert.throws(() => accountReply({ username: "synthetic", official_only: null, + forced_provider_preferences: null, data_policy }, null)); + } + assert.throws(() => accountWrite({ username: "synthetic", official_only: 0 }, null)); + assert.deepEqual(accountWrite({ username: "synthetic" }, null), { + username: "synthetic", official_only: null, forced_provider_preferences: null, + }); +}); + + +test("zero and empty policies survive the negotiated account reply", () => { + for (const policy of [{ official_only: 0, forced_provider_preferences: null }, + { official_only: null, forced_provider_preferences: {} }]) { + const row = { username: "synthetic", ...policy, data_policy: { version: 1, ...policy } }; + assert.equal(accountReply(row, 1), row); + assert.throws(() => accountReply(row, null), /key_controls_require_upgrade/); + assert.deepEqual(accountReply(row, null, { management: true }), { username: "synthetic" }); + } +}); diff --git a/tests/test_account_envelope_integration.mjs b/tests/test_account_envelope_integration.mjs new file mode 100644 index 00000000..31141381 --- /dev/null +++ b/tests/test_account_envelope_integration.mjs @@ -0,0 +1,94 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { readFileSync } from "node:fs"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { scryptSync } from "node:crypto"; +import { build } from "esbuild"; +import { Miniflare, convertV4MiniflareOptions } from "./helpers/local_miniflare.mjs"; +import { applyMigrations } from "./d1_migrations.mjs"; +import { collectContainerEnv } from "../worker/container-env.mjs"; + +const run = promisify(execFile); +const config = JSON.parse(readFileSync(new URL("../wrangler.jsonc", import.meta.url))); +const digest = scryptSync("mllm_synthetic_incident_key_long_enough", "synthetic-salt", 64, + { N: 32768, r: 8, p: 1, maxmem: 64 * 1024 * 1024 }).toString("hex"); + +test("migrated account rows cross the real private RPC and registered Container routes", async t => { + const bundle = await build({ stdin: { resolveDir: process.cwd(), contents: ` + import {handleIntelligenceOutbound} from './worker/intelligence-outbound.mjs'; + export default {fetch(request,env) { + // The private account host is owned by the main Worker, never Knowledge. + env.KNOWLEDGE_SERVICE = {fetch(){throw new Error('Old Knowledge must not receive account requests')}}; + return handleIntelligenceOutbound(new Request('http://intelligence.internal'+new URL(request.url).pathname, request),env); + }};` }, bundle: true, format: "esm", platform: "neutral", external: ["node:*", "cloudflare:*"], write: false }); + const mf = new Miniflare(convertV4MiniflareOptions({ modules: true, script: bundle.outputFiles[0].text, + compatibilityFlags: ["nodejs_compat"], d1Databases: ["INTELLIGENCE_DB"], bindings: config.vars })); + t.after(() => mf.dispose()); + const db = await mf.getD1Database("INTELLIGENCE_DB"); + await applyMigrations(db); + // Insert a pre-existing account directly, letting the actual migrations supply + // NULL defaults. No helper manufactures a policy envelope or an RPC response. + await db.prepare(`INSERT INTO control_users(username,api_key_hash,api_key_prefix,scopes,is_admin,created_at, + last_used_at,last_used_ip,created_by,secret_scan_mode,shadow_eval_rate) + VALUES('synthetic',?,?,'chat,models',0,'2026-10-01T00:00:00Z','2026-10-02T00:00:00Z', + '192.0.2.1','admin','observe',0)`) + .bind(`scrypt:32768:8:1$synthetic-salt$${digest}`, "mllm_mllm_syn").run(); + const rpc = async body => { + const response = await mf.dispatchFetch("http://local/v1/users", { method: "POST", + headers: { "content-type": "application/json" }, body: JSON.stringify({ version: 1, ...body }) }); + return { status: response.status, body: await response.json() }; + }; + const containerEnv = collectContainerEnv({ ...config.vars, INTELLIGENCE_DB: db, KNOWLEDGE_SERVICE: {}, + ADMIN_API_KEY: "synthetic-bootstrap-admin-long-enough", FLASK_SECRET_KEY: "synthetic-session-secret-long-enough", + JWT_SECRET: "synthetic-jwt-secret-long-enough", OPENROUTER_API_KEY: "synthetic-operator" }); + assert.equal(containerEnv.CONFIG_REVISION_SYNC_ENABLED ?? "false", "false"); + assert.equal(containerEnv.STREAM_COST_BREAKER_ENABLED ?? "false", "false"); + const routes = async () => { + const { stdout } = await run(process.env.PYTHON || "python3", ["-I", "tests/support/account_envelope_driver.py", + (await mf.ready).href.replace(/\/$/, ""), JSON.stringify(containerEnv)], + { cwd: process.cwd(), env: { PATH: process.env.PATH }, timeout: 120000 }); + return stdout.split("\n").filter(line => line.startsWith('{"path":')).map(line => JSON.parse(line)); + }; + await t.test("unset flags and an old Knowledge binding do not break ordinary durable keys", async () => { + assert.deepEqual((await routes()).map(row => row.status), [200, 200]); + const current = await rpc({ operation: "get", username: "synthetic", data_policy_version: 1 }); + assert.deepEqual(current.body.user.data_policy, { version: 1, official_only: null, forced_provider_preferences: null }); + }); + await t.test("legacy readers receive the exact earlier row shape", async () => { + const legacy = await rpc({ operation: "get", username: "synthetic" }); + assert.equal(legacy.status, 200); + assert.equal(Object.keys(legacy.body.user).length, 19); + assert.equal(Object.hasOwn(legacy.body.user, "data_policy"), false); + assert.equal(Object.hasOwn(legacy.body.user, "official_only"), false); + const write = await rpc({ operation: "upsert", user: legacy.body.user }); + assert.equal(write.status, 200); + }); + await t.test("unsupported envelopes are refused", async () => { + assert.equal((await rpc({ operation: "get", username: "synthetic", data_policy_version: 2 })).status, 400); + }); + await db.prepare("UPDATE control_users SET official_only=1, forced_provider_preferences=? WHERE username='synthetic'") + .bind(JSON.stringify({ data_collection: "deny" })).run(); + await t.test("current controls reach the actual provider dispatch", async () => { + const rows = await routes(); + assert.deepEqual(rows.map(row => row.status), [200, 200]); + assert.equal(rows[1].preferences.data_collection, "deny"); + }); + await t.test("legacy readers and writers cannot erase or bypass controlled accounts", async () => { + assert.equal((await rpc({ operation: "get", username: "synthetic" })).status, 503); + const management = await rpc({ operation: "list", after: null, limit: 200 }); + assert.equal(management.status, 200); + const projected = management.body.users.find(row => row.username === "synthetic"); + assert.equal(Object.hasOwn(projected, "data_policy"), false); + assert.equal(Object.hasOwn(projected, "official_only"), false); + assert.equal(Object.hasOwn(projected, "forced_provider_preferences"), false); + const current = (await rpc({ operation: "get", username: "synthetic", data_policy_version: 1 })).body.user; + const legacy = Object.fromEntries(Object.entries(current).filter(([name]) => !["data_policy", "official_only", "forced_provider_preferences"].includes(name))); + assert.equal((await rpc({ operation: "upsert", user: legacy })).status, 200); + assert.equal((await rpc({ operation: "get", username: "synthetic", data_policy_version: 1 })).body.user.official_only, 1); + }); + await db.prepare("UPDATE control_users SET revoked_at='2026-10-20T00:00:00Z' WHERE username='synthetic'").run(); + await t.test("the same registered requests reject a revoked durable key", async () => { + assert.deepEqual((await routes()).map(row => row.status), [401, 401]); + }); +}); diff --git a/tests/test_cloudflare_worker.mjs b/tests/test_cloudflare_worker.mjs index e843ea5b..86f7b956 100644 --- a/tests/test_cloudflare_worker.mjs +++ b/tests/test_cloudflare_worker.mjs @@ -2439,7 +2439,7 @@ test("worker answers /health directly without touching the container", async () assert.equal(stub.getCalls(), 0); assert.deepEqual(await response.json(), { status: "healthy", - key_controls: {protocol: 1, enforcement: false}, + key_controls: {protocol: 2, enforcement: true}, mode: "worker-fallback", }); }); @@ -2451,7 +2451,7 @@ test("worker checks application readiness through the container health endpoint" assert.equal(forwardedUrl.pathname, "/healthz"); assert.equal(request.method, "GET"); assert.equal(request.redirect, "manual"); - return new Response(JSON.stringify({ status: "ready", database: "ok", main_key_controls: {protocol: 1, enforcement: false} }), { + return new Response(JSON.stringify({ status: "ready", database: "ok", main_key_controls: {protocol: 2, enforcement: true} }), { status: 200, headers: { "Content-Type": "application/json" }, }); @@ -2468,7 +2468,7 @@ test("worker checks application readiness through the container health endpoint" assert.equal(stub.getCalls(), 1); assert.equal(stub.getStartCalls(), 0); assert.equal(response.headers.get("Access-Control-Allow-Origin"), origin); - assert.deepEqual(await response.json(), { status: "ready", database: "ok", main_key_controls: {protocol: 1, enforcement: false} }); + assert.deepEqual(await response.json(), { status: "ready", database: "ok", main_key_controls: {protocol: 2, enforcement: true} }); assert.match(response.headers.get("X-MultiLLM-Worker-Build"), /^[0-9a-f]{64}$/); }); diff --git a/tests/test_config_revision_sync.py b/tests/test_config_revision_sync.py index 6024acf9..4c3e5850 100644 --- a/tests/test_config_revision_sync.py +++ b/tests/test_config_revision_sync.py @@ -312,7 +312,7 @@ def __call__(self, payload, *, endpoint): return {"version": 1, "overrides": []} if endpoint == "users": if payload["operation"] == "capabilities": - return {"version": 1, "key_controls": {"protocol": 1, "enforcement": False}} + return {"version": 1, "key_controls": {"protocol": 2, "enforcement": True}} assert payload["operation"] == "list", "Enabled authentication must use the installed copy" users = [row for row in state["users"] if payload["after"] is None or row["username"] > payload["after"]] return {"version": 1, "users": users[:payload["limit"]]} @@ -330,6 +330,8 @@ def account_states(changes=None): row.update(username="alice", api_key_hash=generate_password_hash("synthetic-revision-key"), api_key_prefix="mllm_syntheti", scopes="chat,models", is_admin=0, created_at="2026-10-09T00:00:00+00:00") + from services.dispatch_policy import envelope + row["data_policy"] = envelope(row) return [{"users": [row], "revisions": {"key_controls": 1, "model_grants": 1}}, {"users": [{**row, **(changes or {"revoked_at": "2026-10-09T01:00:00+00:00"})}], "revisions": {"key_controls": 2, "model_grants": 2}}] diff --git a/tests/test_dashboard_scripts.mjs b/tests/test_dashboard_scripts.mjs index 48a88b95..b2579d56 100644 --- a/tests/test_dashboard_scripts.mjs +++ b/tests/test_dashboard_scripts.mjs @@ -352,7 +352,7 @@ test('usage page and key limit dialog helpers format budgets and build control p daily_budget_usd: '2.5', monthly_budget_usd: '', allowed_models: 'auto:*\nfree:*, openai:gpt-4.1', allowed_ips: ' 203.0.113.0/24 ', expires_at: '2026-12-31T23:30', }); - assert.equal(JSON.stringify(payload), JSON.stringify({ secret_scan_mode: null, shadow_eval_rate: null, daily_budget_usd: 2.5, monthly_budget_usd: null, + assert.equal(JSON.stringify(payload), JSON.stringify({ official_only: false, forced_provider_preferences: null, secret_scan_mode: null, shadow_eval_rate: null, daily_budget_usd: 2.5, monthly_budget_usd: null, allowed_models: ['auto:*', 'free:*', 'openai:gpt-4.1'], allowed_ips: ['203.0.113.0/24'], expires_at: '2026-12-31T23:30:00Z' })); for (const source of ['static/js/usage.js', 'static/js/key-controls.js']) { assert.doesNotMatch(readFileSync(source, 'utf8'), /innerHTML/, source); diff --git a/tests/test_dispatch_network.py b/tests/test_dispatch_network.py new file mode 100644 index 00000000..d6635250 --- /dev/null +++ b/tests/test_dispatch_network.py @@ -0,0 +1,62 @@ +"""Unlisted modules and alternate clients cannot escape runtime admission.""" +import socket +import _socket +from contextlib import closing + +import pytest + +from services.dispatch_network import network_admitted +from services.provider_dispatch import provider_http + + +def test_urllib3_bypass_in_an_unlisted_module_fails_before_connection(): + namespace = {} + exec(compile("import urllib3\ndef send():\n return urllib3.PoolManager(retries=False).request('GET', 'http://127.0.0.1:9')", + "unlisted_transport.py", "exec"), namespace) + with pytest.raises(AssertionError, match="outside provider chokepoint"): + namespace["send"]() + + +@pytest.mark.parametrize("call", [lambda: socket.create_connection(("127.0.0.1", 9)), + lambda: socket.socket().connect(("127.0.0.1", 9)), lambda: socket.socket().connect_ex(("127.0.0.1", 9))]) +def test_lowest_socket_layers_require_admission(call): + with pytest.raises(AssertionError, match="outside provider chokepoint"): + call() + + +@pytest.mark.parametrize("method", ["connect", "connect_ex"]) +def test_extension_socket_bypass_is_audited_before_loopback_connection(method, verified_transport_principal): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: + server.bind(("127.0.0.1", 0)) + server.listen(1) + server.settimeout(0.02) + with closing(_socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as client: + assert not network_admitted() + with pytest.raises(AssertionError, match="outside provider chokepoint"): + getattr(client, method)(server.getsockname()) + with pytest.raises(TimeoutError): + server.accept() + + def admitted(**kwargs): + with closing(_socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as client: + assert network_admitted() + assert getattr(client, method)(server.getsockname()) in (None, 0) + client.sendall(b"synthetic") + connection, _ = server.accept() + with connection: + assert connection.recv(9) == b"synthetic" + provider_http("xai", "GET", "https://api.x.ai/v1/models", transport=admitted) + assert not network_admitted() + + +def test_capability_exists_only_during_admitted_transport(verified_transport_principal): + assert not network_admitted() + def send(**kwargs): + assert network_admitted() + raise RuntimeError("synthetic transport failure") + with pytest.raises(RuntimeError): + provider_http("xai", "GET", "https://api.x.ai/v1/models", transport=send) + assert not network_admitted() + + +from tests.dispatch_fixture import verified_transport_principal # noqa: E402, F401 diff --git a/tests/test_dispatch_network_worker.mjs b/tests/test_dispatch_network_worker.mjs new file mode 100644 index 00000000..ad528c90 --- /dev/null +++ b/tests/test_dispatch_network_worker.mjs @@ -0,0 +1,109 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import net from "node:net"; +import http from "node:http"; +import { readFile } from "node:fs/promises"; +import { networkAdmitted } from "../worker/dispatch-network.mjs"; +import { providerFetch, providerOperation } from "../worker/provider-dispatch.mjs"; +import { policyStore, dispatchEnv } from "./helpers/dispatch_fixture.mjs"; +import { providerBinding } from "./helpers/provider_binding.mjs"; + +const authority = { authenticated: true, keyId: "synthetic" }; + +test("SDK fixtures guard every method, nested handle and later replacement by default", async () => { + let calls = 0; + const binding = providerBinding({ run() { calls++; }, items: { get() { return { info() { calls++; } }; } } }); + assert.throws(() => binding.run(), /outside provider chokepoint/); + assert.throws(() => binding.items.get("synthetic"), /outside provider chokepoint/); + let handle; + await providerOperation("cloudflare", () => { binding.run(); handle = binding.items.get("synthetic"); handle.info(); }, + { env: dispatchEnv(), authority }); + assert.equal(calls, 2); + assert.throws(() => handle.info(), /outside provider chokepoint/); + binding.run = () => { calls++; }; + assert.throws(() => binding.run(), /outside provider chokepoint/); + assert.equal(calls, 2); +}); + +test("removing AI admission wrappers in memory fails before either SDK dispatch", async () => { + const url = new URL("../worker/ai-outbound.mjs", import.meta.url); + const source = await readFile(url, "utf8"); + let mutations = 0; + const mutated = source.replace(/providerOperation\("cloudflare", \(\) => (env\.AI\.run\([^\n]+\))\)/g, + (_, call) => { mutations++; return call; }) + .replace(/from (["'])(\.[^"']+)\1/g, (_, quote, path) => `from ${JSON.stringify(new URL(path, url).href)}`); + assert.equal(mutations, 2, "mutate both shared AI and speech dispatches"); + const { handleAiOutbound } = await import(`data:text/javascript,${encodeURIComponent(mutated)}`); + for (const [path, body] of [["embeddings", { model: "@cf/baai/bge-m3", input: "synthetic" }], + ["audio/speech", { model: "@cf/deepgram/aura-2-en", input: "synthetic" }]]) { + let calls = 0; + const env = { INTELLIGENCE_DB: policyStore(), AI: providerBinding({ run() { calls++; return {}; } }) }; + const response = await handleAiOutbound(new Request(`http://ai.internal/v1/${path}`, { + method: "POST", headers: { "content-type": "application/json", + "X-MultiLLM-Dispatch-Principal": '{"version":1,"username":"synthetic"}' }, body: JSON.stringify(body), + }), env); + assert.equal(response.status, 502, "an unadmitted SDK call cannot return success"); + assert.equal(calls, 0, "the provider double must reject before running its implementation"); + assert.equal(networkAdmitted(), false); + } +}); + +test("an unlisted axios-style adapter cannot bypass the global fetch guard", async t => { + const previous = globalThis.fetch; + t.after(() => { globalThis.fetch = previous; }); + let sends = 0; + globalThis.fetch = () => { sends++; return Response.json({}); }; + const source = "export default { get: url => fetch(url), request: config => fetch(config.url) };"; + const { default: axios } = await import(`data:text/javascript,${encodeURIComponent(source)}`); + assert.throws(() => axios.get("https://unrelated.example"), /outside provider chokepoint/); + assert.throws(() => axios.request({ url: "https://unrelated.example" }), /outside provider chokepoint/); + assert.equal(sends, 0); + await providerFetch("xai", "https://api.x.ai/v1/models", {}, { env: dispatchEnv(), authority }); + assert.equal(sends, 1); + assert.equal(networkAdmitted(), false); +}); + +test("axios-style node HTTP and direct socket adapters are guarded too", () => { + assert.throws(() => http.get("http://127.0.0.1:9"), /outside provider chokepoint/); + const socket = new net.Socket(); + try { assert.throws(() => socket.connect(9, "127.0.0.1"), /outside provider chokepoint/); } + finally { socket.destroy(); } +}); + +test("capability ends after failed HTTP and SDK transports", async () => { + const options = { env: dispatchEnv(), authority }; + await assert.rejects(providerFetch("xai", "https://api.x.ai/v1/models", {}, { ...options, send() { + assert.equal(networkAdmitted(), true); throw Error("synthetic transport failure"); + } }), /synthetic transport failure/); + assert.equal(networkAdmitted(), false); + await assert.rejects(providerOperation("cloudflare", async () => { + assert.equal(networkAdmitted(), true); throw Error("synthetic SDK failure"); + }, options), /synthetic SDK failure/); + assert.equal(networkAdmitted(), false); +}); + +test("public assets strip every credential header even with restricted controls", async () => { + const env = { INTELLIGENCE_DB: policyStore({ official_only: 1, forced_provider_preferences: null }), XAI_API_KEY: "synthetic-current" }; + await providerFetch("xai", "https://unrelated-cdn.example/asset.mp4", { headers: { + authorization: "Bearer synthetic-cached", "x-api-key": "synthetic", cookie: "synthetic", + } }, { env, authority, publicAsset: true, send(request) { + for (const name of ["authorization", "x-api-key", "cookie"]) assert.equal(request.headers.get(name), null); + return new Response("synthetic"); + } }); +}); + + +test("an API operation cannot inject operator secrets into another origin", async () => { + const env = { INTELLIGENCE_DB: policyStore({ official_only: 1, forced_provider_preferences: null }), XAI_API_KEY: "synthetic-current" }; + await assert.rejects(providerFetch("xai", "https://api.x.ai.unrelated.example/v1/videos", {}, { env, authority, + send() { assert.fail("Unapproved API destination"); } }), { code: "attested_credential_unavailable" }); +}); + +test("asynchronous work cannot retain a capability after its admitted call ends", async () => { + let release, delayed; + await providerOperation("cloudflare", async () => { + delayed = new Promise(resolve => { release = resolve; }).then(() => networkAdmitted()); + }, { env: dispatchEnv(), authority }); + release(); + assert.equal(await delayed, false); +}); diff --git a/tests/test_edge_request_metrics_worker.mjs b/tests/test_edge_request_metrics_worker.mjs index bb4d3034..9b7c37af 100644 --- a/tests/test_edge_request_metrics_worker.mjs +++ b/tests/test_edge_request_metrics_worker.mjs @@ -81,7 +81,7 @@ test("parser carry stays at most 64 KiB and recovers after oversized lines", () const parser = createUsageObserver(true); parser.feed(encoder.encode('data: ' + 'x'.repeat(200000))); assert.ok(parser.carryBytes <= 65536); parser.feed(encoder.encode('\n\ndata: {"usage":{"input_tokens":0,"output_tokens":2}}\n\ndata: [DONE]\n\n')); - assert.deepEqual(parser.finish().usage, { input_tokens: 0, output_tokens: 2 }); + assert.deepEqual(parser.finish().usage, { input_tokens: 0, output_tokens: 2, cached_input_tokens: null, reasoning_tokens: null }); }); test("partial usage, missing usage, stream error and cancellation remain unknown", async () => { @@ -211,7 +211,7 @@ test("explicit estimate collaborator retains measured zero and marks estimated c test("multiline UTF-8 SSE and large JSON keep bounds and original bytes", async () => { const parser = createUsageObserver(true), raw = 'data: {"usage":\r\ndata: {"input_tokens":0,"output_tokens":0}}\r\n\r\ndata: [DONE]\r\n\r\n'; const bytes = encoder.encode(raw); for (const byte of bytes) { parser.feed(new Uint8Array([byte])); assert.ok(parser.carryBytes <= 65536); } - assert.deepEqual(parser.finish().usage, { input_tokens: 0, output_tokens: 0 }); + assert.deepEqual(parser.finish().usage, { input_tokens: 0, output_tokens: 0, cached_input_tokens: null, reasoning_tokens: null }); const events = [], big = JSON.stringify({ output: "漢".repeat(40000), usage: { input_tokens: 2, output_tokens: 3 } }); const response = await observeNativeResponse(new Response(big), { provider: "linkapi" }, { finalize: event => events.push(event) }); assert.equal(await response.text(), big); assert.equal(events[0].usage_basis, "unknown"); assert.equal(events[0].cost_usd, null); diff --git a/tests/test_gemini_data_guards.py b/tests/test_gemini_data_guards.py new file mode 100644 index 00000000..a74c07f8 --- /dev/null +++ b/tests/test_gemini_data_guards.py @@ -0,0 +1,122 @@ +"""Paid-tier admission stays bound to the operator credential on every path.""" + +import json +import os +from unittest.mock import patch + +import requests + +from services.auto_route_service import AutoRoute +from tests.unified_api_test_case import UnifiedApiTestCase + + +class GeminiDataRouteTest(UnifiedApiTestCase): + def setUp(self): + super().setUp() + self.app.config["GEMINI_DATA_TIER"] = "paid" + os.environ["GEMINI_API_KEY"] = "AIza-attested-synthetic" + os.environ["OPENAI_API_KEY"] = "synthetic-operator" + auth = self.app_module.AuthService + with patch.object(auth, "get_current_user", return_value={"username": "admin", "is_admin": True}): + created = auth.create_user("guarded", is_admin=True) + auth.set_key_controls("guarded", {"official_only": True}) + self.key = created["api_key"] + + def endpoints(self): + return [("/gemini/v1beta/models/gemini-test-model:generateContent", {"contents": [{"parts": [{"text": "Synthetic"}]}]}), + ("/gemini/chat/completions", {"model": "gemini-test-model", "messages": [{"role": "user", "content": "Synthetic"}]}), + ("/v1/chat/completions", {"model": "gemini:gemini-test-model", "messages": [{"role": "user", "content": "Synthetic"}]})] + + @staticmethod + def native_response(): + response = requests.Response() + response.status_code = 200 + response._content = json.dumps({"candidates": [{"content": {"parts": [{"text": "Synthetic"}]}, "finishReason": "STOP"}]}).encode() + response.headers["Content-Type"] = "application/json" + return response + + def test_raw_native_and_unified_refuse_caller_credentials_before_upstream(self): + overrides = [("?key=AIza-caller-synthetic", {}), ("?%6bey=", {}), + ("", {"x-goog-api-key": "AIza-caller-synthetic"}), + ("", {"X-MultiLLM-Api-Key": self.key, "Authorization": "Bearer AIza-caller-synthetic"})] + with patch.object(self.app_module.ProxyService, "_make_base_request") as send: + for endpoint, payload in self.endpoints(): + for query, extra in overrides: + with self.subTest(endpoint=endpoint, override=query or next(iter(extra))): + response = self.client.post(endpoint + query, json=payload, + headers={"Authorization": f"Bearer {self.key}", **extra}) + assert response.status_code == 403, response.json + assert response.json["error"] == "provider_credential_not_allowed" + send.assert_not_called() + + def test_every_route_uses_only_the_attested_operator_key(self): + with patch.object(self.app_module.ProxyService, "_make_base_request", side_effect=lambda **_: self.native_response()) as send: + for endpoint, payload in self.endpoints(): + with self.subTest(endpoint=endpoint): + response = self.client.post(endpoint, json=payload, headers={"Authorization": f"Bearer {self.key}"}) + assert response.status_code == 200, response.json + headers = send.call_args.kwargs["headers"] + assert headers["x-goog-api-key"] == "AIza-attested-synthetic" + assert not any(name.lower() == "authorization" for name in headers) + + def test_missing_attested_binding_never_uses_cached_or_alias_credentials(self): + os.environ.pop("GEMINI_API_KEY") + with patch.object(self.app_module.AuthService, "get_api_key", return_value="AIza-unattested-synthetic"), \ + patch.object(self.app_module.ProxyService, "_make_base_request") as send: + for endpoint, payload in self.endpoints(): + with self.subTest(endpoint=endpoint): + response = self.client.post(endpoint, json=payload, headers={"Authorization": f"Bearer {self.key}"}) + assert response.status_code == 503, response.json + assert response.json["error"] == "attested_credential_unavailable" + send.assert_not_called() + + def test_automatic_fallback_refuses_overrides_and_missing_attestation(self): + from services.auto_route_service import AutoRouteService + route = AutoRoute("auto:attested", ("openai:first", "gemini:gemini-test-model"), "synthetic") + calls = [] + def send(**kwargs): + calls.append(kwargs) + assert kwargs["api_provider"] == "openai" + response = requests.Response() + response.status_code = 429 + response._content = b'{"error":{"message":"Synthetic limit"}}' + response.headers["Content-Type"] = "application/json" + return response + with patch.object(AutoRouteService, "get_route", side_effect=lambda name: route if name == route.id else None), \ + patch.object(self.app_module.ProxyService, "_make_base_request", side_effect=send): + for query, missing in [("?key=AIza-caller-synthetic", False), ("", True)]: + with self.subTest(missing=missing): + if missing: + os.environ.pop("GEMINI_API_KEY") + calls.clear() + response = self.client.post("/v1/chat/completions" + query, + json={"model": route.id, "messages": [{"role": "user", "content": "Synthetic"}]}, + headers={"Authorization": f"Bearer {self.key}"}) + assert response.status_code >= 400, response.json + assert len(calls) == 1, (response.status_code, response.json) + + +def test_transport_boundary_blocks_overrides_even_on_forced_raw_dispatch(monkeypatch): + import pytest + from flask import Flask, g + from error_handlers import APIError + from services.proxy_service import ProxyService + + monkeypatch.setenv("GEMINI_API_KEY", "AIza-attested-synthetic") + app = Flask(__name__) + app.config["GEMINI_DATA_TIER"] = "paid" + url = "https://provider.example/v1beta/models/example:generateContent" + overrides = [(url, {"key": "AIza-caller-synthetic"}, {}), + (url + "?key=AIza-caller-synthetic", {}, {}), + (url, {}, {"x-goog-api-key": "AIza-caller-synthetic"}), + (url, {}, {"authorization": "Bearer AIza-caller-synthetic"})] + with app.test_request_context(headers={"Authorization": "Bearer synthetic-gateway"}): + g.authenticated_user = {"official_only": True} + with patch.object(ProxyService, "_make_base_request") as send: + for force_raw in (False, True): + for target, params, headers in overrides: + with pytest.raises(APIError) as error: + ProxyService.make_request("POST", target, headers, params, b"{}", "gemini", + use_cache=False, force_raw_passthrough=force_raw) + assert error.value.status_code == 403 + send.assert_not_called() diff --git a/tests/test_intelligence_auth_worker.mjs b/tests/test_intelligence_auth_worker.mjs index 7428d837..fa1c1be8 100644 --- a/tests/test_intelligence_auth_worker.mjs +++ b/tests/test_intelligence_auth_worker.mjs @@ -25,6 +25,15 @@ test("D1 credentials survive new callers, rotate atomically and retain revocatio const added = await readFile(new URL("../intelligence-migrations/0043_integration_data_policy.sql", import.meta.url), "utf8"); for (const statement of added.split(";").filter(x=>x.trim())) await db.prepare(statement).run(); assert.equal((await lookup(prefix)).id,"integration:omni"); + for (const [official, forced] of [[0, null], [null, '{}'], [1, null], [null, '{"data_collection":"deny"}']]) { + await db.prepare("UPDATE intelligence_principals SET official_only=?,forced_provider_preferences=? WHERE id='integration:omni'") + .bind(official, forced).run(); + const denied = await call({operation:"lookup",keyPrefix:prefix}); + assert.equal(denied.status,503); + assert.equal((await denied.json()).principal,undefined); + } + await db.prepare("UPDATE intelligence_principals SET official_only=NULL,forced_provider_preferences=NULL").run(); + const next = "mllm_intelligence_" + "b".repeat(16); const rotate = {operation:"rotate",principalId:"integration:omni",expectedVersion:1,keyPrefix:next,keyHash:hash}; const races = await Promise.all([call(rotate),call(rotate)]); diff --git a/tests/test_key_controls.py b/tests/test_key_controls.py index 97fc39d5..729ab7b6 100644 --- a/tests/test_key_controls.py +++ b/tests/test_key_controls.py @@ -20,7 +20,7 @@ def test_validation_normalizes_every_control(): "daily_budget_usd": 5.0, "monthly_budget_usd": 50.123457, "allowed_models": "auto:*,free:*,openai:gpt-4.1", "allowed_ips": "203.0.113.7/32,198.51.100.0/24,2001:db8::/32,192.0.2.1/32", - "expires_at": "2026-10-01T12:00:00+00:00", "secret_scan_mode": None, "shadow_eval_rate": None, + "expires_at": "2026-10-01T12:00:00+00:00", "secret_scan_mode": None, "shadow_eval_rate": None, "official_only": None, "forced_provider_preferences": None, } assert key_controls.validate({}) == key_controls.empty() assert key_controls.validate({"allowed_models": [], "allowed_ips": "", "daily_budget_usd": None}) == key_controls.empty() @@ -132,7 +132,7 @@ def test_sqlite_accounts_keep_controls_through_rotation_and_backups(tmp_path, mo def test_stored_values_are_read_defensively(): row = {"daily_budget_usd": 2, "monthly_budget_usd": "9", "allowed_models": "", "allowed_ips": "10.0.0.0/8", - "expires_at": None} + "expires_at": None, "official_only": None, "forced_provider_preferences": None} assert key_controls.from_storage(row) == {"daily_budget_usd": 2.0, "monthly_budget_usd": None, - "allowed_models": None, "allowed_ips": "10.0.0.0/8", "expires_at": None, "secret_scan_mode": None, "shadow_eval_rate": None} + "allowed_models": None, "allowed_ips": "10.0.0.0/8", "expires_at": None, "secret_scan_mode": None, "shadow_eval_rate": None, "official_only": None, "forced_provider_preferences": None} assert key_controls.public(row)["allowed_ips"] == ["10.0.0.0/8"] diff --git a/tests/test_key_controls_contract_worker.mjs b/tests/test_key_controls_contract_worker.mjs new file mode 100644 index 00000000..7de63846 --- /dev/null +++ b/tests/test_key_controls_contract_worker.mjs @@ -0,0 +1,179 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { readFileSync } from "node:fs"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { scryptSync } from "node:crypto"; +import { runtimeBundle, runtimeModule, MemoryStorage } from "./helpers/runtime_versions.mjs"; +import { Miniflare, convertV4MiniflareOptions } from "./helpers/local_miniflare.mjs"; +import { applyMigrations } from "./d1_migrations.mjs"; +import { collectContainerEnv } from "../worker/container-env.mjs"; +import { requirePeer } from "../worker/peer-capability.mjs"; + +const run = promisify(execFile), A = "b6b0e43", versions = [A, ""]; +const config = JSON.parse(readFileSync(new URL("../wrangler.jsonc", import.meta.url))); +const policies = [ + { official_only: null, forced_provider_preferences: null }, + { official_only: 1, forced_provider_preferences: null }, + { official_only: null, forced_provider_preferences: '{"data_collection":"deny"}' }, + { official_only: 0, forced_provider_preferences: null }, + { official_only: null, forced_provider_preferences: '{}' }, +]; +const key = "mllm_synthetic_incident_key_long_enough"; +const digest = scryptSync(key, "syntheticsalt", 64, { N: 32768, r: 8, p: 1, maxmem: 64 * 1024 * 1024 }).toString("hex"); +const privateEntry = `export {default,KnowledgeCatalogue} from './worker/knowledge/index.mjs'; +export {defaultPolicy} from './worker/knowledge/policy.mjs';`; + +test("every expansion/enforcement runtime combination preserves NULL accounts and fences controlled rollback", async t => { + const mains = new Map(), privates = new Map(); + for (const version of versions) { + mains.set(version, (await runtimeModule("export {default} from './cloudflare-worker.mjs';", version)).default); + privates.set(version, await runtimeModule(privateEntry, version)); + } + const original = globalThis.fetch; + let external = 0; + globalThis.fetch = async () => { external++; return Response.json({ results: [] }); }; + t.after(() => { globalThis.fetch = original; }); + for (const main of versions) { + const script = await runtimeBundle(`import {handleIntelligenceOutbound} from './worker/intelligence-outbound.mjs'; + export default {fetch(request,env){return handleIntelligenceOutbound(new Request('http://intelligence.internal'+new URL(request.url).pathname,request),env)}}`, main, "neutral"); + const mf = new Miniflare(convertV4MiniflareOptions({ modules: true, script, compatibilityFlags: ["nodejs_compat"], + d1Databases: ["INTELLIGENCE_DB"], bindings: config.vars })); + try { + const db = await mf.getD1Database("INTELLIGENCE_DB"); + await applyMigrations(db); + await db.prepare(`INSERT INTO control_users(username,api_key_hash,api_key_prefix,scopes,is_admin,created_at) + VALUES('synthetic',?,?,'chat,models,knowledge:read,knowledge:manage',0,'2026-10-01T00:00:00Z')`) + .bind(`scrypt:32768:8:1$syntheticsalt$${digest}`, "mllm_mllm_syn").run(); + // A restricted neighbour must not interrupt a different NULL-policy account. + await db.prepare(`INSERT INTO control_users(username,api_key_hash,api_key_prefix,scopes,is_admin,created_at,official_only) + VALUES('synthetic-neighbour',?,'synthetic-other-prefix','chat',0,'2026-10-01T00:00:00Z',1)`) + .bind(`scrypt:32768:8:1$syntheticsalt$${digest}`).run(); + await db.prepare("INSERT INTO intelligence_principals(id,scopes,version,created_at) VALUES('integration:synthetic','[\"chat\"]',1,'2026-10-01T00:00:00Z')").run(); + await db.prepare("INSERT INTO intelligence_credentials(principal_id,version,key_prefix,key_hash) VALUES('integration:synthetic',1,'mllm_intelligence_aaaaaaaaaaaaaaaa',?)") + .bind(`scrypt:32768:8:1$syntheticsalt$${digest}`).run(); + const settings = collectContainerEnv({ ...config.vars, ADMIN_API_KEY: "synthetic-bootstrap-long-key", + FLASK_SECRET_KEY: "synthetic-session-long-enough", JWT_SECRET: "synthetic-jwt-long-enough", OPENROUTER_API_KEY: "synthetic-operator" }); + // Exercise Container authentication directly too: front-door rejection alone + // cannot prove that a legacy account RPC preserved the rollback fence. + for (const policy of policies) for (const reader of [A, "9153952"]) { + await db.prepare("UPDATE control_users SET official_only=?,forced_provider_preferences=? WHERE username='synthetic'") + .bind(policy.official_only, policy.forced_provider_preferences).run(); + await db.prepare("UPDATE intelligence_principals SET official_only=?,forced_provider_preferences=? WHERE id='integration:synthetic'") + .bind(policy.official_only, policy.forced_provider_preferences).run(); + const { stdout } = await run(process.env.PYTHON || "python3", ["-I", "tests/support/account_envelope_driver.py", + (await mf.ready).href.replace(/\/$/, ""), JSON.stringify(settings), reader, + JSON.stringify([["/v1/chat/completions", "post"], ["/probe/integration", "post"]])], + { cwd: process.cwd(), env: { PATH: process.env.PATH }, timeout: 120000 }); + const rows = stdout.split("\n").filter(line => line.startsWith('{"path":')).map(JSON.parse); + assert.deepEqual(rows.map(row => row.status), policy === policies[0] ? [200, 200] : [503, 503]); + assert.deepEqual(rows.map(row => row.dispatches), policy === policies[0] ? [1, 2] : [0, 0]); + } + for (const container of versions) for (const privateRef of versions) for (const [index, policy] of policies.entries()) { + await t.test(`${main || "B"}/${container || "B"}/${privateRef || "B"}/policy-${index}`, async () => { + await db.prepare("UPDATE control_users SET official_only=?,forced_provider_preferences=? WHERE username='synthetic'") + .bind(policy.official_only, policy.forced_provider_preferences).run(); + let forwarded = 0, privatePosts = 0, containerDispatches = 0, submittedPreferences; + external = 0; + const env = { ...config.vars, ADMIN_API_KEY: "synthetic-bootstrap-long-key", EXA_API_KEY: "synthetic-operator", INTELLIGENCE_DB: db }; + const knowledge = privates.get(privateRef), storage = new MemoryStorage(), configuration = knowledge.defaultPolicy(); + configuration.enabled = true; + Object.assign(configuration.providers.exa, { enabled: true, limit: 100, background_limit: 50, hard_limit_confirmed: true, retention_allowed: true }); + await storage.put("policy", configuration); + const catalogue = new knowledge.KnowledgeCatalogue({ storage }, env); + env.KNOWLEDGE_AUTHORITY = { idFromName: () => 1, get: () => ({ fetch: (url, init) => catalogue.fetch(new Request(url, init)) }) }; + env.KNOWLEDGE_SERVICE = { fetch: (url, init) => { + if (init?.method === "POST") privatePosts++; + return knowledge.default.fetch(new Request(url, init), env, { waitUntil() {} }); + } }; + env.MULTILLM_PROXY_CONTAINER = { getByName: () => ({ async fetch(input) { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname, health = path === "/healthz"; + if (!health) forwarded++; + const { stdout } = await run(process.env.PYTHON || "python3", ["-I", "tests/support/account_envelope_driver.py", + (await mf.ready).href.replace(/\/$/, ""), JSON.stringify(settings), container, + JSON.stringify([[path, health || request.method === "GET" ? "get" : "post"]])], + { cwd: process.cwd(), env: { PATH: process.env.PATH }, timeout: 120000 }); + const row = stdout.split("\n").filter(line => line.startsWith('{"path":')).map(JSON.parse).at(-1); + containerDispatches += row.dispatches; + if (row.dispatches) submittedPreferences = row.preferences; + return Response.json(row.body, { status: row.status }); + } }) }; + // Read the complete account set with real login/session/CSRF handling. + // Neither the current user's policy nor another user's may disable management. + const { stdout: management, stderr: managementErrors } = await run(process.env.PYTHON || "python3", ["-I", "tests/support/account_envelope_driver.py", + (await mf.ready).href.replace(/\/$/, ""), JSON.stringify(settings), container, + JSON.stringify([["/login", "post", "synthetic"], ["/login", "post", "admin"], ["/users", "get"]])], + { cwd: process.cwd(), env: { PATH: process.env.PATH }, timeout: 120000 }); + const results = management.split("\n").filter(line => line.startsWith('{"path":')).map(JSON.parse); + assert.deepEqual(results.map(row => row.status), [302, 302, 200], managementErrors); + assert.ok(results.every(row => row.dispatches === 0)); + assert.deepEqual(await db.prepare("SELECT official_only,forced_provider_preferences FROM control_users WHERE username='synthetic'").first(), policy, + "Dashboard sign-in must preserve stored restrictions"); + const worker = mains.get(main); + if (index === 0) { + const models = await worker.fetch(new Request("https://gateway.example/v1/models", { + headers: { authorization: `Bearer ${key}` }, + }), env, { waitUntil() {} }); + assert.equal(models.status, 200, "unrestricted models during rollback: " + await models.clone().text()); + } + const chat = await worker.fetch(new Request("https://gateway.example/v1/chat/completions", { + method: "POST", headers: { authorization: `Bearer ${key}`, "content-type": "application/json" }, + body: JSON.stringify({ model: "openrouter:example", messages: [{ role: "user", content: "synthetic" }] }), + }), env, { waitUntil() {} }); + const chatAllowed = index === 0 || !main && !container; + assert.equal(chat.status, chatAllowed ? 200 : 503, "chat status: " + await chat.clone().text()); + assert.equal(containerDispatches, chatAllowed ? 1 : 0, "provider dispatches"); + if (chatAllowed && index === 2) assert.equal(submittedPreferences.data_collection, "deny"); + if (!chatAllowed && main === "") assert.equal(forwarded, 0, "No content sent to an expansion Container"); + const response = await worker.fetch(new Request("https://gateway.example/v1/knowledge/native/exa_search", { + method: "POST", headers: { authorization: `Bearer ${key}`, "content-type": "application/json" }, body: JSON.stringify({ query: "synthetic" }), + }), env, { waitUntil() {} }); + if (index === 0) { + assert.equal(response.status, 200, "unrestricted Knowledge: " + await response.clone().text()); + assert.equal(external, 1); + } else { + assert.ok(response.status >= 400, "controlled Knowledge must be enforced or refused"); + assert.equal(external, 0); + if (main === "" && privateRef === A) assert.equal(privatePosts, 0, "No content sent to an expansion private Worker"); + } + }); + } + } finally { await mf.dispose(); } + } +}); + +test("controlled peer admission refuses absent, malformed, failed and stale capabilities without a content request", async () => { + for (const capability of [null, {}, { protocol: 1, enforcement: false }, { protocol: 2, enforcement: false }, { protocol: "2", enforcement: true }]) { + const calls = []; + await assert.rejects(requirePeer({ fetch(url, init) { calls.push(init.method); return Response.json({ key_controls: capability }); } }, "http://knowledge.internal/ready"), { status: 503 }); + assert.deepEqual(calls, ["GET"]); + } + await assert.rejects(requirePeer({ fetch() { throw Error("unavailable"); } }, "http://knowledge.internal/ready"), { status: 503 }); +}); + +test("private Knowledge accepts headerless NULL accounts but refuses controlled legacy or mismatched senders", async () => { + const { default: knowledge, KnowledgeCatalogue } = await runtimeModule(privateEntry); + const { policyStore } = await import("./helpers/dispatch_fixture.mjs"); + for (const [index, policy] of policies.entries()) { + let delegated = 0; + const env = { INTELLIGENCE_DB: policyStore(policy) }; + const catalogue = new KnowledgeCatalogue({ storage: new MemoryStorage() }, env); + env.KNOWLEDGE_AUTHORITY = { idFromName: () => 1, get: () => ({ fetch(url, init) { + delegated++; return catalogue.fetch(new Request(url, init)); + } }) }; + const response = await knowledge.fetch(new Request("http://knowledge.internal/v1/dispatch", { method: "POST", + headers: { "content-type": "application/json" }, body: JSON.stringify({ version: 1, operation: "status", + principal: { id: "synthetic", scopes: ["knowledge:manage"] }, payload: {} }), + }), env, { waitUntil() {} }); + if (index) { assert.equal(response.status, 503); assert.equal(delegated, 0); } + else assert.equal(response.status, 200); + } + const { handleKnowledgeOutbound } = await import("../worker/knowledge-outbound.mjs"); + let sent = 0; + const response = await handleKnowledgeOutbound(new Request("http://knowledge.internal/v1/dispatch", { method: "POST", + headers: { "content-type": "application/json", "X-MultiLLM-Dispatch-Principal": JSON.stringify({ version: 1, username: "different" }) }, + body: JSON.stringify({ version: 1, operation: "status", principal: { id: "synthetic", scopes: ["knowledge:manage"] }, payload: {} }), + }), { INTELLIGENCE_DB: policyStore(), KNOWLEDGE_SERVICE: { fetch() { sent++; return Response.json({}); } } }); + assert.equal(response.status, 403); assert.equal(sent, 0); +}); diff --git a/tests/test_key_controls_expand.py b/tests/test_key_controls_expand.py index 2d22a767..93bf46ae 100644 --- a/tests/test_key_controls_expand.py +++ b/tests/test_key_controls_expand.py @@ -1,166 +1,9 @@ -"""Expansion preserves legacy rows and fences every future controlled dispatch.""" +"""Run the expansion assertions against the pinned rollback target, never enforcement code.""" import subprocess import sys -import types -from unittest.mock import MagicMock, Mock -import pytest -from flask import Flask, g -from error_handlers import APIError -from services.dispatch_policy import envelope -from services.provider_dispatch import capture_dispatch, dispatch_as, provider_http -from services.user_store import USER_FIELDS, _row - - -def account(policy): - return {"username": "synthetic", "data_policy": envelope(policy)} - - -@pytest.mark.parametrize("controls", [ - {"official_only": None, "forced_provider_preferences": None}, - {"official_only": 1, "forced_provider_preferences": None}, - {"official_only": 0, "forced_provider_preferences": None}, - {"official_only": False, "forced_provider_preferences": None}, - {"official_only": None, "forced_provider_preferences": '{}'}, - {"official_only": None, "forced_provider_preferences": '{"data_collection":"deny"}'}, -]) -@pytest.mark.parametrize("provider", ["openai", "gemini", "openrouter", "linkapi", "knowledge", "cloudflare"]) -def test_future_policies_refuse_before_every_transport_but_nulls_keep_working(controls, provider, monkeypatch): - current = account(controls) - monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: current) - send = Mock() - with dispatch_as({"username": "synthetic"}): - if controls["official_only"] is not None or controls["forced_provider_preferences"] is not None: - with pytest.raises(APIError) as error: - provider_http(provider, "POST", "https://provider.example/generate", transport=send) - assert error.value.status_code == 503 - send.assert_not_called() - else: - provider_http(provider, "POST", "https://provider.example/generate", transport=send) - send.assert_called_once() - - -def test_background_callback_rereads_policy_after_rollback(monkeypatch): - current = account({"official_only": None, "forced_provider_preferences": None}) - monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: current) - send = Mock() - with Flask(__name__).test_request_context(): - g.authenticated_user = current - callback = capture_dispatch(lambda: provider_http("openai", "POST", "https://provider.example/", transport=send)) - current = account({"official_only": 1, "forced_provider_preferences": None}) - with pytest.raises(APIError) as error: - callback() - assert error.value.status_code == 503 - send.assert_not_called() - - -def test_both_row_shapes_remain_readable_without_a_global_account_list_fence(): - row = dict.fromkeys(USER_FIELDS) - row.update(username="synthetic", api_key_hash="synthetic", api_key_prefix="synthetic", scopes="chat", - is_admin=0, created_at="2026-10-01T00:00:00Z") - assert _row(row) == row - controlled = {"official_only": 1, "forced_provider_preferences": None} - assert _row({**row, **controlled, "data_policy": envelope(controlled), "future_metadata": 1})["data_policy"]["official_only"] == 1 - - -@pytest.mark.parametrize("policy", [{}, {"version": 1, "official_only": 1}, - {"version": 1, "official_only": "invalid", "forced_provider_preferences": None}]) -def test_malformed_current_policy_never_dispatches(policy, monkeypatch): - monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: {"data_policy": policy}) - send = Mock() - with dispatch_as({"username": "synthetic"}), pytest.raises(APIError) as error: - provider_http("openai", "POST", "https://provider.example/", transport=send) - assert error.value.status_code == 503 - send.assert_not_called() - - -@pytest.mark.parametrize("controls", [{"official_only": 1, "forced_provider_preferences": None}, - {"official_only": 0, "forced_provider_preferences": None}, - {"official_only": False, "forced_provider_preferences": None}, - {"official_only": None, "forced_provider_preferences": '{}'}, - {"official_only": None, "forced_provider_preferences": '{"data_collection":"deny"}'}]) -def test_media_and_both_shadow_dispatches_refuse_future_controls(controls, monkeypatch): - from services.media_storage import fetch_public - from services.shadow_eval_data_policy import guarded_dispatch - monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: account(controls)) - send = Mock() - session = Mock() - session.__enter__ = Mock(return_value=session) - session.__exit__ = Mock(return_value=False) - monkeypatch.setattr("services.media_storage._session", lambda: session) - with dispatch_as({"username": "synthetic"}), pytest.raises(APIError) as error: - fetch_public("https://images.example.com/asset.png") - assert error.value.status_code == 503 - session.request.assert_not_called() - for role in ("candidate", "judge"): - with pytest.raises(APIError) as error: - guarded_dispatch({"key_id": "synthetic"}, send)({"role": role}) - assert error.value.status_code == 503 - send.assert_not_called() - - -OLD = "544bf49ba3b3d03d185ec5b5a71b2b1877dda786" - - -def pinned_module(name, monkeypatch): - module = types.ModuleType("pinned_" + name) - monkeypatch.setitem(sys.modules, module.__name__, module) - source = subprocess.check_output(["git", "show", f"{OLD}:services/{name}.py"], text=True) - exec(compile(source, f"{OLD}:{name}", "exec"), module.__dict__) - return module - - -@pytest.fixture -def null_principal(monkeypatch): - monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: { - "username": "synthetic", "data_policy": envelope({"official_only": None, "forced_provider_preferences": None})}) - with dispatch_as({"username": "synthetic"}): - yield - - -def test_old_and_expansion_video_api_operations_do_not_follow_redirects(monkeypatch, null_principal): - from services import video_generation - for module in (pinned_module("video_generation", monkeypatch), video_generation): - send = Mock() - monkeypatch.setattr(module.requests, "request", send) - options = {"provider": "xai"} if module is video_generation else {} - module._send("POST", "https://api.x.ai/v1/videos/generations", timeout=10, **options) - assert send.call_args.kwargs["allow_redirects"] is False - - -def test_old_and_expansion_private_ai_posts_do_not_follow_redirects(monkeypatch, null_principal): - from services import cloudflare_ai - for module in (pinned_module("cloudflare_ai", monkeypatch), cloudflare_ai): - session = MagicMock() - session.__enter__.return_value = session - response = MagicMock(status_code=302, headers={}) - response.iter_content.return_value = iter([b"redirect"]) - session.request.return_value = response - session.post.return_value = response - monkeypatch.setattr(module.requests, "Session", lambda: session) - result = module.post("/v1/images/generations", {"model": "synthetic"}, 10) - assert result.status_code == 302 - send = session.request if module is cloudflare_ai else session.post - assert send.call_args.kwargs["allow_redirects"] is False - - -@pytest.mark.parametrize("redirects", [False, True]) -def test_expansion_boundary_keeps_explicit_python_redirect_mode(redirects, null_principal): - send = Mock() - provider_http("xai", "GET", "https://api.x.ai/v1/videos/synthetic", transport=send, allow_redirects=redirects) - assert send.call_args.kwargs["allow_redirects"] is redirects - - -@pytest.mark.parametrize("controls", [ - {"official_only": 0, "forced_provider_preferences": None}, - {"official_only": False, "forced_provider_preferences": None}, - {"official_only": None, "forced_provider_preferences": {}}, - {"official_only": None, "forced_provider_preferences": '{}'}, -]) -def test_non_null_policies_never_enter_shadow_sampling(controls): - from services.shadow_eval_contract import eligible, make_sample - user = {"username": "synthetic", "shadow_eval_rate": 0.2, **controls} - payload = {"model": "auto:synthetic", "messages": [{"role": "user", "content": "synthetic"}]} - assert not eligible(payload, user, "/v1/chat/completions") - assert make_sample(payload, user, "auto:synthetic", {"content": "synthetic"}, "openai:synthetic", 1, {}) is None +def test_pinned_expansion_runtime_contracts(): + result = subprocess.run([sys.executable, "-I", "tests/support/expand_snapshot_driver.py"], + capture_output=True, text=True, timeout=120) + assert result.returncode == 0, result.stdout + result.stderr diff --git a/tests/test_key_controls_expand_worker.mjs b/tests/test_key_controls_expand_worker.mjs index bcd61fc7..552f0563 100644 --- a/tests/test_key_controls_expand_worker.mjs +++ b/tests/test_key_controls_expand_worker.mjs @@ -8,11 +8,16 @@ import { OLD, runtimeBundle, runtimeModule, MemoryStorage } from "./helpers/runt import { Miniflare, convertV4MiniflareOptions } from "./helpers/local_miniflare.mjs"; import { applyMigrations } from "./d1_migrations.mjs"; import { collectContainerEnv } from "../worker/container-env.mjs"; -import { handleControlUsersRequest } from "../worker/control-users-d1.mjs"; -import { providerFetch, providerOperation } from "../worker/provider-dispatch.mjs"; + + import { controlNetwork } from "../worker/dispatch-network.mjs"; import { policyStore } from "./helpers/dispatch_fixture.mjs"; +const EXPAND = "b6b0e43"; +const { handleControlUsersRequest, providerFetch, providerOperation, legacyBindingControls } = await runtimeModule(` +export {handleControlUsersRequest} from './worker/control-users-d1.mjs'; +export {providerFetch,providerOperation} from './worker/provider-dispatch.mjs'; +export {legacyBindingControls} from './worker/legacy-binding-policy.mjs';`, EXPAND); const run = promisify(execFile); const config = JSON.parse(readFileSync(new URL("../wrangler.jsonc", import.meta.url))); const states = [ @@ -35,11 +40,11 @@ test("expansion refuses future controls at every Worker provider boundary", asyn test("old and expansion account RPC and Container pairs keep NULL-policy routes working", async t => { const digest = scryptSync("mllm_synthetic_incident_key_long_enough", "synthetic-salt", 64, { N: 32768, r: 8, p: 1, maxmem: 64 * 1024 * 1024 }).toString("hex"); - for (const revision of [OLD, ""]) { - const admission = revision ? "" : `import {providerFetch} from './worker/provider-dispatch.mjs';`; + for (const revision of [OLD, EXPAND]) { + const admission = revision === OLD ? "" : `import {providerFetch} from './worker/provider-dispatch.mjs';`; const script = await runtimeBundle(`${admission} import {handleIntelligenceOutbound} from './worker/intelligence-outbound.mjs'; export default {async fetch(request,env){ - ${revision ? "" : `if(new URL(request.url).pathname==='/probe-provider') { + ${revision === OLD ? "" : `if(new URL(request.url).pathname==='/probe-provider') { try { return await providerFetch('openrouter','https://openrouter.ai/api/v1/chat/completions',{}, {env,authority:{legacyBinding:true},send:()=>Response.json({ok:true})}); } catch { return Response.json({error:'key_controls_require_upgrade'},{status:503}); } @@ -55,7 +60,7 @@ test("old and expansion account RPC and Container pairs keep NULL-policy routes .bind(`scrypt:32768:8:1$synthetic-salt$${digest}`, "mllm_mllm_syn").run(); const settings = collectContainerEnv({ ...config.vars, ADMIN_API_KEY: "synthetic-bootstrap-long-enough", FLASK_SECRET_KEY: "synthetic-session-long-enough", JWT_SECRET: "synthetic-jwt-long-enough", OPENROUTER_API_KEY: "synthetic-operator" }); - for (const container of [OLD, ""]) await t.test(`${revision || "expand"} RPC / ${container || "expand"} Container`, async () => { + for (const container of [OLD, EXPAND]) await t.test(`${revision || "expand"} RPC / ${container || "expand"} Container`, async () => { const { stdout } = await run(process.env.PYTHON || "python3", ["-I", "tests/support/account_envelope_driver.py", (await mf.ready).href.replace(/\/$/, ""), JSON.stringify(settings), container, JSON.stringify([["/v1/models", "get"], ["/v1/chat/completions", "post"], ["/login", "post", "synthetic"], ["/login", "post", "admin"], ["/users", "get"]])], @@ -63,11 +68,11 @@ test("old and expansion account RPC and Container pairs keep NULL-policy routes const rows = stdout.split("\n").filter(line => line.startsWith('{"path":')).map(JSON.parse); assert.deepEqual(rows.map(row => row.status), [200, 200, 302, 302, 200]); }); - if (!revision) { + if (revision === EXPAND) { // A future controlled row must never poison login or a full management cache reload. await db.prepare("UPDATE control_users SET official_only=1 WHERE username='synthetic'").run(); const { stdout: management } = await run(process.env.PYTHON || "python3", ["-I", "tests/support/account_envelope_driver.py", - (await mf.ready).href.replace(/\/$/, ""), JSON.stringify(settings), "", + (await mf.ready).href.replace(/\/$/, ""), JSON.stringify(settings), EXPAND, JSON.stringify([["/login", "post", "synthetic"], ["/login", "post", "admin"], ["/users", "get"], ["/v1/chat/completions", "post", "admin"]])], { cwd: process.cwd(), env: { PATH: process.env.PATH }, timeout: 120000 }); const results = management.split("\n").filter(line => line.startsWith('{"path":')).map(JSON.parse); @@ -95,7 +100,7 @@ test("old and expansion account RPC and Container pairs keep NULL-policy routes await db.prepare("UPDATE control_users SET official_only=?,forced_provider_preferences=? WHERE username='synthetic'") .bind(policy.official_only, policy.forced_provider_preferences).run(); const { stdout } = await run(process.env.PYTHON || "python3", ["-I", "tests/support/account_envelope_driver.py", - (await mf.ready).href.replace(/\/$/, ""), JSON.stringify(settings)], + (await mf.ready).href.replace(/\/$/, ""), JSON.stringify(settings), EXPAND], { cwd: process.cwd(), env: { PATH: process.env.PATH }, timeout: 120000 }); const rows = stdout.split("\n").filter(line => line.startsWith('{"path":')).map(JSON.parse); assert.equal(rows.at(-1).status, 503); @@ -136,12 +141,12 @@ test("old and expansion Knowledge pairs keep unrestricted requests identical; ro const entry = `export {default,KnowledgeCatalogue} from './worker/knowledge/index.mjs'; export {defaultPolicy} from './worker/knowledge/policy.mjs'; export {handleKnowledgeEdgeRequest} from './worker/knowledge-edge.mjs';`; - const modules = new Map([[OLD, await runtimeModule(entry, OLD)], ["", await runtimeModule(entry, "")]]); + const modules = new Map([[OLD, await runtimeModule(entry, OLD)], [EXPAND, await runtimeModule(entry, EXPAND)]]); const original = globalThis.fetch; let dispatches = 0; globalThis.fetch = async () => { dispatches++; return Response.json({ results: [] }); }; try { - for (const main of [OLD, ""]) for (const privateRef of [OLD, ""]) for (const policy of main === OLD && privateRef === OLD ? states.slice(0, 1) : states) { + for (const main of [OLD, EXPAND]) for (const privateRef of [OLD, EXPAND]) for (const policy of main === OLD && privateRef === OLD ? states.slice(0, 1) : states) { dispatches = 0; const edge = modules.get(main), knowledge = modules.get(privateRef); const storage = new MemoryStorage(), configuration = knowledge.defaultPolicy(); @@ -180,8 +185,7 @@ test("legacy background work runs only while every durable policy remains NULL", test("registered Container forwarding refuses controlled accounts before sending any body", async () => { - const { loadWorkerModule } = await import("./helpers/load_cloudflare_worker.mjs"); - const worker = (await loadWorkerModule()).default; + const worker = (await runtimeModule("export {default} from './cloudflare-worker.mjs';", EXPAND)).default; for (const policy of states) for (const path of ["/v1/chat/completions", "/v1/images/generations", "/v1/videos/generations", "/v1/embeddings", "/v1/audio/speech", "/gemini/v1beta/models/example:generateContent", "/v1/models"]) { let sent = 0; @@ -197,7 +201,6 @@ test("registered Container forwarding refuses controlled accounts before sending }); test("a fenced expansion Container preserves a NULL private call beside other controlled accounts", async () => { - const { legacyBindingControls } = await import("../worker/legacy-binding-policy.mjs"); for (const policy of states.slice(1)) { const database = policyStore(policy); for (const capability of [{ protocol: 1, enforcement: false }, { protocol: 0 }, null]) { diff --git a/tests/test_key_data_guards.py b/tests/test_key_data_guards.py new file mode 100644 index 00000000..5bfddf71 --- /dev/null +++ b/tests/test_key_data_guards.py @@ -0,0 +1,152 @@ +"""Data controls protect an app whose requests carry personal data.""" + +import json +from types import SimpleNamespace +from unittest.mock import patch + +import pytest +from flask import Flask, Response, g + +from error_handlers import APIError +from services import key_controls, key_data_guards +from tests.unified_api_test_case import UnifiedApiTestCase + + +def test_controls_validate_storage_and_public_values(): + controls = key_controls.validate({"official_only": True, + "forced_provider_preferences": {"data_collection": "deny", "zdr": True}}) + assert controls["official_only"] == 1 + restored = key_controls.from_storage(controls) + assert key_controls.public(restored)["official_only"] is True + assert key_controls.public(restored)["forced_provider_preferences"] == {"data_collection": "deny", "zdr": True} + for payload in ({"official_only": "true"}, {"official_only": 1}, + {"forced_provider_preferences": []}, {"forced_provider_preferences": "oops"}, + {"forced_provider_preferences": {"data_collection": "invalid"}}, + {"forced_provider_preferences": {"data_collection": []}}, + {"forced_provider_preferences": {"only": ["x" * 5000]}}): + with pytest.raises(APIError) as error: + key_controls.validate(payload) + assert error.value.status_code == 400 + with pytest.raises(APIError) as error: + key_controls.from_storage({"official_only": 2}) + assert error.value.status_code == 503 + + +def test_registry_classes_and_tiers_fail_closed(): + from config import Config + from providers.registry import get_registry + registry = get_registry(Config.API_BASE_URLS) + for name, adapter in registry.items(): + assert adapter.data_class in {"official", "subscription", "reseller", "free"}, name + assert key_data_guards.provider_data_class("openai") == "official" + assert key_data_guards.provider_data_class("ce-unlisted") == "subscription" + assert key_data_guards.provider_data_class("custom-relay") == "reseller" + assert key_data_guards.provider_data_class("gemini", {}) == "free" + assert key_data_guards.provider_data_class("gemini", {"GEMINI_DATA_TIER": "paid"}) == "official" + assert key_data_guards.provider_data_class("gemini", {"GEMINI_DATA_TIER": "paid", + "FREE_ROUTE_FREE_TIER_PROVIDERS": "gemini"}) == "free" + assert not key_controls.model_allowed({"official_only": True}, "free:text") + assert key_controls.model_allowed({"official_only": True}, "auto:chat") + + +@pytest.mark.parametrize("caller,forced,expected", [ + ({"data_collection": "allow", "sort": "price"}, {"data_collection": "deny"}, + {"data_collection": "deny", "sort": "price"}), + ({"data_collection": "deny"}, {"data_collection": "allow"}, {"data_collection": "deny"}), + ({"max_price": {"prompt": 4, "completion": 2}}, {"max_price": {"prompt": 1}}, + {"max_price": {"prompt": 1, "completion": 2}}), +]) +def test_preferences_cannot_be_weakened_at_transport(caller, forced, expected): + app = Flask(__name__) + with app.test_request_context(): + g.authenticated_user = {"forced_provider_preferences": forced} + original = {"model": "example/model", "provider": caller} + data = json.dumps(original).encode() + guarded = key_data_guards.guard_request("openrouter", data) + assert json.loads(guarded)["provider"] == expected + assert json.loads(data) == original + assert key_data_guards.guard_request("openai", data) == data + + +def test_auto_filters_candidates_before_dispatch_and_refuses_empty(): + from routes.auto_routes import dispatch_auto_route + app = Flask(__name__) + route = SimpleNamespace(id="auto:guarded", candidates=("opencode:example", "openai:example"), canary=None) + calls = [] + with app.test_request_context(), patch("routes.auto_routes.AutoRouteService.get_route", return_value=route), \ + patch("routes.auto_routes.canary_traffic.prepare_request", return_value=None): + g.authenticated_user = {"official_only": True} + response = dispatch_auto_route({"model": route.id}, validate_candidate=lambda _: None, + dispatch_candidate=lambda payload, model, decision: (calls.append(model) or Response("{}", mimetype="application/json"))) + assert response.status_code == 200 + assert calls == ["openai:example"] + route.candidates = ("opencode:example",) + with pytest.raises(APIError) as error: + dispatch_auto_route({"model": route.id}, validate_candidate=lambda _: None, + dispatch_candidate=lambda *_: pytest.fail("No eligible provider")) + assert error.value.status_code == 403 + assert error.value.payload["error"] == "provider_class_not_allowed" + + +def test_cascade_filters_tiers_and_nested_auto_without_dispatch(): + from routes.cascades import dispatch_cascade, validate_cascade_target + app = Flask(__name__) + config = {"name": "cascade:guarded", "tiers": [{"model": "opencode:example"}], "checks": ["complete"]} + with app.test_request_context(), patch("routes.cascades.CascadeService.get_route", return_value=config): + g.authenticated_user = {"official_only": True} + with pytest.raises(APIError) as error: + dispatch_cascade({"model": config["name"]}, lambda *_: pytest.fail("No eligible tier")) + assert error.value.payload["error"] == "provider_class_not_allowed" + config["tiers"] = [{"model": "auto:guarded"}] + route = SimpleNamespace(id="auto:guarded", candidates=("opencode:example",)) + with patch("routes.unified.AutoRouteService.get_route", return_value=route): + with pytest.raises(APIError) as error: + validate_cascade_target(app, None, config["name"], None) + assert error.value.status_code == 403 + + +def test_cascade_uses_remaining_official_tier(monkeypatch): + from tests.test_cascades import CONFIG, answer, run + from services import request_accounting, usage_ledger + app = Flask(__name__) + monkeypatch.setattr(request_accounting, "begin", lambda **_: None) + monkeypatch.setattr(usage_ledger.LEDGER, "record", lambda _: None) + with app.test_request_context(): + g.authenticated_user = {"official_only": True} + config = {**CONFIG, "tiers": [{"model": "opencode:example"}, {"model": "openai:example"}]} + response, calls = run(config, [answer()]) + assert response.status_code == 200 + assert [body["model"] for body in calls] == ["openai:example"] + + +class KeyDataRouteTest(UnifiedApiTestCase): + def key(self, **controls): + auth = self.app_module.AuthService + with patch.object(auth, "get_current_user", return_value={"username": "admin", "is_admin": True}): + created = auth.create_user("guarded", is_admin=True) + auth.set_key_controls("guarded", controls) + return created["api_key"] + + def test_direct_classes_and_free_routes_refuse_before_transport(self): + key = self.key(official_only=True) + with patch("app.ProxyService.make_request") as transport: + for endpoint, model in (("/v1/chat/completions", "opencode:glm-5.2"), + ("/v1/chat/completions", "free:text"), + ("/linkapi/v1/chat/completions", "example/model")): + response = self.client.post(endpoint, json={"model": model, "messages": []}, + headers={"Authorization": f"Bearer {key}"}) + assert response.status_code == 403, response.get_json() + assert response.get_json()["error"] == "provider_class_not_allowed" + transport.assert_not_called() + + def test_transport_merges_forced_preferences_on_unified_and_raw_calls(self): + key = self.key(forced_provider_preferences={"data_collection": "deny"}) + with patch.object(self.app_module.ProxyService, "_make_base_request", return_value=self._chat_response()) as upstream, \ + patch.object(self.app_module.AuthService, "get_api_key", return_value="synthetic-provider-key"): + for endpoint, model in (("/v1/chat/completions", "openrouter:example/model"), + ("/openrouter/chat/completions", "example/model")): + response = self.client.post(endpoint, headers={"Authorization": f"Bearer {key}"}, + json={"model": model, "messages": [{"role": "user", "content": "example"}], + "provider": {"data_collection": "allow", "sort": "price"}}) + assert response.status_code == 200, response.get_json() + assert json.loads(upstream.call_args.kwargs["data"])["provider"] == {"data_collection": "deny", "sort": "price"} diff --git a/tests/test_key_data_guards_worker.mjs b/tests/test_key_data_guards_worker.mjs new file mode 100644 index 00000000..e8dcdc12 --- /dev/null +++ b/tests/test_key_data_guards_worker.mjs @@ -0,0 +1,85 @@ +import { policyStore } from "./helpers/dispatch_fixture.mjs"; +import { dataPolicy } from "../worker/dispatch-policy.mjs"; +import assert from "node:assert/strict"; +import test from "node:test"; +import { convertV4MiniflareOptions, Miniflare } from "./helpers/local_miniflare.mjs"; +import { guardNativeRequest, mergePreferences, providerDataClass, validDataControls } from "../worker/key-data-guards.mjs"; +import { nativeGenerationFetch } from "../worker/gateway-extensions.mjs"; +import { handleControlUsersRequest } from "../worker/control-users-d1.mjs"; +import { applyMigrations } from "./d1_migrations.mjs"; + +const request = (provider = "openrouter", preferences = { data_collection: "allow" }) => new Request(`https://example.test/${provider}/v1/chat/completions`, { + method: "POST", headers: { "content-type": "application/json" }, + body: JSON.stringify({ model: "example/model", provider: preferences, messages: [{ role: "user", content: "example" }] }), +}); +const authority = (provider, principal) => ({ authenticated: true, provider, principal, keyId: principal.id ?? "example", + route: `/${provider}/v1/chat/completions` }); + +async function database(t) { + const mf = new Miniflare(convertV4MiniflareOptions({ modules: true, + script: "export default {fetch(){return new Response('ok')}}", d1Databases: ["INTELLIGENCE_DB"] })); + t.after(() => mf.dispose()); + const db = await mf.getD1Database("INTELLIGENCE_DB"); + await applyMigrations(db); + return { INTELLIGENCE_DB: db }; +} + +test("provider classes and explicit tier choices exclude unverified endpoints", () => { + assert.equal(providerDataClass("openai"), "official"); + assert.equal(providerDataClass("opencode"), "subscription"); + assert.equal(providerDataClass("nanogpt"), "reseller"); + assert.equal(providerDataClass("free"), "free"); + assert.equal(providerDataClass("custom"), "reseller"); + assert.equal(providerDataClass("ce-custom"), "subscription"); + assert.equal(providerDataClass("gemini"), "free"); + assert.equal(providerDataClass("gemini", { GEMINI_DATA_TIER: "paid" }), "official"); + assert.equal(providerDataClass("gemini", { GEMINI_DATA_TIER: "paid", FREE_ROUTE_FREE_TIER_PROVIDERS: "gemini" }), "free"); + for (const value of [2, true, "true"]) assert.equal(validDataControls({ official_only: value }), false); + assert.equal(validDataControls({ forced_provider_preferences: "[]" }), false); + assert.equal(validDataControls({ forced_provider_preferences: '{"data_collection":"invalid"}' }), false); +}); + +test("native direct requests refuse before calling any upstream", async () => { + for (const provider of ["opencode", "nanogpt", "gemini", "ce-gpt-pro", "free"]) { + const response = await nativeGenerationFetch(request(provider), { INTELLIGENCE_DB: policyStore({ official_only: 1, forced_provider_preferences: null }) }, {}, + authority(provider, { official_only: 1 }), () => assert.fail("Must not dispatch")); + assert.equal(response.status, 403); + assert.equal((await response.json()).error.code, "provider_class_not_allowed"); + } + let calls = 0; + const response = await nativeGenerationFetch(new Request("https://api.openai.com/v1/chat/completions", request("openai")), { INTELLIGENCE_DB: policyStore({ official_only: 1, forced_provider_preferences: null }), OPENAI_API_KEY: "synthetic-operator" }, {}, + authority("openai", { official_only: 1 }), async () => { calls++; return new Response("{}"); }); + assert.equal(response.status, 200); + assert.equal(calls, 1); +}); + +test("forced preferences override caller allow and preserve caller deny", async () => { + const principal = { forced_provider_preferences: '{"data_collection":"deny","max_price":{"prompt":1}}' }; + const guarded = await guardNativeRequest(request("openrouter", { data_collection: "allow", sort: "price", max_price: { completion: 2 } }), { INTELLIGENCE_DB: policyStore({ official_only: null, forced_provider_preferences: principal.forced_provider_preferences }) }, authority("openrouter", principal)); + assert.deepEqual((await guarded.request.json()).provider, + { data_collection: "deny", sort: "price", max_price: { completion: 2, prompt: 1 } }); + assert.deepEqual(mergePreferences({ data_collection: "deny" }, { data_collection: "allow" }), { data_collection: "deny" }); + const pollution = mergePreferences({}, JSON.parse('{"__proto__":{"polluted":true}}')); + assert.equal({}.polluted, undefined); + assert.equal(Object.hasOwn(pollution, "__proto__"), true); +}); + +test("durable key controls round-trip and native dispatch reads them", async t => { + const env = await database(t); + const user = { username: "example", api_key_hash: "synthetic-hash", api_key_prefix: "mllm_example", scopes: "chat,models", + is_admin: 0, created_at: "2026-10-20T00:00:00Z", last_login: null, last_used_at: null, last_used_ip: null, + created_by: null, rotated_at: null, revoked_at: null, daily_budget_usd: null, monthly_budget_usd: null, + allowed_models: null, allowed_ips: null, expires_at: null, secret_scan_mode: null, shadow_eval_rate: null, + official_only: 1, forced_provider_preferences: '{"data_collection":"deny"}' }; + const call = body => handleControlUsersRequest(new Request("http://intelligence.internal/v1/users", { + method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ version: 1, data_policy_version: 1, ...body }), + }), env); + assert.equal((await call({ operation: "upsert", user })).status, 200); + assert.deepEqual((await (await call({ operation: "get", username: user.username })).json()).user, { ...user, data_policy: dataPolicy(user) }); + const denied = await guardNativeRequest(request("opencode"), env, authority("opencode", { id: user.username })); + assert.equal(denied.response.status, 403); + const guarded = await guardNativeRequest(request(), env, authority("openrouter", { id: user.username })); + assert.equal((await guarded.request.json()).provider.data_collection, "deny"); + const unavailable = await guardNativeRequest(request(), { INTELLIGENCE_DB: { prepare() { throw new Error(); } } }, authority("openrouter", { id: user.username })); + assert.equal(unavailable.response.status, 503); +}); diff --git a/tests/test_key_data_intelligence.py b/tests/test_key_data_intelligence.py new file mode 100644 index 00000000..b1031f71 --- /dev/null +++ b/tests/test_key_data_intelligence.py @@ -0,0 +1,66 @@ +"""Key controls survive managed routing and its background transport handoff.""" + +import json +from unittest.mock import patch + +from services.intelligence_store import IntelligenceStore +from tests.intelligence_fixtures import IntelligenceApiTestCase, completion, upstream +from tests.test_intelligence_policy import candidate, policy + + +class KeyDataIntelligenceTest(IntelligenceApiTestCase): + def controlled_key(self, **controls): + auth = self.app_module.AuthService + with patch.object(auth, "get_current_user", return_value={"username": "admin", "is_admin": True}): + created = auth.create_user("guarded", scopes=["chat", "models"]) + auth.set_key_controls("guarded", controls) + self.headers["Authorization"] = f"Bearer {created['api_key']}" + + def test_intelligence_skips_nonofficial_and_refuses_an_empty_policy(self): + self.controlled_key(official_only=True) + IntelligenceStore.seed(policy(candidates=[candidate("navyai:small"), candidate("openai:large")])) + with self.requests(return_value=upstream(completion())) as send: + response = self.post() + assert response.status_code == 200, response.json + assert send.call_count == 1 + assert send.call_args.kwargs["api_provider"] == "openai" + with patch("routes.intelligence.load_policy", return_value=policy(candidates=[candidate("navyai:small")])): + with self.requests() as send: + response = self.post() + assert response.status_code == 403, response.json + assert response.json["error"]["code"] == "provider_class_not_allowed" + send.assert_not_called() + + def test_forced_preferences_are_captured_before_background_submission(self): + self.controlled_key(forced_provider_preferences={"data_collection": "deny"}) + IntelligenceStore.seed(policy(candidates=[candidate("openrouter:example/model")])) + with self.requests(return_value=upstream(completion())) as send: + response = self.post() + assert response.status_code == 200, response.json + assert json.loads(send.call_args.kwargs["data"])["provider"] == {"data_collection": "deny"} + + def test_managed_submission_pins_the_attested_credential_before_background_dispatch(self): + self.app.config["GEMINI_DATA_TIER"] = "paid" + self.controlled_key(official_only=True) + IntelligenceStore.seed(policy(candidates=[candidate("gemini:gemini-test-model")])) + with patch.dict("os.environ", {"GEMINI_API_KEY": "AIza-attested-synthetic"}), \ + patch.object(self.app_module.AuthService, "get_api_key", return_value="AIza-attested-synthetic"), \ + self.requests(return_value=upstream(completion())) as send: + response = self.post() + assert response.status_code == 200, response.json + assert send.call_count == 1 + headers = send.call_args.kwargs["headers"] + assert headers["x-goog-api-key"] == "AIza-attested-synthetic" + assert not any(name.lower() == "authorization" for name in headers) + + def test_managed_submission_refuses_a_missing_attested_binding(self): + import os + self.app.config["GEMINI_DATA_TIER"] = "paid" + self.controlled_key(official_only=True) + IntelligenceStore.seed(policy(candidates=[candidate("gemini:gemini-test-model")])) + with patch.dict("os.environ"): + os.environ.pop("GEMINI_API_KEY", None) + with self.requests() as send: + response = self.post() + assert response.status_code == 503, response.json + send.assert_not_called() diff --git a/tests/test_knowledge_authority.mjs b/tests/test_knowledge_authority.mjs index d587e4a2..7a01d716 100644 --- a/tests/test_knowledge_authority.mjs +++ b/tests/test_knowledge_authority.mjs @@ -265,3 +265,24 @@ test("product-site learning and overrides survive Durable Object reopen and rebu assert.ok(rebuilt.sites["python.org"].pinned); assert.deepEqual(rebuilt.blocked, overrides.blocked); }); + + +test("background refresh and reconciliation retain the source account identity", async t => { + const f = await fixture(t); + const owner = { authenticated: true, keyId: "source-key" }; + const other = { authenticated: true, keyId: "admin" }; + const source = (await f.call("source.create", { url: "https://flask.palletsprojects.com/en/3.1.3/api/", + product: "flask", version: "3.1.3", dispatch_principal: owner })).result; + const job = (await f.call("job.enqueue", { source_id: source.id, dispatch_principal: other })).result; + assert.deepEqual(job.dispatch_principal, owner); + const replay = (await f.call("job.enqueue", { source_id: source.id, dispatch_principal: other })).result; + assert.equal(replay.id, job.id); + assert.deepEqual(replay.dispatch_principal, owner); + await f.call("job.cancel", { id: job.id }); + await f.reopen(); + const replacement = (await f.call("job.enqueue", { source_id: source.id, dispatch_principal: other })).result; + assert.notEqual(replacement.id, job.id); + assert.deepEqual(replacement.dispatch_principal, owner); + const persisted = (await f.call("job.get", { id: replacement.id })).result; + assert.deepEqual(persisted.source.dispatch_principal, owner); +}); diff --git a/tests/test_knowledge_service.mjs b/tests/test_knowledge_service.mjs index 9419c70a..6f512532 100644 --- a/tests/test_knowledge_service.mjs +++ b/tests/test_knowledge_service.mjs @@ -305,7 +305,8 @@ test("Container outbound forwarding accepts only its fixed private target and st const result = await handleKnowledgeOutbound(new Request("http://knowledge.internal/v1/dispatch", options), env); assert.equal(result.status, 200); assert.equal(requests.length, 1); - assert.deepEqual(requests[0].options.headers, { "content-type": "application/json" }); + assert.deepEqual(requests[0].options.headers, { "content-type": "application/json", "X-MultiLLM-Key-Controls": "2", + "X-MultiLLM-Dispatch-Principal": JSON.stringify({ version: 1, username: manager.id }) }); for (const target of ["http://knowledge.internal/other", "https://knowledge.internal/v1/dispatch", "http://other.internal/v1/dispatch", "http://knowledge.internal/v1/dispatch?url=anything"]) { assert.equal((await handleKnowledgeOutbound(new Request(target, options), env)).status, 400); } diff --git a/tests/test_knowledge_skills.mjs b/tests/test_knowledge_skills.mjs index 789829ef..5b12730a 100644 --- a/tests/test_knowledge_skills.mjs +++ b/tests/test_knowledge_skills.mjs @@ -401,7 +401,7 @@ test("8 MiB sync ingress and outbound boundary accept exact size and reject one for (const [bytes, expected] of [[SYNC_REQUEST_BYTES, 200], [SYNC_REQUEST_BYTES + 1, 413]]) { const response = await handleKnowledgeEdgeRequest(request("https://gateway.example/v1/knowledge/skills", raw.padEnd(bytes)), env); assert.equal(response.status, expected); - const outbound = await handleKnowledgeOutbound(request("http://knowledge.internal/v1/dispatch", JSON.stringify({ operation: "skills.sync", principal: { id: "synthetic" } }).padEnd(bytes)), env); + const outbound = await handleKnowledgeOutbound(request("http://knowledge.internal/v1/dispatch", JSON.stringify({ operation: "skills.sync", principal: { id: "synthetic-principal" } }).padEnd(bytes)), env); assert.equal(outbound.status, expected); } const response = await handleKnowledgeEdgeRequest(request("https://gateway.example/v1/knowledge/skills/find", JSON.stringify({ query: "x" }).padEnd(65537)), env); diff --git a/tests/test_knowledge_skills_import.mjs b/tests/test_knowledge_skills_import.mjs index 5da45db3..51b98c5f 100644 --- a/tests/test_knowledge_skills_import.mjs +++ b/tests/test_knowledge_skills_import.mjs @@ -292,3 +292,21 @@ test("the generated MCP contract matches the Worker's import flags, roots and sc const schema = tool("knowledge_skills_import").definition.inputSchema.properties; for (const [field, value] of [["commit", SHA1], ["version", "1.0.0-beta+2"], ["clawhub", "awspace/pdf"]]) assert.match(value, new RegExp(schema[field].pattern), field); }); + + +test("approved reimport binds legacy watches and preserves the importing account", async t => { + const { env, store } = library(t); + const repo = github({ head: SHA1, commits: { [SHA1]: { tree: "t1", files: { "SKILL.md": SKILL } } } }); + const payload = { repository: "acme/skills", path: "skills/pg", commit: SHA1 }; + const options = { skills: store, fetch: repo.fetch }; + await submit(env, "skills.import", payload, options); + assert.deepEqual(store.read("pg-tools").origin.dispatch_principal, { authenticated: true, keyId: "synthetic-principal" }); + const legacy = store.read("pg-tools"); + delete legacy.origin.dispatch_principal; + store.sql.exec("UPDATE skills SET record = ? WHERE skill_id = ?", JSON.stringify(legacy), "pg-tools"); + assert.equal((await submit(env, "skills.import", payload, options)).status, "unchanged"); + assert.deepEqual(store.read("pg-tools").origin.dispatch_principal, { authenticated: true, keyId: "synthetic-principal" }); + await dispatchKnowledge(env, { version: 1, operation: "skills.import", payload, + principal: { id: "other-principal", scopes: MANAGE } }, options); + assert.deepEqual(store.read("pg-tools").origin.dispatch_principal, { authenticated: true, keyId: "synthetic-principal" }); +}); diff --git a/tests/test_media_outbound_worker.mjs b/tests/test_media_outbound_worker.mjs index 6bf6cce7..d172699a 100644 --- a/tests/test_media_outbound_worker.mjs +++ b/tests/test_media_outbound_worker.mjs @@ -5,7 +5,7 @@ import test from "./helpers/policy_test.mjs"; import { fetchPublic, handleMediaOutbound, publicHttpsUrl } from "../worker/media-outbound.mjs"; import { OLD, runtimeModule } from "./helpers/runtime_versions.mjs"; -const EXPAND = ""; // The contract layer pins its expansion rollback target here. +const EXPAND = "b6b0e43"; // The contract layer pins its expansion rollback target here. const fetchImage = body => handleMediaOutbound(new Request("http://media.internal/v1/fetch", { method: "POST", headers: { "content-type": "application/json", "X-MultiLLM-Dispatch-Principal": '{"version":1,"username":"fixture-reader"}' }, body: JSON.stringify(body) }), { INTELLIGENCE_DB: policyStore() }); diff --git a/tests/test_outbound_inventory.py b/tests/test_outbound_inventory.py new file mode 100644 index 00000000..fc05820f --- /dev/null +++ b/tests/test_outbound_inventory.py @@ -0,0 +1,48 @@ +"""Reviewed outbound surfaces: additions or edits require another boundary review. + +This intentionally pins the complete transport module, not just a line containing +fetch: moving admission after a call must also invalidate the inventory. The +manifest records why each transport is guarded or is not a provider dispatch. +""" +import hashlib +import json +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +MANIFEST = ROOT / "tests/outbound_inventory.json" + +def inventory(sources): + return {name: hashlib.sha256(text.encode()).hexdigest() + for name, text in sources.items()} + + +def sources(): + files = [path for suffix in ("*.py", "*.mjs", "*.js", "*.cjs", "*.ts") for path in ROOT.glob(suffix)] + for directory in ("services", "routes", "providers", "utils", "worker"): + for suffix in ("*.py", "*.mjs", "*.js", "*.cjs", "*.ts"): + files.extend((ROOT / directory).rglob(suffix)) + return {str(path.relative_to(ROOT)): path.read_text() for path in files} + + +def test_every_outbound_surface_has_an_unchanged_reviewed_boundary(): + reviewed = json.loads(MANIFEST.read_text()) + assert all(item["reason"] and item["kind"] in {"guarded", "internal", "non-provider"} for item in reviewed.values()) + assert inventory(sources()) == {name: item["sha256"] for name, item in reviewed.items()}, ( + "Outbound transport changed: review every call and update its explicit inventory classification only after checking admission.") + + +def test_inventory_catches_new_modules_and_bypasses_inside_existing_transports(): + original = {"services/example.py": "value = 1\n"} + assert inventory(original) + assert inventory({**original, "services/bypass.py": "import requests as network\nnetwork.post(url, json=body)\n"}) + worker = {"worker/example.mjs": "providerOperation('cloudflare', run)"} + assert inventory(worker) != inventory({"worker/example.mjs": worker["worker/example.mjs"] + "\nreturn fetch(url, {body});"}) + assert inventory({"worker/new.mjs": "return env.AI.fetch(url);"}) + assert inventory({"services/new.py": "from http import client as network\nnetwork.HTTPSConnection(host).request(method, path)"}) + + +def test_inventory_pins_previously_unlisted_modules_and_alternate_clients(): + for module, mutation in [("routes/intelligence.py", "import urllib3\nurllib3.PoolManager().request(method, url)"), + ("worker/new.js", "import axios from 'axios'; axios.get(url)")]: + before = {module: "value = 1"} + assert inventory(before) != inventory({module: before[module] + "\n" + mutation}) diff --git a/tests/test_peer_capability.py b/tests/test_peer_capability.py new file mode 100644 index 00000000..8122f854 --- /dev/null +++ b/tests/test_peer_capability.py @@ -0,0 +1,43 @@ +"""Management reads are independent of current dispatch enforcement authority.""" +import pytest + +from error_handlers import APIError +from services import user_store +from services.dispatch_policy import envelope + + +@pytest.mark.parametrize("controls", [ + {"official_only": None, "forced_provider_preferences": None}, + {"official_only": 1, "forced_provider_preferences": None}, + {"official_only": None, "forced_provider_preferences": '{"data_collection":"deny"}'}, +]) +@pytest.mark.parametrize("capability", [None, {}, {"protocol": 1, "enforcement": False}, + {"protocol": 2, "enforcement": True}]) +def test_current_main_capability_is_required_only_for_controlled_rows(controls, capability, monkeypatch): + calls = [] + row = dict.fromkeys(user_store.USER_FIELDS) + row.update(username="synthetic", api_key_hash="synthetic", api_key_prefix="synthetic", scopes="chat", + is_admin=0, created_at="2026-10-01T00:00:00Z", **controls, data_policy=envelope(controls)) + + def rpc(payload, **_): + calls.append(payload["operation"]) + return ({"version": 1, "key_controls": capability} if payload["operation"] == "capabilities" + else {"version": 1, "user": row}) + + monkeypatch.setattr(user_store, "request_private_intelligence", rpc) + monkeypatch.setattr(user_store, "READ_RETRY_DELAY_SECONDS", 0) + # Account decoding is management, even when another account is restricted. + assert user_store.get_user("synthetic")["username"] == "synthetic" + assert calls == ["get"] + from services.auth_service import AuthService + from services.provider_dispatch import current_principal + monkeypatch.setenv("AUTH_STORAGE_BACKEND", "d1") + monkeypatch.setattr(AuthService, "_load_user_by_username", lambda _: row) + controlled = controls["official_only"] or controls["forced_provider_preferences"] is not None + if controlled and capability != {"protocol": 2, "enforcement": True}: + with pytest.raises(APIError) as error: + current_principal({"username": "synthetic"}) + assert error.value.status_code == 503 + else: + assert current_principal({"username": "synthetic"})["username"] == "synthetic" + assert calls == (["get", "capabilities"] if controlled else ["get"]) diff --git a/tests/test_prompt_cache_cost_worker.mjs b/tests/test_prompt_cache_cost_worker.mjs index ec61228f..a4799faf 100644 --- a/tests/test_prompt_cache_cost_worker.mjs +++ b/tests/test_prompt_cache_cost_worker.mjs @@ -8,7 +8,8 @@ import { applyMigrations } from './d1_migrations.mjs'; const base = {at:'2026-10-01T00:00:00.000Z', principal:'reader', key_prefix:null, kind:'chat', endpoint:'/v1/chat/completions', requested_model:'openai:m', selected_model:'openai:m', status:200, - latency_ms:1, input_tokens:100, output_tokens:10, cost_usd:0.00019, cost_basis:'usage', request_id:null}; + latency_ms:1, input_tokens:100, output_tokens:10, cost_usd:0.00019, cost_basis:'usage', request_id:null, + cached_input_tokens:null, reasoning_tokens:null}; const buckets = Object.fromEntries(BUCKET_FIELDS.map(name => [name, null])); Object.assign(buckets, {ordinary_input_tokens:40, cache_read_input_tokens:60, cache_write_input_tokens:0, ordinary_input_cost_microusd:80, cache_read_input_cost_microusd:30, cache_write_input_cost_microusd:0, diff --git a/tests/test_provider_assets.py b/tests/test_provider_assets.py new file mode 100644 index 00000000..0d386242 --- /dev/null +++ b/tests/test_provider_assets.py @@ -0,0 +1,128 @@ +"""Assets retain admission without inheriting API credentials or redirect bodies.""" +import io +from unittest.mock import Mock, patch + +import pytest +import requests + +from error_handlers import APIError +from services import media_storage, video_generation +from services.dispatch_policy import envelope +from services.provider_assets import asset_http +from services.provider_dispatch import dispatch_as, provider_http +from tests.unified_api_test_case import UnifiedApiTestCase + + +def response(status=200, location=None): + result = requests.Response() + result.status_code = status + result.raw = io.BytesIO(b"synthetic-video") + result.headers.update({"Content-Type": "video/mp4", "Content-Length": "15"}) + if location is not None: + result.headers["Location"] = location + return result + + +@pytest.fixture +def authority(monkeypatch): + user = {"username": "synthetic-assets", "data_policy": envelope({"official_only": 1, "forced_provider_preferences": None})} + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: user) + monkeypatch.setenv("XAI_API_KEY", "synthetic-operator-secret") + monkeypatch.setenv("OPENAI_API_KEY", "synthetic-operator-secret") + with dispatch_as({"username": user["username"]}): + yield user + + +@pytest.mark.parametrize("consumer", ["relay", "storage"]) +@pytest.mark.parametrize("restricted", [False, True]) +def test_public_video_retrieval_never_receives_credentials(consumer, restricted, authority, monkeypatch): + authority["data_policy"]["official_only"] = 1 if restricted else None + send = Mock(return_value=response()) + monkeypatch.setattr("services.provider_dispatch.requests.request", send) + monkeypatch.setattr(media_storage, "put_stream", lambda *args, **kwargs: {"size": args[2]}) + url = "https://unrelated-cdn.example/asset.mp4" + headers = {"Authorization": "Bearer synthetic-cached", "x-goog-api-key": "synthetic-cached", "Cookie": "synthetic"} + if consumer == "relay": + assert video_generation.stream_content(url, headers, provider="xai").get_data() == b"synthetic-video" + else: + assert media_storage.store_video("mv_synthetic", url, headers, owner=authority["username"], model="xai:synthetic") + assert send.call_args.kwargs["headers"] == {} + + +@pytest.mark.parametrize("consumer", ["relay", "storage"]) +@pytest.mark.parametrize("restricted", [False, True]) +def test_video_redirects_follow_location_and_strip_secrets(consumer, restricted, authority, monkeypatch): + authority["data_policy"]["official_only"] = 1 if restricted else None + send = Mock(side_effect=[response(302, "https://cdn.example/one"), response(307, "/two"), response()]) + monkeypatch.setattr("services.provider_dispatch.requests.request", send) + monkeypatch.setattr(media_storage, "put_stream", lambda *args, **kwargs: {"size": args[2]}) + url = "https://api.openai.com/v1/videos/synthetic/content" + headers = {"Authorization": "Bearer synthetic-cached"} + if consumer == "relay": + assert video_generation.stream_content(url, headers, provider="openai").get_data() == b"synthetic-video" + else: + assert media_storage.store_video("mv_synthetic", url, headers, owner=authority["username"], model="openai:synthetic") + calls = [call.kwargs for call in send.call_args_list] + assert calls[0]["headers"]["Authorization"] == "Bearer " + ("synthetic-operator-secret" if restricted else "synthetic-cached") + assert [call["url"] for call in calls[1:]] == ["https://cdn.example/one", "https://cdn.example/two"] + assert all(call["headers"] == {} and call["allow_redirects"] is False for call in calls[1:]) + + +@pytest.mark.parametrize("consumer", ["relay", "storage"]) +@pytest.mark.parametrize("location", [None, "http://cdn.example/asset", "https://cdn.example/loop"]) +def test_unresolved_or_unsafe_redirects_are_errors(consumer, location, authority, monkeypatch): + send = Mock(side_effect=lambda **kwargs: response(302, location)) + monkeypatch.setattr("services.provider_dispatch.requests.request", send) + put = Mock() + monkeypatch.setattr(media_storage, "put_stream", put) + with pytest.raises(APIError) as error: + if consumer == "relay": + video_generation.stream_content("https://cdn.example/one", {}, provider="xai") + else: + media_storage.store_video("mv_synthetic", "https://cdn.example/one", {}, owner=authority["username"], model="xai:synthetic") + assert error.value.status_code == 502 + assert send.call_count <= 6 + put.assert_not_called() + + +def test_each_redirect_rechecks_policy_and_never_reintroduces_credentials(authority, monkeypatch): + send = Mock(side_effect=[response(302, "https://cdn.example/one"), + response(302, "https://api.openai.com/v1/videos/synthetic/content"), response()]) + monkeypatch.setattr("services.provider_dispatch.requests.request", send) + asset_http("openai", "https://api.openai.com/v1/videos/synthetic/content").close() + assert [c.kwargs["headers"] for c in send.call_args_list][1:] == [{}, {}] + def changed(**kwargs): + authority["data_policy"] = None + return response(302, "https://cdn.example/two") + send.side_effect = changed + with pytest.raises(APIError) as error: + asset_http("xai", "https://cdn.example/one") + assert error.value.status_code == 503 + assert send.call_count == 4 + + +def test_unapproved_api_origin_cannot_receive_attested_secret(authority, monkeypatch): + send = Mock() + with pytest.raises(APIError): + provider_http("xai", "GET", "https://api.x.ai.unrelated.example/v1/videos/synthetic", transport=send) + send.assert_not_called() + + +class RegisteredVideoAssetTest(UnifiedApiTestCase): + def test_restricted_registered_content_route_keeps_status_credential_off_cdn(self): + from tests.test_media_generation import upstream + auth = self.app_module.AuthService + with patch.object(auth, "get_current_user", return_value={"username": "admin", "is_admin": True}): + created = auth.create_user("synthetic-assets", is_admin=True) + auth.set_key_controls("synthetic-assets", {"official_only": True}) + with self.app.app_context(): + job = video_generation.issue_job_id("synthetic-assets", "xai", "synthetic", "synthetic-video") + with patch.dict("os.environ", {"XAI_API_KEY": "synthetic-operator-secret"}), \ + patch.object(auth, "get_api_key", return_value="synthetic-cached"), \ + patch("services.provider_dispatch.requests.request", side_effect=[upstream(200, + {"video": {"url": "https://unrelated-cdn.example/asset.mp4"}}), response()]) as send: + result = self.client.get(f"/v1/videos/{job}/content", headers={"Authorization": "Bearer " + created["api_key"]}) + assert result.status_code == 200, result.json + assert result.data == b"synthetic-video" + assert send.call_args_list[0].kwargs["headers"]["Authorization"] == "Bearer synthetic-operator-secret" + assert send.call_args_list[1].kwargs["headers"] == {} diff --git a/tests/test_provider_dispatch.py b/tests/test_provider_dispatch.py new file mode 100644 index 00000000..ab10ff7f --- /dev/null +++ b/tests/test_provider_dispatch.py @@ -0,0 +1,170 @@ +"""All provider adapters share current durable policy and credential admission.""" +import json +import os +from unittest.mock import Mock, patch + +import pytest +from flask import Flask, g + +from error_handlers import APIError +from services.dispatch_policy import envelope, verify +from services.provider_dispatch import capture_dispatch, provider_http +from services.user_store import _row +from tests.unified_api_test_case import UnifiedApiTestCase + + +def account(**controls): + return {"username": "synthetic-user", "api_key_prefix": "synthetic", "data_policy": envelope({ + "official_only": None, "forced_provider_preferences": None, **controls})} + + +@pytest.mark.parametrize("policy", [None, {}, {"version": 0, "official_only": None, "forced_provider_preferences": None}, + {"version": 1, "official_only": None}, {"version": 1, "forced_provider_preferences": None}, + {"version": 1, "official_only": "false", "forced_provider_preferences": None}]) +def test_missing_old_or_malformed_policy_never_authorizes(policy, monkeypatch): + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: {**account(), "data_policy": policy}) + send = Mock() + with Flask(__name__).test_request_context(): + g.authenticated_user = {"username": "synthetic-user", "is_admin": True} + with pytest.raises(APIError) as error: + provider_http("openai", "POST", "https://provider.example/v1/chat/completions", transport=send) + assert error.value.status_code == 503 + send.assert_not_called() + + +@pytest.mark.parametrize("provider", ["opencode", "free", "linkapi", "knowledge", "unknown"]) +def test_every_adapter_uses_current_controls_instead_of_cached_principal(provider, monkeypatch): + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: account(official_only=1)) + send = Mock() + with Flask(__name__).test_request_context(): + g.authenticated_user = account() + with pytest.raises(APIError) as error: + provider_http(provider, "POST", "https://provider.example/generate", transport=send) + assert error.value.status_code == 403 + send.assert_not_called() + + +def test_deferred_dispatch_captures_identity_but_rereads_controls(monkeypatch): + current = account() + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: current) + send = Mock() + with Flask(__name__).test_request_context(): + g.authenticated_user = current + deferred = capture_dispatch(lambda: provider_http("linkapi", "POST", "https://provider.example/generate", transport=send)) + current = account(official_only=1) + with pytest.raises(APIError) as error: + deferred() + assert error.value.status_code == 403 + send.assert_not_called() + + +def test_forced_preferences_merge_at_actual_http_boundary(monkeypatch): + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: account(forced_provider_preferences={"data_collection": "deny"})) + monkeypatch.setenv("OPENROUTER_API_KEY", "synthetic-operator") + send = Mock() + with Flask(__name__).test_request_context(): + g.authenticated_user = account() + provider_http("openrouter", "POST", "https://openrouter.ai/api/v1/chat/completions", transport=send, + json={"provider": {"data_collection": "allow", "sort": "price"}}, headers={"Authorization": "Bearer synthetic-cached"}) + assert send.call_args.kwargs["json"]["provider"] == {"data_collection": "deny", "sort": "price"} + assert send.call_args.kwargs["headers"]["Authorization"] == "Bearer synthetic-operator" + assert send.call_args.kwargs["allow_redirects"] is False + + +@pytest.mark.parametrize("policy", [None, {}, {"version": 2, "official_only": None, "forced_provider_preferences": None}]) +def test_mixed_version_rpc_rows_fail_closed(policy): + from services.user_store import USER_FIELDS + row = dict.fromkeys(USER_FIELDS) + row.update(username="synthetic-user", api_key_hash="synthetic-hash", api_key_prefix="synthetic", scopes="chat", is_admin=0, + created_at="2026-10-20T00:00:00Z") + if policy is not None: + row["data_policy"] = policy + with pytest.raises(APIError): + _row(row) + row["data_policy"] = envelope(row) + assert _row(row)["official_only"] is None + assert verify(row["data_policy"])["version"] == 1 + + +class ProviderDispatchRouteTest(UnifiedApiTestCase): + def setUp(self): + super().setUp() + self.app.config["GEMINI_DATA_TIER"] = "paid" + os.environ["GEMINI_API_KEY"] = "AIza-attested-synthetic" + auth = self.app_module.AuthService + with patch.object(auth, "get_current_user", return_value={"username": "admin", "is_admin": True}): + created = auth.create_user("synthetic-video", is_admin=True) + auth.set_key_controls("synthetic-video", {"official_only": True}) + self.headers = {"Authorization": "Bearer " + created["api_key"]} + + def test_gemini_video_creation_reselects_attested_credential(self): + from tests.test_media_generation import upstream + with patch.object(self.app_module.AuthService, "get_api_key", return_value="AIza-stale-synthetic"), \ + patch("services.provider_dispatch.requests.request", return_value=upstream(200, {"name": "operations/synthetic"})) as send: + response = self.client.post("/v1/videos", headers=self.headers, + json={"model": "gemini:veo-3.1-generate-preview", "prompt": "Synthetic landscape"}) + assert response.status_code == 200, response.json + assert send.call_args.kwargs["headers"]["x-goog-api-key"] == "AIza-attested-synthetic" + + def test_video_overrides_and_missing_attestation_do_not_dispatch(self): + for query, extra, missing in [("?key=caller-synthetic", {}, False), ("", {"x-goog-api-key": "caller-synthetic"}, False), ("", {}, True)]: + if missing: + os.environ.pop("GEMINI_API_KEY") + with patch.object(self.app_module.AuthService, "get_api_key", return_value="AIza-stale-synthetic"), \ + patch("services.provider_dispatch.requests.request") as send: + response = self.client.post("/v1/videos" + query, headers={**self.headers, **extra}, + json={"model": "gemini:veo-3.1-generate-preview", "prompt": "Synthetic landscape"}) + assert response.status_code == (503 if missing else 403), response.json + send.assert_not_called() + + def test_bootstrap_recovery_does_not_grant_provider_authority(self): + from services.auth_service import AuthService + AuthService._forget_verified_keys() + with patch.object(AuthService, "_load_user_by_username", side_effect=APIError("Synthetic store unavailable", 503)), \ + patch("requests.sessions.Session.request") as send: + response = self.client.post("/v1/chat/completions", headers={"Authorization": "Bearer admin-test-key"}, + json={"model": "openai:synthetic", "messages": [{"role": "user", "content": "Synthetic"}]}) + assert response.status_code == 503, response.json + send.assert_not_called() + + +@pytest.mark.parametrize("response", [{"version": 1, "principal": None}, {"version": 0, "principal": account()}, + {"version": 1, "principal": {"username": "integration:synthetic"}}, + {"version": 1, "principal": {"username": "integration:synthetic", "data_policy": {"version": 1, "official_only": None}}}]) +def test_integration_dispatch_rejects_old_or_incomplete_worker_authority(response, monkeypatch): + monkeypatch.setattr("services.intelligence_d1_store.request_private_intelligence", lambda *args, **kwargs: response) + send = Mock() + with pytest.raises(APIError) as error: + provider_http("openai", "POST", "https://provider.example/generate", reference={"username": "integration:synthetic"}, transport=send) + assert error.value.status_code == 503 + send.assert_not_called() + + +@pytest.mark.parametrize("operation", ["poll", "content", "watch"]) +def test_video_deferred_operations_recheck_current_policy_and_attestation(operation, monkeypatch): + from services import video_generation + from services.provider_dispatch import dispatch_as + app = Flask(__name__) + app.config["GEMINI_DATA_TIER"] = "paid" + current = account(official_only=1) + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: current) + monkeypatch.delenv("GEMINI_API_KEY", raising=False) + send = Mock() + job = {"p": "gemini", "u": "operations/synthetic", "m": "veo-synthetic", "t": 1} + def execute(): + if operation == "content": + return video_generation.stream_content("https://generativelanguage.googleapis.com/v1beta/files/synthetic.mp4", {"x-goog-api-key": "synthetic-cached"}, provider="gemini") + return video_generation.job_status(job, "synthetic-job", "synthetic-cached", "https://provider.example") + with app.test_request_context(): + g.authenticated_user = current + deferred = capture_dispatch(execute) + with patch("services.provider_dispatch.requests.request", send), dispatch_as({"username": "synthetic-user"}): + with pytest.raises(APIError) as error: + deferred() + assert error.value.status_code == 503 + monkeypatch.setenv("GEMINI_API_KEY", "synthetic-current") + app.config["GEMINI_DATA_TIER"] = "free" + with pytest.raises(APIError) as error: + deferred() + assert error.value.status_code == 403 + send.assert_not_called() diff --git a/tests/test_provider_dispatch_worker.mjs b/tests/test_provider_dispatch_worker.mjs new file mode 100644 index 00000000..89250ada --- /dev/null +++ b/tests/test_provider_dispatch_worker.mjs @@ -0,0 +1,177 @@ +import assert from "node:assert/strict"; +import { providerBinding } from "./helpers/provider_binding.mjs"; +import test from "node:test"; +import { providerFetch, providerOperation, withDispatchPrincipal } from "../worker/provider-dispatch.mjs"; +import { verifyDataPolicy } from "../worker/dispatch-policy.mjs"; +import { handleAiOutbound } from "../worker/ai-outbound.mjs"; +import { policyStore } from "./helpers/dispatch_fixture.mjs"; +import { loadWorkerModule } from "./helpers/load_cloudflare_worker.mjs"; +import { makeRoleplayEnv, roleplayRequest, withGlobalFetch } from "./helpers/roleplay_fixture.mjs"; + +const worker = (await loadWorkerModule()).default; +const authority = { authenticated: true, keyId: "synthetic-reader" }; +const input = (provider = "openrouter") => new Request((provider === "gemini" ? "https://generativelanguage.googleapis.com" : "https://openrouter.ai") + "/v1/chat/completions", { + method: "POST", headers: { "content-type": "application/json", authorization: "Bearer synthetic-cached" }, + body: JSON.stringify({ provider: { data_collection: "allow", sort: "price" }, messages: [] }), +}); +const controls = () => ({ official_only: null, forced_provider_preferences: null }); + +for (const row of [null, {}, { official_only: null }, { forced_provider_preferences: null }, + { official_only: 1, forced_provider_preferences: "[]" }]) { + test(`an incomplete or malformed durable policy refuses HTTP and SDK dispatch: ${JSON.stringify(row)}`, async () => { + const env = { INTELLIGENCE_DB: policyStore(row) }; + await assert.rejects(providerFetch("openai", input(), {}, { env, authority, send: () => assert.fail("HTTP dispatch") }), + { code: "key_data_controls_unavailable", status: 503 }); + await assert.rejects(withDispatchPrincipal(env, authority, () => providerOperation("cloudflare", () => assert.fail("SDK dispatch"))), + { code: "key_data_controls_unavailable", status: 503 }); + }); +} + +test("a versioned envelope requires both explicit fields and the supported version", () => { + for (const value of [null, {}, { version: 0, ...controls() }, { version: true, ...controls() }, + { version: 1, official_only: null }, { version: 1, ...controls(), extra: null }]) { + assert.throws(() => verifyDataPolicy(value)); + } + assert.deepEqual(verifyDataPolicy({ version: 1, ...controls() }), { version: 1, ...controls() }); +}); + +test("each retry, poll and fallback rereads current controls rather than a captured policy", async () => { + const row = controls(), env = { INTELLIGENCE_DB: policyStore(row) }; + let calls = 0; + const send = () => { calls++; return new Response("{}"); }; + await providerFetch("linkapi", input(), {}, { env, authority, send }); + row.official_only = 1; + for (const provider of ["linkapi", "nanogpt", "opencode", "free", "unknown"]) { + await assert.rejects(providerFetch(provider, input(), {}, { env, authority, send }), { code: "provider_class_not_allowed", status: 403 }); + } + assert.equal(calls, 1); +}); + +test("forced routing preferences and current attested credentials replace cached headers at dispatch", async () => { + const env = { INTELLIGENCE_DB: policyStore({ official_only: 1, forced_provider_preferences: '{"data_collection":"deny"}' }), + OPENROUTER_API_KEY: "synthetic-current" }; + await providerFetch("openrouter", input(), {}, { env, authority, send: async request => { + assert.equal(request.headers.get("authorization"), "Bearer synthetic-current"); + assert.deepEqual((await request.json()).provider, { data_collection: "deny", sort: "price" }); + assert.equal(request.redirect, "manual"); + return new Response("{}"); + } }); + await assert.rejects(providerFetch("openrouter", input(), {}, { env, authority, realtime: true }), + { code: "forced_provider_preferences_not_supported", status: 403 }); + delete env.OPENROUTER_API_KEY; + await assert.rejects(providerFetch("openrouter", input(), {}, { env, authority }), { code: "attested_credential_unavailable", status: 503 }); +}); + +test("Gemini never uses cached or query credentials for a restricted principal", async () => { + const env = { INTELLIGENCE_DB: policyStore({ official_only: 1, forced_provider_preferences: null }), GEMINI_DATA_TIER: "paid" }; + await assert.rejects(providerFetch("gemini", input("gemini"), {}, { env, authority }), { code: "attested_credential_unavailable" }); + env.GEMINI_API_KEY = "synthetic-attested"; + await assert.rejects(providerFetch("gemini", "https://provider.example/generate?key=synthetic-caller", {}, { env, authority }), + { code: "provider_credential_not_allowed", status: 403 }); + await providerFetch("gemini", input("gemini"), {}, { env, authority, send: request => { + assert.equal(request.headers.get("authorization"), null); + assert.equal(request.headers.get("x-goog-api-key"), "synthetic-attested"); + return new Response("{}"); + } }); +}); + +test("private inference bindings refuse missing identity and unavailable current authority before SDK calls", async () => { + let calls = 0; + const env = { AI: providerBinding({ run() { calls++; assert.fail("SDK dispatch"); } }) }; + for (const headers of [{}, { "X-MultiLLM-Dispatch-Principal": '{"version":0,"username":"synthetic-reader"}' }, + { "X-MultiLLM-Dispatch-Principal": '{"version":1,"username":"synthetic-reader"}' }]) { + const result = await handleAiOutbound(new Request("http://ai.internal/v1/embeddings", { method: "POST", headers: { "content-type": "application/json", ...headers }, + body: JSON.stringify({ model: "@cf/baai/bge-m3", input: "Synthetic" }) }), env); + assert.equal(result.status, 503); + } + assert.equal(calls, 0); +}); + +for (const [name, row, expected] of [["official", { official_only: 1, forced_provider_preferences: null }, 403], + ["forced", { official_only: null, forced_provider_preferences: '{"data_collection":"deny"}' }, 403], + ["old schema", {}, 503], ["missing account", null, 503]]) { + test(`registered roleplay refuses ${name} controls before generation, compaction or fallback`, async () => { + const fixture = makeRoleplayEnv({ INTELLIGENCE_DB: policyStore(row) }); + let calls = 0; + const response = await withGlobalFetch(() => { calls++; assert.fail("Roleplay upstream dispatch"); }, () => worker.fetch( + roleplayRequest({ messages: [{ role: "user", content: "Synthetic turn" }], stream: false, model: "roleplay:glm" }), + fixture.env, { waitUntil() {} })); + assert.equal(response.status, expected, await response.clone().text()); + assert.equal(calls, 0); + }); +} + +test("registered roleplay refuses an unreadable control store even with an administrator credential", async () => { + const fixture = makeRoleplayEnv({ INTELLIGENCE_DB: { prepare() { throw Error("Synthetic outage"); } } }); + await withGlobalFetch(() => assert.fail("Roleplay upstream dispatch"), async () => { + const response = await worker.fetch(roleplayRequest({ messages: [{ role: "user", content: "Synthetic turn" }], + model: "roleplay:glm", stream: false }), fixture.env, { waitUntil() {} }); + assert.equal(response.status, 503, await response.clone().text()); + }); +}); + + +test("index polling rereads current controls before every SDK call", async () => { + const { KnowledgeCorpus } = await import("../worker/knowledge/corpus.mjs"); + let controls = { official_only: null, forced_provider_preferences: null }, calls = 0; + const env = { INTELLIGENCE_DB: { prepare(query) { + return { bind() { return this; }, async first() { return controls; } }; + } }, KNOWLEDGE_INDEX: providerBinding({ search() { assert.fail("Unexpected search"); }, items: { get() { return { async info() { + calls++; return { id: "synthetic-item", key: "revisions/synthetic.txt", status: "completed" }; + } }; } } }) }; + const corpus = new KnowledgeCorpus(env); + const artifact = { snapshot_key: "snapshots/synthetic.txt", index_key: "revisions/synthetic.txt", + content_hash: "a".repeat(64), byte_length: 1 }; + await withDispatchPrincipal(env, { authenticated: true, keyId: "synthetic" }, async () => { + assert.equal((await corpus.reconcileRevision(artifact, "synthetic-item")).status, "completed"); + controls = { official_only: 1 }; + await assert.rejects(() => corpus.reconcileRevision(artifact, "synthetic-item"), { code: "key_data_controls_unavailable" }); + controls = null; + await assert.rejects(() => corpus.reconcileRevision(artifact, "synthetic-item"), { code: "key_data_controls_unavailable" }); + }); + assert.equal(calls, 1); +}); + + +test("import watches use the source account instead of the scheduler and refuse legacy ownerless imports", async () => { + const { checkImports } = await import("../worker/knowledge/skills-import.mjs"); + const env = { INTELLIGENCE_DB: policyStore({ official_only: 1, forced_provider_preferences: null }) }; + const source = { source: "github", repository: "example/synthetic", path: "", ref: "HEAD", commit: "a".repeat(40) }; + for (const dispatch_principal of [undefined, { authenticated: true, keyId: "source-key" }]) { + let checked = 0, calls = 0; + const store = { async call(name, body) { + if (name === "imports.due") return [{ skill_id: "synthetic", origin: { ...source, dispatch_principal } }]; + assert.equal(name, "imports.checked"); assert.equal(body.result.status, "error"); checked++; + } }; + await withDispatchPrincipal({ INTELLIGENCE_DB: policyStore() }, { authenticated: true, keyId: "admin" }, async () => { + assert.equal(await checkImports(env, store, { fetch() { calls++; assert.fail("Watch dispatch"); } }), 1); + }); + assert.equal(calls, 0); assert.equal(checked, 1); + } +}); + + +for (const policy of [{ official_only: 0, forced_provider_preferences: null }, + { official_only: null, forced_provider_preferences: '{}' }]) { + test(`every non-NULL policy requires current operator credentials and an approved origin: ${JSON.stringify(policy)}`, async () => { + const env = { INTELLIGENCE_DB: policyStore(policy), OPENROUTER_API_KEY: "synthetic-current" }; + const options = { env, authority, send: () => assert.fail("Refused credential must not leave") }; + await assert.rejects(providerFetch("openrouter", "https://unapproved.example/v1/chat", { + headers: { authorization: "Bearer synthetic-cached" }, + }, options), { code: "attested_credential_unavailable", status: 503 }); + for (const origin of ["https://unapproved.example", "https://openrouter.ai"]) { + await assert.rejects(providerFetch("openrouter", origin + "/api/v1/chat?key=synthetic-caller", {}, options), + { code: "provider_credential_not_allowed", status: 403 }); + } + const send = request => { + assert.equal(request.headers.get("authorization"), "Bearer " + env.OPENROUTER_API_KEY); + return new Response("{}"); + }; + await providerFetch("openrouter", input(), {}, { env, authority, send }); + env.OPENROUTER_API_KEY = "synthetic-changed-binding"; + await providerFetch("openrouter", input(), {}, { env, authority, send }); + delete env.OPENROUTER_API_KEY; + await assert.rejects(providerFetch("openrouter", input(), {}, options), + { code: "attested_credential_unavailable", status: 503 }); + }); +} diff --git a/tests/test_realtime_worker.mjs b/tests/test_realtime_worker.mjs index 9a733962..00cdcd6a 100644 --- a/tests/test_realtime_worker.mjs +++ b/tests/test_realtime_worker.mjs @@ -68,7 +68,7 @@ const done = (id = "r1") => JSON.stringify({ type: "response.done", response: { test("disabled, empty and malformed flags preserve registered Container bytes and headers", async () => { for (const flag of [undefined, "", "false", "garbage"]) { - let calls = 0; const env = { INTELLIGENCE_DB: policyStore(), REALTIME_ENABLED: flag, MULTILLM_PROXY_CONTAINER: { getByName() { return { + let calls = 0; const env = { REALTIME_ENABLED: flag, INTELLIGENCE_DB: policyStore(), MULTILLM_PROXY_CONTAINER: { getByName() { return { async fetch(req) { calls++; assert.equal(req.headers.get("upgrade"), "websocket"); return new Response("unchanged 雪", { status: 418, headers: { "x-original": "yes" } }); } }; } } }; const response = await worker.fetch(request(), env, {}); @@ -191,7 +191,7 @@ test("dashboard key model grants, scopes, expiry and tickets use current D1 auth assert.equal((await handleRealtimeRequest(request({ authorization: `Bearer ${key}` }), f.env, f.ctx, f.options)).status, 403); f.sql.prepare("UPDATE control_users SET scopes='chat,audio',expires_at='2000-01-01T00:00:00Z' WHERE username='fixture-user'").run(); assert.equal((await handleRealtimeRequest(request({ authorization: `Bearer ${key}` }), f.env, f.ctx, f.options)).status, 401); - f.sql.prepare("UPDATE control_users SET expires_at=NULL WHERE username='fixture-user'").run(); + f.sql.prepare("UPDATE control_users SET expires_at=NULL,official_only=1 WHERE username='fixture-user'").run(); assert.equal((await handleRealtimeRequest(request({ authorization: `Bearer ${key}` }), f.env, f.ctx, f.options)).status, 101); f.server.emit("close", { code: 1000 }); await f.flush(); assert.equal(f.calls(), 1); }); @@ -287,8 +287,8 @@ test("registered Realtime sockets and ticket issuance enforce provider classes", try { for (const request of [f.socket(), f.ticket()]) { const response = await worker.fetch(request, f.env, f.ctx); - assert.equal(response.status, 503); - assert.equal((await response.json()).error.code, "key_data_controls_unavailable"); + assert.equal(response.status, 403); + assert.equal((await response.json()).error.code, "provider_class_not_allowed"); } assert.equal(dials, 0); assert.equal(f.sql.prepare("SELECT COUNT(*) AS n FROM realtime_tickets").get().n, 0); @@ -302,8 +302,8 @@ for (const selected of ["openrouter:realtime-test", "openai:realtime-test"]) { .run('{"data_collection":"deny"}'); for (const request of [f.socket(), f.ticket()]) { const response = await worker.fetch(request, f.env, f.ctx); - assert.equal(response.status, 503); - assert.equal((await response.json()).error.code, "key_data_controls_unavailable"); + assert.equal(response.status, 403); + assert.equal((await response.json()).error.code, "forced_provider_preferences_not_supported"); } assert.equal(f.calls(), 0); }); @@ -320,8 +320,8 @@ test("ticket consumption rechecks changed data controls before consumption or di f.sql.prepare("UPDATE control_users SET official_only=?,forced_provider_preferences=? WHERE username='controlled'") .run(official, preferences); const response = await worker.fetch(f.socket(value), f.env, f.ctx); - assert.equal(response.status, 503); - assert.ok(["key_data_controls_unavailable", "realtime_auth_unavailable"].includes((await response.json()).error.code)); + assert.equal(response.status, preferences === "invalid" ? 503 : 403); + assert.equal((await response.json()).error.code, code); assert.equal(f.sql.prepare("SELECT consumed_at FROM realtime_tickets").get().consumed_at, null); assert.equal(f.sql.prepare("SELECT COUNT(*) AS n FROM realtime_sessions").get().n, 0); assert.equal(f.calls(), 0); diff --git a/tests/test_secret_firewall.py b/tests/test_secret_firewall.py index 370b1aa3..a4129c0a 100644 --- a/tests/test_secret_firewall.py +++ b/tests/test_secret_firewall.py @@ -24,6 +24,16 @@ def reset_audit_window(monkeypatch): def application(monkeypatch, mode): from services.proxy_service import ProxyService from services import audit_log + from services.auth_service import AuthService + from services.dispatch_policy import envelope + original = AuthService._load_user_by_username + + def load(username): + if username == "synthetic-agent": + return {"username": username, "data_policy": envelope({"official_only": None, "forced_provider_preferences": None})} + return original(username) + + monkeypatch.setattr(AuthService, "_load_user_by_username", load) events, bodies = [], [] monkeypatch.setenv("SECRET_SCAN_DEFAULT", mode) monkeypatch.setattr(audit_log, "record", lambda *args, **kw: events.append([args, kw])) diff --git a/tests/test_shadow_eval_data_guards.py b/tests/test_shadow_eval_data_guards.py new file mode 100644 index 00000000..60632256 --- /dev/null +++ b/tests/test_shadow_eval_data_guards.py @@ -0,0 +1,89 @@ +"""Controlled source content cannot reach replay candidates or judges.""" + +import time +from unittest.mock import Mock + +import pytest +from flask import Flask, Response, g + +from services.dispatch_policy import envelope +from services.shadow_eval_contract import default_config, encoded, make_sample +from services.shadow_eval_data_policy import source_allows_replay +from services.shadow_eval_runner import evaluate, run +from services.shadow_eval_sampling import sample_success +from tests.test_shadow_eval import configured, response, sample, store # noqa: F401 + +CONTROLS = [{"official_only": True}, {"forced_provider_preferences": {"data_collection": "deny"}}, + {"forced_provider_preferences": {}}] + + +@pytest.mark.parametrize("controls", CONTROLS) +@pytest.mark.parametrize("stream", [False, True]) +def test_controlled_requests_never_enter_sampling(controls, stream, monkeypatch): + submit = Mock() + draw = Mock(return_value=0) + monkeypatch.setattr("services.shadow_eval_sampling.submit", submit) + monkeypatch.setattr("services.shadow_eval_sampling.random.random", draw) + payload = {"model": "auto:example", "messages": [{"role": "user", "content": "Synthetic"}]} + app = Flask(__name__) + with app.test_request_context("/v1/chat/completions", method="POST", json=payload): + g.authenticated_user = {"username": "sampled-user", "shadow_eval_rate": 0.2, **controls} + original = Response(iter([b"data: [DONE]\n\n"]), mimetype="text/event-stream") if stream else response("Synthetic") + assert sample_success(original, payload) is original + if stream: + assert list(original.response) == [b"data: [DONE]\n\n"] + submit.assert_not_called() + draw.assert_not_called() + assert make_sample(payload, controls, "auto:example", {"content": "Synthetic"}, + "openai:production", 1, {}) is None + + +@pytest.mark.parametrize("controls", CONTROLS) +def test_legacy_samples_from_controlled_keys_skip_candidate_and_judges(store, controls, monkeypatch): + configured(store, max_replays_per_run=1) + store.put(sample()) + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: {"username": "sampled-user", **controls}) + dispatch = Mock() + app = Flask(__name__) + with app.test_request_context(): + assert run(app, dispatch, store=store) == 0 + dispatch.assert_not_called() + assert store.results() == [] + + +@pytest.mark.parametrize("controls", CONTROLS) +@pytest.mark.parametrize("change_after", [0, 1, 2]) +def test_source_policy_rechecked_before_candidate_and_each_judge(controls, change_after, monkeypatch): + current = {"username": "sampled-user", "official_only": None, "forced_provider_preferences": None, **(controls if change_after == 0 else {})} + current["data_policy"] = envelope(current) + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: current.copy()) + monkeypatch.setattr("services.shadow_eval_runner.accounted_dispatch", lambda payload, dispatch, **_: dispatch(payload)) + calls = [] + + def dispatch(payload): + calls.append(payload["model"]) + if len(calls) == change_after: + current.update(controls) + current["data_policy"] = envelope(current) + if len(calls) > 1: + return response(encoded({"winner": "A", "confidence": 0.9, "reasons": ["Synthetic"]})) + return response("Candidate synthetic") + + with Flask(__name__).test_request_context(): + result = evaluate(sample(), "openrouter:example/model", default_config(), dispatch, deadline=time.monotonic() + 10) + assert len(calls) == change_after + assert calls == ["openrouter:example/model", "free:json"][:change_after] + assert result["outcome"] == "failed" + + +@pytest.mark.parametrize("source", [None, {"revoked_at": "2026-10-20"}, {"expires_at": "2000-01-01T00:00:00Z"}]) +def test_unknown_or_inactive_source_authority_refuses_replay(source, monkeypatch): + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: source) + assert not source_allows_replay(sample()) + + +def test_unavailable_source_authority_refuses_replay(monkeypatch): + def unavailable(_): + raise RuntimeError("Synthetic storage failure") + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", unavailable) + assert not source_allows_replay(sample()) diff --git a/tests/test_shadow_sampling_policy.py b/tests/test_shadow_sampling_policy.py index c7ccdc64..2f54eb42 100644 --- a/tests/test_shadow_sampling_policy.py +++ b/tests/test_shadow_sampling_policy.py @@ -30,7 +30,7 @@ def loaded_account(policy): is_admin=0, created_at="2026-10-01T00:00:00Z", shadow_eval_rate=0.2, **policy, data_policy=envelope(policy)) user = AuthService._row_to_user(_row(raw)) - assert "official_only" not in user and "forced_provider_preferences" not in user + assert user["data_policy"] == envelope(policy) return user diff --git a/tests/test_stream_cost_breaker.py b/tests/test_stream_cost_breaker.py index e0d412fb..43dd7ace 100644 --- a/tests/test_stream_cost_breaker.py +++ b/tests/test_stream_cost_breaker.py @@ -184,10 +184,17 @@ def test_strict_d1_rows_missing_schema_and_off_wire(monkeypatch): monkeypatch.setattr(user_store,"_call",lambda *a,**k:{"user":row}) with pytest.raises(APIError) as error: breaker.get_user("reader") assert error.value.status_code==503 and error.value.payload["error"]=="stream_cost_storage_unavailable" + from services.dispatch_policy import envelope + row["data_policy"] = envelope(row) row["max_stream_cost_microusd"]=4 assert breaker.get_user("reader")["max_stream_cost_microusd"]==4 row["unknown"]=None - assert breaker.get_user("reader")["max_stream_cost_microusd"] == 4 # Additive protocol metadata is ignored. + result = breaker.get_user("reader") + assert result["max_stream_cost_microusd"] == 4 and "unknown" not in result + # Additive metadata is tolerated, but the verified policy fields remain required. + row.pop("official_only") + with pytest.raises(APIError): breaker.get_user("reader") + row["official_only"] = None row.pop("unknown"); row.pop("max_stream_cost_microusd") monkeypatch.setenv("STREAM_COST_BREAKER_ENABLED","false") captured=[] diff --git a/tests/test_stream_cost_breaker_worker.mjs b/tests/test_stream_cost_breaker_worker.mjs index 1f83f953..c1bfbb76 100644 --- a/tests/test_stream_cost_breaker_worker.mjs +++ b/tests/test_stream_cost_breaker_worker.mjs @@ -67,7 +67,7 @@ function fakeDb({missing=false,stored={}}={}) { } async function call(db,body,flags=env) { return handleControlUsersRequest(new Request("http://intelligence.internal/v1/users",{method:"POST", - headers:{"content-type":"application/json"},body:JSON.stringify({version:1,...body})}),{...flags,INTELLIGENCE_DB:db}); + headers:{"content-type":"application/json"},body:JSON.stringify({version:1,data_policy_version:1,...body})}),{...flags,INTELLIGENCE_DB:db}); } test("strict caps and omitted updates preserve existing column",async()=>{ for(const value of [-1,0.5,true,NaN,1e16]) assert.equal(validUser(account({max_stream_cost_microusd:value})),false); diff --git a/tests/test_usage_ledger.py b/tests/test_usage_ledger.py index 14573a3e..a2854783 100644 --- a/tests/test_usage_ledger.py +++ b/tests/test_usage_ledger.py @@ -178,7 +178,7 @@ def fake(payload, *, endpoint): if payload["operation"] == "totals": return {"version": 1, "totals": {"day_usd": 1.5, "month_usd": 3.0, "day_requests": 2, "month_requests": 4}} if payload["operation"] == "summary": - return {"version": 1, "rows": [{"model": "openai:gpt-4.1", **dict.fromkeys(usage_store.TOTAL_COLUMNS[:7], 1), + return {"version": 1, "rows": [{"model": "openai:gpt-4.1", **dict.fromkeys(usage_store.SUMMARY_COLUMNS, 1), "latency_buckets": [1] + [0] * 10}]} raise PrivateIntelligenceError(503, "storage_unavailable") diff --git a/tests/test_usage_ledger_worker.mjs b/tests/test_usage_ledger_worker.mjs index 8c46f8df..dd38cba0 100644 --- a/tests/test_usage_ledger_worker.mjs +++ b/tests/test_usage_ledger_worker.mjs @@ -64,8 +64,8 @@ test("a flush stores raw rows and daily totals once, even when the batch is retr assert.equal(recent[0].status, 502); const older = (await call({ operation: "recent", since: "2026-09-01T00:00:00.000Z", principal: null, before: recent[0].id, limit: 10 })).body.rows; assert.deepEqual(older.map(item => [item.principal, item.status]), [["alice", 200]]); - assert.deepEqual(Object.keys(recent[0]).sort(), ["at", "cost_basis", "cost_usd", "endpoint", "id", "input_tokens", "key_prefix", "kind", - "latency_ms", "output_tokens", "principal", "request_id", "requested_model", "selected_model", "status"]); + assert.deepEqual(Object.keys(recent[0]).sort(), ["at", "cached_input_tokens", "cost_basis", "cost_usd", "endpoint", "id", "input_tokens", "key_prefix", "kind", + "latency_ms", "output_tokens", "principal", "reasoning_tokens", "request_id", "requested_model", "selected_model", "status"]); const pruned = (await call({ operation: "prune", events_before: "2026-09-26T00:00:00.000Z", rollups_before: "2026-09-26", limit: 100 })).body; assert.deepEqual(pruned.pruned, { events: 1, batches: 0, rollups: 1 }); diff --git a/tests/test_usage_receipts_worker.mjs b/tests/test_usage_receipts_worker.mjs index c4f5c9b7..60ac4249 100644 --- a/tests/test_usage_receipts_worker.mjs +++ b/tests/test_usage_receipts_worker.mjs @@ -145,7 +145,7 @@ test("private dispatcher flag, target, missing table and size bounds precede sto test("ledger flush records once, default rows and responses stay unchanged, missing key preserves usage", async t => { const { env, sql } = database(t); sql.exec("CREATE TABLE control_users (id TEXT PRIMARY KEY)"); - for (const migration of ["0007_usage_ledger.sql", "0016_usage_buckets.sql"]) { + for (const migration of ["0007_usage_ledger.sql", "0016_usage_buckets.sql", "0042_usage_token_details.sql"]) { sql.exec(readFileSync(new URL(`../intelligence-migrations/${migration}`, import.meta.url), "utf8")); } const row = { at: "2026-10-09T00:00:00Z", principal: "alice", key_prefix: null, kind: "chat", endpoint: "/v1/chat/completions", diff --git a/tests/test_usage_token_details.py b/tests/test_usage_token_details.py new file mode 100644 index 00000000..474da3c2 --- /dev/null +++ b/tests/test_usage_token_details.py @@ -0,0 +1,32 @@ +"""Provider-reported token details remain separate from optional price buckets.""" + +import json + +from services import usage_store +from services.usage_types import StreamUsageObserver, UsageObservation + + +def test_usage_details_survive_partial_streams_and_sql_rollups(tmp_path, monkeypatch): + monkeypatch.setenv("USAGE_DB_PATH", str(tmp_path / "usage.sqlite3")) + monkeypatch.setenv("CONTROL_PLANE_DATABASE_URL", "") + observer = StreamUsageObserver(65536) + for body in ({"usage": {"prompt_tokens": 100, "prompt_tokens_details": {"cached_tokens": 60}}}, + {"usage": {"completion_tokens": 20, "completion_tokens_details": {"reasoning_tokens": 8}}}): + observer.feed(("data: " + json.dumps(body) + "\n\n").encode()) + observation = observer.finish() + assert (observation.input_tokens, observation.cached_input_tokens, observation.reasoning_tokens) == (100, 60, 8) + from tests.test_usage_ledger import row + store = usage_store.SqlUsageStore() + store.record("a" * 32, [{**row(tokens=(100, 20)), "cached_input_tokens": 60, "reasoning_tokens": 8}]) + for group in ("model", "principal"): + [summary] = store.summary(group, "2000-01-01", "2100-01-01", None, 10) + assert (summary["cached_input_tokens"], summary["reasoning_tokens"]) == (60, 8) + assert usage_store.summarize(summary)["cache_hit_rate"] == 60 + native = UsageObservation.from_body({"usage": {"input_tokens": 10, "output_tokens": 3, + "cache_read_input_tokens": 60, "cache_creation_input_tokens": 30}}) + assert (native.input_tokens, native.cached_input_tokens) == (100, 60) + responses = UsageObservation.from_body({"response": {"usage": {"input_tokens": 100, + "input_tokens_details": {"cached_tokens": 60}, "output_tokens_details": {"reasoning_tokens": 8}}}}) + assert (responses.cached_input_tokens, responses.reasoning_tokens) == (60, 8) + assert UsageObservation.from_body({"usage": {"prompt_tokens_details": {"cached_tokens": -1}, + "completion_tokens_details": {"reasoning_tokens": True}}}).cached_input_tokens is None diff --git a/tests/test_usage_token_details_worker.mjs b/tests/test_usage_token_details_worker.mjs new file mode 100644 index 00000000..83b86655 --- /dev/null +++ b/tests/test_usage_token_details_worker.mjs @@ -0,0 +1,39 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { convertV4MiniflareOptions, Miniflare } from "./helpers/local_miniflare.mjs"; +import { createUsageObserver } from "../worker/request-telemetry.mjs"; +import { handleUsageLedgerRequest, recordNativeUsage } from "../worker/usage-ledger-d1.mjs"; +import { applyMigrations } from "./d1_migrations.mjs"; + +async function database(t) { + const mf = new Miniflare(convertV4MiniflareOptions({ modules: true, + script: "export default {fetch(){return new Response('ok')}}", d1Databases: ["INTELLIGENCE_DB"] })); + t.after(() => mf.dispose()); + const db = await mf.getD1Database("INTELLIGENCE_DB"); + await applyMigrations(db); + return { INTELLIGENCE_DB: db }; +} + +test("reported cache and reasoning tokens persist in atomic D1 rollups", async t => { + const env = await database(t); + const observer = createUsageObserver(true); + for (const usage of [{ prompt_tokens: 100, prompt_tokens_details: { cached_tokens: 60 } }, + { completion_tokens: 20, completion_tokens_details: { reasoning_tokens: 8 } }]) { + observer.feed(new TextEncoder().encode(`data: ${JSON.stringify({ usage })}\n\n`)); + } + const { usage } = observer.finish(); + assert.deepEqual(usage, { input_tokens: 100, output_tokens: 20, cached_input_tokens: 60, reasoning_tokens: 8 }); + await recordNativeUsage(env, { ...usage, provider: "openrouter", model: "example/model", principal: "example", + status: 200, endpoint: "/v1/chat/completions", duration_ms: 5, requestId: "a".repeat(32), cost_usd: null, cost_basis: null }); + const stored = await env.INTELLIGENCE_DB.prepare("SELECT cached_input_tokens, reasoning_tokens FROM usage_events").first(); + assert.deepEqual(stored, { cached_input_tokens: 60, reasoning_tokens: 8 }); + for (const group of ["model", "principal"]) { + const response = await handleUsageLedgerRequest(new Request("http://intelligence.internal/v1/usage", { + method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ version: 1, operation: "summary", + group, since: "2000-01-01", until: "2100-01-01", principal: null, limit: 10 }), + }), env); + const [row] = (await response.json()).rows; + assert.equal(row.cached_input_tokens, 60); + assert.equal(row.reasoning_tokens, 8); + } +}); diff --git a/tests/test_user_store_d1.py b/tests/test_user_store_d1.py index 5d2c3631..364b8ddc 100644 --- a/tests/test_user_store_d1.py +++ b/tests/test_user_store_d1.py @@ -26,7 +26,7 @@ def __call__(self, payload, *, endpoint): if self.down: raise PrivateIntelligenceError(503, "storage_unavailable") if operation == "capabilities": - return {"version": 1, "key_controls": {"protocol": 1, "enforcement": False}} + return {"version": 1, "key_controls": {"protocol": 2, "enforcement": True}} if operation == "list": names = sorted(name for name in self.rows if payload["after"] is None or name > payload["after"]) return {"version": 1, "users": [dict(self.rows[name]) for name in names[:payload["limit"]]]} @@ -38,7 +38,7 @@ def __call__(self, payload, *, endpoint): if row["api_key_prefix"] == payload["prefix"] and row["revoked_at"] is None]} if operation == "upsert": assert set(payload["user"]) == set(user_store.USER_FIELDS) - self.rows[payload["user"]["username"]] = {**payload["user"], "data_policy": envelope({"official_only": None, "forced_provider_preferences": None, **payload["user"]})} + self.rows[payload["user"]["username"]] = {**payload["user"], "data_policy": envelope(payload["user"])} return {"version": 1, "stored": True} if operation == "delete": return {"version": 1, "deleted": self.rows.pop(payload["username"], None) is not None} @@ -196,7 +196,7 @@ def refuse(self, payload, *, endpoint): user_store.get_user("agent") with pytest.raises(APIError): user_store.upsert_user(d1.rows["agent"]) - assert d1.calls == ["capabilities", "capabilities", "get", "upsert"] + assert d1.calls == ["get", "upsert"] def test_account_lookups_get_a_longer_transport_budget(monkeypatch): @@ -281,40 +281,27 @@ def fake_submit(url, body, stopped, deadline, results, slots, success_statuses, assert store.transport_stats()["users"]["slot_exhausted"] == before + 1 -def test_policy_extensions_are_sent_only_after_a_readable_capability(monkeypatch): +def test_contract_announces_the_account_protocol_without_requiring_dispatch_capability(monkeypatch): calls = [] def peer(payload, *, endpoint): calls.append(dict(payload)) - if payload["operation"] == "capabilities": - return {"key_controls": {"protocol": 1, "enforcement": False}} return {"user": None} monkeypatch.setattr(user_store, "request_private_intelligence", peer) assert user_store.get_user("synthetic") is None - assert calls == [{"operation": "capabilities"}, {"operation": "get", "username": "synthetic", "data_policy_version": 1}] - calls.clear() - - def old_peer(payload, *, endpoint): - calls.append(dict(payload)) - if payload["operation"] == "capabilities": - raise PrivateIntelligenceError(400, "invalid_request") - return {"user": None} - - monkeypatch.setattr(user_store, "request_private_intelligence", old_peer) - assert user_store.get_user("synthetic") is None - assert calls == [{"operation": "capabilities"}, {"operation": "get", "username": "synthetic"}] + assert calls == [{"operation": "get", "username": "synthetic", "data_policy_version": 1}] -@pytest.mark.parametrize("capability", [None, {}, {"protocol": 0}, {"protocol": True}]) -def test_malformed_capability_never_downgrades_to_an_unverified_row(monkeypatch, capability): +@pytest.mark.parametrize("policy", [None, {}, {"version": 0}, {"version": True}]) +def test_malformed_management_policy_never_downgrades_to_an_unverified_row(monkeypatch, policy): calls = [] def peer(payload, *, endpoint): calls.append(payload) - return {"key_controls": capability} + return {"user": {"data_policy": policy}} monkeypatch.setattr(user_store, "request_private_intelligence", peer) with pytest.raises(APIError): user_store.get_user("synthetic") - assert calls == [{"operation": "capabilities"}] + assert calls == [{"operation": "get", "username": "synthetic", "data_policy_version": 1}] diff --git a/tests/test_vertex_dispatch.py b/tests/test_vertex_dispatch.py new file mode 100644 index 00000000..e22d723a --- /dev/null +++ b/tests/test_vertex_dispatch.py @@ -0,0 +1,124 @@ +"""Restricted Vertex calls refresh only the current operator credential binding.""" +import json +from types import SimpleNamespace +from unittest.mock import Mock, patch + +import pytest +from flask import Flask, g + +from error_handlers import APIError +from services.dispatch_network import network_admitted +from services.dispatch_policy import envelope +from services.provider_dispatch import provider_http +from tests.unified_api_test_case import UnifiedApiTestCase + +URL = "https://us-central1-aiplatform.googleapis.com/v1/projects/synthetic/locations/us-central1/endpoints/openapi/chat/completions" + + +def binding(name="synthetic-a"): + return json.dumps({"type": "service_account", "client_email": name + "@synthetic.iam.gserviceaccount.com", + "token_uri": "https://oauth2.googleapis.com/token"}) + + +@pytest.fixture +def vertex(monkeypatch): + app = Flask(__name__) + app.config["API_BASE_URLS"] = {"googleai": URL} + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: {"username": "synthetic", "data_policy": envelope({ + "official_only": 1, "forced_provider_preferences": None})}) + monkeypatch.delenv("GOOGLE_APPLICATION_CREDENTIALS_JSON", raising=False) + monkeypatch.delenv("GOOGLE_APPLICATION_CREDENTIALS", raising=False) + monkeypatch.setenv("GOOGLEAI_API_KEY", "synthetic-unattested-key") + with app.test_request_context(): + g.authenticated_user = {"username": "synthetic"} + yield + + +def credentials(token="synthetic-current"): + result = SimpleNamespace(token=token) + def refresh(request): + assert network_admitted() + result.refresh = Mock(side_effect=refresh) + return result + + +@pytest.mark.parametrize("source", ["json", "file"]) +def test_current_service_account_binding_overrides_cached_token(vertex, monkeypatch, tmp_path, source): + path = tmp_path / "operator.json" + def set_binding(value): + if source == "json": + monkeypatch.setenv("GOOGLE_APPLICATION_CREDENTIALS_JSON", value) + else: + path.write_text(value) + monkeypatch.setenv("GOOGLE_APPLICATION_CREDENTIALS", str(path)) + send = Mock() + current = credentials("synthetic-first") + set_binding(binding()) + with patch("google.oauth2.service_account.Credentials.from_service_account_info", return_value=current) as build: + provider_http("googleai", "POST", URL, transport=send, headers={"Authorization": "Bearer synthetic-cached"}) + assert send.call_args.kwargs["headers"]["Authorization"] == "Bearer synthetic-first" + current.token = "synthetic-second" + set_binding(binding("synthetic-b")) + provider_http("googleai", "POST", URL, transport=send, headers={"Authorization": "Bearer synthetic-first"}) + assert send.call_args.kwargs["headers"]["Authorization"] == "Bearer synthetic-second" + assert build.call_args.args[0]["client_email"].startswith("synthetic-b@") + assert current.refresh.call_count == 2 + + +@pytest.mark.parametrize("mode", ["missing", "malformed", "missing-file", "changed-during-refresh", "untrusted-token-origin"]) +def test_unverified_or_changed_service_account_cannot_dispatch(vertex, monkeypatch, mode): + send = Mock() + monkeypatch.setattr("services.provider_credentials.shutil.which", lambda _: None) + if mode == "malformed": + monkeypatch.setenv("GOOGLE_APPLICATION_CREDENTIALS_JSON", "{}") + elif mode == "missing-file": + monkeypatch.setenv("GOOGLE_APPLICATION_CREDENTIALS", "/nonexistent/synthetic.json") + elif mode == "untrusted-token-origin": + monkeypatch.setenv("GOOGLE_APPLICATION_CREDENTIALS_JSON", binding().replace("oauth2.googleapis.com", "unrelated.example")) + elif mode == "changed-during-refresh": + monkeypatch.setenv("GOOGLE_APPLICATION_CREDENTIALS_JSON", binding()) + current = credentials() + if mode == "changed-during-refresh": + current.refresh.side_effect = lambda _: monkeypatch.setenv("GOOGLE_APPLICATION_CREDENTIALS_JSON", binding("synthetic-b")) + with patch("google.oauth2.service_account.Credentials.from_service_account_info", return_value=current): + with pytest.raises(APIError) as error: + provider_http("googleai", "POST", URL, transport=send, headers={"Authorization": "Bearer synthetic-cached"}) + assert error.value.status_code == 503 + send.assert_not_called() + + +@pytest.mark.parametrize("changed", [False, True]) +def test_gcloud_binds_a_fresh_token_to_the_current_operator_account(vertex, monkeypatch, changed): + monkeypatch.setattr("services.provider_credentials.shutil.which", lambda _: "/trusted/gcloud") + answers = ["synthetic-a", "synthetic-current", "synthetic-b" if changed else "synthetic-a"] + run = Mock(side_effect=lambda *args, **kwargs: SimpleNamespace(stdout=answers.pop(0))) + monkeypatch.setattr("services.provider_credentials.subprocess.run", run) + send = Mock() + if changed: + with pytest.raises(APIError): + provider_http("googleai", "POST", URL, transport=send) + send.assert_not_called() + else: + provider_http("googleai", "POST", URL, transport=send) + assert send.call_args.kwargs["headers"]["Authorization"] == "Bearer synthetic-current" + assert run.call_args_list[1].args[0] == ["/trusted/gcloud", "auth", "print-access-token", "synthetic-a", "--quiet"] + + +class RegisteredVertexDispatchTest(UnifiedApiTestCase): + def test_restricted_native_vertex_route_uses_current_service_account(self): + from tests.test_media_generation import upstream + auth = self.app_module.AuthService + with patch.object(auth, "get_current_user", return_value={"username": "admin", "is_admin": True}): + created = auth.create_user("synthetic-vertex", is_admin=True) + auth.set_key_controls("synthetic-vertex", {"official_only": True}) + self.app.config["API_BASE_URLS"]["googleai"] = URL.rsplit("/", 1)[0] + with patch.dict("os.environ", {"GOOGLE_APPLICATION_CREDENTIALS_JSON": binding(), "PROJECT_ID": "synthetic", + "LOCATION": "us-central1", "GOOGLE_ENDPOINT": "us-central1-aiplatform.googleapis.com"}), \ + patch("google.oauth2.service_account.Credentials.from_service_account_info", return_value=credentials()), \ + patch.object(auth, "get_google_token", return_value="synthetic-cached"), \ + patch("requests.sessions.Session.request", return_value=upstream(200, + {"choices": [{"message": {"role": "assistant", "content": "Synthetic"}}]})) as send: + result = self.client.post("/googleai/v1/chat/completions", headers={"Authorization": "Bearer " + created["api_key"]}, + json={"model": "synthetic", "messages": [{"role": "user", "content": "Synthetic"}]}) + assert result.status_code == 200, result.json + assert send.call_args.kwargs["headers"]["Authorization"] == "Bearer synthetic-current" diff --git a/tests/test_wave3_transport_worker.mjs b/tests/test_wave3_transport_worker.mjs index 877c00be..833fd23b 100644 --- a/tests/test_wave3_transport_worker.mjs +++ b/tests/test_wave3_transport_worker.mjs @@ -1,4 +1,4 @@ -import { policyFixtureEnv } from "./helpers/dispatch_fixture.mjs"; +import { policyStore } from "./helpers/dispatch_fixture.mjs"; import assert from "node:assert/strict"; import test from "node:test"; import { DatabaseSync } from "node:sqlite"; @@ -6,7 +6,7 @@ import { readFile } from "node:fs/promises"; import { createHash } from "node:crypto"; import { handleUsageLedgerRequest, recordNativeUsage } from "../worker/usage-ledger-d1.mjs"; import { BUCKET_FIELDS } from "../worker/usage-buckets-d1.mjs"; -import { handleKnowledgeEdgeRequest as rawhandleKnowledgeEdgeRequest } from "../worker/knowledge-edge.mjs"; +import { handleKnowledgeEdgeRequest } from "../worker/knowledge-edge.mjs"; import { dispatchKnowledge } from "../worker/knowledge/service.mjs"; import { HandoffStore } from "../worker/knowledge/handoff-store.mjs"; import { fixture, request as knowledgeQuery } from "./knowledge_fixture.mjs"; @@ -17,7 +17,7 @@ const clock = Date.parse("2026-10-09T00:00:00Z"); const enabled = { CONTENT_RETENTION_ENABLED: "true" }; const base = { at: new Date(clock).toISOString(), principal: "reader", key_prefix: null, kind: "chat", endpoint: "/v1/chat/completions", requested_model: "openai:fixture", selected_model: "openai:fixture", - status: 200, latency_ms: 300, input_tokens: 100, output_tokens: 10, cost_usd: 0.001, cost_basis: "usage", request_id: null }; + status: 200, latency_ms: 300, input_tokens: 100, output_tokens: 10, cost_usd: 0.001, cost_basis: "usage", request_id: null, cached_input_tokens: null, reasoning_tokens: null }; const buckets = { ordinary_input_tokens: 40, cache_read_input_tokens: 60, cache_write_input_tokens: 0, ordinary_input_cost_microusd: 80, cache_read_input_cost_microusd: 30, cache_write_input_cost_microusd: 0, output_cost_microusd: 80, bucket_basis: "measured", bucket_source: "openai" }; @@ -34,6 +34,7 @@ async function ledger(t, migrated = true) { t.after(() => sqlite.close()); const migration = await readFile(new URL("../intelligence-migrations/0007_usage_ledger.sql", import.meta.url), "utf8"); sqlite.exec(migration.slice(migration.indexOf("CREATE TABLE IF NOT EXISTS usage_events"))); + sqlite.exec(await readFile(new URL("../intelligence-migrations/0042_usage_token_details.sql", import.meta.url), "utf8")); const extend = async () => sqlite.exec(await readFile(new URL("../intelligence-migrations/0016_usage_buckets.sql", import.meta.url), "utf8")); if (migrated) await extend(); const statements = []; @@ -174,7 +175,7 @@ function knowledgeRequest(path, payload, headers = {}) { } function knowledgeCapture(flags = {}) { const captured = []; - return { captured, env: { ADMIN_API_KEY: "knowledge-fixture", ADMIN_USERNAME: "owner", ...flags, + return { captured, env: { ADMIN_API_KEY: "knowledge-fixture", ADMIN_USERNAME: "owner", INTELLIGENCE_DB: policyStore(), ...flags, KNOWLEDGE_SERVICE: { async fetch(_url, init) { captured.push(init.body); return Response.json({ version: 1, result: { status: "fixture" } }); @@ -283,7 +284,7 @@ test("handoff Durable Object wrapper passes the trusted snapshot and clock to th test("captured edge policy survives service configuration changes and forbids caller-selected payload identity", async () => { const f = await fixture(); await f.published(); - const edgeEnv = { ...enabled, ADMIN_API_KEY: "knowledge-fixture", ADMIN_USERNAME: "owner", + const edgeEnv = { ...enabled, ADMIN_API_KEY: "knowledge-fixture", ADMIN_USERNAME: "owner", INTELLIGENCE_DB: policyStore(), CONTENT_RETENTION_POLICY_JSON: '{"routes":{"/v1/knowledge/context":"zero"}}', KNOWLEDGE_SERVICE: { async fetch(_url, init) { const envelope = JSON.parse(init.body); @@ -320,5 +321,3 @@ test("public key and route zero prevent durable roleplay recovery and memory rea assert.equal(entry.storage.operations.put, 0); } }); - -function handleKnowledgeEdgeRequest(request, env, ...args) { return rawhandleKnowledgeEdgeRequest(request, policyFixtureEnv(env), ...args); } diff --git a/worker/account-contract.mjs b/worker/account-contract.mjs new file mode 100644 index 00000000..d4bc2e0c --- /dev/null +++ b/worker/account-contract.mjs @@ -0,0 +1,30 @@ +/** Negotiate additive account metadata before crossing a strict Container decoder. */ +import { controlled } from "./key-controls-capability.mjs"; +import { verifyDataPolicy } from "./dispatch-policy.mjs"; + +export const ACCOUNT_POLICY_VERSION = 1; +const POLICY_FIELDS = new Set(["official_only", "forced_provider_preferences", "data_policy"]); + +export function accountPolicyVersion(body) { + if (!Object.hasOwn(body, "data_policy_version")) return null; + if (body.data_policy_version !== ACCOUNT_POLICY_VERSION) throw new Error("Unsupported account policy version"); + delete body.data_policy_version; + return ACCOUNT_POLICY_VERSION; +} + +export const hasAccountPolicy = controlled; + +export function accountReply(row, version, { management = false } = {}) { + if (!row || version === ACCOUNT_POLICY_VERSION) return row; + // Management remains readable to strict legacy decoders. Authentication by + // prefix refuses controlled rows separately, before projection. + verifyDataPolicy(row.data_policy); + if (hasAccountPolicy(row) && !management) throw new Error("key_controls_require_upgrade"); + return Object.fromEntries(Object.entries(row).filter(([name]) => !POLICY_FIELDS.has(name))); +} + +export function accountWrite(user, version) { + if (version === ACCOUNT_POLICY_VERSION || !user || typeof user !== "object" || Array.isArray(user)) return user; + if ([...POLICY_FIELDS].some(name => Object.hasOwn(user, name))) throw new Error("Account policy requires negotiation"); + return { ...user, official_only: null, forced_provider_preferences: null }; +} diff --git a/worker/control-users-d1.mjs b/worker/control-users-d1.mjs index 8f8559c8..cb3fb91d 100644 --- a/worker/control-users-d1.mjs +++ b/worker/control-users-d1.mjs @@ -1,9 +1,10 @@ +import { accountPolicyVersion, accountReply, accountWrite } from "./account-contract.mjs"; /** * Durable dashboard accounts (username, key hash and metadata) in D1, reachable only * through the Container's private outbound handler. Fixed statements, no client SQL. */ import { dataPolicy } from "./dispatch-policy.mjs"; -import { KEY_CONTROLS, expansionFence } from "./key-controls-capability.mjs"; +import { KEY_CONTROLS } from "./key-controls-capability.mjs"; import { validDataControls } from "./key-data-guards.mjs"; import { STREAM_CAP_FIELD, streamCostEnabled, validStreamCap } from "./stream-cost-breaker.mjs"; import { commitRevision, revisionSyncSettings } from "./config-revision.mjs"; @@ -219,13 +220,10 @@ export async function handleControlUsersRequest(request, env) { if (!body || Array.isArray(body) || typeof body !== "object" || body.version !== 1) return reply({ error: "invalid_request" }, 400); } catch { return reply({ error: "invalid_request" }, 400); } const db = env.INTELLIGENCE_DB; - const policyVersion = body.data_policy_version === 1 ? 1 : null; - delete body.data_policy_version; + let policyVersion; + try { policyVersion = accountPolicyVersion(body); } + catch { return reply({ error: "invalid_request" }, 400); } if (body.operation === "capabilities") return reply({ version: 1, key_controls: KEY_CONTROLS }); - const accountReply = user => { - if (!user || policyVersion === 1) return user; - return Object.fromEntries(Object.entries(user).filter(([name]) => !["official_only", "forced_provider_preferences", "data_policy"].includes(name))); - }; try { if (AUDIT_OPERATIONS.has(body.operation)) { const result = await handleAuditOperation(db, body); @@ -240,24 +238,22 @@ export async function handleControlUsersRequest(request, env) { : db.prepare(`SELECT ${columns} FROM control_users WHERE username > ? ORDER BY username LIMIT ?`).bind(body.after, body.limit) ).all()).results; const users = await readUsers(query, env); - return reply({ version: 1, users: users.map(user => accountReply(user, policyVersion)) }); + return reply({ version: 1, users: users.map(user => accountReply(user, policyVersion, { management: true })) }); } case "get": { if (!fields(body, ["version", "operation", "username"]) || !text(body.username, 128)) break; const user = await readUsers(columns => db.prepare(`SELECT ${columns} FROM control_users WHERE username=?`) .bind(body.username).first(), env); - if (policyVersion !== 1 && user) expansionFence(user); return reply({ version: 1, user: accountReply(user ?? null, policyVersion) }); } case "by_prefix": { if (!fields(body, ["version", "operation", "prefix"]) || !text(body.prefix, 64)) break; const users = await activeUsersByPrefix(db, body.prefix, env); - if (policyVersion !== 1) users.forEach(expansionFence); - return reply({ version: 1, users: users.map(accountReply) }); + return reply({ version: 1, users: users.map(user => accountReply(user, policyVersion)) }); } case "upsert": { if (!fields(body, ["version", "operation", "user"])) break; - body.user = { official_only: null, forced_provider_preferences: null, ...body.user }; + body.user = accountWrite(body.user, policyVersion); if (!validUser(body.user)) break; if (!streamCostEnabled(env) && body.user[STREAM_CAP_FIELD] != null) return reply({ error: "invalid_request" }, 400); const user = { official_only: null, forced_provider_preferences: null, shadow_eval_rate: null, secret_scan_mode: null, ...body.user }; diff --git a/worker/intelligence-auth-d1.mjs b/worker/intelligence-auth-d1.mjs index 44c68e14..50562f13 100644 --- a/worker/intelligence-auth-d1.mjs +++ b/worker/intelligence-auth-d1.mjs @@ -1,4 +1,4 @@ -import { expansionFence } from "./key-controls-capability.mjs"; +import { KEY_CONTROLS, controlled, expansionFence } from "./key-controls-capability.mjs"; import { dataPolicy } from "./dispatch-policy.mjs"; import { logFailure } from "./log.mjs"; @@ -78,7 +78,14 @@ export async function handleIntelligenceAuthRequest(request, env) { if (body.operation === "lookup") { if (!fields(body, ["version", "operation", "keyPrefix"]) || !validPrefix(body.keyPrefix)) return reply({error: "invalid_request"}, 400); - return reply({version: 1, principal: await lookupIntegrationPrincipal(db, body.keyPrefix)}); + const principal = await lookupIntegrationPrincipal(db, body.keyPrefix); + if (principal && !principal.revokedAt) { + const policy = await db.prepare("SELECT official_only, forced_provider_preferences FROM intelligence_principals WHERE id = ? AND revoked_at IS NULL").bind(principal.id).first(); + if (!policy) throw Error("key_data_controls_unavailable"); + dataPolicy(policy); + if (controlled(policy)) throw Error("key_controls_require_upgrade"); + } + return reply({version: 1, principal}); } if (typeof body.principalId !== "string" || !idPattern.test(body.principalId)) return reply({error: "invalid_request"}, 400); if (body.operation === "dispatch_policy") { @@ -86,7 +93,7 @@ export async function handleIntelligenceAuthRequest(request, env) { const row = await db.prepare(`SELECT p.official_only, p.forced_provider_preferences, c.key_prefix FROM intelligence_principals p JOIN intelligence_credentials c ON c.principal_id=p.id AND c.version=p.version WHERE p.id=? AND p.revoked_at IS NULL`).bind(body.principalId).first(); - return reply({version: 1, principal: row ? { username: body.principalId, api_key_prefix: row.key_prefix, + return reply({version: 1, key_controls: KEY_CONTROLS, principal: row ? { username: body.principalId, api_key_prefix: row.key_prefix, data_policy: dataPolicy(row) } : null}); } const now = new Date().toISOString(); diff --git a/worker/key-controls-capability.mjs b/worker/key-controls-capability.mjs index 74ae012b..3f97a0f5 100644 --- a/worker/key-controls-capability.mjs +++ b/worker/key-controls-capability.mjs @@ -1,5 +1,5 @@ /** Code capability, never an operator feature flag. */ -export const KEY_CONTROLS = Object.freeze({ protocol: 1, enforcement: false }); +export const KEY_CONTROLS = Object.freeze({ protocol: 2, enforcement: true }); export const controlled = row => row?.official_only != null || row?.forced_provider_preferences != null; export function expansionFence(row) { if (!KEY_CONTROLS.enforcement && controlled(row)) { diff --git a/worker/key-data-guards.mjs b/worker/key-data-guards.mjs index 6fba1cce..f9612651 100644 --- a/worker/key-data-guards.mjs +++ b/worker/key-data-guards.mjs @@ -30,7 +30,7 @@ export function providerDataClass(provider, env = {}) { } export function providerClassPermitted(user, provider, env = {}) { - return !user?.official_only || providerDataClass(provider, env) === "official"; + return (user?.official_only !== 1 && user?.official_only !== true) || providerDataClass(provider, env) === "official"; } export function mergePreferences(caller, forced) { @@ -65,7 +65,7 @@ export async function guardNativeRequest(request, env, authority) { return { response: Response.json({ error: { code: "provider_class_not_allowed" } }, { status: 403 }) }; } const forced = parsePreferences(user.forced_provider_preferences); - if (authority.provider !== "openrouter" || !forced || !request.body) return { request }; + if (authority.provider !== "openrouter" || forced === null || !request.body) return { request }; const payload = await request.clone().json(); if (!object(payload)) return { response: Response.json({ error: { code: "invalid_request" } }, { status: 400 }) }; payload.provider = mergePreferences(payload.provider, forced); diff --git a/worker/knowledge-edge.mjs b/worker/knowledge-edge.mjs index 5348c6ce..cd9072c8 100644 --- a/worker/knowledge-edge.mjs +++ b/worker/knowledge-edge.mjs @@ -1,3 +1,4 @@ +import { requireControlledPeer, capabilityHeader } from "./peer-capability.mjs"; import { currentDataControls } from "./key-data-guards.mjs"; /** * Knowledge MCP and REST at the edge for credentials the Worker can verify itself: @@ -284,13 +285,15 @@ async function dispatch(env, operation, principal, payload, signal) { if (Buffer.byteLength(body) > (operation === "skills.sync" ? SYNC_REQUEST_BYTES : MAX_REQUEST_BYTES)) { throw new KnowledgeEdgeError("request_too_large", "The Knowledge request exceeds 64 KiB.", 413); } - await currentDataControls(env, { keyId: principal.id }); + const policy = await currentDataControls(env, { keyId: principal.id }); + await requireControlledPeer(policy, env.KNOWLEDGE_SERVICE, "http://knowledge.internal/ready"); const deadline = AbortSignal.timeout(DEADLINE_MS); let response; let result; try { response = await env.KNOWLEDGE_SERVICE.fetch("http://knowledge.internal/v1/dispatch", { - method: "POST", headers: { "content-type": "application/json" }, body, + method: "POST", headers: { "content-type": "application/json", [capabilityHeader]: "2", + "X-MultiLLM-Dispatch-Principal": JSON.stringify({ version: 1, username: principal.id }) }, body, signal: signal ? AbortSignal.any([signal, deadline]) : deadline, }); result = await response.json(); diff --git a/worker/knowledge-outbound.mjs b/worker/knowledge-outbound.mjs index 416f335c..d94a91e8 100644 --- a/worker/knowledge-outbound.mjs +++ b/worker/knowledge-outbound.mjs @@ -1,3 +1,5 @@ +import { bindingPrincipal } from "./provider-dispatch.mjs"; +import { requireControlledPeer, requireControlledSender, capabilityHeader } from "./peer-capability.mjs"; import { SYNC_REQUEST_BYTES } from "./knowledge/skills-validation.mjs"; import { errorReply, fail, readJson } from "./knowledge/contracts.mjs"; @@ -9,9 +11,14 @@ export async function handleKnowledgeOutbound(request, env) { if (!env.KNOWLEDGE_SERVICE) fail("knowledge_not_configured", "The private Knowledge service binding is not configured.", 503); const payload = await readJson(request, SYNC_REQUEST_BYTES); if (payload.operation !== "skills.sync" && new TextEncoder().encode(JSON.stringify(payload)).length > 65536) fail("request_too_large", "Request is too large.", 413); - await (await import("./key-data-guards.mjs")).currentDataControls(env, { keyId: payload.principal?.id }); + const source = bindingPrincipal(request, payload.principal?.id); + if (source.keyId !== payload.principal?.id) fail("invalid_principal", "The dispatch principal does not match.", 403); + const policy = await (await import("./key-data-guards.mjs")).currentDataControls(env, source); + requireControlledSender(policy, request); + await requireControlledPeer(policy, env.KNOWLEDGE_SERVICE, "http://knowledge.internal/ready"); return env.KNOWLEDGE_SERVICE.fetch("http://knowledge.internal/v1/dispatch", { - method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(payload), signal: request.signal, + method: "POST", headers: { "content-type": "application/json", [capabilityHeader]: "2", + "X-MultiLLM-Dispatch-Principal": JSON.stringify({ version: 1, username: payload.principal?.id }) }, body: JSON.stringify(payload), signal: request.signal, }); } catch (error) { return errorReply(error); } } diff --git a/worker/knowledge/authority.mjs b/worker/knowledge/authority.mjs index c2a25607..8da966a5 100644 --- a/worker/knowledge/authority.mjs +++ b/worker/knowledge/authority.mjs @@ -61,7 +61,8 @@ async function evictDiscovery(tx, sources) { async function createSource(tx, input, now, discovered = false) { const policy = await policyOf(tx); - const parsed = parseSource(input, policy); + const { dispatch_principal, ...sourceInput } = input; + const parsed = parseSource(sourceInput, policy); const id = await digest(`${parsed.url}\0${parsed.product}\0${parsed.version}`); const existing = await tx.get(`source:${id}`); if (existing && (discovered || existing.identity_confirmed)) return existing; @@ -77,6 +78,7 @@ async function createSource(tx, input, now, discovered = false) { ? { ...existing, ...parsed, title: parsed.title || existing.title, identity_confirmed: true, revision: existing.revision + 1 } : { ...parsed, id, identity_confirmed: !discovered, revision: 1, fence: 0, current_artifact: null, created_at: new Date(now).toISOString(), last_checked_at: null }; + if (dispatch_principal?.authenticated === true && typeof dispatch_principal.keyId === "string") source.dispatch_principal = dispatch_principal; await tx.put(`source:${id}`, source); // A new discovery has no revision yet, so no cached answer or excerpt depends on it. // Bumping here made every parallel query that discovered a source fail with corpus_changed. @@ -138,7 +140,9 @@ async function enqueue(tx, input, now) { retainedByPolicy(artifact, await policyOf(tx)); Object.assign(job, { status: "snapshot", artifact_id: artifact.id, index_key: artifact.index_key }); } - await tx.put(`source:${source.id}`, { ...source, fence: job.fence }); + const principal = source.dispatch_principal ?? input.dispatch_principal; + if (principal?.authenticated === true && typeof principal.keyId === "string") job.dispatch_principal = principal; + await tx.put(`source:${source.id}`, { ...source, ...(job.dispatch_principal ? { dispatch_principal: job.dispatch_principal } : {}), fence: job.fence }); await tx.put(`job:${id}`, job); return job; } diff --git a/worker/knowledge/index.mjs b/worker/knowledge/index.mjs index f97bb9c5..dafa273f 100644 --- a/worker/knowledge/index.mjs +++ b/worker/knowledge/index.mjs @@ -1,3 +1,4 @@ +import { requireControlledSender } from "../peer-capability.mjs"; import { currentDataControls } from "../key-data-guards.mjs"; import { KEY_CONTROLS } from "../key-controls-capability.mjs"; import { bindingPrincipal, withDispatchPrincipal } from "../provider-dispatch.mjs"; @@ -87,7 +88,7 @@ export class KnowledgeSkills extends DurableObject { if (typeof body.principal !== "string" || !body.principal || body.principal.length > 256) fail("invalid_principal", "Invalid skills principal.", 403); const source = bindingPrincipal(request, body.principal); if (source.keyId !== body.principal) fail("invalid_principal", "The dispatch principal does not match.", 403); - await currentDataControls(this.env, source); + requireControlledSender(await currentDataControls(this.env, source), request); return await withDispatchPrincipal(this.env, bindingPrincipal(request, body.principal), async () => reply(await this.skills.call(body.operation, body.payload, body.principal))); } catch (error) { return failure("knowledge_skills_failed", error); } } @@ -108,7 +109,7 @@ export default { if (body.operation !== "skills.sync" && new TextEncoder().encode(JSON.stringify(body)).length > 65536) fail("request_too_large", "Request is too large.", 413); const source = bindingPrincipal(request, body.principal?.id); if (source.keyId !== body.principal?.id) fail("invalid_principal", "The dispatch principal does not match.", 403); - await currentDataControls(env, source); + requireControlledSender(await currentDataControls(env, source), request); let decision; const result = await dispatchKnowledge(env, body, { signal: request.signal, waitUntil: promise => ctx.waitUntil(promise), onSecretScan: value => { decision = value; } }); const response = reply(result); diff --git a/worker/knowledge/providers/transport.mjs b/worker/knowledge/providers/transport.mjs index ceab3047..4efa73a6 100644 --- a/worker/knowledge/providers/transport.mjs +++ b/worker/knowledge/providers/transport.mjs @@ -70,7 +70,7 @@ export async function fetchBounded(provider, url, options, { fetchImpl = fetch, try { if (controller.signal.aborted) throw new Error("aborted"); // Workers supports manual redirects; the non-2xx check rejects them before any follow-up. - const response = await providerFetch(provider, url, { ...options, redirect: "manual", signal: controller.signal }, { send: async checked => fetchImpl(checked.url, await transportOptions(checked, options)) }); + const response = await providerFetch(provider, url, { ...options, signal: controller.signal }, { send: async checked => fetchImpl(checked.url, await transportOptions(checked, options)) }); if (response.status < 200 || response.status >= 300) { let body = null; if ([402, 429].includes(response.status) || (provider === "alexandria" && response.status === 403)) { diff --git a/worker/knowledge/service.mjs b/worker/knowledge/service.mjs index 5a4e7613..c827fab7 100644 --- a/worker/knowledge/service.mjs +++ b/worker/knowledge/service.mjs @@ -1,4 +1,4 @@ -import { withDispatchPrincipal } from "../provider-dispatch.mjs"; +import { dispatchContext, withDispatchPrincipal } from "../provider-dispatch.mjs"; import { authorize, fail, fields, parseQuery, publicUrl, validId } from "./contracts.mjs"; import { getAuthority } from "./authority-client.mjs"; import { KnowledgeCorpus } from "./corpus.mjs"; @@ -62,10 +62,10 @@ export async function scheduleSource(env, authority, sourceId, signal, artifactI return allocation.enabled && allocation.background_limit >= allocation.units_per_call; })) fail("background_disabled", "Enable source and index background allowances before scheduling ingestion.", 409); active(); - const job = await authority.call("job.enqueue", { source_id: sourceId, ...(artifactId ? { artifact_id: artifactId } : {}) }); + const job = await authority.call("job.enqueue", { source_id: sourceId, dispatch_principal: dispatchContext()?.authority, ...(artifactId ? { artifact_id: artifactId } : {}) }); try { active(); - const created = await env.KNOWLEDGE_INGESTION.createBatch([{ id: job.id, params: { job_id: job.id } }]); + const created = await env.KNOWLEDGE_INGESTION.createBatch([{ id: job.id, params: { job_id: job.id, dispatch_principal: job.dispatch_principal } }]); if (!created.length) { active(); const instance = await env.KNOWLEDGE_INGESTION.get(job.id); @@ -146,7 +146,7 @@ async function runKnowledge(env, envelope, options = {}) { return memos.call(operation === "memos.stats" ? "stats" : "purge", parsed); } if (operation === "policy.update") return { policy: await authority.call("policy.update", payload) }; - if (operation === "sources.create") return { source: await authority.call("source.create", payload), job: null }; + if (operation === "sources.create") return { source: await authority.call("source.create", { ...payload, dispatch_principal: dispatchContext()?.authority }), job: null }; if (operation === "sources.update") return { source: await authority.call("source.update", payload) }; fields(payload, ["id"], ["id"]); if (operation === "sources.refresh") return { job: await scheduleSource(env, authority, payload.id) }; @@ -168,7 +168,7 @@ export async function maintainKnowledge(env, { authority = getAuthority(env), co const claimed = await authority.call("artifact.expiration_claim", { id: artifact.id, expires_at: artifact.expires_at }); // Fixed storage cleanup uses the current durable operator account; ingestion // and reconciliation Workflows continue to carry the original source account. - await withDispatchPrincipal(env, { legacyBinding: true }, + await withDispatchPrincipal(env, { authenticated: true, keyId: env.ADMIN_USERNAME || "admin" }, () => corpus.removeArtifact(claimed)); await authority.call("artifact.expire", { id: artifact.id, expires_at: artifact.expires_at }); } catch (error) { diff --git a/worker/knowledge/skills-import.mjs b/worker/knowledge/skills-import.mjs index 5e8c46bd..1b5de7de 100644 --- a/worker/knowledge/skills-import.mjs +++ b/worker/knowledge/skills-import.mjs @@ -63,7 +63,7 @@ async function get(url, headers, options, maxBytes) { const timer = setTimeout(cancel, options.importTimeoutMs ?? TIMEOUT_MS); try { let response; - try { response = await providerFetch("skills", url, { headers, redirect: "manual", signal: controller.signal }, { send: async checked => (options.fetch ?? fetch)(checked.url, await transportOptions(checked, { headers })) }); } + try { response = await providerFetch("skills", url, { headers, signal: controller.signal }, { send: async checked => (options.fetch ?? fetch)(checked.url, await transportOptions(checked, { headers })) }); } catch { fail(controller.signal.aborted ? "upstream_timeout" : "upstream_unavailable", "The skill source could not be reached.", controller.signal.aborted ? 504 : 502); } if (!response.ok) { await response.body?.cancel(); diff --git a/worker/knowledge/skills-store.mjs b/worker/knowledge/skills-store.mjs index a4d19bf2..5dfaad33 100644 --- a/worker/knowledge/skills-store.mjs +++ b/worker/knowledge/skills-store.mjs @@ -1,4 +1,4 @@ -import { providerOperation } from "../provider-dispatch.mjs"; +import { dispatchContext, providerOperation } from "../provider-dispatch.mjs"; import { fail, fields, isRecord } from "./contracts.mjs"; import { digest } from "./evidence.mjs"; import { parseFind, parseGet, parseSync, validateSkill, skillKey, SKILLS_LIMIT } from "./skills-validation.mjs"; @@ -136,7 +136,7 @@ export class SkillsStore { const { record, files } = validated, old = this.read(record.skill_id); if (old && old.root !== record.root) return { skill_id: record.skill_id, status: "rejected", reason: "root_conflict" }; const status = !old ? "created" : old.content_hash === record.content_hash ? "unchanged" : "updated"; - if (status === "unchanged" && origin && !dryRun && pinOf(old.origin) !== pinOf(origin)) { + if (status === "unchanged" && origin && !dryRun && (pinOf(old.origin) !== pinOf(origin) || !old.origin?.dispatch_principal)) { // Identical content at a new pin moves only the pin. this.storage.transactionSync(() => { const row = this.rows("SELECT record FROM skills WHERE skill_id = ?", record.skill_id)[0]; @@ -214,7 +214,9 @@ export class SkillsStore { return { skill_id: skill.skill_id, status: "rejected", reason: "skills_limit" }; } const result = await this.syncSkill(skill, payload.dry_run === true, performance.now() + 45000, - { ...origin, imported_at: new Date(this.clock()).toISOString() }); + { ...origin, dispatch_principal: existing?.origin?.dispatch_principal ?? (dispatchContext()?.authority?.authenticated === true + ? { authenticated: true, keyId: dispatchContext().authority.keyId } : null), + imported_at: new Date(this.clock()).toISOString() }); if (!payload.dry_run && result.status !== "rejected") this.ledger.forget(skill.skill_id); return result; } diff --git a/worker/knowledge/skills.mjs b/worker/knowledge/skills.mjs index 8ed63eff..dfbba696 100644 --- a/worker/knowledge/skills.mjs +++ b/worker/knowledge/skills.mjs @@ -1,3 +1,4 @@ +import { dispatchContext } from "../provider-dispatch.mjs"; import { fail } from "./contracts.mjs"; import { checkImports, importSkill, reportSkills } from "./skills-import.mjs"; import { discoverSkills, previewSkill } from "./skills-market.mjs"; @@ -8,7 +9,7 @@ function skillsStore(env, options) { async call(operation, payload, principal) { const stub = env.KNOWLEDGE_SKILLS.get(env.KNOWLEDGE_SKILLS.idFromName("personal")); const response = await stub.fetch("http://skills.internal/dispatch", { method: "POST", - headers: { "content-type": "application/json" }, body: JSON.stringify({ operation, payload, principal }), signal: AbortSignal.timeout(50000) }); + headers: { "content-type": "application/json", "X-MultiLLM-Key-Controls": "2", "X-MultiLLM-Dispatch-Principal": JSON.stringify({ version: 1, username: dispatchContext()?.authority?.keyId }) }, body: JSON.stringify({ operation, payload, principal }), signal: AbortSignal.timeout(50000) }); const body = await response.json(); if (!response.ok || body.version !== 1) fail(body.error?.code ?? "skills_unavailable", body.error?.message ?? "Skills storage is unavailable.", response.ok ? 503 : response.status); return body.result; diff --git a/worker/peer-capability.mjs b/worker/peer-capability.mjs new file mode 100644 index 00000000..f9c8275c --- /dev/null +++ b/worker/peer-capability.mjs @@ -0,0 +1,18 @@ +/** Probe the actual peer before transmitting controlled content. */ +import { controlled } from "./key-controls-capability.mjs"; +import { DispatchError } from "./provider-dispatch.mjs"; +export const capabilityHeader = "X-MultiLLM-Key-Controls"; +export async function requirePeer(peer, url, minimum = 2) { + try { + const response = await peer.fetch(url, { method: "GET", redirect: "manual", signal: AbortSignal.timeout(5000) }); + const capability = (await response.json()).key_controls; + if (!response.ok || !Number.isInteger(capability?.protocol) || capability.protocol < minimum + || minimum >= 2 && capability.enforcement !== true) throw Error(); + } catch { throw new DispatchError("key_controls_peer_unavailable"); } +} +export async function requireControlledPeer(policy, peer, url) { + if (controlled(policy)) await requirePeer(peer, url); +} +export function requireControlledSender(policy, request) { + if (controlled(policy) && request.headers.get(capabilityHeader) !== "2") throw new DispatchError("key_controls_peer_unavailable"); +} diff --git a/worker/peer-controls.mjs b/worker/peer-controls.mjs index 5fc1894b..cef5d7cd 100644 --- a/worker/peer-controls.mjs +++ b/worker/peer-controls.mjs @@ -1,4 +1,5 @@ /** Admission before content crosses an independently deployed runtime boundary. */ +import { requireControlledPeer } from "./peer-capability.mjs"; import { resolvePrincipal } from "./knowledge-edge.mjs"; import { currentDataControls, legacyDataControls } from "./key-data-guards.mjs"; import { DispatchError } from "./provider-dispatch.mjs"; @@ -17,11 +18,14 @@ export async function delegationPolicy(request, env) { ? currentDataControls(env, { keyId: resolved.principal.id }) : legacyDataControls(env); } -export async function admitContainerDelegation(request, env, _container) { +export async function admitContainerDelegation(request, env, container) { const headers = request.headers; if (!["authorization", "x-multillm-api-key", "x-api-key", "x-goog-api-key"].some(name => headers.has(name)) && !(new URL(request.url).pathname.startsWith("/linkapi/") && new URL(request.url).searchParams.has("key"))) return; - try { await delegationPolicy(request, env); } + try { + const policy = await delegationPolicy(request, env); + await requireControlledPeer(policy, container, "https://container.internal/healthz"); + } catch { throw new DispatchError("key_data_controls_unavailable"); } } diff --git a/worker/provider-dispatch.mjs b/worker/provider-dispatch.mjs index dfc9eecf..31b10264 100644 --- a/worker/provider-dispatch.mjs +++ b/worker/provider-dispatch.mjs @@ -1,5 +1,5 @@ import { legacyBindingControls } from "./legacy-binding-policy.mjs"; -import { KEY_CONTROLS } from "./key-controls-capability.mjs"; +import { KEY_CONTROLS, controlled } from "./key-controls-capability.mjs"; /** Sole provider network boundary. Deferred work carries identity, never cached policy. */ import { AsyncLocalStorage } from "node:async_hooks"; import { currentDataControls, legacyDataControls, mergePreferences, parsePreferences, providerClassPermitted } from "./key-data-guards.mjs"; @@ -23,14 +23,14 @@ export function bindingPrincipal(request, legacyId) { const value = JSON.parse(request.headers.get("X-MultiLLM-Dispatch-Principal")); if (value.version !== 1 || typeof value.username !== "string" || !value.username || Object.keys(value).sort().join() !== "username,version") throw Error(); - return { authenticated: true, keyId: value.username }; + return { authenticated: true, keyId: value.username, senderCapability: request.headers.get("X-MultiLLM-Key-Controls") }; } catch { throw new DispatchError("key_data_controls_unavailable"); } } async function admission(provider, options) { const state = options.env ? options : dispatchContext(); try { - if (!KEY_CONTROLS.enforcement && state?.env && (!state.authority || state.authority.legacyBinding)) { + if (state?.env && (!state.authority || state.authority.legacyBinding)) { return { ...state, policy: await (state.authority?.legacyBinding ? legacyBindingControls(state.env) : legacyDataControls(state.env)) }; } if (!state?.authority || state.authority.authenticated !== true) throw Error(); @@ -71,7 +71,7 @@ function apiOrigin(provider, url, env, options) { } function credentials(provider, request, env, policy, options) { - if (!policy.official_only && policy.forced_provider_preferences === null || provider === "cloudflare") return request; + if (!controlled(policy) || provider === "cloudflare") return request; const url = new URL(request.url); if ([...url.searchParams.keys()].some(name => name.toLowerCase() === "key")) { throw new DispatchError("provider_credential_not_allowed", 403); @@ -108,7 +108,7 @@ export async function providerFetch(provider, input, init = {}, options = {}) { const headers = new Headers(request.headers); headers.delete("content-length"); request = new Request(request, { headers, body: JSON.stringify(payload) }); } - const headers = new Headers(request.headers); headers.delete("x-multillm-dispatch-principal"); + const headers = new Headers(request.headers); headers.delete("x-multillm-dispatch-principal"); headers.delete("x-multillm-key-controls"); request = new Request(request, { headers, redirect: "manual" }); return admittedNetwork(() => (options.send ?? fetch)(request)); } diff --git a/worker/realtime.mjs b/worker/realtime.mjs index 48415ef0..e6da2e79 100644 --- a/worker/realtime.mjs +++ b/worker/realtime.mjs @@ -253,7 +253,7 @@ async function openSession(request, env, ctx, settings, options) { const url = new URL(settings.providers.get(model)); url.protocol = "https:"; handedOff = true; const response = await providerFetch(provider, url.href, { method: "GET", headers: upstreamHeaders(request, key), - redirect: "manual", signal: controller.signal }, { env, authority: { authenticated: true, keyId: principal.owner }, realtime: true, + signal: controller.signal }, { env, authority: { authenticated: true, keyId: principal.owner }, realtime: true, send: checked => (options.dial ?? fetch)(checked.url, { method: checked.method, headers: checked.headers, redirect: checked.redirect, signal: checked.signal }) }); socket = response.webSocket; diff --git a/worker/request-telemetry.mjs b/worker/request-telemetry.mjs index 11a2f1ac..7d136de6 100644 --- a/worker/request-telemetry.mjs +++ b/worker/request-telemetry.mjs @@ -11,7 +11,7 @@ const millis = value => Math.min(86_400_000, Math.max(0, Math.round(value))); export function createUsageObserver(sse = false, onToken = () => {}) { let carry = new Uint8Array(), data = [], dataBytes = 0, oversized = false, eventOversized = false; - let usage = { input_tokens: null, output_tokens: null }, completed = false, failed = false; + let usage = { input_tokens: null, output_tokens: null, cached_input_tokens: null, reasoning_tokens: null }, completed = false, failed = false; const consume = text => { if (text.trim() === "[DONE]") { completed = true; return; } let value; @@ -19,7 +19,14 @@ export function createUsageObserver(sse = false, onToken = () => {}) { if (!value || typeof value !== "object") return; const observed = value.usage ?? value.response?.usage ?? value.message?.usage; if (observed && typeof observed === "object") { - const input = component(observed.prompt_tokens ?? observed.input_tokens); + let input = component(observed.prompt_tokens ?? observed.input_tokens); + const cached = component(observed.prompt_tokens_details?.cached_tokens ?? observed.input_tokens_details?.cached_tokens ?? observed.cache_read_input_tokens); + const reasoning = component(observed.completion_tokens_details?.reasoning_tokens ?? observed.output_tokens_details?.reasoning_tokens); + if (cached !== null) usage.cached_input_tokens = cached; + if (reasoning !== null) usage.reasoning_tokens = reasoning; + if (input !== null && (Object.hasOwn(observed, "cache_read_input_tokens") || Object.hasOwn(observed, "cache_creation_input_tokens"))) { + input += (cached ?? 0) + (component(observed.cache_creation_input_tokens) ?? 0); + } const output = component(observed.completion_tokens ?? observed.output_tokens); if (input !== null) usage.input_tokens = input; if (output !== null) usage.output_tokens = output; diff --git a/worker/roleplay/transport.mjs b/worker/roleplay/transport.mjs index 93cb08c0..5d0b957e 100644 --- a/worker/roleplay/transport.mjs +++ b/worker/roleplay/transport.mjs @@ -587,11 +587,7 @@ async function fetchCandidate(candidate, payload, env, settings, signal, key) { env, authority, unsupportedPreferences: true, send: async permitted => useOpenCodeContainer ? containerNamespace.getByName("primary").fetch(permitted) : fetch(permitted.url, { ...requestInit, headers: permitted.headers, body: await permitted.text() }), }).catch(error => { if (!(error instanceof DispatchError)) throw error; return dispatchFailure(error); })); - const response = useOpenCodeContainer && !resolvePIIPolicy(env, piiOptions) - ? await providerFetch(candidate.provider, new Request("https://roleplay.internal/opencode/v1/chat/completions", requestInit), {}, { - env, authority, unsupportedPreferences: true, send: checked => containerNamespace.getByName("primary").fetch(checked), - }).catch(error => { if (!(error instanceof DispatchError)) throw error; return dispatchFailure(error); }) - : await firewallFetch(new Request(useOpenCodeContainer ? "https://roleplay.internal/opencode/v1/chat/completions" : candidate.endpoint, requestInit), env, { + const response = await firewallFetch(new Request(useOpenCodeContainer ? "https://roleplay.internal/opencode/v1/chat/completions" : candidate.endpoint, requestInit), env, { route: "/v1/roleplay/chat/completions", provider: candidate.provider, onDecision: settings.onSecretScan, principal: { id: authority.keyId }, }, submit); diff --git a/worker/usage-buckets-d1.mjs b/worker/usage-buckets-d1.mjs index ea81b620..bf2294d9 100644 --- a/worker/usage-buckets-d1.mjs +++ b/worker/usage-buckets-d1.mjs @@ -5,7 +5,7 @@ export const BUCKET_FIELDS = Object.freeze(["ordinary_input_tokens", "cache_read "ordinary_input_cost_microusd", "cache_read_input_cost_microusd", "cache_write_input_cost_microusd", "output_cost_microusd", "bucket_basis", "bucket_source"]); const BASE_FIELDS = Object.freeze(["at", "principal", "key_prefix", "kind", "endpoint", "requested_model", "selected_model", - "status", "latency_ms", "input_tokens", "output_tokens", "cost_usd", "cost_basis", "request_id"]); + "status", "latency_ms", "input_tokens", "output_tokens", "cost_usd", "cost_basis", "request_id", "cached_input_tokens", "reasoning_tokens"]); const KINDS = new Set(["chat", "responses", "images", "videos", "embeddings", "audio", "proxy"]); const MODEL = /^[A-Za-z0-9][A-Za-z0-9._:/+@-]{0,255}$/; const ENDPOINT = /^\/[A-Za-z0-9._~:/@+-]{0,255}$/; @@ -19,12 +19,14 @@ const count = (value, maximum = Number.MAX_SAFE_INTEGER) => Number.isSafeInteger const timestamp = value => typeof value === "string" && AT.test(value) && Number.isFinite(Date.parse(value)); export function validBaseUsageRow(row) { + if (row) row = { cached_input_tokens: null, reasoning_tokens: null, ...row }; return row !== null && typeof row === "object" && !Array.isArray(row) && fields(row, BASE_FIELDS) && timestamp(row.at) && text(row.principal, 256) && optional(row.key_prefix, value => text(value, 64)) && KINDS.has(row.kind) && typeof row.endpoint === "string" && ENDPOINT.test(row.endpoint) && optional(row.requested_model, value => typeof value === "string" && MODEL.test(value)) && optional(row.selected_model, value => typeof value === "string" && MODEL.test(value)) && Number.isSafeInteger(row.status) && row.status >= 100 && row.status <= 599 + && optional(row.cached_input_tokens, count) && optional(row.reasoning_tokens, count) && count(row.latency_ms, 86_400_000) && optional(row.input_tokens, count) && optional(row.output_tokens, count) && optional(row.cost_usd, value => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1_000_000) && (row.cost_basis === null || row.cost_basis === "usage" || row.cost_basis === "estimate") @@ -51,7 +53,7 @@ export function usageBucketsEnabled(env) { export function serializeUsageBuckets(row, env) { if (!row || typeof row !== "object" || Array.isArray(row)) throw new TypeError("invalid usage row"); - const base = Object.fromEntries(BASE_FIELDS.map(name => [name, row[name]])); + const base = Object.fromEntries(BASE_FIELDS.map(name => [name, row[name] ?? null])); if (!validBaseUsageRow(base)) throw new TypeError("invalid usage row"); const allowed = new Set([...BASE_FIELDS, ...BUCKET_FIELDS]); if (Object.keys(row).some(name => !allowed.has(name))) throw new TypeError("invalid usage fields"); @@ -91,7 +93,7 @@ export async function recordUsageWithBuckets(env, body, { recordBase, recordExte } catch { return Response.json({ version: 1, error: { code: "invalid_request", message: "Usage ledger operation failed" } }, { status: 400 }); } - const legacy = { ...body, rows: rows.map(row => Object.fromEntries(BASE_FIELDS.map(name => [name, row[name]]))) }; + const legacy = { ...body, rows: rows.map(row => Object.fromEntries(BASE_FIELDS.map(name => [name, row[name] ?? null]))) }; const headers = { "cache-control": "no-store" }; if (!enabled) return Response.json(await recordBase(legacy), { headers }); try { diff --git a/worker/usage-ledger-d1.mjs b/worker/usage-ledger-d1.mjs index bb3692d6..8e011470 100644 --- a/worker/usage-ledger-d1.mjs +++ b/worker/usage-ledger-d1.mjs @@ -16,7 +16,7 @@ export const LATENCY_BUCKETS = Object.freeze([250, 500, 1000, 2000, 4000, 8000, const BUCKET_COLUMNS = [...LATENCY_BUCKETS.map(bound => `lat_le_${bound}`), "lat_gt_120000"]; const GROUPS = Object.freeze({ day: "day", model: "model", principal: "principal" }); const EVENT_COLUMNS = ["id", "at", "principal", "key_prefix", "kind", "endpoint", "requested_model", "selected_model", - "status", "latency_ms", "input_tokens", "output_tokens", "cost_usd", "cost_basis", "request_id"]; + "status", "latency_ms", "input_tokens", "output_tokens", "cost_usd", "cost_basis", "request_id", "cached_input_tokens", "reasoning_tokens"]; const ROW_FIELDS = EVENT_COLUMNS.slice(1); const MAX_BODY_BYTES = 262144; const MAX_ROWS = 500; @@ -49,14 +49,15 @@ const APPLIED = "EXISTS (SELECT 1 FROM usage_batches WHERE id = ?2 AND token = ? const INSERT_EVENTS = `INSERT INTO usage_events (at, day, ${ROW_FIELDS.slice(1).join(", ")}) SELECT ${field("at")}, substr(${field("at")}, 1, 10), ${ROW_FIELDS.slice(1).map(field).join(", ")} FROM json_each(?1) WHERE ${APPLIED}`; -const TOTALS = ["requests", "errors", "input_tokens", "output_tokens", "cost_usd", "priced_requests", "latency_ms_total", ...BUCKET_COLUMNS]; +const TOTALS = ["requests", "errors", "input_tokens", "output_tokens", "cost_usd", "priced_requests", "latency_ms_total", ...BUCKET_COLUMNS, "cached_input_tokens", "reasoning_tokens"]; const UPSERT_DAILY = `INSERT INTO usage_daily (day, principal, model, ${TOTALS.join(", ")}) SELECT substr(${field("at")}, 1, 10) AS usage_day, ${field("principal")} AS usage_principal, COALESCE(${field("selected_model")}, ${field("requested_model")}, 'unknown') AS usage_model, COUNT(*), SUM(${field("status")} >= 400), COALESCE(SUM(${field("input_tokens")}), 0), COALESCE(SUM(${field("output_tokens")}), 0), COALESCE(SUM(${field("cost_usd")}), 0), SUM(${field("cost_usd")} IS NOT NULL), SUM(${field("latency_ms")}), - ${BUCKET_COLUMNS.map((_, index) => `SUM(${field("bucket")} = ${index})`).join(", ")} + ${BUCKET_COLUMNS.map((_, index) => `SUM(${field("bucket")} = ${index})`).join(", ")}, + COALESCE(SUM(${field("cached_input_tokens")}), 0), COALESCE(SUM(${field("reasoning_tokens")}), 0) FROM json_each(?1) WHERE ${APPLIED} GROUP BY usage_day, usage_principal, usage_model ON CONFLICT(day, principal, model) DO UPDATE SET ${TOTALS.map(name => `${name} = ${name} + excluded.${name}`).join(", ")}`; @@ -64,7 +65,7 @@ const SUMS = TOTALS.map(name => `SUM(${name}) AS ${name}`).join(", "); function summaryRow(group, row) { const result = { [group]: row[group] }; - for (const name of TOTALS.slice(0, 7)) result[name] = row[name] ?? 0; + for (const name of [...TOTALS.slice(0, 7), "cached_input_tokens", "reasoning_tokens"]) result[name] = row[name] ?? 0; result.latency_buckets = BUCKET_COLUMNS.map(name => row[name] ?? 0); return result; } @@ -203,7 +204,7 @@ export async function recordNativeUsage(env, event, ctx) { const row = { at: new Date().toISOString(), principal: organisationsEnabled(env) ? tenantStorageKey(event.principal, event.tenantContext) : event.principal, key_prefix: null, kind, endpoint: event.endpoint, requested_model: model, selected_model: model, status: event.status, latency_ms: event.duration_ms, input_tokens: event.input_tokens, - output_tokens: event.output_tokens, cost_usd: event.cost_usd, cost_basis: event.cost_basis, + output_tokens: event.output_tokens, cached_input_tokens: event.cached_input_tokens ?? null, reasoning_tokens: event.reasoning_tokens ?? null, cost_usd: event.cost_usd, cost_basis: event.cost_basis, request_id: event.requestId }; if (!validRow(row)) throw new Error("Invalid native usage metadata"); submitNativeEvent(env, event, ctx);