vega-core Contract
This page defines the exact boundary between vega-backend and vega-core:
how the backend invokes the engine, what it passes in, what it receives back,
and how the vega-core Python API is structured internally.
Integration boundary
vega-core is always run as a subprocess — never imported directly by the backend
process. The backend adapter (LocalVegaCoreService in adapters/engine/vega_core/)
spawns vega-core/tests/manual/e2e_scan_debug.py with CLI arguments, reads a
JSON event stream from stdout, and reads artifact JSON files from the artifact root
after the process exits.
flowchart LR
subgraph runner[ECS Runner / Local Process]
LSvc[LocalVegaCoreService\nadapters/engine/vega_core/]
Sub[subprocess:\ne2e_scan_debug.py]
Codex[Codex CLI]
end
subgraph vega_core[vega-core internals]
API[api/entrypoints.py\nplan_repo / run_scan / verify_and_patch]
Orch[StageOrchestrator]
Stages[Stages:\nplan, threat_model, audit, verify, patch]
end
ArtifactRoot[(artifact root\nlocal filesystem / S3)]
LSvc -->|spawn subprocess\nCLI args| Sub
Sub --> API --> Orch --> Stages
Stages -->|events → stdout| Sub
Sub -->|JSON lines| LSvc
Stages --> ArtifactRoot
Codex --> Stages
How the backend calls vega-core
VegaCoreEngineAdapter → LocalVegaCoreService
VegaCoreEngineAdapter (the port implementation) delegates to LocalVegaCoreService for all scan phases.
LocalVegaCoreService is used in both local development and ECS production — the "Local" in the name refers
to running on the same filesystem as the runner task, not that it is a development-only stub.
Each phase maps to a subprocess call with --enabled-stages:
VegaCoreEngineAdapter method |
subprocess --enabled-stages |
|---|---|
run_plan(...) |
plan |
run_threat_model(...) |
threat_model |
run_audit(...) |
audit |
run_verification(...) |
verify |
Subprocess CLI arguments
python e2e_scan_debug.py \
--target <source root directory> \
--artifact-root <artifact output dir> \
--repo-id <repository_id> \
--scan-id <scan_id> \
--codex-bin codex \
--model <model name> \
--max-workers-per-runner <N> \
--max-fast-runners-per-scan <N> \
--depth standard|quick|deep \
--enabled-stages plan,threat_model,audit \
--stream-json-to-stdout \
[--reuse-plan-artifact <path>] \
[--threat-model-strategy codex_direct|llm_bug_class_selection|none] \
[--audit-strategy default|variant_analysis]\
[--bug-class memory-safety ...] \
[--include-path src/ ...]
Event stream (stdout)
With --stream-json-to-stdout, the subprocess emits one JSON object per line on
stdout as events occur. Each line is a CoreEvent:
{
"kind": "scan_progress",
"stage": "audit",
"payload": { "completed": 3, "total": 10, "component": "src/parser.c" },
"occurred_at": "2026-01-01T12:00:00Z"
}
LocalVegaCoreService reads these lines and passes each to the EngineEventSink
callback, which stores them as DomainEvents.
Artifact output
After the subprocess exits (return code 0), artifacts are written as JSON files under
--artifact-root/<repo_id>/<scan_id>/:
| Artifact kind | File path | Contents |
|---|---|---|
plan |
plan/<hash>.json |
Component manifest (files grouped by component) |
threat_model |
threat_model/<hash>.json |
Threat model per component |
raw_findings |
raw_findings/<hash>.json |
Undeduped audit findings |
deduplicated_bugs |
deduplicated_bugs/<hash>.json |
Deduplicated BugRecord list |
audit_component_state |
audit_component_state/<hash>.json |
Per-component audit progress |
verification_evidence |
verification_evidence/<hash>.json |
Verification results |
patch |
patch/<hash>.json |
Patch / remediation results |
LocalVegaCoreService._engine_result_from_artifacts() reads these files and wraps them
in EngineResult(artifact_refs={...}). The backend then uploads each artifact to S3
and records an ArtifactRecord.
vega-core Python API (internal)
Within the subprocess, e2e_scan_debug.py calls vega-core's public Python API.
Backend developers working on the adapter need to understand this API to add or change
how arguments are mapped.
Entry points (api/entrypoints.py)
from api import configure_core_runtime, plan_repo, run_scan, verify_and_patch
configure_core_runtime
def configure_core_runtime(
*,
config: Any | None = None, # RuntimeConfig (from env if None)
event_sink: Any | None = None, # callable(CoreEvent) -> None
cancellation_token: Any | None = None,
) -> None: ...
Must be called once per process before any scan function.
plan_repo
def plan_repo(request: PlanRequest) -> CoreResult: ...
@dataclass(frozen=True)
class PlanRequest:
repo_id: str
source_snapshot: ArtifactRef
include_paths: tuple[str, ...] = ()
policy: RunPolicy = field(default_factory=RunPolicy)
metadata: Mapping[str, Any] = field(default_factory=dict)
Returns: CoreResult with artifacts["plan"] = ArtifactRef to the plan JSON.
run_scan
def run_scan(request: ScanRequest) -> CoreResult: ...
@dataclass(frozen=True)
class ScanRequest:
repo_id: str
scan_id: str
source_snapshot: ArtifactRef
plan_artifact: ArtifactRef | None = None
intent: ScanIntent = field(default_factory=ScanIntent)
execution: ScanExecution = field(default_factory=ScanExecution)
policy: RunPolicy = field(default_factory=RunPolicy)
metadata: Mapping[str, Any] = field(default_factory=dict)
@dataclass(frozen=True)
class ScanIntent:
include_paths: tuple[str, ...] = ()
bug_classes: tuple[str, ...] = ()
depth: Literal["quick", "standard", "deep"] = "standard"
variant_context: str | None = None
@dataclass(frozen=True)
class ScanExecution:
threat_model_strategy: str = "codex_direct"
audit_strategy: str = "default"
threat_model_artifact: ArtifactRef | None = None
audit_resume_artifact: ArtifactRef | None = None
audit_resume_policy: Literal["fresh", "skip_completed"] = "fresh"
assigned_component_ids: tuple[str, ...] = ()
audit_shard_id: str | None = None
Returns: CoreResult with:
- artifacts["threat_model"], artifacts["raw_findings"], artifacts["deduplicated_bugs"]
- data["selected_plugin"], data["plugin_selection"]
verify_and_patch
def verify_and_patch(request: VerificationRequest) -> CoreResult: ...
@dataclass(frozen=True)
class VerificationRequest:
repo_id: str
verification_id: str
deduplicated_bugs: tuple[Mapping[str, Any], ...]
source_snapshot: ArtifactRef
plan_artifact: ArtifactRef | None = None
policy: RunPolicy = field(default_factory=RunPolicy)
metadata: Mapping[str, Any] = field(default_factory=dict)
Returns: CoreResult with:
- artifacts["patch"] — remediation results
- artifacts["verification_evidence"] — per-finding verification status
CoreResult
@dataclass(frozen=True)
class CoreResult:
status: StageStatus # "completed" | "cancelled" | "failed"
run_id: str
artifacts: Mapping[str, ArtifactRef] = field(default_factory=dict)
data: Mapping[str, Any] = field(default_factory=dict)
error: StageError | None = None
Always check result.status before accessing result.artifacts.
ArtifactRef
@dataclass(frozen=True)
class ArtifactRef:
uri: str # "file:///path/to/artifact.json" or "s3://bucket/key"
kind: str # e.g. "plan", "threat_model"
content_type: str # e.g. "application/json"
metadata: Mapping[str, Any] = field(default_factory=dict)
RunPolicy
@dataclass(frozen=True)
class RunPolicy:
enabled_stages: tuple[StageName, ...] = ()
agent_provider: str | None = None
model: str | None = None
max_workers_per_runner: int = 1
max_fast_runners_per_scan: int = 1
max_cost_cents: int | None = None
stage_timeout_seconds: Mapping[StageName, int] = field(default_factory=dict)
retry_count: int = 0
verify_before_patch: bool = True
metadata: Mapping[str, Any] = field(default_factory=dict)
Legacy fallback
VegaCoreEngineAdapter has a backward-compatibility fallback for older vega-core
deployments. If the injected service does not have run_plan() (new API), it
falls back to calling the old methods:
| New method | Legacy fallback |
|---|---|
run_plan() |
service.plan_source() |
run_threat_model() |
service.threat_model_source() |
run_audit() |
service.run_audit() → service.scan_source() |
run_verification() |
service.verify_findings() |
If no service is injected at all and no legacy_settings are provided, the adapter
raises PortDependencyError("vega-core engine service is not configured").
Event contract
CoreEvent values emitted during a scan (see stdout stream above):
| Kind | Action backend takes |
|---|---|
scan_started |
Update scan stage status |
scan_progress |
Update scan progress percentage |
scan_log |
Store debug log event |
finding_updated |
Upsert FindingRecord via NormalizeAndUpsertFindingsUseCase |
finding_verified |
Update finding verification status |
stage_started / stage_completed |
Stage-level progress bookmarks |
component_worker_started / _completed / _failed |
Per-component worker status |
scan_completed |
Engine audit loop finished |
scan_failed |
Engine encountered an error |
scan_cancelled |
Scan was cancelled cooperatively |
vega_core_started / vega_core_completed |
Subprocess lifecycle (from LocalVegaCoreService) |
Unknown kinds are stored as generic domain events. Do not discard unknown events; they may become first-class in future releases.
Contract stability
The subprocess interface (e2e_scan_debug.py CLI args and artifact layout) is the
stable backend boundary. The Python API (api/entrypoints.py, api/requests.py,
api/results.py) is stable for vega-core internal consumers.
Internal vega-core types (framework/, orchestrator/, stages/) are implementation
details and may change without notice.