feat(worker,admin): pricing façade, run-record scan, and the pure cost fold - #76
Merged
Merged
Conversation
# Conflicts: # specs/interfaces.md
…t fold (issue #53) Closes gaps 4 and 5's data layer for issue #53: aggregation and re-pricing, with no database, no new deps, and no second pricing table. - worker/src/pricing.mjs (exported ./pricing): the admin's ONLY road to pi-ai's rate tables -- listPricedModels, getPricedModel (own-key hardened: the generated catalog resolves __proto__ lookups through the prototype chain), isZeroRated, piAiVersion (resolved package's version read from disk; pi-ai exports no ./package.json), and reprice(quad, target). reprice builds a fresh Usage with a zeroed cost skeleton every call -- calculateCost mutates its argument in place and TypeErrors without one, both pinned by test -- and makes exactly one judgment: cacheWrite1h forwards only to anthropic targets (clamped to cacheWrite), folded short for everyone else, because the 1h premium is an Anthropic billing rule and applying it elsewhere would invent cost. Enumeration via the side-effect-free providers/all, never ./compat. The test file doubles as the pinned-artifact pricing-surface guard: mutation contract, tier threshold key (input+cacheRead+cacheWrite), opus-4-6 and codex rates, the 272k tier boundary, all-zero kimi/zai tables, and the exact resolved version -- a pin bump fails the BUILD, not the screen. - admin/src/read-model.mjs scanRunRecords: listRuns' sibling without the 50-clamp; retention-bounded, hard-capped at 92 days even under keep-forever; not a query surface. - admin/src/costs.mjs (pure, fs-free, purity-regex-tested with dayKey as the one permitted worker coupling): the fold where FACTS meet OPINIONS. Classification (metered/plan/zero-rated/estimated/seeded/ unknown) happens at fold time and is never stored, so editing subscriptions.json retroactively reclassifies history correctly. Every dollar is a typed {usd, class, floor, coverage} value; a sum stays metered only when every addend is; plan-covered rows carry their API-equivalent as an estimate, never $0.00; unknown-limit windows produce facts only, never burn-down; verdicts degrade to NO_BASELINE rather than compare against nothing; whatIfFlow seeds from the flow's own median quad, else the OQ-002 band labeled unmeasured -- always a band, never a point. Specs minted in-PR: DES-COST-FOLD-BY-SCAN (Rejected: analytics store, rollup/index files, a redis cost series, storing the classification) and INT-PRICING-EXPORT-CONTRACT. DES-RUN-HISTORY-FLAT-FILES-NO-DB unchanged, checked and leaned on. Tests: full suite 1546, 0 fail (21 pricing-guard, 19 fold, 5 scan new). Signed-off-by: Rob Boerman <robboerman@live.nl>
…a literal byte The per-(provider,model) row key joined its pair with a LITERAL 0x00 byte in the source, which is semantically identical to the u0000 escape at runtime and completely different to every tool that reads the file as text: file(1) classifies it as data, grep goes binary, and a reviewer's diff shows an opaque blob. The runner's meter spells the same separator as the six-character escape for exactly this reason; now both sides do. Signed-off-by: Rob Boerman <robboerman@live.nl>
# Conflicts: # specs/design.md # specs/interfaces.md
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.
The data layer for #53's gaps 4 and 5 — aggregation and what-if re-pricing with no database, no new deps, and no second pricing table. Stacked on #73 (usage ledger) and #74 (subscriptions file); their commits appear here until they merge, after which this PR's diff collapses to its own three parts.
worker/src/pricing.mjs(exported./pricing)The admin's only road to pi-ai's rate tables — the
budget/dayKeyanti-drift idiom instead of a fourth exact pin and a second drift axis.reprice(quad, target)builds a freshUsagewith a zeroed cost skeleton on every call (pi-ai'scalculateCostmutates its argument and TypeErrors without one — both pinned by test) and inherits pi-ai's tier selection and 1h premium. One judgment call, commented as such:cacheWrite1hforwards only toanthropictargets (clamped tocacheWrite), folded short for everyone else — the premium is an Anthropic billing rule; applying it elsewhere would invent cost. Catalog lookups are own-key hardened (the generated catalog genuinely resolves__proto__/toStringthrough the prototype chain — found and pinned). Enumeration via the side-effect-freeproviders/all, never./compat.The test file doubles as the pinned-artifact pricing-surface guard: mutation contract, tier threshold key (
input+cacheRead+cacheWrite), exact opus-4-6/codex rates, the 272k tier boundary, all-zero kimi/zai tables (correct data, not missing data), and the exact resolved version0.80.7— a pi-ai pin bump that reshapes pricing fails the build, not the screen.scanRunRecords+admin/src/costs.mjsscanRunRecordsislistRuns' sibling without the 50-clamp — retention-bounded, hard-capped at 92 days even under keep-forever, explicitly not a query surface.costs.mjsis pure and fs-free (purity-regex-tested;dayKeyis the one permitted worker coupling, so admin and worker cannot drift on UTC day boundaries) and is where facts meet opinions: classification happens at fold time and is never stored, so editingsubscriptions.jsonretroactively reclassifies history — correctly.The honesty rules, each pinned by a golden-fixture test:
{usd, class, floor, coverage}value;fmtCost(feat(admin): pure panel primitives for the costs surface — sparkline, typed cost formatting, line input, ASCII glyphs #75) is its only renderer;meteredonly when every addend is — one estimated addend demotes it visibly, with coverage;repriceat the declaredcounterfactualModel) as an estimate — never $0.00, never free ($0 (unrated)for unmatched zero-rate runs);unpriced/unresolved/metered:false/pre-Token accounting & optional token cap (unblocked by #21: pi 0.80.7 exposes per-turn usage) #25 records);hypothetical(vs actual metered spend), and NO_BASELINE whenever the comparison would be against nothing;whatIfFlowseeds from the flow's own median quad (one 10× outlier must not move the estimate); with zero ledgered history, the OQ-002 $0.5–$5 band labeledunmeasured (OQ-002)— always a band, never a point;ratesDriftedaccuses only when both versions are present.Specs (same PR)
Minted
DES-COST-FOLD-BY-SCAN(Rejected, each with its reason: embedded analytics store; rollup/index files — a second source of truth that goes stale on every sweep, retry overwrite, and subscriptions edit; a redis cost series — TTL'd enforcement state is not history; storing the classification — a record written under one subscriptions file lies under the next) andINT-PRICING-EXPORT-CONTRACT.DES-RUN-HISTORY-FLAT-FILES-NO-DBunchanged, checked and leaned on.Tests
Full suite 1546, 0 fail — 21 pricing-guard, 19 fold (purity regex first), 5 scan tests new. The COSTS view +
/dispatch costsPR that composes this with #75's primitives follows.