From c26f6a3562bab9b8bbc931a8552cfce96c9b3e7d Mon Sep 17 00:00:00 2001 From: Abhyudaya Baiju Tharakan Date: Tue, 20 Oct 2026 20:02:22 +0530 Subject: [PATCH 1/7] feat: enforce key controls after protocol expansion --- .github/workflows/ci.yml | 8 +- .github/workflows/deploy.yml | 12 +- README.md | 2 + config.py | 1 + docs/deployment-cloudflare.md | 40 +- docs/key-controls-rollout.md | 19 +- docs/key-data-guards.md | 188 ++ package.json | 6 +- providers/openai_compatible.py | 5 + routes/auto_routes.py | 2 + routes/cascades.py | 21 +- routes/intelligence.py | 7 + routes/proxy.py | 2 + routes/unified.py | 6 +- routes/usage.py | 6 +- scripts/deploy.sh | 3 + scripts/key_data_preflight.mjs | 101 + services/auth_service.py | 12 +- services/control_plane_backup.py | 2 +- services/key_controls.py | 22 +- services/key_controls_capability.py | 2 +- services/key_data_guards.py | 2 - services/peer_capability.py | 8 + services/prompt_cache_cost.py | 7 +- services/provider_assets.py | 3 - services/provider_dispatch.py | 30 +- services/request_accounting.py | 8 +- services/shadow_eval_data_policy.py | 6 +- services/usage_store.py | 29 +- services/usage_types.py | 22 +- services/user_store.py | 27 +- static/js/key-controls.js | 4 + static/js/usage.js | 6 +- templates/usage.html | 6 +- templates/users.html | 9 +- tests/fixtures/secret_firewall_coverage.json | 5 +- tests/helpers/runtime_versions.mjs | 4 +- tests/outbound_inventory.json | 2457 ++++++++++++++++++ tests/support/account_envelope_driver.py | 5 +- tests/support/config_revision_fixtures.mjs | 2 +- tests/support/expand_snapshot_driver.py | 46 + tests/test_account_contract_worker.mjs | 21 + tests/test_account_envelope_integration.mjs | 90 + tests/test_cloudflare_worker.mjs | 6 +- tests/test_config_revision_sync.py | 2 + tests/test_dashboard_scripts.mjs | 2 +- tests/test_dispatch_network.py | 62 + tests/test_dispatch_network_worker.mjs | 109 + tests/test_edge_request_metrics_worker.mjs | 4 +- tests/test_gemini_data_guards.py | 122 + tests/test_key_controls.py | 6 +- tests/test_key_controls_contract_worker.mjs | 138 + tests/test_key_controls_expand.py | 96 +- tests/test_key_controls_expand_worker.mjs | 45 +- tests/test_key_data_guards.py | 152 ++ tests/test_key_data_guards_worker.mjs | 85 + tests/test_key_data_intelligence.py | 66 + tests/test_key_data_preflight.mjs | 70 + tests/test_knowledge_authority.mjs | 21 + tests/test_knowledge_service.mjs | 3 +- tests/test_knowledge_skills.mjs | 2 +- tests/test_knowledge_skills_import.mjs | 18 + tests/test_outbound_inventory.py | 48 + tests/test_peer_capability.py | 37 + tests/test_prompt_cache_cost_worker.mjs | 3 +- tests/test_provider_assets.py | 128 + tests/test_provider_dispatch.py | 170 ++ tests/test_provider_dispatch_worker.mjs | 151 ++ tests/test_realtime_worker.mjs | 16 +- tests/test_shadow_eval_data_guards.py | 89 + tests/test_stream_cost_breaker.py | 9 +- tests/test_stream_cost_breaker_worker.mjs | 2 +- tests/test_usage_ledger.py | 2 +- tests/test_usage_ledger_worker.mjs | 4 +- tests/test_usage_receipts_worker.mjs | 2 +- tests/test_usage_token_details.py | 32 + tests/test_usage_token_details_worker.mjs | 39 + tests/test_user_store_d1.py | 4 +- tests/test_vertex_dispatch.py | 124 + tests/test_wave3_transport_worker.mjs | 13 +- worker/account-contract.mjs | 31 + worker/control-users-d1.mjs | 13 +- worker/intelligence-auth-d1.mjs | 11 +- worker/key-controls-capability.mjs | 2 +- worker/key-data-guards.mjs | 4 +- worker/knowledge-edge.mjs | 7 +- worker/knowledge-outbound.mjs | 11 +- worker/knowledge/index.mjs | 5 +- worker/knowledge/providers/transport.mjs | 2 +- worker/knowledge/service.mjs | 10 +- worker/knowledge/skills-import.mjs | 2 +- worker/knowledge/skills-store.mjs | 8 +- worker/knowledge/skills.mjs | 2 +- worker/legacy-binding-policy.mjs | 18 + worker/peer-capability.mjs | 18 + worker/peer-controls.mjs | 8 +- worker/provider-dispatch.mjs | 9 +- worker/realtime.mjs | 2 +- worker/request-telemetry.mjs | 11 +- worker/roleplay/transport.mjs | 6 +- worker/usage-buckets-d1.mjs | 8 +- worker/usage-ledger-d1.mjs | 11 +- wrangler.jsonc | 2 +- 103 files changed, 5063 insertions(+), 284 deletions(-) create mode 100644 docs/key-data-guards.md create mode 100644 scripts/key_data_preflight.mjs create mode 100644 services/peer_capability.py create mode 100644 tests/outbound_inventory.json create mode 100644 tests/support/expand_snapshot_driver.py create mode 100644 tests/test_account_contract_worker.mjs create mode 100644 tests/test_account_envelope_integration.mjs create mode 100644 tests/test_dispatch_network.py create mode 100644 tests/test_dispatch_network_worker.mjs create mode 100644 tests/test_gemini_data_guards.py create mode 100644 tests/test_key_controls_contract_worker.mjs create mode 100644 tests/test_key_data_guards.py create mode 100644 tests/test_key_data_guards_worker.mjs create mode 100644 tests/test_key_data_intelligence.py create mode 100644 tests/test_key_data_preflight.mjs create mode 100644 tests/test_outbound_inventory.py create mode 100644 tests/test_peer_capability.py create mode 100644 tests/test_provider_assets.py create mode 100644 tests/test_provider_dispatch.py create mode 100644 tests/test_provider_dispatch_worker.mjs create mode 100644 tests/test_shadow_eval_data_guards.py create mode 100644 tests/test_usage_token_details.py create mode 100644 tests/test_usage_token_details_worker.mjs create mode 100644 tests/test_vertex_dispatch.py create mode 100644 worker/account-contract.mjs create mode 100644 worker/legacy-binding-policy.mjs create mode 100644 worker/peer-capability.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b2d170d..270ea6c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + fetch-depth: 0 - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.11" @@ -50,6 +51,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + fetch-depth: 0 - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.11" @@ -84,6 +86,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + fetch-depth: 0 - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.11" @@ -106,6 +109,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + fetch-depth: 0 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: "24" @@ -119,8 +123,10 @@ jobs: run: git diff --exit-code -- static/css/style.css - name: Frontend build contracts run: node --test tests/test_frontend_build.mjs + - name: Check release configuration + run: node scripts/key_data_preflight.mjs --check # Bundle-only checks cannot attest remote deployment prerequisites. The - # enforcement revision adds release admission 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 2961c79b..627e5cf1 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" @@ -121,6 +122,8 @@ jobs: run: npm install --global "$(node -p "require('./package.json').packageManager")" - name: Install deployment dependencies run: pnpm install --frozen-lockfile + - name: Check release configuration + run: node scripts/key_data_preflight.mjs --check - name: Check migration files run: node scripts/verify_d1_migrations.mjs --check - name: List pending D1 migrations @@ -136,6 +139,11 @@ jobs: run: | pnpm exec wrangler d1 migrations apply "$D1_DATABASE" --remote node scripts/verify_d1_migrations.mjs "$D1_DATABASE" + - name: Check migrated schema and required bindings + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: node scripts/key_data_preflight.mjs --prepare - name: Deploy Knowledge Worker id: knowledge env: @@ -148,7 +156,9 @@ jobs: env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - run: pnpm exec wrangler deploy --tag "$SHA" --message "$DEPLOY_MESSAGE" + run: | + pnpm exec wrangler deploy --containers-rollout=none --tag "$SHA" --message "$DEPLOY_MESSAGE" + pnpm exec wrangler deploy --tag "$SHA" --message "$DEPLOY_MESSAGE" - name: Verify deployment id: verify 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 4ab4bfae..7c4c42ca 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 @@ -383,14 +388,20 @@ the code, and nothing ships unless ci passed. 3. Add *Containers Edit* to the API token, and run the `deploy` workflow once from the **Actions** tab to deploy the current head of `main`. -**(b) Workers Builds keeps deploying the main Worker.** Leave `DEPLOY_MAIN_WORKER` unset -(the default) or `false`, and keep the Workers Builds deploy command as -`npx wrangler deploy`; do not change it to `npm run deploy`, which would apply the -migrations and deploy the Knowledge Worker a second time. Actions applies migrations and -deploys the Knowledge Worker after ci passes. Workers Builds starts at push time, so after -a push that adds a migration the new main Worker usually runs for a few minutes before -its tables exist, and `/ready` answers 503 `d1_schema_missing` until they do. Code must -tolerate a missing new table, and a push whose ci fails still ships the main Worker. +**(b) Workers Builds remains connected.** It deploys the main Worker and its +Container independently of the GitHub workflow, which also deploys the private +Knowledge Worker. Keep the default Wrangler build command: its preflight refuses +main upload until migrations and an expansion-capable private Worker are +ready. A push before CI completes should fail that build. It does not become a +successful deployment automatically after the workflow finishes: rerun the release +only after prerequisites pass, and avoid competing deploy owners. Do not remove the build +preflight for a release. Prefer option (a) to preserve the full +migrations → private Worker → main Worker → Container order in one workflow. + +The account/control RPC is handled by the main Worker against its own D1 binding; +it does not pass through the private Knowledge service. See +[key data controls](key-data-guards.md#account-contract-and-release-preflight) for +the negotiated account contract and local integration evidence. ## One-time setup @@ -442,7 +453,8 @@ which can show no commit until the next deploy. `npm run deploy` (`scripts/deploy.sh`) runs the same order from your machine: the migration checks, `wrangler d1 migrations apply`, the remote check, the Knowledge -Worker and the main Worker. From a clean checkout it tags both versions with the commit. +Worker, the main 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, for example `npm run deploy -- --containers-rollout immediate` for a one-shot rollout of a changed Container image. Use it only when the workflow cannot run, and never while a deploy run diff --git a/docs/key-controls-rollout.md b/docs/key-controls-rollout.md index 30371c0d..33bc2983 100644 --- a/docs/key-controls-rollout.md +++ b/docs/key-controls-rollout.md @@ -20,7 +20,10 @@ If a later revision enables an account restriction, rolling back to this revisio refuses provider dispatch with 503 before sending content. This dormant fence includes native routes, private bindings, Knowledge, Realtime and deferred work. A malformed or unreadable current policy cannot grant dispatch. Legacy background jobs without an -identity run only while a fresh durable read proves every policy is NULL. +identity run only while a fresh durable read proves every policy is NULL. Headerless +private calls from a verified expansion Container remain compatible beside controlled +accounts: that Container applies its current-account refusal fence before the hop. +An unreadable or malformed store cannot use this compatibility proof. ## Contract @@ -33,3 +36,17 @@ peers during independent deployments. Keep deployment preflight enabled. 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 +loading controlled rows. 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. diff --git a/docs/key-data-guards.md b/docs/key-data-guards.md new file mode 100644 index 00000000..59d8d9d8 --- /dev/null +++ b/docs/key-data-guards.md @@ -0,0 +1,188 @@ +# 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`. + +An earlier 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; this does not establish the exact version +combination or error cause in a historical deployment without its logs or +Container fingerprint. + +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. + +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 100a15b6..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", + "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..e453dec5 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 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(bool(user.get("official_only"))) 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..0228f7f6 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 model_permitted + user = getattr(g, "authenticated_user", None) or {} + if user.get("official_only"): + 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 3ecb8ad5..1f368b9a 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -18,8 +18,11 @@ if commit="$(git rev-parse HEAD 2>/dev/null)"; then fi fi +node scripts/key_data_preflight.mjs --check node scripts/verify_d1_migrations.mjs --check npx --no-install wrangler d1 migrations apply "$DATABASE" --remote node scripts/verify_d1_migrations.mjs "$DATABASE" +node scripts/key_data_preflight.mjs --prepare npx --no-install wrangler deploy --config wrangler.knowledge.jsonc ${annotations[@]+"${annotations[@]}"} +npx --no-install wrangler deploy ${annotations[@]+"${annotations[@]}"} --containers-rollout=none npx --no-install wrangler deploy ${annotations[@]+"${annotations[@]}"} "$@" diff --git a/scripts/key_data_preflight.mjs b/scripts/key_data_preflight.mjs new file mode 100644 index 00000000..fdebbca3 --- /dev/null +++ b/scripts/key_data_preflight.mjs @@ -0,0 +1,101 @@ +#!/usr/bin/env node +/** Release admission, before uploading a Worker or replacing its Container. */ +import { execFileSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { pathToFileURL } from "node:url"; +import { localMigrations, remoteAppliedMigrations } from "./verify_d1_migrations.mjs"; + +const ROOT = new URL("../", import.meta.url); +const REQUIRED_SECRETS = ["ADMIN_API_KEY", "FLASK_SECRET_KEY", "JWT_SECRET"]; +const REQUIRED_COLUMNS = ["official_only", "forced_provider_preferences", "max_stream_cost_microusd"]; +class PreflightError extends Error {} +const requireThat = (condition, message) => { if (!condition) throw new PreflightError(message); }; + +export function checkConfig(main, knowledge) { + const database = config => config.d1_databases?.find(item => item.binding === "INTELLIGENCE_DB"); + const first = database(main), second = database(knowledge); + requireThat(main.vars?.AUTH_STORAGE_BACKEND === "d1", "AUTH_STORAGE_BACKEND must explicitly select d1"); + requireThat(first?.database_id && first.database_name && first.migrations_dir === "intelligence-migrations", + "Main Worker needs the migrated INTELLIGENCE_DB binding"); + requireThat(second?.database_id === first.database_id && second?.database_name === first.database_name + && second?.migrations_dir === first.migrations_dir, "Both Workers must bind the same migrated D1"); + requireThat(main.services?.some(item => item.binding === "KNOWLEDGE_SERVICE" && item.service === knowledge.name), + "KNOWLEDGE_SERVICE must target the configured private Worker"); + requireThat(knowledge.workers_dev === false && knowledge.preview_urls === false, + "Knowledge Worker must remain private"); + requireThat(main.containers?.some(item => item.class_name === "MultiLLMProxyContainer" && item.image === "./Dockerfile") + && main.durable_objects?.bindings?.some(item => item.name === "MULTILLM_PROXY_CONTAINER" + && item.class_name === "MultiLLMProxyContainer"), "Main Worker needs its Container binding and image"); + return first.database_name; +} + +export function checkPrerequisites({ main, knowledge, run, migrations = localMigrations() }) { + const database = checkConfig(main, knowledge); + const applied = remoteAppliedMigrations(database, run); + requireThat(migrations.every(name => applied.includes(name)) && applied.every(name => migrations.includes(name)), + "D1 migration ledger must exactly match this release"); + const results = JSON.parse(run(["d1", "execute", database, "--remote", "--json", "--command", "PRAGMA table_info(control_users)"])); + const columns = results.flatMap(item => item.results ?? []).map(item => item.name); + requireThat(REQUIRED_COLUMNS.every(name => columns.includes(name)), "Account policy columns are missing from D1"); + const secrets = JSON.parse(run(["secret", "list", "--config", "wrangler.jsonc"])); + requireThat(REQUIRED_SECRETS.every(name => main.vars?.[name] || secrets.some(item => item.name === name)), + "Main Worker needs ADMIN_API_KEY, FLASK_SECRET_KEY and JWT_SECRET bindings"); +} + +export function sourceCapability(commit) { + if (!/^[0-9a-f]{40}$/.test(commit)) return null; + try { + const source = execFileSync("git", ["show", `${commit}:worker/key-controls-capability.mjs`], + { cwd: ROOT, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }); + const match = /protocol: ([12]), enforcement: (true|false)/.exec(source); + return match ? { protocol: Number(match[1]), enforcement: match[2] === "true" } : null; + } catch { return null; } +} + +export function checkRelease(options) { + checkPrerequisites(options); + const { commit, run } = options; + requireThat(/^[0-9a-f]{40}$/.test(commit), "Release needs a full commit identity"); + const deployment = JSON.parse(run(["deployments", "status", "--json", "--config", "wrangler.knowledge.jsonc"])); + requireThat(deployment.versions?.length === 1 && deployment.versions[0].percentage === 100, + "Private Worker must have one fully active version"); + const version = JSON.parse(run(["versions", "view", deployment.versions[0].version_id, "--json", "--config", "wrangler.knowledge.jsonc"])); + const capability = (options.sourceCapability ?? sourceCapability)(version.annotations?.["workers/tag"]); + requireThat(capability?.protocol >= 1, "Deploy and verify the expansion private Worker before the main Worker"); +} + +function main(args) { + const mainConfig = JSON.parse(readFileSync(new URL("wrangler.jsonc", ROOT))); + const knowledge = JSON.parse(readFileSync(new URL("wrangler.knowledge.jsonc", ROOT))); + try { + checkConfig(mainConfig, knowledge); + if (args[0] === "--check" || process.env.WRANGLER_COMMAND === "dev") { + console.log("Release configuration checked (no deployment admission)."); + return 0; + } + requireThat(["--prepare", "--deploy"].includes(args[0]), "Use --check, --prepare or --deploy"); + const git = args => execFileSync("git", args, { cwd: ROOT, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim(); + const commit = git(["rev-parse", "HEAD"]); + requireThat(!git(["status", "--porcelain", "--untracked-files=no", "--", ".", ":(exclude)worker/build-id.mjs"]), + "Deploy from a clean committed checkout"); + const run = args => execFileSync("npx", ["--no-install", "wrangler", ...args], { + cwd: ROOT, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 60000, + env: { ...process.env, WRANGLER_SEND_METRICS: "false" }, + }); + const options = { main: mainConfig, knowledge, commit, run }; + if (args[0] === "--prepare") { + checkPrerequisites(options); + console.log("Release prerequisites checked before private Worker deployment."); + return 0; + } + checkRelease(options); + console.log("Release admitted: migrations, account schema, required bindings and private Worker expansion verified."); + return 0; + } catch (error) { + // CLI output may contain environment details; print only our own diagnostics. + const message = error instanceof PreflightError ? error.message : "Could not verify release prerequisites"; + console.error(`Deployment preflight refused: ${message}. No main upload is admitted.`); + return 1; + } +} +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) process.exitCode = main(process.argv.slice(2)); diff --git a/services/auth_service.py b/services/auth_service.py index 98ad35c3..d1ddb143 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 return { - "data_policy": dict(row).get("data_policy") or envelope({"official_only": None, "forced_provider_preferences": None, **dict(row)}), + "data_policy": 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 6726ccb3..3361d155 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 expansion_fence(policy): diff --git a/services/key_data_guards.py b/services/key_data_guards.py index ad3ad5e0..92aaa2a7 100644 --- a/services/key_data_guards.py +++ b/services/key_data_guards.py @@ -75,8 +75,6 @@ def has_data_controls(user): def model_permitted(user, model, config=None): - from services.key_controls_capability import expansion_fence - expansion_fence(user.get("data_policy") or user) if not user.get("official_only"): 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..afe9e9ed 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,22 @@ 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) return {**user, **policy} - except APIError: - raise except Exception: raise unavailable() from None @@ -123,8 +119,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 +133,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 +144,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 e5a7ba81..002b88f6 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"}) @@ -61,7 +61,14 @@ def _call(operation, **values): attempts = 2 if operation in READ_OPERATIONS else 1 for attempt in range(1, attempts + 1): try: - return request_private_intelligence({"operation": operation, **values}, endpoint="users") + response = request_private_intelligence({"operation": operation, "data_policy_version": 1, **values}, endpoint="users") + rows = response.get("users", [response.get("user")]) if isinstance(response, dict) else [] + from services.key_data_guards import has_data_controls + if operation in READ_OPERATIONS and any(isinstance(row, dict) and has_data_controls(row) for row in rows): + capability = request_private_intelligence({"operation": "capabilities"}, endpoint="users").get("key_controls") + from services.peer_capability import require_enforcement + require_enforcement(capability) + return response except Exception as error: cause = (f"{error.status} {error.code}" if isinstance(error, PrivateIntelligenceError) else getattr(error, "code", None) or type(error).__name__) @@ -77,16 +84,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) @@ -95,11 +99,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/fixtures/secret_firewall_coverage.json b/tests/fixtures/secret_firewall_coverage.json index 8c2586b6..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", @@ -375,6 +376,7 @@ "worker/knowledge/skills.mjs", "worker/latency-slo.mjs", "worker/learned-cooldown-d1.mjs", + "worker/legacy-binding-policy.mjs", "worker/log.mjs", "worker/managed-state-dispatch.mjs", "worker/media-files.mjs", @@ -389,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..aefdf54b --- /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": "ee56a4b8ee4fae80c99e1e80f869bca8cb1b4d55ed4d00c7df7c3e87ebe11f42" + }, + "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": "13b441e4f87b0656bfce994cf7c9d17ed22ed321bea0c604fc07eaa5a0f547fd" + }, + "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": "37b851466b62cb272e7597b1772f3c5c5ec121a07d592a263dd38f2cd8b977be" + }, + "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": "6bb99b0b757b5b25e90697415d1e41e7129dc7a7ca227915935afad56c7556d2" + }, + "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": "4b05ae698b86c28a9f1c07f1e4c447b00ae75c7c8ad7e6273984a1929d110150" + }, + "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": "1d7a76d1da81202063b624e8d6058c0191b6c7f7155b0d7db26f8e016eecaedd" + }, + "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": "f8458ed0a5d9c02741d8b02fe16ab3c43d7d17bcc126f0e3abfced046f84f038" + }, + "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": "355a6183654ad51ed9bbe2b62b8901679e3680c5cb0aa5ff7f9bf08aec158b1a" + }, + "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": "5df7d8a5ed174885c1bcee9727602f6bc34be857d1eb0c05d020a34eb487670c" + }, + "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": "e435ee51c9f902577a830c54490208e4a039e04151b1862d104c579d18129ba6" + }, + "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": "a6bfb6d8b1219e027b4d6dcd27a27e046cb6447bd4982f51ed503b16c313cbe4" + }, + "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": "779faffcf4f3598ba23b02072a654577b924b96d310dded08379646ea895a988" + }, + "services/video_generation.py": { + "kind": "guarded", + "reason": "Provider adapter, protocol helper or orchestration; delegates network I/O to the guarded provider clients.", + "sha256": "f4b24fcc3509dfddca33a7be8ddb976e05f277b73fe73a4e7e8cbaa4bc2ca770" + }, + "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": "d549ea11498ddc7c7f5a0c9163540df6bd17c157cf0e9b3b5f5bcada183f6617" + }, + "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": "d5044d6217a6bc9af01e8cf5d6e269f715a578ff7c2f92d7096d5dba008b6ee2" + }, + "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": "803a9a5fe34a66fa27cc4bbc843a4c5d4466f7a732e8d54045515581070db7d9" + }, + "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": "f101e5bef18548d394914d263f8e7cf4ea5827af831c990c65896a26dcb2f003" + }, + "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": "afa2dbf2607b778c5b736bec68193ac7f902cc8f34f1340a2391b35c36ea23e6" + }, + "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": "fe8dd519fa01b31151c2fb68275f4e92efd8103b98d12277790c60db082edfd5" + }, + "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": "e5825928da3dd8fbf35104d32a733006909b01adce0c551943822337a7c14d38" + }, + "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": "f79aaa27509fe6f76f9b86239c9e79ef8a472a4294a57d072c401619b9248e61" + }, + "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 4267ce67..097af3e1 100644 --- a/tests/support/account_envelope_driver.py +++ b/tests/support/account_envelope_driver.py @@ -88,12 +88,13 @@ def transport(self, method, url, **kwargs): client = app.test_client() headers = {"Authorization": "Bearer mllm_synthetic_incident_key_long_enough"} -for path, method in [("/v1/models", "get"), ("/v1/chat/completions", "post")]: +requests_to_run = json.loads(sys.argv[4]) if len(sys.argv) > 4 else [("/v1/models", "get"), ("/v1/chat/completions", "post")] +for path, method in requests_to_run: arguments = {"json": {"model": "openrouter:openai/gpt-6-luna", "messages": [ {"role": "user", "content": "synthetic"}, ]}} if method == "post" else {} response = getattr(client, method)(path, headers=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..268e4e8a --- /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 = "8e34da7" +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..0756ff4c --- /dev/null +++ b/tests/test_account_contract_worker.mjs @@ -0,0 +1,21 @@ +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, + }); +}); diff --git a/tests/test_account_envelope_integration.mjs b/tests/test_account_envelope_integration.mjs new file mode 100644 index 00000000..39fabe69 --- /dev/null +++ b/tests/test_account_envelope_integration.mjs @@ -0,0 +1,90 @@ +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 () => { + const expanded = await rpc({ operation: "get", username: "synthetic" }); + assert.equal(expanded.status, 200); + assert.equal(expanded.body.user.official_only, 1); + 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 169fa4b3..5c26f74c 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} }); }); test("worker relies on container.fetch for startup and readiness", async () => { diff --git a/tests/test_config_revision_sync.py b/tests/test_config_revision_sync.py index 0971a4fd..29cce541 100644 --- a/tests/test_config_revision_sync.py +++ b/tests/test_config_revision_sync.py @@ -328,6 +328,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_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..9b2216c0 --- /dev/null +++ b/tests/test_key_controls_contract_worker.mjs @@ -0,0 +1,138 @@ +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 = "8e34da7", 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"}' }, +]; +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(); + 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" }); + 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 ? "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 }); + } }) }; + const worker = mains.get(main); + 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 f0952e16..93bf46ae 100644 --- a/tests/test_key_controls_expand.py +++ b/tests/test_key_controls_expand.py @@ -1,91 +1,9 @@ -"""Expansion preserves legacy rows and fences every future controlled dispatch.""" -from unittest.mock import Mock +"""Run the expansion assertions against the pinned rollback target, never enforcement code.""" +import subprocess +import sys -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": 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"] 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": 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() +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 e3ac96e9..954fa362 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 = "8e34da7"; +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 = [ @@ -33,7 +38,7 @@ 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, ""]) { + for (const revision of [OLD, EXPAND]) { 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)}}`, revision, "neutral"); const mf = new Miniflare(convertV4MiniflareOptions({ modules: true, script, compatibilityFlags: ["nodejs_compat"], @@ -46,19 +51,19 @@ 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], { 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), [200, 200]); }); - if (!revision) { + if (revision === EXPAND) { for (const policy of states.slice(1)) { 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); @@ -99,12 +104,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(); @@ -143,8 +148,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; @@ -158,3 +162,24 @@ test("registered Container forwarding refuses controlled accounts before sending assert.equal(sent, policy === states[0] ? 1 : 0, path); } }); + +test("a fenced expansion Container preserves a NULL private call beside other controlled accounts", async () => { + for (const policy of states.slice(1)) { + const database = policyStore(policy); + for (const capability of [{ protocol: 1, enforcement: false }, { protocol: 0 }, null]) { + let probes = 0; + const env = { INTELLIGENCE_DB: database, MULTILLM_PROXY_CONTAINER: { getByName: () => ({ fetch(url, init) { + probes++; assert.equal(new URL(url).pathname, "/healthz"); assert.equal(init.method, "GET"); + return Response.json({ key_controls: capability }); + } }) } }; + if (capability?.protocol === 1) assert.deepEqual(await legacyBindingControls(env), states[0]); + else await assert.rejects(legacyBindingControls(env)); + assert.equal(probes, 1); + } + } + let probes = 0; + await assert.rejects(legacyBindingControls({ INTELLIGENCE_DB: policyStore({}), MULTILLM_PROXY_CONTAINER: { + getByName() { probes++; assert.fail("Unreadable policy must not negotiate a fallback"); }, + } })); + assert.equal(probes, 0); +}); 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_key_data_preflight.mjs b/tests/test_key_data_preflight.mjs new file mode 100644 index 00000000..5502176f --- /dev/null +++ b/tests/test_key_data_preflight.mjs @@ -0,0 +1,70 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { readFileSync } from "node:fs"; +import { bundleArguments, bundleConfig } from "../scripts/check_worker_bundle.mjs"; +import { checkConfig, checkPrerequisites, checkRelease } from "../scripts/key_data_preflight.mjs"; + +const main = JSON.parse(readFileSync(new URL("../wrangler.jsonc", import.meta.url))); +const knowledge = JSON.parse(readFileSync(new URL("../wrangler.knowledge.jsonc", import.meta.url))); +const commit = "a".repeat(40); +function release(changes = {}) { + const evidence = { + migrations: [{ name: "0043_integration_data_policy.sql" }], + columns: ["official_only", "forced_provider_preferences", "max_stream_cost_microusd"].map(name => ({ name })), + secrets: ["ADMIN_API_KEY", "FLASK_SECRET_KEY", "JWT_SECRET"].map(name => ({ name, type: "secret_text" })), + versions: [{ version_id: "private-version", percentage: 100 }], tag: commit, capability: { protocol: 1, enforcement: false }, ...changes, + }; + return { main, knowledge, commit, sourceCapability: tag => /^[a-f0-9]{40}$/.test(tag || "") ? evidence.capability : null, migrations: ["0043_integration_data_policy.sql"], run: args => { + if (args[0] === "secret") return JSON.stringify(evidence.secrets); + if (args[0] === "deployments") return JSON.stringify({ versions: evidence.versions }); + if (args[0] === "versions") return JSON.stringify({ annotations: { "workers/tag": evidence.tag } }); + if (args[0] === "d1") return JSON.stringify([{ success: true, + results: args.at(-1).includes("PRAGMA") ? evidence.columns : evidence.migrations }]); + throw new Error("Unexpected release probe"); + } }; +} +test("committed release bindings are consistent", () => assert.equal(checkConfig(main, knowledge), "multillm-intelligence")); +test("absent or mismatched configuration refuses deploy admission", () => { + for (const mutation of [config => { delete config.d1_databases; }, config => { delete config.services; }, + config => { config.vars.AUTH_STORAGE_BACKEND = "sql"; }, config => { config.containers = []; }]) { + const config = structuredClone(main); mutation(config); + assert.throws(() => checkConfig(config, knowledge)); + } + assert.throws(() => checkConfig(main, { ...knowledge, d1_databases: [] })); +}); +test("a complete release is admitted without reading secret values", () => assert.doesNotThrow(() => checkRelease(release()))); +test("prerequisites refuse missing main bindings before updating the private Worker", () => { + assert.throws(() => checkPrerequisites(release({ secrets: [] }))); + assert.doesNotThrow(() => checkPrerequisites(release({ tag: "b".repeat(40) }))); +}); +test("missing credentials, schema, migrations or current private version refuse deployment", () => { + for (const changes of [{ secrets: [] }, { columns: [] }, { migrations: [] }, { capability: null }, + { versions: [] }, { versions: [{ version_id: "old", percentage: 50 }, { version_id: "new", percentage: 50 }] }]) { + assert.throws(() => checkRelease(release(changes))); + } + assert.throws(() => checkRelease({ ...release(), run: () => { throw new Error("Unavailable evidence"); } })); +}); +test("independent builds cannot upload through the default build without preflight", () => { + assert.match(main.build.command, /^node scripts\/key_data_preflight.mjs --deploy && /); + const ci = readFileSync(new URL("../.github/workflows/ci.yml", import.meta.url), "utf8"); + assert.equal(ci.split("node scripts/check_worker_bundle.mjs --config").length - 1, 2); + for (const mainWorker of [false, true]) assert.ok(bundleArguments("temporary", "output", mainWorker).includes("--dry-run")); + const injected = bundleArguments("temporary", "--dry-run=false", true); + assert.equal(injected.includes("--dry-run=false"), false); + assert.ok(injected.includes("--dry-run")); + const copy = bundleConfig(main); + assert.equal(Object.hasOwn(copy, "build"), false); + assert.deepEqual(copy, Object.fromEntries(Object.entries(main).filter(([name]) => name !== "build"))); + assert.match(main.build.command, /key_data_preflight/); + const manual = readFileSync(new URL("../scripts/deploy.sh", import.meta.url), "utf8"); + const workflow = readFileSync(new URL("../.github/workflows/deploy.yml", import.meta.url), "utf8"); + assert.ok(manual.indexOf("key_data_preflight.mjs --check") < manual.indexOf("migrations apply")); + assert.ok(workflow.indexOf("key_data_preflight.mjs --check") < workflow.indexOf("Apply D1 migrations")); + assert.ok(workflow.indexOf("key_data_preflight.mjs --prepare") < workflow.indexOf("Deploy Knowledge Worker")); + assert.ok(manual.indexOf("key_data_preflight.mjs --prepare") < manual.indexOf("wrangler deploy --config wrangler.knowledge.jsonc")); +}); + +test("a different expansion commit is compatible with independently deployed enforcement", () => { + assert.doesNotThrow(() => checkRelease(release({ tag: "b".repeat(40) }))); + assert.throws(() => checkRelease(release({ tag: null }))); +}); 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_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..353e44a8 --- /dev/null +++ b/tests/test_peer_capability.py @@ -0,0 +1,37 @@ +"""Controlled Container reads require the current main's enforcement capability.""" +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) + 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: + user_store.get_user("synthetic") + assert error.value.status_code == 503 + else: + assert user_store.get_user("synthetic")["username"] == "synthetic" + if not controlled: + assert calls == ["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..be9437f4 --- /dev/null +++ b/tests/test_provider_dispatch_worker.mjs @@ -0,0 +1,151 @@ +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); + } +}); 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_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_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 74b04c46..ff0af76c 100644 --- a/tests/test_user_store_d1.py +++ b/tests/test_user_store_d1.py @@ -22,6 +22,8 @@ def __init__(self): def __call__(self, payload, *, endpoint): assert endpoint == "users" operation = payload["operation"] + if operation == "capabilities": + return {"version": 1, "key_controls": {"protocol": 2, "enforcement": True}} self.calls.append(operation) if self.down: raise PrivateIntelligenceError(503, "storage_unavailable") @@ -36,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} 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..793bae10 --- /dev/null +++ b/worker/account-contract.mjs @@ -0,0 +1,31 @@ +/** Negotiate additive account metadata before crossing a strict Container decoder. */ +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 function hasAccountPolicy(row) { + return Boolean(row?.official_only) || row?.forced_provider_preferences != null; +} + +export function accountReply(row, version) { + if (!row || version === ACCOUNT_POLICY_VERSION) return row; + // Expansion decoders understand controlled rows and refuse their dispatch. + // Older strict decoders also refuse this shape; never project controls away. + verifyDataPolicy(row.data_policy); + if (hasAccountPolicy(row)) return row; + 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 c88ff49a..fee1bd9f 100644 --- a/worker/control-users-d1.mjs +++ b/worker/control-users-d1.mjs @@ -1,3 +1,4 @@ +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. @@ -219,14 +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; - if (user.official_only !== null || user.forced_provider_preferences !== null) 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); @@ -255,7 +252,7 @@ export async function handleControlUsersRequest(request, env) { } 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..22b4c07e 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, expansionFence } from "./key-controls-capability.mjs"; import { dataPolicy } from "./dispatch-policy.mjs"; import { logFailure } from "./log.mjs"; @@ -78,7 +78,12 @@ 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 || policy.official_only === 1 || policy.forced_provider_preferences !== null) 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 +91,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 c6b47fbc..e96b2f99 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 === 1 || row?.official_only === true || 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 262fd996..6fba1cce 100644 --- a/worker/key-data-guards.mjs +++ b/worker/key-data-guards.mjs @@ -1,4 +1,4 @@ -import { KEY_CONTROLS, expansionFence } from "./key-controls-capability.mjs"; +import { KEY_CONTROLS, controlled, expansionFence } from "./key-controls-capability.mjs"; /** Shared provider classes and administrator-owned preferences at native dispatch. */ import classes from "../providers/data-classes.json" with { type: "json" }; @@ -84,7 +84,7 @@ export async function legacyDataControls(env) { if (!Array.isArray(rows?.results)) throw new TypeError("Unverifiable legacy controls"); for (const row of rows.results) { if (!validDataControls(row)) throw new TypeError("Unverifiable legacy controls"); - expansionFence(row); + if (controlled(row)) { const error = new TypeError("Legacy controlled accounts"); error.code = "legacy_controlled_accounts"; throw error; } } return { official_only: null, forced_provider_preferences: null }; } 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/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 64160011..dfbba696 100644 --- a/worker/knowledge/skills.mjs +++ b/worker/knowledge/skills.mjs @@ -9,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", "X-MultiLLM-Dispatch-Principal": JSON.stringify({ version: 1, username: dispatchContext()?.authority?.keyId }) }, 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/legacy-binding-policy.mjs b/worker/legacy-binding-policy.mjs new file mode 100644 index 00000000..6b590360 --- /dev/null +++ b/worker/legacy-binding-policy.mjs @@ -0,0 +1,18 @@ +/** Headerless private calls are compatible only with the expansion Container fence. */ +import { legacyDataControls } from "./key-data-guards.mjs"; +export async function legacyBindingControls(env) { + try { return await legacyDataControls(env); } + catch (error) { + // Store outages and malformed rows never acquire an alternate authority. + if (error.code !== "legacy_controlled_accounts") throw error; + const container = env.MULTILLM_PROXY_CONTAINER?.getByName("primary"); + const response = await container.fetch("https://container.internal/healthz", { + method: "GET", redirect: "manual", signal: AbortSignal.timeout(5000), + }); + const capability = (await response.json()).key_controls; + if (!response.ok || capability?.protocol !== 1 || capability.enforcement !== false) throw Error("Unverifiable legacy sender"); + // A's Container rereads and refuses controlled accounts before this private + // hop. It sends no new headers, so a verified A sender proves this call is NULL. + return { official_only: null, forced_provider_preferences: null }; + } +} 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 cbc67f59..ba640408 100644 --- a/worker/provider-dispatch.mjs +++ b/worker/provider-dispatch.mjs @@ -1,3 +1,4 @@ +import { legacyBindingControls } from "./legacy-binding-policy.mjs"; import { KEY_CONTROLS } from "./key-controls-capability.mjs"; /** Sole provider network boundary. Deferred work carries identity, never cached policy. */ import { AsyncLocalStorage } from "node:async_hooks"; @@ -22,15 +23,15 @@ 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)) { - return { ...state, policy: await legacyDataControls(state.env) }; + 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(); const user = await currentDataControls(state.env, state.authority); @@ -107,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); diff --git a/wrangler.jsonc b/wrangler.jsonc index 9807bd6e..deb6925c 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -4,7 +4,7 @@ "keep_vars": true, "main": "./cloudflare-worker.mjs", "build": { - "command": "python scripts/build_release_metadata.py" + "command": "node scripts/key_data_preflight.mjs --deploy && python scripts/build_release_metadata.py" }, "version_metadata": { "binding": "CF_VERSION_METADATA" From d9a85de1d9ba9bc16c85ebe7ae5bc5a6ef795905 Mon Sep 17 00:00:00 2001 From: Abhyudaya Baiju Tharakan Date: Tue, 20 Oct 2026 20:31:33 +0530 Subject: [PATCH 2/7] fix: keep account management independent of dispatch capability --- docs/key-data-guards.md | 23 ++++++++++++++---- services/provider_dispatch.py | 6 +++++ services/user_store.py | 6 ----- tests/support/account_envelope_driver.py | 27 +++++++++++++++++++-- tests/test_key_controls_contract_worker.mjs | 23 +++++++++++++++++- tests/test_peer_capability.py | 16 ++++++++---- 6 files changed, 82 insertions(+), 19 deletions(-) diff --git a/docs/key-data-guards.md b/docs/key-data-guards.md index 59d8d9d8..bd01f690 100644 --- a/docs/key-data-guards.md +++ b/docs/key-data-guards.md @@ -146,7 +146,7 @@ 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`. -An earlier Container checks the exact account field set. Sending the three new +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. @@ -159,9 +159,20 @@ 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; this does not establish the exact version -combination or error cause in a historical deployment without its logs or -Container fingerprint. +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, @@ -176,7 +187,9 @@ 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. -Workers Builds can deploy the main Worker and Container without waiting for the +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 diff --git a/services/provider_dispatch.py b/services/provider_dispatch.py index afe9e9ed..06dfabfe 100644 --- a/services/provider_dispatch.py +++ b/services/provider_dispatch.py @@ -65,6 +65,12 @@ def current_principal(reference=None): if reference.get("api_key_prefix") and reference["api_key_prefix"] != user.get("api_key_prefix"): raise unavailable() policy = verify(user.get("data_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 Exception: raise unavailable() from None diff --git a/services/user_store.py b/services/user_store.py index 002b88f6..f2de48a3 100644 --- a/services/user_store.py +++ b/services/user_store.py @@ -62,12 +62,6 @@ def _call(operation, **values): for attempt in range(1, attempts + 1): try: response = request_private_intelligence({"operation": operation, "data_policy_version": 1, **values}, endpoint="users") - rows = response.get("users", [response.get("user")]) if isinstance(response, dict) else [] - from services.key_data_guards import has_data_controls - if operation in READ_OPERATIONS and any(isinstance(row, dict) and has_data_controls(row) for row in rows): - capability = request_private_intelligence({"operation": "capabilities"}, endpoint="users").get("key_controls") - from services.peer_capability import require_enforcement - require_enforcement(capability) return response except Exception as error: cause = (f"{error.status} {error.code}" if isinstance(error, PrivateIntelligenceError) diff --git a/tests/support/account_envelope_driver.py b/tests/support/account_envelope_driver.py index 097af3e1..9c6b475a 100644 --- a/tests/support/account_envelope_driver.py +++ b/tests/support/account_envelope_driver.py @@ -1,6 +1,7 @@ """Registered Container routes, using actual HTTP to the disposable main Worker.""" import json import os +import re import sys import tempfile import types @@ -86,14 +87,36 @@ def transport(self, method, url, **kwargs): requests.sessions.Session.request = transport from app import app # noqa: E402 +if revision: + from jinja2 import FunctionLoader + + def versioned_template(name): + if ".." in name or name.startswith("/"): + return None + result = subprocess.run(["git", "show", revision + ":templates/" + name], + cwd=root, capture_output=True, text=True) + return result.stdout if result.returncode == 0 else None + + app.jinja_loader = FunctionLoader(versioned_template) client = app.test_client() headers = {"Authorization": "Bearer mllm_synthetic_incident_key_long_enough"} requests_to_run = json.loads(sys.argv[4]) if len(sys.argv) > 4 else [("/v1/models", "get"), ("/v1/chat/completions", "post")] -for path, method in requests_to_run: +for operation in requests_to_run: + path, method = operation[:2] + if path == "/login": + username = operation[2] + client = app.test_client() + page = client.get("/login") + token = re.search(r'name="csrf_token" value="([^"]+)"', page.get_data(as_text=True)).group(1) + response = client.post("/login", data={"username": username, "csrf_token": token, + "api_key": "mllm_synthetic_incident_key_long_enough" if username == "synthetic" else os.environ["ADMIN_API_KEY"]}) + print(json.dumps({"path": path, "status": response.status_code, "dispatches": len(seen)})) + continue arguments = {"json": {"model": "openrouter:openai/gpt-6-luna", "messages": [ {"role": "user", "content": "synthetic"}, ]}} if method == "post" else {} - response = getattr(client, method)(path, headers=headers, **arguments) + 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, "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})) diff --git a/tests/test_key_controls_contract_worker.mjs b/tests/test_key_controls_contract_worker.mjs index 9b2216c0..537edb27 100644 --- a/tests/test_key_controls_contract_worker.mjs +++ b/tests/test_key_controls_contract_worker.mjs @@ -43,6 +43,10 @@ test("every expansion/enforcement runtime combination preserves NULL accounts an 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(); 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" }); for (const container of versions) for (const privateRef of versions) for (const [index, policy] of policies.entries()) { @@ -68,14 +72,31 @@ test("every expansion/enforcement runtime combination preserves NULL accounts an 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 ? "get" : "post"]])], + 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" }] }), diff --git a/tests/test_peer_capability.py b/tests/test_peer_capability.py index 353e44a8..8122f854 100644 --- a/tests/test_peer_capability.py +++ b/tests/test_peer_capability.py @@ -1,4 +1,4 @@ -"""Controlled Container reads require the current main's enforcement capability.""" +"""Management reads are independent of current dispatch enforcement authority.""" import pytest from error_handlers import APIError @@ -26,12 +26,18 @@ def rpc(payload, **_): 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: - user_store.get_user("synthetic") + current_principal({"username": "synthetic"}) assert error.value.status_code == 503 else: - assert user_store.get_user("synthetic")["username"] == "synthetic" - if not controlled: - assert calls == ["get"] + assert current_principal({"username": "synthetic"})["username"] == "synthetic" + assert calls == (["get", "capabilities"] if controlled else ["get"]) From 5402e051a4a64e41b89cb0b9de9d42497eadd262 Mon Sep 17 00:00:00 2001 From: Abhyudaya Baiju Tharakan Date: Tue, 20 Oct 2026 20:41:07 +0530 Subject: [PATCH 3/7] fix: scope release preflight to selected Worker configurations --- scripts/key_data_preflight.mjs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/key_data_preflight.mjs b/scripts/key_data_preflight.mjs index fdebbca3..25b930e6 100644 --- a/scripts/key_data_preflight.mjs +++ b/scripts/key_data_preflight.mjs @@ -6,6 +6,8 @@ import { pathToFileURL } from "node:url"; import { localMigrations, remoteAppliedMigrations } from "./verify_d1_migrations.mjs"; const ROOT = new URL("../", import.meta.url); +const MAIN_CONFIG = process.env.MAIN_WORKER_CONFIG || "wrangler.jsonc"; +const KNOWLEDGE_CONFIG = process.env.KNOWLEDGE_WORKER_CONFIG || "wrangler.knowledge.jsonc"; const REQUIRED_SECRETS = ["ADMIN_API_KEY", "FLASK_SECRET_KEY", "JWT_SECRET"]; const REQUIRED_COLUMNS = ["official_only", "forced_provider_preferences", "max_stream_cost_microusd"]; class PreflightError extends Error {} @@ -37,7 +39,7 @@ export function checkPrerequisites({ main, knowledge, run, migrations = localMig const results = JSON.parse(run(["d1", "execute", database, "--remote", "--json", "--command", "PRAGMA table_info(control_users)"])); const columns = results.flatMap(item => item.results ?? []).map(item => item.name); requireThat(REQUIRED_COLUMNS.every(name => columns.includes(name)), "Account policy columns are missing from D1"); - const secrets = JSON.parse(run(["secret", "list", "--config", "wrangler.jsonc"])); + const secrets = JSON.parse(run(["secret", "list", "--config", MAIN_CONFIG])); requireThat(REQUIRED_SECRETS.every(name => main.vars?.[name] || secrets.some(item => item.name === name)), "Main Worker needs ADMIN_API_KEY, FLASK_SECRET_KEY and JWT_SECRET bindings"); } @@ -56,17 +58,17 @@ export function checkRelease(options) { checkPrerequisites(options); const { commit, run } = options; requireThat(/^[0-9a-f]{40}$/.test(commit), "Release needs a full commit identity"); - const deployment = JSON.parse(run(["deployments", "status", "--json", "--config", "wrangler.knowledge.jsonc"])); + const deployment = JSON.parse(run(["deployments", "status", "--json", "--config", KNOWLEDGE_CONFIG])); requireThat(deployment.versions?.length === 1 && deployment.versions[0].percentage === 100, "Private Worker must have one fully active version"); - const version = JSON.parse(run(["versions", "view", deployment.versions[0].version_id, "--json", "--config", "wrangler.knowledge.jsonc"])); + const version = JSON.parse(run(["versions", "view", deployment.versions[0].version_id, "--json", "--config", KNOWLEDGE_CONFIG])); const capability = (options.sourceCapability ?? sourceCapability)(version.annotations?.["workers/tag"]); requireThat(capability?.protocol >= 1, "Deploy and verify the expansion private Worker before the main Worker"); } function main(args) { - const mainConfig = JSON.parse(readFileSync(new URL("wrangler.jsonc", ROOT))); - const knowledge = JSON.parse(readFileSync(new URL("wrangler.knowledge.jsonc", ROOT))); + const mainConfig = JSON.parse(readFileSync(new URL(MAIN_CONFIG, ROOT))); + const knowledge = JSON.parse(readFileSync(new URL(KNOWLEDGE_CONFIG, ROOT))); try { checkConfig(mainConfig, knowledge); if (args[0] === "--check" || process.env.WRANGLER_COMMAND === "dev") { @@ -78,7 +80,7 @@ function main(args) { const commit = git(["rev-parse", "HEAD"]); requireThat(!git(["status", "--porcelain", "--untracked-files=no", "--", ".", ":(exclude)worker/build-id.mjs"]), "Deploy from a clean committed checkout"); - const run = args => execFileSync("npx", ["--no-install", "wrangler", ...args], { + const run = args => execFileSync("npx", ["--no-install", "wrangler", ...args, ...(args[0] === "d1" ? ["--config", MAIN_CONFIG] : [])], { cwd: ROOT, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 60000, env: { ...process.env, WRANGLER_SEND_METRICS: "false" }, }); From 7beea0c37024bf7d620521bf1b0f501e4cacab50 Mon Sep 17 00:00:00 2001 From: Abhyudaya Baiju Tharakan Date: Tue, 20 Oct 2026 21:55:30 +0530 Subject: [PATCH 4/7] fix: retain every non-null policy at legacy and credential boundaries --- routes/cascades.py | 4 ++-- routes/intelligence.py | 4 ++-- tests/outbound_inventory.json | 12 +++++----- tests/test_account_contract_worker.mjs | 10 ++++++++ tests/test_intelligence_auth_worker.mjs | 9 +++++++ tests/test_key_controls_contract_worker.mjs | 18 ++++++++++++-- tests/test_provider_dispatch_worker.mjs | 26 +++++++++++++++++++++ tests/test_shadow_sampling_policy.py | 8 ++++--- worker/account-contract.mjs | 5 ++-- worker/intelligence-auth-d1.mjs | 6 +++-- worker/key-data-guards.mjs | 4 ++-- worker/provider-dispatch.mjs | 4 ++-- 12 files changed, 86 insertions(+), 24 deletions(-) diff --git a/routes/cascades.py b/routes/cascades.py index e453dec5..69baee10 100644 --- a/routes/cascades.py +++ b/routes/cascades.py @@ -289,7 +289,7 @@ 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) - from services.key_data_guards import model_permitted + 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: @@ -303,7 +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(bool(user.get("official_only"))) + denied += int(has_data_controls(user)) continue if tier["model"].startswith("free:"): from services.free_model_policy import FREE_MODELS diff --git a/routes/intelligence.py b/routes/intelligence.py index 0228f7f6..41ed6474 100644 --- a/routes/intelligence.py +++ b/routes/intelligence.py @@ -168,9 +168,9 @@ 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 model_permitted + from services.key_data_guards import has_data_controls, model_permitted user = getattr(g, "authenticated_user", None) or {} - if user.get("official_only"): + if has_data_controls(user): policy["candidates"] = [candidate for candidate in policy["candidates"] if model_permitted(user, candidate["model"])] if not policy["candidates"]: diff --git a/tests/outbound_inventory.json b/tests/outbound_inventory.json index ac97a8f8..e3cf1fde 100644 --- a/tests/outbound_inventory.json +++ b/tests/outbound_inventory.json @@ -147,7 +147,7 @@ "routes/cascades.py": { "kind": "non-provider", "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", - "sha256": "13b441e4f87b0656bfce994cf7c9d17ed22ed321bea0c604fc07eaa5a0f547fd" + "sha256": "7d398b0e40b90bc860081b7c4343b9073002a700cdd550af3ea171e0f74cd8ab" }, "routes/chat_cache.py": { "kind": "non-provider", @@ -227,7 +227,7 @@ "routes/intelligence.py": { "kind": "non-provider", "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", - "sha256": "6bb99b0b757b5b25e90697415d1e41e7129dc7a7ca227915935afad56c7556d2" + "sha256": "1085b546c4eefb475af30e077ffeaebb905a4bef40af4f4ccf550f12a7e6cf4f" }, "routes/intelligence_media.py": { "kind": "guarded", @@ -1502,7 +1502,7 @@ "worker/account-contract.mjs": { "kind": "internal", "reason": "Negotiates private account RPC metadata; no provider dispatch.", - "sha256": "d549ea11498ddc7c7f5a0c9163540df6bd17c157cf0e9b3b5f5bcada183f6617" + "sha256": "cfc08f4bd8a4bfb2fe7ac34c3264aee790ee24ea87e0bf0eb17bdc384bbec4ab" }, "worker/admission-do.mjs": { "kind": "internal", @@ -1682,7 +1682,7 @@ "worker/intelligence-auth-d1.mjs": { "kind": "non-provider", "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", - "sha256": "803a9a5fe34a66fa27cc4bbc843a4c5d4466f7a732e8d54045515581070db7d9" + "sha256": "a4b9433cad6d971e280bf8be146273a49ebed611b0fe0c8249680e265006dbac" }, "worker/intelligence-d1.mjs": { "kind": "non-provider", @@ -1702,7 +1702,7 @@ "worker/key-data-guards.mjs": { "kind": "non-provider", "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", - "sha256": "f101e5bef18548d394914d263f8e7cf4ea5827af831c990c65896a26dcb2f003" + "sha256": "98248fd1ee5be5aa7d99734a0d51b0a00032a57629bdac3abe76dd38631d14ba" }, "worker/knowledge-edge.mjs": { "kind": "internal", @@ -2052,7 +2052,7 @@ "worker/provider-dispatch.mjs": { "kind": "guarded", "reason": "Sole Worker HTTP/SDK boundary; fresh durable policy precedes fetch or admitted callbacks.", - "sha256": "fe8dd519fa01b31151c2fb68275f4e92efd8103b98d12277790c60db082edfd5" + "sha256": "77e2ffe56e3f5bdd7fb76d6ea01478449c3111625652c96385a3fabf8c0218b9" }, "worker/realtime-metering.mjs": { "kind": "non-provider", diff --git a/tests/test_account_contract_worker.mjs b/tests/test_account_contract_worker.mjs index 0756ff4c..79acb822 100644 --- a/tests/test_account_contract_worker.mjs +++ b/tests/test_account_contract_worker.mjs @@ -19,3 +19,13 @@ test("legacy account projection refuses missing or malformed verification", () = 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.equal(accountReply(row, null), row); + } +}); 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_contract_worker.mjs b/tests/test_key_controls_contract_worker.mjs index 68e77606..1405e197 100644 --- a/tests/test_key_controls_contract_worker.mjs +++ b/tests/test_key_controls_contract_worker.mjs @@ -16,6 +16,8 @@ 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"); @@ -49,6 +51,18 @@ test("every expansion/enforcement runtime combination preserves NULL accounts an .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) { + 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), A], + { 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, policy === policies[0] ? 200 : 503); + assert.equal(rows.at(-1).dispatches, policy === policies[0] ? 1 : 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'") @@ -109,8 +123,8 @@ test("every expansion/enforcement runtime combination preserves NULL accounts an 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()); + if (index === 0 || index === 3 && !main && !privateRef) { + assert.equal(response.status, 200, "admitted Knowledge: " + await response.clone().text()); assert.equal(external, 1); } else { assert.ok(response.status >= 400, "controlled Knowledge must be enforced or refused"); diff --git a/tests/test_provider_dispatch_worker.mjs b/tests/test_provider_dispatch_worker.mjs index be9437f4..89250ada 100644 --- a/tests/test_provider_dispatch_worker.mjs +++ b/tests/test_provider_dispatch_worker.mjs @@ -149,3 +149,29 @@ test("import watches use the source account instead of the scheduler and refuse 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_shadow_sampling_policy.py b/tests/test_shadow_sampling_policy.py index b358363a..2f54eb42 100644 --- a/tests/test_shadow_sampling_policy.py +++ b/tests/test_shadow_sampling_policy.py @@ -6,7 +6,6 @@ import pytest from flask import Flask, Response, g -from services.auth_service import AuthService from services.dispatch_policy import envelope from services.key_data_guards import has_data_controls from services.shadow_eval_contract import eligible, make_sample @@ -25,6 +24,7 @@ def loaded_account(policy): + from services.auth_service import AuthService raw = dict.fromkeys(USER_FIELDS) raw.update(username="synthetic", api_key_hash="synthetic", api_key_prefix="synthetic", scopes="chat", is_admin=0, created_at="2026-10-01T00:00:00Z", shadow_eval_rate=0.2, @@ -36,6 +36,7 @@ def loaded_account(policy): @pytest.fixture def capture(monkeypatch): + monkeypatch.setenv("CONTENT_RETENTION_ENABLED", "false") pending = queue.Queue(maxsize=32) monkeypatch.setattr(sampling, "_QUEUE", pending) monkeypatch.setattr(sampling, "_WORKER", Mock(is_alive=Mock(return_value=True))) @@ -76,7 +77,7 @@ def complete(user, stream=False): @pytest.mark.parametrize("stream", [False, True]) def test_loaded_account_sample_success_captures_and_persists_only_null_policy(policy, stream, capture, monkeypatch): current = loaded_account(policy) - monkeypatch.setattr(AuthService, "_load_user_by_username", lambda _: current) + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: current) unrestricted = policy == POLICIES[0] assert has_data_controls(current) is not unrestricted assert eligible(PAYLOAD, current, "/v1/chat/completions", random_value=0) is unrestricted @@ -95,7 +96,7 @@ def test_loaded_account_sample_success_captures_and_persists_only_null_policy(po @pytest.mark.parametrize("replacement", [*POLICIES[1:], None, {}]) def test_policy_change_or_unreadable_authority_stops_deferred_retention(stage, replacement, capture, monkeypatch): current = loaded_account(POLICIES[0]) - monkeypatch.setattr(AuthService, "_load_user_by_username", lambda _: current) + monkeypatch.setattr("services.auth_service.AuthService._load_user_by_username", lambda _: current) response = complete(current, stream=True) if stage == "persist": list(response.response) @@ -118,6 +119,7 @@ def test_malformed_canonical_policy_cannot_fall_back_to_flat_nulls(policy): @pytest.mark.parametrize("policy", [{}, {"version": 1}, {"version": 0, **POLICIES[0]}]) def test_account_loading_does_not_replace_malformed_envelopes_with_unrestricted_policy(policy): from error_handlers import APIError + from services.auth_service import AuthService with pytest.raises(APIError): AuthService._row_to_user({"data_policy": policy}) diff --git a/worker/account-contract.mjs b/worker/account-contract.mjs index 793bae10..ef9e6c8c 100644 --- a/worker/account-contract.mjs +++ b/worker/account-contract.mjs @@ -1,4 +1,5 @@ /** 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; @@ -11,9 +12,7 @@ export function accountPolicyVersion(body) { return ACCOUNT_POLICY_VERSION; } -export function hasAccountPolicy(row) { - return Boolean(row?.official_only) || row?.forced_provider_preferences != null; -} +export const hasAccountPolicy = controlled; export function accountReply(row, version) { if (!row || version === ACCOUNT_POLICY_VERSION) return row; diff --git a/worker/intelligence-auth-d1.mjs b/worker/intelligence-auth-d1.mjs index 22b4c07e..50562f13 100644 --- a/worker/intelligence-auth-d1.mjs +++ b/worker/intelligence-auth-d1.mjs @@ -1,4 +1,4 @@ -import { KEY_CONTROLS, 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"; @@ -81,7 +81,9 @@ export async function handleIntelligenceAuthRequest(request, env) { 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 || policy.official_only === 1 || policy.forced_provider_preferences !== null) throw Error("key_controls_require_upgrade"); + if (!policy) throw Error("key_data_controls_unavailable"); + dataPolicy(policy); + if (controlled(policy)) throw Error("key_controls_require_upgrade"); } return reply({version: 1, principal}); } 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/provider-dispatch.mjs b/worker/provider-dispatch.mjs index ba640408..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"; @@ -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); From 2baa3428786d73b0a0d6d6a6cf85bfcdc4e5403a Mon Sep 17 00:00:00 2001 From: Abhyudaya Baiju Tharakan Date: Tue, 20 Oct 2026 22:06:50 +0530 Subject: [PATCH 5/7] fix: keep legacy management projection separate from identity reads --- tests/outbound_inventory.json | 4 ++-- tests/support/account_envelope_driver.py | 21 +++++++++++++++++++++ tests/test_account_contract_worker.mjs | 3 ++- tests/test_key_controls_contract_worker.mjs | 14 ++++++++++---- worker/account-contract.mjs | 3 ++- worker/control-users-d1.mjs | 5 ++--- 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/tests/outbound_inventory.json b/tests/outbound_inventory.json index 698ea58a..a2467c07 100644 --- a/tests/outbound_inventory.json +++ b/tests/outbound_inventory.json @@ -1502,7 +1502,7 @@ "worker/account-contract.mjs": { "kind": "internal", "reason": "Negotiates private account RPC metadata; no provider dispatch.", - "sha256": "241a044ecade58e0000b56ca41233e242a040ad73a49a6bded169aefedb036b3" + "sha256": "7d4e309ebafd2da9143d9bc19562173626466a2947428273c4686a04fc3c0f76" }, "worker/admission-do.mjs": { "kind": "internal", @@ -1597,7 +1597,7 @@ "worker/control-users-d1.mjs": { "kind": "non-provider", "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", - "sha256": "fb66cec8052bd9743460e28878f223cfa1c5f7825281927e6ba12c619da1a1d2" + "sha256": "4f7a8ae40dd9fdf5997a1a28c348680faf509b1834030fa8d988e5468668956b" }, "worker/cors-policy.mjs": { "kind": "non-provider", diff --git a/tests/support/account_envelope_driver.py b/tests/support/account_envelope_driver.py index 49670f33..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() diff --git a/tests/test_account_contract_worker.mjs b/tests/test_account_contract_worker.mjs index 39130851..d9c23a9d 100644 --- a/tests/test_account_contract_worker.mjs +++ b/tests/test_account_contract_worker.mjs @@ -26,6 +26,7 @@ test("zero and empty policies survive the negotiated account reply", () => { { official_only: null, forced_provider_preferences: {} }]) { const row = { username: "synthetic", ...policy, data_policy: { version: 1, ...policy } }; assert.equal(accountReply(row, 1), row); - assert.deepEqual(accountReply(row, null), { username: "synthetic" }); + assert.throws(() => accountReply(row, null), /key_controls_require_upgrade/); + assert.deepEqual(accountReply(row, null, { management: true }), { username: "synthetic" }); } }); diff --git a/tests/test_key_controls_contract_worker.mjs b/tests/test_key_controls_contract_worker.mjs index fe6d056c..541d5ebc 100644 --- a/tests/test_key_controls_contract_worker.mjs +++ b/tests/test_key_controls_contract_worker.mjs @@ -49,19 +49,25 @@ test("every expansion/enforcement runtime combination preserves NULL accounts an 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 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), A], + (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.equal(rows.at(-1).status, policy === policies[0] ? 200 : 503); - assert.equal(rows.at(-1).dispatches, policy === policies[0] ? 1 : 0); + 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 () => { diff --git a/worker/account-contract.mjs b/worker/account-contract.mjs index 8adeae82..d4bc2e0c 100644 --- a/worker/account-contract.mjs +++ b/worker/account-contract.mjs @@ -14,11 +14,12 @@ export function accountPolicyVersion(body) { export const hasAccountPolicy = controlled; -export function accountReply(row, version) { +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))); } diff --git a/worker/control-users-d1.mjs b/worker/control-users-d1.mjs index b16e6bb7..cb3fb91d 100644 --- a/worker/control-users-d1.mjs +++ b/worker/control-users-d1.mjs @@ -4,7 +4,7 @@ import { accountPolicyVersion, accountReply, accountWrite } from "./account-cont * through the Container's private outbound handler. Fixed statements, no client SQL. */ import { dataPolicy } from "./dispatch-policy.mjs"; -import { KEY_CONTROLS, controlled } 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"; @@ -238,7 +238,7 @@ 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; @@ -249,7 +249,6 @@ export async function handleControlUsersRequest(request, env) { 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.some(controlled)) throw Error("key_controls_require_upgrade"); return reply({ version: 1, users: users.map(user => accountReply(user, policyVersion)) }); } case "upsert": { From 82f27b166519f098b8e133f53429a6cd3ff8fcf2 Mon Sep 17 00:00:00 2001 From: Abhyudaya Baiju Tharakan Date: Tue, 20 Oct 2026 22:16:50 +0530 Subject: [PATCH 6/7] fix: keep management reads independent of dispatch capability --- docs/key-controls-rollout.md | 4 ++- services/user_store.py | 19 +-------------- tests/outbound_inventory.json | 2 +- tests/test_account_envelope_integration.mjs | 10 +++++--- tests/test_user_store_d1.py | 27 ++++++--------------- 5 files changed, 19 insertions(+), 43 deletions(-) diff --git a/docs/key-controls-rollout.md b/docs/key-controls-rollout.md index 5e5ecc7b..fcaedf4d 100644 --- a/docs/key-controls-rollout.md +++ b/docs/key-controls-rollout.md @@ -126,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/services/user_store.py b/services/user_store.py index 2c181746..80215752 100644 --- a/services/user_store.py +++ b/services/user_store.py @@ -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 {"data_policy_version": 1} 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__) diff --git a/tests/outbound_inventory.json b/tests/outbound_inventory.json index a2467c07..f097df2c 100644 --- a/tests/outbound_inventory.json +++ b/tests/outbound_inventory.json @@ -1467,7 +1467,7 @@ "services/user_store.py": { "kind": "non-provider", "reason": "Runtime module pinned independently of HTTP client spelling; network attempts require runtime admission.", - "sha256": "aae3a7501cd93490c746edd66bdb19e32679c4e6e689d14160cadea00b4408ab" + "sha256": "6ce9dad31606745046116d9bb05cc5ac1bafa854905135aa781e8c3a220ab662" }, "services/video_generation.py": { "kind": "guarded", diff --git a/tests/test_account_envelope_integration.mjs b/tests/test_account_envelope_integration.mjs index 39fabe69..31141381 100644 --- a/tests/test_account_envelope_integration.mjs +++ b/tests/test_account_envelope_integration.mjs @@ -75,9 +75,13 @@ test("migrated account rows cross the real private RPC and registered Container assert.equal(rows[1].preferences.data_collection, "deny"); }); await t.test("legacy readers and writers cannot erase or bypass controlled accounts", async () => { - const expanded = await rpc({ operation: "get", username: "synthetic" }); - assert.equal(expanded.status, 200); - assert.equal(expanded.body.user.official_only, 1); + 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); diff --git a/tests/test_user_store_d1.py b/tests/test_user_store_d1.py index 0af8c541..364b8ddc 100644 --- a/tests/test_user_store_d1.py +++ b/tests/test_user_store_d1.py @@ -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}] From 20433f5d53d643b7c8a11784827e2573baafd7d3 Mon Sep 17 00:00:00 2001 From: Abhyudaya Baiju Tharakan Date: Tue, 20 Oct 2026 22:17:07 +0530 Subject: [PATCH 7/7] test: scope transport policy fixtures to their own identities --- tests/dispatch_fixture.py | 12 ++++++++---- tests/test_secret_firewall.py | 10 ++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) 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/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]))