mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 03:56:43 +08:00
docs: add module quality scale
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"""宿主 Module 的渐进式集成质量清单。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class ModuleQualityLevel(StrEnum):
|
||||
"""描述模块当前接受的质量门禁等级。"""
|
||||
|
||||
LEGACY = "legacy"
|
||||
ASSESSED = "assessed"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ModuleQualityProfile:
|
||||
"""记录一个模块已验证规则、维护者和精确豁免原因。"""
|
||||
|
||||
module: str
|
||||
level: ModuleQualityLevel
|
||||
owner: str
|
||||
verified_rules: frozenset[str] = frozenset()
|
||||
exemption_reason: str | None = None
|
||||
|
||||
|
||||
QUALITY_RULES = frozenset(
|
||||
{
|
||||
"fake-client-or-fixture",
|
||||
"zero-real-network-tests",
|
||||
"sync-async-boundary",
|
||||
"no-blocking-io-in-event-loop",
|
||||
"auth-rate-timeout-offline-semantics",
|
||||
"bounded-concurrency-or-polling",
|
||||
"reload-stop-idempotent",
|
||||
"module-contract-v2",
|
||||
"sensitive-log-redaction",
|
||||
"owner-declared",
|
||||
}
|
||||
)
|
||||
|
||||
MODULE_QUALITY_PROFILES = {
|
||||
"bangumi": ModuleQualityProfile(
|
||||
module="bangumi",
|
||||
level=ModuleQualityLevel.ASSESSED,
|
||||
owner="MoviePilot core",
|
||||
verified_rules=frozenset(
|
||||
{
|
||||
"fake-client-or-fixture",
|
||||
"zero-real-network-tests",
|
||||
"sync-async-boundary",
|
||||
"reload-stop-idempotent",
|
||||
"module-contract-v2",
|
||||
"sensitive-log-redaction",
|
||||
"owner-declared",
|
||||
}
|
||||
),
|
||||
exemption_reason=(
|
||||
"外部 Bangumi API 的限流与并发策略仍沿用通用 HTTP adapter;"
|
||||
"本轮仅对配置快照改动面启用 assessed 门禁"
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def get_module_quality_profile(module: str) -> ModuleQualityProfile:
|
||||
"""返回显式 profile;未迁移模块以带原因的 legacy 视图呈现。"""
|
||||
return MODULE_QUALITY_PROFILES.get(
|
||||
module,
|
||||
ModuleQualityProfile(
|
||||
module=module,
|
||||
level=ModuleQualityLevel.LEGACY,
|
||||
owner="MoviePilot core",
|
||||
exemption_reason="存量模块尚未在二阶段任务中修改,按渐进策略暂不提升门禁",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -620,6 +620,15 @@ ModuleMethodSpec(
|
||||
|
||||
质量清单只约束新模块和被修改模块;历史模块以 `legacy`/`exempt + reason` 进入,不允许一次性阻断全部功能。
|
||||
|
||||
**实施记录(2026-08-21)**:
|
||||
|
||||
- `app/runtime/extensions/module/quality.py` 提供十项统一规则、`legacy/assessed` 等级、owner、已验证
|
||||
规则和精确豁免原因;所有存量模块均能生成有 owner/原因的 legacy 视图,不一次性阻断。
|
||||
- 本轮修改的 `bangumi` 首个进入 assessed:fake client、零真实网络、sync/async 边界、reload/stop、
|
||||
Contract V2、敏感日志和 owner 已登记;限流/并发继续复用通用 HTTP adapter 并明确豁免范围。
|
||||
- 详细规则和验收证据见 `docs/refactor/module-quality-scale.md`;自动测试阻止 profile 使用未登记规则,
|
||||
并要求今后修改模块时将对应 profile 纳入同一提交。
|
||||
|
||||
### 阶段 5:定义后台可靠性,不先引入分布式队列
|
||||
|
||||
#### ARCH-250:后台动作可靠性分类 ADR
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Module / Integration 渐进质量清单
|
||||
|
||||
本清单对应 ARCH-242。机器可检查定义位于
|
||||
`app/runtime/extensions/module/quality.py`;它不改变 Module ABI,也不要求一次修完全部历史模块。
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 未在本阶段修改的模块解析为 `legacy`,必须携带统一豁免原因和 owner。
|
||||
- 新模块或本阶段修改的模块必须新增显式 `ModuleQualityProfile`,只可使用登记规则。
|
||||
- `assessed` 表示已明确检查的规则集合,不等于所有规则满分;未覆盖项必须写精确原因。
|
||||
- 测试不得访问真实网络。外部错误、限流和超时通过 fake client、fixture 或 adapter stub 验证。
|
||||
- profile 不能替代 Module Contract V2;对外能力仍须在 contract registry 单独登记。
|
||||
|
||||
## 规则说明
|
||||
|
||||
| 规则 | 验收证据 |
|
||||
| --- | --- |
|
||||
| `fake-client-or-fixture` | provider 测试使用 fake client 或稳定录制 fixture |
|
||||
| `zero-real-network-tests` | 网络守卫下专项测试通过 |
|
||||
| `sync-async-boundary` | 同步 I/O 与 async 入口的调度策略明确 |
|
||||
| `no-blocking-io-in-event-loop` | async 专项测试或受控线程池证据 |
|
||||
| `auth-rate-timeout-offline-semantics` | 鉴权过期、限流、超时、离线结果分别测试 |
|
||||
| `bounded-concurrency-or-polling` | 并发上限、轮询周期或不适用理由明确 |
|
||||
| `reload-stop-idempotent` | init/reload/stop 可重复且资源最终释放 |
|
||||
| `module-contract-v2` | 公开能力进入 Module Contract V2 |
|
||||
| `sensitive-log-redaction` | token/cookie/password 不进入日志 |
|
||||
| `owner-declared` | profile 有维护 owner |
|
||||
|
||||
## 当前 assessed 切片
|
||||
|
||||
`bangumi`:本轮配置快照改造已验证 fake client、零真实网络、同步/异步边界、reload/stop、
|
||||
Contract V2、敏感日志和 owner。限流/并发仍复用通用 HTTP adapter,未在本切片重复实现。
|
||||
|
||||
+2
-1
@@ -6225,7 +6225,7 @@
|
||||
"app.workflow.actions.transfer_file -> app.workflow",
|
||||
"app.workflow.actions.transfer_file -> app.workflow.actions"
|
||||
],
|
||||
"module_count": 772,
|
||||
"module_count": 773,
|
||||
"modules": [
|
||||
"app",
|
||||
"app.adapters",
|
||||
@@ -6868,6 +6868,7 @@
|
||||
"app.runtime.extensions.module",
|
||||
"app.runtime.extensions.module.contracts",
|
||||
"app.runtime.extensions.module.dispatcher",
|
||||
"app.runtime.extensions.module.quality",
|
||||
"app.runtime.extensions.module_manager",
|
||||
"app.runtime.extensions.plugin",
|
||||
"app.runtime.extensions.plugin.access",
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Module Integration Quality Scale 渐进门禁测试。"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from app.runtime.extensions.module.quality import (
|
||||
MODULE_QUALITY_PROFILES,
|
||||
QUALITY_RULES,
|
||||
ModuleQualityLevel,
|
||||
get_module_quality_profile,
|
||||
)
|
||||
|
||||
|
||||
MODULE_ROOT = Path(__file__).parents[1] / "app" / "modules"
|
||||
|
||||
|
||||
def test_every_module_has_quality_view_with_owner_and_reason() -> None:
|
||||
"""所有存量模块都必须能解析为 assessed 或有理由的 legacy profile。"""
|
||||
modules = {
|
||||
path.name
|
||||
for path in MODULE_ROOT.iterdir()
|
||||
if path.is_dir() and not path.name.startswith(("_", "."))
|
||||
}
|
||||
|
||||
assert modules
|
||||
for module in modules:
|
||||
profile = get_module_quality_profile(module)
|
||||
assert profile.owner
|
||||
assert profile.level in ModuleQualityLevel
|
||||
if profile.level is ModuleQualityLevel.LEGACY:
|
||||
assert profile.exemption_reason
|
||||
|
||||
|
||||
def test_assessed_profiles_only_use_declared_rules() -> None:
|
||||
"""显式 profile 不得通过拼写新规则绕过统一质量维度。"""
|
||||
for profile in MODULE_QUALITY_PROFILES.values():
|
||||
assert profile.verified_rules
|
||||
assert profile.verified_rules <= QUALITY_RULES
|
||||
|
||||
|
||||
def test_bangumi_changed_slice_meets_required_quality_rules() -> None:
|
||||
"""本轮修改的 Bangumi 模块必须满足配置快照切片相关门禁。"""
|
||||
profile = get_module_quality_profile("bangumi")
|
||||
|
||||
assert profile.level is ModuleQualityLevel.ASSESSED
|
||||
assert {
|
||||
"fake-client-or-fixture",
|
||||
"zero-real-network-tests",
|
||||
"reload-stop-idempotent",
|
||||
"module-contract-v2",
|
||||
"owner-declared",
|
||||
} <= profile.verified_rules
|
||||
Reference in New Issue
Block a user