Skip to content

fix(sharing): reject an onboarding pair the platform cannot price - #201

Merged
argszero merged 1 commit into
mainfrom
fix/sharing-onboarding-priceable
Sep 13, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/sharing-onboarding-priceable

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

POST /api/sharings (routes/sharing.rs::create) validated only that model and key were non-empty, then stored provider / plan / model verbatim. But every call is billed with dao::get_model_price(&conn, &key.provider, model) (gateway.rs:182 → dao.rs:190, WHERE provider = ?1 AND model = ?2), and key.provider is caller-supplied — so a pair with no models row takes the None => (0.0, 0.0) arm of settle_usage and the call is silently free: the request is served normally (200, the upstream tokens are really spent), yet the consumer is not charged, the sharer earns nothing, and usage_records.cost stays 0.

This PR rejects an onboarding whose (provider, model) the platform cannot price, so such a key can never enter the pool.

Related Issue

(no open issue)

Changes

  • routes/sharing.rs::create — after the existing non-empty check (and before the INSERT), look up the pair in models and answer 400 {"error": "provider 与 model 不在模型目录中,无法计价"} when there is no priceable row.
  • The check is deliberately the models row, not membership in [[providers]]: openai / anthropic / google / xai have [[models]] rows but no [[providers]] row, so a providers-table check would reject legitimate keys. Priceability is exactly what the settlement path requires.
  • No config / schema / ui/ change, so no example-file sync is needed.

Tests

  • cargo test — 189 passed (was 188; the new test is the only added case)
  • cargo fmt --check — clean
  • cargo clippy --all-targets -- -D warnings — clean
  • New test create_rejects_unpriceable_provider_model: provider outside the catalog → 400; mistyped model → 400; neither is stored (the guard precedes the INSERT); a catalog pair → 200 and one row stored (positive control).
  • Teeth proof: removing only the guard turns exactly that test red (assertion left: 200, right: 400) while the rest of the module stays green.

Checklist

  • Branch fix/sharing-onboarding-priceable follows the convention
  • Conventional Commits message (fix(sharing): …)
  • Single responsibility, minimal change (1 file, +75/−0)

Out of scope (deliberate)

  • A plan that is not in config [[plans]] is still accepted: it is stored status='on' while resolve_outbound always returns None, so the key is unroutable forever (onboard 200 / use 503) and list_models_with_availability still counts it. That is a separate symptom (routing, not pricing) and gets its own change.
  • settle_usage's None => (0.0, 0.0) arm is untouched: a key whose model is deleted later legitimately settles 0.

POST /api/sharings stored whatever `provider` / `model` the caller sent, and
every call is then priced with `dao::get_model_price(&conn, &key.provider,
model)`. When no `models` row matches, `settle_usage` falls back to
`(0.0, 0.0)`: the gateway still serves the request (200, the upstream tokens
are really spent) but the consumer is not charged, the sharer earns nothing
and `usage_records.cost` stays 0 — a silently free call.

Validate the pair against the `models` table before the INSERT and answer 400
when no priceable row exists, so an unpriceable key can never be onboarded.

The check is the `models` row, not membership in `[[providers]]`: openai /
anthropic / google / xai only exist in `[[models]]`, so a providers-table
check would reject legitimate keys. Zero-pricing for a key whose model is
deleted later is unchanged — that is the documented behaviour, not this path.

Tests: a provider outside the catalog -> 400, a mistyped model -> 400, neither
row is stored (the check runs before the INSERT), and a catalog pair -> 200 as
the positive control.
@argszero
argszero merged commit 928dd24 into main Sep 13, 2026
1 check passed
@argszero
argszero deleted the fix/sharing-onboarding-priceable branch September 13, 2026 06:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant