Canonical money and text for showback statements¶
Tenant showback statements (#2554) are recomputed independently by two parties and compared byte for byte, so every value that enters a statement needs exactly one encoding. bernstein.core.cost.showback_canonical fixes the three ground rules; everything downstream (line-item receipts, rollup projections, bernstein tenant showback / verify-statement) builds on them.
Money: fixed-scale integers¶
| Rule | Value |
|---|---|
| Scale | nano-USD (1 USD = 10^9), integer arithmetic only |
| Rounding | exactly once, at line-item creation (nano_usd_from_float), banker's rounding to 1 nano-USD |
| Totals | integer sums of exact line-item values; aggregation order can never change a digit |
| In payloads | string-encoded integers ("250000000") so every raw JSON number stays inside the I-JSON safe range |
| Operator text form | nano_usd_from_decimal_str / nano_usd_to_decimal_str; rendering always carries nine fractional digits |
Nano rather than micro scale: per-token line items on inexpensive models fall below one micro-USD, and a fixed scale must hold the smallest value the ledger can attribute without a second rounding step.
Text: reject, don't repair¶
Every string key and value in a statement must already be Unicode NFC. require_nfc rejects anything else instead of normalizing it, because a verifier must hash exactly the bytes it was handed, and normalization tables move between Unicode versions; rejection semantics are stable, and data accepted once stays NFC under later Unicode versions.
Statement bytes¶
canonical_statement_bytes validates the payload tree (no floats anywhere, NFC everywhere, integers inside the I-JSON range) and then encodes it with the shared RFC 8785 canonicalizer used for agent-card signing. Two writers with the same payload produce identical bytes; the statement hash is therefore comparable across machines and languages.
Cross-language vectors¶
tests/fixtures/showback/canonical_vectors.json carries the parse, render, float-bridge, NFC, and statement-hash vectors a non-Python implementation must reproduce. Extend the vectors file rather than adding cases in test code; tests/unit/test_showback_canonical.py binds the file to the Python implementation.