From 08b16165fac946efbc4fa4e1e5e7964aeb4b2474 Mon Sep 17 00:00:00 2001 From: ChuckBuilds Date: Tue, 4 Aug 2026 12:37:56 -0400 Subject: [PATCH] feat(store): protect the one population the sunset would break The B6 sunset deletes each plugin's guarded-import fallback, so a plugin that floors at 3.2.0 must never reach a core that lacks the 3.2.0 modules. The gate could not stop that for the population most at risk. A device installed from the v3.1.0 release reports __version__ = "1.0.0". The gate treated anything below TRUSTWORTHY_FLOOR as "unknown, do not block" -- correct while every manifest floors at 2.0.0, because blocking would have emptied the plugin store for those users. But after the sunset it hands them a 3.2.0-floored plugin with no fallback, which fails to load with one log line. Nothing else in the system protects them: they cannot be told apart from a genuine 1.0.0 install. On an untrustworthy core the gate now refuses a floor ABOVE 2.0.0 and still allows anything at or below it. A floor above the ecosystem baseline says the plugin needs modules that arrived after 2.0.0, and a core reporting below that -- whether it is the v3.1.0 release or something genuinely ancient -- will not have them. Refusing leaves the user on the version they already run instead of one that cannot load. Measured against all 42 published manifests: today (every manifest floors at 2.0.0) core 1.0.0 / 2.0.0 / 3.1.0 / 3.2.0 / unparseable -> 0 of 42 refused after B6 (same manifests floored at 3.2.0) core 1.0.0 -> 38 refused, core 3.1.0 -> 38 refused, core 3.2.0 -> 0 So nobody loses the store today, and the sunset cannot reach a core that cannot run it. Two older tests asserted the previous "allow everything" behaviour; they now express the new rule with a 2.0.0 floor, which is what their no-lockout intent was actually about. The 38-of-42 in that measurement surfaced a separate B6 trap, recorded here because it will bite whoever raises the floors: four plugins (flights, leaderboard, music, stocks) declare the floor as a TOP-LEVEL `min_ledmatrix_version`, a third spelling, which declared_min_version checks before the versions[] array. For those, editing versions[0] is a silent no-op and the floor stays at 2.0.0. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5 --- src/plugin_system/compatibility.py | 28 +++++++++-- test/test_plugin_compatibility_gate.py | 69 +++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/plugin_system/compatibility.py b/src/plugin_system/compatibility.py index fafa34e6..aeb8d351 100644 --- a/src/plugin_system/compatibility.py +++ b/src/plugin_system/compatibility.py @@ -182,11 +182,33 @@ def check(manifest: Dict[str, Any], core_version: str) -> Tuple[bool, Optional[s ``reason`` is user-facing text, present only when incompatible. """ current = parse_semver(core_version) - if current is None or current < TRUSTWORTHY_FLOOR: - return True, None - name = manifest.get('name') or manifest.get('id') or 'This plugin' + if current is None or current < TRUSTWORTHY_FLOOR: + # The version is not evidence of what this core HAS. But a floor above + # the ecosystem baseline says the plugin needs modules that arrived + # *after* 2.0.0 — and a core reporting below that either is the v3.1.0 + # release (which ships __version__ = "1.0.0" and has none of the 3.2.0 + # modules) or is genuinely ancient. Either way it will not have them. + # + # This is the only protection available to that population: they cannot + # be told apart from a real 1.0.0 install, so the gate cannot reason + # about them, and the *plugin's* guarded-import fallback disappears at + # the B6 sunset. Refusing the install leaves them on the version they + # already run instead of handing them one that fails to load. + # + # Floors at or below 2.0.0 are still allowed, which is every manifest + # published today — so this does not lock anyone out of the store. + declared = declared_min_version(manifest) + needed = parse_semver(declared) + if needed is not None and needed > TRUSTWORTHY_FLOOR: + return False, ( + f"{name} requires LEDMatrix {declared} or newer. This system " + f"reports {core_version}, which is too old to identify " + f"reliably — update LEDMatrix, then install it." + ) + return True, None + # Ranges first: they are the canonical field and can rule out a core that # clears the floor. if satisfies_compatible_versions(manifest, current) is False: diff --git a/test/test_plugin_compatibility_gate.py b/test/test_plugin_compatibility_gate.py index 5b5360d9..0d9c356b 100644 --- a/test/test_plugin_compatibility_gate.py +++ b/test/test_plugin_compatibility_gate.py @@ -81,17 +81,24 @@ class TestCheck: ok, reason = compatibility.check({"id": "x"}, "3.2.0") assert ok is True and reason is None - def test_untrustworthy_core_version_allows_everything(self): + def test_untrustworthy_core_allows_todays_ecosystem_floor(self): """The v3.1.0 release reports 1.0.0. Nearly every manifest floors at - 2.0.0, so blocking here would stop those users installing any plugin - at all — strictly worse than the problem being solved.""" + 2.0.0, so blocking *that* would stop those users installing any plugin + at all — strictly worse than the problem being solved. + + A floor ABOVE 2.0.0 is refused instead; see + TestUntrustworthyCoreAndTheSunset for why that case is different.""" ok, reason = compatibility.check( - {"min_ledmatrix_version": "3.2.0"}, "1.0.0") + {"min_ledmatrix_version": "2.0.0"}, "1.0.0") assert ok is True and reason is None - def test_unparseable_core_version_allows(self): - ok, _ = compatibility.check({"min_ledmatrix_version": "3.2.0"}, "not-a-version") + def test_unparseable_core_version_allows_the_ecosystem_floor(self): + """An unidentifiable core is treated exactly like an untrustworthy one: + today's 2.0.0 floor is allowed, a post-sunset floor is not.""" + ok, _ = compatibility.check({"min_ledmatrix_version": "2.0.0"}, "not-a-version") assert ok is True + ok, _ = compatibility.check({"min_ledmatrix_version": "3.2.0"}, "not-a-version") + assert ok is False def test_unparseable_floor_allows(self): ok, _ = compatibility.check({"min_ledmatrix_version": {"nope": 1}}, "3.2.0") @@ -312,3 +319,53 @@ class TestMoreRestrictiveWins: m = {"compatible_versions": [">=2.0.0"], "versions": [{"ledmatrix_min_version": "2.0.0"}]} assert compatibility.check(m, "1.0.0") == (True, None) + + +class TestUntrustworthyCoreAndTheSunset: + """The population B6 would otherwise break. + + A device installed from the v3.1.0 release reports `__version__ = "1.0.0"`. + The gate cannot tell it apart from a genuine 1.0.0 install, so it cannot + reason about what that core actually has — and at the B6 sunset the + plugin's guarded-import fallback is gone. Without this rule the store hands + those users a 3.2.0-floored plugin that fails to load, and nothing else in + the system protects them. + + The rule: on an untrustworthy core, refuse a floor *above* the ecosystem + baseline, allow anything at or below it. Every manifest published today + floors at exactly 2.0.0, so nobody is locked out of the store. + """ + + UNTRUSTWORTHY = ["1.0.0", "0.9.0", "1.9.9"] + + @pytest.mark.parametrize("core", UNTRUSTWORTHY) + def test_refuses_a_post_sunset_floor(self, core): + m = {"name": "Hockey Scoreboard", + "versions": [{"ledmatrix_min_version": "3.2.0"}]} + ok, reason = compatibility.check(m, core) + assert ok is False, ( + f"core {core} must not receive a 3.2.0-floored plugin: after the " + "sunset there is no fallback and it will fail to load" + ) + assert "3.2.0" in reason and core in reason + + @pytest.mark.parametrize("core", UNTRUSTWORTHY) + def test_still_allows_todays_ecosystem_floor(self, core): + """Regression guard: every published manifest floors at 2.0.0. If this + starts refusing, those users lose the plugin store entirely.""" + m = {"versions": [{"ledmatrix_min": "2.0.0"}]} + assert compatibility.check(m, core) == (True, None) + + @pytest.mark.parametrize("core", UNTRUSTWORTHY) + def test_still_allows_an_undeclared_floor(self, core): + assert compatibility.check({"id": "x"}, core) == (True, None) + + def test_trustworthy_core_below_the_floor_is_unaffected(self): + """A core that reports 3.1.0 is believable and already handled by the + ordinary comparison — not by this rule.""" + m = {"name": "P", "versions": [{"ledmatrix_min_version": "3.2.0"}]} + ok, reason = compatibility.check(m, "3.1.0") + assert ok is False + assert "too old to identify" not in reason, ( + "a believable version should get the ordinary message" + )