semantics
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
# [DEF:backend.src.scripts.clean_release_tui:Module]
|
||||
# [DEF:CleanReleaseTuiScript:Module]
|
||||
# @COMPLEXITY: 3
|
||||
# @SEMANTICS: clean-release, tui, ncurses, interactive-validator
|
||||
# @PURPOSE: Interactive terminal interface for Enterprise Clean Release compliance validation.
|
||||
# @LAYER: UI
|
||||
# @RELATION: DEPENDS_ON -> backend.src.services.clean_release.compliance_orchestrator
|
||||
# @RELATION: DEPENDS_ON -> backend.src.services.clean_release.repository
|
||||
# @RELATION: DEPENDS_ON -> [compliance_orchestrator]
|
||||
# @RELATION: DEPENDS_ON -> [repository]
|
||||
# @INVARIANT: TUI refuses startup in non-TTY environments; headless flow is CLI/API only.
|
||||
|
||||
import curses
|
||||
@@ -37,12 +37,15 @@ from src.models.clean_release import (
|
||||
)
|
||||
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.compliance_execution_service import (
|
||||
ComplianceExecutionService,
|
||||
)
|
||||
from src.services.clean_release.enums import CandidateStatus
|
||||
from src.services.clean_release.manifest_service import build_manifest_snapshot
|
||||
from src.services.clean_release.publication_service import publish_candidate
|
||||
from src.services.clean_release.repository import CleanReleaseRepository
|
||||
|
||||
|
||||
# [DEF:TuiFacadeAdapter:Class]
|
||||
# @PURPOSE: Thin TUI adapter that routes business mutations through application services.
|
||||
# @PRE: repository contains candidate and trusted policy/registry snapshots for execution.
|
||||
@@ -67,15 +70,25 @@ class TuiFacadeAdapter:
|
||||
manifests = self.repository.get_manifests_by_candidate(candidate_id)
|
||||
if not manifests:
|
||||
raise ValueError("Manifest required before compliance run")
|
||||
latest_manifest = sorted(manifests, key=lambda item: item.manifest_version, reverse=True)[0]
|
||||
latest_manifest = sorted(
|
||||
manifests, key=lambda item: item.manifest_version, reverse=True
|
||||
)[0]
|
||||
service = ComplianceExecutionService(
|
||||
repository=self.repository,
|
||||
config_manager=self._build_config_manager(),
|
||||
)
|
||||
return service.execute_run(candidate_id=candidate_id, requested_by=actor, manifest_id=latest_manifest.id)
|
||||
return service.execute_run(
|
||||
candidate_id=candidate_id,
|
||||
requested_by=actor,
|
||||
manifest_id=latest_manifest.id,
|
||||
)
|
||||
|
||||
def approve_latest(self, *, candidate_id: str, actor: str):
|
||||
reports = [item for item in self.repository.reports.values() if item.candidate_id == candidate_id]
|
||||
reports = [
|
||||
item
|
||||
for item in self.repository.reports.values()
|
||||
if item.candidate_id == candidate_id
|
||||
]
|
||||
if not reports:
|
||||
raise ValueError("No compliance report available for approval")
|
||||
report = sorted(reports, key=lambda item: item.generated_at, reverse=True)[0]
|
||||
@@ -88,7 +101,11 @@ class TuiFacadeAdapter:
|
||||
)
|
||||
|
||||
def publish_latest(self, *, candidate_id: str, actor: str):
|
||||
reports = [item for item in self.repository.reports.values() if item.candidate_id == candidate_id]
|
||||
reports = [
|
||||
item
|
||||
for item in self.repository.reports.values()
|
||||
if item.candidate_id == candidate_id
|
||||
]
|
||||
if not reports:
|
||||
raise ValueError("No compliance report available for publication")
|
||||
report = sorted(reports, key=lambda item: item.generated_at, reverse=True)[0]
|
||||
@@ -111,24 +128,55 @@ class TuiFacadeAdapter:
|
||||
def get_overview(self, *, candidate_id: str) -> Dict[str, Any]:
|
||||
candidate = self.repository.get_candidate(candidate_id)
|
||||
manifests = self.repository.get_manifests_by_candidate(candidate_id)
|
||||
latest_manifest = sorted(manifests, key=lambda item: item.manifest_version, reverse=True)[0] if manifests else None
|
||||
runs = [item for item in self.repository.check_runs.values() if item.candidate_id == candidate_id]
|
||||
latest_run = sorted(runs, key=lambda item: item.requested_at, reverse=True)[0] if runs else None
|
||||
latest_report = next((item for item in self.repository.reports.values() if latest_run and item.run_id == latest_run.id), None)
|
||||
latest_manifest = (
|
||||
sorted(manifests, key=lambda item: item.manifest_version, reverse=True)[0]
|
||||
if manifests
|
||||
else None
|
||||
)
|
||||
runs = [
|
||||
item
|
||||
for item in self.repository.check_runs.values()
|
||||
if item.candidate_id == candidate_id
|
||||
]
|
||||
latest_run = (
|
||||
sorted(runs, key=lambda item: item.requested_at, reverse=True)[0]
|
||||
if runs
|
||||
else None
|
||||
)
|
||||
latest_report = next(
|
||||
(
|
||||
item
|
||||
for item in self.repository.reports.values()
|
||||
if latest_run and item.run_id == latest_run.id
|
||||
),
|
||||
None,
|
||||
)
|
||||
approvals = getattr(self.repository, "approval_decisions", [])
|
||||
latest_approval = sorted(
|
||||
[item for item in approvals if item.candidate_id == candidate_id],
|
||||
key=lambda item: item.decided_at,
|
||||
reverse=True,
|
||||
)[0] if any(item.candidate_id == candidate_id for item in approvals) else None
|
||||
latest_approval = (
|
||||
sorted(
|
||||
[item for item in approvals if item.candidate_id == candidate_id],
|
||||
key=lambda item: item.decided_at,
|
||||
reverse=True,
|
||||
)[0]
|
||||
if any(item.candidate_id == candidate_id for item in approvals)
|
||||
else None
|
||||
)
|
||||
publications = getattr(self.repository, "publication_records", [])
|
||||
latest_publication = sorted(
|
||||
[item for item in publications if item.candidate_id == candidate_id],
|
||||
key=lambda item: item.published_at,
|
||||
reverse=True,
|
||||
)[0] if any(item.candidate_id == candidate_id for item in publications) else None
|
||||
latest_publication = (
|
||||
sorted(
|
||||
[item for item in publications if item.candidate_id == candidate_id],
|
||||
key=lambda item: item.published_at,
|
||||
reverse=True,
|
||||
)[0]
|
||||
if any(item.candidate_id == candidate_id for item in publications)
|
||||
else None
|
||||
)
|
||||
policy = self.repository.get_active_policy()
|
||||
registry = self.repository.get_registry(policy.internal_source_registry_ref) if policy else None
|
||||
registry = (
|
||||
self.repository.get_registry(policy.internal_source_registry_ref)
|
||||
if policy
|
||||
else None
|
||||
)
|
||||
return {
|
||||
"candidate": candidate,
|
||||
"manifest": latest_manifest,
|
||||
@@ -139,6 +187,8 @@ class TuiFacadeAdapter:
|
||||
"policy": policy,
|
||||
"registry": registry,
|
||||
}
|
||||
|
||||
|
||||
# [/DEF:TuiFacadeAdapter:Class]
|
||||
|
||||
|
||||
@@ -166,11 +216,11 @@ class CleanReleaseTUI:
|
||||
|
||||
curses.start_color()
|
||||
curses.use_default_colors()
|
||||
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) # Header/Footer
|
||||
curses.init_pair(2, curses.COLOR_GREEN, -1) # PASS
|
||||
curses.init_pair(3, curses.COLOR_RED, -1) # FAIL/BLOCKED
|
||||
curses.init_pair(4, curses.COLOR_YELLOW, -1) # RUNNING
|
||||
curses.init_pair(5, curses.COLOR_CYAN, -1) # Text
|
||||
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) # Header/Footer
|
||||
curses.init_pair(2, curses.COLOR_GREEN, -1) # PASS
|
||||
curses.init_pair(3, curses.COLOR_RED, -1) # FAIL/BLOCKED
|
||||
curses.init_pair(4, curses.COLOR_YELLOW, -1) # RUNNING
|
||||
curses.init_pair(5, curses.COLOR_CYAN, -1) # Text
|
||||
|
||||
def _build_repository(self, mode: str) -> CleanReleaseRepository:
|
||||
repo = CleanReleaseRepository()
|
||||
@@ -317,7 +367,9 @@ class CleanReleaseTUI:
|
||||
"prohibited_artifact_categories",
|
||||
["test-data", "demo", "load-test"],
|
||||
),
|
||||
required_system_categories=payload.get("required_system_categories", ["core"]),
|
||||
required_system_categories=payload.get(
|
||||
"required_system_categories", ["core"]
|
||||
),
|
||||
effective_from=now,
|
||||
)
|
||||
)
|
||||
@@ -354,18 +406,21 @@ class CleanReleaseTUI:
|
||||
self.stdscr.addstr(4, 3, "Checks:")
|
||||
check_defs = [
|
||||
(CheckStageName.DATA_PURITY, "Data Purity (no test/demo payloads)"),
|
||||
(CheckStageName.INTERNAL_SOURCES_ONLY, "Internal Sources Only (company servers)"),
|
||||
(
|
||||
CheckStageName.INTERNAL_SOURCES_ONLY,
|
||||
"Internal Sources Only (company servers)",
|
||||
),
|
||||
(CheckStageName.NO_EXTERNAL_ENDPOINTS, "No External Internet Endpoints"),
|
||||
(CheckStageName.MANIFEST_CONSISTENCY, "Release Manifest Consistency"),
|
||||
]
|
||||
|
||||
|
||||
row = 5
|
||||
drawn_checks = {c["stage"]: c for c in self.checks_progress}
|
||||
|
||||
|
||||
for stage, desc in check_defs:
|
||||
status_text = " "
|
||||
color = curses.color_pair(5)
|
||||
|
||||
|
||||
if stage in drawn_checks:
|
||||
c = drawn_checks[stage]
|
||||
if c["status"] == "RUNNING":
|
||||
@@ -377,7 +432,7 @@ class CleanReleaseTUI:
|
||||
elif c["status"] == CheckStageStatus.FAIL:
|
||||
status_text = "FAIL"
|
||||
color = curses.color_pair(3)
|
||||
|
||||
|
||||
self.stdscr.addstr(row, 4, f"[{status_text:^4}] {desc}")
|
||||
if status_text != " ":
|
||||
self.stdscr.addstr(row, 50, f"{status_text:>10}", color | curses.A_BOLD)
|
||||
@@ -396,12 +451,18 @@ class CleanReleaseTUI:
|
||||
|
||||
def draw_status(self):
|
||||
color = curses.color_pair(5)
|
||||
if self.status == CheckFinalStatus.COMPLIANT: color = curses.color_pair(2)
|
||||
elif self.status == CheckFinalStatus.BLOCKED: color = curses.color_pair(3)
|
||||
|
||||
stat_str = str(self.status.value if hasattr(self.status, "value") else self.status)
|
||||
self.stdscr.addstr(18, 3, f"FINAL STATUS: {stat_str.upper()}", color | curses.A_BOLD)
|
||||
|
||||
if self.status == CheckFinalStatus.COMPLIANT:
|
||||
color = curses.color_pair(2)
|
||||
elif self.status == CheckFinalStatus.BLOCKED:
|
||||
color = curses.color_pair(3)
|
||||
|
||||
stat_str = str(
|
||||
self.status.value if hasattr(self.status, "value") else self.status
|
||||
)
|
||||
self.stdscr.addstr(
|
||||
18, 3, f"FINAL STATUS: {stat_str.upper()}", color | curses.A_BOLD
|
||||
)
|
||||
|
||||
if self.report_id:
|
||||
self.stdscr.addstr(19, 3, f"Report ID: {self.report_id}")
|
||||
|
||||
@@ -413,22 +474,36 @@ class CleanReleaseTUI:
|
||||
self.stdscr.addstr(20, 32, f"Publication: {publication.status}")
|
||||
|
||||
if self.violations_list:
|
||||
self.stdscr.addstr(21, 3, f"Violations Details ({len(self.violations_list)} total):", curses.color_pair(3) | curses.A_BOLD)
|
||||
self.stdscr.addstr(
|
||||
21,
|
||||
3,
|
||||
f"Violations Details ({len(self.violations_list)} total):",
|
||||
curses.color_pair(3) | curses.A_BOLD,
|
||||
)
|
||||
row = 22
|
||||
for i, v in enumerate(self.violations_list[:5]):
|
||||
v_cat = str(getattr(v, "code", "VIOLATION"))
|
||||
msg = str(getattr(v, "message", "Violation detected"))
|
||||
location = str(
|
||||
getattr(v, "artifact_path", "")
|
||||
or getattr(getattr(v, "evidence_json", {}), "get", lambda *_: "")("location", "")
|
||||
or getattr(getattr(v, "evidence_json", {}), "get", lambda *_: "")(
|
||||
"location", ""
|
||||
)
|
||||
)
|
||||
msg_text = f"[{v_cat}] {msg} (Loc: {location})"
|
||||
self.stdscr.addstr(row + i, 5, msg_text[:70], curses.color_pair(3))
|
||||
if self.last_error:
|
||||
self.stdscr.addstr(27, 3, f"Error: {self.last_error}"[:100], curses.color_pair(3) | curses.A_BOLD)
|
||||
self.stdscr.addstr(
|
||||
27,
|
||||
3,
|
||||
f"Error: {self.last_error}"[:100],
|
||||
curses.color_pair(3) | curses.A_BOLD,
|
||||
)
|
||||
|
||||
def draw_footer(self, max_y: int, max_x: int):
|
||||
footer_text = " F5 Run F6 Manifest F7 Refresh F8 Approve F9 Publish F10 Exit ".center(max_x)
|
||||
footer_text = " F5 Run F6 Manifest F7 Refresh F8 Approve F9 Publish F10 Exit ".center(
|
||||
max_x
|
||||
)
|
||||
self.stdscr.attron(curses.color_pair(1))
|
||||
self.stdscr.addstr(max_y - 1, 0, footer_text[:max_x])
|
||||
self.stdscr.attroff(curses.color_pair(1))
|
||||
@@ -446,7 +521,9 @@ class CleanReleaseTUI:
|
||||
self.refresh_screen()
|
||||
|
||||
try:
|
||||
result = self.facade.run_compliance(candidate_id=self.candidate_id, actor="operator")
|
||||
result = self.facade.run_compliance(
|
||||
candidate_id=self.candidate_id, actor="operator"
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self.status = CheckFinalStatus.FAILED
|
||||
self.last_error = str(exc)
|
||||
@@ -456,7 +533,9 @@ class CleanReleaseTUI:
|
||||
self.checks_progress = [
|
||||
{
|
||||
"stage": stage.stage_name,
|
||||
"status": CheckStageStatus.PASS if str(stage.decision).upper() == "PASSED" else CheckStageStatus.FAIL,
|
||||
"status": CheckStageStatus.PASS
|
||||
if str(stage.decision).upper() == "PASSED"
|
||||
else CheckStageStatus.FAIL,
|
||||
}
|
||||
for stage in result.stage_runs
|
||||
]
|
||||
@@ -472,11 +551,14 @@ class CleanReleaseTUI:
|
||||
self.status = CheckFinalStatus.FAILED
|
||||
self.refresh_overview()
|
||||
self.refresh_screen()
|
||||
|
||||
# [/DEF:run_checks:Function]
|
||||
|
||||
def build_manifest(self):
|
||||
try:
|
||||
manifest = self.facade.build_manifest(candidate_id=self.candidate_id, actor="operator")
|
||||
manifest = self.facade.build_manifest(
|
||||
candidate_id=self.candidate_id, actor="operator"
|
||||
)
|
||||
self.status = "READY"
|
||||
self.report_id = None
|
||||
self.violations_list = []
|
||||
@@ -570,11 +652,13 @@ class CleanReleaseTUI:
|
||||
self.approve_latest()
|
||||
elif char == curses.KEY_F9:
|
||||
self.publish_latest()
|
||||
|
||||
|
||||
# [/DEF:CleanReleaseTUI:Class]
|
||||
|
||||
|
||||
def tui_main(stdscr: curses.window):
|
||||
curses.curs_set(0) # Hide cursor
|
||||
curses.curs_set(0) # Hide cursor
|
||||
app = CleanReleaseTUI(stdscr)
|
||||
app.loop()
|
||||
|
||||
@@ -597,4 +681,4 @@ def main() -> int:
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
# [/DEF:backend.src.scripts.clean_release_tui:Module]
|
||||
# [/DEF:CleanReleaseTuiScript:Module]
|
||||
|
||||
Reference in New Issue
Block a user