From 15c17aa0a0407d553e6572378c37902a66d42e2b Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Mon, 2 Feb 2026 00:02:12 -0700 Subject: [PATCH] fix: re-register orphaned images and harden catalog branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build_module() now re-registers when a Docker image exists but the registry entry is missing (e.g., after manual registry edits or schema migrations). This fixes auto-build in combo workflows where a module's image exists but isn't tracked. Also fixes gr-rds catalog branch (main → maint-3.10) and adds tests for per-entry registry validation and orphan re-registration. --- src/gnuradio_mcp/middlewares/oot.py | 17 +++++++++++++ src/gnuradio_mcp/oot_catalog.py | 2 +- tests/unit/test_oot_installer.py | 38 +++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/gnuradio_mcp/middlewares/oot.py b/src/gnuradio_mcp/middlewares/oot.py index ee169e5..84d3f7d 100644 --- a/src/gnuradio_mcp/middlewares/oot.py +++ b/src/gnuradio_mcp/middlewares/oot.py @@ -128,6 +128,23 @@ class OOTInstallerMiddleware: # Idempotent: skip if image already exists if not force and self._image_exists(image_tag): existing = self._registry.get(module_name) + if existing is None: + # Image exists but registry entry missing — re-register + existing = OOTImageInfo( + module_name=module_name, + image_tag=image_tag, + git_url=git_url, + branch=branch, + git_commit=commit, + base_image=effective_base, + built_at=datetime.now(timezone.utc).isoformat(), + ) + self._registry[module_name] = existing + self._save_registry() + logger.info( + "Re-registered existing image '%s' for module '%s'", + image_tag, module_name, + ) return OOTInstallResult( success=True, image=existing, diff --git a/src/gnuradio_mcp/oot_catalog.py b/src/gnuradio_mcp/oot_catalog.py index 737fce1..bac313b 100644 --- a/src/gnuradio_mcp/oot_catalog.py +++ b/src/gnuradio_mcp/oot_catalog.py @@ -139,7 +139,7 @@ CATALOG: dict[str, OOTModuleEntry] = { description="FM RDS/RBDS (Radio Data System) decoder", category="Broadcast", git_url="https://github.com/bastibl/gr-rds", - branch="main", + branch="maint-3.10", homepage="https://github.com/bastibl/gr-rds", preinstalled=True, ), diff --git a/tests/unit/test_oot_installer.py b/tests/unit/test_oot_installer.py index 404198c..96b5865 100644 --- a/tests/unit/test_oot_installer.py +++ b/tests/unit/test_oot_installer.py @@ -187,6 +187,44 @@ class TestRegistry: mw._save_registry() assert mw._registry_path.exists() + def test_load_skips_corrupt_entries(self, oot): + """Per-entry validation: one corrupt entry doesn't nuke valid ones.""" + data = { + "good_module": { + "module_name": "good_module", + "image_tag": "gr-oot-good:main-abc1234", + "git_url": "https://example.com/gr-good", + "branch": "main", + "git_commit": "abc1234", + "base_image": "gnuradio-runtime:latest", + "built_at": "2025-01-01T00:00:00+00:00", + }, + "bad_module": { + "image_id": "sha256:deadbeef", + "build_deps": ["libfoo-dev"], + }, + } + oot._registry_path.write_text(json.dumps(data)) + loaded = oot._load_registry() + assert "good_module" in loaded + assert "bad_module" not in loaded + + def test_build_module_reregisters_orphaned_image(self, oot): + """build_module re-registers when image exists but registry empty.""" + # Mock: _get_remote_commit returns a commit, _image_exists says yes + oot._get_remote_commit = MagicMock(return_value="abc1234") + oot._image_exists = MagicMock(return_value=True) + + result = oot.build_module( + git_url="https://github.com/example/gr-test", + branch="main", + ) + + assert result.success is True + assert result.skipped is True + assert "test" in oot._registry + assert oot._registry["test"].image_tag == "gr-oot-test:main-abc1234" + # ────────────────────────────────────────── # Image Naming