Commit remaining workspace changes

This commit is contained in:
2026-03-13 11:45:06 +03:00
parent 36742cd20c
commit 03a90f58bd
8 changed files with 537 additions and 16 deletions

View File

@@ -36,6 +36,7 @@ from src.models.clean_release import (
ReleaseCandidateStatus,
)
from src.services.clean_release.approval_service import approve_candidate
from src.services.clean_release.artifact_catalog_loader import load_bootstrap_artifacts
from src.services.clean_release.compliance_execution_service import ComplianceExecutionService
from src.services.clean_release.enums import CandidateStatus
from src.services.clean_release.manifest_service import build_manifest_snapshot
@@ -270,6 +271,15 @@ class CleanReleaseTUI:
status=ReleaseCandidateStatus.DRAFT,
)
repository.save_candidate(candidate)
imported_artifacts = load_bootstrap_artifacts(
os.getenv("CLEAN_TUI_ARTIFACTS_JSON", "").strip(),
candidate.id,
)
for artifact in imported_artifacts:
repository.save_artifact(artifact)
if imported_artifacts:
candidate.transition_to(CandidateStatus.PREPARED)
repository.save_candidate(candidate)
registry_id = payload.get("registry_id", "REG-1")
entries = [

View File

@@ -0,0 +1,94 @@
# [DEF:backend.src.services.clean_release.artifact_catalog_loader:Module]
# @TIER: STANDARD
# @SEMANTICS: clean-release, artifacts, bootstrap, json, tui
# @PURPOSE: Load bootstrap artifact catalogs for clean release real-mode flows.
# @LAYER: Domain
# @RELATION: DEPENDS_ON -> backend.src.models.clean_release.CandidateArtifact
# @INVARIANT: Artifact catalog must produce deterministic CandidateArtifact entries with required identity and checksum fields.
from __future__ import annotations
import json
from pathlib import Path
from typing import Any, Dict, List
from ...models.clean_release import CandidateArtifact
# [DEF:load_bootstrap_artifacts:Function]
# @PURPOSE: Parse artifact catalog JSON into CandidateArtifact models for TUI/bootstrap flows.
# @PRE: path points to readable JSON file; payload is list[artifact] or {"artifacts": list[artifact]}.
# @POST: Returns non-mutated CandidateArtifact models with required fields populated.
def load_bootstrap_artifacts(path: str, candidate_id: str) -> List[CandidateArtifact]:
if not path or not path.strip():
return []
if not candidate_id or not candidate_id.strip():
raise ValueError("candidate_id must be non-empty for artifact catalog import")
catalog_path = Path(path)
payload = json.loads(catalog_path.read_text(encoding="utf-8"))
raw_artifacts = payload.get("artifacts") if isinstance(payload, dict) else payload
if not isinstance(raw_artifacts, list):
raise ValueError("artifact catalog must be a list or an object with 'artifacts' list")
artifacts: List[CandidateArtifact] = []
for index, raw_artifact in enumerate(raw_artifacts, start=1):
if not isinstance(raw_artifact, dict):
raise ValueError(f"artifact #{index} must be an object")
artifact_id = str(raw_artifact.get("id", "")).strip()
artifact_path = str(raw_artifact.get("path", "")).strip()
artifact_sha256 = str(raw_artifact.get("sha256", "")).strip()
artifact_size = raw_artifact.get("size")
if not artifact_id:
raise ValueError(f"artifact #{index} missing required field 'id'")
if not artifact_path:
raise ValueError(f"artifact #{index} missing required field 'path'")
if not artifact_sha256:
raise ValueError(f"artifact #{index} missing required field 'sha256'")
if not isinstance(artifact_size, int) or artifact_size < 0:
raise ValueError(f"artifact #{index} field 'size' must be non-negative integer")
category = str(raw_artifact.get("detected_category") or raw_artifact.get("category") or "").strip() or None
source_uri = str(raw_artifact.get("source_uri", "")).strip() or None
source_host = str(raw_artifact.get("source_host", "")).strip() or None
metadata_json = raw_artifact.get("metadata_json")
if metadata_json is None:
metadata_json = {
key: value
for key, value in raw_artifact.items()
if key
not in {
"id",
"path",
"sha256",
"size",
"category",
"detected_category",
"source_uri",
"source_host",
"metadata_json",
}
}
if not isinstance(metadata_json, dict):
raise ValueError(f"artifact #{index} field 'metadata_json' must be object")
artifacts.append(
CandidateArtifact(
id=artifact_id,
candidate_id=candidate_id,
path=artifact_path,
sha256=artifact_sha256,
size=artifact_size,
detected_category=category,
declared_category=category,
source_uri=source_uri,
source_host=source_host,
metadata_json=metadata_json,
)
)
return artifacts
# [/DEF:load_bootstrap_artifacts:Function]
# [/DEF:backend.src.services.clean_release.artifact_catalog_loader:Module]