Measured (scratch, reverted): with the graph-fed cross-TU candidate
expansion disabled, finding fingerprints stay exact (fold 0fec78e5...,
yams 247523c5..., status ok) and securityGraphCandidateFunctions
returns an empty set (expanded=0) for these manifests. The 18.8s graph
build is pure overhead for the expansion path here. Remaining graph
consumer is summary priming; S0b tests whether its SCC-ordered eager
prepass can be dropped in favor of the lazy on-demand summary path.
README 'Performance architecture' documents the measurement-first,
gate-guarded optimization loop and the roadmap decision: collapse the
double per-TU parse into single-pass graph building over PCH/module
caching, chosen specifically to preserve portability. The decision
doc records the option-2 scoping: the graph->detector dependency map
(candidate expansion, summary priming, cross-TU property priming) and
the open memory-vs-parse tension to resolve before implementation.
Iteration 12 of the fold optimization loop
(docs/research/optimization-loop-2026-07-30.md).
Implementation of the recorded design began (~7 files plumbed) and
collapsed on an ordering dependency: assembleProjectCallGraph runs
BEFORE the main TU pass because the TU pass consumes the graph
(graph_security_candidates cross-TU expansion; summary priming from
the call graph). In-pass collection cannot serve the same scan —
the TU pass needs the graph the contributions would feed. Reversing
the order breaks candidate expansion (finding fingerprints would
change), and keeping every TU AST alive across both passes is
infeasible (~1.4 GiB RSS for the fold self-scan).
All plumbing reverted; production byte-identical. The ~16.9s
no-index exhaustive build is the inherent cold-run price of
cross-TU facts; target closed.
Iteration 11 of the fold optimization loop
(docs/research/optimization-loop-2026-07-30.md).
Records the campaign iteration ledger (10 iterations, fold self-scan
wall 251.7s -> 86.2s, 66% reduction, fingerprints exact throughout)
and the completed design for the deferred in-pass graph contribution
collection (the no-index exhaustive call-graph build's ~16.9s
re-parse): expose buildContributionFromContext, accumulate
contributions in the main TU pass, feed prebuilt contributions to
assembleProjectCallGraph when they cover the shard's files and no
project index is open. Equivalence argument and worst-case fallback
preservation documented; implementation is the next slice.
No production code change.
Iteration 1 of the fold optimization loop
(docs/research/optimization-loop-2026-07-30.md, workflow-driven
measure->select->implement->validate->checkpoint).
Baseline measured (tests/benchmarks/runs/optimization-2026-07-30/):
- fold self-scan median 251.7s, peak RSS 1390.5 MiB; sql-injection
detector 40.37% of analyze share (101.4s median) with 0 Z3 queries
- yams self-scan median 10.7s, peak RSS 489.9 MiB
Implement attempt: shared thread-local scratch Z3 context for
summarizeLeafFunctionSymbolically (stack samples showed Z3 ast_table
init from the ensureSymbolicSummary chain dominating walk time).
REJECTED by the new characterization test: Z3 hash-consing collapses
same-named parameters across functions and summary composition
silently degrades. Reverted; regression test retained plus rationale
comment at the reverted site.
Also documents: SummaryStore::lookupOrCompute returns pointers into
its internal map (invalidated by clear()); open finding on
load-sensitive summary recomputation under CPU saturation.
No production behavior change; fold:symbolic_summary 10/10 green,
focused suites green, git diff --check clean.