fix: commit semantic repair changes

This commit is contained in:
2026-03-21 11:22:25 +03:00
parent 0900208c1a
commit abee05558f
272 changed files with 4603 additions and 1668 deletions

View File

@@ -1,5 +1,5 @@
# [DEF:backend.src.api.routes.clean_release:Module]
# @COMPLEXITY: 3
# @COMPLEXITY: 4
# @SEMANTICS: api, clean-release, candidate-preparation, compliance
# @PURPOSE: Expose clean release endpoints for candidate preparation and subsequent compliance flow.
# @LAYER: API
@@ -19,10 +19,20 @@ from ...core.logger import belief_scope, logger
from ...dependencies import get_clean_release_repository, get_config_manager
from ...services.clean_release.preparation_service import prepare_candidate
from ...services.clean_release.repository import CleanReleaseRepository
from ...services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
from ...services.clean_release.compliance_orchestrator import (
CleanComplianceOrchestrator,
)
from ...services.clean_release.report_builder import ComplianceReportBuilder
from ...services.clean_release.compliance_execution_service import ComplianceExecutionService, ComplianceRunError
from ...services.clean_release.dto import CandidateDTO, ManifestDTO, CandidateOverviewDTO, ComplianceRunDTO
from ...services.clean_release.compliance_execution_service import (
ComplianceExecutionService,
ComplianceRunError,
)
from ...services.clean_release.dto import (
CandidateDTO,
ManifestDTO,
CandidateOverviewDTO,
ComplianceRunDTO,
)
from ...services.clean_release.enums import (
ComplianceDecision,
ComplianceStageName,
@@ -49,6 +59,8 @@ class PrepareCandidateRequest(BaseModel):
artifacts: List[Dict[str, Any]] = Field(default_factory=list)
sources: List[str] = Field(default_factory=list)
operator_id: str = Field(min_length=1)
# [/DEF:PrepareCandidateRequest:Class]
@@ -59,6 +71,8 @@ class StartCheckRequest(BaseModel):
profile: str = Field(default="enterprise-clean")
execution_mode: str = Field(default="tui")
triggered_by: str = Field(default="system")
# [/DEF:StartCheckRequest:Class]
@@ -69,6 +83,8 @@ class RegisterCandidateRequest(BaseModel):
version: str = Field(min_length=1)
source_snapshot_ref: str = Field(min_length=1)
created_by: str = Field(min_length=1)
# [/DEF:RegisterCandidateRequest:Class]
@@ -76,6 +92,8 @@ class RegisterCandidateRequest(BaseModel):
# @PURPOSE: Request schema for candidate artifact import endpoint.
class ImportArtifactsRequest(BaseModel):
artifacts: List[Dict[str, Any]] = Field(default_factory=list)
# [/DEF:ImportArtifactsRequest:Class]
@@ -83,6 +101,8 @@ class ImportArtifactsRequest(BaseModel):
# @PURPOSE: Request schema for manifest build endpoint.
class BuildManifestRequest(BaseModel):
created_by: str = Field(default="system")
# [/DEF:BuildManifestRequest:Class]
@@ -91,6 +111,8 @@ class BuildManifestRequest(BaseModel):
class CreateComplianceRunRequest(BaseModel):
requested_by: str = Field(min_length=1)
manifest_id: str | None = None
# [/DEF:CreateComplianceRunRequest:Class]
@@ -98,14 +120,19 @@ class CreateComplianceRunRequest(BaseModel):
# @PURPOSE: Register a clean-release candidate for headless lifecycle.
# @PRE: Candidate identifier is unique.
# @POST: Candidate is persisted in DRAFT status.
@router.post("/candidates", response_model=CandidateDTO, status_code=status.HTTP_201_CREATED)
@router.post(
"/candidates", response_model=CandidateDTO, status_code=status.HTTP_201_CREATED
)
async def register_candidate_v2_endpoint(
payload: RegisterCandidateRequest,
repository: CleanReleaseRepository = Depends(get_clean_release_repository),
):
existing = repository.get_candidate(payload.id)
if existing is not None:
raise HTTPException(status_code=409, detail={"message": "Candidate already exists", "code": "CANDIDATE_EXISTS"})
raise HTTPException(
status_code=409,
detail={"message": "Candidate already exists", "code": "CANDIDATE_EXISTS"},
)
candidate = ReleaseCandidate(
id=payload.id,
@@ -125,6 +152,8 @@ async def register_candidate_v2_endpoint(
created_by=candidate.created_by,
status=CandidateStatus(candidate.status),
)
# [/DEF:register_candidate_v2_endpoint:Function]
@@ -140,9 +169,15 @@ async def import_candidate_artifacts_v2_endpoint(
):
candidate = repository.get_candidate(candidate_id)
if candidate is None:
raise HTTPException(status_code=404, detail={"message": "Candidate not found", "code": "CANDIDATE_NOT_FOUND"})
raise HTTPException(
status_code=404,
detail={"message": "Candidate not found", "code": "CANDIDATE_NOT_FOUND"},
)
if not payload.artifacts:
raise HTTPException(status_code=400, detail={"message": "Artifacts list is required", "code": "ARTIFACTS_EMPTY"})
raise HTTPException(
status_code=400,
detail={"message": "Artifacts list is required", "code": "ARTIFACTS_EMPTY"},
)
for artifact in payload.artifacts:
required = ("id", "path", "sha256", "size")
@@ -150,7 +185,10 @@ async def import_candidate_artifacts_v2_endpoint(
if field_name not in artifact:
raise HTTPException(
status_code=400,
detail={"message": f"Artifact missing field '{field_name}'", "code": "ARTIFACT_INVALID"},
detail={
"message": f"Artifact missing field '{field_name}'",
"code": "ARTIFACT_INVALID",
},
)
artifact_model = CandidateArtifact(
@@ -172,6 +210,8 @@ async def import_candidate_artifacts_v2_endpoint(
repository.save_candidate(candidate)
return {"status": "success"}
# [/DEF:import_candidate_artifacts_v2_endpoint:Function]
@@ -179,7 +219,11 @@ async def import_candidate_artifacts_v2_endpoint(
# @PURPOSE: Build immutable manifest snapshot for prepared candidate.
# @PRE: Candidate exists and has imported artifacts.
# @POST: Returns created ManifestDTO with incremented version.
@router.post("/candidates/{candidate_id}/manifests", response_model=ManifestDTO, status_code=status.HTTP_201_CREATED)
@router.post(
"/candidates/{candidate_id}/manifests",
response_model=ManifestDTO,
status_code=status.HTTP_201_CREATED,
)
async def build_candidate_manifest_v2_endpoint(
candidate_id: str,
payload: BuildManifestRequest,
@@ -194,7 +238,10 @@ async def build_candidate_manifest_v2_endpoint(
created_by=payload.created_by,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail={"message": str(exc), "code": "MANIFEST_BUILD_ERROR"})
raise HTTPException(
status_code=400,
detail={"message": str(exc), "code": "MANIFEST_BUILD_ERROR"},
)
return ManifestDTO(
id=manifest.id,
@@ -207,6 +254,8 @@ async def build_candidate_manifest_v2_endpoint(
source_snapshot_ref=manifest.source_snapshot_ref,
content_json=manifest.content_json,
)
# [/DEF:build_candidate_manifest_v2_endpoint:Function]
@@ -221,26 +270,53 @@ async def get_candidate_overview_v2_endpoint(
):
candidate = repository.get_candidate(candidate_id)
if candidate is None:
raise HTTPException(status_code=404, detail={"message": "Candidate not found", "code": "CANDIDATE_NOT_FOUND"})
raise HTTPException(
status_code=404,
detail={"message": "Candidate not found", "code": "CANDIDATE_NOT_FOUND"},
)
manifests = repository.get_manifests_by_candidate(candidate_id)
latest_manifest = sorted(manifests, key=lambda m: m.manifest_version, reverse=True)[0] if manifests else None
latest_manifest = (
sorted(manifests, key=lambda m: m.manifest_version, reverse=True)[0]
if manifests
else None
)
runs = [run for run in repository.check_runs.values() if run.candidate_id == candidate_id]
latest_run = sorted(runs, key=lambda run: run.requested_at or datetime.min.replace(tzinfo=timezone.utc), reverse=True)[0] if runs else None
runs = [
run
for run in repository.check_runs.values()
if run.candidate_id == candidate_id
]
latest_run = (
sorted(
runs,
key=lambda run: run.requested_at
or datetime.min.replace(tzinfo=timezone.utc),
reverse=True,
)[0]
if runs
else None
)
latest_report = None
if latest_run is not None:
latest_report = next((r for r in repository.reports.values() if r.run_id == latest_run.id), None)
latest_report = next(
(r for r in repository.reports.values() if r.run_id == latest_run.id), None
)
latest_policy_snapshot = repository.get_policy(latest_run.policy_snapshot_id) if latest_run else None
latest_registry_snapshot = repository.get_registry(latest_run.registry_snapshot_id) if latest_run else None
latest_policy_snapshot = (
repository.get_policy(latest_run.policy_snapshot_id) if latest_run else None
)
latest_registry_snapshot = (
repository.get_registry(latest_run.registry_snapshot_id) if latest_run else None
)
approval_decisions = getattr(repository, "approval_decisions", [])
latest_approval = (
sorted(
[item for item in approval_decisions if item.candidate_id == candidate_id],
key=lambda item: item.decided_at or datetime.min.replace(tzinfo=timezone.utc),
key=lambda item: item.decided_at
or datetime.min.replace(tzinfo=timezone.utc),
reverse=True,
)[0]
if approval_decisions
@@ -252,7 +328,8 @@ async def get_candidate_overview_v2_endpoint(
latest_publication = (
sorted(
[item for item in publication_records if item.candidate_id == candidate_id],
key=lambda item: item.published_at or datetime.min.replace(tzinfo=timezone.utc),
key=lambda item: item.published_at
or datetime.min.replace(tzinfo=timezone.utc),
reverse=True,
)[0]
if publication_records
@@ -266,19 +343,35 @@ async def get_candidate_overview_v2_endpoint(
source_snapshot_ref=candidate.source_snapshot_ref,
status=CandidateStatus(candidate.status),
latest_manifest_id=latest_manifest.id if latest_manifest else None,
latest_manifest_digest=latest_manifest.manifest_digest if latest_manifest else None,
latest_manifest_digest=latest_manifest.manifest_digest
if latest_manifest
else None,
latest_run_id=latest_run.id if latest_run else None,
latest_run_status=RunStatus(latest_run.status) if latest_run else None,
latest_report_id=latest_report.id if latest_report else None,
latest_report_final_status=ComplianceDecision(latest_report.final_status) if latest_report else None,
latest_policy_snapshot_id=latest_policy_snapshot.id if latest_policy_snapshot else None,
latest_policy_version=latest_policy_snapshot.policy_version if latest_policy_snapshot else None,
latest_registry_snapshot_id=latest_registry_snapshot.id if latest_registry_snapshot else None,
latest_registry_version=latest_registry_snapshot.registry_version if latest_registry_snapshot else None,
latest_report_final_status=ComplianceDecision(latest_report.final_status)
if latest_report
else None,
latest_policy_snapshot_id=latest_policy_snapshot.id
if latest_policy_snapshot
else None,
latest_policy_version=latest_policy_snapshot.policy_version
if latest_policy_snapshot
else None,
latest_registry_snapshot_id=latest_registry_snapshot.id
if latest_registry_snapshot
else None,
latest_registry_version=latest_registry_snapshot.registry_version
if latest_registry_snapshot
else None,
latest_approval_decision=latest_approval.decision if latest_approval else None,
latest_publication_id=latest_publication.id if latest_publication else None,
latest_publication_status=latest_publication.status if latest_publication else None,
latest_publication_status=latest_publication.status
if latest_publication
else None,
)
# [/DEF:get_candidate_overview_v2_endpoint:Function]
@@ -311,6 +404,8 @@ async def prepare_candidate_endpoint(
status_code=status.HTTP_400_BAD_REQUEST,
detail={"message": str(exc), "code": "CLEAN_PREPARATION_ERROR"},
)
# [/DEF:prepare_candidate_endpoint:Function]
@@ -327,27 +422,46 @@ async def start_check(
logger.reason("Starting clean-release compliance check run")
policy = repository.get_active_policy()
if policy is None:
raise HTTPException(status_code=409, detail={"message": "Active policy not found", "code": "POLICY_NOT_FOUND"})
raise HTTPException(
status_code=409,
detail={
"message": "Active policy not found",
"code": "POLICY_NOT_FOUND",
},
)
candidate = repository.get_candidate(payload.candidate_id)
if candidate is None:
raise HTTPException(status_code=409, detail={"message": "Candidate not found", "code": "CANDIDATE_NOT_FOUND"})
raise HTTPException(
status_code=409,
detail={
"message": "Candidate not found",
"code": "CANDIDATE_NOT_FOUND",
},
)
manifests = repository.get_manifests_by_candidate(payload.candidate_id)
if not manifests:
logger.explore("No manifest found for candidate; bootstrapping legacy empty manifest for compatibility")
from ...services.clean_release.manifest_builder import build_distribution_manifest
logger.explore(
"No manifest found for candidate; bootstrapping legacy empty manifest for compatibility"
)
from ...services.clean_release.manifest_builder import (
build_distribution_manifest,
)
boot_manifest = build_distribution_manifest(
manifest_id=f"manifest-{payload.candidate_id}",
candidate_id=payload.candidate_id,
policy_id=getattr(policy, "policy_id", None) or getattr(policy, "id", ""),
policy_id=getattr(policy, "policy_id", None)
or getattr(policy, "id", ""),
generated_by=payload.triggered_by,
artifacts=[],
)
repository.save_manifest(boot_manifest)
manifests = [boot_manifest]
latest_manifest = sorted(manifests, key=lambda m: m.manifest_version, reverse=True)[0]
latest_manifest = sorted(
manifests, key=lambda m: m.manifest_version, reverse=True
)[0]
orchestrator = CleanComplianceOrchestrator(repository)
run = orchestrator.start_check_run(
@@ -364,7 +478,7 @@ async def start_check(
stage_name=ComplianceStageName.DATA_PURITY.value,
status=RunStatus.SUCCEEDED.value,
decision=ComplianceDecision.PASSED.value,
details_json={"message": "ok"}
details_json={"message": "ok"},
),
ComplianceStageRun(
id=f"stage-{run.id}-2",
@@ -372,7 +486,7 @@ async def start_check(
stage_name=ComplianceStageName.INTERNAL_SOURCES_ONLY.value,
status=RunStatus.SUCCEEDED.value,
decision=ComplianceDecision.PASSED.value,
details_json={"message": "ok"}
details_json={"message": "ok"},
),
ComplianceStageRun(
id=f"stage-{run.id}-3",
@@ -380,7 +494,7 @@ async def start_check(
stage_name=ComplianceStageName.NO_EXTERNAL_ENDPOINTS.value,
status=RunStatus.SUCCEEDED.value,
decision=ComplianceDecision.PASSED.value,
details_json={"message": "ok"}
details_json={"message": "ok"},
),
ComplianceStageRun(
id=f"stage-{run.id}-4",
@@ -388,14 +502,20 @@ async def start_check(
stage_name=ComplianceStageName.MANIFEST_CONSISTENCY.value,
status=RunStatus.SUCCEEDED.value,
decision=ComplianceDecision.PASSED.value,
details_json={"message": "ok"}
details_json={"message": "ok"},
),
]
run = orchestrator.execute_stages(run, forced_results=forced)
run = orchestrator.finalize_run(run)
if str(run.final_status) in {ComplianceDecision.BLOCKED.value, "CheckFinalStatus.BLOCKED", "BLOCKED"}:
logger.explore("Run ended as BLOCKED, persisting synthetic external-source violation")
if str(run.final_status) in {
ComplianceDecision.BLOCKED.value,
"CheckFinalStatus.BLOCKED",
"BLOCKED",
}:
logger.explore(
"Run ended as BLOCKED, persisting synthetic external-source violation"
)
violation = ComplianceViolation(
id=f"viol-{run.id}",
run_id=run.id,
@@ -403,12 +523,14 @@ async def start_check(
code="EXTERNAL_SOURCE_DETECTED",
severity=ViolationSeverity.CRITICAL.value,
message="Replace with approved internal server",
evidence_json={"location": "external.example.com"}
evidence_json={"location": "external.example.com"},
)
repository.save_violation(violation)
builder = ComplianceReportBuilder(repository)
report = builder.build_report_payload(run, repository.get_violations_by_run(run.id))
report = builder.build_report_payload(
run, repository.get_violations_by_run(run.id)
)
builder.persist_report(report)
logger.reflect(f"Compliance report persisted for run_id={run.id}")
@@ -418,6 +540,8 @@ async def start_check(
"status": "running",
"started_at": run.started_at.isoformat() if run.started_at else None,
}
# [/DEF:start_check:Function]
@@ -426,11 +550,17 @@ async def start_check(
# @PRE: check_run_id references an existing run.
# @POST: Deterministic payload shape includes checks and violations arrays.
@router.get("/checks/{check_run_id}")
async def get_check_status(check_run_id: str, repository: CleanReleaseRepository = Depends(get_clean_release_repository)):
async def get_check_status(
check_run_id: str,
repository: CleanReleaseRepository = Depends(get_clean_release_repository),
):
with belief_scope("clean_release.get_check_status"):
run = repository.get_check_run(check_run_id)
if run is None:
raise HTTPException(status_code=404, detail={"message": "Check run not found", "code": "CHECK_NOT_FOUND"})
raise HTTPException(
status_code=404,
detail={"message": "Check run not found", "code": "CHECK_NOT_FOUND"},
)
logger.reflect(f"Returning check status for check_run_id={check_run_id}")
checks = [
@@ -462,6 +592,8 @@ async def get_check_status(check_run_id: str, repository: CleanReleaseRepository
"checks": checks,
"violations": violations,
}
# [/DEF:get_check_status:Function]
@@ -470,11 +602,17 @@ async def get_check_status(check_run_id: str, repository: CleanReleaseRepository
# @PRE: report_id references an existing report.
# @POST: Returns serialized report object.
@router.get("/reports/{report_id}")
async def get_report(report_id: str, repository: CleanReleaseRepository = Depends(get_clean_release_repository)):
async def get_report(
report_id: str,
repository: CleanReleaseRepository = Depends(get_clean_release_repository),
):
with belief_scope("clean_release.get_report"):
report = repository.get_report(report_id)
if report is None:
raise HTTPException(status_code=404, detail={"message": "Report not found", "code": "REPORT_NOT_FOUND"})
raise HTTPException(
status_code=404,
detail={"message": "Report not found", "code": "REPORT_NOT_FOUND"},
)
logger.reflect(f"Returning compliance report report_id={report_id}")
return {
@@ -482,11 +620,17 @@ async def get_report(report_id: str, repository: CleanReleaseRepository = Depend
"check_run_id": report.run_id,
"candidate_id": report.candidate_id,
"final_status": getattr(report.final_status, "value", report.final_status),
"generated_at": report.generated_at.isoformat() if getattr(report, "generated_at", None) else None,
"generated_at": report.generated_at.isoformat()
if getattr(report, "generated_at", None)
else None,
"operator_summary": getattr(report, "operator_summary", ""),
"structured_payload_ref": getattr(report, "structured_payload_ref", None),
"violations_count": getattr(report, "violations_count", 0),
"blocking_violations_count": getattr(report, "blocking_violations_count", 0),
"blocking_violations_count": getattr(
report, "blocking_violations_count", 0
),
}
# [/DEF:get_report:Function]
# [/DEF:backend.src.api.routes.clean_release:Module]
# [/DEF:backend.src.api.routes.clean_release:Module]