AUR-433 — aurumcode review --limite: know and cap what a review costs
Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.
aurumcode review --base <ref> --limite <usd> gives the user a hard USD
ceiling on what a single review run may spend calling the model. Before
the model is ever called, the command estimates the cost and prints it;
if that estimate exceeds --limite, the command refuses to call the
model at all — exit 1, nothing spent — instead of running the review
and finding out afterward. When the run proceeds, it also reports the
actual cost once the review completes. Without --limite, nothing
changes: output and exit codes are exactly as documented in
docs/specs/AUR-430.md, AUR-431.md, AUR-435.md and AUR-436.md.
Command
aurumcode review --base <ref> [--fail-on <level>] [--modelo <nome>] [--seguranca] [--limite <usd>]
| Flag | Required | Meaning |
|---|---|---|
--base <ref> |
yes | Unchanged from AUR-430: the ref to diff against HEAD. |
--fail-on <level> |
no | Unchanged from AUR-431. Composes freely with --limite. |
--modelo <nome> |
no | Unchanged from AUR-436. When set, cost is tracked against this model's key; the note and the cost lines both print. |
--seguranca |
no | Unchanged from AUR-435. Composes freely with --limite. |
--limite <usd> |
no | Maximum US dollars this run may spend. Must be a positive number; empty, non-numeric, zero or negative is a usage error (exit 2). Without it, no budget is enforced. |
How the estimate and the enforcement work
This card adds no cost-accounting logic of its own — it wires the
already-existing, already-tested internal/llm/cost.Tracker into
internal/llm.Orchestrator, which already checks a reservation against a
configured ceiling atomically, before ever invoking the provider
(cost.Tracker.Reserve, called inside Orchestrator.Complete ahead of
executeWithTimeout). All the cost-control logic lives in one new file,
cmd/aurumcode/cost.go; main.go only registers the flag and calls into
it, and redaction_wiring_test.go gains the new stderr lines to its
existing filter-identity pin (see Scope below):
- Pricing.
$per 1000 input/output tokens, fromAURUMCODE_LLM_INPUT_USD_PER_1KandAURUMCODE_LLM_OUTPUT_USD_PER_1K(both or neither — half a price is not a price) or, when neither is set, a documented default ($0.01/$0.03per 1000 tokens). - One model key. The chosen
--modeloname, or"default"without it. A small wrapper (fixedModelProvider) forces the orchestrator to resolve every request to this exact key — the offline fixture provider (review.FakeProvider) reports none of its own, andcost.Tracker.Reservefails closed on an unpriced key, so without this every--limiterun would refuse regardless of the limit chosen. - One tracker.
cost.NewTracker(limite, limite, {key: price}), the single place--limite's parsed value reaches enforcement, passed intollm.NewOrchestratoras its budget tracker. - One gate. That tracker, checked by the orchestrator before it ever calls the model. There is no second, independent check in this command: a redundant check would let the two disagree at the margin and let a defect in either hide behind the other.
The printed estimate is a pre-flight figure, not the number the
enforcement decision is made against. It prices the diff text already
available before the review starts, with the same $-per-1000-token
arithmetic and the same output-token cap
(review.DefaultConfig().MaxTokens - ReserveReply) the tracker itself
uses — but the actual gate, inside Orchestrator.Complete, prices the
final assembled prompt (system template plus the diff, redacted a second
time — see internal/review.redactDiff — immediately before that prompt
is built). This command deliberately never re-derives that prompt itself,
so the printed estimate can differ slightly from what the enforced check
sees; in the git-demo fixture the difference is a small fraction of the
total, which is dominated by the fixed output-token cap both figures
already share.
Both cost lines go to stderr, never stdout, exactly like the
--modelo selection note and the --fail-on gate note:
aurumcode review: estimated cost $0.0914, diff-only pre-flight (--limite $0.5000)
aurumcode review: actual cost $0.0181 (--limite $0.5000)
An over-limit run prints only the estimate, plus the refusal:
aurumcode review: estimated cost $0.0914, diff-only pre-flight (--limite $0.0001)
aurumcode review: refusing to call the model: the request's cost would exceed --limite $0.0001; nothing was spent (...)
The refusal message deliberately does not repeat the printed estimate.
Because the printed figure is diff-only and the enforced check prices the
larger, fully assembled prompt, there is a margin where the two disagree:
on the git-demo fixture with the default price, the diff-only estimate is
$0.0914, but the run still refuses for any --limite up to $0.10 and
only proceeds from $0.11 — a --limite $0.10 run prints
estimated cost $0.0914, diff-only pre-flight (--limite $0.1000) and then
refuses, which is correct even though $0.0914 < $0.10. Saying "the
request's cost" rather than restating a dollar figure keeps the two lines
from ever contradicting each other.
Exit codes
| Code | Meaning |
|---|---|
0 |
The review ran (with or without --limite, as long as the estimate fit). |
1 |
The review failed, unchanged from AUR-430/431/435/436, now including: the assembled request's cost would exceed --limite. The model was never called. |
2 |
Usage error, unchanged plus: an empty, non-numeric, zero or negative --limite; a malformed AURUMCODE_LLM_*_USD_PER_1K pair. |
3 |
Unchanged from AUR-431: the --fail-on gate closed. Cost is still reported. |
Example (no secrets, fully offline)
$ cat /tmp/response.json
{
"issues": [
{
"file": "config/demo-tokens.txt",
"line": 4,
"severity": "warning",
"rule_id": "security/hardcoded-secret",
"message": "A credential-shaped value was committed in plain text (DEMO_API_TOKEN)."
}
],
"summary": "The change commits plaintext credential-shaped values."
}
$ cd tests/fixtures/repos/git-demo/repo.git
$ AURUMCODE_LLM_FIXTURE=/tmp/response.json aurumcode review --base HEAD~1 --limite 0.50
aurumcode review: estimated cost $0.0914, diff-only pre-flight (--limite $0.5000)
aurumcode review: actual cost $0.0181 (--limite $0.5000)
config/demo-tokens.txt:4: [warning] A credential-shaped value was committed in plain text (DEMO_API_TOKEN). (rule security/hardcoded-secret: Hardcoded Secrets)
$ echo $?
0
A limit too small to afford this same review:
$ AURUMCODE_LLM_FIXTURE=/tmp/response.json aurumcode review --base HEAD~1 --limite 0.0001
aurumcode review: estimated cost $0.0914, diff-only pre-flight (--limite $0.0001)
aurumcode review: refusing to call the model: the request's cost would exceed --limite $0.0001; nothing was spent (...)
$ echo $?
1
(Without --limite, both runs above print only the finding line, byte
for byte the AUR-430 contract, on stdout — the cost lines and the
refusal all go to stderr.)
Failure diagnosis
- Exit
1,refusing to call the model: the request's cost would exceed --limite ...— the assembled request's cost is higher than the ceiling given (the printed "diff-only pre-flight" estimate can look smaller than--limiteand the run still correctly refuses — see the margin note above). Raise--limite, or lower cost by pricing the actual model withAURUMCODE_LLM_INPUT_USD_PER_1K/AURUMCODE_LLM_OUTPUT_USD_PER_1Kif the built-in default over-prices it. - Exit
2,--limite: must be a number of US dollars (got "...")/must be greater than zero/value must not be empty— the flag's value was missing, non-numeric, zero or negative. Give a positive number, or drop the flag entirely to run unmetered. - Exit
2,AURUMCODE_LLM_INPUT_USD_PER_1K is set but AURUMCODE_LLM_OUTPUT_USD_PER_1K is not(or vice versa) — only one half of a custom price pair was set; a missing half would price those tokens at$0. Set both, or neither to use the documented default. - No cost lines at all —
--limitewas not given. This is the unchanged, unmetered AUR-430 behavior, not a failure.
MUT-001 note
The skeptical mutation targets the one place --limite's parsed value
reaches enforcement — cost.go's buildCostTracker, the single
assignment ceilingUSD := limiteUSD that feeds cost.NewTracker —
inflating it far past the ceiling the user actually gave. The mutant then
admits an over-limit request straight through to the model: the offline
fixture's call counter (AURUMCODE_PROMPT_CAPTURE's file, absent on a
correct refusal) goes from absent to present for the exact same run
tests/acceptance/AUR-433.sh::nominal_case proves refuses, and
tests/integration/AUR-433.go proves the same defect with a literal
numeric count of HTTP requests reaching a loopback endpoint (zero,
correctly; nonzero, under the mutant). The mutation is applied only to a
scratch staged copy inside mutation_case; the committed source carries
no mutation hook, and the unmutated binary reproduces the exact GREEN —
refusal, zero calls — in the same run.
Scope
This card changes only cmd/aurumcode: one new file (cost.go) holding
all cost-control logic, the minimum wiring into main.go (the flag, its
validation, and the call sites into cost.go), and one extension to the
existing redaction_wiring_test.go pin (the new stderr lines added to the
list it already checks for filter-identity, per AUR-432). The review
pipeline — analyzer, prompt, parser, reviewer, providers, and
internal/llm/cost.Tracker itself — is byte-untouched, and every
contract published by AUR-430, AUR-431, AUR-432, AUR-435 and AUR-436
holds verbatim without --limite.