Files
smart-org/apps/api/app/application/container.py
T
mjalmoudy94 72408a0a4f feat(delivery): production deploy over SSH/SFTP/FTP/FTPS (v2 phase A3-5)
The one path that touches a customer's server, and the one the platform cannot
undo. It is human-triggered, confirmed and audited; there is deliberately no
code path from a pipeline run into it (D13).

- deploy_ssh.py: SshDeployer. Release lands in releases/<ts>-<sha7>/, the
  post-deploy command runs with cwd there, and only then does `current` move —
  atomically (relative symlink + mv -T). A failed hook or extraction never moves
  it, so the previous release keeps serving. Host key is pinned on first success
  (TOFU); a mismatch is a hard failure. Prunes to the newest five.
- deploy_ftp.py: FtpDeployer. FTP has no atomic switch and no rollback except a
  backup, so a backup is mandatory: every file about to be overwritten is
  archived under ARTIFACTS_ROOT before anything is uploaded. The artifact is
  extracted, then the build dir (dist/build/public/out) is chosen — a static
  deploy must not publish the repository. Archive members are sanitised: absolute
  paths, `..` and escaping links are refused.
- deploy_target.py: the tester that dispatches on `kind`.
- deploy_service.py + production_deploys (migration a3b4c5d6e7f4) + 4 routes.
  One deploy per project at a time; the org slug must be typed back; the target
  summary reports has_secret, never the secret.

Tests: 499 API (+26) with fake SSH/FTP transports; ruff, mypy, alembic clean.

Report: docs/plan/reports/agent-3-phase-5.md
2026-10-14 01:16:23 +03:30

183 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_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_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",
]