# [DEF:backend.src.models.clean_release:Module] # @TIER: CRITICAL # @SEMANTICS: clean-release, models, lifecycle, compliance, evidence, immutability # @PURPOSE: Define canonical clean release domain entities and lifecycle guards. # @LAYER: Domain # @INVARIANT: Immutable snapshots are never mutated; forbidden lifecycle transitions are rejected. from datetime import datetime from dataclasses import dataclass from enum import Enum from typing import List, Optional, Dict, Any from sqlalchemy import Column, String, DateTime, JSON, ForeignKey, Integer, Boolean from sqlalchemy.orm import relationship from .mapping import Base from ..services.clean_release.enums import ( CandidateStatus, RunStatus, ComplianceDecision, ApprovalDecisionType, PublicationStatus, ClassificationType ) from ..services.clean_release.exceptions import IllegalTransitionError # [DEF:CheckFinalStatus:Class] # @PURPOSE: Backward-compatible final status enum for legacy TUI/orchestrator tests. class CheckFinalStatus(str, Enum): COMPLIANT = "COMPLIANT" BLOCKED = "BLOCKED" FAILED = "FAILED" # [/DEF:CheckFinalStatus:Class] # [DEF:CheckStageName:Class] # @PURPOSE: Backward-compatible stage name enum for legacy TUI/orchestrator tests. class CheckStageName(str, Enum): DATA_PURITY = "DATA_PURITY" INTERNAL_SOURCES_ONLY = "INTERNAL_SOURCES_ONLY" NO_EXTERNAL_ENDPOINTS = "NO_EXTERNAL_ENDPOINTS" MANIFEST_CONSISTENCY = "MANIFEST_CONSISTENCY" # [/DEF:CheckStageName:Class] # [DEF:CheckStageStatus:Class] # @PURPOSE: Backward-compatible stage status enum for legacy TUI/orchestrator tests. class CheckStageStatus(str, Enum): PASS = "PASS" FAIL = "FAIL" SKIPPED = "SKIPPED" RUNNING = "RUNNING" # [/DEF:CheckStageStatus:Class] # [DEF:CheckStageResult:Class] # @PURPOSE: Backward-compatible stage result container for legacy TUI/orchestrator tests. @dataclass class CheckStageResult: stage: CheckStageName status: CheckStageStatus details: str = "" # [/DEF:CheckStageResult:Class] # [DEF:ProfileType:Class] # @PURPOSE: Backward-compatible profile enum for legacy TUI bootstrap logic. class ProfileType(str, Enum): ENTERPRISE_CLEAN = "enterprise-clean" # [/DEF:ProfileType:Class] # [DEF:RegistryStatus:Class] # @PURPOSE: Backward-compatible registry status enum for legacy TUI bootstrap logic. class RegistryStatus(str, Enum): ACTIVE = "ACTIVE" INACTIVE = "INACTIVE" # [/DEF:RegistryStatus:Class] # [DEF:ReleaseCandidateStatus:Class] # @PURPOSE: Backward-compatible release candidate status enum for legacy TUI. class ReleaseCandidateStatus(str, Enum): DRAFT = CandidateStatus.DRAFT.value PREPARED = CandidateStatus.PREPARED.value MANIFEST_BUILT = CandidateStatus.MANIFEST_BUILT.value CHECK_PENDING = CandidateStatus.CHECK_PENDING.value CHECK_RUNNING = CandidateStatus.CHECK_RUNNING.value CHECK_PASSED = CandidateStatus.CHECK_PASSED.value CHECK_BLOCKED = CandidateStatus.CHECK_BLOCKED.value CHECK_ERROR = CandidateStatus.CHECK_ERROR.value APPROVED = CandidateStatus.APPROVED.value PUBLISHED = CandidateStatus.PUBLISHED.value REVOKED = CandidateStatus.REVOKED.value # [/DEF:ReleaseCandidateStatus:Class] # [DEF:ResourceSourceEntry:Class] # @PURPOSE: Backward-compatible source entry model for legacy TUI bootstrap logic. @dataclass class ResourceSourceEntry: source_id: str host: str protocol: str purpose: str enabled: bool = True # [/DEF:ResourceSourceEntry:Class] # [DEF:ResourceSourceRegistry:Class] # @PURPOSE: Backward-compatible source registry model for legacy TUI bootstrap logic. @dataclass class ResourceSourceRegistry: registry_id: str name: str entries: List[ResourceSourceEntry] updated_at: datetime updated_by: str status: str = "ACTIVE" @property def id(self) -> str: return self.registry_id # [/DEF:ResourceSourceRegistry:Class] # [DEF:CleanProfilePolicy:Class] # @PURPOSE: Backward-compatible policy model for legacy TUI bootstrap logic. @dataclass class CleanProfilePolicy: policy_id: str policy_version: str profile: str active: bool internal_source_registry_ref: str prohibited_artifact_categories: List[str] effective_from: datetime required_system_categories: Optional[List[str]] = None @property def id(self) -> str: return self.policy_id @property def registry_snapshot_id(self) -> str: return self.internal_source_registry_ref # [/DEF:CleanProfilePolicy:Class] # [DEF:ComplianceCheckRun:Class] # @PURPOSE: Backward-compatible run model for legacy TUI typing/import compatibility. @dataclass class ComplianceCheckRun: check_run_id: str candidate_id: str policy_id: str requested_by: str execution_mode: str checks: List[CheckStageResult] final_status: CheckFinalStatus # [/DEF:ComplianceCheckRun:Class] # [DEF:ReleaseCandidate:Class] # @PURPOSE: Represents the release unit being prepared and governed. # @PRE: id, version, source_snapshot_ref are non-empty. # @POST: status advances only through legal transitions. class ReleaseCandidate(Base): __tablename__ = "clean_release_candidates" id = Column(String, primary_key=True) name = Column(String, nullable=True) # Added back for backward compatibility with some legacy DTOs version = Column(String, nullable=False) source_snapshot_ref = Column(String, nullable=False) build_id = Column(String, nullable=True) created_at = Column(DateTime, default=datetime.utcnow) created_by = Column(String, nullable=False) status = Column(String, default=CandidateStatus.DRAFT) @property def candidate_id(self) -> str: return self.id def transition_to(self, new_status: CandidateStatus): """ @PURPOSE: Enforce legal state transitions. @PRE: Transition must be allowed by lifecycle rules. """ allowed = { CandidateStatus.DRAFT: [CandidateStatus.PREPARED], CandidateStatus.PREPARED: [CandidateStatus.MANIFEST_BUILT], CandidateStatus.MANIFEST_BUILT: [CandidateStatus.CHECK_PENDING], CandidateStatus.CHECK_PENDING: [CandidateStatus.CHECK_RUNNING], CandidateStatus.CHECK_RUNNING: [ CandidateStatus.CHECK_PASSED, CandidateStatus.CHECK_BLOCKED, CandidateStatus.CHECK_ERROR ], CandidateStatus.CHECK_PASSED: [CandidateStatus.APPROVED, CandidateStatus.CHECK_PENDING], CandidateStatus.CHECK_BLOCKED: [CandidateStatus.CHECK_PENDING], CandidateStatus.CHECK_ERROR: [CandidateStatus.CHECK_PENDING], CandidateStatus.APPROVED: [CandidateStatus.PUBLISHED], CandidateStatus.PUBLISHED: [CandidateStatus.REVOKED], CandidateStatus.REVOKED: [] } current_status = CandidateStatus(self.status) if new_status not in allowed.get(current_status, []): raise IllegalTransitionError(f"Forbidden transition from {current_status} to {new_status}") self.status = new_status.value # [/DEF:ReleaseCandidate:Class] # [DEF:CandidateArtifact:Class] # @PURPOSE: Represents one artifact associated with a release candidate. class CandidateArtifact(Base): __tablename__ = "clean_release_artifacts" id = Column(String, primary_key=True) candidate_id = Column(String, ForeignKey("clean_release_candidates.id"), nullable=False) path = Column(String, nullable=False) sha256 = Column(String, nullable=False) size = Column(Integer, nullable=False) detected_category = Column(String, nullable=True) declared_category = Column(String, nullable=True) source_uri = Column(String, nullable=True) source_host = Column(String, nullable=True) metadata_json = Column(JSON, default=dict) # [/DEF:CandidateArtifact:Class] # [DEF:ManifestItem:Class] @dataclass class ManifestItem: path: str category: str classification: ClassificationType reason: str checksum: Optional[str] = None # [/DEF:ManifestItem:Class] # [DEF:ManifestSummary:Class] @dataclass class ManifestSummary: included_count: int excluded_count: int prohibited_detected_count: int # [/DEF:ManifestSummary:Class] # [DEF:DistributionManifest:Class] # @PURPOSE: Immutable snapshot of the candidate payload. # @INVARIANT: Immutable after creation. class DistributionManifest(Base): __tablename__ = "clean_release_manifests" id = Column(String, primary_key=True) candidate_id = Column(String, ForeignKey("clean_release_candidates.id"), nullable=False) manifest_version = Column(Integer, nullable=False) manifest_digest = Column(String, nullable=False) artifacts_digest = Column(String, nullable=False) created_at = Column(DateTime, default=datetime.utcnow) created_by = Column(String, nullable=False) source_snapshot_ref = Column(String, nullable=False) content_json = Column(JSON, nullable=False) immutable = Column(Boolean, default=True) # Redesign compatibility fields (not persisted directly but used by builder/facade) def __init__(self, **kwargs): # Handle fields from manifest_builder.py if "manifest_id" in kwargs: kwargs["id"] = kwargs.pop("manifest_id") if "generated_at" in kwargs: kwargs["created_at"] = kwargs.pop("generated_at") if "generated_by" in kwargs: kwargs["created_by"] = kwargs.pop("generated_by") if "deterministic_hash" in kwargs: kwargs["manifest_digest"] = kwargs.pop("deterministic_hash") # Ensure required DB fields have defaults if missing if "manifest_version" not in kwargs: kwargs["manifest_version"] = 1 if "artifacts_digest" not in kwargs: kwargs["artifacts_digest"] = kwargs.get("manifest_digest", "pending") if "source_snapshot_ref" not in kwargs: kwargs["source_snapshot_ref"] = "pending" # Pack items and summary into content_json if provided if "items" in kwargs or "summary" in kwargs: content = kwargs.get("content_json", {}) if "items" in kwargs: items = kwargs.pop("items") content["items"] = [ { "path": i.path, "category": i.category, "classification": i.classification.value, "reason": i.reason, "checksum": i.checksum } for i in items ] if "summary" in kwargs: summary = kwargs.pop("summary") content["summary"] = { "included_count": summary.included_count, "excluded_count": summary.excluded_count, "prohibited_detected_count": summary.prohibited_detected_count } kwargs["content_json"] = content super().__init__(**kwargs) # [/DEF:DistributionManifest:Class] # [DEF:SourceRegistrySnapshot:Class] # @PURPOSE: Immutable registry snapshot for allowed sources. class SourceRegistrySnapshot(Base): __tablename__ = "clean_release_registry_snapshots" id = Column(String, primary_key=True) registry_id = Column(String, nullable=False) registry_version = Column(String, nullable=False) created_at = Column(DateTime, default=datetime.utcnow) allowed_hosts = Column(JSON, nullable=False) # List[str] allowed_schemes = Column(JSON, nullable=False) # List[str] allowed_source_types = Column(JSON, nullable=False) # List[str] immutable = Column(Boolean, default=True) # [/DEF:SourceRegistrySnapshot:Class] # [DEF:CleanPolicySnapshot:Class] # @PURPOSE: Immutable policy snapshot used to evaluate a run. class CleanPolicySnapshot(Base): __tablename__ = "clean_release_policy_snapshots" id = Column(String, primary_key=True) policy_id = Column(String, nullable=False) policy_version = Column(String, nullable=False) created_at = Column(DateTime, default=datetime.utcnow) content_json = Column(JSON, nullable=False) registry_snapshot_id = Column(String, ForeignKey("clean_release_registry_snapshots.id"), nullable=False) immutable = Column(Boolean, default=True) # [/DEF:CleanPolicySnapshot:Class] # [DEF:ComplianceRun:Class] # @PURPOSE: Operational record for one compliance execution. class ComplianceRun(Base): __tablename__ = "clean_release_compliance_runs" id = Column(String, primary_key=True) candidate_id = Column(String, ForeignKey("clean_release_candidates.id"), nullable=False) manifest_id = Column(String, ForeignKey("clean_release_manifests.id"), nullable=False) manifest_digest = Column(String, nullable=False) policy_snapshot_id = Column(String, ForeignKey("clean_release_policy_snapshots.id"), nullable=False) registry_snapshot_id = Column(String, ForeignKey("clean_release_registry_snapshots.id"), nullable=False) requested_by = Column(String, nullable=False) requested_at = Column(DateTime, default=datetime.utcnow) started_at = Column(DateTime, nullable=True) finished_at = Column(DateTime, nullable=True) status = Column(String, default=RunStatus.PENDING) final_status = Column(String, nullable=True) # ComplianceDecision failure_reason = Column(String, nullable=True) task_id = Column(String, nullable=True) @property def check_run_id(self) -> str: return self.id # [/DEF:ComplianceRun:Class] # [DEF:ComplianceStageRun:Class] # @PURPOSE: Stage-level execution record inside a run. class ComplianceStageRun(Base): __tablename__ = "clean_release_compliance_stage_runs" id = Column(String, primary_key=True) run_id = Column(String, ForeignKey("clean_release_compliance_runs.id"), nullable=False) stage_name = Column(String, nullable=False) status = Column(String, nullable=False) started_at = Column(DateTime, nullable=True) finished_at = Column(DateTime, nullable=True) decision = Column(String, nullable=True) # ComplianceDecision details_json = Column(JSON, default=dict) # [/DEF:ComplianceStageRun:Class] # [DEF:ComplianceViolation:Class] # @PURPOSE: Violation produced by a stage. class ComplianceViolation(Base): __tablename__ = "clean_release_compliance_violations" id = Column(String, primary_key=True) run_id = Column(String, ForeignKey("clean_release_compliance_runs.id"), nullable=False) stage_name = Column(String, nullable=False) code = Column(String, nullable=False) severity = Column(String, nullable=False) artifact_path = Column(String, nullable=True) artifact_sha256 = Column(String, nullable=True) message = Column(String, nullable=False) evidence_json = Column(JSON, default=dict) # [/DEF:ComplianceViolation:Class] # [DEF:ComplianceReport:Class] # @PURPOSE: Immutable result derived from a completed run. # @INVARIANT: Immutable after creation. class ComplianceReport(Base): __tablename__ = "clean_release_compliance_reports" id = Column(String, primary_key=True) run_id = Column(String, ForeignKey("clean_release_compliance_runs.id"), nullable=False) candidate_id = Column(String, ForeignKey("clean_release_candidates.id"), nullable=False) final_status = Column(String, nullable=False) # ComplianceDecision summary_json = Column(JSON, nullable=False) generated_at = Column(DateTime, default=datetime.utcnow) immutable = Column(Boolean, default=True) # [/DEF:ComplianceReport:Class] # [DEF:ApprovalDecision:Class] # @PURPOSE: Approval or rejection bound to a candidate and report. class ApprovalDecision(Base): __tablename__ = "clean_release_approval_decisions" id = Column(String, primary_key=True) candidate_id = Column(String, ForeignKey("clean_release_candidates.id"), nullable=False) report_id = Column(String, ForeignKey("clean_release_compliance_reports.id"), nullable=False) decision = Column(String, nullable=False) # ApprovalDecisionType decided_by = Column(String, nullable=False) decided_at = Column(DateTime, default=datetime.utcnow) comment = Column(String, nullable=True) # [/DEF:ApprovalDecision:Class] # [DEF:PublicationRecord:Class] # @PURPOSE: Publication or revocation record. class PublicationRecord(Base): __tablename__ = "clean_release_publication_records" id = Column(String, primary_key=True) candidate_id = Column(String, ForeignKey("clean_release_candidates.id"), nullable=False) report_id = Column(String, ForeignKey("clean_release_compliance_reports.id"), nullable=False) published_by = Column(String, nullable=False) published_at = Column(DateTime, default=datetime.utcnow) target_channel = Column(String, nullable=False) publication_ref = Column(String, nullable=True) status = Column(String, default=PublicationStatus.ACTIVE) # [/DEF:PublicationRecord:Class] # [DEF:CleanReleaseAuditLog:Class] # @PURPOSE: Represents a persistent audit log entry for clean release actions. import uuid class CleanReleaseAuditLog(Base): __tablename__ = "clean_release_audit_logs" id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) candidate_id = Column(String, index=True, nullable=True) action = Column(String, nullable=False) # e.g. "TRANSITION", "APPROVE", "PUBLISH" actor = Column(String, nullable=False) timestamp = Column(DateTime, default=datetime.utcnow) details_json = Column(JSON, default=dict) # [/DEF:CleanReleaseAuditLog:Class] # [/DEF:backend.src.models.clean_release:Module]