fix(027): stabilize shared acceptance gates and compatibility collateral
This commit is contained in:
@@ -31,11 +31,12 @@ from ...models.clean_release import (
|
||||
ComplianceRun,
|
||||
ComplianceStageRun,
|
||||
ComplianceViolation,
|
||||
CheckFinalStatus,
|
||||
)
|
||||
from .policy_engine import CleanPolicyEngine
|
||||
from .repository import CleanReleaseRepository
|
||||
from .stages import derive_final_status
|
||||
from ...core.logger import belief_scope
|
||||
from ...core.logger import belief_scope, logger
|
||||
|
||||
|
||||
# [DEF:CleanComplianceOrchestrator:Class]
|
||||
@@ -54,28 +55,71 @@ class CleanComplianceOrchestrator:
|
||||
|
||||
# [DEF:start_check_run:Function]
|
||||
# @PURPOSE: Initiate a new compliance run session.
|
||||
# @PRE: candidate_id/policy_id/manifest_id identify existing records in repository.
|
||||
# @PRE: candidate_id and policy_id are provided; legacy callers may omit persisted manifest/policy records.
|
||||
# @POST: Returns initialized ComplianceRun in RUNNING state persisted in repository.
|
||||
# @SIDE_EFFECT: Reads manifest/policy and writes new ComplianceRun via repository.save_check_run.
|
||||
# @DATA_CONTRACT: Input -> (candidate_id:str, policy_id:str, requested_by:str, manifest_id:str), Output -> ComplianceRun
|
||||
def start_check_run(self, candidate_id: str, policy_id: str, requested_by: str, manifest_id: str) -> ComplianceRun:
|
||||
# @SIDE_EFFECT: Reads manifest/policy when present and writes new ComplianceRun via repository.save_check_run.
|
||||
# @DATA_CONTRACT: Input -> (candidate_id:str, policy_id:str, requested_by:str, manifest_id:str|None), Output -> ComplianceRun
|
||||
def start_check_run(
|
||||
self,
|
||||
candidate_id: str,
|
||||
policy_id: str,
|
||||
requested_by: str | None = None,
|
||||
manifest_id: str | None = None,
|
||||
**legacy_kwargs,
|
||||
) -> ComplianceRun:
|
||||
with belief_scope("start_check_run"):
|
||||
manifest = self.repository.get_manifest(manifest_id)
|
||||
actor = requested_by or legacy_kwargs.get("triggered_by") or "system"
|
||||
execution_mode = str(legacy_kwargs.get("execution_mode") or "").strip().lower()
|
||||
manifest_id_value = manifest_id
|
||||
|
||||
if manifest_id_value and str(manifest_id_value).strip().lower() in {"tui", "api", "scheduler"}:
|
||||
logger.reason(
|
||||
"Detected legacy positional execution_mode passed through manifest_id slot",
|
||||
extra={"candidate_id": candidate_id, "execution_mode": manifest_id_value},
|
||||
)
|
||||
execution_mode = str(manifest_id_value).strip().lower()
|
||||
manifest_id_value = None
|
||||
|
||||
manifest = self.repository.get_manifest(manifest_id_value) if manifest_id_value else None
|
||||
policy = self.repository.get_policy(policy_id)
|
||||
if not manifest or not policy:
|
||||
|
||||
if manifest_id_value and manifest is None:
|
||||
logger.explore(
|
||||
"Manifest lookup missed during run start; rejecting explicit manifest contract",
|
||||
extra={"candidate_id": candidate_id, "manifest_id": manifest_id_value},
|
||||
)
|
||||
raise ValueError("Manifest or Policy not found")
|
||||
|
||||
if policy is None:
|
||||
logger.explore(
|
||||
"Policy lookup missed during run start; using compatibility placeholder snapshot",
|
||||
extra={"candidate_id": candidate_id, "policy_id": policy_id, "execution_mode": execution_mode or "unspecified"},
|
||||
)
|
||||
|
||||
manifest_id_value = manifest_id_value or f"manifest-{candidate_id}"
|
||||
manifest_digest = getattr(manifest, "manifest_digest", "pending")
|
||||
registry_snapshot_id = (
|
||||
getattr(policy, "registry_snapshot_id", None)
|
||||
or getattr(policy, "internal_source_registry_ref", None)
|
||||
or "pending"
|
||||
)
|
||||
|
||||
check_run = ComplianceRun(
|
||||
id=f"check-{uuid4()}",
|
||||
candidate_id=candidate_id,
|
||||
manifest_id=manifest_id,
|
||||
manifest_digest=manifest.manifest_digest,
|
||||
manifest_id=manifest_id_value,
|
||||
manifest_digest=manifest_digest,
|
||||
policy_snapshot_id=policy_id,
|
||||
registry_snapshot_id=policy.registry_snapshot_id,
|
||||
requested_by=requested_by,
|
||||
registry_snapshot_id=registry_snapshot_id,
|
||||
requested_by=actor,
|
||||
requested_at=datetime.now(timezone.utc),
|
||||
started_at=datetime.now(timezone.utc),
|
||||
status=RunStatus.RUNNING,
|
||||
)
|
||||
logger.reflect(
|
||||
"Initialized compliance run with compatibility-safe dependency placeholders",
|
||||
extra={"run_id": check_run.id, "candidate_id": candidate_id, "policy_id": policy_id},
|
||||
)
|
||||
return self.repository.save_check_run(check_run)
|
||||
# [/DEF:start_check_run:Function]
|
||||
|
||||
@@ -88,33 +132,46 @@ class CleanComplianceOrchestrator:
|
||||
def execute_stages(self, check_run: ComplianceRun, forced_results: Optional[List[ComplianceStageRun]] = None) -> ComplianceRun:
|
||||
with belief_scope("execute_stages"):
|
||||
if forced_results is not None:
|
||||
# In a real scenario, we'd persist these stages.
|
||||
for index, result in enumerate(forced_results, start=1):
|
||||
if isinstance(result, ComplianceStageRun):
|
||||
stage_run = result
|
||||
else:
|
||||
status_value = getattr(result, "status", None)
|
||||
if status_value == "PASS":
|
||||
decision = ComplianceDecision.PASSED.value
|
||||
elif status_value == "FAIL":
|
||||
decision = ComplianceDecision.BLOCKED.value
|
||||
else:
|
||||
decision = ComplianceDecision.ERROR.value
|
||||
stage_run = ComplianceStageRun(
|
||||
id=f"{check_run.id}-stage-{index}",
|
||||
run_id=check_run.id,
|
||||
stage_name=result.stage.value,
|
||||
status=result.status.value,
|
||||
decision=decision,
|
||||
details_json={"details": result.details},
|
||||
)
|
||||
self.repository.stage_runs[stage_run.id] = stage_run
|
||||
|
||||
check_run.final_status = derive_final_status(forced_results).value
|
||||
check_run.status = RunStatus.SUCCEEDED
|
||||
return self.repository.save_check_run(check_run)
|
||||
|
||||
# Real Logic Integration
|
||||
candidate = self.repository.get_candidate(check_run.candidate_id)
|
||||
policy = self.repository.get_policy(check_run.policy_snapshot_id)
|
||||
if not candidate or not policy:
|
||||
check_run.status = RunStatus.FAILED
|
||||
return self.repository.save_check_run(check_run)
|
||||
|
||||
registry = self.repository.get_registry(check_run.registry_snapshot_id)
|
||||
manifest = self.repository.get_manifest(check_run.manifest_id)
|
||||
|
||||
if not registry or not manifest:
|
||||
if not candidate or not policy or not registry or not manifest:
|
||||
check_run.status = RunStatus.FAILED
|
||||
check_run.finished_at = datetime.now(timezone.utc)
|
||||
return self.repository.save_check_run(check_run)
|
||||
|
||||
# Simulate stage execution and violation detection
|
||||
# 1. DATA_PURITY
|
||||
summary = manifest.content_json.get("summary", {})
|
||||
purity_ok = summary.get("prohibited_detected_count", 0) == 0
|
||||
|
||||
if not purity_ok:
|
||||
check_run.final_status = ComplianceDecision.BLOCKED
|
||||
else:
|
||||
check_run.final_status = ComplianceDecision.PASSED
|
||||
|
||||
check_run.final_status = (
|
||||
ComplianceDecision.PASSED.value if purity_ok else ComplianceDecision.BLOCKED.value
|
||||
)
|
||||
check_run.status = RunStatus.SUCCEEDED
|
||||
check_run.finished_at = datetime.now(timezone.utc)
|
||||
|
||||
@@ -129,9 +186,18 @@ class CleanComplianceOrchestrator:
|
||||
# @DATA_CONTRACT: Input -> ComplianceRun, Output -> ComplianceRun
|
||||
def finalize_run(self, check_run: ComplianceRun) -> ComplianceRun:
|
||||
with belief_scope("finalize_run"):
|
||||
# If not already set by execute_stages
|
||||
if check_run.status == RunStatus.FAILED:
|
||||
check_run.finished_at = datetime.now(timezone.utc)
|
||||
return self.repository.save_check_run(check_run)
|
||||
|
||||
if not check_run.final_status:
|
||||
check_run.final_status = ComplianceDecision.PASSED
|
||||
stage_results = [
|
||||
stage_run
|
||||
for stage_run in self.repository.stage_runs.values()
|
||||
if stage_run.run_id == check_run.id
|
||||
]
|
||||
derived = derive_final_status(stage_results)
|
||||
check_run.final_status = derived.value
|
||||
|
||||
check_run.status = RunStatus.SUCCEEDED
|
||||
check_run.finished_at = datetime.now(timezone.utc)
|
||||
|
||||
@@ -13,7 +13,12 @@ from dataclasses import dataclass
|
||||
from typing import Dict, Iterable, List, Tuple
|
||||
|
||||
from ...core.logger import belief_scope, logger
|
||||
from ...models.clean_release import CleanPolicySnapshot, SourceRegistrySnapshot
|
||||
from ...models.clean_release import (
|
||||
CleanPolicySnapshot,
|
||||
SourceRegistrySnapshot,
|
||||
CleanProfilePolicy,
|
||||
ResourceSourceRegistry,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -39,7 +44,11 @@ class SourceValidationResult:
|
||||
# @TEST_EDGE: external_endpoint -> endpoint not present in enabled internal registry entries
|
||||
# @TEST_INVARIANT: deterministic_classification -> VERIFIED_BY: [policy_valid]
|
||||
class CleanPolicyEngine:
|
||||
def __init__(self, policy: CleanPolicySnapshot, registry: SourceRegistrySnapshot):
|
||||
def __init__(
|
||||
self,
|
||||
policy: CleanPolicySnapshot | CleanProfilePolicy,
|
||||
registry: SourceRegistrySnapshot | ResourceSourceRegistry,
|
||||
):
|
||||
self.policy = policy
|
||||
self.registry = registry
|
||||
|
||||
@@ -48,23 +57,45 @@ class CleanPolicyEngine:
|
||||
logger.reason("Validating enterprise-clean policy and internal registry consistency")
|
||||
reasons: List[str] = []
|
||||
|
||||
# Snapshots are immutable and assumed active if resolved by facade
|
||||
if not self.policy.registry_snapshot_id.strip():
|
||||
reasons.append("Policy missing registry_snapshot_id")
|
||||
|
||||
content = self.policy.content_json or {}
|
||||
registry_ref = (
|
||||
getattr(self.policy, "registry_snapshot_id", None)
|
||||
or getattr(self.policy, "internal_source_registry_ref", "")
|
||||
or ""
|
||||
)
|
||||
if not str(registry_ref).strip():
|
||||
reasons.append("Policy missing internal_source_registry_ref")
|
||||
|
||||
content = dict(getattr(self.policy, "content_json", None) or {})
|
||||
if not content:
|
||||
content = {
|
||||
"profile": getattr(getattr(self.policy, "profile", None), "value", getattr(self.policy, "profile", "standard")),
|
||||
"prohibited_artifact_categories": list(
|
||||
getattr(self.policy, "prohibited_artifact_categories", []) or []
|
||||
),
|
||||
"required_system_categories": list(
|
||||
getattr(self.policy, "required_system_categories", []) or []
|
||||
),
|
||||
"external_source_forbidden": getattr(self.policy, "external_source_forbidden", False),
|
||||
}
|
||||
|
||||
profile = content.get("profile", "standard")
|
||||
|
||||
|
||||
if profile == "enterprise-clean":
|
||||
if not content.get("prohibited_artifact_categories"):
|
||||
reasons.append("Enterprise policy requires prohibited artifact categories")
|
||||
if not content.get("external_source_forbidden"):
|
||||
reasons.append("Enterprise policy requires external_source_forbidden=true")
|
||||
|
||||
if self.registry.id != self.policy.registry_snapshot_id:
|
||||
|
||||
registry_id = getattr(self.registry, "id", None) or getattr(self.registry, "registry_id", None)
|
||||
if registry_id != registry_ref:
|
||||
reasons.append("Policy registry ref does not match provided registry")
|
||||
|
||||
if not self.registry.allowed_hosts:
|
||||
|
||||
allowed_hosts = getattr(self.registry, "allowed_hosts", None)
|
||||
if allowed_hosts is None:
|
||||
entries = getattr(self.registry, "entries", []) or []
|
||||
allowed_hosts = [entry.host for entry in entries if getattr(entry, "enabled", True)]
|
||||
|
||||
if not allowed_hosts:
|
||||
reasons.append("Registry must contain allowed hosts")
|
||||
|
||||
logger.reflect(f"Policy validation completed. blocking_reasons={len(reasons)}")
|
||||
@@ -72,8 +103,17 @@ class CleanPolicyEngine:
|
||||
|
||||
def classify_artifact(self, artifact: Dict) -> str:
|
||||
category = (artifact.get("category") or "").strip()
|
||||
content = self.policy.content_json or {}
|
||||
|
||||
content = dict(getattr(self.policy, "content_json", None) or {})
|
||||
if not content:
|
||||
content = {
|
||||
"required_system_categories": list(
|
||||
getattr(self.policy, "required_system_categories", []) or []
|
||||
),
|
||||
"prohibited_artifact_categories": list(
|
||||
getattr(self.policy, "prohibited_artifact_categories", []) or []
|
||||
),
|
||||
}
|
||||
|
||||
required = content.get("required_system_categories", [])
|
||||
prohibited = content.get("prohibited_artifact_categories", [])
|
||||
|
||||
@@ -100,7 +140,11 @@ class CleanPolicyEngine:
|
||||
},
|
||||
)
|
||||
|
||||
allowed_hosts = set(self.registry.allowed_hosts or [])
|
||||
allowed_hosts = getattr(self.registry, "allowed_hosts", None)
|
||||
if allowed_hosts is None:
|
||||
entries = getattr(self.registry, "entries", []) or []
|
||||
allowed_hosts = [entry.host for entry in entries if getattr(entry, "enabled", True)]
|
||||
allowed_hosts = set(allowed_hosts or [])
|
||||
normalized = endpoint.strip().lower()
|
||||
|
||||
if normalized in allowed_hosts:
|
||||
|
||||
@@ -17,6 +17,7 @@ from .manifest_builder import build_distribution_manifest
|
||||
from .policy_engine import CleanPolicyEngine
|
||||
from .repository import CleanReleaseRepository
|
||||
from .enums import CandidateStatus
|
||||
from ...models.clean_release import ReleaseCandidateStatus
|
||||
|
||||
|
||||
def prepare_candidate(
|
||||
@@ -34,7 +35,11 @@ def prepare_candidate(
|
||||
if policy is None:
|
||||
raise ValueError("Active clean policy not found")
|
||||
|
||||
registry = repository.get_registry(policy.registry_snapshot_id)
|
||||
registry_ref = (
|
||||
getattr(policy, "registry_snapshot_id", None)
|
||||
or getattr(policy, "internal_source_registry_ref", None)
|
||||
)
|
||||
registry = repository.get_registry(registry_ref) if registry_ref else None
|
||||
if registry is None:
|
||||
raise ValueError("Registry not found for active policy")
|
||||
|
||||
@@ -48,22 +53,29 @@ def prepare_candidate(
|
||||
manifest = build_distribution_manifest(
|
||||
manifest_id=f"manifest-{candidate_id}",
|
||||
candidate_id=candidate_id,
|
||||
policy_id=policy.policy_id,
|
||||
policy_id=getattr(policy, "policy_id", None) or getattr(policy, "id", ""),
|
||||
generated_by=operator_id,
|
||||
artifacts=classified,
|
||||
)
|
||||
repository.save_manifest(manifest)
|
||||
|
||||
# Note: In the new model, BLOCKED is a ComplianceDecision, not a CandidateStatus.
|
||||
# CandidateStatus.PREPARED is the correct next state after preparation.
|
||||
candidate.transition_to(CandidateStatus.PREPARED)
|
||||
repository.save_candidate(candidate)
|
||||
current_status = getattr(candidate, "status", None)
|
||||
if violations:
|
||||
candidate.status = ReleaseCandidateStatus.BLOCKED.value
|
||||
repository.save_candidate(candidate)
|
||||
response_status = ReleaseCandidateStatus.BLOCKED.value
|
||||
else:
|
||||
if current_status in {CandidateStatus.DRAFT, CandidateStatus.DRAFT.value, "DRAFT"}:
|
||||
candidate.transition_to(CandidateStatus.PREPARED)
|
||||
else:
|
||||
candidate.status = ReleaseCandidateStatus.PREPARED.value
|
||||
repository.save_candidate(candidate)
|
||||
response_status = ReleaseCandidateStatus.PREPARED.value
|
||||
|
||||
status_value = candidate.status.value if hasattr(candidate.status, "value") else str(candidate.status)
|
||||
manifest_id_value = getattr(manifest, "manifest_id", None) or getattr(manifest, "id", "")
|
||||
return {
|
||||
"candidate_id": candidate_id,
|
||||
"status": status_value,
|
||||
"status": response_status,
|
||||
"manifest_id": manifest_id_value,
|
||||
"violations": violations,
|
||||
"prepared_at": datetime.now(timezone.utc).isoformat(),
|
||||
|
||||
@@ -11,7 +11,12 @@ from __future__ import annotations
|
||||
from typing import Dict, Iterable, List
|
||||
|
||||
from ..enums import ComplianceDecision, ComplianceStageName
|
||||
from ....models.clean_release import ComplianceStageRun
|
||||
from ....models.clean_release import (
|
||||
ComplianceStageRun,
|
||||
CheckFinalStatus,
|
||||
CheckStageResult,
|
||||
CheckStageStatus,
|
||||
)
|
||||
from .base import ComplianceStage
|
||||
from .data_purity import DataPurityStage
|
||||
from .internal_sources_only import InternalSourcesOnlyStage
|
||||
@@ -44,8 +49,34 @@ def build_default_stages() -> List[ComplianceStage]:
|
||||
# @PURPOSE: Convert stage result list to dictionary by stage name.
|
||||
# @PRE: stage_results may be empty or contain unique stage names.
|
||||
# @POST: Returns stage->status dictionary for downstream evaluation.
|
||||
def stage_result_map(stage_results: Iterable[ComplianceStageRun]) -> Dict[ComplianceStageName, ComplianceDecision]:
|
||||
return {ComplianceStageName(result.stage_name): ComplianceDecision(result.decision) for result in stage_results if result.decision}
|
||||
def stage_result_map(
|
||||
stage_results: Iterable[ComplianceStageRun | CheckStageResult],
|
||||
) -> Dict[ComplianceStageName, CheckStageStatus]:
|
||||
normalized: Dict[ComplianceStageName, CheckStageStatus] = {}
|
||||
for result in stage_results:
|
||||
if isinstance(result, CheckStageResult):
|
||||
normalized[ComplianceStageName(result.stage.value)] = CheckStageStatus(result.status.value)
|
||||
continue
|
||||
|
||||
stage_name = getattr(result, "stage_name", None)
|
||||
decision = getattr(result, "decision", None)
|
||||
status = getattr(result, "status", None)
|
||||
|
||||
if not stage_name:
|
||||
continue
|
||||
|
||||
normalized_stage = ComplianceStageName(stage_name)
|
||||
if decision == ComplianceDecision.BLOCKED:
|
||||
normalized[normalized_stage] = CheckStageStatus.FAIL
|
||||
elif decision == ComplianceDecision.ERROR:
|
||||
normalized[normalized_stage] = CheckStageStatus.SKIPPED
|
||||
elif decision == ComplianceDecision.PASSED:
|
||||
normalized[normalized_stage] = CheckStageStatus.PASS
|
||||
elif decision:
|
||||
normalized[normalized_stage] = CheckStageStatus(str(decision))
|
||||
elif status:
|
||||
normalized[normalized_stage] = CheckStageStatus(str(status))
|
||||
return normalized
|
||||
# [/DEF:stage_result_map:Function]
|
||||
|
||||
|
||||
@@ -53,7 +84,7 @@ def stage_result_map(stage_results: Iterable[ComplianceStageRun]) -> Dict[Compli
|
||||
# @PURPOSE: Identify mandatory stages that are absent from run results.
|
||||
# @PRE: stage_status_map contains zero or more known stage statuses.
|
||||
# @POST: Returns ordered list of missing mandatory stages.
|
||||
def missing_mandatory_stages(stage_status_map: Dict[ComplianceStageName, ComplianceDecision]) -> List[ComplianceStageName]:
|
||||
def missing_mandatory_stages(stage_status_map: Dict[ComplianceStageName, CheckStageStatus]) -> List[ComplianceStageName]:
|
||||
return [stage for stage in MANDATORY_STAGE_ORDER if stage not in stage_status_map]
|
||||
# [/DEF:missing_mandatory_stages:Function]
|
||||
|
||||
@@ -62,19 +93,19 @@ def missing_mandatory_stages(stage_status_map: Dict[ComplianceStageName, Complia
|
||||
# @PURPOSE: Derive final run status from stage results with deterministic blocking behavior.
|
||||
# @PRE: Stage statuses correspond to compliance checks.
|
||||
# @POST: Returns one of PASSED/BLOCKED/ERROR according to mandatory stage outcomes.
|
||||
def derive_final_status(stage_results: Iterable[ComplianceStageRun]) -> ComplianceDecision:
|
||||
def derive_final_status(stage_results: Iterable[ComplianceStageRun | CheckStageResult]) -> CheckFinalStatus:
|
||||
status_map = stage_result_map(stage_results)
|
||||
missing = missing_mandatory_stages(status_map)
|
||||
if missing:
|
||||
return ComplianceDecision.ERROR
|
||||
return CheckFinalStatus.FAILED
|
||||
|
||||
for stage in MANDATORY_STAGE_ORDER:
|
||||
decision = status_map.get(stage)
|
||||
if decision == ComplianceDecision.ERROR:
|
||||
return ComplianceDecision.ERROR
|
||||
if decision == ComplianceDecision.BLOCKED:
|
||||
return ComplianceDecision.BLOCKED
|
||||
if decision == CheckStageStatus.SKIPPED:
|
||||
return CheckFinalStatus.FAILED
|
||||
if decision == CheckStageStatus.FAIL:
|
||||
return CheckFinalStatus.BLOCKED
|
||||
|
||||
return ComplianceDecision.PASSED
|
||||
return CheckFinalStatus.COMPLIANT
|
||||
# [/DEF:derive_final_status:Function]
|
||||
# [/DEF:backend.src.services.clean_release.stages:Module]
|
||||
@@ -50,6 +50,7 @@ class GitService:
|
||||
with belief_scope("GitService.__init__"):
|
||||
backend_root = Path(__file__).parents[2]
|
||||
self.legacy_base_path = str((backend_root / "git_repos").resolve())
|
||||
self._uses_default_base_path = base_path == "git_repos"
|
||||
self.base_path = self._resolve_base_path(base_path)
|
||||
self._ensure_base_path_exists()
|
||||
# [/DEF:backend.src.services.git_service.GitService.__init__:Function]
|
||||
@@ -281,6 +282,9 @@ class GitService:
|
||||
normalized_key = self._normalize_repo_key(fallback_key)
|
||||
target_path = os.path.join(self.base_path, normalized_key)
|
||||
|
||||
if not self._uses_default_base_path:
|
||||
return target_path
|
||||
|
||||
try:
|
||||
session = SessionLocal()
|
||||
try:
|
||||
@@ -345,10 +349,14 @@ class GitService:
|
||||
logger.warning(
|
||||
f"[init_repo][Action] Existing path is not a Git repository, recreating: {repo_path}"
|
||||
)
|
||||
if os.path.isdir(repo_path):
|
||||
shutil.rmtree(repo_path)
|
||||
else:
|
||||
os.remove(repo_path)
|
||||
stale_path = Path(repo_path)
|
||||
if stale_path.exists():
|
||||
shutil.rmtree(stale_path, ignore_errors=True)
|
||||
if stale_path.exists():
|
||||
try:
|
||||
stale_path.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
repo = Repo.clone_from(auth_url, repo_path)
|
||||
self._ensure_gitflow_branches(repo, dashboard_id)
|
||||
return repo
|
||||
|
||||
@@ -23,14 +23,25 @@ MASKED_API_KEY_PLACEHOLDER = "********"
|
||||
# @PURPOSE: Load and validate the Fernet key used for secret encryption.
|
||||
# @PRE: ENCRYPTION_KEY environment variable must be set to a valid Fernet key.
|
||||
# @POST: Returns validated key bytes ready for Fernet initialization.
|
||||
# @RELATION: DEPENDS_ON -> backend.src.core.logger
|
||||
# @SIDE_EFFECT: Emits belief-state logs for missing or invalid encryption configuration.
|
||||
# @INVARIANT: Encryption initialization never falls back to a hardcoded secret.
|
||||
def _require_fernet_key() -> bytes:
|
||||
raw_key = os.getenv("ENCRYPTION_KEY", "").strip()
|
||||
if not raw_key:
|
||||
raise RuntimeError("ENCRYPTION_KEY must be set to a valid Fernet key")
|
||||
with belief_scope("_require_fernet_key"):
|
||||
raw_key = os.getenv("ENCRYPTION_KEY", "").strip()
|
||||
if not raw_key:
|
||||
logger.explore("Missing ENCRYPTION_KEY blocks EncryptionManager initialization")
|
||||
raise RuntimeError("ENCRYPTION_KEY must be set")
|
||||
|
||||
key = raw_key.encode()
|
||||
Fernet(key)
|
||||
return key
|
||||
key = raw_key.encode()
|
||||
try:
|
||||
Fernet(key)
|
||||
except Exception as exc:
|
||||
logger.explore("Invalid ENCRYPTION_KEY blocks EncryptionManager initialization")
|
||||
raise RuntimeError("ENCRYPTION_KEY must be a valid Fernet key") from exc
|
||||
|
||||
logger.reflect("Validated ENCRYPTION_KEY for EncryptionManager initialization")
|
||||
return key
|
||||
# [/DEF:_require_fernet_key:Function]
|
||||
|
||||
# [DEF:EncryptionManager:Class]
|
||||
|
||||
Reference in New Issue
Block a user