mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-01 21:07:01 +08:00
docs(subscribe): record governance acceptance evidence
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# MoviePilot 订阅执行治理
|
||||
|
||||
> 状态:`active(2026-09-01 已由 MoviePilot v3 接管)`
|
||||
> 当前叶:`SUB-GOV-003B`
|
||||
> 状态:`completed(2026-09-01)`
|
||||
> 当前叶:`none(全部叶已完成或按条件停止)`
|
||||
> 迁移来源:`V3-RDY-009A`、`V3-RDY-009A1`、`V3-RDY-009A1B`、
|
||||
> `V3-RDY-009A1C`、`V3-RDY-009A1D`
|
||||
> 适用范围:V3 订阅刷新、匹配、兜底搜索、下载提交与用户状态闭环
|
||||
@@ -236,11 +236,11 @@ RSS/Spider 保持现有逐站点串行刷新,不通过并发化换取几秒收
|
||||
| `SUB-GOV-002B` | 为兜底搜索建立每站点并发、间隔和错误冷却预算;RSS/Spider 保持基线压力 | 002A | `completed(2026-09-01)` |
|
||||
| `SUB-GOV-002C` | 联合运行日常 Match、手工搜索和兜底搜索,验证公平性、锁等待、站点压力和失败恢复 | 001D, 002B | `completed(2026-09-01)` |
|
||||
| `SUB-GOV-003A` | 建立跨入口的订阅级下载幂等、下载器不确定终态和取消补偿 | 001C, 002A | `completed(2026-09-01)` |
|
||||
| `SUB-GOV-003B` | 后端提供订阅及批次业务状态,前端展示排队、匹配、搜索、提交、完成、失败和取消 | 002A, 003A | `in_progress` |
|
||||
| `SUB-GOV-004` | 多条订阅记录指向同一媒体时的跨记录季集去重和产品规则 | 003A | `pending(最低优先级)` |
|
||||
| `SUB-GOV-003B` | 后端提供订阅及批次业务状态,前端展示排队、匹配、搜索、提交、完成、失败和取消 | 002A, 003A | `completed(2026-09-01)` |
|
||||
| `SUB-GOV-004` | 多条订阅记录指向同一媒体时的跨记录季集去重和产品规则 | 003A | `completed(2026-09-01)` |
|
||||
|
||||
当前只激活 `SUB-GOV-003B`。001B–D 是日常 Match 主线;002A–C 是 24 小时兜底搜索主线;003A/B
|
||||
只有在前两条链路的身份和终态稳定后实施。每个叶子完成验收并更新本表后,才激活下一个满足依赖的叶子。
|
||||
全部行为叶已完成;条件叶 `SUB-GOV-001E` 因前置治理已消除可重复的本地等待热点而未激活。001B–D 是
|
||||
日常 Match 主线;002A–C 是 24 小时兜底搜索主线;003A/B 和 004 在身份、终态与提交幂等稳定后完成。
|
||||
|
||||
### 6.1 SUB-GOV-001A 验收证据
|
||||
|
||||
@@ -360,6 +360,45 @@ RSS/Spider 保持现有逐站点串行刷新,不通过并发化换取几秒收
|
||||
- 验证:下载幂等、取消补偿、订阅提交与搜索治理 `131 passed`;架构合同、依赖、复杂度和质量 ratchet
|
||||
`284 passed`;Ruff 低水位 `568`、mypy 低水位 `9,589`,Host 架构基线与 `git diff --check` 通过。
|
||||
|
||||
### 6.10 SUB-GOV-003B 验收证据
|
||||
|
||||
- 后端新增持久订阅执行状态,将搜索任务、站点预算等待和下载提交账本统一映射为稳定业务状态;订阅列表返回
|
||||
`execution_status`,批次列表、批次详情和取消使用独立执行端点,并按可访问订阅集合校验批次权限;
|
||||
- 状态覆盖 queued、matching、searching、waiting_site_budget、preparing、submitting、accepted、retryable、
|
||||
reconcile_required、failed、cancelling、cancelled 和 completed,且只在真实可取消边界暴露取消能力;
|
||||
- Alembic `f3c8a1d6b2e9` 持久化任务阶段和状态,旧数据库升级后可继续恢复队列;最终迁移链唯一 head 为
|
||||
`a7d9e2c4f6b1`;
|
||||
- 前端提交 `2cb37c6f` 在订阅卡片展示稳定状态和安全错误详情,在列表顶部展示批次进度与取消入口;轮询只在页面
|
||||
活跃且存在活动执行时运行,终态自动停止;中文简体、中文繁体和英文文案同步;
|
||||
- 前端验证:`yarn typecheck`、`yarn eslint . --max-warnings=0`、卡片/列表专项 `74 passed`、`yarn build`
|
||||
通过;使用真实组件和模拟 API 在桌面及 `390x844` 视口检查批次横幅、进度、取消按钮和三类卡片状态,未见
|
||||
重叠、截断或容器溢出。
|
||||
|
||||
### 6.11 SUB-GOV-004 验收证据
|
||||
|
||||
- 下载准入在单条订阅幂等键之外增加 canonical 媒体范围身份;同媒体来源、媒体 ID、类型、季集覆盖和下载模式
|
||||
相同的多条订阅记录共享跨记录防重事实,不依赖标题字符串碰撞;
|
||||
- 第一条记录已成功时,第二条记录复用成功结果而不再次调用下载器;第一条处于提交中或结果不确定时,第二条
|
||||
被冻结等待现有事实,不把不确定结果误判成可重试;
|
||||
- 普通与洗版、不同季、不同集范围或不同媒体身份继续保持独立,避免跨记录治理改变合法下载产品规则;迁移
|
||||
`a7d9e2c4f6b1` 增加可查询媒体范围键并保持 SQLite/PostgreSQL 可逆升级;
|
||||
- `tests/test_subscription_download_governance.py` 覆盖跨订阅行成功复用、活动提交阻断、不确定终态和范围隔离;
|
||||
全量后端测试与架构门禁共同验证旧插件 ABI 和单条订阅行为未回退。
|
||||
|
||||
### 6.12 最终规模与全局验收证据
|
||||
|
||||
- 固定受控矩阵和原始证据位于 `docs/refactor/subscription-governance-scale-2026-09-01.json`,对应规模测试提交
|
||||
`93d0631da`;环境为 macOS、Python 3.14.3 和隔离 SQLite,真实外部请求与真实下载均为 0;
|
||||
- 中档为 100 条订阅、10 个站点、1000 候选、100 个唯一媒体:候选检查从基线 100000 次降为 1000 次,命中
|
||||
集合相同,事实加载 100 次;首个匹配本地耗时 0.475ms,批次本地耗时 49.783ms;
|
||||
- 大档为 200 条订阅、20 个站点、1200 候选、100 个唯一媒体:候选检查从基线 240000 次降为 2400 次,命中
|
||||
集合相同,事实加载 100 次并命中租约 100 次;首个匹配本地耗时 0.55ms,批次本地耗时 113.729ms;
|
||||
- 两档持久队列均全部完成,每站点峰值在途为 1;重复站点租约和冷却认领均被阻止,任务/站点租约过期后可恢复,
|
||||
取消批次进入终态,重叠下载只有一次模拟外部提交,重复提交为 0,不确定下载保持冻结;
|
||||
- 上述耗时只衡量进程内路由和隔离 SQLite 队列,不是生产网络 SLO。最终后端全量验证为
|
||||
`7712 passed, 9 skipped`;mypy 低水位 `9,588`、Ruff 低水位 `567`,复杂度、依赖、运行时合同、Host
|
||||
架构基线、API surface、schema exports、数据库技能文档和 `git diff --check` 全部通过。
|
||||
|
||||
## 7. 上线前验证与验收
|
||||
|
||||
### 7.1 场景
|
||||
|
||||
112
docs/refactor/subscription-governance-scale-2026-09-01.json
Normal file
112
docs/refactor/subscription-governance-scale-2026-09-01.json
Normal file
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"durable_cases": [
|
||||
{
|
||||
"cancel_requested": true,
|
||||
"cancelled_batch_state": "cancelled",
|
||||
"cancelled_task_count": 10,
|
||||
"cooldown_claims_blocked": 10,
|
||||
"duplicate_download_reused_success": true,
|
||||
"duplicate_download_submissions": 0,
|
||||
"duplicate_site_claims_blocked": 10,
|
||||
"external_download_submissions": 1,
|
||||
"first_task_completion_local_ms": 24.648,
|
||||
"name": "medium",
|
||||
"queue_created_count": 100,
|
||||
"queue_finished_count": 100,
|
||||
"queue_state": "completed",
|
||||
"recovery_batch_id_stable": true,
|
||||
"running_task_cancelled": true,
|
||||
"site_count": 10,
|
||||
"site_peak_inflight_per_site": 1,
|
||||
"site_recovered_after_lease_expiry": true,
|
||||
"task_completion_local_p50_ms": 147.463,
|
||||
"task_completion_local_p95_ms": 258.077,
|
||||
"task_recovered_after_lease_expiry": true,
|
||||
"uncertain_download_frozen": true
|
||||
},
|
||||
{
|
||||
"cancel_requested": true,
|
||||
"cancelled_batch_state": "cancelled",
|
||||
"cancelled_task_count": 10,
|
||||
"cooldown_claims_blocked": 20,
|
||||
"duplicate_download_reused_success": true,
|
||||
"duplicate_download_submissions": 0,
|
||||
"duplicate_site_claims_blocked": 20,
|
||||
"external_download_submissions": 1,
|
||||
"first_task_completion_local_ms": 36.372,
|
||||
"name": "large",
|
||||
"queue_created_count": 200,
|
||||
"queue_finished_count": 200,
|
||||
"queue_state": "completed",
|
||||
"recovery_batch_id_stable": true,
|
||||
"running_task_cancelled": true,
|
||||
"site_count": 20,
|
||||
"site_peak_inflight_per_site": 1,
|
||||
"site_recovered_after_lease_expiry": true,
|
||||
"task_completion_local_p50_ms": 291.708,
|
||||
"task_completion_local_p95_ms": 510.505,
|
||||
"task_recovered_after_lease_expiry": true,
|
||||
"uncertain_download_frozen": true
|
||||
}
|
||||
],
|
||||
"environment": {
|
||||
"database": "SQLite isolated temporary files",
|
||||
"external_downloads": 0,
|
||||
"external_requests": 0,
|
||||
"platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O",
|
||||
"python": "3.14.3"
|
||||
},
|
||||
"gates": {
|
||||
"cancellation_terminal": true,
|
||||
"candidate_product_avoided": true,
|
||||
"download_idempotent": true,
|
||||
"fresh_facts_bounded_by_unique_media": true,
|
||||
"match_sets_equal": true,
|
||||
"queue_completed": true,
|
||||
"site_pressure_bounded": true,
|
||||
"task_and_site_recovery": true
|
||||
},
|
||||
"generated_at": "2026-09-01T07:35:04+00:00",
|
||||
"match_cases": [
|
||||
{
|
||||
"baseline_comparisons": 100000,
|
||||
"batch_local_ms": 49.783,
|
||||
"candidate_check_reduction_percent": 99.0,
|
||||
"candidate_checks": 1000,
|
||||
"candidate_count": 1000,
|
||||
"fact_hits": 0,
|
||||
"fact_loads": 100,
|
||||
"first_match_local_ms": 0.475,
|
||||
"match_sets_equal": true,
|
||||
"media_count": 100,
|
||||
"name": "medium",
|
||||
"routed_candidates": 1000,
|
||||
"site_count": 10,
|
||||
"subscription_completion_local_p50_ms": 22.854,
|
||||
"subscription_completion_local_p95_ms": 43.688,
|
||||
"subscription_count": 100
|
||||
},
|
||||
{
|
||||
"baseline_comparisons": 240000,
|
||||
"batch_local_ms": 113.729,
|
||||
"candidate_check_reduction_percent": 99.0,
|
||||
"candidate_checks": 2400,
|
||||
"candidate_count": 1200,
|
||||
"fact_hits": 100,
|
||||
"fact_loads": 100,
|
||||
"first_match_local_ms": 0.55,
|
||||
"match_sets_equal": true,
|
||||
"media_count": 100,
|
||||
"name": "large",
|
||||
"routed_candidates": 2400,
|
||||
"site_count": 20,
|
||||
"subscription_completion_local_p50_ms": 53.507,
|
||||
"subscription_completion_local_p95_ms": 102.284,
|
||||
"subscription_count": 200
|
||||
}
|
||||
],
|
||||
"method": "Deterministic local controlled sample. Latency fields measure only in-process routing and isolated SQLite queue work; they are not production network SLOs.",
|
||||
"passed": true,
|
||||
"revision": "93d0631da33a9b78137875e9deee0b3c8a660846",
|
||||
"schema_version": 1
|
||||
}
|
||||
Reference in New Issue
Block a user