"""Synthetic release safety cases; no cluster, credentials or real content.""" import copy import importlib.util import json import os import stat from pathlib import Path from types import SimpleNamespace from unittest.mock import Mock import pytest SOURCE = Path(__file__).resolve().parents[1] / "tools/coulomb_image_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-image-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() def test_nested_capsule_and_release_root_stay_private_under_normal_umask(tmp_path, monkeypatch): root, _ = capsule(tmp_path) nested = root / "charts/coulomb/templates/deployment.yaml" nested.parent.mkdir(parents=True) nested.write_bytes(b"synthetic chart") plan = {"files": {str(p.relative_to(root)): release.sha(p.read_bytes()) for p in root.rglob("*") if p.is_file() and p.name != "manifest.json"}} (root / "manifest.json").write_text(json.dumps(plan)) monkeypatch.setattr(release.os, "geteuid", lambda: 0) monkeypatch.setattr(release, "JOURNAL_ROOT", tmp_path / "journal") previous = os.umask(0o022) try: instance = release.Release(root, "sha256:" + release.sha(release.canonical(plan))) finally: os.umask(previous) for path in [tmp_path / "journal", *(tmp_path / "journal").rglob("*")]: assert stat.S_IMODE(path.stat().st_mode) == (0o700 if path.is_dir() else 0o600) assert (instance.root / nested.relative_to(root)).read_bytes() == b"synthetic chart" @pytest.mark.parametrize("problem", ["broad", "link", "ancestor_link", "file"]) def test_unsafe_release_parent_is_refused_without_changing_it(tmp_path, monkeypatch, problem): root, digest = capsule(tmp_path) journal = tmp_path / "journal" if problem == "broad": journal.mkdir(mode=0o755) elif problem == "file": journal.write_text("untouched") elif problem == "link": journal.symlink_to(root, target_is_directory=True) else: alias = tmp_path / "alias" alias.symlink_to(root, target_is_directory=True) journal = alias / "journal" monkeypatch.setattr(release.os, "geteuid", lambda: 0) monkeypatch.setattr(release, "JOURNAL_ROOT", journal) before = {str(p): p.lstat() for p in tmp_path.rglob("*")} with pytest.raises((release.Refused, OSError)): release.Release(root, digest) assert before == {str(p): p.lstat() for p in tmp_path.rglob("*")} @pytest.mark.parametrize("field", ["business_rows_sha256", "content_files_sha256", "migration_rows_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", "migration_rows_sha256": "synthetic-migrations"} 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() @pytest.mark.parametrize("rollback_spec,rollback_ready,verified", [ ({"synthetic": "old"}, 1, True), ({"synthetic": "candidate"}, 1, False), ({"synthetic": "old"}, 0, False), ]) def test_postflight_failure_rolls_back_image_without_restoring_data( tmp_path, monkeypatch, rollback_spec, rollback_ready, verified): 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", "migration_rows_sha256": "synthetic-migrations"} instance.plan = {"candidate_spec_sha256": release.sha(release.canonical(spec)), "baseline_revision": 18, "baseline_spec_sha256": release.sha(release.canonical({"synthetic": "old"})), "expected_data": baseline} instance.preflight = Mock() instance.capture = Mock() instance.save = Mock() instance.deployment = Mock(side_effect=[ {"spec": copy.deepcopy(spec), "status": {"readyReplicas": 1}}, {"spec": rollback_spec, "status": {"readyReplicas": rollback_ready}}, ]) 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", "18"] 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 verified 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() @pytest.mark.parametrize("field", ["global_slug_constraint", "migration_0003_applied"]) @pytest.mark.parametrize("value", [False, None]) def test_image_only_release_requires_existing_migration_and_constraint(field, value): instance = object.__new__(release.Release) baseline = {"business_tables": 2, "business_rows": 3, "business_rows_sha256": "rows", "content_files": 4, "content_files_sha256": "files", "migration_rows_sha256": "migrations"} instance.plan = {"expected_data": baseline} observed = dict(baseline, duplicate_slug_groups=0, tenant_slug_constraint=True, global_slug_constraint=True, migration_0003_applied=True) instance.same_data(observed) observed[field] = value with pytest.raises(release.Refused, match="already_migrated_baseline_required"): instance.same_data(observed) def test_rollback_does_not_overwrite_unrecognized_snapshot_helper(tmp_path, monkeypatch): pin = tmp_path / "snapshot.py" pin.write_bytes(b"concurrent owner change") monkeypatch.setattr(release, "SNAPSHOT", pin) instance = object.__new__(release.Release) instance.plan = {"files": {"release/identity-export-snapshot.py": release.sha(b"new pin")}} with pytest.raises(release.Refused, match="snapshot_pin_changed_during_rollback"): instance.restore_pin(b"old pin") assert pin.read_bytes() == b"concurrent owner change" def test_known_candidate_helper_can_be_rolled_back(tmp_path, monkeypatch): pin = tmp_path / "snapshot.py" pin.write_bytes(b"new pin") monkeypatch.setattr(release, "SNAPSHOT", pin) instance = object.__new__(release.Release) instance.plan = {"files": {"release/identity-export-snapshot.py": release.sha(b"new pin")}} instance.restore_pin(b"old pin") assert pin.read_bytes() == b"old pin" # A completed inverse can be checked again without changing it. instance.restore_pin(b"old pin") assert pin.read_bytes() == b"old pin" def test_missing_installation_refuses_before_execution_journal(tmp_path, monkeypatch): root, _ = capsule(tmp_path) plan = json.loads((root / 'manifest.json').read_text()) plan['prerequisite_installation_digest'] = 'sha256:' + 'a' * 64 (root / 'manifest.json').write_text(json.dumps(plan)) digest = 'sha256:' + release.sha(release.canonical(plan)) monkeypatch.setattr(release.os, 'geteuid', lambda: 0) monkeypatch.setattr(release, 'JOURNAL_ROOT', tmp_path / 'journal') monkeypatch.setattr(release, 'INSTALL_RECEIPT', tmp_path / 'missing.json') with pytest.raises(release.Refused, match='installation_required_before_image'): release.Release(root, digest) assert not (tmp_path / 'journal').exists() @pytest.fixture def installed(monkeypatch): instance = object.__new__(release.Release) digest = 'sha256:' + 'a' * 64 instance.plan = {'prerequisite_installation_digest': digest} receipt = {'schema': 'coulomb.backup-source-supervised-installation.v1', 'proposal_digest': digest, 'status': 'sources_installed_units_inactive_verified', 'verified_targets': 19, 'timers_activated': False} path = SimpleNamespace(parents=[], lstat=lambda: SimpleNamespace( st_mode=stat.S_IFREG | 0o600, st_uid=0, st_nlink=1), read_bytes=lambda: json.dumps(receipt).encode()) monkeypatch.setattr(release, 'INSTALL_RECEIPT', path) state = {'active': 'inactive', 'enabled': 'disabled', 'jobs': b''} def command(arguments): if arguments[1] == 'list-jobs': return SimpleNamespace(stdout=state['jobs']) return SimpleNamespace(stdout=(f"LoadState=loaded\nActiveState={state['active']}\n" f"UnitFileState={state['enabled']}\n").encode()) instance.command = Mock(side_effect=command) return instance, receipt, state def test_completed_installer_and_inactive_units_admit_release_prerequisite(installed): instance, _, _ = installed instance.verify_installation_prerequisite() assert instance.command.call_count == 5 @pytest.mark.parametrize('field,value', [ ('status', 'installation_requires_inspection'), ('verified_targets', 18), ('proposal_digest', 'sha256:' + 'b' * 64), ('timers_activated', True), ]) def test_wrong_or_incomplete_installation_never_checks_or_changes_units(installed, field, value): instance, receipt, _ = installed receipt[field] = value with pytest.raises(release.Refused, match='verified_inactive_backup_installation'): instance.verify_installation_prerequisite() instance.command.assert_not_called() @pytest.mark.parametrize('field,value', [('active', 'active'), ('enabled', 'enabled'), ('jobs', b'17 coulomb-social-paired-backup.service start waiting\n'), ('jobs', b'unrecognized job format\n')]) def test_active_enabled_or_queued_backup_refuses_image_release(installed, field, value): instance, _, state = installed state[field] = value with pytest.raises(release.Refused): instance.verify_installation_prerequisite() assert all(c.args[0][1] in {'show', 'list-jobs'} for c in instance.command.call_args_list) def test_an_explicit_release_plan_seals_its_own_artifacts(tmp_path, monkeypatch): monkeypatch.setattr(launcher, 'ROOT', tmp_path) paths = ['tools/coulomb-image-release.py', 'tools/coulomb_image_release_node.py', 'tools/coulomb_image_probe.py', 'release/next-values.yaml', 'release/next-snapshot.py', 'charts/coulomb-social/Chart.yaml', 'release/next-evidence/proof.json'] for name in paths: path = tmp_path / name path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(('synthetic ' + name).encode()) plan = {'values_file': paths[3], 'snapshot_file': paths[4], 'evidence_directory': 'release/next-evidence', 'rehearsal_evidence_sha256': {'proof.json': release.sha((tmp_path / paths[-1]).read_bytes())}} path = tmp_path / 'release/next.json' path.write_text(json.dumps(plan)) prepared, digest, content = launcher.prepare(path) assert set(content) == set(paths) | {'manifest.json'} assert digest == 'sha256:' + release.sha(release.canonical(prepared)) plan['values_file'] = '../outside.yaml' path.write_text(json.dumps(plan)) with pytest.raises(RuntimeError, match='release_paths'): launcher.prepare(path) @pytest.mark.parametrize('name', ['release/missing.yaml', '../outside.yaml', '/outside.yaml']) def test_node_refuses_unsealed_artifact_reference_before_journal(tmp_path, monkeypatch, name): root, _ = capsule(tmp_path) plan = json.loads((root / 'manifest.json').read_text()) plan['values_file'] = name (root / 'manifest.json').write_text(json.dumps(plan)) monkeypatch.setattr(release.os, 'geteuid', lambda: 0) monkeypatch.setattr(release, 'JOURNAL_ROOT', tmp_path / 'journal') with pytest.raises(release.Refused, match='reviewed_release_artifact'): release.Release(root, 'sha256:' + release.sha(release.canonical(plan))) assert not (tmp_path / 'journal').exists() def test_initializer_refusal_is_structured_and_never_calls_execute(monkeypatch, capsys): monkeypatch.setattr(release, 'Release', Mock(side_effect=release.Refused( 'inactive_backup_installation_required_before_image_release'))) monkeypatch.setattr(release.argparse.ArgumentParser, 'parse_args', lambda _: SimpleNamespace( capsule=Path('/synthetic'), expect_digest='sha256:' + 'a' * 64, execute=False, preflight=True)) with pytest.raises(SystemExit): release.main() result = json.loads(capsys.readouterr().out) assert result['execution_started'] is False assert result['failure_reason'] == 'inactive_backup_installation_required_before_image_release' def test_custom_snapshot_artifact_is_used_for_recognized_pin_rollback(tmp_path, monkeypatch): path = tmp_path / 'snapshot.py' path.write_bytes(b'new reviewed pin') monkeypatch.setattr(release, 'SNAPSHOT', path) instance = object.__new__(release.Release) instance.plan = {'snapshot_file': 'release/next-snapshot.py', 'files': {'release/next-snapshot.py': release.sha(path.read_bytes())}} instance.restore_pin(b'previous pin') assert path.read_bytes() == b'previous pin'