Story 2092: the broker bills the real provider cost

Why this exists (the catastrophe it prevents)

Before this change the broker never knew what a run actually cost. It guessed: it multiplied token counts by a hand-typed price table of three models. Every other model in the Mira/Vega roster (qwen, minimax, seed, gemini-flash-lite, deepseek-v4-pro) fell through to a deliberately-high $5 / $25-per-1M fallback. For a cheap model that is roughly an 18x over-estimate. Two things break from that single lie:

The brain already knows the truth: OpenRouter returns provider_details['cost'] per call. Story A1 (brain) started stamping that real number onto the model.usage event as cost_usd. This story makes the broker read it.

Satellite view: one number, two consumers

brain (OpenRouter real cost)
  -> model.usage event { input_tokens, output_tokens, model_id, cost_usd }
  -> broker accumulates totals.cost_usd
       |-> cost cap check      (abort if over the per-route cap)
       |-> per-firm credit debit (record_run_debit)

Both the cap and the debit read the same accumulator, totals.cost_usd. So there is exactly one place to fix: what we add to that accumulator per model.usage event.

Airplane view: what changed

SurfaceChange
ModelUsagePayload (agent_contracts/events.py) Adds cost_usd: float | None. Mandatory: the wire base is extra="forbid", so once the brain emits the field, an unaware broker would reject the whole event and fail the run. Optional-with-default keeps old streams parsing.
_usage_cost_usd() (clerk_worker/runner.py) New helper. Returns payload.cost_usd when present. When null, falls back to the static estimate and fires the billing_cost_fallback counter + a loud logger.warning. The model.usage handler now adds _usage_cost_usd(event.payload) to totals.cost_usd.
pricing.py The hand-priced _MODEL_PRICING table is deleted. token_cost_usd is now the flat conservative-high fallback only, renamed _FALLBACK_PRICING and documented as "not the price source".
pramaan.billing.cost_fallback (observability/metrics.py) New registry-backed counter + emit_billing_cost_fallback. Any non-zero rate means the brain stopped reporting cost - alarm on it.

Microscopic view: the fallback is the alarm

The fallback path is designed to be loud, not silent. If it ever runs in production it means the brain regressed and billing quietly went back to guessing. So it does three things at once - bill conservative-high (never free), emit an alarmable counter tagged by model_id, and log a warning - so the regression surfaces before the next invoice does.

Documented non-goal (Risk #3)

One shared OpenRouter key carries one global provider rate limit, so a heavy firm's burst can throttle others at the model layer. Accepted for the pilot; backstopped by the per-run cost cap and the per-firm circuit breaker. Revisit at >=10 firms or the first observed cross-firm throttle. No per-firm key/rate-limit is built here.

Verification

uv run ruff check src/shared/agent_contracts/pricing.py src/shared/agent_contracts/events.py src/shared/observability/metrics.py src/clerk_worker/runner.py
uv run pytest tests/unit/agent_contracts tests/unit/clerk_worker tests/unit/shared/observability -q

Key assertions: test_real_event_cost_is_primary_not_token_estimate (a $0.50 real cost on a 1-token call breaches a $0.10 cap - only possible if the real number is read) and test_missing_event_cost_falls_back_and_increments_counter (no cost_usd -> static estimate + counter fires once, tagged by model).

In one sentence: the broker now bills the provider's real cost and only guesses when forced to - loudly.