Preserve the completed teach-and-repeat laboratory stage: reference preparation, cascaded acquisition, local tracking and recovery, recording lifecycle, replay qualification, and persistent Rerun scene controls. Document the open grid-picking regression and Rerun upgrade contract. No autonomous driving or loop-closure optimization is claimed.
339 lines
12 KiB
Python
339 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
import plistlib
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from k1link.local_service_launchd import (
|
|
MissionCoreLaunchAgentError,
|
|
plan_mission_core_launch_agent,
|
|
)
|
|
|
|
|
|
def _write_agent(
|
|
*,
|
|
path: Path,
|
|
repository: Path,
|
|
uv_entrypoint: Path,
|
|
environment: dict[str, str] | None = None,
|
|
) -> None:
|
|
path.write_bytes(
|
|
plistlib.dumps(
|
|
{
|
|
"Label": "com.nodedc.mission-core.local",
|
|
"ProgramArguments": [str(uv_entrypoint), "run", "k1link", "serve"],
|
|
"WorkingDirectory": str(repository),
|
|
"EnvironmentVariables": environment or {"PATH": "/usr/bin:/bin"},
|
|
}
|
|
)
|
|
)
|
|
path.chmod(0o600)
|
|
|
|
|
|
def test_launch_agent_plan_disables_sync_and_enables_watchdog(tmp_path: Path) -> None:
|
|
repository = tmp_path / "repo"
|
|
repository.mkdir()
|
|
uv_entrypoint = tmp_path / "uv"
|
|
uv_entrypoint.write_text("#!/bin/sh\n")
|
|
uv_entrypoint.chmod(0o700)
|
|
agent = tmp_path / "agent.plist"
|
|
_write_agent(path=agent, repository=repository, uv_entrypoint=uv_entrypoint)
|
|
|
|
plan = plan_mission_core_launch_agent(
|
|
repository_root=repository,
|
|
agent_path=agent,
|
|
)
|
|
desired = plistlib.loads(plan.desired_payload)
|
|
|
|
assert desired["ProgramArguments"] == [
|
|
str(uv_entrypoint),
|
|
"run",
|
|
"--no-sync",
|
|
"k1link",
|
|
"serve",
|
|
]
|
|
assert desired["EnvironmentVariables"]["MISSIONCORE_SERVICE_WATCHDOG"] == "1"
|
|
assert desired["KeepAlive"] is True
|
|
assert desired["RunAtLoad"] is True
|
|
assert desired["AbandonProcessGroup"] is False
|
|
assert desired["ExitTimeOut"] == 20
|
|
assert desired["ProcessType"] == "Interactive"
|
|
assert plan.to_dict()["desired_process_type"] == "Interactive"
|
|
assert plan.current_sha256 != plan.desired_sha256
|
|
assert plan.current_working_directory == repository
|
|
assert plan.desired_working_directory == repository
|
|
assert plan.preserved_data_directory is None
|
|
assert "MISSIONCORE_DATA_DIR" not in desired["EnvironmentVariables"]
|
|
assert plan.to_dict()["changes"]["repository_migration"] is False
|
|
assert plan.to_dict()["changes"]["data_directory_preserved"] is False
|
|
assert plan.to_dict()["changes"]["local_observatory_worker_enabled"] is False
|
|
|
|
|
|
def test_background_migration_changes_scheduling_without_changing_operator_state(tmp_path):
|
|
repository = tmp_path / "repo"
|
|
repository.mkdir()
|
|
uv = tmp_path / "uv"
|
|
uv.write_text("#!/bin/sh\n")
|
|
agent = tmp_path / "agent.plist"
|
|
environment = {"PATH": "/usr/bin:/bin", "MISSIONCORE_DATA_DIR": "/existing/evidence"}
|
|
_write_agent(path=agent, repository=repository, uv_entrypoint=uv, environment=environment)
|
|
previous = plistlib.loads(agent.read_bytes())
|
|
previous["ProcessType"] = "Background"
|
|
agent.write_bytes(plistlib.dumps(previous))
|
|
original = agent.read_bytes()
|
|
plan = plan_mission_core_launch_agent(repository_root=repository, agent_path=agent)
|
|
desired = plistlib.loads(plan.desired_payload)
|
|
assert plan.current_process_type == "Background"
|
|
assert desired["ProcessType"] == "Interactive"
|
|
assert desired["EnvironmentVariables"] == {**environment, "MISSIONCORE_SERVICE_WATCHDOG": "1"}
|
|
assert desired["AbandonProcessGroup"] is False
|
|
assert "Nice" not in desired and "HardResourceLimits" not in desired
|
|
assert agent.read_bytes() == original # A plan never changes the running service.
|
|
|
|
|
|
def test_launch_agent_plan_explicitly_enables_local_observatory_worker(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
repository = tmp_path / "repo"
|
|
repository.mkdir()
|
|
data_directory = repository / ".runtime" / "mission-core"
|
|
data_directory.mkdir(parents=True, mode=0o700)
|
|
for name in (
|
|
"observatory-artifact-store",
|
|
"observatory-worker-source-cas",
|
|
"observatory-worker-result-staging",
|
|
):
|
|
(data_directory / name).mkdir(mode=0o700)
|
|
uv_entrypoint = tmp_path / "uv"
|
|
uv_entrypoint.write_text("#!/bin/sh\n")
|
|
uv_entrypoint.chmod(0o700)
|
|
agent = tmp_path / "agent.plist"
|
|
_write_agent(path=agent, repository=repository, uv_entrypoint=uv_entrypoint)
|
|
|
|
plan = plan_mission_core_launch_agent(
|
|
repository_root=repository,
|
|
agent_path=agent,
|
|
enable_local_observatory_worker=True,
|
|
)
|
|
desired = plistlib.loads(plan.desired_payload)
|
|
environment = desired["EnvironmentVariables"]
|
|
|
|
assert environment["MISSIONCORE_DATA_DIR"] == str(data_directory)
|
|
assert environment["MISSIONCORE_OBSERVATORY_WORKER_LOCAL_ENABLED"] == "1"
|
|
assert environment["MISSIONCORE_ARTIFACT_STORE_ROOT"] == str(
|
|
data_directory / "observatory-artifact-store"
|
|
)
|
|
assert environment["MISSIONCORE_OBSERVATORY_WORKER_SOURCE_CAS_ROOT"] == str(
|
|
data_directory / "observatory-worker-source-cas"
|
|
)
|
|
assert environment["MISSIONCORE_OBSERVATORY_WORKER_RESULT_STAGING_ROOT"] == str(
|
|
data_directory / "observatory-worker-result-staging"
|
|
)
|
|
assert plan.local_observatory_worker_enabled is True
|
|
assert plan.to_dict()["changes"]["local_observatory_worker_enabled"] is True
|
|
|
|
|
|
def test_launch_agent_plan_rejects_missing_local_observatory_roots(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
repository = tmp_path / "repo"
|
|
repository.mkdir()
|
|
(repository / ".runtime" / "mission-core").mkdir(parents=True, mode=0o700)
|
|
uv_entrypoint = tmp_path / "uv"
|
|
uv_entrypoint.write_text("#!/bin/sh\n")
|
|
uv_entrypoint.chmod(0o700)
|
|
agent = tmp_path / "agent.plist"
|
|
_write_agent(path=agent, repository=repository, uv_entrypoint=uv_entrypoint)
|
|
|
|
with pytest.raises(
|
|
MissionCoreLaunchAgentError,
|
|
match="local Observatory artifact store is unavailable",
|
|
):
|
|
plan_mission_core_launch_agent(
|
|
repository_root=repository,
|
|
agent_path=agent,
|
|
enable_local_observatory_worker=True,
|
|
)
|
|
|
|
|
|
def test_launch_agent_plan_rejects_cross_repository_migration_by_default(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
current_repository = tmp_path / "current-repo"
|
|
current_repository.mkdir()
|
|
desired_repository = tmp_path / "desired-repo"
|
|
desired_repository.mkdir()
|
|
uv_entrypoint = tmp_path / "uv"
|
|
uv_entrypoint.write_text("#!/bin/sh\n")
|
|
uv_entrypoint.chmod(0o700)
|
|
agent = tmp_path / "agent.plist"
|
|
_write_agent(
|
|
path=agent,
|
|
repository=current_repository,
|
|
uv_entrypoint=uv_entrypoint,
|
|
)
|
|
|
|
with pytest.raises(
|
|
MissionCoreLaunchAgentError,
|
|
match="current launch agent targets another repository",
|
|
):
|
|
plan_mission_core_launch_agent(
|
|
repository_root=desired_repository,
|
|
agent_path=agent,
|
|
)
|
|
|
|
|
|
def test_launch_agent_plan_binds_explicit_cross_repository_migration(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
current_repository = tmp_path / "current-repo"
|
|
current_repository.mkdir()
|
|
desired_repository = tmp_path / "desired-repo"
|
|
desired_repository.mkdir()
|
|
other_repository = tmp_path / "other-repo"
|
|
other_repository.mkdir()
|
|
data_directory = current_repository / ".runtime" / "mission-core"
|
|
data_directory.mkdir(parents=True, mode=0o700)
|
|
uv_entrypoint = tmp_path / "uv"
|
|
uv_entrypoint.write_text("#!/bin/sh\n")
|
|
uv_entrypoint.chmod(0o700)
|
|
agent = tmp_path / "agent.plist"
|
|
_write_agent(
|
|
path=agent,
|
|
repository=current_repository,
|
|
uv_entrypoint=uv_entrypoint,
|
|
)
|
|
|
|
with pytest.raises(
|
|
MissionCoreLaunchAgentError,
|
|
match="does not target the expected current repository",
|
|
):
|
|
plan_mission_core_launch_agent(
|
|
repository_root=desired_repository,
|
|
agent_path=agent,
|
|
expected_current_repository_root=other_repository,
|
|
)
|
|
|
|
plan = plan_mission_core_launch_agent(
|
|
repository_root=desired_repository,
|
|
agent_path=agent,
|
|
expected_current_repository_root=current_repository,
|
|
)
|
|
desired = plistlib.loads(plan.desired_payload)
|
|
document = plan.to_dict()
|
|
|
|
assert plan.current_working_directory == current_repository
|
|
assert plan.desired_working_directory == desired_repository
|
|
assert plan.preserved_data_directory == data_directory
|
|
assert desired["WorkingDirectory"] == str(desired_repository)
|
|
assert desired["EnvironmentVariables"]["MISSIONCORE_DATA_DIR"] == str(data_directory)
|
|
assert document["current_working_directory"] == str(current_repository)
|
|
assert document["desired_working_directory"] == str(desired_repository)
|
|
assert document["preserved_data_directory"] == str(data_directory)
|
|
assert document["changes"]["repository_migration"] is True
|
|
assert document["changes"]["data_directory_preserved"] is True
|
|
assert document["changes"]["preserved_data_directory"] == str(data_directory)
|
|
|
|
|
|
def test_launch_agent_migration_retains_existing_private_data_directory(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
current_repository = tmp_path / "current-repo"
|
|
current_repository.mkdir()
|
|
desired_repository = tmp_path / "desired-repo"
|
|
desired_repository.mkdir()
|
|
configured_data_directory = tmp_path / "canonical-data"
|
|
configured_data_directory.mkdir(mode=0o700)
|
|
uv_entrypoint = tmp_path / "uv"
|
|
uv_entrypoint.write_text("#!/bin/sh\n")
|
|
uv_entrypoint.chmod(0o700)
|
|
agent = tmp_path / "agent.plist"
|
|
_write_agent(
|
|
path=agent,
|
|
repository=current_repository,
|
|
uv_entrypoint=uv_entrypoint,
|
|
environment={
|
|
"PATH": "/usr/bin:/bin",
|
|
"MISSIONCORE_DATA_DIR": str(configured_data_directory),
|
|
},
|
|
)
|
|
|
|
plan = plan_mission_core_launch_agent(
|
|
repository_root=desired_repository,
|
|
agent_path=agent,
|
|
expected_current_repository_root=current_repository,
|
|
)
|
|
desired = plistlib.loads(plan.desired_payload)
|
|
|
|
assert plan.preserved_data_directory == configured_data_directory
|
|
assert (
|
|
desired["EnvironmentVariables"]["MISSIONCORE_DATA_DIR"]
|
|
== str(configured_data_directory)
|
|
)
|
|
|
|
|
|
def test_launch_agent_migration_rejects_symlinked_data_directory(tmp_path: Path) -> None:
|
|
current_repository = tmp_path / "current-repo"
|
|
current_repository.mkdir()
|
|
desired_repository = tmp_path / "desired-repo"
|
|
desired_repository.mkdir()
|
|
private_directory = tmp_path / "private-data"
|
|
private_directory.mkdir(mode=0o700)
|
|
symlinked_directory = tmp_path / "data-link"
|
|
symlinked_directory.symlink_to(private_directory, target_is_directory=True)
|
|
uv_entrypoint = tmp_path / "uv"
|
|
uv_entrypoint.write_text("#!/bin/sh\n")
|
|
uv_entrypoint.chmod(0o700)
|
|
agent = tmp_path / "agent.plist"
|
|
_write_agent(
|
|
path=agent,
|
|
repository=current_repository,
|
|
uv_entrypoint=uv_entrypoint,
|
|
environment={
|
|
"PATH": "/usr/bin:/bin",
|
|
"MISSIONCORE_DATA_DIR": str(symlinked_directory),
|
|
},
|
|
)
|
|
|
|
with pytest.raises(
|
|
MissionCoreLaunchAgentError,
|
|
match="data directory is not a private canonical directory",
|
|
):
|
|
plan_mission_core_launch_agent(
|
|
repository_root=desired_repository,
|
|
agent_path=agent,
|
|
expected_current_repository_root=current_repository,
|
|
)
|
|
|
|
|
|
def test_launch_agent_migration_rejects_nonprivate_default_data_directory(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
current_repository = tmp_path / "current-repo"
|
|
current_repository.mkdir()
|
|
desired_repository = tmp_path / "desired-repo"
|
|
desired_repository.mkdir()
|
|
data_directory = current_repository / ".runtime" / "mission-core"
|
|
data_directory.mkdir(parents=True, mode=0o755)
|
|
data_directory.chmod(0o755)
|
|
uv_entrypoint = tmp_path / "uv"
|
|
uv_entrypoint.write_text("#!/bin/sh\n")
|
|
uv_entrypoint.chmod(0o700)
|
|
agent = tmp_path / "agent.plist"
|
|
_write_agent(
|
|
path=agent,
|
|
repository=current_repository,
|
|
uv_entrypoint=uv_entrypoint,
|
|
)
|
|
|
|
with pytest.raises(
|
|
MissionCoreLaunchAgentError,
|
|
match="data directory is not a private canonical directory",
|
|
):
|
|
plan_mission_core_launch_agent(
|
|
repository_root=desired_repository,
|
|
agent_path=agent,
|
|
expected_current_repository_root=current_repository,
|
|
)
|