Skip to content

Findings Persistence

This page covers how findings flow from vega-core audit output through SharedTriageService deduplication to backend database records.

Finding lifecycle

flowchart TD
    A[Agent output] -->|parsed by AuditStage| B[Raw finding]
    B -->|finding_updated event| C[vega-backend event handler]
    C -->|upsert| D[(Postgres findings table)]
    B -->|vega.raw_findings artifact| E[(S3)]
    B -->|ScanTransition| F[SharedTriageService.ingest]
    F -->|DeduplicatedBugBatch| G[CoreResult.data.deduplicated_bugs]
    G -->|finding_deduped event| C
    C -->|upsert bug record| D
    D --> H[Dashboard findings view]

Raw findings (audit output)

AuditStage emits one finding_updated event per finding:

{
  "kind": "finding_updated",
  "payload": {
    "finding_id": "component_id/hash",
    "title": "SQL Injection in query builder",
    "severity": "high",
    "bug_class": "sql_injection",
    "file_path": "src/db/query.py",
    "line_start": 87,
    "component_id": "db/query_builder",
    "scan_id": "scan_xyz"
  }
}

Backend upserts finding records from this event. The finding_id is stable for a given component and finding signature.

Deduplication via SharedTriageService

After all audit components complete, ScanTransition calls SharedTriageService.ingest(context, batch). The service:

  1. Receives raw findings from RawFindingBatch.
  2. Groups findings by bug signature (file path + bug class + line range).
  3. Assigns stable bug_id values to deduplicated bugs.
  4. Returns a DeduplicatedBugBatch with unique bugs.
  5. Emits finding_deduped events for backend to record associations.

The deduplicated bugs are included in CoreResult.data["deduplicated_bugs"] and written to the vega.deduplicated_bugs artifact.

Verification status

After verify_and_patch runs, VerifyStage emits finding_verified events:

{
  "kind": "finding_verified",
  "payload": {
    "finding_id": "bug_id",
    "status": "verified",
    "confidence": "high",
    "triggerable": true,
    "root_cause": "Missing authorization check..."
  }
}

Backend updates findings.verification_status from this event.

Possible verification statuses:

Status Meaning
null Not yet verified
"verified" Reproducible — eligible for patch
"not_verified" Agent could not reproduce
"insufficient_evidence" Unclear — needs review

Patch status

After PatchStage completes, patch_finding_completed events report per-bug patch results:

{
  "kind": "patch_finding_completed",
  "payload": {
    "finding_id": "bug_id",
    "status": "accepted",
    "confidence": "high"
  }
}

And a final patch_created event with the artifact ref:

{
  "kind": "patch_created",
  "payload": {
    "patch": {"uri": "s3://...", "kind": "patch", ...},
    "accepted_patch_count": 1,
    "rejected_patch_count": 0,
    "skipped_unverified_count": 0
  }
}

For first-class patch status in the UI, backend should add patch fields to the findings table: patch_status, patch_artifact_ref, patch_result, patch_updated_at.

Multi-shard deduplication

When multiple scan runner containers process different component shards, each runner produces its own raw findings. Backend is responsible for aggregating these and running a final deduplication pass, or coordinating a shared triage process before final bug records are committed.

The current in-process SharedTriageService handles single-runner scans automatically. Multi-runner deduplication coordination is a backend responsibility.

Debugging missing findings

  1. Was raw_findings_ready emitted? Check finding_count in the payload.
  2. Was finding_updated emitted per component? If not, the audit stage may have had parse errors — check agent_log events.
  3. Was raw_findings_batch_received emitted by triage? If not, check ScanTransition wiring.
  4. Check vega.raw_findings artifact in S3 / local store.
  5. Check CoreResult.data["deduplicated_bugs"] length.
  6. If findings appear in raw but not in deduplication, check triage logic and bug signature matching.