diff --git a/PUBLIC_SOURCE_EXPORT.md b/PUBLIC_SOURCE_EXPORT.md index 6290110..d6716b6 100644 --- a/PUBLIC_SOURCE_EXPORT.md +++ b/PUBLIC_SOURCE_EXPORT.md @@ -1,3 +1,3 @@ # Public source export -This source snapshot was generated from the private canonical repository at revision `b619192999975e4ac44b14e43df48ae75bd745b2`. It contains no private Git history or operational evidence. Changes are published from the canonical repository. +This source snapshot was generated from the private canonical repository at revision `ef07e484d2ff369f8d57d6cf3c10bd00c59b5fe1`. It contains no private Git history or operational evidence. Changes are published from the canonical repository. diff --git a/PUBLIC_SOURCE_MANIFEST.json b/PUBLIC_SOURCE_MANIFEST.json index 5ca01f8..763cfe5 100644 --- a/PUBLIC_SOURCE_MANIFEST.json +++ b/PUBLIC_SOURCE_MANIFEST.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceRevision": "b619192999975e4ac44b14e43df48ae75bd745b2", + "sourceRevision": "ef07e484d2ff369f8d57d6cf3c10bd00c59b5fe1", "files": [ { "path": ".dockerignore", @@ -1659,8 +1659,8 @@ }, { "path": "backend/tests/test_runner_fleet_specialize.py", - "sha256": "2d692ef97b2414c9ac114f25d4337b58e6989e719dc6643a3635fa3fab5288e5", - "bytes": 200698 + "sha256": "4df95dc997d0ef9918209b233a470d70eadcb0ca62046be25a5bb399cbce8496", + "bytes": 200956 }, { "path": "backend/tests/test_runner_fleet_v2_execution_surface.py", @@ -1694,8 +1694,8 @@ }, { "path": "backend/tests/test_runner_specialization_child_boundary.py", - "sha256": "62bd56ea8ac22c21b8c39dfeec885285063e778a3b8490f3a7a2229e3ca4121a", - "bytes": 8886 + "sha256": "06a38a118334f69d8ef0f991c3568bac6a5666f2254cafca8185de22cd43d147", + "bytes": 12516 }, { "path": "backend/tests/test_runner_specialization_controller_surface.py", diff --git a/backend/tests/test_runner_fleet_specialize.py b/backend/tests/test_runner_fleet_specialize.py index 490b76d..0bfe562 100644 --- a/backend/tests/test_runner_fleet_specialize.py +++ b/backend/tests/test_runner_fleet_specialize.py @@ -3530,7 +3530,12 @@ def test_specialization_stage_fixture_quiesces_old_before_registering_and_never_ monkeypatch.setattr(specialize, "specialization_state", lambda: "active" if marker_active["value"] else "blocked") if probe_fails: - monkeypatch.setattr(specialize, "_rollback_before_activation", lambda _journal: True) + monkeypatch.setattr(specialize, "_record_migration_failure", lambda _journal, _error: events.append("failure-recorded")) + def rollback(_journal): + assert "failure-recorded" in events + events.append("rollback") + return True + monkeypatch.setattr(specialize, "_rollback_before_activation", rollback) with pytest.raises( specialize.RunnerFleetSpecializeError, match="failed_rolled_back", diff --git a/backend/tests/test_runner_specialization_child_boundary.py b/backend/tests/test_runner_specialization_child_boundary.py index b131047..55928e1 100644 --- a/backend/tests/test_runner_specialization_child_boundary.py +++ b/backend/tests/test_runner_specialization_child_boundary.py @@ -1,6 +1,7 @@ from __future__ import annotations import hashlib +import json import os import shutil from pathlib import Path @@ -11,6 +12,75 @@ import pytest from test_runner_fleet_specialize import specialize # noqa: F401 +@pytest.mark.parametrize("cause", [ + "resource_cgroup_process_mapping_invalid", "resource_cgroup_probe_failed", + "resource_cgroup_probe_lock_busy", "resource_cgroup_probe_lock_untrusted", + "resource_cgroup_probe_lock_store_untrusted", "resource_cgroup_probe_lock_unavailable", + "resource_cgroup_command_failed", "resource_cgroup_command_unavailable", + "resource_cgroup_probe_io_failed", "resource_cgroup_probe_path_invalid", + "secret-value", "resource_cgroup_probe_failed\nsecret-value", +]) +def test_rollback_diagnostic_preserves_only_fixed_codes(specialize, monkeypatch, tmp_path, cause): + root = tmp_path / "backups" + root.mkdir(mode=0o700) + backup = root / "specialized-owned" + backup.mkdir(mode=0o700) + monkeypatch.setattr(specialize, "_SPECIALIZED_BACKUP_ROOT", root) + journal = {"backupDirectory": str(backup), "sourceRevision": "a" * 40, + "sourceTree": "b" * 40, "stage": "state_prepared"} + error = specialize.RunnerFleetSpecializeError("runner_fleet_specialization_cgroup_family_unavailable") + error.__cause__ = RuntimeError(cause) + specialize._record_migration_failure(journal, error) + raw = (backup / "migration-failure.json").read_bytes() + record = json.loads(raw) + assert record == {"schemaVersion": 1, "sourceRevision": "a" * 40, "sourceTree": "b" * 40, + "stage": "state_prepared", "failureCode": str(error), + "causeCode": cause if cause in specialize._RESOURCE_FAILURE_CODES else str(error)} + assert b"secret-value" not in raw + + +@pytest.mark.parametrize("code", [ + "runner_fleet_specialization_container_health_timeout", + "runner_fleet_specialization_dind_population_invalid", + "runner_fleet_specialization_cgroup_family_result_invalid", +]) +def test_dind_preparation_codes_survive_both_finite_boundaries(specialize, monkeypatch, capsys, code): + def fail(): + raise specialize.RunnerFleetSpecializeError(code) + monkeypatch.setattr(specialize, "_apply_from_fixed_script", fail) + assert specialize._main(["--apply-fixed", "--confirm", specialize.CONFIRMATION]) == 1 + captured = capsys.readouterr() + assert captured.out == "" + assert captured.err == f"APPOPS_SPECIALIZATION_FAILURE:{code}\n" + result = SimpleNamespace(returncode=1, stdout="", stderr=captured.err) + assert specialize._script_failure_code(result) == code + + +@pytest.mark.parametrize("defect", ["foreign", "symlink", "stage", "revision", "write"]) +def test_rollback_diagnostic_refuses_untrusted_storage_and_never_prevents_rollback( + specialize, monkeypatch, tmp_path, defect, +): + root = tmp_path / "backups" + root.mkdir(mode=0o700) + backup = root / "specialized-owned" + backup.mkdir(mode=0o700) + monkeypatch.setattr(specialize, "_SPECIALIZED_BACKUP_ROOT", root) + journal = {"backupDirectory": str(backup), "sourceRevision": "a" * 40, + "sourceTree": "b" * 40, "stage": "state_prepared"} + if defect == "foreign": + journal["backupDirectory"] = str(tmp_path) + elif defect == "symlink": + monkeypatch.setattr(specialize, "_trusted_directory", lambda _path: (_ for _ in ()).throw(OSError("private-secret"))) + elif defect == "stage": + journal["stage"] = "secret-value" + elif defect == "revision": + journal["sourceRevision"] = "secret-value" + else: + monkeypatch.setattr(specialize, "_write_private_atomic", lambda *_a, **_k: (_ for _ in ()).throw(OSError("private-secret"))) + specialize._record_migration_failure(journal, RuntimeError("secret-value")) + assert not (backup / "migration-failure.json").exists() + + def test_child_fixed_failure_is_finite_and_does_not_echo_exception(specialize, monkeypatch, capsys): def fail(): raise specialize.RunnerFleetSpecializeError("runner_fleet_specialization_entrypoint_untrusted")