feat: price a run from a versioned rate table - #33
Merged
Merged
Conversation
`estimatedCostUsd` was hardcoded null in every run manifest and the report said so: "no pricing rule has been implemented". Most of the pieces were already here — a rate snapshot, an estimator that prices input and output separately, and a manifest field that already permitted a number. What was missing was a table keyed by model and a way to tell later which rates produced a figure. Both are now in `@stateproof/core`: - `PRICING_TABLE`, keyed by the model id a manifest already records, with `PRICING_TABLE_VERSION` covering the table as a whole. A rate change bumps the version rather than editing an entry, so a later change cannot silently restate what an earlier run cost. - `priceRun` returns the cost and the version together, because the two have to agree: a figure without the table that produced it cannot be checked, and a version without a figure says nothing. A model the table does not price stays unpriced. Pricing it at another model's rates would produce a number that looks measured and is not, which is the failure the estimator was already written to avoid. `ModelUsage` gains `pricingTableVersion`, optional so the twelve pinned manifests written before the table existed still validate. Those are records of what happened and are not rewritten; the field appears on runs made from here. The rate table moved from `@stateproof/submission` into core, beside the manifest schema whose fields it fills, because the runners that make the model calls cannot depend on the package that reports on them. Submission re-exports it, so its importers are unchanged and there is one table rather than two that can disagree. Ten tests cover the paths that matter: input and output priced at their own rates, an unpriced model, a missing token count, and a zero-token run priced as zero rather than as unpriced. The scoring-integrity guard now asserts the two fields agree instead of asserting the feature is absent. Full suite 547 passing; `pnpm reproduce` still PASSED (27 checks). Refs #10.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #10. Covers the first two bullets; the per-run budget is deliberately left out, see below.
What was already here
More than the issue implies.
packages/submission/src/pricing.tsalready had a rate snapshot, an estimator that prices input and output separately, andModelUsage.estimatedCostUsdalready permitted a number. What was missing was a table keyed by model, and any way to tell later which rates produced a figure.What this adds
PRICING_TABLE, keyed by the model id a manifest already records, so a lookup needs nothing else, withPRICING_TABLE_VERSIONcovering the table as a whole. A rate change bumps the version rather than editing an entry in place, so a later change cannot silently restate what an earlier run cost.priceRunreturns the cost and the version together, because they have to agree. A figure without the table that produced it cannot be checked later; a version without a figure says nothing.A model the table does not price stays unpriced. My first attempt passed the looked-up rates with
?? undefined, which fell through to the estimator's default and would have priced an unknown model at Opus rates. That is exactly the failure the file's own doc comment warns about, sopriceRunnow returns null for both fields instead.Two things I checked before touching anything
Pinned artifacts. The hashes in
submission/reproduction-manifest.jsonare over canonical predictions, not run manifests, so populating a cost field does not invalidate the frozen evaluation.pnpm reproducestill reports PASSED (27 checks).Historical manifests. Twelve tracked manifests carry
modelUsagewith exactly five keys, andModelUsageSchemais.strict().pricingTableVersionis therefore optional, so those still validate. They are records of what happened and are not rewritten; the field appears on runs made from here.One move worth flagging
The rate table moved from
@stateproof/submissioninto@stateproof/core. The runners that make the model calls needed it, and makingagentsdepend onsubmissionwould have inverted the layering, since submission reports on agent runs. Core already owns the manifest schema whose fields this fills, so the fields and the only code that populates them now sit together. Submission re-exports the table, so its importers are unchanged and there is one table rather than two that can drift.Tests
Ten in
packages/core/test/pricing.test.ts, aimed at the property that matters rather than at arithmetic: a cost figure is never invented.NaNor negative token count0rather than as unpricedThe
scoring-integrityguard assertedestimatedCostUsdwas null because the feature did not exist. It now asserts the two fields agree, which is the real invariant. The report line that read "Cost in USD is deliberately null: no pricing rule has been implemented" was true and is now false, so it is replaced.Full suite 547 passing across 31 files,
pnpm typecheckclean.Not in this PR
The optional per-run budget that stops compilation before it is exceeded. It touches compilation control flow and needs a decision about what happens mid-suite when the budget runs out, which is a product call rather than a mechanical one. Happy to take it as a follow-up once you have a view.
Note
Low Risk
Overview
This pull request adds a model-keyed
PRICING_TABLEand supporting types to the core package, providing versioned input and output rates for pricing calculations. The submission pricing flow now applies these rates, reports input and output costs separately, and includes the pricing snapshot information used for a run. Runners and scoring logic propagate pricing metadata, while core exports, manifest schema fields, and pricing/scoring tests are updated to cover the new behavior. Per-run budget handling remains unchanged and is not included.Written by Gitzilla for commit 6c3fccb. This will update automatically on new runs. Configure in the Gitzilla dashboard.