mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-04 18:28:06 +00:00
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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
This commit is contained in:
co-authored by
Claude Opus 5
parent
f31b458bfd
commit
08b16165fa
@@ -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.
|
``reason`` is user-facing text, present only when incompatible.
|
||||||
"""
|
"""
|
||||||
current = parse_semver(core_version)
|
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'
|
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
|
# Ranges first: they are the canonical field and can rule out a core that
|
||||||
# clears the floor.
|
# clears the floor.
|
||||||
if satisfies_compatible_versions(manifest, current) is False:
|
if satisfies_compatible_versions(manifest, current) is False:
|
||||||
|
|||||||
@@ -81,17 +81,24 @@ class TestCheck:
|
|||||||
ok, reason = compatibility.check({"id": "x"}, "3.2.0")
|
ok, reason = compatibility.check({"id": "x"}, "3.2.0")
|
||||||
assert ok is True and reason is None
|
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
|
"""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
|
2.0.0, so blocking *that* would stop those users installing any plugin
|
||||||
at all — strictly worse than the problem being solved."""
|
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(
|
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
|
assert ok is True and reason is None
|
||||||
|
|
||||||
def test_unparseable_core_version_allows(self):
|
def test_unparseable_core_version_allows_the_ecosystem_floor(self):
|
||||||
ok, _ = compatibility.check({"min_ledmatrix_version": "3.2.0"}, "not-a-version")
|
"""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
|
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):
|
def test_unparseable_floor_allows(self):
|
||||||
ok, _ = compatibility.check({"min_ledmatrix_version": {"nope": 1}}, "3.2.0")
|
ok, _ = compatibility.check({"min_ledmatrix_version": {"nope": 1}}, "3.2.0")
|
||||||
@@ -312,3 +319,53 @@ class TestMoreRestrictiveWins:
|
|||||||
m = {"compatible_versions": [">=2.0.0"],
|
m = {"compatible_versions": [">=2.0.0"],
|
||||||
"versions": [{"ledmatrix_min_version": "2.0.0"}]}
|
"versions": [{"ledmatrix_min_version": "2.0.0"}]}
|
||||||
assert compatibility.check(m, "1.0.0") == (True, None)
|
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"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user