mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-31 13:07:56 +08:00
143 lines
4.2 KiB
Python
143 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""防止宿主消费者绕过 application Facade 直接定位进程级运行时。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
if __package__:
|
|
from scripts.architecture.baseline import discover_modules, resolve_imports
|
|
else: # pragma: no cover - 直接执行脚本时从同目录解析
|
|
from baseline import discover_modules, resolve_imports
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class RuntimeFacadePolicy:
|
|
"""声明一个 concrete 运行时模块的合法装配与兼容消费者。"""
|
|
|
|
name: str
|
|
dependency: str
|
|
exact_consumers: frozenset[str]
|
|
consumer_prefixes: tuple[str, ...] = ()
|
|
|
|
def allows(self, module_name: str) -> bool:
|
|
"""判断模块是否属于明确的组合根、实现包或兼容入口。"""
|
|
if module_name in self.exact_consumers:
|
|
return True
|
|
return any(
|
|
module_name == prefix or module_name.startswith(f"{prefix}.")
|
|
for prefix in self.consumer_prefixes
|
|
)
|
|
|
|
|
|
RUNTIME_FACADE_POLICIES = (
|
|
RuntimeFacadePolicy(
|
|
name="scheduler",
|
|
dependency="app.scheduler",
|
|
exact_consumers=frozenset(
|
|
{
|
|
"app.scheduler",
|
|
"app.startup.initializers.modules",
|
|
"app.startup.initializers.scheduler",
|
|
}
|
|
),
|
|
),
|
|
RuntimeFacadePolicy(
|
|
name="module",
|
|
dependency="app.runtime.extensions.module_manager",
|
|
exact_consumers=frozenset(
|
|
{
|
|
"app.sdk.plugins",
|
|
"app.startup.initializers.modules",
|
|
}
|
|
),
|
|
),
|
|
RuntimeFacadePolicy(
|
|
name="plugin",
|
|
dependency="app.runtime.extensions.plugin_manager",
|
|
exact_consumers=frozenset(
|
|
{
|
|
"app.sdk.plugins",
|
|
"app.startup.initializers.modules",
|
|
"app.startup.initializers.plugins",
|
|
}
|
|
),
|
|
),
|
|
RuntimeFacadePolicy(
|
|
name="command",
|
|
dependency="app.command",
|
|
exact_consumers=frozenset(
|
|
{
|
|
"app.startup.initializers.command",
|
|
"app.startup.initializers.modules",
|
|
}
|
|
),
|
|
),
|
|
RuntimeFacadePolicy(
|
|
name="workflow",
|
|
dependency="app.workflow",
|
|
exact_consumers=frozenset({"app.startup.initializers.workflow"}),
|
|
consumer_prefixes=("app.workflow",),
|
|
),
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True, order=True, slots=True)
|
|
class ServiceLocatorViolation:
|
|
"""描述一处绕过 application Facade 的 concrete 运行时依赖。"""
|
|
|
|
policy: str
|
|
source: str
|
|
dependency: str
|
|
|
|
def render(self) -> str:
|
|
"""返回适合 CI 输出的稳定诊断文本。"""
|
|
return (
|
|
f"{self.source}: {self.policy} 运行时必须经 application Facade 访问,"
|
|
f"不得直接依赖 {self.dependency}"
|
|
)
|
|
|
|
|
|
def collect_service_locator_violations(
|
|
graph: dict[str, set[str]] | None = None,
|
|
) -> list[ServiceLocatorViolation]:
|
|
"""扫描宿主依赖图,返回绕过已登记 Facade 的运行时直连。"""
|
|
if graph is None:
|
|
modules = discover_modules()
|
|
known_modules = set(modules)
|
|
graph = {
|
|
module_name: resolve_imports(module_name, path, known_modules)
|
|
for module_name, path in modules.items()
|
|
}
|
|
|
|
violations: list[ServiceLocatorViolation] = []
|
|
for policy in RUNTIME_FACADE_POLICIES:
|
|
for module_name, dependencies in graph.items():
|
|
if (
|
|
policy.dependency not in dependencies
|
|
or policy.allows(module_name)
|
|
):
|
|
continue
|
|
violations.append(
|
|
ServiceLocatorViolation(
|
|
policy=policy.name,
|
|
source=module_name,
|
|
dependency=policy.dependency,
|
|
)
|
|
)
|
|
return sorted(violations)
|
|
|
|
|
|
def main() -> int:
|
|
"""执行进程级运行时服务定位零债务门禁。"""
|
|
violations = collect_service_locator_violations()
|
|
if violations:
|
|
print("\n".join(violation.render() for violation in violations))
|
|
return 1
|
|
print("进程级运行时 Facade 门禁通过")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|