from __future__ import annotations from .models import ( DependencySignals, Finding, GateCheck, GateState, ReleaseGate, RepositoryMetrics, RepositoryPolicy, ScanCoverage, Severity, TestSignals, ) def build_release_gate( *, health_score: int, findings: list[Finding], metrics: RepositoryMetrics, tests: TestSignals, dependencies: DependencySignals, coverage: ScanCoverage | None = None, policy: RepositoryPolicy | None = None, ) -> ReleaseGate: policy = policy or RepositoryPolicy() active_findings = [item for item in findings if not item.suppressed] blockers = [item for item in active_findings if item.severity == Severity.BLOCKER] warnings = [item for item in active_findings if item.severity == Severity.WARNING] dependency_issues = dependencies.unbounded_dependencies + dependencies.unpinned_dependencies checks = [ GateCheck( key="security", label="Security basics", state=GateState.BLOCK if len(blockers) > policy.max_blockers else GateState.PASS, detail=( f"{len(blockers)} blocker finding(s), policy allows {policy.max_blockers}" if blockers else "No blocker-level secret/environment findings" ), ), GateCheck( key="tests", label="Tests" + (" (required)" if policy.require_tests else ""), state=( GateState.BLOCK if policy.require_tests and not metrics.test_files else GateState.PASS if metrics.test_files else GateState.WARN ), detail=( f"{metrics.test_files} test file(s) detected" if metrics.test_files else "Tests are required by repository policy" if policy.require_tests else "No test files detected" ), ), GateCheck( key="ci", label="Continuous integration" + (" (required)" if policy.require_ci else ""), state=( GateState.BLOCK if policy.require_ci and not tests.ci_validation_workflows else GateState.PASS if tests.ci_validation_workflows else GateState.WARN ), detail=( f"{len(tests.ci_validation_workflows)} validation workflow(s) detected" if tests.ci_validation_workflows else f"{len(tests.ci_deploy_only_workflows)} deploy-only workflow(s) do not provide validation evidence" if tests.ci_deploy_only_workflows else "CI is required by repository policy" if policy.require_ci else "No validation workflow detected" ), ), GateCheck( key="coverage", label="Coverage evidence", state=( GateState.PASS if tests.coverage_evidence else GateState.WARN if metrics.test_files else GateState.PASS ), detail=( f"{len(tests.coverage_evidence)} coverage evidence source(s) detected" if tests.coverage_evidence else "Tests exist but no coverage-producing command or retained coverage output was detected" if metrics.test_files else "No test suite is present to produce coverage evidence" ), ), GateCheck( key="analysis_coverage", label="Analysis coverage", state=GateState.WARN if coverage and coverage.truncated else GateState.PASS, detail=( "; ".join(coverage.notes) if coverage and coverage.truncated else "Analysis completed within configured limits" ), ), GateCheck( key="dependencies", label="Dependency hygiene", state=GateState.WARN if dependency_issues else GateState.PASS, detail=( f"{dependencies.unbounded_dependencies} unbounded and {dependencies.unpinned_dependencies} unpinned declaration(s)" if dependency_issues else f"{dependencies.declared_dependencies} dependency declaration(s), no unbounded or unpinned versions" ), ), GateCheck( key="health", label="Repository health", state=( GateState.PASS if health_score >= max(85, policy.min_health_score) else GateState.WARN if health_score >= max(65, policy.min_health_score) else GateState.BLOCK ), detail=( f"Health score {health_score}/100" if not policy.min_health_score else f"Health score {health_score}/100; policy minimum {policy.min_health_score}" ), ), GateCheck( key="warnings", label="Engineering warnings", state=GateState.WARN if warnings else GateState.PASS, detail=f"{len(warnings)} warning finding(s)" if warnings else "No warning-level findings", ), ] if any(check.state == GateState.BLOCK for check in checks): status = "BLOCKED" elif any(check.state == GateState.WARN for check in checks): status = "READY WITH WARNINGS" else: status = "READY" return ReleaseGate(status=status, checks=checks)