The summed job deadline alone did not protect the test budget. Setup that
overran its allowance still ate into pytest's window, which is the same
failure this change set out to remove, just with more headroom.
Every step before pytest now carries its own ceiling, and their sum is the
`setup-timeout-minutes` default. Setup can no longer overrun into the test
budget without failing its own step first, and a slow setup step now reports
as a red step naming itself rather than a cancelled shard whose tests passed.
Model the workflow YAML the guard reads with Pydantic instead of bare dicts,
so the shapes it depends on are validated once at the boundary. A workflow
that does not parse is now reported as a finding rather than a traceback.
`prisma generate` runs `npm install prisma@<version>` whenever the
prisma-client-py binary cache directory has no CLI entrypoint, pulling ~85 MB
of query and schema engines over the network. Every workflow pointed
PRISMA_BINARY_CACHE_DIR at `${{ runner.temp }}/prisma-cache`, which GitHub
wipes and recreates per job, so that cache was empty on every job of every
run and the download was never avoidable.
The download is normally a few seconds and occasionally minutes. On one
proxy-db run it took 5m18s on a single shard against 3.8s on its eleven
siblings, which pushed the job past its 15 minute timeout and cancelled a
shard whose tests were at 99% and all passing.
Leave PRISMA_BINARY_CACHE_DIR unset so the binaries land in the
prisma-client-py default, which is already keyed by prisma and engine
version, and restore both that path and the @prisma/engines staging cache
through a shared composite action.
Job timeouts also counted setup against the test budget. `timeout-minutes`
now bounds the pytest step, with a separate allowance for checkout,
dependency install, and client generation, so slow setup shows up as a slow
job instead of a cancelled test run.
check_prisma_binary_cache.py guards all three invariants: no workflow
reintroduces the override, every job that generates the client restores the
cache, and the version the action greps out of uv.lock still resolves.
Whole-tree basedpyright drops from 147,728 to 146,543 errors (reportAny
-670, reportExplicitAny -244) with no rule increasing repo-wide or in
any file. TypedDicts, Mapping/Sequence views, Protocols, and precise
helper return types replace Any; no casts, ignores, or runtime changes.
Budgets ratcheted down by the fixed amounts.
Both restated what the test name and the surrounding setup already say, so
they were maintenance cost without explanatory value. The reasoning they
carried lives in the commit that added the gates.
/policies/list and /prompts/list are default-deny for internal_user, but the
Virtual Keys create/edit flow, the Teams forms and the Playground called them
on mount, so every internal user landing on the dashboard fired two requests
that 401. Add viewPolicies and viewPrompts to the capability map and use them
to gate the nav entry, the form field and the fetch together, following the
pattern from the Tool Policies migration. Non-admins now see no policy or
prompt selector at all rather than an empty dropdown.
The Usage page admits internal users because their own usage view works,
but the entity breakdown selector inside it also offered Organization
Usage, so picking it fired /organization/daily/activity and collected a
401. Neither that route nor /agent/daily/activity appears in any non-admin
route list, so both are default-deny. The team breakdown leaked the second
one too: it fetches agent activity unconditionally to fill its Top Agents
card, which 401s for the same roles.
Adds viewOrganizationUsage and viewAgentUsage to the existing capability
map and points the selector option, the page section, and the fetch's
enabled flag at the same capability, so a role that cannot call the
endpoint never sees the breakdown and never issues the request. The team
and tag breakdowns, which internal users can read, are untouched, and the
default Usage view was already one of those.
The Logs nav entry is open to internal users so they can read their own
request logs, but the page rendered all four tabs unconditionally. Audit
Logs calls GET /audit and Deleted Teams calls GET /v2/team/list?status=deleted,
neither of which an internal user is permitted to call, so the page fired
requests that came back 401.
Gate both tabs on new viewAuditLogs / viewDeletedTeams capabilities, using
the same CAPABILITY_ROLES map and useCan hook introduced for Tool Policies.
Hiding a tab drops its panel from the tree entirely, so the request is never
issued rather than issued and rejected.
Selecting a tab also mapped index 0 to "request logs" and every other index
to "audit logs", which activated the audit panel whenever a user opened
Deleted Keys or Deleted Teams. Derive the active tab from the visible tab
list instead, so the mapping survives tabs being filtered out.
xAI already converts Responses usage to chat Usage so web_search_calls survive
cost tracking. The chat completions bridge then re-ran the Responses usage
transform and crashed on missing input_tokens. Pass through already-chat Usage
and chat-shaped dumps instead
Gate web search like OpenAI (output/annotations/web_search_requests).
xAI uses server_side_tool_usage_details only for per-call cost math, with
web_search_requests mirrored in llms/xai for existing gate compatibility.
Treat positive web_search_calls as a web-search signal in built-in tool
cost gating, and mirror counts onto prompt_tokens_details.web_search_requests
when attaching xAI tool usage details so charges are not skipped.
Use usage.server_side_tool_usage_details.web_search_calls at $5/1k calls
instead of legacy num_sources_used/web_search_requests. Preserve tool usage
details through Responses usage transform for accurate response cost.
A poll of a Vertex passthrough batch wrote nothing to the managed-object row,
so status and file_object stayed frozen at the create-time snapshot and
GET /v1/batches served a stale status and an empty output file id for the life
of the batch. Only the create may claim a batch, but every observation of one
may refresh its state.
store_unified_object_id takes create_if_missing, which the poll clears: it
refreshes status and file_object through update_many, and leaves a row that is
absent absent rather than creating one owned by the observer, since created_by
and team_id are written by whoever reaches the create branch. The update payload
is now shared with the upsert so it cannot drift into writing api_key,
request_tags, created_by or team_id.
The passthrough identity re-assertion that was previously part of this PR ships
separately in #36121, so this PR keeps only the batch attribution work.
The creating key owns user_api_key_alias only when it actually has one. Guarding
the overwrite on the presence of a key rather than on a resolved alias nulled the
field out for every key generated without key_alias, and for any key rotated or
deleted before its batch finished, losing the creating user's alias that the spend
row previously carried. The guard now matches the team-alias line below it.