feat(plane): budget metering (task 2)

MeteredClient wraps a ChatClient, sums the provider's usage rows per call,
prices them from a Model manifest's parameters (cache-hit, input, output per
million), and raises BudgetExhausted before a call once the usd or calls
ceiling is reached. A 401/402 or "Insufficient Balance" failure becomes
AccountError with the provider's message. An unpriced model reports usd None,
never zero, and refuses a usd budget; summary() gives the spend block of a
task's summary.json.

The two plane modules are registered as spec-owned in the module audit (their
consumers land in tasks 3-4) and the audit manifest is regenerated.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-09-29 17:41:09 +02:00
co-authored by Claude Fable 5.1
parent 152728c052
commit f460735a87
5 changed files with 384 additions and 5 deletions
@@ -1,7 +1,7 @@
{
"modules": 118,
"modules": 120,
"counts": {
"load_bearing": 109,
"load_bearing": 111,
"standalone_capability": 9
},
"orphaned": [],
@@ -400,6 +400,7 @@
"model.scan",
"model.specs",
"model.taint",
"plane.budget",
"preprocess",
"push.assistance",
"push.cache",
@@ -409,7 +410,8 @@
"push.reuse",
"push.runner",
"push.snapshot",
"push.vetoes"
"push.vetoes",
"tool_hunter"
]
},
{
@@ -479,7 +481,9 @@
"reason": "model-grounded-detection Req 8: the judge client, wired by model/judge.py in group 3",
"importers": [
"cli",
"push.assistance"
"plane.budget",
"push.assistance",
"tool_hunter"
]
},
{
@@ -645,6 +649,18 @@
"pair_gate"
]
},
{
"module": "plane",
"classification": "load_bearing",
"reason": "ai-service-plane Req 1-4: the service plane package; runner and reconciler integration in tasks 3-4",
"importers": []
},
{
"module": "plane.budget",
"classification": "load_bearing",
"reason": "ai-service-plane Req 2.3/2.4: metered chat client with usd and calls ceilings; task integration in tasks 3, 6",
"importers": []
},
{
"module": "policy",
"classification": "load_bearing",
@@ -1041,6 +1057,7 @@
"mapping",
"model.candidates",
"model.layout",
"model.scan",
"model.specs",
"pairs",
"semantic.cst",
@@ -1048,6 +1065,7 @@
"semantic.functions",
"semantic.ir",
"semantic.obligations.check",
"semantic.obligations.facts",
"semantic.obligations.operations",
"semantic.obligations.shapes",
"semantic.overlay",
@@ -1093,10 +1111,12 @@
"mapping",
"model.candidates",
"model.layout",
"model.scan",
"model.specs",
"pairs",
"semantic",
"semantic.obligations.check",
"semantic.obligations.facts",
"semantic.obligations.operations",
"semantic.obligations.shapes",
"semantic.overlay",
@@ -1293,7 +1313,8 @@
"classification": "load_bearing",
"reason": "reachable from gate, map_gate, pair_gate or the CLI",
"importers": [
"model.endpoint"
"model.endpoint",
"plane.budget"
]
},
{