v16 Contract
The v16 contract is the agreement between the backend and the scan engine about how they communicate. Keeping this boundary clean means v16's internal implementation can change without breaking the backend, and vice versa.
The boundary
Backend world │ v16 world
─────────────────────────────────┼───────────────────────────────────
app/projects/service.py │
↓ │
app/projects/v16_adapter.py ─────┼──→ v16/adapter.py
│ ↓
│ v16 internals
│ (planning, auditing, codex_runner)
←────────────┼── V16Event objects streamed back
app/projects/v16_adapter.py │
↓ │
ProjectService (events, findings)│
The backend should only import from v16/adapter.py. It should never import from v16's internal modules (v16.audit, v16.planner, etc.). This keeps the contract explicit and stable.
The v16 API
The entry point is v16/adapter.py's scan_source() function:
def scan_source(
source_root: str, # path to the source directory
threat_profile: dict, # structured threat profile
event_sink: Callable, # callback for each V16Event
cancellation_token: ..., # cooperative cancellation
settings: ..., # scan configuration
) -> None:
...
The backend adapter calls this function (indirectly via V16ServiceAdapter.scan_source()). The function runs synchronously until the scan completes, fails, or is cancelled. Events are delivered via the event_sink callback as they occur.
The event contract
v16 emits V16Event objects through the event_sink. The event has a kind field and a payload:
| Kind | When it's emitted | Payload contains |
|---|---|---|
scan_started |
v16 begins executing | scan metadata |
scan_progress |
After each planning/audit step | message, component, progress percentage |
scan_log |
Debug output from v16 or Codex | log level, message |
finding_updated |
When a security issue is discovered | finding fields (title, severity, file, evidence) |
scan_completed |
All components audited | summary statistics |
scan_failed |
Unrecoverable error | error message |
scan_cancelled |
Cancellation was requested | (empty) |
The backend adapter in app/projects/v16_adapter.py receives each event and:
1. Converts it to a Vega event record and persists it via ProjectService
2. For finding_updated events, also calls ProjectService.upsert_finding()
What the backend adapter does
app/projects/v16_adapter.py is responsible for:
- Loading the v16 module from the configured path (
VEGA_V16_ROOT) - Translating backend scan parameters into v16's expected format
- Providing the
event_sinkcallback - Mapping raw v16 event payloads to Vega's stable event/finding models
- Passing the
CancellationTokento v16 so cancellation works cooperatively
Changing the contract
If you need to change what the backend sends to v16, or what events v16 sends back:
- Update
v16/adapter.py's function signature orV16Eventmodel - Update
app/projects/v16_adapter.pyto match - Update tests in
tests/test_v16_adapter.py - Make sure no raw v16 internal structures leak into the frontend response models
Avoid leaking v16 internals into API responses
Frontend Pydantic response models should use Vega's own event and finding types. If a v16 event has fields that Vega doesn't have a concept for yet, don't blindly add them to the API — define the right abstraction first.