Problem
Every scan re-discovers, re-reads, re-parses, and re-analyzes the entire tree, even when one file changed. On CI (the primary deployment surface per the GitHub Action) consecutive runs on a PR typically touch a handful of files; the 2-minute max_runtime_ms default budget is spent re-doing identical work, and large monorepos brush against max_files/max_total_bytes limits unnecessarily.
Current behavior
- Pipeline:
authmap-discovery → parse_files_in_parallel[_selective] (crates/authmap-parsers/src/lib.rs) → adapters → analysis. No cache exists at any stage; there is no scan-scoping flag beyond the target path (crates/authmap-cli/src/main.rs).
authmap diff/baseline compare completed scan outputs but still require full re-scans to produce them.
Proposed implementation
Cache at the parse/adapter-output boundary; always re-run analysis. Cross-file features (Django include resolution, Next.js re-exports, mount/prefix composition, route→mutation linking) make caching final analysis results unsafe, but per-file parse trees are expensive and per-file adapter raw output (routes/evidence/mutations/diagnostics keyed to a single file) is deterministic given (file content, config, version).
- Cache layer
.authmap/cache/ (gitignored; document in docs/DATA_HANDLING.md) keyed by (content_hash, config_hash, authmap_version, adapter_set). Store serialized per-file adapter output (FrameworkAdapter raw emissions are already serde-serializable types in authmap-core).
- On hit: skip read+parse+adapter for that file, splice cached output into the analysis input. On miss: normal path, write-through.
- Invalidation caveat: adapters that read other files (Django URLconf include chains, Next.js re-export resolution) must either declare cross-file inputs (hash the resolved dependency set into the key) or be excluded from caching in v1 — start by caching only the leaf-file extraction stages and measuring, rather than promising full correctness for cross-file resolution.
--no-cache flag and cache = false config escape hatch; cache misses/corruption degrade silently to full scan.
- Diff-scoped scanning
authmap scan --changed-from <git-ref> (and --changed-files <path>... for non-git callers): resolve the changed set via git diff --name-only, force cache-miss for those files, and warm everything else from cache. This is sugar over the cache, not a separate mechanism — a diff-scoped scan without a warm cache just performs a full scan.
- Measurement
- Land after (or with) the benchmark harness so the win is quantified; report cache hit-rate in the scan diagnostics/summary so users can see it working.
Acceptance criteria
References
Problem
Every scan re-discovers, re-reads, re-parses, and re-analyzes the entire tree, even when one file changed. On CI (the primary deployment surface per the GitHub Action) consecutive runs on a PR typically touch a handful of files; the 2-minute
max_runtime_msdefault budget is spent re-doing identical work, and large monorepos brush againstmax_files/max_total_byteslimits unnecessarily.Current behavior
authmap-discovery→parse_files_in_parallel[_selective](crates/authmap-parsers/src/lib.rs) → adapters → analysis. No cache exists at any stage; there is no scan-scoping flag beyond the target path (crates/authmap-cli/src/main.rs).authmap diff/baseline compare completed scan outputs but still require full re-scans to produce them.Proposed implementation
Cache at the parse/adapter-output boundary; always re-run analysis. Cross-file features (Django include resolution, Next.js re-exports, mount/prefix composition, route→mutation linking) make caching final analysis results unsafe, but per-file parse trees are expensive and per-file adapter raw output (routes/evidence/mutations/diagnostics keyed to a single file) is deterministic given (file content, config, version).
.authmap/cache/(gitignored; document indocs/DATA_HANDLING.md) keyed by(content_hash, config_hash, authmap_version, adapter_set). Store serialized per-file adapter output (FrameworkAdapterraw emissions are already serde-serializable types inauthmap-core).--no-cacheflag andcache = falseconfig escape hatch; cache misses/corruption degrade silently to full scan.authmap scan --changed-from <git-ref>(and--changed-files <path>...for non-git callers): resolve the changed set viagit diff --name-only, force cache-miss for those files, and warm everything else from cache. This is sugar over the cache, not a separate mechanism — a diff-scoped scan without a warm cache just performs a full scan.Acceptance criteria
--changed-fromproduces output identical to a full scan on the same tree (correctness first).--no-cachebypasses it.docs/USAGE.md,docs/CONFIGURATION.md,docs/DATA_HANDLING.md(what is stored on disk) updated; CHANGELOG entry.References
crates/authmap-parsers/src/lib.rs(parse_files_in_parallel_selective)crates/authmap-cli/src/main.rs(scan command surface)docs/CONFIGURATION.md; prior perf issue Add performance budgets and large-repository safeguards #23