# B 站博主动态采集辅助工具 创建人员:dev.developer.project.secondary / infodev-2 文件职责:说明 B 站博主动态采集重型本地协调工具的入口、输入输出、安全边界、运行方法和验收方法。 管理规范/模板:../../项目规范.md;../编码规范.md;../开发审计规范.md 引用文件:../../ai-video-downloader/draft/2026-08-04-哔哩哔哩动态采集需求.md;../../dev/project-dev/bili_dynamic_collector.py;../../dev/project-dev/bili_dynamic_collector.example.json;../开发事项总纲.md;../开发事项计划.md;../开发执行日志.md 记录方式:工具说明;公开 CLI、配置、状态、停止边界或验收结论变化时更新。 ## 1. 定位与边界 本工具是本地文件协调器,不是爬虫或浏览器机器人。它只读取人工或浏览器扩展已导出的无认证动态元数据,并完成以下工作: 1. 按博主和最近时间窗筛选动态。 2. 以动态 ID、opus ID、BV 号和规范化来源 URL 去重。 3. 生成待办 JSON,并维护逐事件 `manifest.jsonl`。 4. 生成保留发布时间的 Windows 安全文件名。 5. 按明确映射,把浏览器下载目录中已完成的视频安全移动到配置的视频目录。 6. 生成给 `case_analysis.media_processor` 的完整、内容寻址 Codex 原生任务 handoff envelope;工具本身不发送。 工具不联网,不调用 B 站接口,不读取浏览器配置,不控制浏览器或扩展,不添加、重试或下载扩展任务,也不发送 Codex 消息。登录、验证码、会员、DRM、访问控制和来源合法性不明均由人工停止处理。 ## 2. 文件入口 | 入口 | 路径 | |---|---| | CLI | `dev/project-dev/bili_dynamic_collector.py` | | 开发示例配置 | `dev/project-dev/bili_dynamic_collector.example.json` | | 测试 | `dev/project-dev/test/test_bili_dynamic_collector.py` | | 脱敏 fixture | `dev/project-dev/test/fixtures/bili_dynamic_collector/dynamic_items.json` | 示例配置故意只指向 `dev/project-dev/tmp/bili-dynamic-collector-mvp/`。项目管理员完成正式输出路径授权扩展前,不得把开发运行改到 `F:\video\<博主名>\` 或 `ana-data/news-<博主名>/`;本事项也不修改任何既有正式采集文件。 ## 3. 配置合同 配置为 strict UTF-8 JSON。相对路径按配置文件所在目录解析。 ```json { "schema_version": 1, "creator": { "name": "青枫浦上Q", "dynamic_url": "https://space.bilibili.com/1420210197/dynamic" }, "timezone": "Asia/Shanghai", "window_hours": 72, "minimum_complete_age_seconds": 30, "title_max_length": 48, "allowed_source_hosts": ["space.bilibili.com", "www.bilibili.com"], "allowed_video_extensions": [".mp4", ".mkv", ".mov", ".webm"], "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" }, "paths": { "state_dir": "tmp/bili-dynamic-collector-mvp/state", "download_dir": "tmp/bili-dynamic-collector-mvp/downloads", "video_dir": "tmp/bili-dynamic-collector-mvp/videos" } } ``` `native_handoff` 八个身份/路由字段全部必填,三个 thread ID 必须是小写 UUID。配置、导出、映射或历史 manifest 任意层级出现名称含 `password`、`cookie`、`token`、`secret`、`authorization`、`captcha`、`session`、密码、令牌、验证码或会话的字段时,三个命令都按 `E_SECRET_FIELD` 安全停止;只报告字段路径,不输出字段值,也不改写业务状态。 ## 4. 动态导出合同 工具接收人工或扩展导出的 JSON 清单,不接收 HTML、浏览器 profile 或认证材料。 ```json { "schema_version": 1, "creator": "青枫浦上Q", "items": [ { "dynamic_id": "123456", "opus_id": "987654", "bvid": null, "content_type": "text", "published_at": "2026-08-04T10:00:00+08:00", "title": "短标题", "source_url": "https://www.bilibili.com/opus/987654" } ] } ``` 合同要求: - `content_type` 只能是 `text`、`article`、`image`、`video`。 - `published_at` 必须带 UTC offset 或 `Z`。 - `dynamic_id`、`opus_id`、`bvid` 至少一项存在;`source_url` 必须存在且 host 已登记。 - 时间窗包含下界和当前时刻;窗口外和未来时间项只计数、不生成待办。 - 同一输入内或历史 manifest 中,只要任一稳定标识命中活动/成功实体,就不会重复生成待办。 - 最新状态为 `QUEUE_FAILED`、`MOVE_FAILED` 或 `PROCESSING_FAILED` 时可重试,并追加新事件,不覆盖旧事件。 ## 5. 检查与定时入口 ```powershell python dev/project-dev/bili_dynamic_collector.py ` --config dev/project-dev/bili_dynamic_collector.example.json ` check ` --input <无认证动态导出.json> ``` `check` 是供 Codex 定时任务调用的入口。stdout 始终是一行 JSON;有新增时状态为 `TODO_GENERATED` 并给出待办路径,无新增时为 `NO_NEW_ITEMS` 且不新建空待办文件。`--now <带 offset 的 ISO-8601>` 只用于可重复测试或由调度器显式注入权威时钟。 待办动作只描述人工下一步:文字/专栏导出完整 UTF-8 文本、图片下载原图、视频打开播放页并使用已安装扩展。工具不会替代这些交互。 ## 6. 命名与 manifest 文件 stem 为 `YYYYMMDD-HHmmss_<类型>_<短标题>`。标题会替换 Windows 非法字符、去除尾部空格/点、保护保留名并按配置截断;不同实体占用同一 stem 时依次加 `_01`、`_02`。图片待办另给出同一动态多图的 `_{sequence:02d}` 命名模式。 `state_dir/manifest.jsonl` 是 append-only 事件账。每行至少包含:博主、动态/opus/BV 标识、内容类型、发布时间、标题、来源 URL、稳定去重键、建议 stem、本地文件、SHA-256、采集时间、状态、失败原因、视频处理状态和文本路径。每行 JSON parse 后会先经过与配置/输入相同的递归秘密字段门禁,再读取 schema、身份或状态。写入采用同目录临时文件、flush/fsync 和原子替换;重跑不改写历史事件。 视频终态还执行状态感知证据校验:`VIDEO_MOVED`/`COMPLETE` 必须有无控制字符的非空 `local_file` 和 64 位小写十六进制 SHA-256;`VIDEO_MOVED_SOURCE_RETAINED` 还必须有非空、无控制字符的 `failure_reason`。缺失、空白或格式损坏统一以 `E_MANIFEST`/exit 3 停止,不投影成功且不改写任何业务文件。 MVP 状态主链为: ```text TODO_QUEUED -> MOVE_FAILED -> TODO_QUEUED(显式重试) -> VIDEO_MOVED / VIDEO_MOVED_SOURCE_RETAINED -> READY_FOR_HANDOFF(video_processing_status) ``` ## 7. 安全移动已完成视频 先由人工确认浏览器扩展任务已完成,再准备映射: ```json { "schema_version": 1, "items": [ { "source_file": "扩展下载结果.mp4", "bvid": "BV1xxxxxxxxxx" } ] } ``` `source_file` 相对路径按 `download_dir` 解析;也可给绝对路径,但解析后仍必须位于该目录。每项可同时提供动态 ID、opus ID、BV 号和来源 URL;每个已提供选择器都必须独立、唯一命中,且全部命中同一实体,未知或不一致的选择器不会被忽略。 ```powershell python dev/project-dev/bili_dynamic_collector.py ` --config <已授权配置.json> ` move-completed ` --mapping <已完成视频映射.json> ``` 移动采用两阶段全批预检。阶段 A 先解析全部选择器、拒绝重复实体并读取最新状态;只有最新状态精确为 `TODO_QUEUED` 或 `MOVE_FAILED` 的活动行进入阶段 B。处理阶段、缺失/空白或未知状态在复制前以 `E_STATUS` 停止。阶段 B 才检查临时下载后缀、完成宽限时间、视频后缀、下载根、链接/junction/reparse、物理同源和目标唯一性。任一预检失败时,整批不创建目标、不删除源、不追加 manifest。完整预检通过后的跨盘移动采用“目标目录隐藏 partial → flush/fsync → SHA-256 复验 → 同目录 hard-link no-overwrite 提交 → manifest → 删除源”的顺序;文件系统不支持安全 no-overwrite hard-link 时停止,不退化为可能覆盖的移动。 源删除失败时,目标和 SHA-256 仍可信,manifest 追加 `VIDEO_MOVED_SOURCE_RETAINED`,stdout exit 为 `4`,由人工核对并清理源副本。相同 mapping 重跑继续复用已记录的目标、hash 和失败原因并返回 exit 4;即使保留源后来被人工删除也不降级、不追加事件。正常 `VIDEO_MOVED` 重跑在源已删除后返回 `ALREADY_MOVED`/0。正式目标绝不覆盖。 ## 8. 生成视频处理员原生 handoff ```powershell python dev/project-dev/bili_dynamic_collector.py ` --config <已授权配置.json> ` handoff ``` 工具会先逐个确认视频仍在 `video_dir` 内、不是链接且 SHA-256 与 manifest 一致,然后在 `state_dir/handoffs/` 生成内容寻址的完整 `` Markdown。正文含精确 source/target AI、thread、role、reply thread、`scope/evidence/expected_action`,状态为 `PROCESSING_REQUESTED`;handoff ID 由路由和排序后的证据派生。同一路由/证据即使 `--now` 不同也得到相同 bytes 并返回 `REUSED`。stdout 始终 `sent=false`;文件生成不等于发送,仍由视频下载员通过 Codex 原生任务通信确认并发送。 生成前会验证全部 handoff 消费标量:内部 entity ID、可选 BV、canonical UTC 发布时间、标题、来源 URL、本地路径及 SHA-256。CR/LF、其他控制字符或格式异常均在文件创建前以 `E_MANIFEST` 停止;错误输出只含安全字段路径,不回显污染值。 ## 9. 退出码与停止处理 | exit | 含义 | |---|---| | `0` | 成功或无新增 | | `1` | 未预期内部失败,默认关闭 | | `2` | 输入/配置合同错误 | | `3` | 安全停止:路径、认证字段、重名、未完成、身份冲突等 | | `4` | 目标已安全提交,但源文件删除失败并保留 | | `130` | 用户中断 | 登录提示、验证码、付费墙、会员异常、DRM、访问控制、来源合法性不明和浏览器扩展任务异常不由本 CLI 处理;人工应停止并报告,不得向配置或输入补入认证秘密。 ## 10. 动态页主动刷新协调层 后继事项 `DEV-PROJECT-INFO-BILI-DYNAMIC-REFRESH-COLLECTOR-20260813-001` 的 runtime-v2 唯一产品入口为: ```powershell python -B dev/project-dev/bili_dynamic_collector.py ` --config dev/project-dev/bili_dynamic_collector.example.json ` refresh-run ``` `refresh-run` 由受信 controller 独占一次性密钥、deadline、action/evaluate dispatch、source-controlled extractor、evidence 签名与提交。其标准输入/输出是严格 JSONL 的 supported-Chrome 请求/响应协议;调用方不能读取 capability、注入自定义 adapter、手写 schema 3 evidence 或直接绑定成功终态。`refresh-begin`/`refresh-commit` 仅保留历史 schema 1/2 恢复边界,新 schema 3 run 不能从这两个公开入口创建。工具不读取、导出或持久化 Cookie、token、localStorage,也不提供 Raw CDP、坐标或截图降级。刷新身份固定为 `青枫浦上Q / 1420210197 / https://space.bilibili.com/1420210197/dynamic`,每个 run 最多刷新一次。 页面 observation 合同与只读提取脚本以精确 hash 绑定。只有页面身份、正文、72 小时时间窗与终止标记共同证明完整覆盖时,空结果才可成为 `REFRESH_CONFIRMED_NO_NEW`。公开搜索/API 的空结果、`-412`、验证码、访问限制和不可读页面不能证明 no-new,分别落入 `REFRESH_FAILED_PAGE_UNREADABLE`、`REFRESH_BLOCKED_AUTH_OR_ACCESS` 或 `PARTIAL_DISCOVERY_UNCONFIRMED`。发现通过合同的新正文、原图或视频 metadata 时才产生 `NEW_ITEMS_SAVED`。 state lock 使用稳定 kernel-backed 文件;正式 manifest 锁使用进程创建 identity、archive 同目录同卷 quarantine、generation 与 claim attempt。小时运行以 168 个 UTC slot 有界保存;state/formal 两份 manifest 通过 pending phase 和正式锁一致提交,锁、前像、路径、hash、并发或恢复有歧义时安全停止。 开发测试只重放脱敏状态:2026-08-13 既有一次 reload 后 15 秒页面边界和 8 秒只读 evaluate 超时,固定为 `REFRESH_FAILED_PAGE_UNREADABLE`;无凭据公开 endpoint 的 `code=-412` 仅作非权威 partial/access 附证。本实现没有再次刷新、联网、下载或写正式 `ana-data`。完整实现与验证证据见 `B站动态主动刷新采集器实现与验证证据.md`。 ## 11. 开发验证 ```powershell python -m py_compile dev/project-dev/bili_dynamic_collector.py python dev/project-dev/bili_dynamic_collector.py --help python -m unittest dev.project-dev.test.test_bili_dynamic_collector -v ``` 目标测试包含 13 个离线用例,覆盖选择器一致性、同源/同实体全批预检、三类终态重跑、六类禁止状态、terminal 缺失/空白/格式损坏矩阵、handoff 全标量控制字符/格式注入矩阵、manifest secret gate、规范 handoff 稳定性及原有窗口/命名/移动边界。目标 13/13、新鲜闭环 1/1、project 76/76 均 PASS;审核员另独立复跑原阻断反例 2/2、目标 13/13、project 76/76,限定实现复审结论为 `PASS/0`。测试只使用脱敏 JSON 和临时小字节文件,不联网、不启动浏览器、不读写 `F:\video\`、不读写正式 `ana-data`,也不复制博主正文或真实媒体。请求方已按 `HANDOFF-VIDEO-DOWNLOADER-INFODEV2-BILI-DYNAMIC-COLLECTOR-PASS0-ACK-20260805-001` 接收并关闭本开发事项;真实网络、浏览器、下载、自动 native send、每小时调度、`F:\video\青枫浦上Q` 和正式 `ana-data` 的既有授权由视频下载员按项目配置消费,本工具和本开发事项本身不构成这些动作的授权来源。 ## 12. 主动刷新小时采集器的失败关闭边界 主动刷新产品入口使用 `refresh-run`,并由产品级 `JsonLineSupportedChromeAdapter` 执行 controller→adapter→extractor→evidence→commit 单一路径。受信 key 只存在于 controller 进程内,pending 仅保存 SHA-256 commitment;产品模块不暴露签名/绑定 helper,任意调用方 adapter 在动作前以 `E_CONTROLLER_ADAPTER` 停止。可读页面的 observation cards 必须与提交 items 精确双向绑定;缺项、额外项、跨卡身份重叠或字段漂移均失败关闭,不能被当作无新增。历史目录会跨 schema 1/2 全量归并身份组件,并对状态优先级和 schema 2 artifact path/bytes/SHA-256 做严格验证。 每个小时只写一个不可变终态 slot;`latest.json` 只是精确八键索引,可从已验证 slot 重建。无正式变化也必须形成 `NO_FORMAL_CHANGE` receipt。崩溃恢复会验证 pending 阶段、证据身份、formal-lock generation/attempt、同卷 quarantine owner marker 和 durable slot/latest readback;歧义状态不会写正式 manifest。HOLD/4 修复后的离线目标测试为 collector 13 项 + refresh 17 项,共 `30/30 PASS`。本能力仍不构成 Chrome、网络、正式采集或计划任务的运行授权。 HOLD/3 修复进一步冻结:schema 2 UID 必须是精确 string,schema 1 数字 stable ID 只允许 1—32 位 ASCII;pending→STARTED 崩溃只重建同 run,slot 第三内容不覆盖,TERMINAL_RECORDED 必须重新绑定 planned/receipt/manifest/slot。formal-lock 隔离采用 pending intent 先行、no-overwrite rename、目录 durable flush,并能在主锁已先删除后继续 exact cleanup。当前离线目标回归为 `34/34 PASS`。 runtime HOLD/3 后继修复冻结:新 schema 3 pending 的原始 `deadline_at` 必须在任何 adapter/open-tabs/reload/goto/evaluate 调用前校验,过期即 `E_OVERALL_DEADLINE`,协议请求数与 evidence 文件数均为 0;deadline 不得重置。五态产品测试通过真实 `refresh-run` 子进程和严格 JSONL transcript 覆盖 new/empty/unparsed/identity/access,不使用 Fake adapter。当前 refresh=`39/39 PASS`,collector+refresh=`52/52 PASS`;正式 manifest 保持 `51598 bytes / 48 lines / 0E69AF2AF1C4B0014D2033A21F3986E283ECFD758EA652E7F2416006736C8D73`。 ## 13. V009 本机 unpacked 自用入口(覆盖第 10、12 节旧运行口径) 调用方 JSONL 不再是可信运行入口。`refresh-run` 现在固定在创建 pending 或读取 stdin 前返回 `E_TRUSTED_ADAPTER_REQUIRED/SAFETY_STOP/exit3`,并明确 `authoritative=false,saved=false,no_new=false`。任何公开 index、fixture、caller transcript、unpacked 自报 ID/hash/enabled 或旧 evidence 都不能形成保存或 no-new。 离线实现位于 `dev/project-dev/bili_dynamic_refresh_extension/` 与 `dev/project-dev/bili_dynamic_refresh_native_host/`。扩展是独立 cookie-free MV3,固定 ID=`gllihoanalkiollgpeggamfhajmmnmml`;Host=`com.project_info.bili_dynamic_refresh`,allowed origin 只有该 ID。源树、reviewer source approval、账户持有人在 Chrome 可见界面手工加载形成的 local-load approval、Host install receipt 和当前 Chrome 实例必须同时精确匹配。unpacked 路线是当前本机单用户的程序性信任,不等同于签名分发,也不声称抵御已控制本机账户的恶意进程。 当前只完成源代码与离线合成测试。没有正式 source/load approval,没有 build/install/HKCU,没有打开 Chrome 或加载扩展。永久不使用 CWS upload/publish、ExtensionInstallForcelist、ExtensionSettings 或 Chrome policy;后续 Host 注册若获批也必须是独立 CreateNew、默认不执行、可回滚门禁,且不得修改完整视频扩展/Host。 ## 14. 30 分钟持续归档协调器(覆盖旧“小时运行”调度口径) 当前周期入口为 `dev/project-dev/bili_half_hour_pipeline.py` 与受管配置 `dev/project-dev/bili_half_hour_pipeline.config.json`。固定 creator 是 `青枫浦上Q / 1420210197 / https://space.bilibili.com/1420210197/dynamic`,固定周期 30 分钟;协调器本身不读取 Cookie、Profile、localStorage 或网络载荷,而由已登录 Chrome 的受控运行层完成页面刷新和采集,再读取现有 append-only formal/handoff 事实。旧第 10—13 节的小时/V009 离线入口仍作为历史合同与兼容实现保留,不再表示当前调度频率。 每轮先 `initialize`(首次只建立当前 formal/handoff baseline),再 `begin`、页面刷新/采集、`reconcile`、outbox 分发与 `finish`。state 用非阻塞进程锁和确定性 half-hour slot 防重叠;cursor 同时冻结行数与完整前缀 SHA-256,历史重写、截断或并发漂移均 fail-closed。正文/原图形成 `GIT_DELIVERY_READY`,未完成视频形成 `VIDEO_DOWNLOAD_READY`,新的 processing `READY` 形成 `VIDEO_TRANSCRIPTION_READY`;转写 receipt 再形成 `MINUTES_READY` 与 transcript Git outbox,纪要 receipt 形成 minutes Git outbox。每个 outbox ID 同时绑定 source 与规范 payload;任何读取、派发、回执和 Git 消费都会从 exact formal/handoff/terminal 行重新投影并核对。角色状态严格单向为 `CREATED→DISPATCH_INTENT→OBSERVED→COMPLETE`,terminal 不得早于 observed,重启只恢复同一 envelope,不制造第二次 handoff。 输出布局保持:正文、原图、manifest、转写、纪要在 `ana-data/news-青枫浦上Q/`;MKV 和 `.download.json` 只在 `F:\video\青枫浦上Q\`。Git 发布拒绝预存 staged changes;受管根、完整祖先链和最终文件均须 ordinary/non-reparse、严格解析包含,并在读/hash/commit 边界复核身份。文件内容由安全句柄读入后直接写入 Git blob,再通过任务自有临时 index 形成 exact tree;共享 index、无关 dirty changes在所有异常/进程中断窗口保持不变。持久 `GIT_COMMIT_INTENT` 绑定 parent/tree/files/message/author/time,重启确定性恢复 non-force push;禁止 `git add .`、空提交、force push、原视频/mapping/秘密入库。已登记 job/config 是连续执行授权,正常心跳不得逐轮索权。 操作员可见状态区分:`RUN_STARTED/RUN_RESUMED`、`RECONCILED`、待分发 outbox、下游 terminal receipt、`RUN_COMPLETE/RUN_FAILED`。无新增仍须完成本轮并保留 cursor;有新增必须等相应 outbox/回执闭环,媒体已下载但后处理待补时继续沿分层结果报告,禁止重下载。 ### 14.1 统一用户可见命名与路径迁移 正文、专栏、原图、视频转写和纪要统一采用 `YYYYMMDD-HHMMSS___`。视频的 transcript/minutes 分别放在同 basename 的 `.transcript/`、`.minutes/` 目录,目录内文件也使用同 basename。标题按 Windows 规则确定性清洗并限制 64 字符;时间和标题只来自冻结的 formal/mapping,BVID 始终为末尾稳定 ID。MKV、`.download.json` 和 FLAC 仍使用 BVID 内部名,分别留在视频根和 `intermediate/transcription/`,永不进入 Git。 `plan-video-artifact-migration` 只读生成确定性迁移计划;`migrate-video-artifacts` 先 CreateNew 内容寻址 report,再逐文件验证字节/SHA、无覆盖迁移并建立 Git outbox。`artifact-relocations/.json` 是不可变别名账本,历史 formal、terminal、receipt 不重写;旧路径缺失时,resolver 只有在 old/new/bytes/SHA 唯一绑定后才读取新路径。公开文件迁移提交同时精确删除旧 Git 路径,不能把新旧副本并存。 ### 14.2 共享 index 诊断 `git-preflight` 是只读入口:冻结 `.git/index` 的字节、SHA 和文件身份,比较当前 HEAD 的 staged path,报告 archive 范围“staged delete + worktree present”以及 `.git/index.lock` 身份。无论成功、失败或中断,共享 index 必须字节与文件身份不变;工具不执行 reset、checkout、add 或 lock 清理。交付继续使用任务临时 `GIT_INDEX_FILE`,共享 index 的用户原始 staged 集不能因 HEAD 移动而被重新解释或改写。 ### 14.3 运行与恢复命令 ```powershell python -B dev/project-dev/bili_half_hour_pipeline.py --config dev/project-dev/bili_half_hour_pipeline.config.json git-preflight python -B dev/project-dev/bili_half_hour_pipeline.py --config dev/project-dev/bili_half_hour_pipeline.config.json plan-video-artifact-migration python -B dev/project-dev/bili_half_hour_pipeline.py --config dev/project-dev/bili_half_hour_pipeline.config.json migrate-video-artifacts ``` 半小时失败若没有可验证 Host terminal,必须记为失败而非 no-new。恢复时保持同一 task/owner/reviewer,先解决受信 Host/浏览器生命周期问题,再从最后 accepted cursor 补采;每个 slot 仍只刷新一次。Codex Desktop signed bundle 的 Stdio `write EOF` 竞态属于上游客户端边界,项目侧只移除 recurring node_repl/globalThis 触发并固定脱敏 `E_NATIVE_PEER_CLOSED`,不能宣称已永久修复客户端。 ## 15. Codex 桌面 stdio 触发隔离(覆盖第 14 节的交互浏览器操作口径) 半小时页面刷新不再由 Codex 自动化通过持久 `node_repl`、`globalThis` 标签页对象或交互 Chrome 工具执行。独立 cookie-free MV3 扩展使用 `chrome.alarms` 在半小时边界触发,并通过短生命周期 Native Messaging port 完成握手、唯一刷新、观察和 Host 提交;Codex 自动化仅处理源控协调器和下游 handoff,不拥有浏览器 transport。 每个槽位只创建一个 inactive 的任务自有标签页。标签页以 slot/lease 页面标记和 `chrome.storage.session` 记录双重绑定;跨 dynamic/opus 导航时,document-start content script 只能向本扩展 service worker 请求经 tab/window/URL/lease 重绑的标记,不能直接读取 session storage。Host commit 完成或失败/finally 后,扩展重新核对 tab/window/URL/标记,先退休持久所有权,再唯一执行一次 `tabs.remove`。已关闭是幂等成功;对象漂移、关闭失败只记录固定脱敏状态,禁止导航到 `about:blank`、禁止第二次 remove/update,也绝不关闭用户已有标签页或共享窗口。 Native port 在首次消息前安装 message/disconnect handler,写入按序执行;peer 在写前、写中、写后关闭统一为 `E_NATIVE_PEER_CLOSED`。Python Host 将单个 frame 合并为一次受互斥保护的写和一次 flush,只吞并确定的 pipe peer-close,其他 I/O 错误继续失败。连续 100 个槽位、service-worker 重启、同 ID 用户替换、关闭失败和 peer-close 竞态均由生产形状测试覆盖。 本节只消除本项目制造的重复 stdio/tab 触发,不能修改已签名 Codex 桌面客户端的主进程写竞态。自动化 `q` 必须保持暂停,直到源审 `PASS/0`、可验证部署以及受控客户端重启清除旧管道;若可信扩展/Host 尚未可验证,返回固定 `E_TRUSTED_ADAPTER_UNAVAILABLE`,不得回退到旧交互循环。脱敏复现和上游边界见 `Codex桌面Stdio写EOF脱敏复现与项目侧缓解.md`。 ## 16. 功能 Dynamic Native Host 1.1(待同链复审和部署) `bili_dynamic_refresh_extension` 1.1 与 `com.project_info.bili_dynamic_refresh` 不再使用固定 `E_TRUSTED_ADAPTER_UNAVAILABLE` stub。扩展在已登录的 allowlisted dynamic/opus 页面只读取可见 DOM:creator UID/name、规范页面身份、完整正文、公开原图候选、BVID、发布时间和页面可见时长;它不读取或传输 Cookie、token、signed URL、localStorage、Chrome Profile、验证码内容或原始网络记录。访问控制、owner 不匹配、未解析卡片、正文/原图未稳定、聚合 bundle 超过 900000 bytes 或字段含秘密形状时均 fail-closed,不能形成 no-new。 Host 通过 Native Messaging exact origin、reviewed local-unpacked source identity、slot/lease 和 HMAC 序列绑定一次 capture。公开原图仅允许无 query/fragment 的 `i0/i1/i2.hdslb.com/bfs/` 原图 URL,下载器禁用代理继承、限制重定向/字节/魔数,以 CreateNew 临时 intake 交给既有 `bili_article_image_collector.py`;正文/原图由该收集器按 formal append-only 保存,视频只追加规范 BVID metadata,再由 `bili_half_hour_pipeline.py` 投影现有 exact-once outbox。capture bundle 和原图 URL不进入 durable terminal;终端只保留 slot、run、capture SHA、数量和 outbox ID。相同 slot 的 durable terminal replay 在 Native/tab/page action 前返回。 构建/安装入口为 `bili_dynamic_refresh_native_host/build_host.ps1` 与 `install_native_host.ps1`。构建只接受固定离线 Python/PyInstaller 6.15.0/provision identity 和原 reviewer 一次合并 PASS receipt,精确绑定 extension、Host、article collector、half-hour pipeline 四套 source manifest;安装复用同一 PASS receipt,不再制造 build-hash 二次复审门。安装默认仅 ValidateOnly,`-Install` 只向缺失的版本目录 CreateNew 写 EXE、runtime config、native manifest、install receipt 与由可见 unpacked-load 事实机械生成的 identity facts,并在缺失的 `HKCU\Software\Google\Chrome\NativeMessagingHosts\com.project_info.bili_dynamic_refresh` 注册默认 String。扩展继续采用 Chrome 可见界面的本机 unpacked 加载,不使用 CWS、policy、Raw CDP 或 Profile 写入。 扩展安装/重载本身只恢复 durable slot 并建立 30 分钟 alarm,不会在 Host 身份材料尚未完成时抢先采集。一次真实部署回填通过扩展 action 的 `kickoff.html` 明确触发 `RUN_CURRENT_SLOT_REQUEST`;消息只能来自同扩展的精确页面 URL,并仍进入同一个 FIFO、durable STARTED/terminal 与 exact-once 协调器。它不是逐轮许可入口,日常后续轮次仍完全由 alarm 自动执行。 当前 source tests 已覆盖非空图文+原图+视频、零新增重放、malformed/secret/owner mismatch、Host 已许可重启拒绝、peer-close、100 槽位生命周期、受控当前时槽入口和任务自有 tab 关闭。此节记录的是待复审 source 状态;在原 reviewer `PASS/0`、一次受控 build/install/reload、真实从最后 accepted cursor 回补并得到当前 `RUN_COMPLETE` 前,`q` 仍保持暂停。项目侧部署只能移除高频触发,不能永久修复已签名 Codex Desktop 的主进程 pending WriteWrap/EOF 竞态。 ### 16.1 Native Host 预终态脱敏诊断 Host 在正式 run/outbox/terminal journal 之前另有固定 schema 的 append-only phase ledger,用于区分进程入口、运行配置、本地身份、Chrome launcher、首帧、maintenance、slot HELLO、durable replay/resume、session 与 durable handoff 边界。每行只包含 UTC 100ns 时间、build/session nonce 的 SHA-256、固定 phase、固定错误码,以及 PID/进程创建 FILETIME;禁止写入页面 URL/标题/正文/capture payload、Cookie/header/token/signed URL、localStorage/Profile、命令行、原始异常或任意调用方字符串。未知错误一律收敛为 `E_HOST_INTERNAL`。 ledger 位于已审核 Host 的唯一版本安装根、与 `runtime-config.json` 同目录,因而在配置解析失败前仍可用。该版本稳定态只拥有一个固定 `preterminal-phase-ledger-v1.jsonl` 和一个固定 lock;JSONL 最多 131072 bytes、512 条诊断事件,达到任一上限后停止追加并固定返回 `CAPACITY_REACHED`,已有字节 mutation0,业务 run 继续执行;永不轮转为多段,也不删除、替换、覆盖正式 ledger。首次初始化使用唯一固定 `.initializing` CreateNew 暂存对象:Windows 内核在 OWNER 首记录 durable retain 前负责 crash-close 自动回收;OWNER 首记录严格绑定当前 build/session hash 与该 held 文件的 volume/file identity,fsync 后才取消自动回收,再以 no-replace 原子发布为正式 ledger。若崩溃发生在 retain 后、发布前,重启只恢复 OWNER 记录与当前暂存对象身份完全一致的对象;若发生在发布后、lock owner receipt 完成前,重启只接受正式 ledger 的同一 OWNER 身份,并只修复空或其 canonical receipt 前缀。未知暂存、目标、receipt 或身份漂移保持原字节并失败关闭。初始化成功后 `.initializing` 必须不存在。两个受信 Chrome parent 通过同一 held lock 串行,Host 生命周期内持续持有根目录、lock 与 JSONL 句柄;每行 append 后立即 flush/fsync,并从同一 held handle 精确读回完整前缀和新行,不能只比较路径或大小。锁内先验证 OWNER 首记录和全部 canonical JSONL;仅允许截除 OWNER 后没有终止换行的撕裂事件尾部,完整但非法的行一律失败关闭,随后才可 append。任何 trace、回读、对象身份、`PROCESS_EXIT` 或 close 失败均由最外层非递归边界固定收敛为 `HOST_FATAL/E_PRETERMINAL_LEDGER`,不输出原始异常;maintenance/replay/session 的写端 peer-close 和 ACK 前 EOF则保留 `PROCESS_EXIT/E_NATIVE_PEER_CLOSED`,不得被误写为 `OK` 或 `E_PROTOCOL`。该 ledger 只用于诊断早退,不替代 formal terminal,也不能把失败解释为 no-new。