Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a17f16-d99c-7ea3-95f0-8212897fba6b
142 lines
6.3 KiB
Python
142 lines
6.3 KiB
Python
"""Synthetic release safety cases; no cluster, credentials or real content."""
|
|
import copy
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
SOURCE = Path(__file__).resolve().parents[1] / "tools/coulomb_storage_release_node.py"
|
|
spec = importlib.util.spec_from_file_location("storage_release", SOURCE)
|
|
release = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(release)
|
|
|
|
launcher_spec = importlib.util.spec_from_file_location("storage_launcher", SOURCE.with_name("coulomb-storage-release.py"))
|
|
launcher = importlib.util.module_from_spec(launcher_spec)
|
|
launcher_spec.loader.exec_module(launcher)
|
|
|
|
|
|
def capsule(tmp_path):
|
|
root = tmp_path / "capsule"
|
|
root.mkdir()
|
|
(root / "probe.py").write_bytes(b"synthetic read-only probe")
|
|
plan = {"files": {"probe.py": release.sha((root / "probe.py").read_bytes())}}
|
|
(root / "manifest.json").write_text(json.dumps(plan))
|
|
return root, "sha256:" + release.sha(release.canonical(plan))
|
|
|
|
|
|
def test_capsule_tampering_refuses_before_privileged_work(tmp_path, monkeypatch):
|
|
root, digest = capsule(tmp_path)
|
|
monkeypatch.setattr(release.os, "geteuid", lambda: 0)
|
|
monkeypatch.setattr(release, "JOURNAL_ROOT", tmp_path / "journal")
|
|
(root / "probe.py").write_bytes(b"changed")
|
|
with pytest.raises(release.Refused, match="capsule_file_differs"):
|
|
release.Release(root, digest)
|
|
assert not (tmp_path / "journal").exists()
|
|
|
|
|
|
def test_preflight_never_creates_an_execution_journal(tmp_path, monkeypatch):
|
|
root, digest = capsule(tmp_path)
|
|
monkeypatch.setattr(release.os, "geteuid", lambda: 0)
|
|
monkeypatch.setattr(release, "JOURNAL_ROOT", tmp_path / "journal")
|
|
instance = release.Release(root, digest, read_only=True)
|
|
instance.save()
|
|
assert not (tmp_path / "journal").exists()
|
|
|
|
|
|
def test_prior_attempt_is_not_blindly_replayed(tmp_path, monkeypatch):
|
|
root, digest = capsule(tmp_path)
|
|
journal = tmp_path / "journal"
|
|
(journal / digest.removeprefix("sha256:")).mkdir(parents=True)
|
|
monkeypatch.setattr(release.os, "geteuid", lambda: 0)
|
|
monkeypatch.setattr(release, "JOURNAL_ROOT", journal)
|
|
with pytest.raises(release.Refused, match="prior_execution_requires_review"):
|
|
release.Release(root, digest)
|
|
|
|
|
|
def test_mutable_staging_is_frozen_before_live_work(tmp_path, monkeypatch):
|
|
root, digest = capsule(tmp_path)
|
|
monkeypatch.setattr(release.os, "geteuid", lambda: 0)
|
|
monkeypatch.setattr(release, "JOURNAL_ROOT", tmp_path / "journal")
|
|
instance = release.Release(root, digest)
|
|
(root / "probe.py").write_bytes(b"changed after verification")
|
|
assert (instance.root / "probe.py").read_bytes() == b"synthetic read-only probe"
|
|
assert (instance.journal / "receipt.json").is_file()
|
|
|
|
|
|
@pytest.mark.parametrize("field", ["business_rows_sha256", "content_files_sha256"])
|
|
def test_dataset_drift_is_not_accepted(field):
|
|
instance = object.__new__(release.Release)
|
|
baseline = {"business_tables": 2, "business_rows": 3, "business_rows_sha256": "synthetic-a",
|
|
"content_files": 4, "content_files_sha256": "synthetic-b"}
|
|
instance.plan = {"expected_data": baseline}
|
|
changed = dict(baseline, duplicate_slug_groups=0, tenant_slug_constraint=True)
|
|
changed[field] = "different"
|
|
with pytest.raises(release.Refused, match="dataset_drifted"):
|
|
instance.same_data(changed)
|
|
|
|
|
|
def test_failed_preflight_never_captures_or_upgrades():
|
|
instance = object.__new__(release.Release)
|
|
instance.preflight = Mock(side_effect=release.Refused("guard stopped"))
|
|
instance.capture = Mock()
|
|
instance.command = Mock()
|
|
with pytest.raises(release.Refused):
|
|
instance.execute()
|
|
instance.capture.assert_not_called()
|
|
instance.command.assert_not_called()
|
|
|
|
|
|
def test_postflight_failure_rolls_back_image_without_restoring_data(tmp_path, monkeypatch):
|
|
pin = tmp_path / "snapshot.py"
|
|
pin.write_bytes(b"old pin")
|
|
monkeypatch.setattr(release, "SNAPSHOT", pin)
|
|
instance = object.__new__(release.Release)
|
|
instance.receipt = {}
|
|
instance.root = tmp_path
|
|
spec = {"synthetic": "candidate"}
|
|
baseline = {"business_tables": 2, "business_rows": 3, "business_rows_sha256": "synthetic-a",
|
|
"content_files": 4, "content_files_sha256": "synthetic-b"}
|
|
instance.plan = {"candidate_spec_sha256": release.sha(release.canonical(spec)), "baseline_revision": 17,
|
|
"expected_data": baseline}
|
|
instance.preflight = Mock()
|
|
instance.capture = Mock()
|
|
instance.save = Mock()
|
|
instance.deployment = lambda: {"spec": copy.deepcopy(spec), "status": {"readyReplicas": 1}}
|
|
instance.probe = lambda mode: dict(baseline, business_rows_sha256="changed")
|
|
instance.command = Mock(return_value=SimpleNamespace(stdout=b"", returncode=0))
|
|
instance.restore_pin = Mock()
|
|
with pytest.raises(release.Refused, match="dataset_drifted"):
|
|
instance.execute()
|
|
commands = [call.args[0] for call in instance.command.call_args_list]
|
|
assert commands[0][:2] == ["helm", "upgrade"] and "--atomic" in commands[0]
|
|
assert commands[1][:4] == ["helm", "rollback", "coulomb-social", "17"]
|
|
assert not any("pg_restore" in command for command in commands)
|
|
instance.restore_pin.assert_called_once_with(b"old pin")
|
|
assert instance.receipt["image_rollback_completed"] is True
|
|
|
|
|
|
def test_live_launcher_rejects_unattended_execution_before_ssh(monkeypatch):
|
|
_, digest, _ = launcher.prepare()
|
|
monkeypatch.setattr(launcher.sys, "argv", ["release", "--execute", "--expect-digest", digest])
|
|
monkeypatch.setattr(launcher.sys, "stdin", SimpleNamespace(isatty=lambda: False))
|
|
command = Mock()
|
|
monkeypatch.setattr(launcher.subprocess, "run", command)
|
|
with pytest.raises(SystemExit, match="own terminal"):
|
|
launcher.main()
|
|
command.assert_not_called()
|
|
|
|
|
|
def test_timeout_gives_the_existing_capture_resume_window(monkeypatch):
|
|
instance = object.__new__(release.Release)
|
|
instance.env = {}
|
|
process = Mock()
|
|
process.communicate.side_effect = release.subprocess.TimeoutExpired(["synthetic"], 1)
|
|
monkeypatch.setattr(release.subprocess, "Popen", Mock(return_value=process))
|
|
with pytest.raises(release.Refused, match="cleanup_window"):
|
|
instance.command(["synthetic"], timeout=1)
|
|
process.terminate.assert_called_once()
|
|
process.wait.assert_called_once_with(timeout=450)
|
|
process.kill.assert_not_called()
|