diff --git a/docs/site/src/content/docs/changelog.mdx b/docs/site/src/content/docs/changelog.mdx index a0bd47f45..b38d3c505 100644 --- a/docs/site/src/content/docs/changelog.mdx +++ b/docs/site/src/content/docs/changelog.mdx @@ -34,8 +34,6 @@ Documentation updates for the v0.21.0 beta-32 release: reviewed release-image definitions under `release/docker/`. - Documented operator-mounted configuration, governed packages, secrets, keys, and audit storage for all three runtime images. -- Documented that the Relay installer verifies and installs matching `relay` - and `relayctl` binaries together on Linux amd64. - Added the v0.21.0 candidate documentation archive. ## 2026-08-12 diff --git a/release/manifests/registry-stack-beta-32.yaml b/release/manifests/registry-stack-beta-32.yaml index c4a702a83..a6db6e4fe 100644 --- a/release/manifests/registry-stack-beta-32.yaml +++ b/release/manifests/registry-stack-beta-32.yaml @@ -22,7 +22,7 @@ artifacts: identifier_catalog: path: products/identifiers/generated/catalog.v1.json - sha256: 7a8130406862922a1c52f81a43577446713ab10ffe76b49179946ad861af3ac9 + sha256: f5523ec180e3c3d18f7f18a1704d3932e84a66f03ead0e9124d3c6d366aa20f0 entry_count: 47 external: {} diff --git a/release/notes/v0.21.0.md b/release/notes/v0.21.0.md index 5596f6bc8..cdb5cbf00 100644 --- a/release/notes/v0.21.0.md +++ b/release/notes/v0.21.0.md @@ -28,9 +28,6 @@ Python, and Node. - Relay reads the operator-mounted runtime configuration selected by its `--runtime` argument, which defaults in the image to `/etc/relay/runtime.yaml`. -- The Relay installer downloads, verifies, and installs the matching `relay` - and `relayctl` binaries together on Linux amd64. If either asset cannot be - verified or installed, the installer preserves the previous pair. - The repository's top-level `docker/` definitions remain local source-build images. Official release images use the reviewed definitions under `release/docker/`. diff --git a/release/scripts/registry-release b/release/scripts/registry-release index 3eb4e3f30..b3dfe08c6 100755 --- a/release/scripts/registry-release +++ b/release/scripts/registry-release @@ -270,6 +270,20 @@ def sha256(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() +def local_tag_target(repo: Path, tag: str) -> str | None: + resolved = subprocess.run( + ["git", "rev-parse", "--verify", f"refs/tags/{tag}^{{}}"], + cwd=repo, + text=True, + capture_output=True, + check=False, + ) + target = resolved.stdout.strip() + if resolved.returncode == 0 and HEX40.fullmatch(target): + return target + return None + + def validate(manifest_path: Path) -> int: manifest = load_yaml(manifest_path) errors: list[str] = [] @@ -318,12 +332,15 @@ def validate(manifest_path: Path) -> int: if repository.returncode == 0 and repository.stdout.strip() else ROOT ) + catalog_source_ref = source_ref if has_source_ref else None + if catalog_source_ref is None: + catalog_source_ref = local_tag_target(repo_root, source_tag) errors.extend( identifier_catalog_errors( version, identifier_catalog, repo_root, - source_ref if has_source_ref else None, + catalog_source_ref, ) ) for name, artifact_version in sorted(artifacts.items()): diff --git a/release/scripts/test_registry_release.py b/release/scripts/test_registry_release.py index feffb8b12..01038ee25 100755 --- a/release/scripts/test_registry_release.py +++ b/release/scripts/test_registry_release.py @@ -1411,8 +1411,8 @@ def test_validate_current_selects_highest_semver_manifest(self) -> None: manifest_dir.mkdir() source_ref = git(ROOT, "rev-parse", "HEAD") for version, release_id in ( - ("0.19.0", "beta-29"), - ("0.20.0", "beta-30"), + ("0.91.0", "beta-29"), + ("0.92.0", "beta-30"), ): manifest = write_manifest( manifest_dir, @@ -1435,7 +1435,7 @@ def test_validate_current_selects_highest_semver_manifest(self) -> None: ) self.assertEqual(0, result.returncode, result.stderr) - self.assertIn("beta-30 0.20.0", result.stdout) + self.assertIn("beta-30 0.92.0", result.stdout) self.assertNotIn("beta-29", result.stdout) def test_validate_current_requires_a_release_manifest(self) -> None: @@ -1625,6 +1625,73 @@ def test_validate_uses_identifier_catalog_from_recorded_source_ref(self) -> None self.assertNotEqual(0, mismatched.returncode) self.assertIn("does not match the committed catalog bytes", mismatched.stderr) + def test_validate_uses_identifier_catalog_from_existing_source_tag(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = init_repo(Path(tmp)) + catalog_path = root / "products/identifiers/generated/catalog.v1.json" + catalog_path.parent.mkdir(parents=True) + catalog_path.write_text( + json.dumps({"version": 1, "entries": [{"status": "active"}]}) + + "\n", + encoding="utf-8", + ) + git(root, "add", str(catalog_path.relative_to(root))) + git(root, "commit", "-m", "record tagged catalog") + tagged_source = git(root, "rev-parse", "HEAD") + git(root, "tag", "v0.19.1") + manifest = write_manifest( + root, + version="0.19.1", + source_ref=tagged_source, + ) + data = yaml.safe_load(manifest.read_text(encoding="utf-8")) + del data["stack"]["source_ref"] + del data["stack"]["status"] + manifest.write_text( + yaml.safe_dump(data, sort_keys=False), encoding="utf-8" + ) + + catalog_path.write_text( + json.dumps( + { + "version": 1, + "entries": [ + {"status": "active"}, + {"status": "active"}, + ], + } + ) + + "\n", + encoding="utf-8", + ) + git(root, "add", str(catalog_path.relative_to(root))) + git(root, "commit", "-m", "advance current catalog") + accepted = run_tool("validate", str(manifest)) + + data = yaml.safe_load(manifest.read_text(encoding="utf-8")) + data["identifier_catalog"]["sha256"] = hashlib.sha256( + catalog_path.read_bytes() + ).hexdigest() + data["identifier_catalog"]["entry_count"] = 2 + manifest.write_text( + yaml.safe_dump(data, sort_keys=False), encoding="utf-8" + ) + mismatched = run_tool("validate", str(manifest)) + + self.assertEqual(0, accepted.returncode, accepted.stderr) + self.assertNotEqual(0, mismatched.returncode) + self.assertIn("does not match the committed catalog bytes", mismatched.stderr) + + def test_validate_copied_manifest_uses_tool_repository_source_tag(self) -> None: + source = ROOT / "release/manifests/registry-stack-beta-32.yaml" + with tempfile.TemporaryDirectory() as tmp: + copied = Path(tmp) / source.name + copied.write_bytes(source.read_bytes()) + result = run_tool("validate", str(copied)) + + self.assertEqual(0, result.returncode, result.stderr) + self.assertIn("validated", result.stdout) + def test_relay_installer_joins_the_exact_inventory_after_v0_19_0(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp)