"""Operator API for Deployment Control Plane V1. All mutations accept a typed plan and an idempotency key. There is no endpoint for command execution, Compose upload, arbitrary host paths or raw secrets. """ from __future__ import annotations import re from typing import Annotated, Literal from fastapi import APIRouter, Depends, Header, HTTPException, Query, Request from pydantic import BaseModel, ConfigDict, Field from sqlmodel import Session from starlette.concurrency import run_in_threadpool from ..core.security import require_operator_request from ..db.session import get_session from ..schemas.deployment import DeploymentPlanV1 from ..schemas.forge_bootstrap import ForgeBootstrapPreflightV1 from ..schemas.forge_bootstrap_apply import ForgeBootstrapApplyV1 from ..schemas.forge_secret_delivery import ( ForgeSecretDeliveryRequestV1, ForgeSecretDeliveryResultV1, ) from ..schemas.forge_runtime_readiness import ForgeRuntimeReadinessResultV1 from ..schemas.forge_route_stage import ForgeRouteStageResultV1 from ..services.deployment_broker import DeploymentBrokerClient from ..services.deployment_control_plane import ( DeploymentControlError, DeploymentControlPlane, ) def _require_deployment_operator(request: Request) -> str: return require_operator_request(request) router = APIRouter( prefix="/api/deployments", tags=["deployments"], dependencies=[Depends(_require_deployment_operator)], ) class DeploymentRequestV1(BaseModel): model_config = ConfigDict(extra="forbid") plan: DeploymentPlanV1 operation: Literal["preflight", "apply", "rollback"] = "preflight" class WorkMeshPromotionPreflightRequest(BaseModel): model_config = ConfigDict(extra="forbid") revision: str = Field(pattern=r"^[0-9a-f]{40}$") DeploymentSession = Annotated[Session, Depends(get_session)] DeploymentIdempotencyKey = Annotated[str | None, Header(alias="Idempotency-Key")] DeploymentLimit = Annotated[int, Query(ge=1, le=250)] _FORGE_ROUTE_JOB_ID = re.compile(r"^forge-install-[0-9a-f]{64}$") _FORGE_ROUTE_IDEMPOTENCY_KEY = re.compile(r"^[A-Za-z0-9._:-]{8,128}$") async def _require_empty_forge_route_request(request: Request) -> None: lengths = request.headers.getlist("content-length") if ( request.query_params or len(lengths) > 1 or (lengths and lengths[0] != "0") or request.headers.getlist("transfer-encoding") ): raise HTTPException(status_code=400, detail="invalid_forge_route_request") try: async for chunk in request.stream(): if chunk: raise HTTPException(status_code=400, detail="invalid_forge_route_request") except HTTPException: raise except Exception: # noqa: BLE001 - transport detail is not part of this contract. raise HTTPException(status_code=400, detail="invalid_forge_route_request") from None def _idempotency_header(value: str | None) -> str: if not value: raise HTTPException(status_code=400, detail="Idempotency-Key is required for deployment operations.") return value.strip() @router.post("") def request_deployment( payload: DeploymentRequestV1, session: DeploymentSession, idempotency_key: DeploymentIdempotencyKey = None, ): try: return DeploymentControlPlane().execute( session, plan=payload.plan, idempotency_key=_idempotency_header(idempotency_key), operation=payload.operation, ) except DeploymentControlError as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc @router.post("/workmesh/promotions/preflight") def preflight_workmesh_promotion( payload: WorkMeshPromotionPreflightRequest, session: DeploymentSession, idempotency_key: DeploymentIdempotencyKey = None, ): from ..services.workmesh_promotion import ( WorkMeshPromotionError, WorkMeshPromotionService, ) try: return WorkMeshPromotionService().preflight( session, revision=payload.revision, idempotency_key=_idempotency_header(idempotency_key), ) except WorkMeshPromotionError as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc @router.post("/forge-bootstrap/preflight") def preflight_forge_bootstrap( payload: ForgeBootstrapPreflightV1, session: DeploymentSession, idempotency_key: DeploymentIdempotencyKey = None, ): from ..services.forge_bootstrap_preflight import ( ForgeBootstrapPreflightError, ForgeBootstrapPreflightService, ) try: return ForgeBootstrapPreflightService().preflight( session, payload, idempotency_key=_idempotency_header(idempotency_key), ) except ForgeBootstrapPreflightError as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc @router.post( "/forge-bootstrap/secrets", response_model=ForgeSecretDeliveryResultV1, ) def prepare_forge_bootstrap_secrets( payload: ForgeSecretDeliveryRequestV1, actor_identity: Annotated[str, Depends(_require_deployment_operator)], session: DeploymentSession, idempotency_key: DeploymentIdempotencyKey = None, ): """Deliver only the verified protected Forge inputs to the fixed controller.""" from ..services.forge_secret_delivery import prepare_forge_secret_delivery try: return prepare_forge_secret_delivery( session, profile=payload.profile, idempotency_key=_idempotency_header(idempotency_key), actor_identity=actor_identity, ) except ValueError as exc: # Service messages are fixed safe codes; never surface upstream text. raise HTTPException(status_code=409, detail="forge_secret_delivery_failed") from exc @router.post("/forge-bootstrap/first-install") def apply_forge_first_install( payload: ForgeBootstrapApplyV1, session: DeploymentSession, idempotency_key: DeploymentIdempotencyKey = None, ): from ..services.forge_bootstrap_apply import ( ForgeBootstrapApplyError, ForgeBootstrapApplyService, ) try: return ForgeBootstrapApplyService().apply( session, payload, idempotency_key=_idempotency_header(idempotency_key), ) except ForgeBootstrapApplyError as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc @router.post("/forge-bootstrap/first-install/{job_id}/resume") def resume_forge_first_install(job_id: str, session: DeploymentSession): from ..services.forge_bootstrap_apply import ForgeBootstrapApplyService result = ForgeBootstrapApplyService().resume(session, job_id) if result is None: raise HTTPException(status_code=404, detail="Forge first-install request not found.") return result @router.get("/workmesh/promotions/{promotion_id}") def get_workmesh_promotion(promotion_id: str, session: DeploymentSession): from ..services.workmesh_promotion import WorkMeshPromotionService result = WorkMeshPromotionService().refresh(session, promotion_id) if result is None: raise HTTPException(status_code=404, detail="WorkMesh promotion not found.") return result @router.get("/workmesh/promotions") def list_workmesh_promotions( session: DeploymentSession, limit: DeploymentLimit = 20, ): from ..services.workmesh_promotion import WorkMeshPromotionService return {"items": WorkMeshPromotionService.list_promotions(session, limit=limit)} @router.get("/forge-bootstrap/first-install/{job_id}") def get_forge_first_install(job_id: str, session: DeploymentSession): from ..services.forge_bootstrap_apply import ForgeBootstrapApplyService result = ForgeBootstrapApplyService().status(session, job_id) if result is None: raise HTTPException(status_code=404, detail="Forge first-install request not found.") return result @router.get( "/forge-bootstrap/first-install/{job_id}/readiness", response_model=ForgeRuntimeReadinessResultV1, ) def get_forge_first_install_readiness( job_id: str, request: Request, session: DeploymentSession, ): """Return only a fresh, fixed controller observation for a known install.""" content_lengths = request.headers.getlist("content-length") if ( request.query_params or len(content_lengths) > 1 or (content_lengths and content_lengths[0] != "0") or request.headers.getlist("transfer-encoding") ): raise HTTPException(status_code=400, detail="invalid_readiness_request") from ..services.forge_bootstrap_apply import ForgeBootstrapApplyService return ForgeBootstrapApplyService().current_readiness(session, job_id) @router.post( "/forge-bootstrap/first-install/{job_id}/route", response_model=ForgeRouteStageResultV1, description=( "Stages only the fixed Forge NPM host in a disabled state after fresh runtime and network checks. " "The request body must be empty; this operation does not issue certificates or activate routing." ), ) async def stage_forge_first_install_route( job_id: str, request: Request, actor: Annotated[str, Depends(_require_deployment_operator)], session: DeploymentSession, ): if _FORGE_ROUTE_JOB_ID.fullmatch(job_id) is None: raise HTTPException(status_code=404, detail="Forge first-install request not found.") await _require_empty_forge_route_request(request) keys = request.headers.getlist("idempotency-key") if len(keys) != 1 or _FORGE_ROUTE_IDEMPOTENCY_KEY.fullmatch(keys[0]) is None: raise HTTPException(status_code=400, detail="invalid_forge_route_idempotency_key") from ..services.forge_route_stage import stage_forge_route try: return await run_in_threadpool( stage_forge_route, session, job_id=job_id, idempotency_key=keys[0], actor_identity=actor, ) except Exception: # noqa: BLE001 - no controller/NPM details may reach the caller. raise HTTPException(status_code=503, detail="forge_route_operation_unavailable") from None @router.get( "/forge-bootstrap/first-install/{job_id}/route", response_model=ForgeRouteStageResultV1, description=( "Reads and reconciles the fixed disabled Forge host using NPM GET requests only. " "It never retries a host creation." ), ) async def get_forge_first_install_route( job_id: str, request: Request, actor_identity: Annotated[str, Depends(_require_deployment_operator)], session: DeploymentSession, ): if _FORGE_ROUTE_JOB_ID.fullmatch(job_id) is None: raise HTTPException(status_code=404, detail="Forge first-install request not found.") await _require_empty_forge_route_request(request) from ..services.forge_route_stage import reconcile_forge_route try: return await run_in_threadpool( reconcile_forge_route, session, job_id=job_id, actor_identity=actor_identity, ) except Exception: # noqa: BLE001 - no controller/NPM details may reach the caller. raise HTTPException(status_code=503, detail="forge_route_reconcile_unavailable") from None @router.get("") def list_deployments(session: DeploymentSession, limit: DeploymentLimit = 100): return {"items": DeploymentControlPlane().list_jobs(session, limit=limit)} @router.get("/delivery-status") def get_delivery_status(): """Expose the controller's bounded, secret-free runnerless delivery view.""" return DeploymentBrokerClient().delivery_status() @router.get("/{job_id}") def get_deployment(job_id: str, session: DeploymentSession): if job_id.startswith("forge-install-"): raise HTTPException( status_code=404, detail="Use the fixed Forge first-install status route for this job.", ) try: return DeploymentControlPlane().refresh(session, job_id) except DeploymentControlError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc