A push to `dev` now becomes a live demo, and a failed build never takes the demo down. - Four tables (pipeline_runs, pipeline_steps, releases, app_runtimes) and their migration; alembic upgrade/downgrade/check all clean. - DeliveryService implements DeliveryHooks: on_app_created provisions the demo port + host + first build (idempotently); on_branch_pushed queues a run for `dev` and supersedes older *queued* runs, leaving a running one alone. - The 8-step pipeline. Blue/green is real: the replacement starts on a staging port, is healthchecked, and only then does the route move — then the previous container is stopped. A failure removes the replacement, releases its port and leaves the old container serving. - The demo port ledger is the DATABASE, not the router adapters' in-memory maps: the API and the delivery worker are separate processes. - Runs are claimed with a conditional UPDATE, so two workers never build the same run. - Live logs are batched (1 event / 500 ms, 200 lines) and redacted as a whole tail — per-chunk redaction misses a secret split across pipe reads, and the *stored* tail leaked until this was fixed. - delivery worker: drain the queue, then poll remotes for pushes made outside the engine. - 7 routes; reads need membership, actions need projects.manage and are audited. Tests: 415 API (+21, 1 docker-marked skipped); ruff, mypy, alembic check clean. Report: docs/plan/reports/agent-3-phase-3.md
181 lines
6.2 KiB
Python
181 lines
6.2 KiB
Python
"""Composition root for the application services.
|
|
|
|
This module builds every application service from a single ``AsyncSession``.
|
|
It exists so that the HTTP layer and the standalone workers share **one**
|
|
wiring: if the API and a worker ever disagreed about which adapter backs a
|
|
port, that would be a bug, and having two copies of the wiring is exactly how
|
|
such a bug appears.
|
|
|
|
Deliberately free of FastAPI: the HTTP dependency functions in
|
|
``app.interfaces.http.deps`` delegate here, and so do the workers in
|
|
``app.workers``. Nothing in the inner layers imports this module.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.application.container_delivery import (
|
|
build_delivery_hooks,
|
|
build_delivery_service,
|
|
build_deploy_target_tester,
|
|
build_repo_workspace,
|
|
)
|
|
from app.application.container_engine import build_workforce_bootstrap
|
|
from app.application.container_org import build_memory, build_model_grants
|
|
from app.application.services.billing_service import BillingService
|
|
from app.application.services.knowledge_service import KnowledgeService
|
|
from app.application.services.project_service import ProjectService
|
|
from app.application.services.sandbox_service import SandboxService
|
|
from app.application.services.workforce_service import WorkforceService
|
|
from app.infrastructure.adapters.registry import (
|
|
get_domain_router,
|
|
get_git_provider,
|
|
get_model_provider,
|
|
get_payment_provider,
|
|
get_sandbox_provider,
|
|
get_vector_store,
|
|
)
|
|
from app.infrastructure.repositories.ai_control import (
|
|
AiModelRepository,
|
|
ModelCredentialRepository,
|
|
RateCardRepository,
|
|
RoutingPolicyRepository,
|
|
)
|
|
from app.infrastructure.repositories.audit import UsageLedgerRepository
|
|
from app.infrastructure.repositories.billing import (
|
|
CreditWalletRepository,
|
|
InvoiceRepository,
|
|
PaymentRepository,
|
|
PlanRepository,
|
|
SubscriptionRepository,
|
|
)
|
|
from app.infrastructure.repositories.knowledge import (
|
|
DocumentChunkRepository,
|
|
DocumentRepository,
|
|
EmbeddingJobRepository,
|
|
KnowledgeSourceRepository,
|
|
RetrievalPolicyRepository,
|
|
)
|
|
from app.infrastructure.repositories.projects import (
|
|
PreviewDomainRepository,
|
|
ProjectGitCredentialRepository,
|
|
ProjectGitRepoRepository,
|
|
ProjectRepository,
|
|
)
|
|
from app.infrastructure.repositories.sandbox import (
|
|
CommandRunRepository,
|
|
PreviewUrlRepository,
|
|
ResourceLimitRepository,
|
|
SandboxSessionRepository,
|
|
SandboxTemplateRepository,
|
|
SnapshotRepository,
|
|
WorkspaceRepository,
|
|
)
|
|
from app.infrastructure.repositories.workforce import (
|
|
AgentTemplateRepository,
|
|
ArtifactRepository,
|
|
EmployeeRepository,
|
|
EmployeeToolPermissionRepository,
|
|
MessageRepository,
|
|
PromptVersionRepository,
|
|
RunRepository,
|
|
RunStepRepository,
|
|
TaskRepository,
|
|
ThreadRepository,
|
|
ToolCallRepository,
|
|
)
|
|
|
|
|
|
def build_project_service(session: AsyncSession) -> ProjectService:
|
|
"""Project + Git repo + preview domain, with the configured adapters."""
|
|
return ProjectService(
|
|
projects=ProjectRepository(session),
|
|
repos=ProjectGitRepoRepository(session),
|
|
credentials=ProjectGitCredentialRepository(session),
|
|
domains=PreviewDomainRepository(session),
|
|
git=get_git_provider(),
|
|
router=get_domain_router(),
|
|
)
|
|
|
|
|
|
def build_workforce_service(session: AsyncSession) -> WorkforceService:
|
|
"""Employees, threads, tasks and the run engine."""
|
|
return WorkforceService(
|
|
employees=EmployeeRepository(session),
|
|
templates=AgentTemplateRepository(session),
|
|
prompts=PromptVersionRepository(session),
|
|
permissions=EmployeeToolPermissionRepository(session),
|
|
threads=ThreadRepository(session),
|
|
messages=MessageRepository(session),
|
|
tasks=TaskRepository(session),
|
|
runs=RunRepository(session),
|
|
run_steps=RunStepRepository(session),
|
|
tool_calls=ToolCallRepository(session),
|
|
artifacts=ArtifactRepository(session),
|
|
models=AiModelRepository(session),
|
|
credentials=ModelCredentialRepository(session),
|
|
routing_policies=RoutingPolicyRepository(session),
|
|
rate_cards=RateCardRepository(session),
|
|
ledger=UsageLedgerRepository(session),
|
|
provider=get_model_provider(),
|
|
)
|
|
|
|
|
|
def build_billing_service(session: AsyncSession) -> BillingService:
|
|
"""Wallets, invoices, payments and the usage ledger."""
|
|
return BillingService(
|
|
wallets=CreditWalletRepository(session),
|
|
invoices=InvoiceRepository(session),
|
|
payments=PaymentRepository(session),
|
|
plans=PlanRepository(session),
|
|
subscriptions=SubscriptionRepository(session),
|
|
ledger=UsageLedgerRepository(session),
|
|
payment_provider=get_payment_provider(),
|
|
)
|
|
|
|
|
|
def build_knowledge_service(session: AsyncSession) -> KnowledgeService:
|
|
"""Knowledge sources, documents, chunks and the vector store."""
|
|
return KnowledgeService(
|
|
sources=KnowledgeSourceRepository(session),
|
|
documents=DocumentRepository(session),
|
|
chunks=DocumentChunkRepository(session),
|
|
jobs=EmbeddingJobRepository(session),
|
|
policies=RetrievalPolicyRepository(session),
|
|
vector_store=get_vector_store(),
|
|
)
|
|
|
|
|
|
def build_sandbox_service(session: AsyncSession) -> SandboxService:
|
|
"""The sandbox gateway: the only sanctioned path for running code."""
|
|
return SandboxService(
|
|
templates=SandboxTemplateRepository(session),
|
|
workspaces=WorkspaceRepository(session),
|
|
sessions=SandboxSessionRepository(session),
|
|
commands=CommandRunRepository(session),
|
|
snapshots=SnapshotRepository(session),
|
|
previews=PreviewUrlRepository(session),
|
|
limits=ResourceLimitRepository(session),
|
|
rate_cards=RateCardRepository(session),
|
|
ledger=UsageLedgerRepository(session),
|
|
projects=ProjectRepository(session),
|
|
provider=get_sandbox_provider(),
|
|
)
|
|
|
|
|
|
__all__ = [
|
|
"build_billing_service",
|
|
"build_delivery_hooks",
|
|
"build_delivery_service",
|
|
"build_deploy_target_tester",
|
|
"build_memory",
|
|
"build_model_grants",
|
|
"build_repo_workspace",
|
|
"build_workforce_bootstrap",
|
|
"build_knowledge_service",
|
|
"build_project_service",
|
|
"build_sandbox_service",
|
|
"build_workforce_service",
|
|
]
|