mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 16:07:01 +08:00
refactor: separate config debt from boundaries
This commit is contained in:
@@ -109,6 +109,9 @@
|
||||
宿主 Module 的部署配置读取也已统一到 `RuntimeSettingsCompat`:PostgreSQL、Redis、qBittorrent、
|
||||
rTorrent 和 Transmission 不再直接导入全局 Settings,模块目录由架构测试保持零直连;兼容代理仍保留
|
||||
启动早期与旧插件/测试的动态 Settings 注入语义。
|
||||
配置治理基线进一步区分债务与批准边界:canonical 未批准 Settings 直连和非组合根
|
||||
`SystemConfigOper()` 构造均为 `0`;`app/db/base.py`、`engine.py`、`session.py` 的启动前数据库基础设施
|
||||
读取及 startup 唯一 Oper 构造点以带理由的固定边界登记。四个集合都只能减少,不能新增或换位置。
|
||||
|
||||
### P2:中长期可演进性债务
|
||||
|
||||
@@ -1256,6 +1259,10 @@ FS proxy、Local/WebPush 存储适配以及 Module host adapter 切换到动态
|
||||
Settings,避免代理自递归,配置债务由 15 个文件降至 8 个。数据库引擎/Session 与下载器模块保留底层
|
||||
Settings 读取作为基础设施边界,架构基线已明确记录该例外,启动与架构专项测试通过。
|
||||
|
||||
2026-08-24 将 configuration baseline 升级为 schema v2:待整改的直接 Settings 与 Oper 构造均清零;
|
||||
数据库模型/引擎/Session 的 3 个启动前读取和 startup 的唯一 Oper 构造点进入带理由的批准边界。ratchet
|
||||
同时冻结债务与批准边界,批准项不能扩张,因此不会通过新增“例外”掩盖配置回流。
|
||||
|
||||
同日修正适配器配置下沉边界:OCR、CookieCloud、DoH、Rust 和资源签名等低层实现不再直接依赖
|
||||
`app.application`,由 `app.runtime.settings` 端口承接组合根注入;未启动装配时仍回退旧 Settings ABI,
|
||||
架构依赖专项和官方插件语义观察均通过。
|
||||
|
||||
@@ -65,6 +65,15 @@ CONFIGURATION_EXCLUDED_ROOTS = (
|
||||
APP_ROOT / "runtime" / "compat",
|
||||
APP_ROOT / "testing",
|
||||
)
|
||||
FOUNDATIONAL_SETTINGS_BOUNDARIES = {
|
||||
"app/db/base.py": "模型声明阶段必须在运行时配置服务装配前确定数据库主键类型",
|
||||
"app/db/engine.py": "数据库引擎是运行时配置服务的底层依赖,不能通过兼容代理自递归",
|
||||
"app/db/session.py": "数据库会话与连接配额必须在应用组合根装配前可用",
|
||||
}
|
||||
COMPOSITION_ROOT_OPER_BOUNDARIES = {
|
||||
("app/startup/initializers/modules.py", "SystemConfigOper"):
|
||||
"启动组合根负责构造唯一的系统配置数据库适配器",
|
||||
}
|
||||
|
||||
|
||||
def discover_modules() -> dict[str, Path]:
|
||||
@@ -118,9 +127,11 @@ def iter_runtime_import_nodes(tree: ast.AST):
|
||||
|
||||
|
||||
def collect_configuration_debt_baseline() -> dict[str, Any]:
|
||||
"""收集宿主 canonical 代码直接读取 settings 和构造数据库配置适配器的债务。"""
|
||||
"""分离配置债务与数据库基础设施、组合根的固定批准边界。"""
|
||||
settings_files: list[str] = []
|
||||
foundational_settings: list[dict[str, str]] = []
|
||||
oper_calls: list[dict[str, Any]] = []
|
||||
composition_root_calls: list[dict[str, str]] = []
|
||||
for path in sorted(APP_ROOT.rglob("*.py")):
|
||||
if any(path.is_relative_to(root) for root in CONFIGURATION_EXCLUDED_ROOTS):
|
||||
continue
|
||||
@@ -143,14 +154,22 @@ def collect_configuration_debt_baseline() -> dict[str, Any]:
|
||||
if alias.name == "SystemConfigOper"
|
||||
)
|
||||
if imports_settings:
|
||||
settings_files.append(relative)
|
||||
if reason := FOUNDATIONAL_SETTINGS_BOUNDARIES.get(relative):
|
||||
foundational_settings.append({"file": relative, "reason": reason})
|
||||
else:
|
||||
settings_files.append(relative)
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Name):
|
||||
continue
|
||||
if node.func.id in direct_oper_names:
|
||||
oper_calls.append({"file": relative, "name": node.func.id})
|
||||
call = {"file": relative, "name": node.func.id}
|
||||
boundary_key = (relative, node.func.id)
|
||||
if reason := COMPOSITION_ROOT_OPER_BOUNDARIES.get(boundary_key):
|
||||
composition_root_calls.append({**call, "reason": reason})
|
||||
else:
|
||||
oper_calls.append(call)
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"schema_version": 2,
|
||||
"scope": {
|
||||
"root": "app",
|
||||
"excluded": [
|
||||
@@ -164,10 +183,18 @@ def collect_configuration_debt_baseline() -> dict[str, Any]:
|
||||
"count": len(settings_files),
|
||||
"files": settings_files,
|
||||
},
|
||||
"foundational_settings_boundaries": {
|
||||
"count": len(foundational_settings),
|
||||
"entries": foundational_settings,
|
||||
},
|
||||
"system_config_oper_constructions": {
|
||||
"count": len(oper_calls),
|
||||
"calls": oper_calls,
|
||||
},
|
||||
"composition_root_oper_boundaries": {
|
||||
"count": len(composition_root_calls),
|
||||
"entries": composition_root_calls,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1326,6 +1353,8 @@ def configuration_ratchet_matches(
|
||||
sections = (
|
||||
("settings_imports", "files"),
|
||||
("system_config_oper_constructions", "calls"),
|
||||
("foundational_settings_boundaries", "entries"),
|
||||
("composition_root_oper_boundaries", "entries"),
|
||||
)
|
||||
for section, entries_key in sections:
|
||||
expected_section = expected.get(section, {})
|
||||
|
||||
+32
-14
@@ -1,5 +1,32 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"composition_root_oper_boundaries": {
|
||||
"count": 1,
|
||||
"entries": [
|
||||
{
|
||||
"file": "app/startup/initializers/modules.py",
|
||||
"name": "SystemConfigOper",
|
||||
"reason": "启动组合根负责构造唯一的系统配置数据库适配器"
|
||||
}
|
||||
]
|
||||
},
|
||||
"foundational_settings_boundaries": {
|
||||
"count": 3,
|
||||
"entries": [
|
||||
{
|
||||
"file": "app/db/base.py",
|
||||
"reason": "模型声明阶段必须在运行时配置服务装配前确定数据库主键类型"
|
||||
},
|
||||
{
|
||||
"file": "app/db/engine.py",
|
||||
"reason": "数据库引擎是运行时配置服务的底层依赖,不能通过兼容代理自递归"
|
||||
},
|
||||
{
|
||||
"file": "app/db/session.py",
|
||||
"reason": "数据库会话与连接配额必须在应用组合根装配前可用"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema_version": 2,
|
||||
"scope": {
|
||||
"excluded": [
|
||||
"app/plugins",
|
||||
@@ -10,20 +37,11 @@
|
||||
"root": "app"
|
||||
},
|
||||
"settings_imports": {
|
||||
"count": 3,
|
||||
"files": [
|
||||
"app/db/base.py",
|
||||
"app/db/engine.py",
|
||||
"app/db/session.py"
|
||||
]
|
||||
"count": 0,
|
||||
"files": []
|
||||
},
|
||||
"system_config_oper_constructions": {
|
||||
"calls": [
|
||||
{
|
||||
"file": "app/startup/initializers/modules.py",
|
||||
"name": "SystemConfigOper"
|
||||
}
|
||||
],
|
||||
"count": 1
|
||||
"calls": [],
|
||||
"count": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ def _performance_sample(
|
||||
def _transaction_sample(methods: list[dict[str, str]]) -> dict:
|
||||
"""构造最小事务债务 fixture,供单向 ratchet 行为测试。"""
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"schema_version": 2,
|
||||
"scope": "app/db/models and app/db/oper transaction ownership debt",
|
||||
"model_decorators": {
|
||||
"count": len(methods),
|
||||
@@ -75,8 +75,12 @@ def _transaction_sample(methods: list[dict[str, str]]) -> dict:
|
||||
def _configuration_sample(
|
||||
settings_files: list[str],
|
||||
oper_calls: list[dict[str, str]],
|
||||
foundational_entries: list[dict[str, str]] | None = None,
|
||||
composition_entries: list[dict[str, str]] | None = None,
|
||||
) -> dict:
|
||||
"""构造最小配置债务 fixture,供单向 ratchet 行为测试。"""
|
||||
foundational_entries = foundational_entries or []
|
||||
composition_entries = composition_entries or []
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"scope": {
|
||||
@@ -91,6 +95,14 @@ def _configuration_sample(
|
||||
"count": len(oper_calls),
|
||||
"calls": oper_calls,
|
||||
},
|
||||
"foundational_settings_boundaries": {
|
||||
"count": len(foundational_entries),
|
||||
"entries": foundational_entries,
|
||||
},
|
||||
"composition_root_oper_boundaries": {
|
||||
"count": len(composition_entries),
|
||||
"entries": composition_entries,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -414,6 +426,22 @@ def test_configuration_ratchet_allows_removal_but_rejects_new_access() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_configuration_ratchet_rejects_new_approved_boundaries() -> None:
|
||||
"""批准边界同样只能减少,不能借新增理由把新债务改名为豁免。"""
|
||||
existing = {"file": "app/db/base.py", "reason": "model declaration"}
|
||||
added = {"file": "app/db/new.py", "reason": "new exemption"}
|
||||
expected = _configuration_sample([], [], [existing])
|
||||
|
||||
assert architecture_baseline.configuration_ratchet_matches(
|
||||
expected,
|
||||
_configuration_sample([], [], []),
|
||||
)
|
||||
assert not architecture_baseline.configuration_ratchet_matches(
|
||||
expected,
|
||||
_configuration_sample([], [], [existing, added]),
|
||||
)
|
||||
|
||||
|
||||
def test_architecture_write_host_only_updates_host_files(
|
||||
tmp_path: Path,
|
||||
monkeypatch,
|
||||
|
||||
@@ -173,11 +173,11 @@ def test_host_oper_does_not_call_base_implicit_write_wrappers() -> None:
|
||||
|
||||
|
||||
def test_configuration_debt_baseline_tracks_canonical_direct_access() -> None:
|
||||
"""配置债务基线必须排除插件兼容面,并冻结两个可下降的直接访问集合。"""
|
||||
"""配置基线必须把零债务与固定基础设施边界分开冻结。"""
|
||||
baseline_path = BASELINE_ROOT / "configuration-debt-baseline.json"
|
||||
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
|
||||
|
||||
assert baseline["schema_version"] == 1
|
||||
assert baseline["schema_version"] == 2
|
||||
assert baseline["scope"]["excluded"] == [
|
||||
"app/plugins",
|
||||
"app/sdk",
|
||||
@@ -190,6 +190,20 @@ def test_configuration_debt_baseline_tracks_canonical_direct_access() -> None:
|
||||
assert baseline["system_config_oper_constructions"]["count"] == len(
|
||||
baseline["system_config_oper_constructions"]["calls"]
|
||||
)
|
||||
assert baseline["settings_imports"] == {"count": 0, "files": []}
|
||||
assert baseline["system_config_oper_constructions"] == {
|
||||
"count": 0,
|
||||
"calls": [],
|
||||
}
|
||||
assert {
|
||||
entry["file"]
|
||||
for entry in baseline["foundational_settings_boundaries"]["entries"]
|
||||
} == {"app/db/base.py", "app/db/engine.py", "app/db/session.py"}
|
||||
assert baseline["foundational_settings_boundaries"]["count"] == 3
|
||||
assert baseline["composition_root_oper_boundaries"]["count"] == 1
|
||||
assert baseline["composition_root_oper_boundaries"]["entries"][0]["file"] == (
|
||||
"app/startup/initializers/modules.py"
|
||||
)
|
||||
|
||||
|
||||
def test_startup_performance_baseline_records_normal_and_safe_lifecycle_resources():
|
||||
|
||||
Reference in New Issue
Block a user