Auto-Fix Reconciliation
When you ship a fix, Vygl notices automatically: a previously-seen fingerprint that doesn’t reappear in a full scan is marked auto_fixed. Reconciliation is per-branch and per-scan-type, so each branch evolves independently and a SAST-only scan can’t accidentally close SCA findings.
When auto-fix runs
Section titled “When auto-fix runs”After a scan ingest, reconciliation runs when:
full_scan=true— the scan covered the entire repository, not a PR diff or a partial run.- The ingest specifies a branch — any branch, not just the project’s default. Each branch reconciles its own row state.
- Eligible scan types — only findings whose
scan_typeis in the ingest’sscan_typesare considered. A SAST-only full scan won’t close SCA, Secrets, IaC, or Malware rows. - Same origin —
origin=scaningests reconcileorigin=scanrows;origin=cve_watchingests reconcile their own. The two never cross. - Eligible status — only findings in
open,acknowledged, orfix_claimedare reconciled. Sticky decisions (fixed,false_positive,ignored,suppressed,auto_fixed) survive.
Per-row, the rule is simple: if the fingerprint isn’t in the new scan’s results, the row flips to auto_fixed. CVE Watch hourly ingests run their own reconciliation pass for origin=cve_watch rows, so an OSV advisory that’s withdrawn or a package that’s upgraded out of the vulnerable range closes its CVE-watch finding the same way.
Why per-branch?
Section titled “Why per-branch?”Each branch has its own row state for a given fingerprint, so reconciliation is naturally scoped: a feature-branch full scan auto-fixes its own findings without touching main, and a main full scan only acts on main’s rows. There’s no cross-branch flapping when developers branch and merge.
Why per-scan-type?
Section titled “Why per-scan-type?”Each engine knows what it scanned. Marking a finding auto_fixed because the scan that ran “didn’t include it” — when the engine for that scan type didn’t even run — would be a false positive on the platform. Per-scan-type gating fixes that.
Verifying a fix_claimed row
Section titled “Verifying a fix_claimed row”When a fix_claimed row’s fingerprint is missing from a qualifying scan, it’s promoted to auto_fixed and decorated with metadata.claim_attribution (claimed_by_user_id, claimed_by_actor_kind, claimed_at, verified_by_scan_id, verified_at). A finding.status_change.claim_verified audit event is emitted alongside the promotion — your fix-claim got verified by the next eligible scan.
If a fix_claimed row’s fingerprint does reappear, the row flips back to open with claim_disproven_at set and a finding.status_change.claim_disproven audit event. See Triage for the full lifecycle.
What’s preserved
Section titled “What’s preserved”When a finding flips to auto_fixed:
- Its history stays — first-seen scan, last-seen scan, every prior status transition.
- Its AI verdict stays in
metadata.ai_verification. - Its fingerprint stays — if the same issue is reintroduced, this row is the one that resurrects rather than a duplicate being created.
metadata.claim_attributionstays for verifiedfix_claimed → auto_fixedrows.
Reopening an auto-fixed finding
Section titled “Reopening an auto-fixed finding”If an auto_fixed finding’s fingerprint reappears, the row is automatically flipped back to open:
status_reasonis set to"Re-detected by scan {short_scan_id}".status_changed_byis cleared (the system, not a user, did this).- A
finding.status_change.auto_fixed_resurrectedaudit entry is emitted. - Prior triage history and AI verdict survive on the row’s metadata.
This handles the regression case cleanly — the same finding appears in the timeline as first_detected → auto_fixed → resurrected → ..., all on one row.