# [DEF:backend.src.services.clean_release.preparation_service:Module] # @TIER: STANDARD # @SEMANTICS: clean-release, preparation, manifest, policy-evaluation # @PURPOSE: Prepare release candidate by policy evaluation and deterministic manifest creation. # @LAYER: Domain # @RELATION: DEPENDS_ON -> backend.src.services.clean_release.policy_engine # @RELATION: DEPENDS_ON -> backend.src.services.clean_release.manifest_builder # @RELATION: DEPENDS_ON -> backend.src.services.clean_release.repository # @INVARIANT: Candidate preparation always persists manifest and candidate status deterministically. from __future__ import annotations from datetime import datetime, timezone from typing import Dict, Iterable from .manifest_builder import build_distribution_manifest from .policy_engine import CleanPolicyEngine from .repository import CleanReleaseRepository from ...models.clean_release import ReleaseCandidateStatus def prepare_candidate( repository: CleanReleaseRepository, candidate_id: str, artifacts: Iterable[Dict], sources: Iterable[str], operator_id: str, ) -> Dict: candidate = repository.get_candidate(candidate_id) if candidate is None: raise ValueError(f"Candidate not found: {candidate_id}") policy = repository.get_active_policy() if policy is None: raise ValueError("Active clean policy not found") registry = repository.get_registry(policy.internal_source_registry_ref) if registry is None: raise ValueError("Registry not found for active policy") engine = CleanPolicyEngine(policy=policy, registry=registry) validation = engine.validate_policy() if not validation.ok: raise ValueError(f"Invalid policy: {validation.blocking_reasons}") classified, violations = engine.evaluate_candidate(artifacts=artifacts, sources=sources) manifest = build_distribution_manifest( manifest_id=f"manifest-{candidate_id}", candidate_id=candidate_id, policy_id=policy.policy_id, generated_by=operator_id, artifacts=classified, ) repository.save_manifest(manifest) candidate.status = ReleaseCandidateStatus.BLOCKED if violations else ReleaseCandidateStatus.PREPARED repository.save_candidate(candidate) return { "candidate_id": candidate_id, "status": candidate.status.value, "manifest_id": manifest.manifest_id, "violations": violations, "prepared_at": datetime.now(timezone.utc).isoformat(), } # [/DEF:backend.src.services.clean_release.preparation_service:Module]