fix: 增加插件 V3t 运行时兼容门禁 (#6475)

* fix(plugin): enforce declared runtime compatibility

* chore(ci): record plugin runtime compatibility edges

---------

Co-authored-by: jxxghp <jxxghp@gmail.com>
This commit is contained in:
InfinityPacer
2026-08-27 07:06:16 +08:00
committed by GitHub
co-authored by jxxghp
parent 627775c090
commit 3b7f8c357d
8 changed files with 119 additions and 5 deletions
+3 -3
View File
@@ -1,8 +1,8 @@
{
"application": {
"covered_lines": 9309,
"percent": 77.8,
"statements": 11965
"covered_lines": 9312,
"percent": 77.81,
"statements": 11968
},
"domain": {
"covered_lines": 3390,
+9 -2
View File
@@ -179,8 +179,8 @@
"app.adapters.system.host"
]
},
"edge_count": 6810,
"edge_sha256": "141ed79f9097aaed4b1933f2fe931b7364e7a1a88d63ea8ca79a6012255f8d9c",
"edge_count": 6817,
"edge_sha256": "e3d43fec9f7bc936ef5a2ffe7ba11ea054d1ba7d1c42e7101978480cd99a63fe",
"edges": [
"app -> app.runtime",
"app -> app.runtime.compat",
@@ -221,6 +221,7 @@
"app.adapters.external.market -> app.adapters.system.plugin",
"app.adapters.external.market -> app.adapters.system.plugin.manifest",
"app.adapters.external.market -> app.foundation",
"app.adapters.external.market -> app.foundation.environment",
"app.adapters.external.market -> app.foundation.singleton",
"app.adapters.external.market -> app.foundation.url",
"app.adapters.external.market -> app.foundation.version",
@@ -2961,6 +2962,8 @@
"app.application.plugin.inventory -> app.application.plugin",
"app.application.plugin.inventory -> app.application.plugin.identity",
"app.application.plugin.inventory -> app.application.plugin.source",
"app.application.plugin.inventory -> app.foundation",
"app.application.plugin.inventory -> app.foundation.environment",
"app.application.plugin.recovery -> app.application",
"app.application.plugin.recovery -> app.application.plugin",
"app.application.plugin.recovery -> app.application.plugin.install",
@@ -6085,6 +6088,10 @@
"app.runtime.extensions.plugin.lifecycle -> app.runtime.observability",
"app.runtime.extensions.plugin.lifecycle -> app.schemas",
"app.runtime.extensions.plugin.lifecycle -> app.schemas.plugin",
"app.runtime.extensions.plugin.loader -> app.foundation",
"app.runtime.extensions.plugin.loader -> app.foundation.environment",
"app.runtime.extensions.plugin.loader -> app.runtime",
"app.runtime.extensions.plugin.loader -> app.runtime.settings",
"app.runtime.extensions.plugin.loader -> app.schemas",
"app.runtime.extensions.plugin.loader -> app.schemas.plugin",
"app.runtime.extensions.plugin.metadata -> app.runtime",
+21
View File
@@ -82,6 +82,27 @@ def test_only_v3_compatible_entries_are_candidates() -> None:
assert not inventory.candidates_for("Undeclared")
def test_free_threaded_runtime_excludes_explicit_v3t_false(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""V3t 只拒绝明确声明不支持的插件,未声明仍保持兼容。"""
monkeypatch.setattr(
"app.application.plugin.inventory.is_free_threaded_runtime",
lambda: True,
)
inventory = PluginCandidateInventoryReader(
market_loader=lambda *_args: {
"Allowed": {"version": "1.0.0"},
"Rejected": {"version": "1.0.0", "v3t": False},
},
).load([THIRD_PARTY_MARKET])
assert {candidate.plugin_id for candidate in inventory.online_candidates} == {
"Allowed"
}
def test_official_source_is_classified_and_public_candidate_uses_plugin_version() -> None:
"""官方仓库使用官方来源类型,候选公共字段与 Plugin schema 对齐。"""
reader = PluginCandidateInventoryReader(
+21
View File
@@ -1006,6 +1006,27 @@ class TestPluginHelper:
)
assert not PluginHelper.is_package_plugin_compatible({}, "")
def test_free_threaded_package_compatibility_honors_explicit_v3t_false(
self,
monkeypatch,
) -> None:
"""V3t 只把 package 中明确的 v3t:false 视为运行时不兼容。"""
from app.adapters.external import market as market_module
from app.adapters.external.market import PluginHelper
monkeypatch.setattr(market_module, "is_free_threaded_runtime", lambda: True)
monkeypatch.setattr(
market_module,
"settings",
SimpleNamespace(VERSION_FLAG="v3"),
)
assert PluginHelper.is_package_plugin_compatible({}, "v3")
assert PluginHelper.is_package_plugin_compatible({"v3t": True}, "v3")
assert not PluginHelper.is_package_plugin_compatible(
{"v3t": False}, "v3"
)
def test_get_online_plugins_force_keeps_release_cache_scoped(self, monkeypatch):
"""
全市场刷新不清理 Release 缓存,Release 接口按请求仓库协调刷新两类数据。
+29
View File
@@ -113,6 +113,35 @@ def test_loader_executes_each_instance_in_an_isolated_module_namespace(
assert plugin_package.demoplugin is source_module
def test_loader_runtime_gate_only_rejects_explicit_incompatible_declarations(
tmp_path,
monkeypatch,
):
"""运行目录缺少声明或 runtime 为空时保持历史插件可加载。"""
from app.runtime.extensions.plugin import loader as loader_module
monkeypatch.setattr(
loader_module,
"get_runtime_setting",
lambda key: "v3" if key == "VERSION_FLAG" else None,
)
monkeypatch.setattr(loader_module, "is_free_threaded_runtime", lambda: True)
missing = tmp_path / "missing"
missing.mkdir()
assert PluginLoader._is_runtime_compatible(missing)
empty = tmp_path / "empty"
empty.mkdir()
(empty / "package.json").write_text('{"runtime": {}}', encoding="utf-8")
assert PluginLoader._is_runtime_compatible(empty)
rejected = tmp_path / "rejected"
rejected.mkdir()
(rejected / "package.json").write_text('{"v3t": false}', encoding="utf-8")
assert not PluginLoader._is_runtime_compatible(rejected)
def test_clone_service_persists_descriptor_without_copying_source_package():
"""创建分身只写实例描述和隔离配置,并始终跟随源插件版本。"""
instances: dict[str, PluginInstance] = {}