Events and Findings
Events and findings are the two outputs of a scan. They serve different purposes: events are a running log of what happened, findings are durable security issues for human review.
Events
An event is an append-only record emitted during a scan. Events are never edited or deleted — they're a log. They exist to:
- Drive the live scan feed in the dashboard (polling-based)
- Provide a full debug trace for operators after the scan finishes
Events have a kind field that describes what happened:
| Kind | Description |
|---|---|
scan_started |
The scan engine started running |
scan_progress |
Progress update (e.g., "auditing component 3 of 8") |
scan_log |
A debug log line from v16 or Codex |
finding_updated |
A security issue was found or updated |
scan_completed |
The scan finished successfully |
scan_failed |
The scan encountered an unrecoverable error |
scan_cancelled |
A user or operator stopped the scan |
Events have a JSON payload that varies by kind. For finding_updated, the payload contains the finding details. For scan_progress, it contains a message and progress percentage.
Key files: app/events/models.py, app/events/service.py
Findings
A finding is a structured, durable record of a specific security issue. Unlike events (which are logs), findings are meant for triage — users review them, mark them confirmed or dismissed, and track them over time.
Each finding has:
| Field | Description |
|---|---|
severity |
critical, high, medium, low, or info |
title |
Short description of the issue |
file_path |
Path to the affected file in the repository |
status |
open, confirmed, or dismissed |
evidence |
The scan engine's supporting reasoning |
scan_id |
The scan that produced this finding |
repository_id |
The repository it belongs to |
Key files: app/projects/models.py, app/projects/service.py
How findings are created
Findings don't come from a separate API call — they're automatically extracted from finding_updated events:
v16 scan engine
↓ emits finding_updated event
app/projects/v16_adapter.py
↓ maps to Vega event format, calls back into ProjectService
ProjectService.upsert_finding()
↓ creates or updates the finding record
PostgresStore (or JSON file)
↓ persisted
GET /v1/repositories/:id/findings
↓ frontend reads findings
Upsert semantics — if the scan engine reports the same logical finding twice (e.g., with updated confidence), the backend updates the existing record rather than creating a duplicate. The deduplication key is based on the finding's file path and title within a scan.
Where findings appear in the UI
| Page | What it shows |
|---|---|
| Repository findings page | All findings for one repository across all scans |
| Scan findings view | Findings from one specific scan |
| Project findings page | All findings across all repositories in a project |
| Findings inbox | Cross-project view of all open findings |
| Finding detail page | Full context for one finding including evidence |
Debugging
Events appear in logs but no findings show up:
1. Confirm v16 emitted finding_updated events — check v16-events.jsonl in the scan artifacts.
2. Check app/projects/v16_adapter.py — does it handle the finding_updated event kind from the version of v16 being run?
3. If using Postgres, confirm migration 004_findings_columns.sql has been applied.
4. Check the findings page filters — severity and status filters might be hiding results.
Findings exist but the UI shows nothing:
1. Open the findings page and clear all filters.
2. Check the API response directly: GET /v1/repositories/:id/findings
3. Confirm the frontend is using the correct project/repository ID.