boundgraph-core 0.1.0

Core primitives for the BoundGraph runtime: content-addressed digests, work orders, context, condensates, evidence, and the ledger.
Documentation
//! US-0603 — the shipped pricing catalogue, reconciled.
//!
//! `pricing.json` at the repository root is configuration: it is meant to be
//! edited when a vendor changes its rates, without a release. That is the point
//! of it, and it is also the risk — a hand-edited price with a dropped zero is
//! a ten-fold error in every ceiling the compiler derives, and nothing about a
//! JSON file makes that visible.
//!
//! So the catalogue is reconciled here. Each case states what a known call
//! should cost, computed by hand from the published per-million rates; a
//! catalogue edit that changes any of those figures fails CI rather than
//! silently repricing every flow in the workspace.
//!
//! The tolerance is a hair over a hundredth of a cent. It exists so a rate
//! quoted to more decimal places than a case anticipated does not fail the
//! build, and it is far tighter than any error worth catching: a dropped zero
//! is off by a factor of ten, not by a fraction of a cent.

use boundgraph_core::{AsOf, CostModel, Pricing, TokenUsage};
use rust_decimal::Decimal;

/// Closer than this and two prices are the same price.
fn tolerance() -> Decimal {
    "0.0001".parse().unwrap()
}

fn catalogue() -> Pricing {
    let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../pricing.json");
    let json = std::fs::read_to_string(&path)
        .unwrap_or_else(|e| panic!("{} should be readable: {e}", path.display()));
    Pricing::from_json(&json)
        .unwrap_or_else(|e| panic!("{} should be a pricing catalogue: {e}", path.display()))
}

fn usd(s: &str) -> Decimal {
    s.parse().unwrap()
}

fn model(id: &str) -> CostModel {
    catalogue()
        .get(id)
        .unwrap_or_else(|| panic!("{id} should be in the catalogue"))
        .clone()
}

fn assert_close(actual: Decimal, expected: Decimal, what: &str) {
    let drift = (actual - expected).abs();
    assert!(
        drift <= tolerance(),
        "{what}: priced {actual}, expected {expected} (drift {drift})"
    );
}

// ── the catalogue loads ─────────────────────────────────────────────────────

#[test]
fn the_shipped_catalogue_parses() {
    let pricing = catalogue();
    assert!(!pricing.is_empty(), "an empty catalogue prices nothing");
    assert!(pricing.get("claude-opus-5").is_some(), "the default model");
}

#[test]
fn every_entry_declares_a_window_and_an_output_ceiling() {
    for (id, cost) in catalogue().models() {
        assert!(cost.context_window > 0, "{id} declares no context window");
        assert!(cost.max_output > 0, "{id} declares no output ceiling");
        assert!(
            cost.max_output < cost.context_window,
            "{id} claims it can write more than it can read"
        );
    }
}

// ── reconciliation ──────────────────────────────────────────────────────────

/// One million uncached prompt tokens costs exactly the published input rate.
/// The simplest possible check, and the one a dropped zero fails first.
#[test]
fn a_million_prompt_tokens_costs_the_published_input_rate() {
    for (id, expected) in [
        ("claude-opus-5", "5.00"),
        ("claude-sonnet-5", "3.00"),
        ("claude-haiku-4-5", "1.00"),
        ("claude-fable-5", "10.00"),
    ] {
        let priced = model(id).price(&TokenUsage {
            input: 1_000_000,
            ..TokenUsage::NONE
        });
        assert_close(priced, usd(expected), id);
    }
}

#[test]
fn a_million_completion_tokens_costs_the_published_output_rate() {
    for (id, expected) in [
        ("claude-opus-5", "25.00"),
        ("claude-sonnet-5", "15.00"),
        ("claude-haiku-4-5", "5.00"),
        ("claude-fable-5", "50.00"),
    ] {
        let priced = model(id).price(&TokenUsage {
            output: 1_000_000,
            ..TokenUsage::NONE
        });
        assert_close(priced, usd(expected), id);
    }
}

/// A realistic call, priced by hand: 12k uncached prompt, 800 completion.
///   0.012 × $5 = $0.060
///   0.0008 × $25 = $0.020
#[test]
fn a_representative_call_reconciles_against_the_model() {
    let priced = model("claude-opus-5").price(&TokenUsage {
        input: 12_000,
        output: 800,
        ..TokenUsage::NONE
    });
    assert_close(priced, usd("0.08"), "a 12k/800 call on opus 5");
}

/// The same call with a warm cache: 11k of the prompt read back, 1k uncached.
///   0.011 × $0.50 = $0.0055
///   0.001 × $5    = $0.0050
///   0.0008 × $25  = $0.0200
#[test]
fn a_cached_call_is_priced_at_the_read_rate_not_the_input_rate() {
    let cost = model("claude-opus-5");
    let usage = TokenUsage {
        input: 1_000,
        output: 800,
        cache_read: 11_000,
        cache_write: 0,
    };
    assert_close(cost.price(&usage), usd("0.0305"), "a warm 12k/800 call");

    // And the saving is real: the same call cold costs more than twice as much.
    let cold = cost.price(&TokenUsage {
        input: 12_000,
        output: 800,
        ..TokenUsage::NONE
    });
    assert!(
        cold > cost.price(&usage) * Decimal::TWO,
        "caching should be worth having"
    );
}

// ── the rate relationships ──────────────────────────────────────────────────

/// The ratios every entry is derived from. A catalogue edit that breaks one of
/// these has almost certainly mistyped a rate rather than recorded a real
/// change in how a vendor prices caching.
#[test]
fn every_entry_keeps_the_documented_cache_ratios() {
    let tenth = usd("0.1");
    let write = usd("1.25");
    let long = usd("2");

    for (id, cost) in catalogue().models() {
        assert_close(cost.cache_read, cost.input * tenth, &format!("{id} read"));
        assert_close(cost.cache_write, cost.input * write, &format!("{id} write"));
        if let Some(extended) = cost.cache_write_long {
            assert_close(extended, cost.input * long, &format!("{id} long write"));
        }
    }
}

#[test]
fn a_read_is_cheaper_than_a_miss_and_a_write_is_dearer() {
    for (id, cost) in catalogue().models() {
        assert!(
            cost.cache_read < cost.input,
            "{id}: a read should be cheaper"
        );
        assert!(
            cost.cache_write > cost.input,
            "{id}: a write should be dearer"
        );
        assert!(cost.output > cost.input, "{id}: completions cost more");
    }
}

// ── freshness ───────────────────────────────────────────────────────────────

/// Every entry is dated, so the compiler can refuse a budgeted flow priced
/// against figures nobody has looked at in years (E0602).
#[test]
fn every_entry_is_dated() {
    let catalogue = catalogue();
    let ancient = AsOf::new(2020, 1, 1).unwrap();

    for (id, cost) in catalogue.models() {
        assert!(
            !cost.is_stale(ancient),
            "{id} is dated {}, which predates the catalogue's own existence",
            cost.as_of
        );
    }

    // And the staleness query works over the whole catalogue.
    let future = AsOf::new(2099, 1, 1).unwrap();
    assert_eq!(
        catalogue.stale(future).len(),
        catalogue.len(),
        "everything is stale against a far-future horizon"
    );
}

// ── round trip ──────────────────────────────────────────────────────────────

/// The catalogue is edited by hand, so it has to survive being read and written
/// back — a format that only parses one way is a format that rots.
#[test]
fn the_catalogue_round_trips() {
    let original = catalogue();
    let rewritten = Pricing::from_json(&original.to_json().unwrap()).unwrap();
    assert_eq!(original, rewritten);
}