AUR-441 — aurumcode review --base <ref>: do not pay twice for the same file
Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.
Command
aurumcode review --base <ref> [--fail-on <level>] [--modelo <nome>] [--seguranca]
No new flag. On the --base path, a second invocation against an
unchanged file does not resend that file to the model, and the command
reports how many files were reused. --pr's review path (AUR-438) is
unaffected. Every other published behavior — output shape, exit codes,
--fail-on, --modelo, --seguranca — is unchanged.
Why this exists
The review engine sends the entire reviewed diff to the model in one
prompt, one Complete call per invocation
(internal/review.Reviewer.GenerateReview,
internal/prompt.PromptBuilder.BuildPrompt): there is no per-file send for
a cache to intercept. cmd/aurumcode (cmd/aurumcode/review_cache.go)
filters the diff down to the files internal/review/cache does not already
hold an entry for before calling GenerateReview; when nothing missed,
the call to the model is skipped entirely.
Cache key and storage
Each changed file gets its own cache key: a content digest of that file's own diff (path, language tag, every hunk — the only bytes the engine bases a finding on), combined with the model identity and a prompt-version constant. Any change to what the model would see for that file — a byte in the diff, the model, or a manually-bumped prompt version — misses.
The digest is computed over the raw, unredacted diff content, not the redacted form actually sent to the model: redacting first would collapse two genuinely different file versions that differ only inside a redacted span (a rotated secret, for instance) into the same key, masking a real content change. A digest is one-way, so nothing recoverable is persisted by computing it this way.
"Model identity" is not simply the provider's name:
- The offline fixture provider (
AURUMCODE_LLM_FIXTURE) reports the same name regardless of which canned response file is configured, but two different fixture files answer differently — so, for caching, they are two different models: the fixture file's own content digest is folded into the key wheneverAURUMCODE_LLM_FIXTUREis set. --limite(AUR-433) wraps the provider to pin the cost tracker's accounting key; that wrapper's resolved model key (llm.ModelResolver) is folded in too, so a metered and an unmetered request against identical content never share an entry — a metered call is guarded by a budget check an unmetered one never has.
A live, unwrapped, unfixtured provider's answer is determined by
provider.Name() alone, which is used verbatim.
Entries live one file per key under a cache directory:
AURUMCODE_CACHE_DIR, when set, names it exactly (even a relative path): two invocations that name the same directory reuse each other's entries.- Otherwise, a directory unique to this process invocation
(
$TMPDIR/aurumcode-review-cache-<pid>) — never a location a separateaurumcoderun could land on by coincidence, so a bare, unconfigured second run behaves exactly as if caching were off. This was a deliberate reversal from a first design that scoped the default to the reviewed repository (so a bare second run would reuse it automatically): that design broke AUR-433's already-published--limitecontract, whose own acceptance program runs the built binary several times in a row against one repository — once unmetered, twice at an identical--limiteceiling to prove byte-for-byte determinism, once refused — and asserts every call independently reaches the model and reports its own real, freshly-computed cost. A shared default would have served the second--limiteinvocation from the first's cache entry: no call, an "actual cost" of$0.0000instead of the real figure the first invocation printed, and a determinism assertion that can never pass again by construction, because the two invocations would no longer be doing the same work. Persisted reuse across separate invocations is therefore something the caller always asks for explicitly, viaAURUMCODE_CACHE_DIR— exactly what every proof of this card's own Outcome does.
A cache directory that cannot be created or used degrades the run to exactly the pre-AUR-441 behavior: every file sent, every run. Caching is a performance optimization, never a correctness gate — a write failure never fails an otherwise-successful review.
Output shape
Stdout is byte-identical whether or not caching engaged, in a cold run or a fully-reused one: a cache hit's findings are merged back into the same printed result a fresh call would have produced. The one visible signal is on stderr, and only when at least one file was actually reused:
aurumcode review: reused <N> file(s) from cache (not resent to the model)
This mirrors --fail-on's and --modelo's existing stderr-only notes: the
published stdout contract never grows a new line for a flag- or
state-dependent behavior.
--fail-on composes correctly with a cache hit: the gate reads the merged
result, so a reused finding still closes the gate exactly like a freshly
produced one. --seguranca's deterministic pass is unaffected — it always
scans the full diff and never consults the cache.
Exit codes
Unchanged from docs/specs/AUR-430.md, AUR-431.md, AUR-436.md:
| Code | Meaning |
|---|---|
0 |
Review ran; gate (if any) open. |
1 |
The review itself failed. |
2 |
Usage error. |
3 |
--fail-on gate closed (a cache-hit finding counts). |
Offline, secret-free example
cd tests/fixtures/repos/git-demo/repo.git
export AURUMCODE_LLM_FIXTURE=../../review/known-problem-response.json
export AURUMCODE_CACHE_DIR=/tmp/example-aurumcode-cache
aurumcode review --base HEAD~1
# config/demo-tokens.txt:4: [error] ... (rule security/hardcoded-secret: Hardcoded Secrets)
aurumcode review --base HEAD~1
# config/demo-tokens.txt:4: [error] ... (rule security/hardcoded-secret: Hardcoded Secrets)
# stderr: aurumcode review: reused 2 file(s) from cache (not resent to the model)
The second run's stdout is byte-identical to the first; the reuse note appears only on stderr.
Failure diagnosis
- A second run never reuses anything (stderr never shows
reused ...) — withoutAURUMCODE_CACHE_DIRset, every invocation gets its own process-scoped cache and always behaves like a cold run; setAURUMCODE_CACHE_DIRto the same directory across invocations to observe reuse. - A reused file's finding is missing from the output — check that
AURUMCODE_CACHE_DIRis actually writable and was not cleared between runs; a cache miss simply resends the file, it never drops a finding. - Two different
AURUMCODE_LLM_FIXTUREfiles against the same repository correctly produce two different results, never a stale answer from the other fixture: they are treated as two different models for caching. - A metered (
--limite) and an unmetered run sharing an explicitAURUMCODE_CACHE_DIRcorrectly never serve one another's cached answer: they are treated as two different models for caching (see "Cache key and storage"). --base HEAD(nothing changed) still calls the model exactly like before AUR-441; a zero-file diff is not treated as a full cache hit.
Security
- Whatever is written to a cache file already passed
internal/review.redactReviewResult(AUR-432) before this card's own code ever saw it; nothing model-authored reaches disk unredacted. - The cache key is a one-way digest of raw content, never the content itself, so nothing recoverable is persisted by key computation either.
AURUM_SECRET_CANARYnever appears in stdout, stderr, prompt, or any cache file;tests/acceptance/AUR-441.shandtests/integration/AUR-441.goassert this on every run, including walking every file the cache directory contains.
Validation
tests/acceptance/AUR-441.sh AC-001 (sealed:
./.board/bin/oci-run --profile bootstrap-readonly-v1 --card AUR-441),
with selectors TestAUR441 (tests/unit/AUR-441.go), IntegrationAUR441
(tests/integration/AUR-441.go), E2EAUR441 (tests/e2e/AUR-441.sh) and
AC-001-MUT-001 (invalidating reuse on every execution, which must make
the submission count stop dropping between runs).