创建人员:dev.developer.project.secondary / infodev-2
文件职责:冻结 DEV-PROJECT-INFO-BILI-DYNAMIC-COLLECTOR-MVP-20260804-001 首次实现审核 HOLD/5 的最小重型修复合同、失败语义和验收矩阵。
管理规范/模板:../../../../common/dev-doc/编码规范.md;../../编码规范.md;../../开发审计规范.md
引用文件:../../../ai-video-downloader/draft/2026-08-04-哔哩哔哩动态采集需求.md;../B站博主动态采集辅助工具.md;../../开发审计报告.md;../../../dev/project-dev/bili_dynamic_collector.py;../../../dev/project-dev/test/test_bili_dynamic_collector.py
记录方式:重型编码方案;方案独立审核 PASS 后才允许修改实现与测试。
DEV-PROJECT-INFO-BILI-DYNAMIC-COLLECTOR-MVP-20260804-001。HOLD/5,不增加新功能。DEV-AUDIT-PROJECT-INFO-BILI-DYNAMIC-COLLECTOR-MVP-IMPLEMENTATION-20260804-001=HOLD/5。dev.developer.project.secondary / infodev-2。dev.reviewer.project / inforev。bili_dynamic_collector.py、示例配置和 test_bili_dynamic_collector.py;不拆包、不增加运行模块。F:\video、正式 ana-data 均为 0。本修复从现在起按重型事项处理,原因是现有工具包含三个持久阶段、跨调用 manifest 状态、互斥、重跑和复制/提交/删除事务。当前代码继续视为 TERMINAL_HOLD_DEVELOPMENT_SNAPSHOT_ONLY;本方案 PASS 前不得修改代码或测试。
以下已通过合同不重开:
check / move-completed / handoff 三个公开 CLI 名称、全局 --config 和可选 --now。video_dir 且实物 SHA-256 与 manifest 一致。仍为单一 stdlib 模块,内部职责按函数分层:
| 内部层 | 职责 | 本轮变化 |
|---|---|---|
| JSON/manifest 输入层 | strict UTF-8、JSON、秘密字段、schema | manifest 事件补统一秘密 gate |
| identity/preflight 层 | selector→entity、source→target、批次唯一性 | mapping 改为所有已提供 selector 必须命中同一实体;同源批次拒绝 |
| move 状态/事务层 | 预检、copy/commit、manifest、源删除、重跑 | retained-source 重跑保持 exit 4;预检错误零副作用 |
| native handoff 层 | 路由、证据、完整 envelope、内容寻址输出 | 配置化精确 source/target 身份并输出 canonical envelope |
状态只修一个既有分支:
VIDEO_MOVED_SOURCE_RETAINED
-- 相同 mapping 重跑 --> VIDEO_MOVED_SOURCE_RETAINED / exit 4
不新增“自动清理源”或“自动关闭告警”功能。源副本即使被人工删除,也不由本工具猜测为已解决;未来如需显式确认,另立需求,本轮保持告警粘性。
mapping 每项允许提供 dynamic_id、opus_id、bvid、source_url 中的一项或多项。与 check 的“新实体发现”不同,move mapping 是既有实体的精确引用:
SAFETY_STOP,不忽略错误 selector。source_url 必须走与动态导出相同的 HTTPS、无凭据、registered-host 规范化。现有宽松 resolve_entity() 保留给 check;新增/改造的 exact resolver 只供 move mapping 调用,避免改变已通过的新增实体去重语义。
每项预检解析源文件后形成 source identity:优先使用 (st_dev, st_ino);若平台 inode 为 0,则使用 normcase(resolve(strict=True))。在任何复制、目标创建或 manifest 追加前,对整个 mapping 批次检查:
因此删除当前“预检异常也追加 MOVE_FAILED”的行为。MOVE_FAILED 只记录批次已完整通过预检后,某项在 copy/hash/commit/manifest 的运行期失败;批次运行期仍按顺序记录逐项终态,不承诺跨多个大文件的全批次回滚。
preflight_move() 对最新状态的处理冻结为:
| 最新状态 | 本次结果 | exit |
|---|---|---|
VIDEO_MOVED / COMPLETE |
ALREADY_MOVED |
0 |
VIDEO_MOVED_SOURCE_RETAINED |
VIDEO_MOVED_SOURCE_RETAINED,保留原 local_file/hash/failure_reason |
4 |
TODO_QUEUED / MOVE_FAILED |
执行正常预检与移动 | 0/3/4 |
| 其他 | 安全停止 | 3 |
汇总只要包含一个 retained-source 结果,状态必须是 COMPLETE_WITH_RETAINED_SOURCE、exit 4。相同 mapping 重跑不得写新目标、不得删除/改写现有目标、不得把状态降成 ALREADY_MOVED 或 exit 0,也不追加重复 manifest 事件。
load_manifest() 每行完成 JSON parse 后、读取 schema/identity/状态前,必须对完整事件递归调用现有 reject_secret_keys(event, "$manifest[line]")。任何层级字段名命中 password/passwd/cookie/token/secret/authorization/captcha/session 或中文对应词,三个命令都通过共同入口得到 SAFETY_STOP/E_SECRET_FIELD。
门禁不打印字段值,只返回字段路径。manifest bytes 不改写,state/queue/target/handoff 均无新增。
示例配置新增必需对象 native_handoff:
{
"project_id": "project-info",
"source_ai_id": "video-downloader",
"source_thread_id": "019fcc5d-798f-7ea1-8325-3a4d1f2dc5a5",
"source_role_instance_id": "case_analysis.video_downloader",
"target_ai_id": "media-processor",
"target_thread_id": "019fb7a4-bdfd-79f2-bd6b-e67e2b7d8efd",
"target_role_instance_id": "case_analysis.media_processor",
"reply_thread_id": "019fcc5d-798f-7ea1-8325-3a4d1f2dc5a5"
}
所有字段为非空字符串;thread ID 必须为小写十六进制 UUID 形式;source/target/reply 三个精确线程与 AI 身份来自当前项目配置快照。该对象不包含认证秘密。工具仍不发送消息。
输出必须是可直接作为一次精确目标消息发送的完整 <codex_native_handoff>:
project_id、message_type=video_processing_request、handoff_id。reply_thread_id。status=PROCESSING_REQUESTED。scope:、evidence:、expected_action: 区段。handoff_id 由 route + 排序后视频身份/路径/hash 的 canonical JSON SHA-256 派生,格式 HANDOFF-BILI-DYNAMIC-VIDEO-PROCESSING-<24位大写HEX>。generated_at,因此 ready 集合与 route 不变时,不同 --now 重跑仍得到同一 handoff ID、同一 bytes 和 REUSED。sent=false;生成文件不是自动发送证据。继续复用 exit:0 成功、2 输入合同、3 安全停止、4 retained source、130 中断。新增或细化错误码:
| 错误码 | 条件 | 文件/manifest 副作用 |
|---|---|---|
E_MAPPING_SELECTOR_UNKNOWN |
任一 selector 未命中 | 0 |
E_MAPPING_SELECTOR_CONFLICT |
selectors 指向不同/多个实体 | 0 |
E_SOURCE_DUPLICATE |
批次 source identity 重复 | 0 |
E_ENTITY_DUPLICATE |
批次实体重复 | 0 |
E_SECRET_FIELD |
manifest 任意层级秘密字段 | 0 |
E_CONFIG |
native_handoff 缺字段/格式错误 | 0 |
原有 E_TARGET_CONFLICT、下载完成/范围/重名、hash、路径和 atomic create 错误继续有效。
E_MAPPING_SELECTOR_UNKNOWN;源、目标、manifest bytes 不变。E_MAPPING_SELECTOR_CONFLICT;零副作用。E_SOURCE_DUPLICATE;零副作用。E_ENTITY_DUPLICATE;零副作用。cookie/token:check、move、handoff 共同入口安全停止,字段值不出现在 stdout,state/目标不变。scope/evidence/expected_action 存在;route/ready 相同但 --now 不同仍同一 ID、同一 bytes、REUSED。dev/project-dev/test project flat discover;不运行网络、浏览器、下载、真实媒体或正式路径。dev/tmp/bili-dynamic-collector-mvp/ HOLD 快照。不新增网络请求、B 站 API、浏览器/扩展控制、下载器、登录态、cookie/token、数据库、服务、GUI、自动调度、自动 native send、自动源清理、F:\video 写入或正式 ana-data 写入。不处理前序 HOLD/5 之外的新需求或非阻断建议。
V001 限定方案审核 PASS
-> 修改现有代码/配置/测试/说明
-> L0 + 聚焦反例 + 新鲜 tmp smoke + project 回归
-> 冻结证据
-> 同一 dev.reviewer.project 一次限定实现复审
-> PASS/0 或明确剩余阻断回传请求方
V001 未取得独立 PASS 前,代码、配置、测试和 fixture 继续冻结;只允许本设计、目录入口、账本和审核通信变化。