# [DEF:backend.src.services.clean_release.report_builder:Module] # @TIER: CRITICAL # @SEMANTICS: clean-release, report, audit, counters, violations # @PURPOSE: Build and persist compliance reports with consistent counter invariants. # @LAYER: Domain # @RELATION: DEPENDS_ON -> backend.src.models.clean_release # @RELATION: DEPENDS_ON -> backend.src.services.clean_release.repository # @INVARIANT: blocking_violations_count never exceeds violations_count. # @TEST_CONTRACT: ComplianceCheckRun,List[ComplianceViolation] -> ComplianceReport # @TEST_FIXTURE: blocked_with_two_violations -> file:backend/tests/fixtures/clean_release/fixtures_clean_release.json # @TEST_EDGE: empty_violations_for_blocked -> BLOCKED run with zero blocking violations raises ValueError # @TEST_EDGE: counter_mismatch -> blocking counter cannot exceed total violations counter # @TEST_EDGE: missing_operator_summary -> non-terminal run prevents report creation and summary generation # @TEST_INVARIANT: blocking_count_le_total_count -> VERIFIED_BY: [counter_mismatch, empty_violations_for_blocked] from __future__ import annotations from datetime import datetime, timezone from uuid import uuid4 from typing import List from .enums import RunStatus, ComplianceDecision from ...models.clean_release import ComplianceRun, ComplianceReport, ComplianceViolation from .repository import CleanReleaseRepository class ComplianceReportBuilder: def __init__(self, repository: CleanReleaseRepository): self.repository = repository def build_report_payload(self, check_run: ComplianceRun, violations: List[ComplianceViolation]) -> ComplianceReport: if check_run.status == RunStatus.RUNNING: raise ValueError("Cannot build report for non-terminal run") violations_count = len(violations) blocking_violations_count = sum( 1 for v in violations if bool(getattr(v, "blocked_release", False)) or bool(getattr(v, "evidence_json", {}).get("blocked_release", False)) ) if check_run.final_status == ComplianceDecision.BLOCKED and blocking_violations_count <= 0: raise ValueError("Blocked run requires at least one blocking violation") summary = ( "Compliance passed with no blocking violations" if check_run.final_status == ComplianceDecision.PASSED else f"Blocked with {blocking_violations_count} blocking violation(s)" ) return ComplianceReport( id=f"CCR-{uuid4()}", run_id=check_run.id, candidate_id=check_run.candidate_id, generated_at=datetime.now(timezone.utc), final_status=check_run.final_status, summary_json={ "operator_summary": summary, "violations_count": violations_count, "blocking_violations_count": blocking_violations_count, }, immutable=True, ) def persist_report(self, report: ComplianceReport) -> ComplianceReport: return self.repository.save_report(report) # [/DEF:backend.src.services.clean_release.report_builder:Module]