A first scoping attempt (restrict analyzed regions to changed files) was reverted: it breaks a tested design invariant. The engine deliberately selects an unchanged region's question when a changed region reaches it (test_changed_dependency_reaches_unchanged_ranked_question; test_change_does_not_replace_ranker_selection). _changed_files feeds _evidence_pass only as contextFilter/contextPaths (itemise vs summarise), not to shrink the tier population. Any scope must keep changed regions PLUS the regions they reach. Also: increment 1 over-claimed. Increment 0 measured build=67s/query=0s but NOT the split of the ~236s middle (evidence-tier pass vs affected_context/context resolution vs _scope_work enumeration). Increment 2 must capture per_kind / other_seconds / affected_context timing and cut the dominant phase while preserving reach-into-unchanged -- instrument before fixing. Co-Authored-By: Claude Opus 4.8 <[email protected]>
7.2 KiB
Engine change-scoped analysis (G2 workstream)
Status: proposed / in progress (opened 2026-10-11). Owner: engine. Tracks a concrete lever for the engine-detection gap found while driving the G0 baseline. This is G2 ("improve what moves the unseen-repo numbers"), gated on the pool metric; it is NOT part of the G0 record-07 baseline (that runs on the quick-rule tier).
Problem (measured, not assumed)
On real unseen vulnerability changes the engine produces no finding — but the diagnosis (live, 300 s long-deadline replays) shows it is usually not a detection-capability gap:
- Clean Python injection change (
aws/bedrock-agentcore-sdk-python,install_packages, introducing, 3-file diff):wall=303 s, hit the deadline. The ranker found candidates (static_rank=1.0) butselected: []and every candidatedeferredwithreason: deadline_exhausted,analysis_not_started=deadline_exhausted,change_context_incomplete. Zero analyses ran. And the ranked candidates were repo-wide and unrelated to the push (auth.pyaccess_control/config_secrets), not the changedinstall_packages. The engine is trying to re-audit the whole repo and runs out of budget before analyzing the change. - Noisy last_touch config_secrets change (
jenkinsci/metrics-plugin, 14-file diff): finished in 206 s butcomparison_unknown+capability_unavailable+eligibility_provenance_mismatch+contributor-scan ... projection_unavailable. Multi-causal; correspondence was one part (fixed separately, commitc39fbb8), capability registry is Task 8.3.
Hypothesis
A pre-push hook should vet the code this push changed, not re-scan the whole repository. Seeding/scoping the
engine's analysis to the changed declarations and their dataflow neighborhood (the change_sites are already in
the replay spec) should let analysis actually run within budget instead of being deferred — which is where
detection is being lost, not in Joern's capability.
Anchors (confirm in increment 1)
src/openultrasast/rank.py— the static-evidence ranker (ranking_mode="evidence",existing-static-evidence-v1).src/openultrasast/mapping.py— builds theselected/deferredscope + the budget allocation that defers ondeadline_exhausted.src/openultrasast/push/policy.py,push/runner.py— consume the scope; change_sites arrive via the push input.benchmarks/unseen/replay_entry.py— carrieschanged_paths/declaration_pathsin the spec.
Increments (progress tracking)
-
0. Cost split confirmed (2026-10-11, timed live replay).
build_seconds=66.69(CPG build is cheap),query_seconds=0.0(no query ever executed), total 303 s (hit the deadline), 4,095 candidate questions allanalysis_not_started=deadline_exhausted,selected: [],regions_scanned=0. So of the 303 s, ~67 s built the CPG and ~236 s went to enumerating/ranking/preparing 4,095 repo-wide candidate questions for a 3-file change — the budget was spent before a single query ran. The CPG build is NOT the bottleneck; the over-broad candidate set is. Change-scoping is confirmed as the right lever, and because per-candidate prep includes the line-correspondence work, fewer candidates also cuts that cost (the two fixes compound). -
1. Located (2026-10-11). The over-enumeration is upstream of the scan, not inside it:
push/runner.py:345-348—head_regions = discovery(tip.root, …)discovers regions across the whole head tree (the ~4,095), independent of the change.push/runner.py:397-407— those regions are passed wholesale toscan_repository(..., change_context=context);change_contextis threaded in but never scopes the region set.model/scan.py:167and:364—[_identity(unit, r, f) for r in regions for f in r.families]enumerates all regions × families; withorder_by_evidence=True(runner.py:288/403) the scan evidence-tiers every pair before analysis — that pre-work is the ~236 s measured in increment 0, so the deadline hits atquery_seconds=0.- Primitive already present:
model/regions.py:279computes the changed-path set from the context;compare_targeted_base(runner.py:411) is a precedent for change-targeted work. - Fix locus (increment 2): restrict the analyzed region set to the change's declarations + dataflow
neighbourhood (filter
head_regionsbycontext.changed_pathsbefore the scan, or scope the enumeration insidescan_repository). Keep the CPG whole-repo so taint queries can still trace sources from unchanged code into the changed regions — only the question set is change-scoped.
Correction (2026-10-11), after a reverted first attempt — two things the naive filter got wrong:
- The engine already reaches into unchanged code by design, and it is tested. A changed
sourcethat reaches an unchangedsinkmust still select the unchanged sink's question (tests/test_model_change_context.py::test_changed_dependency_reaches_unchanged_ranked_question;test_change_does_not_replace_ranker_selectionpins that change context must NOT alter the ranker's selection)._changed_filesfeeds_evidence_passonly ascontextFilter/contextPaths— which locations to itemise vs summarise — NOT to shrink the tier population. A "changed files only" scope deletes the reach-into-unchanged behaviour. Any scope must keep changed regions plus the regions they reach (the affected/relationshipsneighbourhood), not changed files alone. - The 236 s is not yet split per phase. Increment 0 measured
build_seconds=66.69,query_seconds=0.0, total 303 s. The ~236 s middle covers the evidence-tier pass (_evidence_pass,per_kind["evidence"]), the context resolution /affected_contextgraph queries, and the_scope_workfact enumeration over all pairs — it was NOT measured which dominates. Increment 2 must first capture that split (per_kind,other_seconds,affected_contexttiming) and cut the dominant phase while preserving reach-into-unchanged. Instrument before fixing.
-
2. Seed analysis from the change. Prioritize (or restrict first-pass to) the changed declarations plus their call/data neighborhood over repo-wide static rank, within budget.
-
3. Soundness. Keep cross-file dataflow INTO the changed function (don't scope to literal changed lines); add tests; keep existing push tests green.
-
4. Live re-test. Re-run the bedrock change: does analysis now RUN and produce a finding on
install_packages(vsdeferred)? Re-run metrics-plugin. -
5. Measure the delta. Engine-arm sample on the pool (catch/false-alarm + engine-coverage rate) — needs the kube lane (blocked on the AX completion path) for scale.
Dependencies / notes
- Coverage
complete_within_scopealso needs the capability registry (Task 8.3) regardless of this work; the win here is findings moving fromcomparison_unknown/deferred to a decided novelty → advisory engine finding. - Implement directly and verify on the LIVE instrument (a fake-client/unit pass is not evidence the live engine changed); the kube-lane and store endpoints are operator-owned infra, not assumptions to make here.