Skip to content

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_sink callback
  • Mapping raw v16 event payloads to Vega's stable event/finding models
  • Passing the CancellationToken to 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:

  1. Update v16/adapter.py's function signature or V16Event model
  2. Update app/projects/v16_adapter.py to match
  3. Update tests in tests/test_v16_adapter.py
  4. 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.