diff --git a/tools/release-management/src/release_management/prepare/category_x.py b/tools/release-management/src/release_management/prepare/category_x.py
index 7ca445a89..8325c201c 100755
--- a/tools/release-management/src/release_management/prepare/category_x.py
+++ b/tools/release-management/src/release_management/prepare/category_x.py
@@ -47,7 +47,9 @@
def pattern_for(term: str) -> re.Pattern[str]:
pieces = re.split(SEPARATORS, term)
body = SEPARATORS.join(re.escape(p) for p in pieces)
- return re.compile(rf"(? list[str]:
diff --git a/tools/release-management/tests/test_prepare.py b/tools/release-management/tests/test_prepare.py
index 1e6012277..b9cf4fbc1 100644
--- a/tools/release-management/tests/test_prepare.py
+++ b/tools/release-management/tests/test_prepare.py
@@ -427,6 +427,27 @@ def test_category_x_cli_names_the_hard_stop_file(tmp_path: Path, capsys: pytest.
assert out["read"] == ["classifications/category-x-hit.md"]
+@pytest.mark.parametrize("deny", ["org.example:blocked-lib", "blocked-lib"])
+def test_category_x_cli_reports_versioned_coordinate(
+ tmp_path: Path, capsys: pytest.CaptureFixture[str], deny: str
+) -> None:
+ manifest = tmp_path / "build.gradle.kts"
+ manifest.write_text('\nimplementation("org.example:blocked-lib:1.2.3")\n' * 2, encoding="utf-8")
+ shown = "app/build.gradle.kts"
+ assert main(["prepare", "category-x", "--deny", deny, f"{shown}={manifest}"]) == 0
+ out = json.loads(capsys.readouterr().out)
+ if deny == "blocked-lib":
+ assert out == {"category_x_hit": False, "category_x_violations": [], "files_scanned": [shown]}
+ else:
+ assert out == {
+ "category_x_hit": True,
+ "category_x_violations": [{"identifier": deny, "found_in": shown}],
+ "handoff_reason": category_x.HANDOFF,
+ "first_match_lines": [{"identifier": deny, "found_in": shown, "line": 2}],
+ "read": ["classifications/category-x-hit.md"],
+ }
+
+
def test_versions_per_manifest(tmp_path: Path) -> None:
files = {
"pyproject.toml": '[project]\nname = "foo"\nversion = "1.0.0.dev0"\n',
diff --git a/tools/release-management/tests/test_prepare_scripts.py b/tools/release-management/tests/test_prepare_scripts.py
index fdaf6877d..79286cef2 100644
--- a/tools/release-management/tests/test_prepare_scripts.py
+++ b/tools/release-management/tests/test_prepare_scripts.py
@@ -101,6 +101,72 @@ def test_no_partial_token_match(self) -> None:
self.assertFalse(out["category_x_hit"])
self.assertEqual(out["files_scanned"], ["pyproject.toml"])
+ def test_versioned_coordinates_match(self) -> None:
+ identifier = "org.example:blocked-lib"
+ manifests = {
+ "build.gradle": "dependencies {\n implementation 'org.example:blocked-lib:1.2.3'\n}\n",
+ "build.gradle.kts": 'dependencies {\n implementation("org.example:blocked-lib:1.2.3")\n}\n',
+ "gradle.lockfile": "# dependency locks\norg.example:blocked-lib:1.2.3=compileClasspath\n",
+ "dependency-tree.txt": "[INFO] dependency tree\n[INFO] +- org.example:blocked-lib:jar:1.2.3:compile\n",
+ }
+ for shown, text in manifests.items():
+ with self.subTest(manifest=shown):
+ out = category_x.scan([identifier], [(shown, self.file(text))])
+ self.assertEqual(
+ out,
+ {
+ "category_x_hit": True,
+ "category_x_violations": [{"identifier": identifier, "found_in": shown}],
+ "handoff_reason": category_x.HANDOFF,
+ "first_match_lines": [{"identifier": identifier, "found_in": shown, "line": 2}],
+ },
+ )
+
+ def test_versioned_coordinate_normalisation(self) -> None:
+ identifier = "ORG.EXAMPLE:BLOCKED-LIB"
+ local = self.file('implementation("org_example:blocked.lib:1.2.3")\n')
+ out = category_x.scan([identifier], [("build.gradle.kts", local)])
+ self.assertTrue(out["category_x_hit"])
+ self.assertEqual(
+ out["first_match_lines"], [{"identifier": identifier, "found_in": "build.gradle.kts", "line": 1}]
+ )
+
+ def test_coordinate_manifest_controls(self) -> None:
+ manifests = {
+ "pom.xml": "org.exampleblocked-lib\n",
+ "libs.versions.toml": 'blocked = { module = "org.example:blocked-lib", version = "1.2.3" }\n',
+ "build.gradle": "implementation group: 'org.example', name: 'blocked-lib', version: '1.2.3'\n",
+ "setup.cfg": "install_requires = blocked-lib>=1.2.3\n",
+ }
+ for shown, text in manifests.items():
+ with self.subTest(manifest=shown):
+ out = category_x.scan(["org.example:blocked-lib"], [(shown, self.file(text))])
+ self.assertTrue(out["category_x_hit"])
+
+ def test_versioned_coordinates_respect_token_boundaries(self) -> None:
+ coordinates = (
+ "other.example:blocked-lib:1.2.3",
+ "prefixorg.example:blocked-lib:1.2.3",
+ "org.example:prefixblocked-lib:1.2.3",
+ "org.example:blocked-lib-extras:1.2.3",
+ "org.example:blocked-lib.extra:1.2.3",
+ "org.example:blocked-lib0:1.2.3",
+ "path/org.example:blocked-lib:1.2.3",
+ "another:org.example:blocked-lib:1.2.3",
+ )
+ for coordinate in coordinates:
+ with self.subTest(coordinate=coordinate):
+ local = self.file(f'implementation("{coordinate}")\n')
+ self.assertFalse(
+ category_x.scan(["org.example:blocked-lib"], [("build.gradle.kts", local)])[
+ "category_x_hit"
+ ]
+ )
+
+ def test_bare_artifact_does_not_match_inside_a_coordinate(self) -> None:
+ local = self.file('implementation("org.example:blocked-lib:1.2.3")\n')
+ self.assertFalse(category_x.scan(["blocked-lib"], [("build.gradle.kts", local)])["category_x_hit"])
+
class NextDevVersionTest(unittest.TestCase):
CONFIGURED: ClassVar[list[str]] = [