Skip to content

fix(pricing): deterministic price tiebreak; admit a zero-cost reserve with a zero hold (#98, #104)#115

Merged
JumpTechCode merged 3 commits into
mainfrom
fix/pricing-determinism-free-model
Jul 20, 2026
Merged

fix(pricing): deterministic price tiebreak; admit a zero-cost reserve with a zero hold (#98, #104)#115
JumpTechCode merged 3 commits into
mainfrom
fix/pricing-determinism-free-model

Conversation

@JumpTechCode

Copy link
Copy Markdown
Contributor

#98 — non-deterministic current-price resolution

resolve_price ordered by published_at DESC LIMIT 1 with no tiebreak. price_book.published_at defaults to func.now() (the transaction timestamp, constant within a transaction), so a bulk seed/migration publishes several versions at the same instant and the "current" pick is arbitrary — and can differ run to run if physical order shifts. The resolved version is stamped on the reservation and drives commit reconciliation, so an ambiguous pick silently shifts the cost basis.

Fix: ORDER BY published_at DESC, version DESC, making "latest published" a total order.

#104 — a genuinely free model can't transit the gate (decision: admit with a zero hold)

reserve raised NonPositiveEstimate (422) whenever the worst-case estimate rounded to 0. Because ceil_micro keeps any positive worst case at ≥ 1 micro, estimate == 0 happens only for a zero-token request or a zero-priced (free) model — so a free model always 4xx'd on reserve.

Fix (per the resolved question): admit a zero estimate with a zero-micro hold so free and paid models route through the same guard. The reserve walks every node at amount 0 — which succeeds on any node with non-negative headroom (ensure_period seeds the row, the guard is headroom >= 0) — and commit later reconciles to 0. estimate_micro never returns negative, so estimate >= 0 always holds.

Dead-error cleanup

NonPositiveEstimate was raised only by reserve, so admitting zero makes it an error the server can never emit — the same vestige pattern the review flags in #96 (unused status column) and #100 (dead 402). Removed the domain type and its api/errors + SDK mappings. The SDK's error_for already falls a 422-with-unmapped-code back to InvalidRequest (what non_positive_estimate mapped to), so client behavior is unchanged.

Tests

  • Integration: two versions sharing a published_at resolve deterministically to the higher version; the existing latest-published test unchanged.
  • Unit: a zero-token reserve is now admitted with a zero hold (walks nodes at amount 0, persists estimated_micro=0, caches the response) instead of raising; NonPositiveEstimate cases dropped from the error-mapping tests.

Local gate: ruff, mypy --strict, import-linter, full unit suite (489), price-book + reserve integration — all green.

Closes #98, #104.

JumpTechCode and others added 3 commits July 20, 2026 11:41
… with a zero hold (#98, #104)

#98: resolve_price ordered only by published_at DESC, but published_at defaults
to the transaction timestamp, so a bulk seed/migration publishes several
versions at the same instant and the "current" pick was arbitrary and could vary
run to run. The resolved version is stamped on the reservation and drives commit
reconciliation, so an ambiguous pick silently shifts the cost basis. Add version
DESC as a deterministic tiebreak so "latest published" is a total order.

#104: reserve raised NonPositiveEstimate (422) when the worst-case estimate
rounded to 0 — which, because ceil_micro keeps any positive worst case at >= 1
micro, only happens for a zero-token request or a genuinely free (zero-priced)
model. A free model could therefore never transit the gate. Admit a zero
estimate with a zero-micro hold instead, so free and paid models route through
the same guard; the reserve walks every node at amount 0 (always succeeds on a
node with any non-negative headroom) and commit reconciles to 0.

NonPositiveEstimate was raised only by reserve, so it is now dead — an error the
server can never emit, the same vestige pattern flagged in #96/#100. Removed the
type and its api/sdk mappings (the SDK's 422 fallback already resolves to
InvalidRequest, so client behavior is unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjJ4QScUKbrMzF1jujL5Bg
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjJ4QScUKbrMzF1jujL5Bg
@JumpTechCode
JumpTechCode merged commit f88ec45 into main Jul 20, 2026
8 checks passed
@JumpTechCode
JumpTechCode deleted the fix/pricing-determinism-free-model branch July 20, 2026 17:47
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.

resolve_price is non-deterministic when price-book versions share a published_at timestamp

1 participant