feat(corpus-seeded-mechanisms): reports name the mechanism, its known fix guard and pair provenance

Task 5.1 (Req 6.1, 6.3).

- reports.py: the markdown and SARIF writers take the mechanisms a run
  cites; a finding cites one through its mechanism:<id> tag (variant) or
  its merged overlay record's mechanism_id; markdown shows Mechanism,
  Known fix guard and Learned from pairs per finding plus a Mechanisms
  section, SARIF properties carry mechanism_id/summary/guard/pairs;
  variant findings keep suspicion, merged inventory findings keep their
  own evidence level; reports without mechanisms are unchanged.
- cli: _search_variants returns the cited mechanisms from the store and
  the scan passes them to both writers.

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 15:35:08 +02:00
co-authored by Claude Fable 5.1
parent 23aae79768
commit 624024e72a
4 changed files with 225 additions and 9 deletions
+6
View File
@@ -90,6 +90,12 @@ def test_variant_hit_merges_into_an_overlay_flow_at_the_same_call_site(tmp_path:
assert not any(f["finding_id"].startswith("variant:") for f in findings) # merged, not duplicated
manifest = json.loads((run_dir / "manifest.json").read_text())
assert manifest["variants"]["merged_into_overlay"] == 1 and manifest["variants"]["findings"] == 0
# Req 6.1 end to end: the merged inventory finding names the mechanism in both reports and keeps its own evidence level
report = (run_dir / "report.md").read_text()
assert f"- Mechanism: `{record.id}`" in report and "- Learned from pairs: `p`" in report
sarif = json.loads((run_dir / "report.sarif").read_text())
props = [item["properties"] for item in sarif["runs"][0]["results"] if item["properties"].get("mechanism_id") == record.id]
assert props and props[0]["mechanism_guard"] == "none" and props[0]["evidence_level"] != "suspicion"
def test_quick_scan_never_searches_variants(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: