from __future__ import annotations import importlib.util import json import sys import types from contextlib import contextmanager from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] MODULE_PATH = ROOT / "ops" / "gitea-deploy-broker" / "registered_legacy_adoption.py" REPOSITORY = "jens/quiz-monitor" CONTAINER = "quiz-monitor-quiz-server-1" IMAGE_ID = "sha256:" + "a" * 64 SOURCE_FINGERPRINT = "b" * 64 RUNTIME_FINGERPRINT = "c" * 64 STATE_SHA256 = "d" * 64 CONTAINER_ID = "e" * 64 CAPTURED_VALUE = "gpt-6-luna" CAPTURED_KEY = "quiz-server/KNOWLEDGE_OPTIMIZER_MODEL" def _load_module(): spec = importlib.util.spec_from_file_location( "registered_legacy_followup_review", MODULE_PATH ) assert spec and spec.loader module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def _load_deploy_module(): if "fcntl" not in sys.modules: fcntl = types.ModuleType("fcntl") fcntl.LOCK_EX = 2 fcntl.LOCK_UN = 8 fcntl.flock = lambda *_args: None sys.modules["fcntl"] = fcntl name = "registered_legacy_followup_deploy_review" spec = importlib.util.spec_from_file_location( name, ROOT / "ops" / "gitea-deploy-broker" / "deploy.py" ) assert spec and spec.loader module = importlib.util.module_from_spec(spec) sys.modules[name] = module spec.loader.exec_module(module) return module def _captured_lineage(module): binding = { "schema": "itworx.registered-legacy-env-capture/v1", "sourceFingerprint": SOURCE_FINGERPRINT, "runtimeFingerprint": RUNTIME_FINGERPRINT, "expectedStateSha256": STATE_SHA256, "containerIDs": {CONTAINER: CONTAINER_ID}, "capturedEnvironmentKeys": [CAPTURED_KEY], } runtime_container = { "Image": IMAGE_ID, "Config": { "Labels": {"com.docker.compose.service": "quiz-server"}, "Env": [f"KNOWLEDGE_OPTIMIZER_MODEL={CAPTURED_VALUE}"], }, } baseline_model = { "services": { "quiz-server": { "environment": {"KNOWLEDGE_OPTIMIZER_MODEL": CAPTURED_VALUE}, "image": "quiz-monitor:baseline", } } } private = { "environmentCapture": binding, "runtimeConfiguration": { "composeModel": baseline_model, "containers": {CONTAINER: runtime_container}, "imageEnvironments": {IMAGE_ID: {}}, }, } baseline = { "environmentCapture": binding, "sourceFingerprint": SOURCE_FINGERPRINT, "runtimeFingerprint": RUNTIME_FINGERPRINT, "expectedStateSha256": STATE_SHA256, } project = {"containers": [CONTAINER]} return baseline_model, baseline, private, project def test_followup_candidate_cannot_mask_a_wrong_captured_environment_overlay(): module = _load_module() baseline_model, baseline, private, project = _captured_lineage(module) candidate_model = { "services": { "quiz-server": { "environment": {"KNOWLEDGE_OPTIMIZER_MODEL": "gpt-5.5"}, "image": "quiz-monitor:candidate", } } } with pytest.raises(module.RegisteredLegacyAdoptionError): module._candidate_runtime_comparison_model( candidate_model, baseline_model, baseline, private, project ) def test_main_revalidates_active_lineage_before_same_sha_fast_return( monkeypatch, tmp_path, capsys ): """A matching receipt cannot hide drift in a previously adopted runtime.""" module = _load_deploy_module() revision = "f" * 40 receipt = {"repository": REPOSITORY, "revision": revision, "tree": "a" * 40} state = { "schema_version": 3, "repository": REPOSITORY, "slug": "quiz-monitor", "compose_project": "quiz-monitor", "revision": revision, "live_revision": revision, "live_status": "healthy", "live_legacy_provenance": False, "live_artifacts": { "registered_legacy": { "requestId": "6f9619ff-8b86-4011-b42d-00c04fc964ff", "revision": revision, "runtimeReceiptSha256": "b" * 64, "runtimeFingerprint": "c" * 64, } }, "live_receipt": receipt, "status": "success", } project = { "slug": "quiz-monitor", "branch": "main", "source_dir": str(tmp_path / "source"), "compose_project": "quiz-monitor", "compose_files": ["docker-compose.yml"], "containers": [CONTAINER], "exact_source": True, "verify_oci_revision": True, "health_timeout": 180, } projects = {REPOSITORY: project} metadata = {"revision": revision, "tree": "a" * 40} events = [] @contextmanager def project_lock(_path, _description): events.append("project-lock") yield class Adoption: def active_lineage(self, repository, observed_project, observed_state, **_kwargs): assert repository == REPOSITORY assert observed_state["live_legacy_provenance"] is False events.append("active-lineage") raise module.DeployError("registered live runtime drift") monkeypatch.setattr(sys, "argv", ["deploy.py", "deploy", REPOSITORY, revision]) monkeypatch.delenv("APPOPS_REGISTERED_LEGACY_REQUEST_ID", raising=False) monkeypatch.setattr(module, "load_projects", lambda: projects) monkeypatch.setattr(module, "repository_path", lambda *_args: tmp_path) monkeypatch.setattr(module, "validate_commit", lambda _repo, _branch, value: value) monkeypatch.setattr(module, "deployment_metadata", lambda *_args: metadata) monkeypatch.setattr(module, "validate_project_config", lambda *_args: None) monkeypatch.setattr(module, "exclusive_lock", project_lock) monkeypatch.setattr(module, "read_state", lambda _project: state) monkeypatch.setattr(module, "verified_current_receipt", lambda *_args: ( events.append("same-sha-receipt") or receipt )) monkeypatch.setattr(module, "_load_registered_legacy_adoption", Adoption) monkeypatch.setattr(module, "_registered_legacy_receipt_key", lambda: b"k" * 32) monkeypatch.setattr(module, "sync_source", lambda *_args, **_kwargs: ( events.append("sync-source"), pytest.fail("drift must be rejected before source sync") )) with pytest.raises(module.DeployError, match="registered live runtime drift"): module.main() assert "active-lineage" in events assert "same-sha-receipt" not in events assert "sync-source" not in events assert json.loads(capsys.readouterr().out or "{}") == {} def test_main_defers_active_followup_source_sync_until_global_lock( monkeypatch, tmp_path ): """Ordinary main deployment must attest the active target before source sync.""" module = _load_deploy_module() previous_revision = "1" * 40 revision = "2" * 40 state_sha = "d" * 64 request_id = "6f9619ff-8b86-4011-b42d-00c04fc964ff" state = { "schema_version": 3, "repository": REPOSITORY, "slug": "quiz-monitor", "compose_project": "quiz-monitor", "revision": previous_revision, "live_revision": previous_revision, "live_status": "healthy", "live_legacy_provenance": False, "live_artifacts": {"registered_legacy": {"requestId": request_id}}, "live_receipt": {"repository": REPOSITORY, "revision": previous_revision}, "status": "success", } project = { "slug": "quiz-monitor", "branch": "main", "source_dir": str(tmp_path / "source"), "compose_project": "quiz-monitor", "compose_files": ["docker-compose.yml"], "containers": [CONTAINER], "exact_source": True, "verify_oci_revision": True, "health_timeout": 180, } projects = {REPOSITORY: project} metadata = {"revision": revision, "tree": "a" * 40} events = [] lock_state = {"global": False} @contextmanager def project_lock(_path, _description): events.append("project-lock-enter") yield @contextmanager def global_lock(): events.append("global-lock-enter") lock_state["global"] = True try: yield finally: lock_state["global"] = False events.append("global-lock-exit") class Adoption: def active_lineage(self, repository, observed_project, observed_state, **_kwargs): assert repository == REPOSITORY assert observed_state == state events.append("active-lineage") return { "requestId": request_id, "revision": previous_revision, "sourceFingerprint": SOURCE_FINGERPRINT, "runtimeFingerprint": RUNTIME_FINGERPRINT, "runtimeReceiptSha256": "e" * 64, "originUnknownSnapshot": {"revision": "unknown"}, } def prepare_followup_deployment(self, repository, observed_project, target, observed_state, **_kwargs): assert lock_state["global"] assert repository == REPOSITORY assert target == revision assert observed_state == state assert "sync-source" not in events events.append("prepare-followup") return { "requestId": request_id, "authorization": { "requestId": request_id, "revision": target, "expectedStateSha256": state_sha, }, "rollbackSnapshot": {"revision": previous_revision}, "originUnknownSnapshot": {"revision": "unknown"}, "expectedStateSha256": state_sha, "validationEvidenceSha256": "f" * 64, } monkeypatch.setattr(sys, "argv", ["deploy.py", "deploy", REPOSITORY, revision]) monkeypatch.delenv("APPOPS_REGISTERED_LEGACY_REQUEST_ID", raising=False) monkeypatch.setattr(module, "load_projects", lambda: projects) monkeypatch.setattr(module, "repository_path", lambda *_args: tmp_path) monkeypatch.setattr(module, "validate_commit", lambda _repo, _branch, value: value) monkeypatch.setattr(module, "deployment_metadata", lambda *_args: metadata) monkeypatch.setattr(module, "validate_project_config", lambda *_args: None) monkeypatch.setattr(module, "exclusive_lock", project_lock) monkeypatch.setattr(module, "exclusive_global_deploy_lock", global_lock) monkeypatch.setattr(module, "read_state", lambda _project: state) monkeypatch.setattr(module, "verified_current_receipt", lambda *_args: None) monkeypatch.setattr(module, "validate_forward_revision", lambda *_args, **_kwargs: None) monkeypatch.setattr(module, "_load_registered_legacy_adoption", Adoption) monkeypatch.setattr(module, "_registered_legacy_receipt_key", lambda: b"k" * 32) def stop_after_sync(*_args, **_kwargs): events.append("sync-source") if not lock_state["global"]: events.append("sync-before-global") raise module.DeployError("test stop after source sync") monkeypatch.setattr(module, "sync_source", stop_after_sync) monkeypatch.setattr(module, "copy_environment", lambda *_args: None) monkeypatch.setattr(module, "restore_live_source", lambda *_args: None) monkeypatch.setattr(module, "write_state", lambda *_args, **_kwargs: None) with pytest.raises(module.DeployError, match="test stop after source sync"): module.main() assert events.index("global-lock-enter") < events.index("active-lineage") assert events.index("active-lineage") < events.index("prepare-followup") assert events.index("prepare-followup") < events.index("sync-source") assert "sync-before-global" not in events def test_main_commits_followup_as_verified_not_legacy_and_keeps_unknown_origin( monkeypatch, tmp_path, capsys ): module = _load_deploy_module() previous_revision = "1" * 40 revision = "2" * 40 state_sha = "d" * 64 request_id = "6f9619ff-8b86-4011-b42d-00c04fc964ff" origin = {"revision": "unknown", "legacy_provenance": True, "opaque": "signed-origin"} known_snapshot = { "revision": previous_revision, "legacy_provenance": False, "registered_legacy": {"requestId": request_id}, "originUnknownSnapshot": origin, } state = { "schema_version": 3, "repository": REPOSITORY, "slug": "quiz-monitor", "compose_project": "quiz-monitor", "revision": previous_revision, "live_revision": previous_revision, "live_status": "healthy", "live_legacy_provenance": False, "live_artifacts": {"registered_legacy": {"requestId": request_id}}, "live_receipt": {"repository": REPOSITORY, "revision": previous_revision}, "status": "success", } project = { "slug": "quiz-monitor", "branch": "main", "source_dir": str(tmp_path / "source"), "compose_project": "quiz-monitor", "compose_files": ["docker-compose.yml"], "containers": [CONTAINER], "exact_source": True, "verify_oci_revision": True, "health_timeout": 180, } projects = {REPOSITORY: project} metadata = {"revision": revision, "tree": "a" * 40} events = [] writes = [] @contextmanager def project_lock(_path, _description): events.append("project-lock-enter") yield @contextmanager def global_lock(): events.append("global-lock-enter") yield events.append("global-lock-exit") class Adoption: def active_lineage(self, repository, _observed_project, observed_state, **_kwargs): assert repository == REPOSITORY assert observed_state == state events.append("active-lineage") return { "requestId": request_id, "revision": previous_revision, "sourceFingerprint": SOURCE_FINGERPRINT, "runtimeFingerprint": RUNTIME_FINGERPRINT, "runtimeReceiptSha256": "e" * 64, "originUnknownSnapshot": origin, } def prepare_followup_deployment(self, repository, _observed_project, target, observed_state, **_kwargs): assert repository == REPOSITORY and target == revision assert observed_state == state events.append("prepare-followup") return { "requestId": request_id, "authorization": { "requestId": request_id, "revision": target, "expectedStateSha256": state_sha, }, "rollbackSnapshot": known_snapshot, "originUnknownSnapshot": origin, "expectedStateSha256": state_sha, "validationEvidenceSha256": "f" * 64, } def record_live_runtime(self, repository, observed_project, observed_request, target, _receipt, **_kwargs): assert repository == REPOSITORY and observed_request == request_id assert target == revision assert observed_project["_registered_legacy_request_id"] == request_id events.append("record-live") return { "requestId": request_id, "revision": target, "runtimeReceiptSha256": "9" * 64, "runtimeFingerprint": "8" * 64, "originUnknownSnapshot": origin, } receipt = {"repository": REPOSITORY, "revision": revision, "tree": metadata["tree"]} monkeypatch.setattr(sys, "argv", ["deploy.py", "deploy", REPOSITORY, revision]) monkeypatch.delenv("APPOPS_REGISTERED_LEGACY_REQUEST_ID", raising=False) monkeypatch.setattr(module, "load_projects", lambda: projects) monkeypatch.setattr(module, "repository_path", lambda *_args: tmp_path) monkeypatch.setattr(module, "validate_commit", lambda _repo, _branch, value: value) monkeypatch.setattr(module, "deployment_metadata", lambda *_args: metadata) monkeypatch.setattr(module, "validate_project_config", lambda *_args: None) monkeypatch.setattr(module, "exclusive_lock", project_lock) monkeypatch.setattr(module, "exclusive_global_deploy_lock", global_lock) monkeypatch.setattr(module, "read_state", lambda _project: state) monkeypatch.setattr(module, "verified_current_receipt", lambda *_args: None) monkeypatch.setattr(module, "validate_forward_revision", lambda *_args, **_kwargs: None) monkeypatch.setattr(module, "revalidate_deployment_candidate", lambda *_args: None) monkeypatch.setattr(module, "_load_registered_legacy_adoption", Adoption) monkeypatch.setattr(module, "_registered_legacy_receipt_key", lambda: b"k" * 32) monkeypatch.setattr(module, "_registered_legacy_validation_reader", lambda _repository: lambda *_args: {}) monkeypatch.setattr(module, "sync_source", lambda *_args, **_kwargs: events.append("sync-source")) monkeypatch.setattr(module, "copy_environment", lambda *_args: None) monkeypatch.setattr(module, "_mcp_hub_assert_no_unresolved_migration_intent", lambda *_args: None) monkeypatch.setattr(module, "ensure_docker_capacity", lambda: None) monkeypatch.setattr(module, "deploy_project", lambda observed_project, _revision, _repository: ( events.append("deploy"), pytest.fail("follow-up deploy lost its derived internal request ID") if observed_project.get("_registered_legacy_request_id") != request_id else None, )) monkeypatch.setattr(module, "verify_deployment", lambda *_args: receipt) monkeypatch.setattr(module, "finish_compose_migration", lambda *_args: None) monkeypatch.setattr(module, "write_state", lambda *_args, **kwargs: writes.append(kwargs)) monkeypatch.setattr(module, "cleanup_rollback_image_tags", lambda *_args, **_kwargs: None) assert module.main() == 0 assert events.index("global-lock-enter") < events.index("active-lineage") assert events.index("prepare-followup") < events.index("sync-source") < events.index("deploy") assert "record-live" in events assert len(writes) == 1 assert writes[0]["live_legacy_provenance"] is False assert writes[0]["rollback_snapshot"] == known_snapshot assert writes[0]["expected_state_sha256"] == state_sha assert json.loads(capsys.readouterr().out)["status"] == "deployed" def test_main_known_current_rollback_restores_source_then_allows_retry( monkeypatch, tmp_path, capsys ): """A failed candidate returns to the verified prior revision and can retry.""" module = _load_deploy_module() previous_revision = "1" * 40 revision = "2" * 40 state_sha = "d" * 64 request_ids = [ "6f9619ff-8b86-4011-b42d-00c04fc964ff", "8c7b4a2d-3d70-4a49-93e4-41d42aeecc03", ] origin = { "revision": "unknown", "legacy_provenance": True, "registered_legacy": {"schema": "origin-unknown-snapshot"}, } state = { "schema_version": 3, "repository": REPOSITORY, "slug": "quiz-monitor", "compose_project": "quiz-monitor", "revision": previous_revision, "live_revision": previous_revision, "live_status": "healthy", "live_legacy_provenance": False, "live_artifacts": {"registered_legacy": {"requestId": request_ids[0]}}, "live_receipt": {"schema": "gitea-deploy-receipt.v1", "repository": REPOSITORY, "revision": previous_revision}, "rollback_snapshot": {"revision": "unknown", "legacy_provenance": True, "registered_legacy": {"schema": "origin-unknown-snapshot"}}, "status": "success", } project_template = { "slug": "quiz-monitor", "branch": "main", "source_dir": str(tmp_path / "source"), "compose_project": "quiz-monitor", "compose_files": ["docker-compose.yml"], "containers": [CONTAINER], "verify_oci_revision": True, "health_timeout": 180, } metadata = {"revision": revision, "tree": "a" * 40} known_snapshot = { "revision": previous_revision, "legacy_provenance": False, "receipt": {"schema": "gitea-deploy-receipt.v1", "repository": REPOSITORY, "revision": previous_revision}, "containers": [{"name": CONTAINER, "image_id": IMAGE_ID}], "registered_legacy": {"schema": "known-current-snapshot"}, "originUnknownSnapshot": origin, } restored_artifacts = { "registered_legacy": {"requestId": request_ids[0], "revision": previous_revision}, "containers": {CONTAINER: {"container_id": CONTAINER_ID, "image_id": IMAGE_ID}}, } restored_receipt = known_snapshot["receipt"] events = [] candidate_attempts = 0 writes = [] @contextmanager def project_lock(_path, _description): yield @contextmanager def global_lock(): events.append("global-lock-enter") try: yield finally: events.append("global-lock-exit") class Adoption: def active_lineage(self, repository, _project, observed_state, **_kwargs): assert repository == REPOSITORY assert observed_state["live_revision"] == previous_revision assert observed_state["live_status"] == "healthy" events.append(f"active-lineage:{observed_state['status']}") return { "requestId": request_ids[0], "revision": previous_revision, "originUnknownSnapshot": origin, } def prepare_followup_deployment(self, repository, _project, target, _state, **_kwargs): assert repository == REPOSITORY and target == revision request_id = request_ids[len([e for e in events if e == "prepare-followup"]) ] events.append("prepare-followup") return { "requestId": request_id, "authorization": { "requestId": request_id, "revision": target, "expectedStateSha256": state_sha, }, "rollbackSnapshot": known_snapshot, "originUnknownSnapshot": origin, "expectedStateSha256": state_sha, "validationEvidenceSha256": "f" * 64, } def restore_deployment_snapshot(self, repository, _project, snapshot, failed, **kwargs): assert repository == REPOSITORY and failed == revision assert snapshot == known_snapshot callback = kwargs.get("restore_source") assert callable(callback) events.append("rollback-core-accepted-snapshot") callback(previous_revision) events.append("prior-source-restored") return { "revision": previous_revision, "artifacts": restored_artifacts, "imageIDs": {CONTAINER: IMAGE_ID}, "liveReceipt": restored_receipt, } def verify_current_live_runtime(self, *_args, **_kwargs): pytest.fail("candidate failure must not claim the failed revision is healthy") def record_live_runtime(self, repository, project, request_id, target, _receipt, **_kwargs): assert repository == REPOSITORY and target == revision assert project["_registered_legacy_request_id"] == request_id events.append("record-live") return { "requestId": request_id, "revision": target, "runtimeReceiptSha256": "9" * 64, "runtimeFingerprint": "8" * 64, "originRequestId": request_ids[0], "originUnknownSnapshot": origin, } adoption = Adoption() candidate_receipt = { "schema": "gitea-deploy-receipt.v1", "repository": REPOSITORY, "revision": revision, "tree": metadata["tree"], } monkeypatch.setattr(sys, "argv", ["deploy.py", "deploy", REPOSITORY, revision]) monkeypatch.delenv("APPOPS_REGISTERED_LEGACY_REQUEST_ID", raising=False) monkeypatch.setattr(module, "load_projects", lambda: {REPOSITORY: dict(project_template)}) monkeypatch.setattr(module, "repository_path", lambda *_args: tmp_path) monkeypatch.setattr(module, "validate_commit", lambda _repo, _branch, value: value) monkeypatch.setattr(module, "deployment_metadata", lambda *_args: metadata) monkeypatch.setattr(module, "validate_project_config", lambda *_args: None) monkeypatch.setattr(module, "exclusive_lock", project_lock) monkeypatch.setattr(module, "exclusive_global_deploy_lock", global_lock) monkeypatch.setattr(module, "read_state", lambda _project: state) monkeypatch.setattr(module, "verified_current_receipt", lambda *_args: None) monkeypatch.setattr(module, "validate_forward_revision", lambda *_args, **_kwargs: None) monkeypatch.setattr(module, "_load_registered_legacy_adoption", lambda: adoption) monkeypatch.setattr(module, "_registered_legacy_receipt_key", lambda: b"k" * 32) monkeypatch.setattr(module, "_registered_legacy_validation_reader", lambda _repo: lambda *_args: {}) monkeypatch.setattr(module, "sync_source", lambda *_args, **_kwargs: events.append("sync-source")) monkeypatch.setattr(module, "copy_environment", lambda *_args: None) monkeypatch.setattr(module, "restore_live_source", lambda _project, _repo, rev: events.append(f"restore-source:{rev}")) monkeypatch.setattr(module, "mcp_hub_prepare_current_receipt_model", lambda *_args: None) monkeypatch.setattr(module, "_mcp_hub_assert_no_unresolved_migration_intent", lambda *_args: None) monkeypatch.setattr(module, "revalidate_deployment_candidate", lambda *_args: None) monkeypatch.setattr(module, "validate_managed_v1_runtime", lambda *_args: None) monkeypatch.setattr(module, "capture_compose_rollback", lambda *_args: pytest.fail("signed rollback snapshot must be reused")) monkeypatch.setattr(module, "ensure_docker_capacity", lambda: None) monkeypatch.setattr(module, "finish_compose_migration", lambda *_args: None) monkeypatch.setattr(module, "restore_compose_migration", lambda *_args: None) monkeypatch.setattr(module, "validate_recorded_receipt", lambda *_args: {"revision": previous_revision}) monkeypatch.setattr(module, "verify_deployment", lambda _project, meta, **_kwargs: ( restored_receipt if meta.get("revision") == previous_revision else candidate_receipt )) def deploy(_project, _revision, _repository): nonlocal candidate_attempts candidate_attempts += 1 events.append(f"deploy:{candidate_attempts}") if candidate_attempts == 1: raise module.DeployError("simulated candidate health failure") monkeypatch.setattr(module, "deploy_project", deploy) def write_state(_repository, project, target, status, message, **kwargs): writes.append((target, status, dict(kwargs))) state.update({ "repository": REPOSITORY, "status": status, "revision": kwargs.get("live_revision", state["live_revision"]), "live_revision": kwargs.get("live_revision", state["live_revision"]), "live_status": kwargs.get("live_status", state["live_status"]), "live_legacy_provenance": kwargs.get( "live_legacy_provenance", state["live_legacy_provenance"] ), "live_artifacts": kwargs.get("live_artifacts", state["live_artifacts"]), "live_receipt": kwargs.get("live_receipt", state["live_receipt"]), "rollback_snapshot": kwargs.get("rollback_snapshot", state["rollback_snapshot"]), "last_attempt": {"revision": target, "status": status, "message": message}, }) if status == "success": state["live_revision"] = revision state["revision"] = revision state["live_status"] = "healthy" state["live_legacy_provenance"] = False state["live_receipt"] = candidate_receipt state["live_artifacts"] = { "registered_legacy": dict(project["_registered_legacy_runtime_binding"]), "containers": restored_artifacts["containers"], } state["rollback_snapshot"] = known_snapshot monkeypatch.setattr(module, "write_state", write_state) monkeypatch.setattr(module, "cleanup_rollback_image_tags", lambda *_args, **_kwargs: None) with pytest.raises(module.DeployError, match="simulated candidate health failure"): module.main() assert "rollback-core-accepted-snapshot" in events assert events.index("rollback-core-accepted-snapshot") < events.index( f"restore-source:{previous_revision}" ) assert state["status"] == "failed" assert state["live_revision"] == previous_revision assert state["live_status"] == "healthy" assert state["live_legacy_provenance"] is False assert state["rollback_snapshot"]["revision"] == "unknown" assert module.main() == 0 assert candidate_attempts == 2 assert len([event for event in events if event == "prepare-followup"]) == 2 assert state["status"] == "success" assert state["live_revision"] == revision assert state["live_legacy_provenance"] is False assert state["live_artifacts"]["registered_legacy"]["originUnknownSnapshot"] == origin assert state["rollback_snapshot"] == known_snapshot assert any(event == "active-lineage:failed" for event in events) assert json.loads(capsys.readouterr().out)["status"] == "deployed" def test_unsigned_known_snapshot_cannot_mutate_source(monkeypatch, tmp_path): """A forged known-current rollback reference is rejected before source restore.""" deploy = _load_deploy_module() adoption = _load_module() project = { "slug": "quiz-monitor", "branch": "main", "source_dir": "/srv/example/appdata/quiz-monitor/source", "compose_project": "quiz-monitor", "compose_files": ["docker-compose.yml"], "containers": [CONTAINER], } previous_revision = "1" * 40 failed_revision = "2" * 40 request_id = "6f9619ff-8b86-4011-b42d-00c04fc964ff" source_changes = [] forged_snapshot = { "revision": previous_revision, "legacy_provenance": False, "registered_legacy": { "schema": adoption.KNOWN_SNAPSHOT_SCHEMA, "requestId": request_id, "snapshotSha256": "0" * 64, "targetRevision": failed_revision, "previousRevision": previous_revision, "originRequestId": request_id, "originUnknownSnapshot": {}, }, } monkeypatch.setattr(deploy, "STATE_ROOT", tmp_path) monkeypatch.setattr(deploy, "read_state", lambda _project: {}) monkeypatch.setattr(deploy, "_load_registered_legacy_adoption", lambda: adoption) monkeypatch.setattr(deploy, "_registered_legacy_receipt_key", lambda: b"k" * 32) monkeypatch.setattr( deploy, "restore_live_source", lambda *_args: source_changes.append("source-restored"), ) with pytest.raises(adoption.RegisteredLegacyAdoptionError): deploy.restore_compose_snapshot( project, forged_snapshot, REPOSITORY, failed_revision, ) assert source_changes == [] def test_candidate_pre_mutation_rechecks_latest_canonical_main_run( monkeypatch, tmp_path ): """A newly failed full run blocks a previously sealed candidate before Compose.""" support_path = ROOT / "backend" / "tests" / "test_registered_legacy_runtime.py" support_spec = importlib.util.spec_from_file_location( "registered_legacy_runtime_followup_review", support_path ) assert support_spec and support_spec.loader support = importlib.util.module_from_spec(support_spec) support_spec.loader.exec_module(support) module = support._load_module() source = tmp_path / "source" source.mkdir() (source / "docker-compose.yml").write_text("services: {}\n", encoding="utf-8") project = support._project(source) model = support._compose_model() boundary = support._DockerComposeBoundary(module, project, model) boundary.install(monkeypatch) runtime, _registry, _read_state, _state_bytes, _snapshot = ( support._adopt_with_production_runtime( module, boundary, project, tmp_path, monkeypatch ) ) bound_project = support._prepare_candidate_overlay( module, boundary, project, tmp_path ) module.prepare_candidate_deployment( REPOSITORY, bound_project, support.REQUEST_ID, support.TARGET_SHA, receipt_key=support.KEY, state_root=tmp_path, runtime=runtime, ) older_success = { "id": 7, "run_number": 19, "run_attempt": 1, "event": "push", "head_sha": support.TARGET_SHA, "head_branch": "main", "path": "managed-validation.yml@refs/heads/main", "created_at": "2026-10-07T12:00:00Z", "status": "completed", "conclusion": "success", "repository": {"full_name": REPOSITORY}, "head_repository": {"full_name": REPOSITORY}, "pull_requests": [], } newer_failure = { **older_success, "id": 8, "run_number": 20, "created_at": "2026-10-07T12:01:00Z", "status": "completed", "conclusion": "failure", } queried = [] def failed_latest_api(path, *, query): queried.append(path) if path == f"/repos/{REPOSITORY}/actions/runs": return { "total_count": 2, "workflow_runs": [older_success, newer_failure], } return support._validation_api(path, query=query) with pytest.raises(module.RegisteredLegacyAdoptionError): module.verify_candidate_pre_mutation( REPOSITORY, bound_project, support.REQUEST_ID, support.TARGET_SHA, receipt_key=support.KEY, state_root=tmp_path, runtime=runtime, api_json=failed_latest_api, ) assert queried == [f"/repos/{REPOSITORY}/branches/main", f"/repos/{REPOSITORY}/actions/runs"] assert boundary.compose_ups == 0 assert not any( command[:3] == ["docker", "compose", "--project-name"] and command[-1:] == ["--force-recreate"] for command in boundary.commands ) verified = module.verify_candidate_pre_mutation( REPOSITORY, bound_project, support.REQUEST_ID, support.TARGET_SHA, receipt_key=support.KEY, state_root=tmp_path, runtime=runtime, api_json=support._validation_api, ) assert verified["status"] == "pre-mutation-ok" assert verified["validationEvidenceSha256"] assert boundary.compose_ups == 0