from __future__ import annotations import json import time from typing import Any from urllib.parse import quote from ..connectors.portainer import PortainerConnector from ..core.redaction import redact_message _CONTROLLER = "gitea-deploy-control" _REPOSITORY = "jens/portfolio" _LEGACY_REVISION = "e59bfb3484531883e5486ee9e10b663ed0f6764f" _RECOVERY_LIVE_REVISION = "52c6f2fc74a1e39ad288f1c5e987a9cd5b072d3e" _RECOVERY_FAILED_REVISION = "b7955d90b63860ffff8e0bd84be123aaba632043" _RECOVERY_FAILURE_MESSAGE = "Existing production containers require a valid healthy live deployment state" _PROJECT: dict[str, object] = { "branch": "main", "slug": "portfolio", "source_dir": "/mnt/cache/appdata/gitea-deploy-runners/sources/portfolio", "exact_source": True, "env_source": "/srv/example/appdata/Portfolio/.env", "compose_files": ["docker-compose.yml"], "compose_project": "portfolio", "containers": ["Portfolio", "Portfolio-api"], "verify_oci_revision": False, "health_timeout": 180, "environment": { "GIT_COMMIT": "{revision}", "BUILD_ID": "build-{short_revision}", }, } _LEGACY_SINGLE_PROJECT: dict[str, object] = { "branch": "main", "slug": "portfolio", "source_dir": "/mnt/cache/appdata/gitea-deploy-runners/sources/portfolio", "exact_source": True, "compose_files": ["docker-compose.yml"], "compose_project": "portfolio", "containers": ["portfolio"], "verify_oci_revision": False, "health_timeout": 180, } _INSPECT_SCRIPT = r''' import json from pathlib import Path import sys repository = sys.argv[1] path = Path("/controller-config/projects.json") try: projects = json.loads(path.read_text(encoding="utf-8")) except Exception: raise SystemExit(20) current = projects.get(repository) if current is None: print(json.dumps({"present": False}, sort_keys=True, separators=(",", ":"))) raise SystemExit(0) if not isinstance(current, dict): print(json.dumps({"present": True, "type": type(current).__name__}, sort_keys=True, separators=(",", ":"))) raise SystemExit(0) safe = {"present": True} for key in ( "branch", "slug", "source_dir", "exact_source", "env_source", "compose_files", "compose_project", "containers", "verify_oci_revision", "health_timeout", "deploy_script", "archive_deploy_script", "manual", ): if key in current: safe[key] = current[key] environment = current.get("environment") if isinstance(environment, dict): safe["environment_keys"] = sorted(str(key) for key in environment.keys()) live_probe = current.get("live_probe") if isinstance(live_probe, dict): safe["live_probe"] = { key: live_probe[key] for key in ("type", "container", "port", "path") if key in live_probe } state_path = Path("/mnt/cache/appdata/gitea-deploy-runners/state/portfolio.json") if state_path.is_file() and not state_path.is_symlink(): try: state = json.loads(state_path.read_text(encoding="utf-8")) except Exception: state = None if isinstance(state, dict): safe_state = { key: state[key] for key in ( "schema_version", "repository", "revision", "live_revision", "live_status", "live_legacy_provenance", "status", "updated_at_epoch", ) if key in state } last_attempt = state.get("last_attempt") if isinstance(last_attempt, dict): safe_attempt = { key: last_attempt[key] for key in ("revision", "status", "updated_at_epoch") if key in last_attempt } message = last_attempt.get("message") if isinstance(message, str): safe_attempt["message"] = message.replace("\r", " ").replace("\n", " ")[:768] safe_state["last_attempt"] = safe_attempt live_artifacts = state.get("live_artifacts") if isinstance(live_artifacts, dict): containers = live_artifacts.get("containers") if isinstance(containers, dict): safe_state["live_artifact_containers"] = sorted(str(key) for key in containers.keys()) safe["state"] = safe_state print(json.dumps(safe, sort_keys=True, separators=(",", ":"))) ''' _ENROLL_SCRIPT = r''' import fcntl import importlib.util import json import os from pathlib import Path import shutil import stat import subprocess import sys import time registry_path = Path("/controller-config/projects.json") host_registry_path = Path("/controller-hub-releases/controller-config/projects.json") state_root = Path("/mnt/cache/appdata/gitea-deploy-runners/state") state_path = state_root / "portfolio.json" project_lock_path = state_root / "locks" / "portfolio.lock" lock_path = Path("/mnt/cache/appdata/gitea-deploy-runners/deploy.lock") ( repository, legacy_revision, recovery_live_revision, recovery_failed_revision, recovery_failure_message, raw_project, raw_legacy_project, ) = sys.argv[1:8] project = json.loads(raw_project) legacy_project = json.loads(raw_legacy_project) expected_project = { "branch": "main", "slug": "portfolio", "source_dir": "/mnt/cache/appdata/gitea-deploy-runners/sources/portfolio", "exact_source": True, "env_source": "/srv/example/appdata/Portfolio/.env", "compose_files": ["docker-compose.yml"], "compose_project": "portfolio", "containers": ["Portfolio", "Portfolio-api"], "verify_oci_revision": False, "health_timeout": 180, "environment": { "GIT_COMMIT": "{revision}", "BUILD_ID": "build-{short_revision}", }, } expected_legacy_project = { "branch": "main", "slug": "portfolio", "source_dir": "/mnt/cache/appdata/gitea-deploy-runners/sources/portfolio", "exact_source": True, "compose_files": ["docker-compose.yml"], "compose_project": "portfolio", "containers": ["portfolio"], "verify_oci_revision": False, "health_timeout": 180, } expected_legacy_revision = "e59bfb3484531883e5486ee9e10b663ed0f6764f" expected_recovery_live_revision = "52c6f2fc74a1e39ad288f1c5e987a9cd5b072d3e" expected_recovery_failed_revision = "b7955d90b63860ffff8e0bd84be123aaba632043" expected_recovery_failure_message = "Existing production containers require a valid healthy live deployment state" if ( repository != "jens/portfolio" or legacy_revision != expected_legacy_revision or recovery_live_revision != expected_recovery_live_revision or recovery_failed_revision != expected_recovery_failed_revision or recovery_failure_message != expected_recovery_failure_message or project != expected_project or legacy_project != expected_legacy_project ): raise SystemExit(40) if registry_path.is_symlink() or not registry_path.is_file(): raise SystemExit(20) registry_stat = registry_path.stat() if not stat.S_ISREG(registry_stat.st_mode): raise SystemExit(21) try: if ( host_registry_path.is_symlink() or not host_registry_path.is_file() or not os.path.samefile(registry_path, host_registry_path) ): raise SystemExit(36) except OSError: raise SystemExit(36) env_source = Path(str(project["env_source"])) if env_source.is_symlink() or not env_source.is_file(): raise SystemExit(41) owner, name = repository.split("/", 1) bare_repo = Path("/gitea-repositories") / owner / f"{name}.git" if not bare_repo.is_dir(): raise SystemExit(24) def git_run(*args): return subprocess.run( ["git", f"--git-dir={bare_repo}", *args], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=10, check=False, ) for revision in (legacy_revision, recovery_live_revision, recovery_failed_revision): resolved = git_run("rev-parse", f"{revision}^{{commit}}") if resolved.returncode != 0 or resolved.stdout.strip() != revision: raise SystemExit(25) main = git_run("rev-parse", "refs/heads/main") main_revision = main.stdout.strip() if main.returncode != 0 or len(main_revision) != 40: raise SystemExit(26) if git_run("merge-base", "--is-ancestor", legacy_revision, main_revision).returncode != 0: raise SystemExit(27) if git_run("merge-base", "--is-ancestor", recovery_live_revision, main_revision).returncode != 0: raise SystemExit(27) if main_revision != recovery_failed_revision: raise SystemExit(58) for compose_file in project["compose_files"]: if git_run("cat-file", "-e", f"{main_revision}:{compose_file}").returncode != 0: raise SystemExit(28) try: if state_root.is_symlink() or not state_root.is_dir(): raise SystemExit(50) project_lock_path.parent.mkdir(parents=True, exist_ok=True) if project_lock_path.parent.is_symlink() or project_lock_path.is_symlink(): raise SystemExit(29) project_lock_handle = project_lock_path.open("a+", encoding="utf-8") lock_handle = lock_path.open("a+", encoding="utf-8") except Exception: raise SystemExit(29) with project_lock_handle as project_lock, lock_handle as lock: try: fcntl.flock(project_lock, fcntl.LOCK_EX) fcntl.flock(lock, fcntl.LOCK_EX) except Exception: raise SystemExit(30) try: projects = json.loads(host_registry_path.read_text(encoding="utf-8")) except Exception: raise SystemExit(31) if not isinstance(projects, dict): raise SystemExit(22) current = projects.get(repository) def legacy_script_target_matches(value): if not isinstance(value, dict): return False expected_keys = { "branch", "slug", "source_dir", "env_source", "compose_files", "compose_project", "containers", "deploy_script", "environment", } if set(value) != expected_keys: return False environment = value.get("environment") return ( value.get("branch") == "main" and value.get("slug") == "portfolio" and value.get("source_dir") == "/srv/example/appdata/Portfolio/source" and value.get("env_source") == "/srv/example/appdata/Portfolio/.env" and value.get("compose_files") == ["docker-compose.yml"] and value.get("compose_project") == "portfolio" and value.get("containers") == ["Portfolio", "Portfolio-api"] and value.get("deploy_script") == "scripts/deploy-unraid.sh" and isinstance(environment, dict) and set(environment) == {"PORTFOLIO_APP_DIR"} and environment.get("PORTFOLIO_APP_DIR") in {"/srv/example/appdata/Portfolio", "/srv/example/appdata/portfolio"} ) if current not in (None, project, legacy_project) and not legacy_script_target_matches(current): raise SystemExit(23) state_requires_reconcile = False runtime_state_requires_reconcile = False existing_state = None if state_path.exists(): if state_path.is_symlink() or not state_path.is_file(): raise SystemExit(42) try: existing_state = json.loads(state_path.read_text(encoding="utf-8")) except Exception: raise SystemExit(42) if not isinstance(existing_state, dict): raise SystemExit(42) if existing_state.get("schema_version") == 3: if existing_state.get("repository") != repository: raise SystemExit(43) if ( current == project and existing_state.get("live_status") == "healthy" and isinstance(existing_state.get("live_revision"), str) and len(existing_state.get("live_revision")) == 40 and ( existing_state.get("live_legacy_provenance") is True or isinstance(existing_state.get("live_receipt"), dict) ) ): print("already-enrolled", flush=True) raise SystemExit(0) last_attempt = existing_state.get("last_attempt") live_artifacts = existing_state.get("live_artifacts") live_receipt = existing_state.get("live_receipt") artifact_containers = ( live_artifacts.get("containers") if isinstance(live_artifacts, dict) else None ) if ( current == project and existing_state.get("revision") == recovery_live_revision and existing_state.get("live_revision") == recovery_live_revision and existing_state.get("live_status") == "unknown" and existing_state.get("live_legacy_provenance") is False and existing_state.get("status") == "failed" and isinstance(last_attempt, dict) and last_attempt.get("revision") == recovery_failed_revision and last_attempt.get("status") == "failed" and last_attempt.get("message") == recovery_failure_message and isinstance(live_receipt, dict) and bool(live_receipt) and isinstance(artifact_containers, dict) and set(artifact_containers) == set(project["containers"]) ): runtime_state_requires_reconcile = True elif legacy_script_target_matches(current): state_requires_reconcile = True else: raise SystemExit(43) elif not ( existing_state.get("repository") == repository and existing_state.get("status") == "success" and existing_state.get("revision") == legacy_revision and "schema_version" not in existing_state and "live_receipt" not in existing_state and "live_legacy_provenance" not in existing_state ): if legacy_script_target_matches(current): state_requires_reconcile = True else: raise SystemExit(43) expected_names = set(project["containers"]) observed_result = subprocess.run( [ "docker", "ps", "--all", "--filter", f"label=com.docker.compose.project={project['compose_project']}", "--format", "{{.Names}}", ], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=15, check=False, ) if observed_result.returncode != 0: raise SystemExit(44) observed_names = {line.strip() for line in observed_result.stdout.splitlines() if line.strip()} if observed_names != expected_names: raise SystemExit(45) for required in sorted(expected_names): inspected = subprocess.run( ["docker", "inspect", required], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=10, check=False, ) if inspected.returncode != 0: raise SystemExit(46) try: container = json.loads(inspected.stdout)[0] except Exception: raise SystemExit(46) runtime = dict(container.get("State") or {}) health = str(dict(runtime.get("Health") or {}).get("Status") or "none") config = dict(container.get("Config") or {}) labels = dict(config.get("Labels") or {}) revision_label = str(labels.get("tech.itworx.commit") or "") build_label = str(labels.get("tech.itworx.build-id") or "") if ( not runtime.get("Running") or health in {"starting", "unhealthy"} or labels.get("com.docker.compose.project") != "portfolio" ): raise SystemExit(46) if legacy_script_target_matches(current) and revision_label != legacy_revision: raise SystemExit(47) if runtime_state_requires_reconcile: if ( revision_label != recovery_live_revision or build_label != f"build-{recovery_live_revision[:12]}" ): raise SystemExit(55) artifacts = dict(dict(existing_state.get("live_artifacts") or {}).get("containers") or {}) artifact = artifacts.get(required) if not isinstance(artifact, dict): raise SystemExit(55) if ( str(artifact.get("image_id") or "") != str(container.get("Image") or "") or str(artifact.get("container_id") or "") != str(container.get("Id") or "") ): raise SystemExit(55) if runtime_state_requires_reconcile: if state_root.is_symlink() or not state_root.is_dir(): raise SystemExit(50) now = int(time.time()) state_backup = state_path.with_name( f"portfolio.json.runtime-reconcile-backup-{now}-{os.getpid()}" ) temporary_state = state_path.with_name( f".{state_path.name}.{os.getpid()}.runtime-reconcile.tmp" ) reconciled_state = dict(existing_state) reconciled_state["live_status"] = "healthy" reconciled_state["status"] = "success" reconciled_state["message"] = ( "Verified Portfolio runtime matches recorded live artifacts; controller state reconciled" ) reconciled_state["updated_at_epoch"] = now reconciled_state["last_attempt"] = { "revision": recovery_live_revision, "status": "success", "message": "Verified live Portfolio runtime state reconciliation", "updated_at_epoch": now, "metadata": {"reconciled_failed_revision": recovery_failed_revision}, } try: mode = stat.S_IMODE(state_path.stat().st_mode) shutil.copy2(state_path, state_backup) with temporary_state.open("x", encoding="utf-8") as handle: handle.write(json.dumps(reconciled_state, indent=2, sort_keys=True) + "\n") handle.flush() os.fsync(handle.fileno()) os.chmod(temporary_state, mode) os.replace(temporary_state, state_path) directory_fd = os.open(str(state_root), os.O_RDONLY) try: os.fsync(directory_fd) finally: os.close(directory_fd) except Exception: try: temporary_state.unlink(missing_ok=True) except Exception: pass raise SystemExit(56) try: verify = json.loads(state_path.read_text(encoding="utf-8")) except Exception: raise SystemExit(57) if not ( isinstance(verify, dict) and verify.get("live_revision") == recovery_live_revision and verify.get("live_status") == "healthy" and verify.get("live_legacy_provenance") is False and isinstance(verify.get("live_receipt"), dict) and bool(verify.get("live_receipt")) and verify.get("status") == "success" ): raise SystemExit(57) print("runtime-state-reconciled", flush=True) raise SystemExit(0) if state_requires_reconcile: if state_root.is_symlink() or not state_root.is_dir(): raise SystemExit(50) state_backup = state_path.with_name( f"portfolio.json.verified-live-backup-{int(time.time())}-{os.getpid()}" ) temporary_state = state_path.with_name(f".{state_path.name}.{os.getpid()}.reconcile.tmp") try: shutil.copy2(state_path, state_backup) reconciled_state = { "repository": repository, "revision": legacy_revision, "status": "success", } with temporary_state.open("x", encoding="utf-8") as handle: handle.write(json.dumps(reconciled_state, indent=2, sort_keys=True) + "\n") handle.flush() os.fsync(handle.fileno()) os.chmod(temporary_state, 0o600) os.replace(temporary_state, state_path) directory_fd = os.open(str(state_root), os.O_RDONLY) try: os.fsync(directory_fd) finally: os.close(directory_fd) existing_state = reconciled_state except Exception: try: temporary_state.unlink(missing_ok=True) except Exception: pass raise SystemExit(54) if current != project: backup = host_registry_path.with_name( f"projects.json.portfolio-backup-{int(time.time())}-{os.getpid()}" ) try: shutil.copy2(host_registry_path, backup) except Exception: raise SystemExit(32) updated = dict(projects) updated[repository] = project temporary = host_registry_path.with_name(f".{host_registry_path.name}.{os.getpid()}.tmp") payload = json.dumps(updated, indent=2, sort_keys=True) + "\n" try: with temporary.open("w", encoding="utf-8") as handle: handle.write(payload) handle.flush() os.fsync(handle.fileno()) os.chmod(temporary, stat.S_IMODE(registry_stat.st_mode)) os.replace(temporary, host_registry_path) directory_fd = os.open(str(host_registry_path.parent), os.O_RDONLY) try: os.fsync(directory_fd) finally: os.close(directory_fd) except Exception: try: temporary.unlink(missing_ok=True) except Exception: pass raise SystemExit(34) if existing_state is None: state_root.mkdir(parents=True, exist_ok=True) if state_root.is_symlink() or not state_root.is_dir(): raise SystemExit(50) temporary_state = state_path.with_name(f".{state_path.name}.{os.getpid()}.tmp") payload = {"repository": repository, "revision": legacy_revision, "status": "success"} try: with temporary_state.open("x", encoding="utf-8") as handle: handle.write(json.dumps(payload, indent=2, sort_keys=True) + "\n") handle.flush() os.fsync(handle.fileno()) os.chmod(temporary_state, 0o600) os.replace(temporary_state, state_path) directory_fd = os.open(str(state_root), os.O_RDONLY) try: os.fsync(directory_fd) finally: os.close(directory_fd) except Exception: try: temporary_state.unlink(missing_ok=True) except Exception: pass raise SystemExit(51) try: controller_path = Path("/opt/gitea-deploy/deploy.py") if controller_path.is_symlink() or not controller_path.is_file(): raise RuntimeError("controller module is unavailable") controller_spec = importlib.util.spec_from_file_location( "appops_locked_portfolio_bootstrap", controller_path ) if controller_spec is None or controller_spec.loader is None: raise RuntimeError("controller module cannot be loaded") controller = importlib.util.module_from_spec(controller_spec) controller_spec.loader.exec_module(controller) controller.bootstrap_legacy_state(repository, project, legacy_revision) except Exception: raise SystemExit(52) try: final_state = json.loads(state_path.read_text(encoding="utf-8")) except Exception: raise SystemExit(53) if not ( isinstance(final_state, dict) and final_state.get("schema_version") == 3 and final_state.get("repository") == repository and final_state.get("live_revision") == legacy_revision and final_state.get("live_status") == "healthy" and final_state.get("live_legacy_provenance") is True ): raise SystemExit(53) print("enrolled", flush=True) ''' class PortfolioDeploymentEnrollment: """Enroll or reconcile the fixed paired Portfolio exact-source controller target.""" def __init__(self, portainer: PortainerConnector | None = None): self.portainer = portainer or PortainerConnector() def _inspect_current_target(self, *, endpoint: str, container_id: str) -> dict[str, Any] | None: safe_container = quote(container_id, safe="") try: created = self.portainer._request( "POST", f"/api/endpoints/{endpoint}/docker/containers/{safe_container}/exec", json={ "AttachStdout": True, "AttachStderr": False, "Tty": True, "Cmd": ["python3", "-c", _INSPECT_SCRIPT, _REPOSITORY], }, ).json() exec_id = str(created.get("Id") or "") if not exec_id: return None safe_exec = quote(exec_id, safe="") response = self.portainer._request( "POST", f"/api/endpoints/{endpoint}/docker/exec/{safe_exec}/start", json={"Detach": False, "Tty": True}, ) raw = (response.text or "").strip() if not raw or len(raw) > 4096: return None decoded = json.loads(raw) return decoded if isinstance(decoded, dict) else None except (RuntimeError, ValueError, TypeError, json.JSONDecodeError): return None def run(self) -> dict[str, Any]: status = self.portainer.get_container_status(_CONTROLLER) if status.get("status") != "ok": return { "status": "controller_unavailable", "reason": redact_message( str(status.get("reason") or status.get("status") or "unavailable") ), } controller = dict(status.get("container") or {}) if controller.get("name") != _CONTROLLER or controller.get("state") != "running": return { "status": "controller_unavailable", "reason": "deployment controller is not running", } container_id = str(controller.get("id") or "") config = self.portainer._config() if not config or not container_id: return { "status": "connector_unavailable", "reason": "Portainer deployment control is unavailable", } endpoint = quote(str(config.endpoint_id), safe="") current_target = self._inspect_current_target(endpoint=endpoint, container_id=container_id) if current_target is not None: container_revisions: dict[str, str | None] = {} for required in ("Portfolio", "Portfolio-api"): try: inspected_container = self.portainer._request( "GET", f"/api/endpoints/{endpoint}/docker/containers/{quote(required, safe='')}/json", ).json() config_payload = inspected_container.get("Config") if isinstance(inspected_container, dict) else None labels = config_payload.get("Labels") if isinstance(config_payload, dict) else None revision = labels.get("tech.itworx.commit") if isinstance(labels, dict) else None container_revisions[required] = str(revision) if revision else None except (RuntimeError, ValueError, TypeError): container_revisions[required] = None current_target["container_revisions"] = container_revisions command = [ "python3", "-c", _ENROLL_SCRIPT, _REPOSITORY, _LEGACY_REVISION, _RECOVERY_LIVE_REVISION, _RECOVERY_FAILED_REVISION, _RECOVERY_FAILURE_MESSAGE, json.dumps(_PROJECT, separators=(",", ":"), sort_keys=True), json.dumps(_LEGACY_SINGLE_PROJECT, separators=(",", ":"), sort_keys=True), ] safe_container = quote(container_id, safe="") try: created = self.portainer._request( "POST", f"/api/endpoints/{endpoint}/docker/containers/{safe_container}/exec", json={ "AttachStdout": False, "AttachStderr": False, "Tty": False, "Cmd": command, }, ).json() exec_id = str(created.get("Id") or "") if not exec_id: return {"status": "error", "reason": "Portfolio enrollment could not start"} safe_exec = quote(exec_id, safe="") self.portainer._request( "POST", f"/api/endpoints/{endpoint}/docker/exec/{safe_exec}/start", json={"Detach": True, "Tty": False}, ) deadline = time.monotonic() + 120 exit_code: int | None = None while time.monotonic() < deadline: inspected = self.portainer._request( "GET", f"/api/endpoints/{endpoint}/docker/exec/{safe_exec}/json" ).json() if not bool(inspected.get("Running")): raw_exit = inspected.get("ExitCode") exit_code = int(raw_exit) if raw_exit is not None else -1 break time.sleep(0.25) except (RuntimeError, ValueError, TypeError) as exc: return {"status": "error", "reason": redact_message(str(exc))} if exit_code is None: return {"status": "error", "reason": "Portfolio enrollment timed out"} reasons = { 20: "deployment projects registry is unavailable or unsafe", 21: "deployment projects registry is not a regular file", 22: "deployment projects registry does not contain an object", 23: "Portfolio already has an unexpected deployment target", 24: "mounted Portfolio bare repository is unavailable", 25: "known Portfolio deployment revisions cannot be resolved", 26: "Portfolio main cannot be resolved", 27: "known Portfolio live revision is not an ancestor of main", 28: "required Portfolio docker-compose.yml is absent from main", 29: "deployment registry lock file cannot be opened", 30: "deployment registry lock cannot be acquired", 31: "deployment projects registry cannot be read as JSON", 32: "deployment projects registry backup cannot be created", 34: "deployment projects registry replacement failed", 36: "deployment projects registry host alias is unavailable or mismatched", 40: "Portfolio fixed enrollment contract was altered", 41: "Portfolio production environment file is unavailable or unsafe", 42: "Portfolio deployment state file is unavailable or invalid", 43: "Portfolio deployment state conflicts with the verified live baseline", 44: "Portfolio Compose project inventory could not be read", 45: "Portfolio Compose project does not contain exactly the expected web/API containers", 46: "required Portfolio production container is missing or unhealthy", 47: "live Portfolio container revision does not match the verified legacy baseline", 50: "deployment state directory is unavailable or unsafe", 51: "verified Portfolio legacy state could not be written durably", 52: "deployment controller rejected Portfolio legacy-state bootstrap", 53: "Portfolio legacy-state bootstrap did not produce the expected managed state", 54: "verified stale Portfolio controller state could not be reconciled safely", 55: "live Portfolio runtime no longer matches the controller-recorded modern artifacts", 56: "verified Portfolio runtime state could not be reconciled durably", 57: "reconciled Portfolio runtime state did not verify after write", 58: "Portfolio main changed after the bounded recovery contract was recorded", } if exit_code != 0: reason = reasons.get( exit_code, "Portfolio enrollment failed closed before any application deployment", ) if exit_code in {23, 43, 47, 55, 58} and current_target is not None: reason = redact_message( f"{reason}: {json.dumps(current_target, sort_keys=True, separators=(',', ':'))}" ) return {"status": "validation_failed", "reason": reason} return { "status": "enrolled", "repository": _REPOSITORY, "legacy_revision": _LEGACY_REVISION, "compose_project": _PROJECT["compose_project"], "containers": list(_PROJECT["containers"]), }