diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 9d8a2c376..c5348271f 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -5019,7 +5019,14 @@ back to a marker-free draft. `scripts/verify-github-release-integrity.sh` is the shared post-publication check. It binds the release database ID, tag, exact source SHA, immutable state, -and single digest-bearing activation marker, then requires `gh release verify` +and single digest-bearing activation marker. Uniqueness counts every asset named +`release-activation.json`, including pending, empty, or malformed entries, +before validating the marker's metadata. Duplicate names must fail before +attestation or download; filtering out invalid entries must not make an +ambiguous inventory acceptable. The regression cases in +`scripts/release_control/verify_github_release_integrity_test.py` cover both +valid duplicates and a valid marker accompanied by a malformed duplicate. +The check then requires `gh release verify` to validate GitHub's signed release attestation. It must then download the activation marker from that release and require `gh release verify-asset` to bind the exact consumed bytes to the signed release attestation. Filename, diff --git a/scripts/release_control/verify_github_release_integrity_test.py b/scripts/release_control/verify_github_release_integrity_test.py index c6a485d96..61b70fd1a 100644 --- a/scripts/release_control/verify_github_release_integrity_test.py +++ b/scripts/release_control/verify_github_release_integrity_test.py @@ -230,6 +230,22 @@ class VerifyGitHubReleaseIntegrityTest(unittest.TestCase): self.assertNotEqual(result.returncode, 0) self.assertIn("activation marker", result.stderr) + def test_rejects_duplicate_activation_names_before_attestation(self) -> None: + for duplicate in ( + {"state": "uploaded", "size": 300, "digest": "sha256:" + "b" * 64}, + {"state": "new", "size": 0, "digest": None}, + ): + with self.subTest(duplicate=duplicate): + release = self.release() + release["assets"].append( + {"name": "release-activation.json", **duplicate} + ) + result, calls = self.run_verifier(release) + self.assertNotEqual(result.returncode, 0) + self.assertIn("activation marker", result.stderr) + self.assertNotIn("release verify", calls) + self.assertNotIn("release download", calls) + def test_rejects_failed_release_attestation(self) -> None: result, _ = self.run_verifier(self.release(), verification_succeeds=False) self.assertNotEqual(result.returncode, 0) diff --git a/scripts/verify-github-release-integrity.sh b/scripts/verify-github-release-integrity.sh index 75cf13acb..6fa4d3138 100755 --- a/scripts/verify-github-release-integrity.sh +++ b/scripts/verify-github-release-integrity.sh @@ -97,6 +97,7 @@ if ! jq -e \ .immutable == true and ($expected_release_id == "" or (.id | tostring) == $expected_release_id) and ($expected_source_sha == "" or .target_commitish == $expected_source_sha) and + ([.assets[]? | select(.name == "release-activation.json")] | length == 1) and ([.assets[]? | select( .name == "release-activation.json" and .state == "uploaded" and