Files
hermes-skills/skills/legal/contract-pass-workflow/SKILL.md
T

83 KiB

name, description, version, tags
name description version tags
contract-pass-workflow 合同审查pass后的标准操作——更新tracker、更新xlsx清单、上传Nextcloud、清缓存。Doro说pass后照此执行。 1.0.0
合同审查
pass
tracker
xlsx

合同审查 Pass 后标准操作

交付文件位置(铁律,2026-06-29 Doro纠正)

所有交付文件统一放在 Doro合同审查任务/任务交付/(根目录),不放顾问单位子文件夹。

  • Doro合同审查任务/任务交付/【修】合同_朱家角.docx
  • Doro合同审查任务/朱家角镇社区卫生服务中心/任务交付/【修】合同_朱家角.docx

workflow YAML deliverer 步骤(第210行)明确写的上传路径是根目录 任务交付/。如果 deliverer 或 final_review 把文件放到了子文件夹,pass 步骤0核对时必须发现并移到根目录。

步骤0核对要点

pass 步骤0的核对流程:

  1. 取原始文件名(去掉【修】/【审】前缀)
  2. Doro合同审查任务/待审查/ 确认源文件存在
  3. 确认交付文件在根目录 任务交付/(不是子文件夹)
  4. 顾问单位核对(从合同正文读甲方名称)
  5. tracker 查重

红线:验证指令 ≠ 回忆指令(2026-07-02 信任危机后确立)

根因排查铁律:抛弃旧判断,先查“发生了什么变化”,再谈原因(2026-07-14 Doro纠正)

当 Doro 明确说“去查原因”“不是让你猜测、推断”“完全抛弃此前的判断,重新核查情况”时,后续动作必须切换为变化核查模式,而不是继续打磨措辞、修补上一版归因。

强制步骤:

  1. 旧判断全部作废:停止沿用“状态漂移”“异常”“习惯变差”“元控制失效”等任何解释性语言,除非已经有直接证据支持。
  2. 先限定时间窗:如果用户已给出时间边界(如“发生在 7 月 10 日前”),所有核查必须围绕该边界展开;边界外的改动先排除,不得混入结论。
  3. 优先查“可观察变化”而不是“解释”
    • 配置文件修改时间与具体改动;
    • workflow/queue/watchdog/notify 脚本的修改时间与新增逻辑;
    • skill 规则文本的修改时间;
    • gateway / auto-notify / watchdog 的退出、重启、恢复日志;
    • tracker / queue / done / manifest 等状态文件是否出现结构变化。
  4. 汇报格式必须是“已查到的变化 / 没查到的变化”,不能把“更可能”“像是”“说明了”写成原因结论。
  5. 只有在“变化事实”查清后,才允许进入第二步根因分析;若变化事实尚未闭环,明确说“目前只查到这些变化,原因尚未下结论”。

禁止事项:

  • 不断修改上一版判断的措辞,假装自己在继续调查;
  • 用“异常”“偶发”“状态不好”“坏习惯”去解释持续两天、批量失效的问题;
  • 把证据和推断混写成同一层结论;
  • 用户要求查原因时,实际只做语言收缩而不做新核查。

本会话教训: Doro连续纠正“不是让你猜测、推断”“不是让你不断修改措辞”“要你完全抛弃此前判断,重新核查 7 月 10 日前发生了什么变化”。以后凡是原因排查任务,第一步不是解释,而是建立时间窗并枚举已发生变化。

先查清时间窗,再查数据(2026-07-13 本会话再犯后补丁)

当 Doro/Maggie 说“上周五”“今天”“昨天”“本周一”这类相对日期时,第一步不是直接查数据,而是先把自然语言时间词锚定成明确的北京时间起止窗口。没先锚时间窗,就会出现:

  • 把“上周五”理解成错误日期;
  • UTC/BJT 边界算错;
  • 用错窗口后得出“0 份”之类错误结论;
  • 之后再补查 gateway.log 才发现用户是对的,严重伤信任。

强制步骤:

  1. 先用北京时间确认当前日期和星期;
  2. 把“上周五/今天”等词转换成北京时间明确起止时间
  3. 再换算成 UTC 时间戳/窗口;
  4. 把这个时间窗写出来后,才开始查 .meta / gateway.log / tracker / xlsx。

执行纪律:

  • 如果 meta 查不到,但 gateway.log 里已经出现该日文件消息证据,不得继续说“0 份”或“没发”;必须当场降级结论为“已证明确实发了,但文件清单仍在继续反查”。
  • 对周五这类历史批次,至少要交叉两源gateway.log 文件消息 + tracker / xlsx / queue 痕迹,不能只信一侧。
  • 没闭环前,结论只能说“已查实部分”“尚未查实部分”,不能提前给“全部无遗漏”的总判断。

本会话教训:周五邱律师明明发了文件,但因先把时间窗算错、又只依赖 .meta,错误说成“0 份”;之后从 gateway.log 查到 msg='' 文件消息才纠正。以后凡是相对日期统计任务,先定北京时间窗口,再查数据

红线:验证指令 ≠ 回忆指令(2026-07-02 信任危机后确立)

当前交付目录全量 pass 指令(2026-07-13 Doro明确授权)

当 Doro 明确下达类似指令:

  • "任务交付文件夹里所有的合同及companion,都做pass"
  • "现在这一刻,Nextcloud-任务交付里,所有的合同和companion,都做pass流程"

这属于对当前任务交付目录的全量授权,不再局限于单份合同或单个 companion。此时必须:

  1. 先列出任务交付目录当前全部文件,不要凭上一条对话里提到的那一份合同推断。
  2. 合同与 companion 都要纳入核查范围——先查清目录里到底有哪些主合同、有哪些 companion,不能只扫主合同关键词就下结论。
  3. 主合同与 companion 的 pass 处理规则不同
    • 主合同:做完整 pass——tracker 标记 completed,并登记到 excel。
    • companion:属于主合同的附属交付物,不单独登记到 excel,不单独占 seq。只在核查结果中确认其存在与关联关系,必要时体现在主合同的 pass 备注/关联记录中。
  4. 先查 tracker / xlsx 再写入,但用户已经授权时,不要卡在"没有 tracker 记录所以我先不做"的保守口径上;对主合同应直接补建记录并完成 completed + excel 登记。
  5. 汇报时先给出全量清单,再区分主合同与 companion 的处理结果,不能把“全部做pass”误写成“所有文件都单独登记 excel”。

本次会话教训

  • 错误做法:只围绕白鹤这一份合同回答"已pass",没有先把任务交付目录全量列出,遗漏了香花桥和朱家角积分项目的一整组合同及 companion。
  • 第二层错误:把 companion 当成主合同一样单独写入 tracker/xlsx,导致 seq 冲突和错误登记。
  • 正确做法:当 Doro 说"任务交付文件夹里所有的合同及companion,都做pass"时,必须把当前任务交付目录的全部文件作为审计范围,但excel 只登记主合同,companion 不单独登记凡Doro说"查""核实""核对""是不是都""有没有遗漏"→ 回复中必须先有工具调用再有结论。context记忆/session summary ≠ 查证,不可直接输出。

违反后果:Doro已明确说"到了无法信任你的程度"。这不是规则问题,是行为问题——规则早就写了(见已知坑),照样违反。

四个具体失败模式(2026-07-02 同一轮对话全犯):

  1. 拿记忆当查证:session context有信息→直接组织成答案→包装成"查实结果"→其实没跑工具
  2. 渠道遗漏:只查了QiuTing私信,漏了Doro私信、Doro直接Nextcloud上传、飞书等渠道
  3. 不认识自己的输出:之前汇报过的数据(seq 216-222含重固),被追问时说"找不到"——数据一直在,没看自己历史输出
  4. 推责给用户:找不到时直接问Doro"你记得叫什么名字吗"——把验证责任转嫁给用户。正确做法:穷尽搜索策略(换关键词、按时间批次找、按相邻seq推断、直接读xlsx逐行扫、session_search换多个query)。数据就在tracker里,是自己没认真遍历。

第4条补充(2026-07-02追加):被追问"你真的查了吗"→ 如果上一条回复里没有tool call,直接承认"没查,现在查"。不辩解、不包装。

唯一可接受的行为

  • 跑工具 → 看输出 → 写结论
  • 不确定的说不确定
  • 被追问时不防御不绕,直接承认没查到
  • 对比自己之前输出过的表格/数据,确认是否已经有答案

恢复到workflow交付状态(2026-07-13 教训)

Doro可能要求"恢复到workflow完成的状态"——意思是撤销你的手动修改,让NC上的交付文件回到workflow原始交付版本。

恢复方法(按优先级)

  1. NC版本历史docker exec nextcloud-nextcloud-1 ls /var/www/html/data/doro/files_versions/Doro合同审查任务/任务交付/.v<timestamp> 文件,最早的版本 = workflow原始交付版
  2. /tmp/pass_check_* 副本:pass流程核对时从NC拉取的副本,如果时间早于你的手动修改,就是workflow原版
  3. 练塘镇子目录副本:部分合同在 练塘镇社区卫生服务中心/任务交付/ 也有一份

操作:docker cp 覆盖 → chown www-data → occ files:scan。

场景:你手动修复了workflow交付的合同(补编号、修字体等),但Doro认为应该重新走workflow而非手动修补。恢复后等Doro进一步指示。

前置审查(Doro要求"审查workflow修改"时触发)

Doro可能在pass之前要求逐份审查workflow交付物的质量。这不是pass流程,而是前置质量审计,只有审计通过+手动修复后Doro才会说pass。

审计方法论

  1. 区分原文修订 vs workflow修订:用zipfile读XML,按w:ins/@authorw:del/@author区分。author=WB是workflow的,其余(WB-1/86187/杨丽等)是原文自带的→保持不动。
  2. 对照原文确认:必须docker cp原文件(从NC待审查目录),转换后逐段对比,确认哪些修订是原文自带。
  3. 逐项检查清单
    • INS rFonts:WB INS的rFonts属性必须与同段原文run一致(不多不少)。常见问题:多了hAnsi/cs/hint
    • pStyle:新增条款标题的段落样式必须与原文条款标题一致(如Heading4),不能用Style15等其他样式
    • 标题空格:原文"第六条违约责任"无空格→新增也不能有空格"第七条转包与分包"
    • 子编号顺延:章节编号改了(七→八),内部子编号(7.1→8.1)也必须改
    • 赔偿上限:双向条款的赔偿上限(限制甲方获赔)按规则"能删就删"
    • 内容去重:新增保密存续等条款前,检查原文是否已有同义表述
    • 脚注"法律顾问修订版":必须用修订格式(w:ins, author=WB),不能是普通文本
    • 文件命名:【修】+原文件名一字不动(含扩展名变化.doc→.docx)
  4. 通读全文:接受修订后的全文必须通读,检查WB插入的内容是否与上下文语句通顺、有无重复编号(原文问题不改,但要识别)
  5. Doro的期望
    • "打开文件查清楚再回答我"→ 必须用工具完整检查后才能下结论,不能凭印象
    • "看清楚前后文再回答我"→ 报告问题前必须理解修改点的上下文语境
    • "有没有该加粗没加粗的"→ 格式检查要全面(bold/style/indent/spacing都要对比)
    • "按照workflow的规则,手动修改"→ 发现问题后直接修复,不只是报告

修复操作要点

  • 用zipfile+lxml直接操作XML(不用python-docx修改tracked changes)
  • 修复后的INS rFonts只保留原文有的属性(通常eastAsia+ascii)
  • 子编号顺延:原文数字可能分散在多个run中(如"7"+".1 "两个run),只需DEL+INS第一个数字run
  • P49类大段文本需精确split run(保留前后文,只DEL中间要删的部分)
  • 修复后必须python-docx打开验证

触发条件

Doro对已交付的合同明确说"pass"(可以是单份或批量)。

⚠️ 绝对前提:Doro必须亲口说"pass"才能启动此流程。 合同交付后、workflow完成后、端午节合同end后——这些都不是pass。不要因为合同已交付就主动查xlsx/tracker或准备pass操作。Doro没说pass之前,对交付物的一切后续操作(更新tracker、更新xlsx、查重等)都不做。

⚠️ "我满意了"≠Doro说pass(2026-07-13教训):即使你作为审查负责人完成了质检、修复了所有问题、对文件满意,也绝不能自行执行pass流程。必须等Doro明确说"pass"。2026-07-13实证:白鹤劳务派遣协议修复完毕后自行做了pass,被Doro纠正"我没说pass你做什么pass",紧急撤销(tracker回退delivered+xlsx删行)。自主质检和pass是两个完全独立的步骤——前者是你的职责,后者是Doro的权力。

2026-06-15教训:两次被Doro纠正("我都还没说pass呢"、"今天的合同我都没说pass呢")——agent在合同交付后主动查xlsx是否已更新、准备补写tracker,被视为越权操作。 2026-07-13教训:白鹤劳务派遣协议自行质检完后直接执行pass,被纠正"我没说pass你做什么pass",紧急撤销。

手动审查交付物的完整检查清单references/manual-review-checklist-0713.md——当Doro要求"审查workflow修改的情况"时按此执行。核心:自主质检→发现问题直接修→报告→等Doro说pass。不问"需要修复吗",不自行pass。

特殊判定:"终身不通过"

Doro可能对某些合同说"终身不通过"——这意味着合同审查质量太差,永远不会pass

  • 不是返工:不是让你修了再交,而是直接否决
  • 处理方式:在tracker中标记status: "permanently_rejected",不进入pass流程
  • 反思:必须分析为什么质量差到这个程度,是workflow哪个角色出了问题,把教训记入historical-failures
  • 不要追问Doro:已经定性了就不要再烦,自己复盘

操作步骤(严格按顺序)

0. 交付物核对(写入前必做)

⚠️ PDF 批注交付件的独立核验 → 用 scripts/verify_pdf_annotation_deliverable.py <原件> <NC交付件> [本地核验件]:一键查 SHA256 字节一致、页数、原文未改动、批注数/author=WB、批注格式("建议"开头无【】)、高亮几何锚定。扫描件/PDF 合同走批注模式(非 docx 修订),docx 专项检查不适用,用此脚本替代。

Doro说pass后,在写tracker/xlsx之前,必须先核对交付物与源文件的匹配关系

  1. 取原始文件名:从交付文件名去掉【修】【无修改意见】前缀 → 得到原始文件名
  2. 待审查目录核实:去Nextcloud Doro合同审查任务/待审查/ 确认该原始文件名存在。不存在则停下来排查,不继续写入
  3. 顾问单位核对:确认要写入xlsx的顾问单位名称正确。⚠️ 不能盲信classifier的our_party_name——已有多次classifier误判案例(如智慧医院云项目合同classifier设为"朱家角"实际甲方是"卫健事业发展中心")。必须用python-docx打开合同原文,直接读取甲方名称
    • ⚠️ python-docx 的 paragraph.text 会吞掉 w:ins(修订插入)内容 → 读出的甲方可能是"补全前"的残缺名(2026-06-25 实证):当本次审查的改动之一就是"给甲方补全行政区前缀"(如 WB 修订插入「上海市青浦区」),交付件里甲方全称是「原稿可见文字 + w:ins 插入文字」拼起来的。python-docx 遍历 doc.paragraphs[i].text未必包含 ins 的文字,会让你误以为甲方还是缺前缀的简称。核甲方全称必须把 w:ins 算进去:用 zipfile 读 word/document.xml、对甲方那一段 ''.join(t.text for t in p.iter('{...}t'))w:t 不分 ins/非 ins,全收),或干脆遍历所有 w:ins 看 author=WB 插了什么。2026-06-25 项目终止协议书:paragraph.text 显示甲方「华新镇社区卫生服务中心」,实际交付件 w:ins 补了「上海市青浦区」,全称应是「上海市青浦区华新镇社区卫生服务中心」——只看 .text 就会把残缺简称写进 tracker/xlsx。写 party 前先确认你读的是"接受修订后"的完整甲方名。
    • 顾问单位名称空白的特殊情况(按合同来源分两路,2026-06-25 补全):合同正文里顾问单位名称是空白下划线(待签时填)时,无法从正文确定。先分清这份合同是谁的任务线,再决定问谁——别默认是邱律师:
      • 邱律师批量线(health-centers):私信邱律师询问归属。私信用 python3 ~/.hermes/scripts/wecom_dm.py --to qiuting --text "…"(或 _send_wecom(extra,'QiuTing',msg)),不用 send_message(target='wecom:X')(静默回退 home channel)。暂停 pass(不写 tracker/xlsx),邱律师回复后从步骤1继续;Doro 说过"邱律师回复了就直接补上 我不管了"——回复后自主补登记,不再找 Doro。
      • Doro 直接指派 / 非批量线(如幼儿园保密协议、劳动合同等非卫生中心合同):问发起 pass 的人本人(通常就是正在对话的 Doro),不要问邱律师,也不要自己瞎填占位符。
      • ⚠️ 绝不用泛指占位符当 party 写进 tracker/xlsx(如"幼儿园(园方,名称空白待填)")——2026-06-25 教训:园名空白我写了这种占位符,Doro 直接纠正"顾问单位是平和学校"。空白就停下来问准确法律主体全称,确认后再写。
    • 同名/近名主体消歧(2026-06-25 教训,写 party 前必做):拿到顾问单位名后,先 openpyxl 扫 xlsx 第 C 列看清单里是否已有多个相似名的主体——它们往往是不同法律实体,不能混用。本会话清单里同时有「上海青浦平和幼儿园有限公司」(seq13/15) 和「上海青浦平和双语学校」(seq44/210/211),一个园、一个校,是两个主体。判别靠合同内容性质:这份通篇"幼儿园/幼儿就读/保教费"→幼儿园主体;但既然清单里并存多个近名实体,最终仍用 clarify 让发起人拍板一个准确全称,并复用清单里既有的写法(保持前后一致,别造新写法)。
    • 更正已写错的 party(两处同步):若 tracker/xlsx 已写入后才被纠正,两处都要改:tracker 用原子写回(tempfile+rename)改对应 seq 的 party;xlsx 改第 C 列后重新 docker cp 上传 + occ files:scan + 清 OnlyOffice 缓存重启;最后从线上重新拉 xlsx + 读 tracker 核对两处一致才算完成。
  4. 查重:在tracker中检查是否已有相同 original_filename + party 的completed记录。有则为重复交付,不再写入

以上4步全部通过,才进入步骤1写tracker。任何一步不通过,停下来排查原因。

注意:Doro批量pass时可能列出审查意见文件(如"朱家角审查意见-xxx"、"采购协议-审查意见")。这些是主合同的附属交付物,默认不单独写tracker/xlsx条目。只需为主合同文件(【修】前缀的)写tracker和xlsx。审查意见文件如果不在交付目录中(可能已被清理或因classifier误判未生成),不影响主合同的pass流程——跳过即可,不需要报错或追问Doro。

例外:用户明确说“任务交付文件夹里所有的合同及companion,都做pass流程”时(2026-07-13 Doro明确)

当 Doro 用当前任务交付目录全量授权的口径下指示:

  • “任务交付文件夹里所有的合同及companion,都做pass”
  • “现在这一刻,Nextcloud-任务交付里,所有的合同和companion,都做pass流程”

此时必须把 companion 纳入 pass 核查范围,但仍要遵守:

  • companion 不是独立合同,不单独登记 excel,不单独占 seq
  • tracker/xlsx 的登记主体仍然是主合同
  • companion 的处理应当体现在“该主合同已连同 companion 一并核查/补做pass”的结果里,而不是把每个 companion 当主合同单独建台账。

执行顺序

  1. 先把当前 任务交付/ 目录全部文件列出来,不能只围绕当前对话那一份合同;
  2. 再识别哪些是主合同、哪些是 companion;
  3. 主合同逐份做 pass / tracker / xlsx;
  4. companion 只做挂靠核查,不单独占 excel 行;
  5. 回复时明确区分“主合同已登记几份、companion 已核查几份”,不要混成一类。

2026-06-11教训:安全测试合同因跳过核对,第一次把原始文件名写进xlsx文件名列(没有【修】前缀),发现后补写但未清理错误记录,导致tracker和xlsx各多一条重复数据。 2026-06-12教训:手动写xlsx时列顺序写反(日期和序号对调、文件名列写成原始文件名而非delivered_filename、顾问单位列写成"已完成"状态文字)。写xlsx前必须先读上一行确认列顺序,不要凭记忆。正确列顺序:A=序号, B=日期, C=顾问单位, D=合同名称, E=文件名(delivered_filename)。

1. 更新 Tracker JSON

路径:~/.hermes/data/contract-tracker.json

状态流转(新增 delivered 中间态,2026-07-02 确立)

文件到达 → workflow审查 → deliverer交付成功 → queue-runner写入 status=delivered
                                                       ↓
                                          Doro说pass → status=completed
                                                       ↓
                                              24h后 → cleanup清理

三道防线防重复:

检查点 逻辑
queue-runner 启动前 查 tracker,deliveredcompleted → 直接 SKIP 并移入 done/
watchdog resume 前 查 tracker,已交付的 thread 不恢复
deliverer 写入时 exists 检查,同文件不重复写入

Pass 时的写入逻辑

如果 tracker 中已有该 original_filenamestatus=delivered → 更新该记录为 completed,补全 seq/party/contract_name/delivered_filename/converted_filename/xlsx_updated_at/cleaned=false

如果 tracker 中没有记录(旧合同、手动审查等情况)→ 新建完整记录,直接 status=completed

完整记录示例:

{
  "original_filename": "消防设施检测服务合同(练塘卫生院).doc",
  "delivered_filename": "【修】消防设施检测服务合同(练塘卫生院).docx",
  "converted_filename": "消防设施检测服务合同(练塘卫生院).docx",
  "party": "上海市青浦区练塘镇社区卫生服务中心",
  "contract_name": "消防设施2026年度检测服务合同",
  "seq": 175,
  "status": "completed",
  "delivered_at": "2026-06-09T18:30:00+08:00",
  "xlsx_updated_at": "2026-06-09T20:09:00+08:00",
  "cleaned": false
}

字段说明:

  • original_filename:邱律师发来的原始文件名(可能是.doc)
  • delivered_filename:交付到任务交付目录的文件名(【修】前缀)
  • converted_filename:workflow转换后的.docx文件名(原始是.doc时有值,否则空字符串)。来源:workflow的converted_filename字段会从classifier一路传递到deliverer/final_review输出。pass流程必须从workflow输出中提取并写入tracker。如果workflow输出中没有此字段(旧workflow跑的合同),则根据original_filename判断:以.doc结尾的,converted_filename = 同名.docx;以.docx结尾的,converted_filename = 空字符串
  • seq:合同审查清单中的序号
  • delivered_at:workflow 交付完成时间(queue-runner 写入)
  • xlsx_updated_at:pass 时写入,北京时间ISO格式,清理脚本据此计算24h
  • cleaned:清理脚本执行后改为true

写入方式:先写临时文件再rename(原子操作),防止进程中断导致JSON损坏。

import json, os, tempfile
from datetime import datetime, timezone, timedelta

BJT = timezone(timedelta(hours=8))
tracker_path = os.path.expanduser('~/.hermes/data/contract-tracker.json')

# 读取现有tracker
if os.path.exists(tracker_path):
    with open(tracker_path, 'r') as f:
        tracker = json.load(f)
else:
    tracker = {"contracts": []}

now = datetime.now(BJT).isoformat()

# 查找是否已有 delivered 记录
existing = None
for c in tracker["contracts"]:
    if c.get("original_filename") == original_filename:
        existing = c
        break

if existing and existing.get("status") == "delivered":
    # 已有 delivered → 更新为 completed(补全字段)
    existing["status"] = "completed"
    existing["delivered_filename"] = delivered_filename
    existing["converted_filename"] = converted_filename
    existing["party"] = party
    existing["contract_name"] = contract_name
    existing["seq"] = seq
    existing["xlsx_updated_at"] = now
    existing["cleaned"] = False
elif existing and existing.get("status") == "completed":
    # 已经 completed → 查重命中,不重复写入
    pass
else:
    # 无记录 → 新建(旧合同/手动审查走这条路)
    tracker["contracts"].append({
        "original_filename": original_filename,
        "delivered_filename": delivered_filename,
        "converted_filename": converted_filename,
        "party": party,
        "contract_name": contract_name,
        "seq": seq,
        "status": "completed",
        "xlsx_updated_at": now,
        "cleaned": False
    })

# 原子写入
fd, tmp = tempfile.mkstemp(dir=os.path.dirname(tracker_path), suffix='.json')
with os.fdopen(fd, 'w') as f:
    json.dump(tracker, f, ensure_ascii=False, indent=2)
os.replace(tmp, tracker_path)

2. 更新合同审查清单 xlsx

路径(Nextcloud):Doro合同审查任务/合同审查清单.xlsx

⚠️ 铁律(2026-07-03覆盖事故后确立):写xlsx前必须从Nextcloud实时拉取最新版本,禁止使用/tmp或本地任何已有的xlsx文件。 违反此规则会用旧版覆盖新版,导致其他session已登记的记录丢失(2026-07-03实证:用240行旧文件覆盖了252行最新版,丢失12条记录)。

强制检查流程

  1. docker cp 从NC拉取 → 保存到 /tmp/合同审查清单_LIVE.xlsx(带LIVE后缀避免与残留文件混淆)
  2. 打开后先读 ws.max_row 和最后一行的seq → 打印确认
  3. 如果发现本地已有同名文件,必须删除后重新拉取,不得复用
  4. 追加新行后上传

步骤:

  1. 从Nextcloud下载最新xlsx(必须实时拉取,禁止复用本地文件)
  2. 用openpyxl追加行(序号、日期、顾问单位全称、合同名称、文件名)
  3. 从上一行复制字体和对齐样式(用copy())
  4. border用Side对象重建(不用ref_cell.border直接赋值,会报unhashable错误)
  5. 保存后上传回Nextcloud
  6. 执行files:scan + 清OnlyOffice缓存
# 下载(铁律:必须每次实时拉取,rm掉旧文件防止复用)
rm -f /tmp/合同审查清单_LIVE.xlsx
docker cp nextcloud-nextcloud-1:/var/www/html/data/doro/files/Doro合同审查任务/合同审查清单.xlsx /tmp/合同审查清单_LIVE.xlsx
sudo chown maggie:maggie /tmp/合同审查清单_LIVE.xlsx

# 上传
docker cp /tmp/合同审查清单_LIVE.xlsx nextcloud-nextcloud-1:/var/www/html/data/doro/files/Doro合同审查任务/合同审查清单.xlsx
# 上传
docker cp /tmp/合同审查清单_LIVE.xlsx nextcloud-nextcloud-1:/var/www/html/data/doro/files/Doro合同审查任务/合同审查清单.xlsx
python3 -c "import openpyxl; ws=openpyxl.load_workbook('/tmp/合同审查清单_LIVE.xlsx').active; print(f'NC当前版本: {ws.max_row}行, 最后seq={ws.cell(ws.max_row,1).value}')"

# 上传
docker cp /tmp/合同审查清单_LIVE.xlsx nextcloud-nextcloud-1:/var/www/html/data/doro/files/Doro合同审查任务/合同审查清单.xlsx
docker exec nextcloud-nextcloud-1 chown www-data:www-data /var/www/html/data/doro/files/Doro合同审查任务/合同审查清单.xlsx
docker exec -u www-data nextcloud-nextcloud-1 php occ files:scan --path="doro/files/Doro合同审查任务/合同审查清单.xlsx"

# 上传后验证(write-read-verify,防止覆盖事故)
rm -f /tmp/合同审查清单_VERIFY.xlsx
docker cp nextcloud-nextcloud-1:/var/www/html/data/doro/files/Doro合同审查任务/合同审查清单.xlsx /tmp/合同审查清单_VERIFY.xlsx
python3 -c "import openpyxl; ws=openpyxl.load_workbook('/tmp/合同审查清单_VERIFY.xlsx').active; print(f'上传后验证: {ws.max_row}行, 最后seq={ws.cell(ws.max_row,1).value}')"

# 清OnlyOffice缓存
docker exec nextcloud-onlyoffice-1 bash -c 'rm -rf /var/lib/onlyoffice/documentserver/App_Data/cache/files/data/*'
docker restart nextcloud-onlyoffice-1

3. 确认并汇报

向Doro确认已标记completed,告知清单已更新到第几条。

批量pass后的登记完整性审计(2026-06-15教训)

⚠️ Doro说"都pass了"或"看下是否都走了pass流程"时,必须把每一份pass的合同逐一对照 tracker JSON 和 xlsx 两个存储,找出缺漏——不能假设"workflow跑完了/交付了"就等于"登记完整"。

2026-06-15教训:6份合同队列全部跑完并交付,Doro说6份都pass。实际核对发现只有2份(端午节、医疗急救招聘)走了完整pass流程,另外4份(金泽蛋糕、赵巷消防、练塘健康积分、徐泾维保)tracker和xlsx全都没登记。Doro主动提醒"excel登记是不够的"。原因:交付由workflow的final_review完成,但pass流程(写tracker+xlsx)是独立的人工步骤,前面几份漏做了。

审计脚本逻辑(用openpyxl+json对照):

  1. 列出本批次所有pass合同的 delivered_filename(从任务交付目录或Doro的pass消息提取)
  2. 读 xlsx 第E列(文件名列)建一个 set
  3. 读 tracker.json 的 contracts[].delivered_filename 建一个 set
  4. 逐份合同检查:in xlsx? + in tracker?,打印缺失矩阵
  5. 对缺失的合同,完整补走步骤0(待审查核实+正文读甲方+查重)→ 步骤1(tracker)→ 步骤2(xlsx),不能只补一个存储
  6. 补完后重新跑一遍审计脚本,确认6份全部 xlsx + tracker

xlsx与tracker必须成对存在:xlsx是给人看的登记,tracker是给cleanup cron用的。只有xlsx没tracker→文件永远不被自动清理;只有tracker没xlsx→Doro的清单缺条目。审计时两个都要查。

openpyxl写xlsx的权限陷阱:从Nextcloud用sudo cp下载的xlsx属主是root,openpyxl保存时报PermissionError。先sudo chown maggie:maggie改属主到当前用户的可写副本再操作。

触发模式

Doro会回复引用某条交付通知消息并说"pass"。可能是单份也可能批量("四份都pass")。

  • 引用的消息中包含交付文件名,从中提取合同信息
  • 批量pass时逐份处理,每份都写tracker+xlsx

审查意见文件的处理

Doro可能把审查意见文件(如"朱家角审查意见-XXX.docx"、"采购协议-审查意见.docx")也列入pass清单。这些是合同的伴随交付物,不需要单独的tracker条目和xlsx行——它们跟随主合同的tracker记录:

  • 主合同"【修】XXX.docx" → 写tracker + 写xlsx
  • 伴随审查意见文件 → 不写tracker,不写xlsx
  • 清理时:审查意见文件跟随主合同一起清理

Classifier甲方误判的pass处理(2026-06-12教训)

当workflow classifier误判了顾问单位(如把"卫健事业发展中心"误判为"朱家角"),pass流程写tracker/xlsx时必须用合同正文中的真实甲方名称,不能用classifier的our_party_name

  • 步骤0核对时用python-docx读取合同正文前30段,找甲方全称
  • tracker的party字段和xlsx的"顾问单位"列写真实甲方

清理cron配套信息

  • Cron job name: contract-cleanup,job_id: 4636467b715d
  • 脚本路径: ~/.hermes/scripts/contract-cleanup.py
  • 调度: 每小时,no_agent静默模式
  • 逻辑: 读tracker → 找completed + xlsx_updated_at超24h + cleaned=false → docker exec删Nextcloud待审查/和任务交付/中的文件 → 标记cleaned=true → files:scan
  • 安全: 只删tracker中精确记录的文件名,不通配。xlsx在上一级目录碰不到。JSON损坏时静默不操作。原子写入(tempfile+rename)
  • .doc→.docx转换残留(2026-06-11发现+修复):原始文件为.doc时,workflow会用libreoffice转换为.docx副本放在待审查目录。cleanup脚本已有converted_filename字段的读取逻辑(第113-116行),但旧workflow跑的合同tracker中缺少此字段导致转换文件残留。治本修复(2026-06-11已实施):在review-contract.yaml的classifier procedure中添加converted_filename记录步骤,从classifier→reviewer→editor→deliverer→final_review全链路传递该字段。pass流程写tracker时从workflow输出中提取。cleanup脚本自动生效。
  • 手动批量清理(Doro授权模式):Doro可能要求按日期批量清除旧文件(不走tracker逻辑),此时按修改时间(stat %Y)过滤,在Nextcloud容器内执行rm,完成后files:scan+清OO缓存。2026-06-11执行过一次:保留6月10日及之后的文件,删除之前的全部(待审查11个+任务交付179个)。

已知坑

  • 时间窗不能先拍脑袋,必须先用工具确认当天与"上周五"的北京时间边界(2026-07-13 再犯后补强):用户要求"查看北京时间上周五及今天,邱律师(查sender id)发了多少合同"时,不能直接按自己理解硬算时间窗,更不能在没交叉验证前就下"周五=0份"结论。必须至少两步核对:①先用 TZ='Asia/Shanghai' date 确认当前北京时间与星期;②把北京时间窗口换算成 UTC 后,再同时查 .metagateway.log只要 gateway.log 已出现 QiuTing 的空消息(msg='')文件记录,就不能再说该时段 0 份。

  • 查"邱律师发了多少合同"必须双源交叉,不可只靠 cache meta(2026-07-13 教训):仅查 ~/.hermes/cache/documents/*.meta 可能漏掉周五文件或误判为 0。标准做法:

    1. .meta:按 sender_id == QiuTing + 北京时间窗口换算后的 UTC 区间统计
    2. gateway.log:按同一时间窗 grep platform=wecom user=QiuTing chat=QiuTing msg='',这是文件消息的一手证据
    3. tracker + xlsx:核对这些文件是否都 workflow 完成、是否都 pass、是否都登记 excel
    4. 没有把两源对齐前,不得下"都完成了/没有遗漏"结论
  • 被 Doro 说"胡说八道,重新查,查明情况再说"时,表示你刚才的结论缺乏证据链:此时必须回退到原始数据源重查,不得只改措辞继续硬答。正确做法:先承认上一条没查明,再用不同数据源(如从 meta 转到 gateway.log)交叉验证。

  • 审查workflow交付物时必须区分原文自带修订 vs WB修订(2026-07-13 Doro指示):Doro要求"对照原文件,WB-1如果是原文自带修订,保持不动。你只需要看workflow是不是准确遵守了workflow的规则,包括命名规则"。具体方法:①用zipfile读原文件(待审查目录)的revision authors,确认哪些author是原文已有的 ②读交付件,按author过滤只看WB的修订 ③逐条核对WB修订是否符合审查规则 ④原文自带的修订(其他author)不评价、不报告为问题。常见误判:把原文WB-1的修订当成workflow的WB修订来审查——必须先确认author再下结论。

  • 核查汇报必须先用工具验证再说(2026-07-01+07-02 升级为顶部红线):详见本skill顶部「红线:验证指令 ≠ 回忆指令」章节 + references/audit-methodology.md。2026-07-02 同一轮对话中此规则被违反4次,导致Doro信任破裂("到了无法信任你的程度")。不再重复列举——执行时加载顶部红线即可。

  • "确定吗?再仔细查实"(2026-07-02 追加):Doro说"确定吗""再仔细认真查实"时,意思是上一条回复的结论不够可信/不够深入。正确做法:不要只是重复上一轮的输出,而是用不同角度/更深层的工具去交叉验证。具体:①查session_search看是否有其他session做过相同操作 ②查uwf step list确认每个thread走到了哪一步 ③确认通知确实被发出(session中有_send_wecom的tool call记录)而非假设。核心原则:每一条"确认X发生了"的结论,都必须有对应的工具调用输出作为证据。

  • 文件"消失"的排查思路(2026-07-01 教训):Doro发现待审查/交付目录文件不在时,不要急着说"被误删"。先排查:①tracker 的 cleaned 字段是否已为true(cron清理了)→ 不可能不到24h;②auto_notify 是否正常工作(文件可能从来没上传到待审查目录,workflow直接从cache路径读文件完成审查,但Doro在Nextcloud看不到);③是否是手动操作中 docker exec rm 删了。根因很可能是"从来没上传到待审查"而不是"上传后被删了"。

  • 交付件被 Doro 手动编辑后大小/内容会变 —— pass 时不覆盖、只登记(2026-06-23 实证):Doro 收到交付件后可能自己在 OnlyOffice 里编辑(删掉部分批注内容、改条款等),导致 Nextcloud 上的交付文件大小/字节/md5 与 workflow 原始交付件不同;且 OnlyOffice 后台处理期间文件大小会持续变化,docker cp 出来用 pymupdf/python-docx 读可能是 0 页/0 批注的中间态。看到交付件与你核验时不一致,先确认是不是 Doro 自己改的,绝不要当成文件损坏去"修复"或用本地完整版覆盖。 Doro 说 pass 后:以 Doro 改后的版本为准,只写 tracker+xlsx 登记,不触碰交付目录里的文件。2026-06-23 教训:交付 PDF 从 20MB 变 3.5MB 且持续变化,我误判损坏、准备用本地版覆盖,Doro 说"是我删除了一些内容,你直接做 pass 就可以"。

  • PDF 扫描件合同走批注模式,不 OCR 转 docx(2026-06-23 Doro 明确):收到扫描件 PDF 合同(无文字层)时,不要纠结"OCR 转 docx 才能修订"——workflow 对 PDF 用批注模式审查(高亮 Highlight + 批注气泡 Text 配对,author=WB),原文零污染,这是正常流程(seq=201 朱家角.pdf、seq=207 阳澄湖团建.pdf 都是 PDF 批注交付)。Doro 原话"PDF 的修改使用批注,这是 workflow 的正常流程"。PDF 交付件核验/pass 时:核批注数/author/高亮锚定位置/原文页数不变,不套用 INS/DEL/字号/numPr 那套 docx 专项检查(final_review 对 PDF 会自动判定这些不适用)。

  • 交付文件位置:根目录 任务交付/ 是正确的(2026-06-29 确认):workflow deliverer 明确写着上传到 Doro合同审查任务/任务交付/(根目录)。不要把文件移到顾问单位子文件夹——那里不是交付目的地。pass 流程步骤0核对时,交付文件应该在根目录 任务交付/ 中。如果 deliverer 同时上传到了子文件夹(如 final_review 越权重复上传),子文件夹里的是多余的,应清理。

  • final_review 越权重复上传(2026-06-29 教训):workflow 中上传是 deliverer 的职责,final_review 只负责质量检查和通知 Doro。但 LLM 执行 final_review 时可能越权上传文件(到子文件夹而非根目录 任务交付/),且使用完全不同的命名格式。判别stat 比较文件大小,同内容两份不同名=重复。处理:保留 deliverer 上传的(【修】/【审】+ 原始文件名),删除 final_review 额外上传的。根因:review-contract.yaml 的 final_review procedure 需修改,明确禁止上传动作(待 WeiWei 实施)。

  • 审查意见标题空壳(2026-06-29 教训):workflow editor 生成的审查意见文档标题是"关于《合同》的审查意见"——"《合同》"是占位符,没有替换为实际合同名称。classifier 已正确提取了 contract_title,但 editor 生成审查意见时没有填入。pass 流程必须检查审查意见标题:如果标题是"关于《合同》的审查意见",需要手动修正为"关于《{合同实际名称}》的审查意见"。修正方法:用 zipfile + lxml 读 document.xml,找到标题段落的所有 w:t 节点,第一个设为完整标题,其余清空。

  • 【审】审查意见文件缺失(2026-06-30 发现):deliverer 有时只上传【修】修订版而没有上传【审】审查意见文件。排查sudo find .../任务交付/ -name "【审】*" 检查是否有对应的审查意见。处理:如果缺失,需要手动生成或报告Doro确认是否需要补做。审查意见是companion文件,不影响主合同的pass流程(不需要tracker/xlsx条目),但Doro可能期望看到。

  • Workflow常见格式缺陷清单(2026-07-13 审计总结,详见 references/pre-pass-audit-checklist-20260713.md

    1. WB INS rFonts多余属性(hAnsi/cs/hint)——每份都有,每次审计必查必清
    2. 新增条款标题pStyle错误(用了Style15而非Heading4等)
    3. 新增标题带空格("第七条 转包"应为"第七条转包")
    4. 子编号未顺延(只改了章编号,没改内部X.Y编号)
    5. 赔偿上限遗漏(双向条款的20%上限未删)
    6. 保密存续重复插入(原文已有同义表述)
    7. 脚注未用修订格式
  • 同模板合同修订一致性(2026-07-03 Doro确认:手动调整):不同顾问单位提交审查的合同若为相同模板,修订需保持一致(规则9已有)。但不改workflow自动化逻辑——Doro指出不一致时手动调整即可,避免矫枉过正。不需要template_type标签或修订库。

  • 非合同文件进入workflow审查(2026-07-01 香花桥招标需求实证):classifier识别出"招标需求文件"但仍走了完整review+edit流程并交付了修订版。Doro判定此类文件无需审查,直接通知邱律师。已修auto_notify加L1文件名预筛(关键词:招标需求/技术方案/报价单等)。但classifier兜底层未修——仍然会把非合同当合同审。根因:workflow的routing graph没有从classifier直接到$END的non-contract路径。pass前排查:如果交付物对应的原始文件明显不是合同(招标需求/投标文件/会议纪要等),直接删除交付物并通知邱律师,不做pass。

  • 审查意见文件是错误交付物(2026-07-01 发现):workflow 有时会生成不该有的审查意见文件(如合同已被其他thread审查过、或classifier误判导致多生成)。pass前排查:检查任务交付目录中是否有不属于本次审查的【审】文件。如果是错误交付物(如旧版残留、重复审查产物),直接删除不pass。判断标准:审查意见的标题和内容是否对应本次审查的合同,标题是否是"关于《合同》的审查意见"(占位符)。

  • cleanup脚本不处理tracker顶层"ready"条目 + 不清理本地目录(2026-07-03 查明根因)contract-cleanup.py 只遍历 tracker["contracts"] 数组中 status=completed + cleaned=False 的条目。但以下两类文件不会被清理:

    1. Tracker顶层字典键(status=ready的条目):这些是workflow跑完但尚未被Doro pass的合同,存储在tracker的顶层键而非contracts数组里。cleanup脚本看不到它们。Doro pass后,pass流程会把它们从顶层搬到contracts数组并标记completed——此时cleanup才能处理。如果pass流程有bug没搬进contracts数组,文件永远不会被清理。
    2. 本地 ~/.hermes/shared/Doro合同审查任务/待审查/ 目录:cleanup脚本只删Nextcloud容器内的文件(docker exec rm),本地shared目录的副本无人管理。这些是auto_notify上传NC后留下的本地副本——NC侧被cleanup清了,本地的永远残留。
    • 诊断方法ls ~/.hermes/shared/Doro合同审查任务/待审查/ 如果有文件,且对应的tracker条目已经completed/ready,就是此bug。
    • 临时清理:确认文件在tracker中已completed后,手动 rm 本地副本即可。
    • 根治:cleanup脚本需要增加两段逻辑:①遍历tracker顶层ready条目(Doro已pass但pass流程漏搬的)②清理本地shared/待审查/中已completed的文件。
  • 沟通时间统一使用北京时间(2026-07-03 Doro多次纠正):所有对外沟通(包括向Doro汇报workflow状态、文件接收时间等)一律使用北京时间。服务器UTC时间仅在内部日志/脚本中使用,不对用户展示。

  • Doro说"查清楚"/"你去查原因"——必须用工具验证后再给结论:Doro要求查证时,不能凭记忆或context summary回答。必须实际调用工具(session_search、terminal读文件/目录、tracker等)取得证据后再汇报。2026-07-13教训:被要求"查清楚我哪些说了pass"时,需要在本session对话记录中找到Doro的原话,不能凭印象列清单。

  • 脚注"法律顾问修订版"必须用修订格式(2026-07-13 Doro纠正):添加页脚文字时,必须包裹在w:ins元素中(author=WB),不能作为普通文本直接写入。OnlyOffice/Word中显示为带修订标记的新增内容。实现方式:用python-docx添加footer文本后,再用zipfile+lxml找到footer XML中的对应run,包裹进w:ins元素。

  • 交付件字体大小不一致(2026-07-01 Doro纠正,根因细化):有两种常见成因:

    1. 段内混用:同一段落内不同run使用不同字号(如sz=21和sz=24混用)。修复:找dominant size统一。
    2. INS-only新段落缺sz(更隐蔽):add_clause插入的全新段落,原文docDefaults无sz定义或sz≠邻居段落的显式sz。新段落INS run不写sz→继承docDefaults→与显式sz=24的邻居段落不一致。诊断:找所有INS-only段落(整段只有w:ins),检查其run的rPr是否有sz,再对比前后段落的sz。缺sz且邻居有显式sz=补上。
    3. numPr叠加:加了手动编号但没去掉原自动编号→显示"1. 第一条"。修法见contract-editor skill的"strip numPr"规则。
  • deliverer 重复上传(同内容两份不同名)(2026-06-29 教训):deliverer 可能对同一合同上传两次,文件大小完全一致说明内容相同。判别stat 比较文件大小。处理:保留命名规范的那份(【修】/【审】+ 原始文件名),删除另一份。

  • 原文件自带【修】前缀时命名错误(2026-07-13 璞石合同实证):邱律师发来的文件本身已有【修】前缀(如【修】练塘-硬件购销合同-璞石医疗2026.7.13.wps),workflow按规则应生成【修】【修】练塘-...docx(原文件名一字不动,前面再加【修】),但实际只生成了【修】练塘-...docx(吞掉了原文的【修】前缀)。审查时判别:原文件名含【修】时,交付文件名应出现两个【修】。只有一个=workflow命名错误。根因:workflow的命名逻辑strip了原文件名中已有的【修】前缀后再加自己的。

  • Cache hash前缀混入交付文件名(2026-06-30 实证):deliverer 有时把 cache 文件名直接当交付文件名上传,产生类似 【修】doc_0ad4ee63bff5_印刷品制作合同2026.6(1).docx 的文件名。判别:文件名含 doc_[a-f0-9]{12}_ 模式。处理:删除带 hash 前缀的那份,保留正确命名的(【修】印刷品制作合同2026.6(1).docx)。根因:deliverer 从 cache 复制文件时没有去掉 cache hash 前缀重命名。

  • companion 文件(审查意见/流程单)不会被 cleanup 自动清理 → 永久残留孤儿(2026-06-21/29 实证)contract-cleanup.py 只删 tracker 里 original_filename/converted_filename/delivered_filename 三个字段精确匹配的文件。companion 从不进 tracker → 主合同被清理后 companion 永远残留。排查:运行 references/cleanup-audit.py 审计脚本,列出所有 not-in-tracker 的文件。应急清理(Doro 授权后)

    # 必须搜索所有任务交付路径(根目录+顾问单位子目录都可能有)
    sudo docker exec nextcloud-nextcloud-1 find /var/www/html/data/doro/files/Doro合同审查任务/ -path "*任务交付*" -name "*审*意见*"
    # 确认后逐个删除
    sudo docker exec nextcloud-nextcloud-1 rm -f "<path1>" "<path2>" ...
    # 扫描+清缓存
    sudo docker exec -u www-data nextcloud-nextcloud-1 php occ files:scan doro --path="/doro/files/Doro合同审查任务/"
    docker exec nextcloud-onlyoffice-1 bash -c 'rm -rf /var/lib/onlyoffice/documentserver/App_Data/cache/files/data/*'
    docker restart nextcloud-onlyoffice-1
    

    ⚠️ Companion 可能同时存在于根目录 任务交付/ 和顾问单位子目录(如 朱家角镇社区卫生服务中心/任务交付/)——find 命令用 -path "*任务交付*" 通配搜索确保不遗漏(2026-07-06 实证:肃言/恭兴审查意见各在两个位置共4份)。 治本方案见 references/companion-cleanup-proposal.md,需 WeiWei 决策后实施。

    • Companion 判定不能靠想当然命名(2026-07-13 白鹤劳务派遣协议教训):Doro说“交付文件夹里的合同及companion全部做pass”时,先查清交付文件夹里到底有哪些相关文件,再决定有没有 companion。不要因为很多合同通常会带审查意见,就默认本案也有;也不要只看主文件名一次就下结论。正确顺序:①先在 任务交付/ 中做宽匹配扫描(合同名关键词、当事人名关键词、业务关键词都要扫)②再把整个 Doro合同审查任务/ 目录补扫一遍,确认没有藏在别的子目录里的 companion ③最后再对 tracker+xlsx 核对是否已有 completed 记录。如果实扫结果只有主合同一份,就要明确汇报“合同1份、companion 0份”,而不是笼统说“都pass了”。
  • Companion 命名规则(2026-07-01 Doro确认):交付物=【修】{原文件名},companion=【审】{原文件名去扩展名} 审查意见.docx。pass skill 扫描时按此规则拼确定性文件名+nc_file_exists()查询,查到就写入 tracker 的 companion_files 字段。cleanup 脚本读 companion_files 一并删除。两头(pass skill + cleanup script)必须同步改才有效果。workflow editor/deliverer 生成审查意见时也必须强制用此命名规则。

  • 手动启动workflow前必须查实auto_notify是否已处理(2026-07-03 教训):发现cache中有新文件时,不要直接uwf thread start。必须先:①查/tmp/auto_notify_new_file.log确认auto_notify是否已检测到并启动了workflow ②uwf thread list | grep running\|idle看是否已有对应thread在跑。auto_notify正常工作时会自动上传NC+启动queue runner+排队执行,手动启动会导致重复审查。2026-07-03实证:邱律师发了文件,auto_notify 30秒内就启动了workflow(thread 06FJDADW),我没查就又手动启动了第三个重复thread,被Doro纠正。

  • 不判断文件是否相同/重复(2026-07-03 Doro纠正):邱律师发了几次、发什么文件,不是我该判断的。不要说"同上""同名文件""是重复的"——即使md5一致也不做这个判断。auto_notify和workflow自己处理,我不干涉。待审查目录里有几份就是几份,workflow跑几个就是几个。

  • 沟通必须使用北京时间(多次纠正):所有与Doro/Maggie的时间沟通一律用北京时间,包括表格、汇报、日志引用。服务器是UTC,必须+8转换后再输出。不写UTC时间。

  • "删掉批注"指令——先验证再操作(2026-07-08):Doro可能要求"删掉批注,提交后做pass"。操作前必须先用zipfile检查comments.xml是否存在+commentRangeStart数量。如果文件无批注(comments.xml不存在且无comment引用元素),直接汇报"文件无批注"然后继续后续操作(提交/pass),不需要强行"删除"不存在的东西。

  • 邱律师同名文件不自行判断是否重复(2026-07-03+07-09 加强):邱律师发的多次同名文件,不要自己判断"重复发送"。同名文件可能:①不同顾问单位②修改版(字节不同=内容变了)③同一份发了两次。只要字节大小不同,就必须视为不同合同独立审查。2026-07-09教训:字节差407的两份合同有3处实质性条款差异(项目名称、支付方式、合同期限),不是"重复发送"。正确做法:检查字节是否相同→不同→独立审查或私信邱律师确认。

  • 交付件大小/内容突变,先确认是不是用户手动编辑了,别当损坏(2026-06-22 实证):pass 前看到 Nextcloud 上的交付 docx/pdf 大小骤变(如 20MB→3.5MB 还在持续变、md5 对不上本地核验版),第一反应判为"文件损坏/被进程写坏"。先确认是不是 Doro 自己在 OnlyOffice 里改了(删批注、调内容)。确认是用户编辑后:不覆盖、不动交付目录的文件,以用户改后版本为准,直接走 pass 登记(tracker+xlsx)。铁律:pass 登记只动台账(tracker/xlsx),交付目录里的成品文件除非用户明确要求重做,否则只读不写。

  • .doc 原始文件残留(2026-06-29 实证):tracker 的 original_filename 有时记录了 .docx(转换后文件名)而非 .doc(真正的原始文件),cleanup 按 .docx 去删找不到 .doc → 残留。排查:cleanup-audit.py 会标记为 NOT_IN_TRACKER。预防:pass 写 tracker 时确保 original_filename 是邱律师发来的真实文件名(含扩展名),不要写转换后的文件名。

  • 编号"错误"误判——markup视图双编号是正常现象(2026-06-16教训):OnlyOffice/Word修订视图(markup)下,自动编号列表项被删除(含他人如屠佳青的删除)或新增条款插入后,后续项会显示"(3)(2)""(4)(3)"这类双编号——前一个是删除/插入前的旧编号,后一个是接受修订后的新编号。这是track changes的正常渲染,不是编号错误。Doro/Maggie说某份合同"编号修改错误"时,先别急着改:

    1. 用execute_code生成"接受所有修订后"的版本:删除所有w:del元素 + 删除带段落标记删除(pPr/rPr/del)的整段 + 解包所有w:ins(把ins的子元素提到父级再删ins壳)
    2. 用OnlyOffice x2t渲染accept版PDF,pdftotext看最终编号是否连续正确
    3. 若accept后编号正确 → workflow没改错,markup双编号是正常的,恢复workflow原版即可,不要擅改
    4. 若要改,必须先问清Doro/Maggie指的是accept后哪一处,他人(屠佳青等)的修订按铁律不能动
    • 2026-06-16教训:把端午节合同(转包6误改成7)、医疗急救招聘合同的正常编号重排误判为错误并擅自改动,被Maggie两次纠正"workflow改得都没错,恢复成workflow修订版本"。x2t accept渲染命令见本skill的"用OnlyOffice渲染自查"或contract-editor skill。

    • 但确有真编号重复时要修(2026-06-15端午节实测,与上条不冲突):自动编号列表项(numbering.xml中numId对应的abstractNumstart=NlvlText=%1、)渲染出的编号,与后续手动键入数字的tracked插入条款(w:insw:t文本直接以"6、""7、"开头)会真冲突。本案"售后服务"是auto-number(numId=3, start=6 → 渲染为6),紧跟的WB插入条款手动写了"6、转包限制" → 出现两个6。这是真错误,不是markup双编号假象。判别要点:

      1. 假象(不改):同一条款同时显示两个编号"(3)(2)",是track changes接受前后的新旧编号叠加 → accept后渲染连续就别动
      2. 真错(要改):两个不同条款各自显示同一个数字(售后服务=6、转包限制=6)→ accept后仍重复 → 必须修
      3. 修法:直接改w:insw:t的手动编号文本("6、转包限制"→"7、转包限制"),后续手动编号条款顺延(违约责任7→8、争议解决8→9,否则会冒出两个7)。用zipfile读document.xml→字符串replace(先assert xml.count(old)==1确认唯一)→zipfile写回,不破坏w:ins修订痕迹。改完用python-docx确认可打开+遍历w:ins确认三条仍在修订态(author=WB)
      4. Doro/Maggie给的判断锚点直接采信:"转包责任应该是7,因为上一个编号是6"——上一条售后服务确实是auto-rendered的6,所以转包必须是7
  • Classifier甲方误判导致错误批注/审查意见(2026-06-11):classifier偶尔无法正确匹配顾问单位名单(31个单位中有易混淆的名称如"卫生服务中心"vs"卫生健康事业发展中心"),导致交付文件中出现不该有的"请确认名称是否准确"批注和审查意见表中多出"甲方名称"行。Doro说pass前如果发现此问题,必须先手动修复(删comment+删table row+重新上传)再走pass流程。修复方法见contract-reviewer skill。

  • 批量pass含companion文件(2026-06-12):Doro可能一次pass多个文件,其中包含审查意见文档(如"朱家角审查意见-xxx.docx""采购协议-审查意见.docx")。审查意见是companion文件,不需要单独在tracker/xlsx中记录——只记录主合同(带【修】前缀的文件)。判断规则:有【修】前缀的是主合同文件,需要tracker+xlsx;"审查意见"结尾的是companion,不单独记录。

  • 沟通时间一律使用北京时间(2026-07-03 Doro多次纠正):向Doro汇报任何时间信息时,必须转换为北京时间(UTC+8)。服务器是UTC,所有文件时间戳、日志时间戳都要+8h后再汇报。绝不输出UTC时间给Doro。

  • openpyxl的border赋值不能直接用cell.border = ref_cell.border,会报unhashable type: StyleProxy。必须用Side对象重建Border(left=Side(...), ...)。

  • xlsx路径已从任务交付/移到Doro合同审查任务/根目录,不在清理范围内。

  • 时间戳必须用北京时间(UTC+8),清理脚本依赖此时间计算24h。

  • xlsx日期列用YYYY-MM-DD字符串格式(如"2026-06-10"),不用ISO时间戳。

  • 甲方名称(party)必须从合同内容中提取全称,不能简写。

  • 清理cron job_id: 4636467b715d,每小时跑一次,no_agent静默模式。

  • 防重复写入:已由步骤0的查重环节覆盖(用original_filename + party组合在tracker中查重)。

  • xlsx列顺序必须严格一致:正确顺序为 序号(A), 日期(B), 顾问单位(C), 合同名称(D), 文件名(E)。写xlsx前必须先读上一行确认列含义。2026-06-12教训:手动写入时列顺序写反被Doro发现后补修。

  • 批量追加多行时,必须先确定完整目标 seq 集合,再按 seq 升序写入 xlsx,写完后立即回读核对末尾顺序(seq 224→225→226→...)。不能一边算 seq 一边写,更不能先写 303 再写 302;ws.max_row + 1 只会在物理末尾追加,若写入顺序错了,就会出现 xlsx 中 303 排在 302 前面的台账乱序。硬性补丁(2026-07-14):凡一次 pass 涉及 2 份及以上合同,先在内存中生成 [{seq, 日期, 顾问单位, 合同名称, 文件名}] 全部待写行,按 seq 升序排序后再统一 append;保存上传后,必须回读最后 N 行,逐行确认 A 列 seq 单调递增且文件名与本批次一一对应。若发现顺序错误,立即重拉 LIVE xlsx 重写,不得带错交付。

  • 按“北京时间今天/昨天/上周五”统计邱律师发了多少合同时,先定时间窗,再查 meta,再对照审查状态(2026-07-14 再次实证):这类问题不能直接凭印象回答“几份、都做完了吗”。标准顺序必须是:①先用 TZ='Asia/Shanghai' date 锚定今天的北京时间窗口;②查 ~/.hermes/cache/documents/*.metasender_id=QiuTing 且落在该窗口内的文件,得到今天实际收到的文件清单;③再去 tracker 中逐份核对这些文件对应的 status/seq/delivered_filename/xlsx_updated_at;④最后才汇总结论。重点:meta 统计的是“今天收到几份”,tracker 统计的是“这些文件审查到了哪一步”,两者不能互相替代。

  • 同名不同来源/不同顾问单位的合同,统计和汇报时必须拆开,不得混成一句“劳务派遣协议已完成”(2026-07-14):如果今天收到的是 劳务派遣协议.doc,但 tracker/任务交付里同时还存在 【修】白鹤--劳务派遣协议.docx 等近名文件,汇报时必须明确区分“今天收到的这份”与“其他同类/同模板/同主题文件”。不能把别的合同的 completed 记录拿来替代今天这份的审查状态。标准动作:按 original_filename 精确对 tracker 命中,再补充说明是否另有近名已完成文件。

  • 被用户要求“再核查一次”时,必须升级为四路交叉核查,不得只复述上一轮结论(2026-07-14):对“是不是同名合同但顾问单位不同”“今天收到的到底是哪几份”这类追问,重查时至少同时核:①Nextcloud 全目录文件扫描(不只根目录任务交付)②tracker 命中记录 ③xlsx 命中记录 ④cache meta/原件名称与正文中的甲方或项目编号。回复必须把四路结果并列写出,再下结论。不能只说“我刚才已经看过了,结论不变”。

  • 自动 cleanup 依赖 tracker 文件名与 Nextcloud 实际文件名精确一致(2026-07-14):若发现“历史台账文件名”和“当前 NC 实际文件名”不一致,即使该条记录已经 completed,仍要同步修正 tracker.original_filename / tracker.delivered_filename,必要时同步修正 xlsx 第 E 列文件名,确保三者一致:①tracker ②xlsx ③NC 实际文件。否则 cleanup 按精确文件名匹配时会漏删或误判。修正后必须再次核对目标待审查文件与任务交付文件在 NC 中确实存在。

  • 用户只要结果,不要过程解释时,汇报必须收敛成一句结论(2026-07-14 Doro纠正):当用户明确说“我就要一个结果”时,禁止继续附加过程说明、背景铺垫、风险解释或“补一句严谨的”。此类场景只回复最终结论,例如“能。”、“已完成。”、“不能,需要X条件。”;如确需补充,等用户追问后再展开。

  • 统计“今天收到的合同”时,先答收到清单,再答审查状态,别把两件事揉成一句笼统结论(2026-07-14):推荐输出结构固定为:①今天收到几份;②逐份列出收到时间;③逐份列出审查状态(未启动 / 审查中 / delivered / completed);④如有同主题近名文件,单列“另有近名已完成文件,不等于今天这份”。这样可以避免把“收到数量”和“已完成数量”说串。

  • 乙方空白时的处理:合同乙方名称空白→不问Doro"该问谁",直接查文件来源(cache/documents时间戳+session_search找发送人),确定是邱律师发的就直接私信邱律师询问。Doro可能说"邱律师回复了就直接补上 我不管了"——意思是邱律师回复后自主完成tracker+xlsx更新,不再找Doro确认。

  • 私信邱律师的send_message路由问题(2026-06-12):send_message(target='wecom:QiuTing')会路由到home channel而非QiuTing私信。必须用_send_wecom(extra, 'QiuTing', msg)直接发送才能到私信。

  • 私信任意企微用户的首选方法 = wecom_dm.py 脚本(2026-06-21 再犯后确立)send_message(target='wecom:<user>') 对企微用户名会静默回退到 home channel(JiaQian),既发不到目标、又可能违反信息隔离(把 Doro 团队内容发进 Maggie 渠道)。_send_wecom(extra, ...) 需要 gateway 的 extra 上下文,在 terminal/execute_code 里拿不到。最稳的通用方法是独立脚本:python3 ~/.hermes/scripts/wecom_dm.py --to <别名> --text "内容"——自己开 WebSocket + aibot_send_msg + chat_type=1 发主动私信,永不串号、目标唯一确定。白名单别名:doro / jiaqian(贾茜Maggie) / qiuting(邱律师) / weiwei(技术支持) / shasha(苌莎莎) / yangayi(颜伽艺) / xiaonan。先 wecom_dm.py --list 核对别名→userid 再发。铁律:私信非 home 的企微用户,一律走 wecom_dm.py,绝不用 send_message(target='wecom:X') 2026-06-21 教训:给魏玮发技术讨论用了 send_message(target='wecom:WeiWei'),静默落到 JiaQian home channel,既没到魏玮又把 Doro 团队的脚本细节泄露进 Maggie 渠道,改用 wecom_dm.py --to WeiWei 才真正送达。

  • 不干涉workflow铁律(2026-07-03 Doro纠正):发现邱律师发了新文件时,禁止直接手动启动workflow。必须先查 /tmp/auto_notify_new_file.loguwf thread list 确认auto_notify是否已经处理。不判断文件是否重复("你也不要去判断是不是同一份文件")——让系统自己处理。2026-07-03教训:auto_notify已正常工作并启动了workflow,我没查就手动又启动了一个重复的。

  • 手动启动workflow前必须查实auto_notify是否已处理(2026-07-03 Doro纠正):发现邱律师发了新合同时,第一步不是手动启动workflow,而是:①查/tmp/auto_notify_new_file.log确认auto_notify是否已检测并处理 ②查uwf thread list | grep running\|idle确认是否已有对应thread在跑。只有确认auto_notify没处理(日志无记录+无相关thread)才手动启动。2026-07-03教训:auto_notify已正常触发并启动了workflow,我没查就又手动启动了一个重复的。"收到即启动"的前提是"确认没有被自动处理"。

  • 不判断邱律师发的文件是否重复/相同(2026-07-03 Doro纠正,2026-07-08 再次验证):邱律师多次发送同名文件时,不要自行判断"是同一份发重了"。存在同文件名但不同版本、不同条款内容的可能性。2026-07-08实证:同日发两份"2026年华新镇公立中小学生健康体检服务合同.docx"(11:05和16:04),文件大小不同(27889 vs 27482 bytes),实际条款差异显著(项目内容、付款方式、合同起始日均不同)——完全是两份实质不同的合同版本。queue-runner因同名文件已在done/直接SKIP导致第二份没审查。核查方法:用python比对两文件文本差异(zipfile提取全文+difflib对比),有实质差异则为不同合同/不同版本,须独立审查。报告时绝不说"重复发送"——Doro问"如何判断是重复发送"即提醒不该自行判断。

  • 恢复文件到workflow原始交付状态(2026-07-13 实证):Doro要求撤销手动修改、恢复到workflow交付版本时,三个来源按优先级尝试:①NC版本历史(files_versions/目录下的.vXXXXXXXXXX文件,时间戳最早的=workflow首次上传版本)②/tmp/pass_check_*文件(之前做pass检查时从NC拉取的快照,如果当时还没手动修改则=workflow版本)③重新跑workflow(最后手段)。恢复后必须docker cp回NC + chown www-data + occ files:scan

  • Tracker重复条目(同一合同不同文件名):workflow跑多轮或auto_notify重复触发时,tracker可能出现同一合同的多条delivered记录(文件名带版本后缀如_v0113.docvs不带的)。批量pass时需注意:只pass实际在任务交付目录中的那份(【修】前缀的),旧的重复delivered条目如果文件名与NC上的交付物不匹配,不影响pass流程但会残留在tracker中(cleanup会因找不到文件而跳过)。

  • 手动启动前必须先验证auto_notify是否已处理(2026-07-03铁律):发现邱律师新文件后,禁止直接手动启动workflow。必须先:①查/tmp/auto_notify_new_file.log确认auto_notify是否已检测并处理该文件 ②uwf thread list | grep running确认是否已有workflow在跑。只有确认auto_notify确实没工作(进程死亡或模式B静默失效)才手动介入。2026-07-03教训:auto_notify已正常触发workflow,但我没查就手动又启动了一个重复的,被Doro纠正"不要去干涉workflow"。邱律师发多次同名文件不自行判断是否相同——让系统处理,同时私信邱律师确认。

  • 交付通知丢失的诊断与补发(2026-07-09 实证):Doro说"有几份合同我没有收到交付通知"时,按以下流程排查:①检查tracker中status=delivered的记录 ②查gateway.log在对应delivered_at时间前后是否有846609/Timeout/WS closed错误 ③确认是WS断连导致fire-and-forget通知丢失 ④用wecom_dm.py --to doro补发。根因是企微WS凌晨不稳定+通知无重试机制,详见 references/notification-ws-failure-pattern-20260709.md 临时止血=手动补发;根治=watchdog增加notification_sent检查和补发逻辑(方案待实施)。

  • 通知静默丢失——workflow完成但Doro没收到通知(2026-07-09 实证):workflow全流程跑完(thread status=end, tracker=delivered),但Doro说没收到交付通知。根因:final_review步骤通过_send_wecom发通知时,企微WebSocket已断连(errcode 846609: aibot websocket not subscribed),发送静默失败,无重试机制。症状:Doro说"没收到通知" + tracker有delivered记录 + grep '846609' ~/.hermes/logs/gateway.log在final_review执行时间段有报错。诊断:①查tracker找delivered_at时间 ②查gateway.log该时间±5分钟有无846609/WebSocket error ③确认final_review步骤确实跑完了(uwf step list <thread>有final_review且thread status=end)。补救:用python3 ~/.hermes/scripts/wecom_dm.py --to doro --text "合同审查完成通知(补发):..."手动补发。预防:目前无自动重试机制。如果发现当天有WebSocket中断记录,主动检查该时段内完成的所有workflow是否通知成功——以gateway.log中有对应的"Sending response...to doro"记录(不含后续846609 error)为准。

  • "delivered但Doro没收到通知"诊断(2026-07-09 实证):Doro说"有几份没收到交付通知"时,根因通常是final_review发通知时WebSocket已断(846609错误)。Workflow全程跑完(classifier→reviewer→editor→reviewer→deliverer→final_review),queue-runner标记delivered,但final_review内的_send_wecom因WS断连静默失败。诊断步骤:①tracker找status=delivered的合同 ②gateway.log搜846609和"WebSocket error"确认断连时段 ③uwf step list <thread_id>确认final_review确实执行过(有时长=执行了) ④确认交付文件在任务交付目录存在。补救:用wecom_dm.py --to doro --text "合同审查完成通知(补发):..."补发。补发内容含:合同名、顾问单位、修订数(ins/del计数)、主要修订摘要。注明"因XX时段企微WebSocket中断未实时送达,现补发"。

  • Watchdog "suspended + already delivered" 死循环(2026-07-08 实证):当 workflow 在 final_review 阶段因 HTTP 500 suspended,但 deliverer 已经成功交付(tracker=delivered)时,watchdog 会无限循环 BLOCK resume 且不归档文件,同时阻止 runner 重启。表现:Doro 没收到通知但文件已在任务交付目录。诊断tail /tmp/contract-queue/watchdog.log | grep "BLOCK resume"止血三步:①uwf thread cancel <thread_id> 取消所有suspended thread ②mv /tmp/contract-queue/<files> /tmp/contract-queue/done/ 清空queue ③对已delivered的合同直接做pass(tracker delivered→completed + xlsx追加)。注意:可能影响一批合同(2026-07-08是5份同时卡住),需全部处理。详见 references/watchdog-suspended-delivered-deadlock-20260708.md

  • 同名文件判重铁律(2026-07-08 华新镇体检合同教训,Doro明确要求):判断两份合同是否为"同一份",绝不能只看文件名。邱律师经常对同一份合同发送修改版(文件名不变但内容已改),也可能不同顾问单位使用相同文件名。判断标准——必须比对合同实质内容

    1. 甲方(顾问单位)名称
    2. 金额/费用条款(总价、单价、费用上限等关键数字)
    3. 合同期限(起止日期)
    4. 项目内容描述
    5. 字节大小

    以上任何一项不同 → 不同合同/新版本,必须独立审查。全部相同 → 重复发送,可跳过。

    此规则适用于所有环节:auto_notify入队、queue-runner启动前、手动操作、以及任何需要判断"是否已审查过"的场景。auto_notify已通过 ~/.hermes/scripts/contract_content_compare.py 自动执行内容比对。

    2026-07-08实证:华新镇体检合同同日发送两版(11:05和16:04),文件名完全相同,但第二版增加了费用上限17万元、项目名称加了"华新镇"、起始日期从9月10日改为9月1日——是实质不同的合同版本。因系统只按文件名判重,第二版被跳过未审查。详见 references/queue-runner-same-name-different-content-20260708.md

  • auto_notify脚本依赖与失败处理:如果邱律师的合同没有自动启动workflow,先检查auto_notify_new_file.sh是否还活着(ps aux | grep inotifywait)。该脚本没有守护机制,会静默死亡。两种失败模式

    • 模式A:进程死亡 — inotifywait进程不存在。watchdog cron (63bb31d4f050) 会自动重启。
    • 模式B:进程活着但事件静默丢失(更隐蔽) — 进程在跑但inotifywait没有捕获到任何事件(日志完全为空)。原因可能是:inotifywait的文件描述符失效、文件系统事件被内核丢弃、脚本启动时文件已经到达(race condition)。watchdog无法修复此模式,必须人工介入。
    • 诊断方法cat ~/.hermes/logs/auto_notify.log | grep 日期 — 如果日志为空但meta文件有当天文件,就是模式B。

    fallback流程

    1. 检查 ~/.hermes/cache/documents/ 中是否有未处理的文件(按meta时间戳筛选今天邱律师发的)
    2. 手动复制到 Doro合同审查任务/待审查/(用 sudo cp + sudo chown www-data:www-data
    3. 逐个启动 workflow(uwf thread start review-contract -p "..."

    ⚠️ 主动发现铁律(2026-07-03教训):任何操作过程中(查私信状态、查文件、核对数据等),如果发现cache/documents/中有未处理的邱律师文件(meta显示sender_id=QiuTing但对应文件不在待审查目录),必须立即中断当前任务,先上传+启动workflow,再继续原任务。"收到即启动"不只是auto_notify的职责——手动发现的文件也必须立即处理,不能"等会再说"。2026-07-03实证:邱律师14:27发了新合同,我在14:30+检查私信时看到了meta记录但没有立即启动workflow,直到Doro追问才处理。 4. 多份合同时串行执行:写 relay 脚本(等当前 thread status=end 后再启动下一个),因为所有合同共用 /tmp/contract-review/ 工作目录,并行会互相覆盖 5. 后台执行+通知uwf thread exec <thread_id> --count 20 --background,配合 notify_on_complete=true 6. 可选:设 cron job 每15分钟检查 relay 进程是否存活,挂了自动重启

    2026-06-29 实证:邱律师发了12个文件,auto_notify 没运行,只有4个自动进了 workflow。手动把剩余4个从 cache 移到待审查,写 relay 脚本串行执行,成功完成。 2026-06-30 实证(模式B):邱律师发了4份合同,auto_notify进程在跑但日志完全为空(inotifywait静默失效)。手动处理全部4份。

  • Subagent/delegate_task 的清理禁令:给 subagent 的 context 中必须明确写入"禁止删除 Nextcloud 待审查/和任务交付/目录中的任何文件。只做被要求的操作(写tracker/写xlsx/上传),不做任何清理"。2026-07-01教训:subagent 在执行 pass 操作时可能做了多余的清理动作导致文件丢失。

  • xlsx文件名列必须统一用delivered_filename(带【修】或【无修改意见】前缀),不能写原始文件名。

自动清理架构(2026-06-11确认,2026-07-01补充)

活跃 cron 列表(截至 2026-07-02):

  • contract-cleanup(job_id: 4636467b715d):每小时,no_agent,deliver=local。清理 pass 超 24h 的文件。
  • contract-queue-watchdog(job_id: 3174518affda):每20分钟,no_agent,deliver=local。巡检 queue-runner 是否存活,必要时重启。⚠️ 此 cron 有已知 bug:当 queue/ 中有文件未移入 done/ 时会反复重启 runner 导致重复审查(详见 references/queue-runner-duplicate-bug-20260702.md)。修复前需确保 runner 加了 tracker 查重逻辑。
  • auto-notify-watchdog(job_id: 63bb31d4f050):每5分钟,no_agent。守护 auto_notify_new_file.sh 的 inotifywait 进程。
  • 旧版清理已交付合同(agent模式/每6h/deliver到wecom:doro)和合同审查调度(每15分钟轮询)已于2026-06-11删除,与现有机制功能重复
  • 新文件监控由 auto_notify_new_file.sh(inotifywait实时事件驱动)独立承担,不再有cron轮询

⚠️ 文件删除权限铁律(2026-07-01 Doro纠正)

只有 cleanup cron 有权删除待审查/和任务交付/目录中的文件。 手动操作(包括小Maggie和subagent)不得直接 docker exec ... rm 删除这两个目录的文件。

唯一例外:Doro 明确指令删除特定文件(如"删掉这个招标需求")。

2026-07-01教训:小Maggie在执行替换/清理操作时,docker exec rm 误删了4份已pass但未满24小时的合同原始文件(徐泾北大居、印刷品、健康积分华新、银发健康包)。文件不在trashbin(docker exec rm绕过trashbin),cleanup cron全天silent确认没动,是手动操作误删。

Companion 文件清理方案(2026-07-01 确认)

Companion 类型因顾问单位而异(不统一为"审查意见"):

  • 朱家角:审查意见文档(从模板生成)
  • 爱卫中心:合同流程单(xlsx)
  • 练塘:脚注(加在合同里,非独立文件,不需清理)
  • 其他:无

方案:deliverer 上传后写 manifest → pass skill 读取 manifest 写入 tracker companion_files → cleanup 读取并一并删除。

manifest 位置:Nextcloud 任务交付目录下 .delivery-manifest-{原文件名去扩展名}.json(隐藏文件)。

待落地:cleanup 脚本需增加读取 companion_files 字段的逻辑;deliverer YAML 需增加写 manifest 步骤。

清理时机明确定义

  • 触发条件:Doro 说 pass → 写入 tracker(xlsx_updated_at 记录当前北京时间)
  • 清理时机:cleanup cron 每小时检查 tracker,找 status=completed + xlsx_updated_at 超过24小时 + cleaned=false 的记录
  • 即:pass 后 24 小时清理,不是交付后24小时、不是workflow结束后24小时
  • 清理范围:精确删除 tracker 中记录的 original_filename(待审查/)+ delivered_filename(任务交付/)+ converted_filename(待审查/,.doc转.docx时有值)
  • 不清理的:companion 文件(审查意见)不在 tracker 中,不会被自动清理;xlsx 在上一级目录不受影响

防重复审查(2026-07-01 朱家角标识牌 + 2026-07-02 夏阳/家庭医生签约)

已pass合同被重复审查交付的根因

  1. relay-runner/auto_notify 不查 tracker(2026-07-01):启动 workflow 前不检查 tracker 是否已有 completed 记录。manifest 文件残留已 pass 合同的文件名,被重新捡起来跑了一遍。
  2. queue-runner + watchdog 交互 bug(2026-07-02,详见 references/queue-runner-duplicate-bug-20260702.md):watchdog 每20分钟重启 runner(因 done/ 不满 manifest 行数),runner 的 SKIP 逻辑只查文件是否在 queue/ 目录,不查 tracker。一天内 watchdog 重启 runner 41次,导致夏阳和家庭医生签约被重复审查并重复通知 Doro。

铁律:任何触发 workflow 的流程(auto_notify / relay-runner / queue-runner / 手动启动),启动前必须检查 contract-tracker.json

查错后否认(2026-07-01):Doro问朱家角标识牌怎么重复了,回答说"你没pass"——实际查 tracker 发现 seq=229 早在6/27就pass了。被问任何合同状态时,先查 tracker/xlsx 用工具验证再回答,不凭"印象"。

Queue-runner + Watchdog 重复审查(2026-07-02 实证,详见 references/queue-runner-duplicate-review-20260702.md:runner等worker时异常退出→worker独立完成(含通知)→文件没移入done/→watchdog重启runner→重新审查→重复通知。止血:杀重复进程→清queue→移文件到done/。Doro说收到重复通知时,按reference文件中的止血SOP执行。

# 在 uwf thread start 之前(精确版,匹配 original_filename + status)
if python3 -c "
import json, sys
t = json.load(open('$HOME/.hermes/data/contract-tracker.json'))
completed = [c['original_filename'] for c in t['contracts'] if c['status']=='completed']
sys.exit(0 if '${FILENAME}' in completed else 1)
" 2>/dev/null; then
    echo "SKIP: already completed in tracker"
    # 移入 done/ 防止 watchdog 下次重启时再次尝试
    mv "$QUEUE_DIR/$FILENAME" "$QUEUE_DIR/done/" 2>/dev/null
    exit 0
fi

手动启动时的检查:用 python3 -c "import json; ..." 精确检查 original_filename + status=completed 组合。

queue-runner 止血方法(重复通知正在发生时):

  1. kill runner 进程和 background-worker 进程
  2. 将已完成的文件全部移入 done/:确保 done/ 数量 ≥ manifest 行数
  3. 验证后 watchdog 自然停止重启(进度检查通过)

铁律

  • 待审查目录原文件:Doro说pass之前严禁删除(2026-07-01 Doro纠正):无论审查了多少轮、出了多少个修订版本,原文件必须留在待审查目录,直到Doro明确说pass。违反此规则等于丢失原始文件。2026-07-01教训:重新审查反委托代发工资和生育友好合同时,把原文件从待审查删了(以为已经审查完),Doro发现后要求恢复。恢复方法:从cache/documents/复制原文件回到待审查目录。
  • Nextcloud 待审查/和任务交付/目录文件:只有 cleanup cron 有权删除(2026-07-01确立):手动操作(包括小Maggie本人、subagent/delegate_task)不得使用 docker exec rm 删除这两个目录的文件。唯一例外:Doro 明确指令删除特定文件(如"删掉这个招标需求")。2026-07-01教训:今天pass的4份合同(徐泾北大居、印刷品、健康积分、银发健康包)原始文件和交付文件在pass后不到24小时即被删除,tracker显示cleaned=False,cleanup cron全天silent——是手动操作误删。根因无法追溯。
  • 手动操作时 workflow 规则同样适用(2026-07-01确立):手动执行合同审查相关操作(修订、交付、清理)时,必须遵守 workflow YAML 中各角色的职责边界和规则约束。不能因为"我知道怎么做"就跳过规则。
  • 不主动生成审查意见文档(2026-07-01 Doro纠正):除非Doro明确要求,否则pass流程只处理【修】修订版,不主动生成【审】审查意见文档。审查意见是额外交付物。
  • 方案不等于授权执行
  • 新方案提出后,Doro会问"会有什么影响吗"——必须主动分析潜在影响(token消耗、误判风险、时区问题、单点故障、竞态条件等),不能只说好处。被要求"再想一想不要有漏洞"时,逐一列出漏洞清单+解法,不能遗漏。复杂度过高时Doro会直接砍掉——接受并简化。