Backend Components
The backend is a FastAPI application organized into six explicit layers following hexagonal architecture. This page maps the app/ directory and explains how the layers relate.
Layer overview
app/entrypoints/ ← process entry points: API server, workers, runners, maintenance, LLM proxy
app/api/ ← HTTP layer: thin route handlers that delegate to use cases via RuntimeContainer
app/application/ ← business logic: use-case classes with Command/Result types
app/domain/ ← pure Pydantic models, state machines, value objects (no I/O)
app/ports/ ← Python Protocol interfaces (no implementations — adapters implement these)
app/adapters/ ← implementations: Postgres, S3, SQS, ECS, Cognito, GitHub, Stripe…
app/composition/ ← wires adapters to ports; builds RuntimeContainer per process role
The HTTP layer (app/api/)
Route handlers in app/api/routers/ are intentionally thin. A route handler:
- Extracts and validates request data (FastAPI/Pydantic handles most of this)
- Resolves the
RuntimeContainerviaDepends(get_runtime_container) - Constructs a
*Commanddataclass from the request - Calls
runtime.use_cases.<use_case>.execute(command) - Maps the
*Resultto a response model and returns it
Business logic does not belong in route handlers. Conditional logic, state transitions, and storage writes belong in use-case classes in application/.
app/api/routes.py assembles all router modules and mounts them under /api (with /v1 as a backwards-compatibility alias).
The application layer (app/application/)
Use-case classes are the heart of the system. Each operation is a class with:
- A *Command dataclass for inputs
- A *Result dataclass for outputs
- An execute(command) -> result method that calls ports (never adapters directly)
Use-case modules are organized by domain subdomain:
| Subdomain | Use cases |
|---|---|
application/projects/ |
CreateWorkspaceProjectUseCase, UpdateWorkspaceProjectUseCase, DeleteWorkspaceProjectUseCase, GetWorkspaceProjectUseCase, ListWorkspaceProjectsUseCase, CreateRepositoryUseCase, DeleteRepositoryUseCase, ListRepositoriesUseCase, ListProjectRepositoriesUseCase, ListRepositoryOverviewsUseCase, GetRepositoryUseCase, SetRepositoryDefaultScopeUseCase, GetRepositoryTreeUseCase, GetRepositorySourceFileUseCase, ListWorkspaceFindingsUseCase, ListWorkspaceScansUseCase, GetProjectFindingsRollupUseCase, GetProjectScanActivityUseCase, ProjectCatalogFacade, EnqueueRepositoryIngestScheduler |
application/ingest/ |
EnqueueRepositoryIngestUseCase, ExecuteRepositoryIngestUseCase, RunQueuedRepositoryIngestUseCase, RunRepositoryIngestUseCase, CancelRepositoryIngestUseCase, RetryRepositoryIngestUseCase, ReconcileRepositoryIngestUseCase |
application/scans/ |
CreateScanUseCase, ExecuteScanUseCase, RunQueuedScanUseCase, ListRepositoryScansUseCase, PauseScanUseCase, ResumeScanUseCase, CancelScanUseCase, RetryScanUseCase, ReconcileScansUseCase, ScanLiveDetailUseCase, ScanActivityFeedUseCase, WorkerComponentViewUseCase, ProjectScanLogEventUseCase, MaterializeScanLogArtifactsUseCase, ReadScanLogArtifactUseCase, ReadScanActivityReadModelUseCase, BackfillScanLogReadModelUseCase, PlanningArtifactUseCase, ScanCapacityPolicy |
application/findings/ |
ListFindingsUseCase, GetFindingDetailUseCase, MutateFindingUseCase, NormalizeAndUpsertFindingsUseCase, FindingRollupUseCase, FindingVerificationUseCase |
application/artifacts/ |
RecordArtifactsUseCase, ListScanArtifactsUseCase, ListDebugArtifactsUseCase, CreateArtifactDownloadUrlUseCase, ReadGeneratedThreatModelUseCase, MaterializeGeneratedThreatModelUseCase, BuildCanonicalArtifactManifestUseCase |
application/billing_policy/ |
ScanEligibilityUseCase, ReserveScanUsageUseCase, SettleScanUsageUseCase, ScanKeyPolicy |
application/operations/ |
OperationsMaintenanceUseCase |
The domain layer (app/domain/)
Pure Pydantic models with no I/O dependencies. Includes:
- Records —
DomainModelsubclasses (e.g.,WorkspaceProject,ScanRecord,FindingRecord) - State machines —
domain/scans/states.py,domain/ingest/states.py,domain/repositories/states.py - Value objects — typed IDs, object references, enumerations
- Domain events —
DomainEventbase and event type definitions
The ports layer (app/ports/)
Python Protocol interfaces that the application layer depends on. Key ports:
| Port | Description |
|---|---|
ProjectStore |
CRUD for WorkspaceProject records |
RepositoryStore |
CRUD for Repository records |
SnapshotStore |
CRUD for SourceSnapshot records |
ScanStore |
CRUD for ScanRecord records |
FindingStore |
CRUD for FindingRecord records |
ArtifactStore |
CRUD for ArtifactRecord records |
EventStore |
Append-only DomainEvent store |
GenericRecordStore |
Document store for auxiliary records |
ScanQueuePort |
Enqueue and dequeue scan jobs |
RepositoryIngestQueuePort |
Enqueue and dequeue ingest jobs |
ScanRunnerLauncherPort |
Launch ECS scan runner tasks |
RepositoryIngestRunnerLauncherPort |
Launch ECS ingest runner tasks |
VegaCoreEnginePort |
Interface to the vega-core scan engine |
ObjectStoragePort |
Upload/download objects (S3 or local files) |
IdentityPort |
Validate tokens, look up users |
GitHubPort |
GitHub App API integration |
PaymentPort |
Stripe payment operations |
BillingPort |
Balance, ledger, and billing policy |
The adapters layer (app/adapters/)
Concrete implementations of ports. Each port has at least two implementations: one for local development and one for production.
| Port | Local implementation | Production implementation |
|---|---|---|
| Storage (all entities) | JsonRecordStore / InMemoryRecordStore |
PostgresRecordStore |
| Events | JsonEventStore |
PostgresEventStore |
| Objects | LocalObjectStorage |
S3ObjectStorage + ScanScopedS3Credentials (STS) |
| Scan queue | LocalScanQueue |
SQSScanQueue |
| Ingest queue | LocalRepositoryIngestQueue |
SQSRepositoryIngestQueue |
| Scan runner | LocalScanRunnerLauncher |
ECSScanRunnerLauncher |
| Ingest runner | LocalRepositoryIngestRunnerLauncher |
ECSRepositoryIngestRunnerLauncher |
| Identity | LocalIdentityAdapter |
CognitoIdentityAdapter |
| GitHub | InMemoryGitHubAdapter |
GitHubAppAdapter |
| Billing | StaticBillingAdapter |
Sub2APIBillingAdapter |
| Scan keys | InMemoryScanKeyAdapter |
Sub2APIScanKeyAdapter |
| Payments | InMemoryPaymentAdapter |
StripePaymentAdapter |
| Worker heartbeat | InMemoryWorkerHeartbeatAdapter |
CloudWatchWorkerHeartbeatAdapter |
| vega-core engine | LocalVegaCoreService |
VegaCoreEngineAdapter |
| Agent logs | LocalAgentLogSink |
S3AgentLogSink |
The composition layer (app/composition/)
The composition layer is responsible for building a fully-wired RuntimeContainer for each process role. Key components:
RuntimeSettings (composition/settings/) — Pydantic settings loaded from environment variables (VEGA_* prefix), with profile defaults (local, test, staging, production) and optional secrets from AWS Secrets Manager.
build_*_runtime() (composition/wiring/) — One builder function per process role:
- build_api_runtime() — for vega-api
- build_scan_worker_runtime() — for vega-scan-worker
- build_scan_runner_runtime() — for vega-scan-runner
- build_repo_ingest_worker_runtime() — for vega-repo-ingest-worker
- build_repo_ingest_runner_runtime() — for vega-repo-ingest-runner
- build_maintenance_runtime() — for vega-maintenance
Each builder reads the settings, instantiates the appropriate adapter implementations, and wires them together into a RuntimeContainer with all use cases.
Full package map
app/
├── main.py Compat shim → entrypoints.api.create_app()
├── llm_proxy/main.py Compat shim → entrypoints.services.llm_proxy
│
├── api/
│ ├── routes.py Assembles all routers under /api (alias: /v1)
│ ├── errors.py Structured error envelope helpers
│ ├── dependencies/
│ │ ├── container.py get_runtime_container FastAPI dependency
│ │ ├── current_user.py Bearer auth → CurrentUser
│ │ └── request_context.py user_id + role extraction helpers
│ └── routers/
│ ├── health.py GET /healthz, /readyz
│ ├── auth.py POST /auth/login, refresh, logout; GET /auth/config, /auth/me
│ ├── users.py POST /users/invitations
│ ├── api_keys.py API key CRUD
│ ├── projects.py WorkspaceProject CRUD + rollup views
│ ├── repositories.py Repository workflow (large router, many routes)
│ ├── scans.py Canonical scan routes
│ ├── findings.py Canonical finding routes
│ ├── artifacts.py Scan artifact routes
│ ├── billing.py Billing management (Stripe, promotions, spend limits)
│ ├── github.py GitHub App integration
│ ├── git_upload.py Temporary git-push remotes
│ ├── sessions.py Legacy upload/analyze flow
│ └── operations.py /ops/* routes
│
├── application/ Use-case classes (all business logic)
│ ├── projects/
│ ├── ingest/
│ ├── scans/
│ ├── findings/
│ ├── artifacts/
│ ├── billing_policy/
│ └── operations/
│
├── domain/ Pure Pydantic models (no I/O)
│ ├── base.py DomainModel base class (Pydantic v2)
│ ├── ids.py, object_refs.py, serialization.py
│ ├── artifacts/, billing/, events/, findings/, identity/
│ ├── ingest/, operations/, projects/, repositories/, scans/
│
├── ports/ Protocol interfaces (no implementations)
│
├── adapters/
│ ├── engine/vega_core/ VegaCoreEngineAdapter, LocalVegaCoreService
│ ├── events/{json,postgres}/
│ ├── identity/{local,cognito}/
│ ├── integrations/{github,stripe,sub2api}/
│ ├── logging/agent_logs.py
│ ├── objects/{local,s3}/
│ ├── operations/{local,cloudwatch}/
│ ├── queues/{local,sqs}/
│ ├── runners/{local,ecs}/
│ └── storage/{json,postgres}/
│
├── composition/
│ ├── container/ RuntimeContainer, use-case wiring
│ ├── settings/ RuntimeSettings, loader, profiles, secrets
│ └── wiring/ build_*_runtime() per process role
│
├── entrypoints/
│ ├── api/ create_app(), lifespan, middleware
│ ├── workers/ scan + repository_ingest worker loops
│ ├── runners/ scan + repository_ingest runner main functions
│ ├── maintenance/ reconcile, cleanup tasks
│ └── services/llm_proxy/ LLM proxy FastAPI app
│
└── storage/migrations/ 001–028 SQL migration files
Legacy sessions
The api/routers/sessions.py router and /api/sessions/ routes implement an older upload/analyze flow. New features should use the project/repository model. The legacy path is preserved for backward compatibility.