Skip to content

PAYG remap: format, parser, and conformance vectors for plan-billed models - #14

Open
iceteaSA wants to merge 1 commit into
cortexkit:masterfrom
iceteaSA:payg-remap
Open

PAYG remap: format, parser, and conformance vectors for plan-billed models#14
iceteaSA wants to merge 1 commit into
cortexkit:masterfrom
iceteaSA:payg-remap

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

PAYG remap: format, parser, and conformance vectors for plan-billed models

Refs cortexkit/astrocyte#3.

What this is

models.dev publishes cost: {input: 0, output: 0} for plan-billed lanes — 486 models
across 60 providers in the 2026-08-13 snapshot. Those zeros are correct as marginal cost
and useless for routing: a spend report that prices plan usage at $0 cannot answer "what
would this call have cost on this platform without the plan", which is the question that
decides where work goes.

This adds a document format and parser that overlays the catalog with sourced rates for
those ids, plus conformance vectors that define what a correct overlay does.

It does NOT add a classifier. See "What is not here".

What is here

  • PaygRemapDoc and friends — the document types, in a new payg_remap module.
  • An exact provider-qualified key newtype with no fallback to a bare model name.
  • A fallible parser with 11 error variants, all reachable and tested.
  • A conformance runner generic over the join, in payg_conformance.
  • Two vector corpora under tests/golden/, following the pattern in
    cortexkit-store-types and cortexkit-cache-core.

Additive: no existing type, function, or test changes. The only edit to lib.rs is nine
export lines; the only deletion in the diff is the version bump to 0.3.0.

Override costs reuse the existing CostSchedule rather than a parallel type, and rates go
through the existing decimal_str_to_nanos — one money representation, and the private
helper stays private.

What is not here, deliberately

No classifier. Nothing in this crate takes a remap document plus a catalog and returns
an outcome. The classification rules are specified as a matrix and shipped as executable
vectors, but the join itself is not implemented here.

The failure taxonomy is still moving. It grew a third mode after one review round, gained
four matrix cells after another, and had its whole "priced" column restructured after a
third. A classifier in this crate would pin that taxonomy to this crate's semver surface
while it is still changing, and a #[cfg(test)] reference implementation would be worse:
as the only executable join in the tree it becomes the de facto normative one, because that
is what people copy.

So the runner is generic over Fn(&PaygRemapDoc, &CatalogDoc, &PaygModelId) -> PaygOutcome
and this crate provides no value of that type. Whoever writes the classifier gets the whole
suite executable in one call. Where it should live is the open question on astrocyte#3.

No data document. payg-remap.json is not in this PR. The crate header says "types and
parsing only, NO bundled data", and I did not want to be the first exception. Where the
canonical document lives is a placement question that belongs with you.

Two gates, and only one of them runs here

The split is explicit:

gate what it covers executed
parse document syntax, kinds, provenance, rate parsing, the structural Default guard here, in this PR
classification the outcome matrix, the all-zero predicate as applied, refusal directions at the classifier's home, via the shipped runner

The parse gate is proven by mutation: each of the 14 guards was deleted and separately
narrowed, and each mutation reddens a named vector. The classification suite is complete
and cell-referenced but does not execute here, because there is nothing to execute it
against. Seventeen of the 31 mutation rows are shipped and unrun until a classifier exists.

What mutation testing found

The first pass ran every mutation as "delete the guard" and reported 14/14 reddened. An
independent reviewer then ran the narrowing class — leave the guard, make it check less —
and five guards survived:

  • the provenance empty-string filter had never executed at all, because every vector
    omitted the field and the lookup short-circuited before reaching it
  • all_zero narrowed to a single field survived, because no positive test proved it does
    not over-fire
  • the schema gate rejected only newer schemas, so schema: 0 passed
  • the id guard rejected only an empty provider, so provider/ passed
  • chained-target could not distinguish checking the target from checking the source

All five were correct, load-bearing code with no test behind them. They are pinned now.
The vectors also encode the resulting rule: a refusal predicate needs both directions, and
a negative vector that omits a field cannot pin a guard that validates the field's contents.

Vector design

Each classification vector carries a cell reference naming the matrix cell it derives
from. A vector whose expected outcome contradicts its cited cell is then catchable by
reading, without executing anything — the matrix is the oracle. The well-formedness test
enforces that every reference resolves and that all 29 cells are covered exactly once.

29 rather than 20: the matrix prints 5 declarations × 4 source states, but the three
resolves_to "by target" cells each expand over the target's own four states.

The test file's doc comment carries the obligation: any classifier implementation must
execute this suite through run_vectors, and one that does not is nonconforming.

Verification

cargo test -p cortexkit-model-catalog     # 15 unit + 7 + 2 + 29 integration + 1 compile-fail doctest
cargo clippy -p cortexkit-model-catalog -- -D warnings

The compile-fail doctest is the structural guard: PaygRemapDoc derives no Default and
parsing is fallible with no infallible constructor, so unwrap_or_default() does not
compile. That is deliberate — a remap document that silently defaults to empty would
reinstate every false zero it exists to remove.

Open questions for you

  1. Where the classifier lives as the taxonomy grows (astrocyte#3).
  2. Where payg-remap.json lives, given the crate is deliberately data-free.
  3. Whether supersession detection belongs in fusiform. DeclarationSuperseded is a
    catalog-era transition — "this id started being priced" — and fusiform's diff pipeline
    already computes that event. Related: a classification is only reproducible against the
    catalog read it came from, so a consumer should record that read's resolved_at_ms
    rather than keying on catalog_version, which advances on its own clock.

Design notes, including the failure modes this cannot represent, are in the astrocyte#3
thread.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Adds a PAYG remap format, parser, and conformance vectors so plan‑billed models can be priced counterfactually. Previously plan lanes had $0 marginal cost; now an overlay can supply sourced rates or refusal rules, with no routing change until a classifier uses it.

  • Adds payg_remap and payg_conformance to cortexkit-model-catalog; exports Payg* types, run_vectors, and the normative is_all_zero predicate; bumps crate to 0.3.0. Additive only; no existing APIs changed.
  • Parser (schema 1) requires counterfactual: "same_platform_list", exact provider/model ids, and provenance. It rejects unknown kinds, self/chained resolves_to, overrides that are all‑zero or lack any positive rate, inexact/negative rates, context_over_200k outside tiers, and non‑string provider id_prefix. Errors report a single root cause; 13 variants are pinned by golden parse vectors.
  • Conformance runner run_vectors(Fn(&PaygRemapDoc, &CatalogDoc, &PaygModelId) -> PaygOutcome) executes the outcome matrix; golden class vectors assert cell coverage and contract without providing a classifier.

Adoption

  • Classifier authors: implement the function above and run the suite via run_vectors to validate outcomes.
  • Consumers must provide a remap data file externally; the crate ships no payg-remap.json.
  • Do not default on parse failure; PaygRemapDoc has no Default.

Written for commit 426d453. Summary will update on new commits.

Review in cubic

@iceteaSA
iceteaSA requested a review from ualtinok as a code owner August 16, 2026 08:40

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread crates/cortexkit-model-catalog/src/payg_remap.rs Outdated
Comment thread crates/cortexkit-model-catalog/src/payg_remap.rs
Comment thread crates/cortexkit-model-catalog/tests/payg_class_vectors.rs Outdated
@iceteaSA

Copy link
Copy Markdown
Collaborator Author

Both P2s were real. Fixed in 2346274, with one deviation from the suggested remedy that I want to flag rather than bury.

The ZeroOverride hole is real and I reproduced it before fixing. An override cost of {"reasoning": 0} parsed clean:

ACCEPTED: input: None, output: None, reasoning: Some(0), tiers: []

Same for an all-zero tiers array with no flat rates.

I did not drop the leading clause, because that breaks something worse. Without (input == Some(0) || output == Some(0)), all_zero on an all-None schedule evaluates [].all(|r| r == 0) on an empty iterator, which is true, and empty tiers is also true. All-None would then classify as ALL-ZERO — and this crate's founding rule is that None is unpriced and not zero. The catalog predicate would have started reporting unpriced models as zero-priced.

The fix is at the override boundary instead. all_zero stays as-is; an overrides_unpriced entry must now supply at least one rate that is Some(n) with n > 0. An override exists to supply a real rate, so one that supplies nothing positive is useless at best and reinstates a false zero at worst. That covers all three bad shapes uniformly:

reasoning-only-zero        refused -> override p/m does not supply a positive rate
all-none                   refused -> override p/m does not supply a positive rate
zero-tiers-only            refused -> override p/m does not supply a positive rate
LEGIT-real-rate            ACCEPTED
LEGIT-zero-beside-real     ACCEPTED

The last two matter as much as the first three: a schedule carrying a real rate beside a zero must still parse, so the guard is checked in both directions.

id_prefix now rejects a non-string with a specific InvalidIdPrefix variant. Absent and null still mean "no prefix" — only a wrong type is an error. You were right about the direction of the damage: silently dropping the prefix widens a narrowly-scoped rule to the whole provider, which is the dangerous way to fail.

P3 — the corpus validators now aggregate failures instead of aborting at the first one, so a drifting fixture reports every missing, duplicated, and mis-contracted cell in one run.

Both new guards were mutation-tested in two classes — deleted, and narrowed to check less — and each reddens a named vector.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread crates/cortexkit-model-catalog/src/payg_remap.rs Outdated
Comment thread crates/cortexkit-model-catalog/tests/payg_class_vectors.rs
@iceteaSA

Copy link
Copy Markdown
Collaborator Author

Both correct. Fixed in 0d6bf7a, with one deliberate departure on the first.

The redundancy is real. parse_rate rejects negatives, so every rate is >= 0, which makes all_zero imply !has_positive_rate; the || reduced to its second clause. The override check now reads !has_positive_rate(&schedule) alone.

I did not delete the helper — it is now public API as is_all_zero. It is the §5.3 ALL-ZERO predicate: the definition a consumer's classifier needs to decide whether a catalog schedule is a false-zero candidate. This crate exists so consumers parse the same shape by construction, and deleting the predicate would have each of them reimplement it independently. As a private unused helper it would also have tripped clippy, so "remove it" and "export it" were the only two honest options.

The doc comment now states why the leading input/output condition exists, because it looks removable and is not:

/// §5.3's ALL-ZERO predicate for one parsed cost schedule.
///
/// At least one of `input` or `output` must be `Some(0)`, every present rate must be
/// `Some(0)`, and every tier rate must be zero. An all-`None` schedule is unpriced, not
/// zero; the leading `input`/`output` condition preserves that distinction.

That condition was proposed for removal in the previous review round. Without it, all_zero on an all-None schedule evaluates [].all(|r| r == 0) on an empty iterator — true — so unpriced schedules would report as zero-priced. There is now a test pinning it, and dropping the clause reddens it by name:

all_none_schedule_is_unpriced_not_zero ... FAILED

Note this is a predicate over one CostSchedule: no remap document, no catalog, no join. It is not a classifier, and this PR still ships none.

Second P3 — the coverage validator is now skipped once the count check has already failed, so a pure count drift reports as a count drift rather than as missing cells. The aggregation test was updated to match and still proves multiple independent failures are collected.

models.dev publishes cost:{input:0,output:0} for plan-billed lanes - 486
models across 60 providers in the 2026-08-13 snapshot. Those zeros are
correct as marginal cost and useless for routing: a spend report that prices
plan usage at $0 cannot answer what a call would have cost on that platform
without the plan.

This adds the document format and parser that overlays the catalog with
sourced rates for those ids, plus the conformance vectors that define what a
correct overlay does.

What is here:
- PaygRemapDoc and the entry kinds, with an exact provider-qualified key
  newtype that never falls back to a bare model name - that fallback silently
  compares a reseller id against the origin provider's price
- a fallible parser with 12 error variants, all reachable and tested
- is_all_zero, the normative ALL-ZERO predicate, exported so consumers do not
  each reimplement it
- a conformance runner generic over the join, with zero implementations of
  that join in this crate
- two vector corpora under tests/golden/, following the pattern in
  cortexkit-store-types and cortexkit-cache-core

What is deliberately absent: the classifier, and the canonical data document.
The failure taxonomy is still moving - it grew a third mode after one review
round, four matrix cells after another, and had its priced column
restructured after a third - so pinning it to this crate's semver surface is
premature. A cfg(test) reference implementation would be worse: as the only
executable join in the tree it becomes the de facto normative one. The crate
header says types and parsing only, no bundled data, so payg-remap.json is
not here either; both placement questions belong to the maintainer.

Two gates, and only one runs here. The parse gate is executed and proven: all
14 guards were mutation-tested in two classes - deleted, and narrowed to
check less - and each reddens a named vector. The classification suite is
complete and cell-referenced but does not execute here, because there is
nothing to execute it against; 17 of 31 mutation rows are shipped and unrun
until a classifier exists.

The narrowing class is why that distinction matters. A removal-only sweep
reported 14/14 green while five guards survived narrowing, every one correct,
load-bearing, and untested - including a provenance filter that had never
executed at all, because every vector omitted the field and the lookup
short-circuited before reaching it.

Each classification vector carries a cell reference naming the matrix cell it
derives from, and a constant CELL_CONTRACT table asserts every vector's
outcome against the matrix. A vector that contradicts its cited cell is then
catchable by reading rather than by execution.

Additive: no existing type, function, or test changes. The only deletion is
the version line, 0.2.0 to 0.3.0.

Refs cortexkit/astrocyte#3
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