diff --git a/README.md b/README.md index d4c7b49..6cdc5a4 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,10 @@ cache policy, content type, and bytes. That content address makes concurrent writes identical even though Garage v2.2.0 has no conditional destination write. An identical retry converges; a changed object, missing digest metadata, wrong address, or nested policy under that immutable prefix fails publication. +Every immutable target is validated and published before mutable objects change. +Mutable default and override partitions receive their final cache policy before +the matching prefix-scoped stale deletion, so publication never exposes a +provisional cache policy or a pointer to a missing immutable target. Artifact input directories must be pairwise disjoint after filesystem resolution. Publication stops before build or upload if one contains another or diff --git a/tests/test_contract.py b/tests/test_contract.py index ee45428..e9be6e9 100644 --- a/tests/test_contract.py +++ b/tests/test_contract.py @@ -417,6 +417,29 @@ class PublishingTests(unittest.TestCase): immutable_publish.call_args.args[2]["cache_control"], ) + def test_immutable_failure_stops_before_mutable_publication(self): + cfg = normalize_site_config(fixture("split-site.yaml"), "baseline.fritzlab.net") + artifact = next(item for item in cfg["artifacts"] if item["name"] == "distributions") + route = next(item for item in cfg["routes"] if item["artifact"] == "distributions") + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + html = root / artifact["build_dir"] + (html / "releases").mkdir(parents=True) + (html / "channels").mkdir() + (html / "releases" / "1.0.js").write_text("release") + (html / "channels" / "stable.json").write_text("channel") + + with patch.dict(os.environ, { + "DIST_S3_ACCESS_KEY": "dist-key", "DIST_S3_SECRET_KEY": "dist-secret" + }, clear=False), patch.object( + deploy, "publish_immutable_rule", side_effect=RuntimeError("immutable failed") + ), patch.object(deploy, "run") as mutable_run, self.assertRaisesRegex( + RuntimeError, "immutable failed" + ): + deploy.s3_sync(artifact, route, root) + + mutable_run.assert_not_called() + def test_absent_artifact_is_detected_before_publish(self): cfg = normalize_site_config(fixture("split-site.yaml"), "baseline.fritzlab.net")