feat(corpus-seeded-mechanisms): structural variant search merged with overlay flows

Task 2.2 (Req 3.1-3.3, 3.6).

- variants.match_shapes: call sites whose trailing callee name and arity
  match a shape and whose source positions all carry a source of any
  kind; constants and arity mismatches never match.
- semantic/variant_search.py: search_tree over a tree's parsed files with
  the store's corpus shapes (bounded by max_mechanisms, language
  degradations recorded); hits_to_findings merges a hit into an overlay
  promote/coverage record at the same call site (mechanism_id, no
  duplicate) and otherwise emits variant:<id>:<path>:<line> at suspicion
  with the pair provenance in the rationale.
- OverlayRecord.mechanism_id (additive).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
This commit is contained in:
Marius Ciepluch
2026-09-05 14:40:15 +02:00
co-authored by Claude Fable 5.1
parent a7b3b36025
commit c1621777f9
3 changed files with 217 additions and 0 deletions
+1
View File
@@ -45,6 +45,7 @@ class OverlayRecord:
engine: str = "none"
language: str = ""
function: str | None = None # enclosing function when the file parsed (additive; pair-corpus-honesty)
mechanism_id: str | None = None # known mechanism whose shape matches this call site (additive; corpus-seeded-mechanisms)
def __post_init__(self) -> None:
if self.disposition not in DISPOSITIONS:
@@ -0,0 +1,118 @@
"""Variant search over a scanned tree and its merge with overlay flows (corpus-seeded-mechanisms, Req 3).
Depends on the shapes (``variants``), the store (``mechanisms``), the overlay records and ``StaticFinding``; the shape
module itself stays free of those imports.
"""
from __future__ import annotations
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, replace
from pathlib import Path
from ..findings import StaticFinding
from ..preprocess import FileTarget
from .facts import SemanticFacts
from .ir import parse_file
from .mechanisms import MechanismStore, corpus_mechanisms
from .overlay import OverlayRecord
from .variants import Shape, VariantHit, match_shapes
LANGUAGE_UNSUPPORTED = "variants_language_unsupported"
@dataclass(frozen=True)
class SearchResult:
hits: tuple[VariantHit, ...]
mechanisms_searched: int
files_searched: int
degradations: tuple[dict[str, object], ...] = ()
def search_tree(
root: Path,
targets: Sequence[FileTarget],
store: MechanismStore,
facts: SemanticFacts,
*,
max_mechanisms: int,
) -> SearchResult:
"""Variant search over the parsed files of a tree with the store's corpus shapes (bounded by ``max_mechanisms``)."""
records = corpus_mechanisms(store.load())[: max(max_mechanisms, 0)]
shapes: list[Shape] = []
ids: dict[str, str] = {}
for record in records:
assert record.shape is not None
shape = Shape.from_dict(record.shape)
shapes.append(shape)
ids[shape.key()] = record.id
if not shapes:
return SearchResult(hits=(), mechanisms_searched=0, files_searched=0)
hits: list[VariantHit] = []
files = 0
degradations: list[dict[str, object]] = []
unsupported: set[str] = set()
for target in targets:
try:
text = (root / target.path).read_text(errors="ignore")
except OSError:
continue
ir = parse_file(target.path, text, target.language)
if not ir.parse_ok:
if ir.reason == "language_unsupported" and target.language not in unsupported:
unsupported.add(target.language)
degradations.append({"stage": "variants", "reason": "variants_language_unsupported", "language": target.language})
continue
files += 1
hits.extend(match_shapes(ir, shapes, facts, mechanism_ids=ids))
return SearchResult(hits=tuple(hits), mechanisms_searched=len(shapes), files_searched=files, degradations=tuple(degradations))
def hits_to_findings(
hits: Sequence[VariantHit],
records: Sequence[OverlayRecord],
summaries: Mapping[str, tuple[str, tuple[str, ...], str]],
) -> tuple[list[StaticFinding], list[OverlayRecord]]:
"""Merge hits into overlay records at the same call site (Req 3.3); the rest become ``suspicion`` findings (Req 3.2)."""
by_site: dict[tuple[str, int | None], list[int]] = {}
for index, record in enumerate(records):
if record.disposition in {"promote", "coverage"} and record.sources and record.sinks:
by_site.setdefault((record.path, record.line), []).append(index)
updated = list(records)
findings: list[StaticFinding] = []
emitted: set[str] = set()
for hit in hits:
indexes = by_site.get((hit.path, hit.line))
if indexes:
for index in indexes:
if updated[index].mechanism_id is None:
updated[index] = replace(updated[index], mechanism_id=hit.mechanism_id)
continue
finding_id = f"variant:{hit.mechanism_id}:{hit.path}:{hit.line}"
if finding_id in emitted:
continue
emitted.add(finding_id)
summary, pairs, cwe = summaries.get(hit.mechanism_id, (hit.mechanism_id, (), ""))
taught_by = ", ".join(pairs) if pairs else "the mechanism store"
findings.append(
StaticFinding(
finding_id=finding_id,
path=hit.path,
title=f"Variant of known mechanism: {hit.sink_name}",
severity="medium",
confidence="low",
evidence_level="suspicion",
rationale=(
f"{summary}. Structural variant ({hit.source_kind} reaches {hit.sink_name}) of a mechanism learned from "
f"{taught_by}; suspicion until the overlay or the sandbox agrees."
),
line=hit.line,
function_name=None,
reachability_status="unknown",
reachability_evidence=[],
reachability_conditions=[],
tags=["variant", f"mechanism:{hit.mechanism_id}", *([f"cwe:{cwe}"] if cwe else [])],
ranking_priority=0.0,
)
)
return findings, updated