diff --git a/backend/tests/test_delivery.py b/backend/tests/test_delivery.py index 2141b16..4f779e3 100644 --- a/backend/tests/test_delivery.py +++ b/backend/tests/test_delivery.py @@ -148,6 +148,32 @@ def test_exclusive_file_does_not_repair_existing_mode(tmp_path, monkeypatch): assert stat.S_IMODE(path.stat().st_mode) == 0o600 +@pytest.mark.parametrize( + "changed", ["release_id", "configuration_id", "manifest_sha256", "selection"] +) +def test_current_receipt_rejects_changed_binding_after_full_validation(monkeypatch, changed): + selected = { + "release_id": "sd-aaaaaaaaaaaa", + "configuration_id": "cfg-" + "a" * 24, + "manifest_sha256": "a" * 64, + "receipt_sha256": "b" * 64, + } + installed = dict(selected) + after = dict(selected) + if changed == "selection": + after["receipt_sha256"] = "c" * 64 + else: + installed[changed] = "different verified binding" + observations = iter([selected, after]) + monkeypatch.setattr(service_install.install, "selection", lambda **options: next(observations)) + monkeypatch.setattr( + service_install.configuration, "installed_receipt_path", lambda *args: Path("/fixture") + ) + monkeypatch.setattr(service_install.configuration, "check_installed", lambda path: installed) + with pytest.raises(policy.DeliveryError, match="selected configuration changed"): + service_install.current_receipt() + + def test_new_copy_hashes_copied_bytes_and_reads_destination_before_acceptance( tmp_path, monkeypatch ): diff --git a/scripts/release/dependency_install.py b/scripts/release/dependency_install.py index 1100520..cb15864 100644 --- a/scripts/release/dependency_install.py +++ b/scripts/release/dependency_install.py @@ -448,7 +448,7 @@ def install_snapshot(source: Path, expected: str, cookie: Path, cookie_sha: str) write_group_file(directory / name, contents[name], mode, group) install.exclusive_file(directory / "receipt.json", canonical(receipt), 0o600) check_group(directory, receipt) - if install.selection() != selected: + if install.selection(check_configuration=False) != selected: raise DeliveryError("selected binding changed during dependency installation") require_stopped() for name in sorted(NAMES): diff --git a/scripts/release/service_install.py b/scripts/release/service_install.py index 6f19f6f..276619e 100644 --- a/scripts/release/service_install.py +++ b/scripts/release/service_install.py @@ -75,11 +75,16 @@ def previous_batch(target: str, name: str) -> Path: def current_receipt() -> tuple[dict, dict]: - selected = install.selection() + selected = install.selection(check_configuration=False) path = configuration.installed_receipt_path( selected["release_id"], selected["configuration_id"] ) installed = configuration.check_installed(path) + if install.selection(check_configuration=False) != selected or any( + installed[key] != selected[key] + for key in ("release_id", "configuration_id", "manifest_sha256") + ): + raise DeliveryError("selected configuration changed during complete receipt validation") hashes = { name: installed["configuration_files"][ str(