Pular para conteúdo

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):

  1. Pricing. $ per 1000 input/output tokens, from AURUMCODE_LLM_INPUT_USD_PER_1K and AURUMCODE_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.03 per 1000 tokens).
  2. One model key. The chosen --modelo name, 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, and cost.Tracker.Reserve fails closed on an unpriced key, so without this every --limite run would refuse regardless of the limit chosen.
  3. One tracker. cost.NewTracker(limite, limite, {key: price}), the single place --limite's parsed value reaches enforcement, passed into llm.NewOrchestrator as its budget tracker.
  4. 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 --limite and the run still correctly refuses — see the margin note above). Raise --limite, or lower cost by pricing the actual model with AURUMCODE_LLM_INPUT_USD_PER_1K / AURUMCODE_LLM_OUTPUT_USD_PER_1K if 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 — --limite was 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.