Files
creator-hub/docs/python-gateway-branch-review.md

478 lines
46 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Python 网关分支审查与修正清单
日期:2026-09-13。状态:**历史分支审查已完成;本文保留迁移前 `cmd/docker_gateway`、Docker wrapper 和容器生命周期路径作为问题证据,不是当前生产入口。当前入口与状态以 `browser_gateway`、部署说明和 native 验证记录为准。**
## 1. 范围、结论与证据边界
- 审查分支:`feat/python-gateway-douyin`
- 基线:`origin/main@1fbf126b21a7d6de45ade70121785075542363d9`
- 分支提交:`860ecaf7ead08bbd1af2c0d8af3604270da78095`,另包含当前未提交及未跟踪文件。不是只审查最近一次提交或 dirty diff。
- 审查前已获取远程信息;远程主线是当前分支祖先,没有待合入的主线提交。当前分支没有配置 upstream。
- 范围包含 58 个已跟踪差异文件,以及 Python 网关、事件监听、依赖锁文件等 11 个未跟踪新增文件;本报告是另行新增的交付物。
- 四个独立只读审查分别覆盖网关迁移、平台动作与事件、数据业务与采集、前端与文档;汇总时合并了重复发现,并复核关键调用链。
- **本轮修正已覆盖网关、事件、动作、采集计划、账号一致性和前端确定缺陷,并同步了部署/计划文档;未重置数据库、未提交 Git,也未进行真实平台写操作。** 下列“已修正”条目以当前代码与本地回归证据为准;真实平台、供应商和浏览器视觉证据仍不能由本地测试替代。
### 1.1 总结
最紧急的问题不是缺少几个页面,而是已有路径会产生错误结果:
1. **没有可靠的新旧事件边界**,此前未记录的旧通知可能在重连后触发自动发送。
2. **并发创建可能误删成功容器**,超时的浏览器脚本也可能在锁释放后继续写入。
3. **代理环境不能正常走通**Go 漏传代理标识,HTTP/HTTPS 上游代理握手又存在协议解析错误。
4. **常用页面不可用或串数据**:设置保存返回 400,工作台切换可崩溃,账号迟返请求可能覆盖另一账号资料。
5. **人工发送链路不完整**:内部 ID 未转平台 ID、刷新后丢失原操作标识、失败与不明分类及证据不足。
6. **采集计划、账号状态、租约和凭据更新存在数据一致性问题**
7. **媒体、生产 AI、私信接收/历史及页面推送仍有实际接线缺口**,不是仅等待真机验收。
### 1.2 优先级及状态口径
| 标记 | 含义 |
| --- | --- |
| P0 | 可能造成未获批准的真实平台写入;相应自动能力在修正并验证前不可视为可用 |
| P1 | 阻断核心功能,或可能误删、串号、丢事件、错误判定;合并前应解决 |
| P2 | 局部正确性、边界、错误反馈或可维护性问题;仍须列入修正,不默默排除 |
| 确定缺陷 | 由当前代码及调用链可推导触发场景;除特别说明外,本轮没有运行复现 |
| 实现缺口 | 缺少实际执行或界面接线,不能用模型、状态表或 Mock 结果替代 |
| 待真机验证 | 代码之外的平台行为和运行证据不足,不据此断言平台支持或不支持 |
GW01—GW11、EV01—EV14、BE01—BE19、UI01—UI09 和 T01 的代码修正已完成并有本地回归;T02、DOC01—DOC06 已按当前证据修正。GAP01—GAP10 已补齐可由本地代码完成的执行、持久化和界面闭环,但真实平台、供应商质量、浏览器视觉及小红书能力仍需外部证据;GAP11 仍明确阻断。P0 的安全门仍有效:边界不明或结果不明不得自动写入或重试。
## 2. 网关生命周期与迁移缺陷
### GW01 · P1:并发创建可能删除另一个请求成功创建的容器
- **证据与影响:**`cmd/docker_gateway/gateway.py:170-177,241-252,322-366`。两个相同 alias、binding_version 的停止态创建请求都在锁外查到不存在;第二个获得锁后未重查,Docker create 返回 409,清理函数在 `created_id` 为空时按相同标签删除第一个请求的容器。持久卷不会因此删除,但已成功创建的容器会消失。旧 Go 实现在锁内再次检查 alias。
- **修正:**锁内重查;明确的创建冲突不能进入“本次创建结果未知”的删除路径。所有清理必须证明资源属于本次操作。
- **验收:**线程屏障制造该竞争;仅一个创建成功,另一个 409,成功容器没有收到 DELETE。
### GW02 · P1HTTP/HTTPS 上游代理 CONNECT 残留响应头破坏 TLS
- **证据与影响:**`cmd/docker_gateway/proxy.py:366-380,477-491,511-523``_read_status` 只读状态行,剩余响应头和空行随后作为隧道数据转发给浏览器。上游返回 200 不代表 HTTPS 可以使用。旧 Go 使用完整 HTTP 响应解析。
- **修正:**完整解析并消费响应头,限制大小和时间,保留响应头之后已经到达的真实隧道数据。优先采用成熟解析能力,不继续增加不完整手写分支。
- **验收:**无附加头、多头、分片头、头与首个隧道包同批到达;浏览器收到的首字节必须是真实隧道数据。现有测试只检查 socket 和认证头,不足以证明转发正确。
### GW03 · P1:停止可被空闲连接无限阻塞,45 秒宽限不等于正常退出
- **证据与影响:**`cmd/docker_gateway/gateway.py:874-886,1018-1037,1427-1446``compose.yaml:52`。HTTP/1.1 非 daemon 请求线程没有空闲/读取超时;`server.shutdown()` 不关闭已有连接,`server_close()` 会等待这些线程。控制面正常的 `http.DefaultClient` 就可能留下 keep-alive 连接。半截请求体也可造成阻塞。清理顺序还先关闭订阅和代理,再等待在途请求。
- **修正:**明确停止接收、回收空闲连接、在途请求期限和依赖关闭顺序。不能仅改 daemon 线程来掩盖未完成清理。
- **验收:**隔离进程覆盖空闲连接、半截请求体、活跃请求、排队请求;记录正常退出码、耗时、未发生 SIGKILL 和 reservation 释放。此前 47 秒重启记录不能证明优雅退出,详见第 6 节。
### GW04 · P1:冷启动拉取镜像时丢失 digest
- **证据与影响:**`cmd/docker_gateway/docker_client.py:142-160,584-586``repo@sha256:…` 被拆开后,只将 repository 传给 `fromImage`,digest 未传出,可能拉取无关默认 tag,随后原 digest 创建失败。
- **修正:**digest 引用完整传给 Docker;普通 tag 引用才按相应契约拆分。
- **验收:**直接断言 Docker 请求 query,覆盖缺本地 digest、已存在 digest 和普通 tag,不仅测试拆字符串函数。
### GW05 · P1:删除校验拒绝控制面的 `runtime-not-found` 清理语义
- **证据与影响:**`cmd/docker_gateway/gateway.py:1180-1205``cmd/control-plane/hub.go:71,140-145,196-225,1789-1794`。创建失败后只剩已知网络,控制面用 `runtime_id="runtime-not-found"` 清理;Python 对非空 ID 强制 64 位十六进制,提前返回 400,残留网络不能收敛。
- **修正:**保留现有 `/v1` 契约时,仅删除路径解释“容器不存在、清理指定网络代”;若实际存在替代容器则必须拒绝。启动和写操作仍须真实容器 ID,不放宽通用检查。
- **验收:**Go 实际生成的 payload 经过 Python HTTP 路由,覆盖仅网络、两者皆无、alias 已被新代占用。
### GW06 · P1:网络初始化失败丢失代信息,部分副作用未回滚
- **证据与影响:**`cmd/docker_gateway/docker_client.py:393-475``gateway.py:190-192,316-333,474-485,929-947`。创建网络后 inspect 失败不一定携带已知 generation;调用方尚未完成赋值。`restore_proxy` 的网络初始化又位于回滚 try 之外。连接部分成功后抛错可留下半完成网络,HTTP 错误也未保留清理所需网络 ID。
- **修正:**首次副作用后立即保留已知代;所有后续错误经过统一回滚边界,返回已知网络代和清理结论。
- **验收:**逐点注入创建后 inspect 失败、runtime 已连接但 gateway 连接失败、操作生效但响应丢失;仅清理本次代,错误记录保留恢复依据。
### GW07 · P1:单个浏览器网络异常使整个 gateway 无法列举和对账
- **证据与影响:**`cmd/docker_gateway/gateway.py:144-147``docker_client.py:231-252``cmd/control-plane/hub.go:927-970`。列表为每个 running 容器同步解析网络 IP,任何一个缺网络/IP 或中途消失就使列表整体失败,控制面跳过整个 gateway 的环境对账。
- **修正:**清单读取与 CDP 地址解析分离;异常环境仍应可识别、可单独恢复,不能返回假地址或假成功。
- **验收:**正常、缺网络、中途消失三个环境共存;正常记录可读,异常记录有真实状态,其他环境仍能对账。
### GW08 · P1:CDP 超时释放锁后,页面脚本仍可能继续写入
- **证据与影响:**`cmd/docker_gateway/douyin.py:34,64-79,136-155,915-924``gateway.py:617-639`。CDP 固定 15 秒,脚本含 20 秒写请求和多个串行读,IM SDK 没有总期限。关闭 CDP 不会取消页面 PromisePython 返回并释放 alias 锁时,旧脚本可能继续提交,后续动作与它重叠。
- **修正:**统一 HTTP/CDP/脚本预算,明确超时后仍在途的动作所有权与核验方式。不能把“连接关闭”当“写入已停止”,不明操作不得自动补发。
- **验收:**离线脚本或 CDP 替身使写入跨过 15 秒;证明旧动作未结束时后续写不会穿透互斥,结果有最终证据或明确不明状态。
### GW09 · P2:普通 HTTP 代理丢弃 chunked 请求体
- **证据与影响:**`cmd/docker_gateway/proxy.py:226-267,307-333`。只识别 Content-Length,却保留 Transfer-Encoding 转发;合法 chunked 请求变成有声明、无正文/终止块,导致挂起或失败。
- **修正:**采用完整 HTTP 请求解析/转发能力,或正确处理分块与 trailer;不能静默丢正文。
- **验收:**分片 chunk、终止块、trailer、大小限制及异常中断,上游正文必须完整一致。
### GW10 · P2:校验接受的默认值与执行不一致,缺字段返回 500
- **证据与影响:**`cmd/docker_gateway/gateway.py:186,1064,1082-1088,1120,1219-1220`。停止态直连创建省略 `network_exit_id` 能通过验证,但后续直接索引触发 KeyError;代理恢复缺代字段也在正式校验前直接索引,变成 500。
- **修正:**统一完成默认值归一化及必填检查,然后才允许副作用。
- **验收:**通过完整 create/HTTP 路由验证合法省略成功、缺必填返回 400,非法请求没有 Docker 调用。
### GW11 · P2:删除时“清理待完成”的 `/v1` 响应被改变
- **证据与影响:**`cmd/docker_gateway/gateway.py:421-427,929-950``cmd/control-plane/hub.go:1789-1796`。旧实现对可重试网络清理失败返回 `202 runtime_cleanup_pending`,新实现传播为 502,控制面仍依赖 202 继续恢复。
- **修正:**恢复待清理语义及载荷,代冲突仍为 409。若改变契约,须另行批准并同步控制面,不能称为保持 `/v1` 不变。
- **验收:**一次暂时失败后的完整控制面请求序列;验证 202、409、真正失败分别处理。
## 3. 平台动作与事件缺陷
### EV01 · P0:首次及恢复监听没有可靠基线,旧通知可能触发自动发送
- **证据与影响:**`cmd/docker_gateway/douyin.py:532-553,575-581,894-906``cmd/control-plane/creator_events.go:248-274``internal/creator/actions.go:245-312`。只给 reconnected 控制消息设置 baselineGo 又只记日志;普通 notice 未经过历史边界判断。此前未入库的旧通知,只要策略和冷却允许,就可能执行。**已入库终态事件仍有去重,不应夸大为所有旧事件都会重放。**
- **修正:**建立并持久保存经平台验证的新旧边界和恢复分类;边界不明、启用前、断连期间事件仅记录,不自动写入。不能用本机启动时间或重连成功冒充平台连续性证明。
- **验收:**首次历史、停用期间、断连、缺时间、重启、冷却到期旧通知均零自动写;只有已确认新边界事件进入执行,分类理由可查。
### EV02 · P1:事件在持久接收前被删除,错误可造成不可恢复丢失
- **证据与影响:**`cmd/docker_gateway/douyin.py:509-529,625-647,670-675,910-912``cmd/control-plane/creator_events.go:194-209,274-278`。浏览器 `splice`、Python `popleft` 都先删除;单条详情失败会终止批次,Go 保存失败仅记日志,重新 start 停掉旧队列,队列溢出还清空全部事件。
- **修正:**先持久接收再确认删除,可重复读取;单条失败隔离,溢出和无法恢复的缺口持久可见,不擅自补发。
- **验收:**取队列后断网、响应丢失、数据库首次失败、首条详情失败、溢出;事件可恢复或明确记录缺口,不能只以重连成功验收。
### EV03 · P1:接收线程同步等待写动作,掩盖后续事件处理延迟
- **证据与影响:**`cmd/control-plane/creator_events.go:194-209,274``internal/creator/actions.go:486-503``migrations/017_creator.sql:192`。一批逐条等待完整自动动作,首条耗时超过 5 秒便阻塞后续;received_at 使用延后的入库时间,未保留最早接收时间。
- **修正:**快速接收和持久登记与耗时执行分离;仍按执行账号串行写。分别保存平台时间、网关接收、处理开始和结束时间。
- **验收:**首个执行器阻塞 10 秒时,第二个事件仍在收到后 5 秒内进入处理,时间记录不能后移掩盖排队。
### EV04 · P1:后台完成动作被记录为“页面已展示”
- **证据与影响:**`internal/creator/actions.go:271-275,501``settings.go:61-68`。后台完成时写 displayed_at,即使从未打开页面,也有“展示”记录,30 秒指标失真。
- **修正:**动作完成与页面展示分别记录;展示时间只能来自实际显示该事件的客户端确认。
- **验收:**不开页面时 displayed_at 为空;显示后才记,并与动作完成时间独立。
### EV05 · P1Douyin 请求漏传代理标识,非直连环境被拒绝
- **证据与影响:**`cmd/control-plane/hub.go:140-145``gatewayGenerationPayload` 不含 network_exit_id`creator.go:685,727,740``creator_events.go:167` 复用它;Python `gateway.py:695-704` 强制比对代理标签,非直连请求变成 409。直连成功证据不能覆盖。
- **修正:**Douyin 请求统一携带当前有效代和 `environment.Exit.ID`,保留校验;不要顺带混入删除清理语义。
- **验收:**Go 真实 payload 穿过 Python 校验,正确代理通过,缺失/错误代理及旧代拒绝,直连继续通过。
### EV06 · P1:每次重灌 Cookie,阻断有效人工登录并销毁监听
- **证据与影响:**`cmd/control-plane/creator.go:661-684,832,936``cmd/docker_gateway/gateway.py:529-538``douyin.py:266-318`。先强制读取存储凭据,再清 Cookie、导航、设置 Cookie;未先复用有效会话。保存凭据为空或过期时,实际已登录账号仍失败;导航销毁监听,SDK 初始化与后设 Cookie 的身份也未证明一致。
- **修正:**先核验有效会话并复用;只有确需恢复登录才注入批准的凭据,再重建并核验页面身份和监听边界。不能通过伪造 Cookie 绕过缺陷。
- **验收:**有效人工会话无存储 Cookie 也可使用;重复采集不导航/清 Cookie;必要恢复后身份、SDK 与监听一致。
### EV07 · P1:采集与写执行器支持的凭据格式不一致
- **证据与影响:**`cmd/control-plane/creator.go:665-672,809-816,913-920``internal/douyin/connector.go:245-287`。采集支持 header 和 JSON bundle,写执行器仅接受 bundle;合法 header 账号到平台写入前就失败,却可能被标不明。
- **修正:**共用已批准格式的解析入口,解析错误明确属于未执行;与会话复用一起修正,不强制覆盖会话。
- **验收:**两种合法格式经过采集和写入口;非法格式零平台写调用且状态准确。
### EV08 · P1:内部评论/作品 ID 原样下发,人工动作无法执行
- **证据与影响:**`web/src/CreatorWorkbenchPage.jsx:237-243,274-283``internal/creator/actions.go:599-618,716``cmd/control-plane/creator.go:688-690``cmd/docker_gateway/douyin.py:395-408`。内部 comment-/work- ID 不等于数字平台 ID;人工回复还允许缺作品 ID。数据库替身执行器不会暴露该问题。
- **修正:**共享执行边界解析 comment_key、work_key、评论所属作品,并核对平台、作者和关联关系;区分通知自带的平台 ID,不让 UI 拼猜。
- **验收:**从本地记录创建回复、评论点赞、作品点赞和转发,网关收到正确平台目标;缺关联、错作者、跨作品均零写入。
### EV09 · P1:目标评论仅在前 50 条查找
- **证据与影响:**`cmd/docker_gateway/douyin.py:921``ACTION_SCRIPT.findComment` 固定首屏,合法目标在后续页就误报 TARGET_MISMATCH。
- **修正:**使用已验证的单条详情或正确分页定位;区分读取失败、不可获取和真正身份不匹配。
- **验收:**第二页目标可以核验;分页失败不报错身份;真正作者/作品不符则拒绝写入。
### EV10 · P1:写后核验失败、确认字段缺失被错误归为“明确失败”
- **证据与影响:**`cmd/docker_gateway/douyin.py:918-924``cmd/control-plane/creator.go:700-722`。点赞 POST 已成功,核验 GET 限流被设 definitivefollow/like/IM 缺确认字段也当明确拒绝。实际可能已写入,错误失败提示可误导另发。
- **修正:**区分写前失败、经验证的明确平台拒绝、写后确认不足。写后读失败不能证明未发送,缺字段不得自动等价为拒绝。
- **验收:**执行实际脚本逻辑的离线测试:写成功后核验失败、缺字段、SDK 空结果均为不明;明确拒绝才失败,全部禁止自动重发。
### EV11 · P1:丢弃平台结果标识,评论成功证据不充分
- **证据与影响:**`cmd/docker_gateway/douyin.py:921,924``cmd/control-plane/creator.go:700-718`。IM 返回的消息、会话标识被 Go 解码结构丢弃。评论可能缺 ID、正文和明确作品标识,只凭发送者就成功,缺作品标识还用请求值补齐;持久结果仅固定字符串,无法查证。
- **修正:**保存最小稳定平台标识、目标及实际确认内容;不得用请求值代替响应证据,不记录无关完整私信。
- **验收:**缺评论 ID、正文错误、父评论不符不能成功;成功私信在操作记录中可查稳定消息和会话标识。
### EV12 · P1:排队后不重核状态,已停用的动作仍可能发出
- **证据与影响:**`internal/creator/actions.go:475-486,698-716``cmd/control-plane/creator.go:650-693``cmd/docker_gateway/gateway.py:617-630`。账号业务检查、策略选择在网关锁排队前完成;锁内仅核代和平台身份。等待期间禁言、撤销授权、停策略、改关系没有最终阻止点。
- **修正:**人工和自动共用按账号执行协调,取得执行权后、实际提交前重核当前条件;自动动作还须检查原策略与关系。
- **验收:**第二个动作排队时逐项改变条件,后续零平台写;已经发出的动作保留真实结果,不能假装撤销。
### EV13 · P1:转发确认了文案,却不发送文案
- **证据与影响:**`cmd/docker_gateway/douyin.py:409-412,921``cmd/control-plane/creator.go:691`。入口要求非空文案,但转发请求不使用 `p.text`,仍报 reposted/succeeded,确认内容与实际行为不符。
- **修正:**先验证对应平台带文案语义;若只有无文案推荐,明确能力差异并取得用户裁决,不静默丢文案。
- **验收:**确认文本进入真实动作且结果一致;不支持时明确阻止,不宣称带文案转发成功。
### EV14 · P2:同页面重连累积旧事件处理器
- **证据与影响:**`cmd/docker_gateway/douyin.py:532-553,894-906`。同 key 新安装前未调用旧 dispose,关闭 CDP 不会移除页面 addEventListenerstop 只释放最新一组。
- **修正:**重装前清理旧状态,安装失败也清理本次处理器。
- **验收:**同页面重复重连后处理器数量固定,stop 后为零。
## 4. 账号、采集、计划及持久数据缺陷
### BE01 · P1:旧作品首次发现后,指标间隔状态错误
- **证据与影响:**`internal/creator/metrics.go:138-155,171-182`。T+2 小时首次发现,将 next_plan 推到 T+3 小时,却保留 1 小时间隔;之后推到 T+5 而非 T+7。
- **修正:**根据发布时间和当前时刻统一计算完整计划状态,不能只改下次时间。
- **验收:**T+2、T+8、T+32 首次发现后连续多个计划点及重启恢复,符合 1、3、7、15、31、55…累计时刻。
### BE02 · P1:固定采集计划按完成时间漂移
- **证据与影响:**`cmd/control-plane/creator.go:852-853``internal/creator/collection.go:255,266-270`。T 开始、T+5 分钟完成的 30 分钟任务,下次变成 T+35;不符合固定起点与迟到跳到严格未来点。
- **修正:**持久保存起点及未来计划点,完成时间仅记实际执行。
- **验收:**慢任务、错过多个周期、重启、暂停恢复后不漂移、不回放历史周期。
### BE03 · P1:修改设置没有重设已有计划
- **证据与影响:**`internal/creator/settings.go:32-57``metrics.go:138-141,171`。仅改 settings,账号未来时间不重设,指标计划混用旧间隔/倍数与新年龄。
- **修正:**一致重算账号、评论未来起点和作品未来计划,保留历史快照。
- **验收:**有存量计划时逐项修改间隔、倍数、上限、年龄;未来按新规则,历史不改、不补造。
### BE04 · P1:普通自有账号不参与作品和一级评论补充采集
- **证据与影响:**`internal/creator/collection.go:259``cmd/control-plane/creator.go:883-891`。到期查询强制 big_account=true,将 W1 的自有账号错误缩减为大号。
- **修正:**采集资格与大号自动响应模式分离。
- **验收:**普通账号、大号、小号符合条件时均可采集,只有自动响应受大号模式控制。
### BE05 · P1:封禁、注销账号仍可被选作采集账号
- **证据与影响:**`internal/creator/collection.go:264-270``cmd/control-plane/creator.go:798-804,903-908,955-964`。检查授权但不检查 banned/deleted 业务状态,自动选择与手动指定均受影响。
- **修正:**所有采集入口复用同一资格检查。
- **验收:**自有、竞品自动选择、竞品手动指定三路均拒绝封禁/注销且零平台请求;历史仍可查。
### BE06 · P1:自有采集的登录/验证阻断仍被自动重试
- **证据与影响:**`cmd/control-plane/creator.go:913-945``internal/creator/collection.go:189-193,265-270,346-347`。身份失败未持久 blocked,分页 challenge/401/403 统一 failed,下轮再次选择。
- **修正:**区分可恢复网络错误和需人工处理的阻断,贯穿身份检查到分页,提供明确人工恢复。
- **验收:**身份不符、验证码、401 后下一轮零请求;网络暂时失败按批准计划恢复。
### BE07 · P1:采集租约不续期,且未覆盖页面数据写入和结束状态
- **证据与影响:**`internal/creator/collection.go:127-136,157-186,316-342,398-408``content.go:94-139`。阶段超过十分钟后断点保存失败;旧持有者被接管后仍可写作品/评论/指标。MarkCompetitorSync 无所有权条件,旧请求可覆盖新结果或暂停状态。
- **修正:**一致续期;一页数据与断点更新受有效领取条件共同保护,最终状态同样核对所有者和暂停状态。
- **验收:**长任务、到期接管、旧请求迟返、运行中暂停恢复;旧持有者不能覆盖新代结果。
### BE08 · P1:资料编辑绕过大小号关系和策略停用规则
- **证据与影响:**`internal/creator/accounts.go:124-143,173-205``cmd/control-plane/creator.go:65-76`。profile 更新直接设 big_account,绕过专用入口的小号晋升检查;关闭再开启或非正常再恢复,不要求重启用策略。
- **修正:**共享事务性状态变更规则,停用所有受影响策略;恢复正常不自动恢复策略。
- **验收:**资料入口和专用入口分别覆盖小号晋升、关闭开启、正常→异常→正常,非法关系拒绝且策略不偷偷恢复。
### BE09 · P1:小号不同归属冲突被吞掉,并发关系检查有空窗
- **证据与影响:**`internal/creator/accounts.go:185-194,248-268``migrations/017_creator.sql:149-155`。小号已归 A,关联 B 时 ON CONFLICT DO NOTHING 仍返回成功;关联与晋升各自先查后写,可并发形成非法角色状态。
- **修正:**同关系重复可幂等,不同归属明确冲突;按一致顺序锁相关账号再检查。
- **验收:**同关系重复、不同归属、关联与晋升双事务竞争,返回结果与实际状态一致。
### BE10 · P1:无文案动作也进入 AI,失败后占用冷却
- **证据与影响:**`internal/creator/actions.go:411-440``logic.go:178-179``cmd/control-plane/creator.go:28`。仅判断 text==空,关注/点赞无文案也调用 generator;生产为 nil,动作失败并已占冷却。
- **修正:**只为需要文本的动作选择或生成文案,不改动冷却对已开始动作的既定语义。
- **验收:**关注、作品点赞、评论点赞在 generator=nil 时执行一次且零 AI 调用;文本动作仍检查配置。
### BE11 · P1:策略启用不校验 AI 必要条件
- **证据与影响:**`internal/creator/actions.go:82-110,152-176,366-440`。缺候选文本、有效 AI 配置或回复要求仍可启用,收到事件后才失败并可能占冷却。
- **修正:**创建、修改、专用启用入口统一校验动作实际需要的条件,不把“审批”布尔值当可用客户端。
- **验收:**三个 enabled=true 入口缺配置均拒绝且无冷却记录;无文案动作不被错误限制。
### BE12 · P1:同一作品第二来源无法匹配对应线索规则
- **证据与影响:**`internal/creator/rules.go:138-144``content.go:222-247`。作品已有多来源表,但分析仍只比最初 SourceTypeowned 后关联 competitorcompetitor 规则被误拒,反向同样。
- **修正:**使用实际来源关联判断规则范围。
- **验收:**双来源两种入库顺序、各来源专用规则都正确,同评论仍只形成一条线索。
### BE13 · P1UID 登记值被当作 SecUID 查询作品
- **证据与影响:**`internal/douyin/creator_collector.go:25-54``cmd/control-plane/creator.go:939-943`。核验允许 UID/SecUID/UniqueID 匹配,却不保留规范映射,后续原样放进 sec_user_id。旧 connector 使用身份响应的 SecUID。
- **修正:**身份核验输出平台规范标识映射,各接口使用其要求的标识。
- **验收:**UID 和 SecUID 登记同账号,最终作品请求均用正确 SecUID;随后补真实只读证据。
### BE14 · P1:密码更新失败可能删除原有密码
- **证据与影响:**`internal/creator/accounts.go:111-139`。新密码先覆盖固定 secret key,数据库失败后删除该 key,旧密码已经无法恢复,数据库却可能仍显示已配置。
- **修正:**新值使用独立引用,成功关联后再清理旧值;失败只清本次新建项,结果不明需核验关联。
- **验收:**已有密码时注入保存失败、数据库失败及结果不明,旧有效凭据不丢,配置标记与引用一致。
### BE15 · P2:冷却时长没有溢出上限
- **证据与影响:**`internal/creator/accounts.go:90-93``actions.go:390-394`。巨大正数转 time.Duration 再乘秒可能溢出到过去,冷却失效。
- **修正:**复用已有时长边界校验。
- **验收:**上限、上限+1、int64 最大值;拒绝溢出,历史占用不改变。
### BE16 · P2:未来发布时间作品被过滤掉,无法进入待核验状态
- **证据与影响:**`internal/creator/collection.go:317-320` 先按窗口跳过,`content.go:203-207` 的 future 标记无法到达。
- **修正:**可靠窗口外和时间无效/未来分别处理;后者保留待核验并说明范围不完整。
- **验收:**正常、窗口外、缺时间、未来时间同批输入;异常时间不丢资料、不建年龄计划。
### BE17 · P2:AI 缺失判定字段被当作明确否定
- **证据与影响:**`internal/creator/bailian.go:110-117,127-134``{}`、null、缺 match 或 match=null 解码为 bool false,后续变成非线索而非分析失败。当前客户端尚未接生产,接线前也须修复。
- **修正:**区分字段缺失/null 和明确 true/false,验证必需依据。
- **验收:**上述不完整样例均失败;合法真/假按真实依据保存。
### BE18 · P2:迟到或重复消息倒退会话最近时间
- **证据与影响:**`internal/creator/actions.go:755,804-816`。消息去重前先无条件更新 last_message_at,旧消息即使重复未插入,也改变会话排序。
- **修正:**消息写入和摘要更新一致,最近时间不得被旧/无时间消息覆盖。
- **验收:**新→旧→重复旧→缺时间消息,时间不倒退且不重复消息。
### BE19 · P2:作品列表持有连接时再查询,低连接池下互相等待
- **证据与影响:**`internal/creator/content.go:368-384``store.go:56`。外层 rows 未关闭,又逐作品查询来源;并发占满池后都等待内层连接。
- **修正:**先读取并关闭外层 rows,再批量读来源,或一次查询完成,避免嵌套占用。
- **验收:**连接池设为 1 时带来源列表仍在期限内返回,再覆盖并发列表。
## 5. 前端确定缺陷
人工目标 ID 错配统一见 EV08;AI、媒体、登录、推送缺口见第 7 节,不重复计数。
### UI01 · P1:设置加载后原样回传只读字段,保存返回 400
- **证据与影响:**`web/src/CreatorSettingsPage.jsx:30-31,43``internal/creator/models.go:23-52``cmd/control-plane/creator.go:39-44,604-611`。GET 含 updated_at,完整合入 form 并 PUTSettingsUpdate 无此字段,严格解码拒绝。现有 Mock fixture 漏了 updated_at。
- **修正:**仅发送可编辑字段,保持后端严格校验。
- **验收:**真实 GET 结构加载后保存,经真实编码/解码成功;请求无只读字段。
### UI02 · P1:工作台切换页签将旧数据按新类型渲染,页面崩溃
- **证据与影响:**`web/src/CreatorWorkbenchPage.jsx:41-42,79-150,501,599-609,800`。共享 data,评论切规则时立即计算 include_keywords.join,切线索时访问 lead.comment.idpending 包裹不能阻止 children 提前计算。
- **修正:**按页签隔离数据或明确数据归属,不匹配时不计算列表。
- **验收:**各类型非空数据逐对切换,延迟/乱序返回,始终不崩溃、不串表。
### UI03 · P1:人工操作标识仅在页面内存,刷新恢复可变成第二次发送
- **证据与影响:**`web/src/CreatorWorkbenchPage.jsx:63-74,237-283,331-347`。响应丢失后刷新,原 key 不可恢复,再确认生成新 key。服务端同 key 去重仍正确,但无法识别原操作的恢复。不是说用户永远不能再发同内容。
- **修正:**复用持久操作记录,恢复原标识和冻结载荷;不明只查询,确需另发必须明确新确认并关联原操作。
- **验收:**创建/执行响应各自丢失,页面重挂和服务重启仍只执行一次;恢复不创建新发送。
### UI04 · P1:局域网 HTTP 下直接调用 randomUUID 可能使发送入口失效
- **证据与影响:**`web/src/CreatorWorkbenchPage.jsx:35-36,243,331`。部署允许非 localhost HTTP,但 crypto.randomUUID 依赖安全上下文;直接调用无能力处理。该环境兼容性结论来自 API 约束,本轮未作浏览器复现。
- **修正:**使用适配批准部署方式的可靠操作标识机制,结合 UI03 持久恢复;不降为弱随机或吞错。
- **验收:**randomUUID 缺失替身及实际局域网 HTTP,回复和私信都能正常取得并恢复标识。
### UI05 · P1:账号切换未隔离迟返请求,可显示和保存另一账号资料
- **证据与影响:**`web/src/CreatorAccountsPage.jsx:105-168,289`。A 的读取/保存等待时切 BA 迟返仍 setForm/setStrategiesB 标题下出现 A 数据,继续保存可能覆盖 B,策略操作也可能落到 A。
- **修正:**结果绑定发起账号,失效响应不应用;提交中处理目标切换,未保存修改明确确认。
- **验收:**两个账号明显不同资料和策略,打乱读取及保存响应;任何时刻均不串用或错操作。
### UI06 · P1:私信账号选择被当前会话平台锁住
- **证据与影响:**`web/src/CreatorWorkbenchPage.jsx:621-640`。顶层账号选项按当前会话平台过滤,有抖音会话时另一平台账号消失,无法正常跨平台切换。
- **修正:**先独立选账号,再清旧会话并读目标账号数据。
- **验收:**两平台各有会话账号双向切换,选项始终可选,请求仅属于新账号。
### UI07 · P2:所有非成功状态都显示“结果不确定”
- **证据与影响:**`web/src/CreatorWorkbenchPage.jsx:287-293,352-358`。failed、blocked、processing 被同文案覆盖,隐藏确定原因和在途状态。
- **修正:**按后端真实状态及原因展示,并关联原操作;不得给不明状态提供重新发送式重试。
- **验收:**成功、失败、阻止、处理中、不明五种结果展示准确。
### UI08 · P2:读取失败被当作空数据
- **证据与影响:**`web/src/CreatorAccountsPage.jsx:105-114``CreatorCompetitorsPage.jsx:72-77`。策略/账号失败直接清空数组,误报尚无策略或可用账号。
- **修正:**错误独立保存,保留已知数据,提供只读重试,依赖数据的写入口禁用。
- **验收:**首次失败和有数据后刷新失败均不得冒充真实空列表。
### UI09 · P2:关键词输入清理规则与后端和需求不一致
- **证据与影响:**`web/src/CreatorWorkbenchPage.jsx:28-32,213-214``internal/creator/logic.go:102-115`。前端 trim 去除更广 Unicode 空白,filter 静默丢空词;后端只清指定普通空白并拒绝空词。
- **修正:**统一有限清理规则,空词明确拒绝,不改变原文匹配语义。
- **验收:**全角空格、NBSP、普通空白、连续逗号;界面与直接 API 输入一致。
## 6. 必须纠正的测试、部署和文档结论
### T01 · P1:覆盖率统计必须排除测试代码
当前命令固定为 `python3 -m coverage run --source=cmd/docker_gateway -m unittest cmd.docker_gateway.test_gateway`,随后执行 `python3 -m coverage report --include='cmd/docker_gateway/*.py' --omit='cmd/docker_gateway/test_*.py' --fail-under=65`。最近一次本地运行 56 项测试通过,生产代码总覆盖率达到 **65%**`.coverage` 只作为本次运行产物,不提交仓库。
- **修正:**统计分母排除测试文件,并补 CDP 超时终止、DM 规范化和网络创建异常回收回归。
- **验收:**覆盖率命令必须与同次测试生成的数据对应;逐文件低覆盖率不被总数掩盖,真实平台验收另行登记。
### T02 · P1:常规测试通过不等于关键数据库和平台逻辑已验证
- `internal/creator/integration_test.go` 无专用数据库地址时仍会 skip;使用 `CREATORHUB_POSTGRES_TEST_URL` 时最近一次已实际运行内容、动作、消息和迁移测试。
- `creator_events_test.go` 覆盖字段/边界转换;新增 stale processing 代码路径仍须在专用数据库环境做崩溃恢复验收。
- Python 动作测试覆盖实际嵌入脚本的关键分类字符串和 CDP 超时终止;不替代真实浏览器/平台写入。
- 前端覆盖错误、忙碌、账号隔离、策略/规则编辑及消息刷新;不替代视觉、真机和外部服务验收。
- **验收口径:**记录 executed/skipped 数量及运行条件;任何 Mock 成功仅证明它覆盖的局部规则。
### DOC01 · P1:正常退出证据仍需与实现分开记录
当前代码已配置 SIGINT/SIGTERM、请求排空和 45 秒 Compose 宽限,且本地已有特定 reservation 回收证据;这不等于所有 Docker 故障注入、并发和强杀场景都通过。部署文档保留该限制,不把单次重启时长包装成完整生命周期验收。
### DOC02 · P1:登录要求冲突,不能自行恢复自动登录
`AGENTS.md``docs/plan01.md` 当前统一为不自动登录。**当前修正仍遵守不自动登录、不暴露密码/Cookie/二维码/验证码。** 有效会话可复用,失效后进入人工登录并由 gateway identity 核验;密码“已保存”不得表述为“登录能力已接通”。
### DOC03 · P2:采集接线与完整性证据分开
当前文档已表述为:存在数字游标校验、分页请求、保存路径和独立计划;完整性、标识转换、分页边界和平台验收仍未由本地代码证明。接受游标参数不等于证明无漏项。
### DOC04 · P2:完成表混杂历史与当前轮次
`docs/plan01.md` 当前已按历史文档、代码接线、实际运行证据和外部待验收项分开;新增能力继续按该三类记录,不能用代码接线代替平台成功。
### DOC05 · P1:浏览器镜像必须由仓库源码和 immutable digest 管理
当前分支新增 `docker/browser-wrapper/`,以显式 `BROWSER_BASE_IMAGE` digest 构建 Xvfb/CDP 包装入口,并在 deployment 文档记录 base/published digest、构建提交和 Dockerfile。干净环境构建/拉取和真实身份核验仍需拥有已批准的第三方 base digest 后执行;本地 tag 不作为证据。
### DOC06 · P1:真实证据只证明局部可达,不证明业务成功
上一轮身份读取 200、事件启动 200、空列表、重启后短期无错误,只能证明这些时点的局部可达。不得扩大为基线、无遗漏、处理时限、正常退出或全动作成功。直接 follow 返回 BUSINESS_REJECTED 且后查状态为未关注,不能证明关注能力成功;当前 EV10 已将非 2xx 和写后核验不足保留为不明。控制面 resolve credential 发生在写前,说明 EV06/EV07 所在链路缺陷,不应一概归咎于“只差用户提供 Cookie”。结果不明的原操作仍不得自动重试。
## 7. 已确认的功能接线缺口
这些项目尚未完成实际流程,不能全部归为“缺真实平台证据”。补齐仍须遵守现有范围、供应商批准与不自动登录等约束。
| 编号/优先级 | 当前证据与缺口 | 修正方向及完成条件 |
| --- | --- | --- |
| GAP01 · P1 | `creator.go` 已装配独立的到期指标调度,作品发现与指标刷新分离;真实平台请求频率仍需外部验收。 | 本地已验证独立到期读取、年龄和计划边界;真实平台需继续证明过龄零请求。 |
| GAP02 · P1 | Bailian adapter 已装配;缺少 key/model 明确返回 unavailable,质量样本和生产供应商仍待批准。 | 本地验证装配、配置校验和失败边界;生产质量与费用由外部验收。 |
| GAP03 · P1 | 新增真实下载、FFmpeg 音轨提取和可配置转写命令;原手写 material-step 路由已删除,转写未配置时明确 failed。 | 本地验证产物驱动和失败状态;音视频、无音轨、空间、供应商质量及重试仍需样本验收。 |
| GAP04 · P1 | Douyin DM notice 可规范化,入站事件和人工出站结果均写入本地会话,重复键幂等;平台实际 DM payload/history 仍待验收。 | 本地验证账号隔离、重复消息和失败状态;平台历史读取与非文本消息需外部证据。 |
| GAP05 · P1 | 事件处理/缺口已持久化,工作台在打开 DM 会话时按 10 秒刷新并支持手动刷新;未新增未批准的双向推送框架。 | 本地验证持久结果恢复;平台关页监听、30 秒可见和真实断连仍待验收。 |
| GAP06 · P1 | 账号页显示登录状态、原因、核验时间和凭据标记;login-result 不得直接写 logged_in,只有 gateway identity 核验可写入。 | 本地验证伪造成功被拒;人工登录入口和真机身份重核仍待外部验收。 |
| GAP07 · P1 | 策略支持编辑/调序、启停;关系列表已有解除入口,更新沿用既有接口。 | 本地验证账号切换和策略更新;解除/再绑定并发效果仍需集成证据。 |
| GAP08 · P1 | 规则支持来源范围、编辑、启停;评论/线索提供人工联系入口,未接自动联系。 | 本地验证来源和人工确认边界;历史判断依据展示仍需补充验收。 |
| GAP09 · P1 | 同步状态、错误、下次时间已显示;链接解析预览与完整组合筛选仍未完成。 | 继续补链接解析/组合筛选,真实0与缺值需保持可区分。 |
| GAP10 · P2 | 账号切换和素材文稿切换已有放弃确认,DM 草稿继续本地保留。 | 仍需覆盖工作台页签/路由切换的未保存状态。 |
| GAP11 · P1 | 小红书当前在 `creator.go:792-794` 等入口明确阻断;登记或列表里有平台选项不等于业务实现。 | 独立完成同范围能力核实与接线;每类缺口单独裁决,不以抖音成功代替,不宣称平台本身不支持。 |
## 8. 建议修正顺序与交付门槛
### 第一批:限制错误外部写入、误删和丢数据
- EV01、EV02、EV10—EV12:历史边界、持久接收、结果证据和写前条件。
- GW01、GW03、GW06、GW08:创建归属、停止排空、网络回滚、仍在途脚本互斥。
- BE07—BE09、BE14:租约、账号关系/停用及凭据更新一致性。
- 每项先有未修时失败的隔离回归,再改代码;不能用放宽代校验、吞异常、清库或无限重试替代根因修正。
### 第二批:恢复最小可用闭环
- 代理和 `/v1`GW02、GW04、GW05、GW07、GW09—GW11、EV05。
- 身份和人工目标:EV06—EV09、EV13、BE13。
- 页面可用性与人工标识恢复:UI01—UI06;无文案自动动作 BE10、启用校验 BE11。
- 验收先经过真实 Go 请求/Python 校验/页面响应契约,再做获批的小规模真实平台动作。结果不明的旧操作仅核验,不能拿新 key 伪装重试。
### 第三批:计划正确性和剩余功能
- BE01—BE06、BE12、BE15—BE19、UI07—UI09、EV03、EV04、EV14。
- 按 GAP01—GAP11 完成实际缺口;AI 配置、平台差异和自动登录争议先取得必要批准。
- 保留原分阶段顺序:先抖音完整流程,再小红书,最后共存及错误恢复,不用通用框架扩大本期范围。
### 共同验收门槛
1. 修正清单每项关联回归用例和结果;条目关闭必须有证据,不以“代码已改”关闭。
2. Python 生产代码覆盖率排除测试后至少 65%,Go 数据库测试明确实际运行,关键竞争/崩溃路径有测试。
3. 后端 test/vet/race 和控制面构建,Python 网关测试/构建、Compose 配置与生命周期通过;迁移后的网关不再要求构建已删除的 Go 可执行入口。
4. 前端 lockfile 安装、非交互测试、构建通过,补真实契约、非空切换、双账号/双平台、乱序和刷新恢复测试。
5. 不泄露凭据,不改变账号身份和操作目标,不自动重试不明写入。日志保留操作、账号、代、阶段、错误类别及时间,避免只有笼统错误而无排查线索。
6. 平台事件、恢复连续性、各动作、媒体产物、AI 质量与界面时限分别验收;HTTP 200、容器 running、空事件队列、无日志错误均不能替代。
7. 尚未完成项目必须继续标注未完成;范围调整需明确批准,不能通过修改文档把缺功能包装成已完成。
## 9. 审查覆盖与局限
### 9.1 覆盖清单
- **网关新增:**`cmd/__init__.py``cmd/docker_gateway/{__init__,docker_client,douyin,gateway,proxy,test_gateway}.py``requirements-gateway.lock``requirements-gateway-dev.lock`
- **旧网关删除:**`cmd/docker-gateway/{main,douyin,proxy}.go``main_test.go``douyin_test.go``proxy_test.go``douyin_cdp_integration_test.go`。重点对照发生契约和清理/代理回归的旧实现;未把每个已删除测试逐一移植或重跑。
- **控制面:**`cmd/control-plane/creator.go``creator_events.go``creator_events_test.go``credential.go``main.go``hub.go``hub_test.go``douyin_test.go`;新增及改动路径沿调用链核对。
- **领域:**`internal/creator/` 全部生产 Go 文件、全部现有测试文件、017—026 十份迁移。
- **采集与凭据:**`internal/douyin/{connector,connector_test,creator_collector,creator_collector_test}.go``internal/phasea/store.go` 的改动与关联读取路径。
- **前端:**`web/src/Creator{Accounts,Competitors,Settings,Workbench}Page.jsx``CreatorPages.test.jsx``BrowsersPage.test.jsx``Layout.jsx``dataProvider.js``lib/ui.jsx``main.jsx`
- **部署/文档:**`Dockerfile``compose.yaml``AGENTS.md``docs/plan01.md``docs/deployment.md``docs/architecture/container-control.md``docs/product/compliance-product-plan.md`。另对照未改动的 Compose 开发配置和相关测试。
### 9.2 未运行或无法从本地代码证明的内容
- 本轮未进行真实平台写操作。所有本地测试、覆盖率和 Compose 配置结果均以交付前实际命令记录为准,不能证明平台业务成功。
- 未实测浏览器布局、键盘操作、局域网 HTTP、自动指纹、两账号全流程、Docker 故障注入、数据库并发、部署正常退出码和真实代理兼容性;这些仍须按原 AC 清单登记运行证据,不能将本报告未发现某项缺陷当作该项通过。
- 平台真实分页完整性、事件 ID/恢复位置、迟到边界、DM 历史范围、带文案转发能力、每类动作成功证据仍待批准的真机验证。
- 本报告不是对主线全部既有代码的全面审计;未改动部分只在解释本分支调用链和迁移契约时对照。
- 未发现问题的文件不等于证明无问题。四路审查的完整原始输出保存在本次会话的 `subagent-artifacts/outputs/8f8a3bc6-a9a0-48fc-9cf4-54a8ab4453b2/` 下,文件为 `review-gateway.md``review-events.md``review-domain.md``review-web-docs.md`。本文汇总后的编号是后续修正跟踪依据。