Files
ss-tools/backend/src/models/clean_release.py
busya 306c5ae742 semantics: complete DEF-to-region migration, fix regressions
- Convert legacy [DEF🆔Type] anchors to #region/#endregion across 329 files
- Reinstate _normalize_timestamp_value in sql_generator.py
- Fix MarkerLogger→logger migration in events.py (molecular CoT markers)
- Fix dataset_review orchestrator dependencies (_build_execution_snapshot)
- Fix config_manager stale-record deletion (moved to save path only)
- Add 77 missing [/DEF:] closers in 5 unbalanced test files
- Update assistant_chat.integration.test.js for #region format
- Apply molecular-cot-logging markers (REASON/REFLECT/EXPLORE) via logger.* methods
2026-05-12 23:54:55 +03:00

697 lines
28 KiB
Python

# #region CleanReleaseModels [C:3] [TYPE Module] [SEMANTICS clean-release, models, lifecycle, compliance, evidence, immutability]
# @BRIEF Define canonical clean release domain entities and lifecycle guards.
# @LAYER: Domain
# @RELATION DEPENDS_ON -> MappingModels
# @PRE: Base mapping model and release enums are available.
# @POST: Provides SQLAlchemy and dataclass definitions for governance domain.
# @SIDE_EFFECT: None (schema definition).
# @DATA_CONTRACT: Model[ReleaseCandidate, CandidateArtifact, DistributionManifest, ComplianceRun, ComplianceReport]
# @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 pydantic import ConfigDict, Field, model_validator
from pydantic.dataclasses import dataclass as pydantic_dataclass
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
# #region ExecutionMode [TYPE Class]
# @BRIEF Backward-compatible execution mode enum for legacy TUI/orchestrator tests.
class ExecutionMode(str, Enum):
TUI = "TUI"
API = "API"
SCHEDULER = "SCHEDULER"
# #endregion ExecutionMode
# #region CheckFinalStatus [TYPE Class]
# @BRIEF Backward-compatible final status enum for legacy TUI/orchestrator tests.
class CheckFinalStatus(str, Enum):
COMPLIANT = "COMPLIANT"
BLOCKED = "BLOCKED"
FAILED = "FAILED"
RUNNING = "RUNNING"
# #endregion CheckFinalStatus
# #region CheckStageName [TYPE Class]
# @BRIEF 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"
# #endregion CheckStageName
# #region CheckStageStatus [TYPE Class]
# @BRIEF Backward-compatible stage status enum for legacy TUI/orchestrator tests.
class CheckStageStatus(str, Enum):
PASS = "PASS"
FAIL = "FAIL"
SKIPPED = "SKIPPED"
RUNNING = "RUNNING"
# #endregion CheckStageStatus
# #region CheckStageResult [TYPE Class]
# @BRIEF Backward-compatible stage result container for legacy TUI/orchestrator tests.
@pydantic_dataclass(config=ConfigDict(validate_assignment=True))
class CheckStageResult:
stage: CheckStageName
status: CheckStageStatus
details: str = ""
# #endregion CheckStageResult
# #region ProfileType [TYPE Class]
# @BRIEF Backward-compatible profile enum for legacy TUI bootstrap logic.
class ProfileType(str, Enum):
ENTERPRISE_CLEAN = "enterprise-clean"
# #endregion ProfileType
# #region RegistryStatus [TYPE Class]
# @BRIEF Backward-compatible registry status enum for legacy TUI bootstrap logic.
class RegistryStatus(str, Enum):
ACTIVE = "ACTIVE"
INACTIVE = "INACTIVE"
# #endregion RegistryStatus
# #region ReleaseCandidateStatus [TYPE Class]
# @BRIEF 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
BLOCKED = CandidateStatus.CHECK_BLOCKED.value
CHECK_ERROR = CandidateStatus.CHECK_ERROR.value
APPROVED = CandidateStatus.APPROVED.value
PUBLISHED = CandidateStatus.PUBLISHED.value
REVOKED = CandidateStatus.REVOKED.value
# #endregion ReleaseCandidateStatus
# #region ResourceSourceEntry [TYPE Class]
# @BRIEF Backward-compatible source entry model for legacy TUI bootstrap logic.
@pydantic_dataclass(config=ConfigDict(validate_assignment=True))
class ResourceSourceEntry:
source_id: str
host: str
protocol: str
purpose: str
enabled: bool = True
# #endregion ResourceSourceEntry
# #region ResourceSourceRegistry [TYPE Class]
# @BRIEF Backward-compatible source registry model for legacy TUI bootstrap logic.
@pydantic_dataclass(config=ConfigDict(validate_assignment=True))
class ResourceSourceRegistry:
registry_id: str
name: str
entries: List[ResourceSourceEntry]
updated_at: datetime
updated_by: str
status: str = "ACTIVE"
immutable: bool = True
allowed_hosts: Optional[List[str]] = None
allowed_schemes: Optional[List[str]] = None
allowed_source_types: Optional[List[str]] = None
@model_validator(mode="after")
def populate_legacy_allowlists(self):
enabled_entries = [entry for entry in self.entries if getattr(entry, "enabled", True)]
if self.allowed_hosts is None:
self.allowed_hosts = [entry.host for entry in enabled_entries]
if self.allowed_schemes is None:
self.allowed_schemes = [entry.protocol for entry in enabled_entries]
if self.allowed_source_types is None:
self.allowed_source_types = [entry.purpose for entry in enabled_entries]
return self
@property
def id(self) -> str:
return self.registry_id
# #endregion ResourceSourceRegistry
# #region CleanProfilePolicy [TYPE Class]
# @BRIEF Backward-compatible policy model for legacy TUI bootstrap logic.
@pydantic_dataclass(config=ConfigDict(validate_assignment=True))
class CleanProfilePolicy:
policy_id: str
policy_version: str
profile: ProfileType
active: bool
internal_source_registry_ref: str
prohibited_artifact_categories: List[str]
effective_from: datetime
required_system_categories: Optional[List[str]] = None
external_source_forbidden: bool = True
immutable: bool = True
content_json: Optional[Dict[str, Any]] = None
@model_validator(mode="after")
def validate_enterprise_policy(self):
if self.profile == ProfileType.ENTERPRISE_CLEAN:
if not self.prohibited_artifact_categories:
raise ValueError("enterprise-clean policy requires prohibited_artifact_categories")
if self.external_source_forbidden is not True:
raise ValueError("enterprise-clean policy requires external_source_forbidden=true")
if self.content_json is None:
self.content_json = {
"profile": self.profile.value,
"prohibited_artifact_categories": list(self.prohibited_artifact_categories or []),
"required_system_categories": list(self.required_system_categories or []),
"external_source_forbidden": self.external_source_forbidden,
}
return self
@property
def id(self) -> str:
return self.policy_id
@property
def registry_snapshot_id(self) -> str:
return self.internal_source_registry_ref
# #endregion CleanProfilePolicy
# #region ComplianceCheckRun [TYPE Class]
# @BRIEF Backward-compatible run model for legacy TUI typing/import compatibility.
@pydantic_dataclass(config=ConfigDict(validate_assignment=True))
class ComplianceCheckRun:
check_run_id: str
candidate_id: str
policy_id: str
started_at: datetime
triggered_by: str
execution_mode: ExecutionMode
final_status: CheckFinalStatus
checks: List[CheckStageResult]
finished_at: Optional[datetime] = None
@model_validator(mode="after")
def validate_final_status_alignment(self):
mandatory_stages = {
CheckStageName.DATA_PURITY,
CheckStageName.INTERNAL_SOURCES_ONLY,
CheckStageName.NO_EXTERNAL_ENDPOINTS,
CheckStageName.MANIFEST_CONSISTENCY,
}
if self.final_status == CheckFinalStatus.COMPLIANT:
observed_stages = {check.stage for check in self.checks}
if observed_stages != mandatory_stages:
raise ValueError("compliant run requires all mandatory stages")
if any(check.status != CheckStageStatus.PASS for check in self.checks):
raise ValueError("compliant run requires PASS on all mandatory stages")
return self
@property
def id(self) -> str:
return self.check_run_id
@property
def run_id(self) -> str:
return self.check_run_id
@property
def status(self) -> RunStatus:
if self.final_status == CheckFinalStatus.RUNNING:
return RunStatus.RUNNING
if self.final_status == CheckFinalStatus.BLOCKED:
return RunStatus.FAILED
return RunStatus.SUCCEEDED
# #endregion ComplianceCheckRun
# #region ReleaseCandidate [TYPE Class]
# @BRIEF 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)
def __init__(self, **kwargs):
if "candidate_id" in kwargs:
kwargs["id"] = kwargs.pop("candidate_id")
if "profile" in kwargs:
kwargs.pop("profile")
status = kwargs.get("status")
if status is None:
kwargs["status"] = CandidateStatus.DRAFT.value
elif isinstance(status, ReleaseCandidateStatus):
kwargs["status"] = status.value
elif isinstance(status, CandidateStatus):
kwargs["status"] = status.value
if not str(kwargs.get("id", "")).strip():
raise ValueError("candidate_id must be non-empty")
super().__init__(**kwargs)
@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
# #endregion ReleaseCandidate
# #region CandidateArtifact [TYPE Class]
# @BRIEF 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)
# #endregion CandidateArtifact
# #region ManifestItem [TYPE Class]
@pydantic_dataclass(config=ConfigDict(validate_assignment=True))
class ManifestItem:
path: str
category: str
classification: ClassificationType
reason: str
checksum: Optional[str] = None
# #endregion ManifestItem
# #region ManifestSummary [TYPE Class]
@pydantic_dataclass(config=ConfigDict(validate_assignment=True))
class ManifestSummary:
included_count: int
excluded_count: int
prohibited_detected_count: int
# #endregion ManifestSummary
# #region DistributionManifest [TYPE Class]
# @BRIEF 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):
items = kwargs.pop("items", None)
summary = kwargs.pop("summary", None)
# 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")
if "policy_id" in kwargs:
kwargs.pop("policy_id")
if items is not None and summary is not None:
expected_count = int(summary.included_count) + int(summary.excluded_count)
if expected_count != len(items):
raise ValueError("manifest summary counts must match items size")
# 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 is not None or summary is not None:
content = dict(kwargs.get("content_json") or {})
if items is not None:
content["items"] = [
{
"path": i.path,
"category": i.category,
"classification": i.classification.value,
"reason": i.reason,
"checksum": i.checksum
} for i in items
]
if summary is not None:
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)
@property
def manifest_id(self) -> str:
return self.id
@property
def deterministic_hash(self) -> str:
return self.manifest_digest
@property
def summary(self) -> ManifestSummary:
payload = (self.content_json or {}).get("summary", {})
return ManifestSummary(
included_count=int(payload.get("included_count", 0)),
excluded_count=int(payload.get("excluded_count", 0)),
prohibited_detected_count=int(payload.get("prohibited_detected_count", 0)),
)
# #endregion DistributionManifest
# #region SourceRegistrySnapshot [TYPE Class]
# @BRIEF 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)
# #endregion SourceRegistrySnapshot
# #region CleanPolicySnapshot [TYPE Class]
# @BRIEF 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)
# #endregion CleanPolicySnapshot
# #region ComplianceRun [TYPE Class]
# @BRIEF 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
# #endregion ComplianceRun
# #region ComplianceStageRun [TYPE Class]
# @BRIEF 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)
# #endregion ComplianceStageRun
# #region ViolationSeverity [TYPE Class]
# @BRIEF Backward-compatible violation severity enum for legacy clean-release tests.
class ViolationSeverity(str, Enum):
CRITICAL = "CRITICAL"
MAJOR = "MAJOR"
MINOR = "MINOR"
# #endregion ViolationSeverity
# #region ViolationCategory [TYPE Class]
# @BRIEF Backward-compatible violation category enum for legacy clean-release tests.
class ViolationCategory(str, Enum):
DATA_PURITY = "DATA_PURITY"
EXTERNAL_SOURCE = "EXTERNAL_SOURCE"
SOURCE_ISOLATION = "SOURCE_ISOLATION"
MANIFEST_CONSISTENCY = "MANIFEST_CONSISTENCY"
EXTERNAL_ENDPOINT = "EXTERNAL_ENDPOINT"
# #endregion ViolationCategory
# #region ComplianceViolation [TYPE Class]
# @BRIEF 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 __init__(self, **kwargs):
if "violation_id" in kwargs:
kwargs["id"] = kwargs.pop("violation_id")
if "check_run_id" in kwargs:
kwargs["run_id"] = kwargs.pop("check_run_id")
if "category" in kwargs:
category = kwargs.pop("category")
kwargs["stage_name"] = category.value if isinstance(category, ViolationCategory) else str(category)
if "location" in kwargs:
kwargs["artifact_path"] = kwargs.pop("location")
if "remediation" in kwargs:
remediation = kwargs.pop("remediation")
evidence = dict(kwargs.get("evidence_json") or {})
evidence["remediation"] = remediation
kwargs["evidence_json"] = evidence
if "blocked_release" in kwargs:
blocked_release = kwargs.pop("blocked_release")
evidence = dict(kwargs.get("evidence_json") or {})
evidence["blocked_release"] = blocked_release
kwargs["evidence_json"] = evidence
if "detected_at" in kwargs:
kwargs.pop("detected_at")
if "code" not in kwargs:
kwargs["code"] = "LEGACY_VIOLATION"
if "message" not in kwargs:
kwargs["message"] = kwargs.get("stage_name", "LEGACY_VIOLATION")
super().__init__(**kwargs)
@property
def violation_id(self) -> str:
return self.id
@violation_id.setter
def violation_id(self, value: str) -> None:
self.id = value
@property
def check_run_id(self) -> str:
return self.run_id
@property
def category(self) -> ViolationCategory:
return ViolationCategory(self.stage_name)
@category.setter
def category(self, value: ViolationCategory) -> None:
self.stage_name = value.value if isinstance(value, ViolationCategory) else str(value)
@property
def location(self) -> Optional[str]:
return self.artifact_path
@property
def remediation(self) -> Optional[str]:
return (self.evidence_json or {}).get("remediation")
@property
def blocked_release(self) -> bool:
return bool((self.evidence_json or {}).get("blocked_release", False))
# #endregion ComplianceViolation
# #region ComplianceReport [TYPE Class]
# @BRIEF 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 __init__(self, **kwargs):
if "report_id" in kwargs:
kwargs["id"] = kwargs.pop("report_id")
if "check_run_id" in kwargs:
kwargs["run_id"] = kwargs.pop("check_run_id")
operator_summary = kwargs.pop("operator_summary", None)
structured_payload_ref = kwargs.pop("structured_payload_ref", None)
violations_count = kwargs.pop("violations_count", None)
blocking_violations_count = kwargs.pop("blocking_violations_count", None)
final_status = kwargs.get("final_status")
final_status_value = getattr(final_status, "value", final_status)
if (
final_status_value in {CheckFinalStatus.BLOCKED.value, ComplianceDecision.BLOCKED.value}
and blocking_violations_count is not None
and int(blocking_violations_count) <= 0
):
raise ValueError("blocked report requires blocking violations")
if (
operator_summary is not None
or structured_payload_ref is not None
or violations_count is not None
or blocking_violations_count is not None
):
kwargs["summary_json"] = {
"operator_summary": operator_summary or "",
"structured_payload_ref": structured_payload_ref,
"violations_count": int(violations_count or 0),
"blocking_violations_count": int(blocking_violations_count or 0),
}
super().__init__(**kwargs)
@property
def report_id(self) -> str:
return self.id
@property
def check_run_id(self) -> str:
return self.run_id
@property
def operator_summary(self) -> str:
return (self.summary_json or {}).get("operator_summary", "")
@property
def structured_payload_ref(self) -> Optional[str]:
return (self.summary_json or {}).get("structured_payload_ref")
@property
def violations_count(self) -> int:
return int((self.summary_json or {}).get("violations_count", 0))
@property
def blocking_violations_count(self) -> int:
return int((self.summary_json or {}).get("blocking_violations_count", 0))
# #endregion ComplianceReport
# #region ApprovalDecision [TYPE Class]
# @BRIEF 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)
# #endregion ApprovalDecision
# #region PublicationRecord [TYPE Class]
# @BRIEF 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)
# #endregion PublicationRecord
# #region CleanReleaseAuditLog [TYPE Class]
# @BRIEF 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)
# #endregion CleanReleaseAuditLog
# #endregion CleanReleaseModels