From 5f60dd9c3b7c6a8fdd9b72747ea9528af7ce40b1 Mon Sep 17 00:00:00 2001 From: Jakub Baranowski Date: Thu, 27 Aug 2026 08:33:03 +0200 Subject: [PATCH 1/4] fix(extensions): bump drifted bundled extension versions (agent-context, git 1.1.0; assess 1.0.1) The bundled agent-context and git extensions have carried version 1.0.0 since they were created while their content kept changing - including fixes for failures that made them unusable on some platforms (agent-context: 15 commits, +1,120/-169 across 7 of its 8 files; git: 23 commits, +2,191/-567 across all 21 files). Because `specify extension update` compares semver only, every installed copy is reported "Up to date (v1.0.0)" forever and never receives those fixes (#4345). Bump both manifests to 1.1.0 and sync extensions/catalog.json so existing installs finally see an available update. assess also drifted (one docs-only change to a shipped command file since its version was set), so it gets a patch bump to 1.0.1; bug has no drift and stays at 1.0.0. Refs #4345 Assisted-by: Claude Code (model: claude-fable-5, autonomous) Co-Authored-By: Claude Fable 5 --- extensions/agent-context/extension.yml | 2 +- extensions/assess/extension.yml | 2 +- extensions/catalog.json | 8 ++++---- extensions/git/extension.yml | 2 +- tests/extensions/git/test_git_extension.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/extensions/agent-context/extension.yml b/extensions/agent-context/extension.yml index 191069e32c..2846b4d9a6 100644 --- a/extensions/agent-context/extension.yml +++ b/extensions/agent-context/extension.yml @@ -3,7 +3,7 @@ schema_version: "1.0" extension: id: agent-context name: "Coding Agent Context" - version: "1.0.0" + version: "1.1.0" description: "Manages coding agent context/instruction files (e.g., CLAUDE.md, copilot-instructions.md) with project-specific plan references and configurable markers" author: spec-kit-core repository: https://github.com/github/spec-kit diff --git a/extensions/assess/extension.yml b/extensions/assess/extension.yml index 9161b268fb..42b281b8ee 100644 --- a/extensions/assess/extension.yml +++ b/extensions/assess/extension.yml @@ -3,7 +3,7 @@ schema_version: "1.0" extension: id: assess name: "Idea Assessment Pipeline" - version: "1.0.0" + version: "1.0.1" description: "Assess an idea before Spec-Driven Development via intake, research, define, shape, and decide. A go verdict hands off to /speckit.specify; a kill closes it. Lives under .specify/assessments//" category: "process" effect: "read-write" diff --git a/extensions/catalog.json b/extensions/catalog.json index d05c48e0e5..af0aae7701 100644 --- a/extensions/catalog.json +++ b/extensions/catalog.json @@ -1,12 +1,12 @@ { "schema_version": "1.0", - "updated_at": "2026-07-17T00:00:00Z", + "updated_at": "2026-08-27T00:00:00Z", "catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json", "extensions": { "agent-context": { "name": "Coding Agent Context", "id": "agent-context", - "version": "1.0.0", + "version": "1.1.0", "description": "Manages coding agent context/instruction files (e.g., CLAUDE.md, copilot-instructions.md) with project-specific plan references and configurable markers", "author": "spec-kit-core", "repository": "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/github/spec-kit", @@ -20,7 +20,7 @@ "assess": { "name": "Idea Assessment Pipeline", "id": "assess", - "version": "1.0.0", + "version": "1.0.1", "description": "Assess an idea before Spec-Driven Development via intake, research, define, shape, and decide. A go verdict hands off to /speckit.specify; a kill closes it. Lives under .specify/assessments//", "author": "spec-kit-core", "repository": "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/github/spec-kit", @@ -51,7 +51,7 @@ "git": { "name": "Git Branching Workflow", "id": "git", - "version": "1.0.0", + "version": "1.1.0", "description": "Feature branch creation, numbering (sequential/timestamp), validation, and Git remote detection", "author": "spec-kit-core", "repository": "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/github/spec-kit", diff --git a/extensions/git/extension.yml b/extensions/git/extension.yml index c92322d8b1..84e2dc35a5 100644 --- a/extensions/git/extension.yml +++ b/extensions/git/extension.yml @@ -3,7 +3,7 @@ schema_version: "1.0" extension: id: git name: "Git Branching Workflow" - version: "1.0.0" + version: "1.1.0" description: "Feature branch creation, numbering (sequential/timestamp), templating, validation, and Git remote detection" author: spec-kit-core repository: https://github.com/github/spec-kit diff --git a/tests/extensions/git/test_git_extension.py b/tests/extensions/git/test_git_extension.py index f6be51caf6..5bc25b9332 100644 --- a/tests/extensions/git/test_git_extension.py +++ b/tests/extensions/git/test_git_extension.py @@ -145,7 +145,7 @@ def test_manifest_validates(self): m = ExtensionManifest(EXT_DIR / "extension.yml") assert m.id == "git" - assert m.version == "1.0.0" + assert m.version == "1.1.0" def test_manifest_commands(self): """Manifest declares expected commands.""" From 874623d1614b0e745ecfc52d8c4f0fd335394599 Mon Sep 17 00:00:00 2001 From: Jakub Baranowski Date: Thu, 27 Aug 2026 10:42:27 +0200 Subject: [PATCH 2/4] fix(bundles): move agent-context pins to 1.1.0 and stop hardcoding them in fixtures Copilot review round 3 on #4351: the agent-context bump left every checked-in bundle pinned to 1.0.0. BundleExtensionPrimitive enforces exact pins against the bundled manifest, so the offline installs in tests/integration/test_bundler_local_install.py and test_bundler_init_install.py failed, and all four examples/bundles/*/bundle.yml examples stopped being installable. - examples/bundles/{business-analyst,developer,product-manager, security-researcher}/bundle.yml: agent-context pin 1.0.0 -> 1.1.0 (exact pins are the point of the example format, so they stay literal). - The two integration-test fixtures now resolve the pin through a new tests/bundler_helpers.bundled_extension_version() helper, which reads the version via the same _locate_bundled_extension lookup the primitive enforces against - so the fixtures test the bundler's pin mechanics rather than a frozen version literal, and the next legitimate extension bump cannot silently break them again. The git and assess extensions are not pinned by any checked-in bundle; tests/contract/test_bundle_cli.py's 1.0.0 pin feeds `bundle validate`, which checks existence only, and keeps passing unchanged. Refs #4345 Assisted-by: Claude Code (model: claude-fable-5, autonomous) Co-Authored-By: Claude Fable 5 --- examples/bundles/business-analyst/bundle.yml | 2 +- examples/bundles/developer/bundle.yml | 2 +- examples/bundles/product-manager/bundle.yml | 2 +- .../bundles/security-researcher/bundle.yml | 2 +- tests/bundler_helpers.py | 18 ++++++++++++++++++ tests/integration/test_bundler_init_install.py | 11 +++++++++-- .../integration/test_bundler_local_install.py | 14 ++++++++++++-- 7 files changed, 43 insertions(+), 8 deletions(-) diff --git a/examples/bundles/business-analyst/bundle.yml b/examples/bundles/business-analyst/bundle.yml index b03875a22e..90d35ce87d 100644 --- a/examples/bundles/business-analyst/bundle.yml +++ b/examples/bundles/business-analyst/bundle.yml @@ -17,7 +17,7 @@ requires: provides: extensions: - id: "agent-context" - version: "1.0.0" + version: "1.1.0" presets: - id: "requirements-elicitation" version: "1.0.0" diff --git a/examples/bundles/developer/bundle.yml b/examples/bundles/developer/bundle.yml index 3a365534e5..3f4dce5465 100644 --- a/examples/bundles/developer/bundle.yml +++ b/examples/bundles/developer/bundle.yml @@ -17,7 +17,7 @@ requires: provides: extensions: - id: "agent-context" - version: "1.0.0" + version: "1.1.0" presets: - id: "implementation-planning" version: "1.0.0" diff --git a/examples/bundles/product-manager/bundle.yml b/examples/bundles/product-manager/bundle.yml index 9abba40bd4..c5f96ab186 100644 --- a/examples/bundles/product-manager/bundle.yml +++ b/examples/bundles/product-manager/bundle.yml @@ -19,7 +19,7 @@ requires: provides: extensions: - id: "agent-context" - version: "1.0.0" + version: "1.1.0" presets: - id: "product-discovery" version: "1.0.0" diff --git a/examples/bundles/security-researcher/bundle.yml b/examples/bundles/security-researcher/bundle.yml index d0b289e872..e017071e94 100644 --- a/examples/bundles/security-researcher/bundle.yml +++ b/examples/bundles/security-researcher/bundle.yml @@ -17,7 +17,7 @@ requires: provides: extensions: - id: "agent-context" - version: "1.0.0" + version: "1.1.0" presets: - id: "security-compliance" version: "1.0.0" diff --git a/tests/bundler_helpers.py b/tests/bundler_helpers.py index 0ebaf2f1c7..2df11e03df 100644 --- a/tests/bundler_helpers.py +++ b/tests/bundler_helpers.py @@ -43,6 +43,24 @@ def valid_manifest_dict(**overrides) -> dict: return data +def bundled_extension_version(extension_id: str) -> str: + """Version declared by the bundled extension the primitives will install. + + Resolved through the same lookup ``BundleExtensionPrimitive`` uses, so + fixtures that pin a real bundled extension stay valid across legitimate + extension version bumps (#4345) instead of hardcoding a literal that + drifts out of sync and trips the exact-pin enforcement. + """ + from specify_cli._assets import _locate_bundled_extension + + bundled_dir = _locate_bundled_extension(extension_id) + assert bundled_dir is not None, f"bundled extension '{extension_id}' not found" + manifest = yaml.safe_load( + (bundled_dir / "extension.yml").read_text(encoding="utf-8") + ) + return manifest["extension"]["version"] + + def write_manifest(directory: Path, data: dict | None = None) -> Path: directory.mkdir(parents=True, exist_ok=True) manifest_path = directory / "bundle.yml" diff --git a/tests/integration/test_bundler_init_install.py b/tests/integration/test_bundler_init_install.py index a13def5ff8..291c67871f 100644 --- a/tests/integration/test_bundler_init_install.py +++ b/tests/integration/test_bundler_init_install.py @@ -18,7 +18,7 @@ from specify_cli.bundler.models.manifest import BundleManifest from specify_cli.commands.bundle import _resolve_init_integration from specify_cli.bundler.services.packager import build_bundle -from tests.bundler_helpers import valid_manifest_dict +from tests.bundler_helpers import bundled_extension_version, valid_manifest_dict runner = CliRunner() @@ -75,7 +75,14 @@ def _build_mini(tmp_path: Path) -> Path: "license": "MIT", }, "requires": {"speckit_version": ">=0.1.0"}, - "provides": {"extensions": [{"id": "agent-context", "version": "1.0.0"}]}, + "provides": { + "extensions": [ + { + "id": "agent-context", + "version": bundled_extension_version("agent-context"), + } + ] + }, } ), encoding="utf-8", diff --git a/tests/integration/test_bundler_local_install.py b/tests/integration/test_bundler_local_install.py index 630c981a73..e9229e5c4d 100644 --- a/tests/integration/test_bundler_local_install.py +++ b/tests/integration/test_bundler_local_install.py @@ -18,7 +18,12 @@ from specify_cli import app from specify_cli.bundler import BundlerError from specify_cli.commands.bundle import _local_manifest_source -from tests.bundler_helpers import make_project, valid_manifest_dict, write_manifest +from tests.bundler_helpers import ( + bundled_extension_version, + make_project, + valid_manifest_dict, + write_manifest, +) def test_local_source_none_for_non_path(): @@ -116,7 +121,12 @@ def test_install_bundled_extension_from_zip_offline(tmp_path: Path): }, "requires": {"speckit_version": ">=0.1.0"}, "provides": { - "extensions": [{"id": "agent-context", "version": "1.0.0"}] + "extensions": [ + { + "id": "agent-context", + "version": bundled_extension_version("agent-context"), + } + ] }, } ), From a21a34f7f1a333f8fd776c0bcfdab69a7da69808 Mon Sep 17 00:00:00 2001 From: CrazyBaran Date: Sat, 19 Sep 2026 11:48:02 +0200 Subject: [PATCH 3/4] test(extensions): pin real-data update delivery and example bundle pins Address Copilot review on #4394. The version bumps are only useful if a copy installed before them is actually offered an update, and until now nothing proved that with the real data: unit tests drive the local-package update route with synthetic extensions, and the version contract only checks catalog/manifest equality. Add tests/contract/test_bundled_extension_update_delivery.py, which installs each bundled extension's real source with its manifest rewritten to the pre-bump 1.0.0, runs `extension update` against the real catalog entry and the real bundled copy (download must never be attempted), and asserts the install reaches the catalog version. The three extensions that had drifted at 1.0.0 when #4345 was filed (agent-context, git, assess) must stay bumped past it - reverting one is a failure, not a skip - while an unbumped extension (bug) skips. A companion test asserts an install already at the bundled version is reported up to date, so fresh installs cannot loop. Fixtures now derive their pins from the bundled manifest, so the four changed examples/bundles/*/bundle.yml pins were no longer exercised by any test even though BundleExtensionPrimitive enforces exact pins. Add a contract test that parses every example bundle and checks each bundled extension pin against its manifest version; it fails when an example is left on a previous version. Refs #4345 Co-Authored-By: Claude Fable 5.1 --- .../test_bundled_extension_update_delivery.py | 145 ++++++++++++++++++ .../test_bundled_extension_versions.py | 37 +++++ 2 files changed, 182 insertions(+) create mode 100644 tests/contract/test_bundled_extension_update_delivery.py diff --git a/tests/contract/test_bundled_extension_update_delivery.py b/tests/contract/test_bundled_extension_update_delivery.py new file mode 100644 index 0000000000..b6258b9c4a --- /dev/null +++ b/tests/contract/test_bundled_extension_update_delivery.py @@ -0,0 +1,145 @@ +"""Contract tests: previously stale bundled installs are now offered an update. + +Every bundled extension shipped at 1.0.0 from its creation while its content +kept changing, so installed copies were reported "Up to date" forever +(#4345). The version bumps in extensions/*/extension.yml and the synced +extensions/catalog.json are what finally make `specify extension update` +offer those installs a newer version, and the local-package route installs +it from the copy bundled with the running spec-kit. + +Unit tests cover that route with synthetic extensions and versions, and the +version contract only checks catalog/manifest equality. These tests close +the gap with the real data: install each bundled extension's real source +at the pre-bump 1.0.0, run `extension update` against the real catalog +entry and the real bundled copy, and assert it reaches the catalog version. +""" + +from __future__ import annotations + +import json +import shutil +from pathlib import Path +from unittest.mock import patch + +import pytest +import yaml +from packaging.version import Version +from typer.testing import CliRunner + +REPO_ROOT = Path(__file__).parents[2] +EXTENSIONS_ROOT = REPO_ROOT / "extensions" +PRE_BUMP_VERSION = "1.0.0" +# Bundled extensions whose content had drifted while still declaring 1.0.0 +# when #4345 was filed. Their catalog version must stay above PRE_BUMP_VERSION +# so a copy installed before the fix is actually offered an update; a version +# that is not bumped past it here is a regression, not a skip. +DRIFTED_BEFORE_BUMP = frozenset({"agent-context", "assess", "git"}) + + +def _catalog_entries() -> dict[str, dict]: + catalog = json.loads((EXTENSIONS_ROOT / "catalog.json").read_text(encoding="utf-8")) + return catalog["extensions"] + + +def _bundled_ids() -> list[str]: + return sorted( + ext_id + for ext_id, entry in _catalog_entries().items() + if entry.get("bundled") + and not entry.get("download_url") + and (EXTENSIONS_ROOT / ext_id / "extension.yml").is_file() + ) + + +def _make_project(tmp_path: Path) -> Path: + project_dir = tmp_path / "project" + project_dir.mkdir() + (project_dir / ".specify").mkdir() + (project_dir / ".claude" / "skills").mkdir(parents=True) + return project_dir + + +def _stale_copy(tmp_path: Path, ext_id: str, version: str) -> Path: + """The real bundled source with only its manifest version rewritten.""" + source = tmp_path / "stale" / ext_id + shutil.copytree(EXTENSIONS_ROOT / ext_id, source) + manifest_path = source / "extension.yml" + data = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) + data["extension"]["version"] = version + manifest_path.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8") + return source + + +def _run_update(project_dir: Path, ext_id: str): + from specify_cli import app + from specify_cli.extensions import ExtensionCatalog + + catalog_info = dict(_catalog_entries()[ext_id]) + catalog_info.setdefault("_install_allowed", True) + # Real catalog entry and real bundled copy; only the network fetch of the + # catalog is replaced, and downloading must never be attempted for a + # bundled extension. + with patch.object(Path, "cwd", return_value=project_dir), \ + patch.object(ExtensionCatalog, "get_extension_info", return_value=catalog_info), \ + patch.object( + ExtensionCatalog, + "download_extension", + side_effect=AssertionError("bundled update must not download"), + ): + return CliRunner().invoke( + app, ["extension", "update", ext_id], input="y\n", catch_exceptions=True + ) + + +def test_bundled_ids_present(): + assert _bundled_ids(), "expected bundled extensions with in-repo sources" + + +@pytest.mark.parametrize("ext_id", _bundled_ids()) +def test_stale_bundled_install_is_updated_to_catalog_version(tmp_path: Path, ext_id: str): + from specify_cli._assets import get_speckit_version + from specify_cli.extensions import ExtensionManager + + catalog_version = Version(_catalog_entries()[ext_id]["version"]) + if catalog_version <= Version(PRE_BUMP_VERSION): + assert ext_id not in DRIFTED_BEFORE_BUMP, ( + f"'{ext_id}' drifted at {PRE_BUMP_VERSION} before #4345 but the catalog still " + f"advertises {catalog_version}; installs made before the fix would never be " + f"offered the shipped changes" + ) + pytest.skip(f"'{ext_id}' has not been bumped past {PRE_BUMP_VERSION}; nothing to deliver") + + project_dir = _make_project(tmp_path) + stale_source = _stale_copy(tmp_path, ext_id, PRE_BUMP_VERSION) + manager = ExtensionManager(project_dir) + manager.install_from_directory(stale_source, get_speckit_version()) + assert manager.registry.get(ext_id)["version"] == PRE_BUMP_VERSION + + result = _run_update(project_dir, ext_id) + + flat = " ".join(result.output.split()) + assert result.exit_code == 0, result.output + assert f"Updated to v{catalog_version}" in flat, flat + assert "Up to date" not in flat, flat + assert ExtensionManager(project_dir).registry.get(ext_id)["version"] == str(catalog_version) + + +@pytest.mark.parametrize("ext_id", _bundled_ids()) +def test_current_bundled_install_is_up_to_date(tmp_path: Path, ext_id: str): + """The bumped catalog must not re-offer an update to an install that + already carries the bundled version, or every fresh install would loop.""" + from specify_cli._assets import get_speckit_version + from specify_cli.extensions import ExtensionManager + + catalog_version = _catalog_entries()[ext_id]["version"] + project_dir = _make_project(tmp_path) + ExtensionManager(project_dir).install_from_directory( + EXTENSIONS_ROOT / ext_id, get_speckit_version() + ) + + result = _run_update(project_dir, ext_id) + + flat = " ".join(result.output.split()) + assert result.exit_code == 0, result.output + assert f"Up to date (v{catalog_version})" in flat, flat + assert ExtensionManager(project_dir).registry.get(ext_id)["version"] == catalog_version diff --git a/tests/contract/test_bundled_extension_versions.py b/tests/contract/test_bundled_extension_versions.py index 2d7f0c6c0e..f86f0afc72 100644 --- a/tests/contract/test_bundled_extension_versions.py +++ b/tests/contract/test_bundled_extension_versions.py @@ -25,6 +25,7 @@ REPO_ROOT = Path(__file__).parents[2] EXTENSIONS_ROOT = REPO_ROOT / "extensions" +EXAMPLE_BUNDLES_ROOT = REPO_ROOT / "examples" / "bundles" def _catalog_entries() -> dict[str, dict]: @@ -81,3 +82,39 @@ def test_catalog_version_is_valid_pep440(ext_id: str): f"extensions/catalog.json entry '{ext_id}' version {version!r} is not a valid " f"PEP 440 version ({exc}); `extension update` would skip it" ) + + +def _example_bundle_extension_pins() -> list[tuple[str, str, str]]: + """(bundle name, extension id, pinned version) for every example bundle.""" + pins = [] + for manifest_path in sorted(EXAMPLE_BUNDLES_ROOT.glob("*/bundle.yml")): + data = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) + for entry in (data.get("provides") or {}).get("extensions") or []: + pins.append((manifest_path.parent.name, entry["id"], str(entry.get("version")))) + return pins + + +def test_example_bundles_pin_extensions(): + assert _example_bundle_extension_pins(), "expected at least one extension pin in examples/bundles" + + +@pytest.mark.parametrize( + "bundle_name,ext_id,pinned", + _example_bundle_extension_pins(), + ids=lambda v: v if isinstance(v, str) else str(v), +) +def test_example_bundle_pins_match_bundled_manifest(bundle_name: str, ext_id: str, pinned: str): + """``BundleExtensionPrimitive`` refuses to install when the resolved + extension version differs from the bundle's pin, so an example bundle that + still pins a bundled extension's previous version fails for every user who + installs it. Test fixtures derive their pins from the bundled manifest and + therefore cannot catch this; the shipped examples must be checked directly.""" + manifest_path = EXTENSIONS_ROOT / ext_id / "extension.yml" + if not manifest_path.is_file(): + pytest.skip(f"'{ext_id}' is not a bundled extension; pin resolves via the catalog at install time") + manifest_version = _manifest_version(ext_id) + assert Version(pinned) == Version(manifest_version), ( + f"examples/bundles/{bundle_name}/bundle.yml pins '{ext_id}' to {pinned} but " + f"{manifest_path.relative_to(REPO_ROOT)} declares {manifest_version}; the bundler " + f"enforces exact pins, so installing this example would fail" + ) From 730681a2e78d19d43d16d3065637c6383d29a7f9 Mon Sep 17 00:00:00 2001 From: CrazyBaran Date: Sat, 19 Sep 2026 12:04:16 +0200 Subject: [PATCH 4/4] test(extensions): pin that every drifted extension stays covered by the delivery tests Address Copilot review round 2 on #4394. The update-delivery tests draw their parametrized cases from the live catalog, so an extension that lost its `bundled` flag, its catalog entry, or its in-repo manifest silently dropped out of both tests, and the DRIFTED_BEFORE_BUMP guard inside them never ran for it. The previous presence test only asserted the case list was non-empty. Replace it with test_drifted_extensions_are_covered, which asserts every extension in DRIFTED_BEFORE_BUMP is still a bundled, in-repo catalog entry. De-bundling one of them is a deliberate decision that should update that set rather than silently shrink coverage. Verified: clearing the bundled flag on `assess` drops its cases from the parametrized tests and this test is the only one that fails. Refs #4345 Co-Authored-By: Claude Fable 5.1 --- .../test_bundled_extension_update_delivery.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/contract/test_bundled_extension_update_delivery.py b/tests/contract/test_bundled_extension_update_delivery.py index b6258b9c4a..f85e745829 100644 --- a/tests/contract/test_bundled_extension_update_delivery.py +++ b/tests/contract/test_bundled_extension_update_delivery.py @@ -91,8 +91,20 @@ def _run_update(project_dir: Path, ext_id: str): ) -def test_bundled_ids_present(): - assert _bundled_ids(), "expected bundled extensions with in-repo sources" +def test_drifted_extensions_are_covered(): + """The parametrized tests below draw their cases from the live catalog, so + an extension that lost its ``bundled`` flag, its catalog entry, or its + in-repo manifest would silently drop out of them and the + ``DRIFTED_BEFORE_BUMP`` guard inside would never run for it. Pin the + coverage itself: every extension known to have drifted must still be a + bundled, in-repo catalog entry. De-bundling one of them is a deliberate + decision that should update this set, not a silent loss of coverage.""" + covered = set(_bundled_ids()) + missing = DRIFTED_BEFORE_BUMP - covered + assert not missing, ( + f"{sorted(missing)} drifted before #4345 but are no longer bundled, in-repo " + f"catalog entries, so the update-delivery tests no longer cover them" + ) @pytest.mark.parametrize("ext_id", _bundled_ids())