refactor(architecture): unify event facts and policy

This commit is contained in:
jxxghp
2026-08-27 09:56:27 +08:00
parent 86157be2a9
commit 1133557849
16 changed files with 4327 additions and 932 deletions
+10 -9
View File
@@ -2,7 +2,7 @@
> 审计日期:2026-08-26
>
> 审计基线`v3@9053db926d20`,与 `origin/v3` 一致
> 首次审计历史快照`v3@9053db926d20`;当前交付状态以路线图和生成 fixture 为准
>
> 文档性质:当前源码的差距清单与分阶段执行说明,不是历史重构结项账本
@@ -69,7 +69,7 @@ MoviePilot V3 已经形成较清晰的模块化单体:`foundation`、`domain`
| 指标 | 当前值 | 解释 |
|---|---:|---|
| 宿主 Python 模块 / 内部依赖边 | 835 / 6,810 | `dependency-baseline.json` 当前快照 |
| 宿主 Python 模块 / 内部依赖边 | 835 / 6,817 | `dependency-baseline.json` 当前快照 |
| 非平凡 SCC | 2 | 新增 Chain 包根环;另一个是隔离的 29 模块 TMDB 移植包环 |
| 跨层 DB 边界债务 | 0 | Application、Chain、API、Agent、Runtime、Workflow 到 DB 的受控债务均为零 |
| Model/Oper 事务债务 | 0 | 自建 Session、自动事务装饰器、直接 commit/rollback 等基线均为零 |
@@ -78,8 +78,8 @@ MoviePilot V3 已经形成较清晰的模块化单体:`foundation`、`domain`
| Python 源码量 | 约 271,400 行 | 60 个文件超过 1,000 行,14 个超过 2,000 行 |
| 长方法 | 281 个超过 80 行 | 67 个超过 150 行,23 个超过 250 行;大量是私有方法 |
| 全量 mypy 历史债务 | 11,983 / 601 文件 | strict frontier 当前只覆盖 41 个文件,且 ratchet 已新增 2 个错误 |
| Ruff 历史诊断 | 973 | 低水位门禁通过,但规则集只覆盖 `E4/E7/E9/F/I` |
| 覆盖率低水位 | Application 77.76%Domain 79.24% | Chain、Runtime、Agent、Adapter、Startup 未进入包级覆盖率门禁 |
| Ruff 历史诊断 | 972 | 低水位门禁通过,但规则集只覆盖 `E4/E7/E9/F/I` |
| 覆盖率低水位 | Application 77.82%Domain 79.24% | Chain、Runtime、Agent、Adapter、Startup 未进入包级覆盖率门禁 |
### 3.3 热点文件
@@ -160,15 +160,17 @@ MoviePilot V3 已经形成较清晰的模块化单体:`foundation`、`domain`
- 架构总览此前仍记录 811 模块、6,572 条边和 1 个 SCC,已经落后于当前基线。
- Event consumer 扫描曾把任意同名 `.register()` 调用当成事件注册;S0-L2.5 已改为证明
canonical EventManager receiver10 个动态误报归零并保留唯一 workflow 动态注册。
- S0-L2.6 已将 producer/consumer 合并为逐调用事实源:99 个 producer(98 静态、1 动态)与
17 个 consumer16 静态、1 动态);consumer 由不可自动写入的精确人工 policy 管理。
**目标与步骤**
- [ ] 指定一个机器可读事实源,文档指标由 fixture 生成或只保留不易漂移的语义描述
- [x] 指定统一 Event 机器事实源;生成快照与人工 consumer policy 分离且互相不能覆盖
- [x] 修正 Oper 示例,分别展示宿主显式 UoW 与插件兼容 Facade,并以文档测试禁止回退。
- [x] 明确 Application/Chain 不永久直连具体 Adapter;业务层拥有 Portstartup 注入实现。
- [x] 让 SCC 规则、精确 policy 和文档声明一致;Chain 临时债务与 TMDB vendor containment 分开治理。
- [x] Event 扫描只识别 EventManager 实例/别名和事件装饰器;未知 receiver 不再污染动态事实。
- [ ] CI 分开报告“快照一致”与“语义规则通过”,禁止把者表述为架构完全正确。
- [x] CI 分开报告“Event 语义 policy”与“宿主快照一致”,禁止把者表述为架构完全正确。
**验收**
@@ -496,8 +498,7 @@ MoviePilot V3 已经形成较清晰的模块化单体:`foundation`、`domain`
- 复杂度脚本只检查 API、Application、Chain 的公共入口;私有长方法、类/文件规模和圈复杂度不受控。
- strict mypy 仅 41 个文件,高风险 lifecycle、Scheduler、Agent、Plugin Manager 多数不在 frontier。
- coverage ratchet 只聚合 Application 和 Domain。
- Task owner gate 盘点原生并发原语;Event producer 别名/关键字识别和 consumer 人工 policy
尚未纳入统一事实源门禁。
- Task owner gate 尚未盘点原生并发原语;Event producer/consumer 已纳入统一事实源和人工 policy
- Ruff 仅是有限规则集的历史低水位,不代表整体风格/正确性无债务。
**目标与步骤**
@@ -508,7 +509,7 @@ MoviePilot V3 已经形成较清晰的模块化单体:`foundation`、`domain`
- [ ] coverage 增加 Chain、Runtime、Agent、Startup 的高风险子包或关键文件组,不用低价值行数冲百分比。
- [ ] 原生并发门禁按 ARCH-101/106 修正。
- [x] Event consumer 扫描证明 canonical EventManager receiver,清除同名方法误报。
- [ ] Event producer 识别和 consumer zero-growth policy 在 S0-L2.6 纳入统一事实源门禁。
- [x] Event producer 别名/关键字/有限条件识别和 consumer exact policy 纳入统一事实源门禁。
- [ ] Module Quality Scale 增加 capability -> required rules -> evidence tests 映射,避免“已登记”等同“已验证”。
- [ ] 修改 CI 或门禁脚本时同时运行 `tests/test_architecture_ci.py` 和对应脚本单元测试。
+16 -5
View File
@@ -7,7 +7,7 @@
> [`docs/rules/04-design-patterns.md`](rules/04-design-patterns.md) 为准,本文与其保持一致;
> 如出现差异,以规则文档为准。
>
> *Last Updated: 2026-08-24*
> *Last Updated: 2026-08-27*
---
@@ -369,6 +369,13 @@ collector 只接受 canonical `eventmanager`、`EventManager()` 及其有限别
`register`/`add_event_listener`;当前宿主有 16 个静态注册点,另保留 1 个由工作流配置驱动的
真实动态注册。`app/plugins/**` 插件副本不进入宿主事实。
生产者与消费者共用 `scripts/architecture/event_facts.py` 这一份逐调用事实源。当前宿主有 99 个
生产调用,其中 98 个静态解析为 100 个事件引用,只有 `Command.send_plugin_event` 的插件事件类型
保持动态;17 个消费注册中 16 个静态、1 个动态。生成的
`runtime-contract-baseline.json` 保存 line-free 事实、数量和枚举索引;人工维护的
`runtime-contract-policy.json` 只批准 consumer 的精确 fingerprint、owner 和理由,任何新增、替换、
重复或陈旧项都会失败,`--write-host` 不会改写该 policy。
```python
from app.sdk.events import snapshot_event_data
@@ -683,6 +690,9 @@ flowchart LR
stream/vendor/diagnostic/control-plane 事实是精确 containment。每条初始边的指纹由测试独立冻结,
bindings/uses 变化、分类互换、通配导入和初始边增长都会失败;债务删除时同步删除冻结项以禁止恢复,
`--write-host` 不会改写人工 policy 或冻结上界。
- `event_facts` 是生产者/消费者唯一收集源;运行快照记录 99 个生产调用和 17 个消费注册,
consumer 的 17 个唯一 fingerprint 另由只读人工 policy 精确准入。CI 将语义 policy 与生成快照
分成独立步骤,前者不能通过刷新后者绕过。
- 任何所有权迁移必须同步更新:canonical 导入、`app/runtime/compat/manifest.py`
SDK 导出(若公开)、`docs/rules/05-architecture.md` 与上述架构测试。
- 延迟导入不被接受为隐藏循环依赖的手段。
@@ -695,17 +705,18 @@ flowchart LR
| 指标 | 当前值 |
|---|---:|
| Python 模块 | 835 |
| 内部导入边 | 6,810 |
| 内部导入边 | 6,817 |
| 非平凡 SCC | 2`ARCH-107` 临时 Chain 包根环;精确 containment 的 TMDB 移植包环) |
| Direct egress | 6612 条待迁移债务,54 条精确 containment |
| Module Contract V2 spec | 215(其中 214 个进入 `run_module` 观察面) |
| Event Contract | 53 |
| Event producer / consumer | 9998 静态、1 动态)/ 17(16 静态、1 动态) |
| Model/Oper 自动事务与自建 Session | 0 |
| 组合根外 `SystemConfigOper()` | 0 |
架构专项验证`tests/test_architecture_dependencies.py`
`tests/test_architecture_contract_baseline.py` 共同提供语义断言和快照比较;
`scripts/architecture/baseline.py --check-host` 只验证快照一致,不能替代依赖合理性审查。
架构专项验证分为两个 CI 投影:`Check event semantic policy` 先运行依赖、Adapter、出口和 Event
语义门禁,`Check host architecture snapshot` 再执行快照测试及一次
`scripts/architecture/baseline.py --check-host`。快照一致只说明事实未漂移,不能替代边界合理性审查。
本总览与本轮架构治理的关系如下:
+21 -20
View File
@@ -78,8 +78,8 @@ G-ARCH 只有在以下条件全部满足后才可完成:
| S0-L2.3 Adapter 直连事实 | `DELIVERED` | S0-L2.1 | `e1483e85d`:锁定 28 条原始 Adapter import 事实,远端 `0/0` |
| S0-L2.4 Adapter zero-growth | `DELIVERED` | S0-L2.3 | `2553226f3`:冻结 28 条直连及 owner,收缩/新增/stale policy 门禁生效,远端 `0/0` |
| S0-L2.4b HTTP/Egress 事实与政策 | `DELIVERED` | S0-L2.4 | `47f0de745``43d52a35b``8d602149f`:冻结 66 条出口事实,消除 CI 类型/覆盖率漂移;远端全绿且 `0/0` |
| S0-L2.5 Event consumer 识别 | `VERIFIED` | S0-L2.1 | consumer 只识别可静态证明的 EventManager 注册10 个同名方法动态误报归零;本地 6,459 passed / 6 skipped待推送 CI |
| S0-L2.6 事实源与 CI 投影 | `PLANNED` | S0-L2.2,S0-L2.4b,S0-L2.5 | fixture/policy/overview 职责固定,CI 分开报告语义 policy 与快照一致性 |
| S0-L2.5 Event consumer 识别 | `DELIVERED` | S0-L2.1 | `86157be2a`consumer 只识别可静态证明的 EventManager 注册;全量 6,459 passed / 6 skippedCI `33029645165`/`33029645254` 全绿,远端 `0/0` |
| S0-L2.6 事实源与 CI 投影 | `VERIFIED` | S0-L2.2,S0-L2.4b,S0-L2.5 | 99/17 条逐调用事实、17 条 consumer policy 与 CI 分层本地通过;全量 6,481 passed / 6 skipped,待推送 CI |
### S1:可靠性、事务与数据合同
@@ -136,7 +136,7 @@ G-ARCH 只有在以下条件全部满足后才可完成:
| S4-L2 Event strict contract | `PLANNED` | S0-L5,S1-L6 | 宿主事件输入/输出按风险 strict,诊断例外只属于第三方插件兼容 |
| S4-L3 Complexity v2 | `PLANNED` | S3 | 私有方法、class/file、圈复杂度进入门禁;所有超限通过职责拆分归零 |
| S4-L4 全量 mypy 清零 | `PLANNED` | S3,S4-L1,S4-L2 | `mypy-baseline.json` 归零并删除债务接受路径,全宿主 strict 类型通过 |
| S4-L5 Ruff 治理债务清零 | `PLANNED` | S3 | 当前受控 973 条诊断归零,规则集扩展经过独立审查且新增诊断为零 |
| S4-L5 Ruff 治理债务清零 | `PLANNED` | S3 | 当前受控 972 条诊断归零,规则集扩展经过独立审查且新增诊断为零 |
| S4-L6 Coverage/并发/质量证据 | `PLANNED` | S3,S4-L1,S4-L2 | 高风险包纳入 coverageraw concurrency 分类清零;Module Quality 有真实 evidence test |
### S5Plugin、Agent、Domain、Startup 与最终收口
@@ -154,48 +154,49 @@ G-ARCH 只有在以下条件全部满足后才可完成:
## 4. 当前活动叶子
### S0-L2.5 Event consumer 识别
### S0-L2.6 事实源与 CI 投影
**Status:** `VERIFIED`(本地验收完成,等待提交、推送和远端 CI 确认)
**Outcome**
Event consumer 从“末级方法名碰巧是 `register`/`add_event_listener`”收紧为可静态证明的
canonical `EventManager` receiver。16 个静态注册点保持不变;10 个 selector、SDK hook、Oper、
Model、TaskRegistry、`atexit` 和 EventManager 内部展开误报归零;配置驱动的 workflow 注册是唯一
真实动态 consumer。
统一 Event producer/consumer 的 AST 事实源,完整解析 positional/keyword 参数、别名、重绑定和
有限条件表达式。生成快照保存逐调用 line-free 事实及 multiplicityconsumer 由独立人工 policy
按 exact fingerprint set 准入,任何刷新快照的操作都不能自动接受新消费注册。
**Ownership**
- `scripts/architecture/event_consumers.py` 的 receiver/event provenance collector。
- `scripts/architecture/baseline.py` runtime event contract 集成与 diagnostics
- `tests/fixtures/architecture/runtime-contract-baseline.json` 的生成事实
- `tests/test_architecture_event_consumers.py` 的 alias、shadow、rebind、decorator 与动态边界测试。
- API listener 所有权规则、架构规范、优化清单与快速架构 CI 投影
- 本路线图的叶子状态和交付记录。
- `scripts/architecture/event_facts.py` 的统一 producer/consumer provenance collector。
- `scripts/architecture/event_policy.py` `runtime-contract-policy.json` 的只读人工 consumer policy
- `scripts/architecture/baseline.py` 的 runtime schema v3、迁移链、事实索引与 diagnostics
- producer/consumer、policy、baseline/CLI 和 CI 分层测试。
- 架构规范、总览、优化清单与本路线图的单一事实说明
**Excluded**
- 不修改生产 EventManager、事件 ABI、handler 执行顺序或插件消费者。
- 不把 `app/plugins/**` 副本纳入宿主扫描。
- producer 关键字/别名解析与 consumer 人工 zero-growth policy 留给 S0-L2.6 统一事实源叶;本叶只把
consumer 识别结果变为真实、完整且可测试的事实。
- 不使用源码行号、通配符或自动写入 policy 接受新 consumer。
**Acceptance**
```bash
.venv/bin/python -m pytest \
tests/test_architecture_event_consumers.py \
tests/test_architecture_event_facts.py \
tests/test_architecture_event_policy.py \
tests/test_architecture_dependencies.py \
tests/test_architecture_contract_baseline.py \
tests/test_architecture_baseline_cli.py \
tests/test_architecture_ci.py -q
.venv/bin/python scripts/architecture/event_policy.py
.venv/bin/python scripts/architecture/baseline.py --check-host --diagnostics
.venv/bin/python scripts/architecture/ruff_ratchet.py
.venv/bin/python scripts/architecture/mypy_ratchet.py
.venv/bin/pylint scripts/architecture/baseline.py \
scripts/architecture/event_consumers.py \
tests/test_architecture_event_consumers.py \
scripts/architecture/event_facts.py \
scripts/architecture/event_policy.py \
tests/test_architecture_event_facts.py \
tests/test_architecture_event_policy.py \
tests/test_architecture_dependencies.py \
tests/test_architecture_contract_baseline.py \
tests/test_architecture_baseline_cli.py \
@@ -205,5 +206,5 @@ git diff --check
**Delivery**
- 单一提交主题:以可证明的 EventManager provenance 替换同名方法扫描并清除全部动态误报
- 单一提交主题:统一 Event facts、锁定 consumer policy 并拆分 CI 语义/快照投影
- 推送 `origin/v3` 后确认提交祖先关系、远端 SHA 和 ahead/behind `0/0`
+15 -6
View File
@@ -569,16 +569,25 @@ removed in the same reviewed change so that it cannot return.
| `compat -> canonical implementation at module import time` | Forbidden |
| Any import that creates a module-level cycle | Forbidden; the complete host graph must match the exact reviewed SCC policy, and temporary debt must have a removal owner |
Event consumer facts require statically proven ownership. A `register` or
`add_event_listener` method name alone is not evidence: the receiver must resolve
to the canonical `app.runtime.events.eventmanager`, an `EventManager` instance,
or a finite alias of either. Unknown receivers are ignored. A proven manager with
an event value that cannot be resolved is recorded as a dynamic consumer; the
current host permits only the configuration-driven workflow registration. The
Event producer and consumer facts share `scripts/architecture/event_facts.py` as
their only collector. A send/register method name alone is not evidence: the
receiver must resolve to the canonical `app.runtime.events.eventmanager`, an
`EventManager` instance, or a proven injected Event publisher/manager port.
Unknown same-name receivers are ignored. Positional and keyword event arguments,
finite aliases and conditional enum choices must be resolved; only a proven
receiver whose event value remains unknowable may produce a dynamic fact. The
collector respects lexical shadowing and rebinds, distinguishes decorator
application from obtaining a decorator factory, and never scans `app/plugins/**`
as host code.
The generated runtime baseline preserves every line-free call fact and its
multiplicity. Consumer admission is separate and non-generated: every current
consumer fingerprint, owner, classification and concrete reason must exactly
match `runtime-contract-policy.json`. New, changed, duplicate, invalid and stale
consumer identities fail independently, and `--write-host` never modifies the
policy. The current host permits one dynamic consumer only: the configuration-
driven workflow registration.
## Key File Locations
| Path | Purpose |