"""Versioned launchd declaration for the canonical local Mission Core service.""" from __future__ import annotations import hashlib import os import plistlib import stat from dataclasses import dataclass from pathlib import Path from typing import Final, cast MISSION_CORE_LAUNCH_AGENT_LABEL: Final = "com.nodedc.mission-core.local" MISSION_CORE_LAUNCH_AGENT_SCHEMA: Final = "missioncore.local-launch-agent-plan/v1" OBSERVATORY_LOCAL_WORKER_ENABLED_ENV: Final = ( "MISSIONCORE_OBSERVATORY_WORKER_LOCAL_ENABLED" ) OBSERVATORY_SOURCE_CAS_ROOT_ENV: Final = ( "MISSIONCORE_OBSERVATORY_WORKER_SOURCE_CAS_ROOT" ) OBSERVATORY_RESULT_STAGING_ROOT_ENV: Final = ( "MISSIONCORE_OBSERVATORY_WORKER_RESULT_STAGING_ROOT" ) ARTIFACT_STORE_ROOT_ENV: Final = "MISSIONCORE_ARTIFACT_STORE_ROOT" class MissionCoreLaunchAgentError(RuntimeError): """The local launch agent cannot be planned without weakening its boundary.""" @dataclass(frozen=True, slots=True) class MissionCoreLaunchAgentPlan: agent_path: Path current_sha256: str desired_sha256: str current_working_directory: Path desired_working_directory: Path preserved_data_directory: Path | None current_program_arguments: tuple[str, ...] desired_program_arguments: tuple[str, ...] local_observatory_worker_enabled: bool desired_payload: bytes def to_dict(self) -> dict[str, object]: return { "schema_version": MISSION_CORE_LAUNCH_AGENT_SCHEMA, "label": MISSION_CORE_LAUNCH_AGENT_LABEL, "agent_path": str(self.agent_path), "current_sha256": self.current_sha256, "desired_sha256": self.desired_sha256, "current_working_directory": str(self.current_working_directory), "desired_working_directory": str(self.desired_working_directory), "preserved_data_directory": ( str(self.preserved_data_directory) if self.preserved_data_directory is not None else None ), "current_program_arguments": list(self.current_program_arguments), "desired_program_arguments": list(self.desired_program_arguments), "changes": { "repository_migration": self.current_working_directory != self.desired_working_directory, "data_directory_preserved": self.preserved_data_directory is not None, "preserved_data_directory": ( str(self.preserved_data_directory) if self.preserved_data_directory is not None else None ), "dependency_sync_disabled": "--no-sync" in self.desired_program_arguments, "self_health_watchdog": True, "bounded_launchd_exit_timeout_seconds": 20, "keep_alive": True, "process_group_owned": True, "local_observatory_worker_enabled": ( self.local_observatory_worker_enabled ), }, } def plan_mission_core_launch_agent( *, repository_root: Path, agent_path: Path, expected_current_repository_root: Path | None = None, enable_local_observatory_worker: bool = False, ) -> MissionCoreLaunchAgentPlan: repository = repository_root.expanduser().resolve(strict=True) expected_current_repository = ( expected_current_repository_root.expanduser().resolve(strict=True) if expected_current_repository_root is not None else None ) path = agent_path.expanduser().absolute() current_payload = _read_private_regular_file(path) try: current = plistlib.loads(current_payload) except plistlib.InvalidFileException as exc: raise MissionCoreLaunchAgentError("current Mission Core launch agent is invalid") from exc if not isinstance(current, dict) or current.get("Label") != MISSION_CORE_LAUNCH_AGENT_LABEL: raise MissionCoreLaunchAgentError("current launch agent identity changed") current_arguments = _program_arguments(current) current_working_directory = current.get("WorkingDirectory") if not isinstance(current_working_directory, str): raise MissionCoreLaunchAgentError("current launch agent working directory is invalid") if expected_current_repository is None and current_working_directory != str(repository): raise MissionCoreLaunchAgentError("current launch agent targets another repository") if ( expected_current_repository is not None and current_working_directory != str(expected_current_repository) ): raise MissionCoreLaunchAgentError( "current launch agent does not target the expected current repository" ) environment_document = current.get("EnvironmentVariables") if not isinstance(environment_document, dict) or any( not isinstance(key, str) or not isinstance(value, str) for key, value in environment_document.items() ): raise MissionCoreLaunchAgentError("current launch agent environment is invalid") environment = cast(dict[str, str], environment_document) # A LaunchAgent started directly from this repository's venv is denied # access to ``.venv/pyvenv.cfg`` by macOS privacy controls because the # checkout is below Downloads. The Homebrew uv launcher is already the # accepted local execution boundary. ``--no-sync`` keeps launch startup # deterministic and prevents dependency mutation during recovery. uv_entrypoint = Path(current_arguments[0]) if ( not uv_entrypoint.is_absolute() or uv_entrypoint.name != "uv" or not uv_entrypoint.exists() ): raise MissionCoreLaunchAgentError("Mission Core uv entrypoint is unavailable") current_repository = Path(current_working_directory) repository_migration = current_repository != repository preserved_data_directory = ( _preserved_migration_data_directory( current_repository=current_repository, environment=environment, ) if repository_migration else None ) desired_environment = dict(environment) desired_environment["MISSIONCORE_SERVICE_WATCHDOG"] = "1" if preserved_data_directory is not None: desired_environment["MISSIONCORE_DATA_DIR"] = str(preserved_data_directory) if enable_local_observatory_worker: data_directory = _local_observatory_data_directory( repository=repository, environment=desired_environment, ) artifact_store = _private_local_worker_directory( data_directory / "observatory-artifact-store", "local Observatory artifact store", ) source_cas = _private_local_worker_directory( data_directory / "observatory-worker-source-cas", "local Observatory source CAS", ) result_staging = _private_local_worker_directory( data_directory / "observatory-worker-result-staging", "local Observatory result staging", ) desired_environment["MISSIONCORE_DATA_DIR"] = str(data_directory) desired_environment[ARTIFACT_STORE_ROOT_ENV] = str(artifact_store) desired_environment[OBSERVATORY_SOURCE_CAS_ROOT_ENV] = str(source_cas) desired_environment[OBSERVATORY_RESULT_STAGING_ROOT_ENV] = str(result_staging) desired_environment[OBSERVATORY_LOCAL_WORKER_ENABLED_ENV] = "1" log_path = repository / ".runtime/mission-core/k1link-serve-launchd.log" desired_program_arguments = ( str(uv_entrypoint), "run", "--no-sync", "k1link", "serve", ) desired: dict[str, object] = { "Label": MISSION_CORE_LAUNCH_AGENT_LABEL, "ProgramArguments": list(desired_program_arguments), "WorkingDirectory": str(repository), "EnvironmentVariables": desired_environment, "KeepAlive": True, "RunAtLoad": True, "AbandonProcessGroup": False, "ProcessType": "Background", "ThrottleInterval": 5, "ExitTimeOut": 20, "StandardOutPath": str(log_path), "StandardErrorPath": str(log_path), } desired_payload = plistlib.dumps(desired, fmt=plistlib.FMT_XML, sort_keys=True) return MissionCoreLaunchAgentPlan( agent_path=path, current_sha256=_sha256(current_payload), desired_sha256=_sha256(desired_payload), current_working_directory=current_repository, desired_working_directory=repository, preserved_data_directory=preserved_data_directory, current_program_arguments=current_arguments, desired_program_arguments=desired_program_arguments, local_observatory_worker_enabled=enable_local_observatory_worker, desired_payload=desired_payload, ) def _local_observatory_data_directory( *, repository: Path, environment: dict[str, str], ) -> Path: configured = environment.get("MISSIONCORE_DATA_DIR", "").strip() candidate = Path(configured) if configured else repository / ".runtime" / "mission-core" return _private_local_worker_directory( candidate, "Mission Core data directory", ) def _private_local_worker_directory(path: Path, label: str) -> Path: if not path.is_absolute(): raise MissionCoreLaunchAgentError(f"{label} is not an absolute directory") try: metadata = path.lstat() resolved = path.resolve(strict=True) except OSError as exc: raise MissionCoreLaunchAgentError(f"{label} is unavailable") from exc if ( resolved != path or not stat.S_ISDIR(metadata.st_mode) or stat.S_IMODE(metadata.st_mode) != 0o700 or metadata.st_uid != os.getuid() ): raise MissionCoreLaunchAgentError( f"{label} is not a private canonical directory" ) return resolved def _program_arguments(document: dict[str, object]) -> tuple[str, ...]: value = document.get("ProgramArguments") if not isinstance(value, list) or not value or any(not isinstance(item, str) for item in value): raise MissionCoreLaunchAgentError("launch agent program arguments are invalid") return tuple(value) def _preserved_migration_data_directory( *, current_repository: Path, environment: dict[str, str], ) -> Path: configured = environment.get("MISSIONCORE_DATA_DIR", "") if configured.strip(): if configured != configured.strip(): raise MissionCoreLaunchAgentError( "current Mission Core data directory is not a canonical absolute path" ) candidate = Path(configured) else: candidate = current_repository / ".runtime" / "mission-core" if not candidate.is_absolute(): raise MissionCoreLaunchAgentError( "current Mission Core data directory is not a canonical absolute path" ) try: resolved = candidate.resolve(strict=True) metadata = candidate.lstat() except OSError as exc: raise MissionCoreLaunchAgentError( "current Mission Core data directory is unavailable" ) from exc if resolved != candidate or ( not stat.S_ISDIR(metadata.st_mode) or stat.S_IMODE(metadata.st_mode) != 0o700 or metadata.st_uid != os.getuid() ): raise MissionCoreLaunchAgentError( "current Mission Core data directory is not a private canonical directory" ) return resolved def _read_private_regular_file(path: Path) -> bytes: if path.is_symlink() or not path.is_file(): raise MissionCoreLaunchAgentError("Mission Core launch agent is unavailable") return path.read_bytes() def _sha256(payload: bytes) -> str: return hashlib.sha256(payload).hexdigest()