# S9 复核单 · 投递「目标忙」判据(活会话恒 working ⇒ 投递恒被推迟) - 任务类别:机制排查与修复 - 复核时间:2026-10-02 09:34–09:45 - 复核会话:`[协作]-[机制排查与修复]-S9 投递目标忙判据复核与放行验证`(sid `6a501f20-8517-422f-bd4c-6e0e9703a3f6`) - 结论:**旧「恒真」死结已解开(有端到端实证);当前卡点已前移到「无活跟进会话」这一层**。 --- ## 一、判据层 1:「目标忙」是否仍恒真 —— **已不恒真** ### 1.1 源码形态(`~/.workbuddy/skills/session-mechanism/scripts/collabd.py`) - `_session_status()`(`:1603`)**只**判「活着 / 已终结」,注释已明写「⛔ 别再用本函数判忙」。 - `_session_busy()`(`:1656`)读宿主状态机日志 `[SessionRunStateMachine] … sessionId=<36位> … busy=true|false`,取**最后一条**;`busy=true` **且**距今 ≤ `SRSM_FRESH=900 秒` 才回 `'busy'`,否则回 `'idle'`,读不到回空串。判据正则用**命名组**(`:1650`),从形状上消掉「数括号错位」那类错(曾致 `busy` 恒读成 `False`)。 - `_target_busy()`(`:1698`)两道判:① 库 `status ∈ SID_DEAD{completed, error, archived}` ⇒ 直接判「没在跑」;② 库 `working` ⇒ 才去看日志细分;读不到 ⇒ **按「没在跑」处理并留日志**(⛔ 不按「忙」,否则等于把恒真死结换个形状留着)。 - 调用点:`:1194`(`_pick_live` 前)、`:1431`(粗判)、`:1564`(拿锁后精判)。 ### 1.2 时间序列实证(`_collabd.log` 全文 09-30 07:41:56 → 10-02 09:38:22) - `no-follow-session` **469 条**;`target-deaf` **81 条**;`target-busy` **69 条**;`main-not-live` **3 条**;`locked` **1 条**。 - **`target-busy` 最后一次出现 = 2026-10-02 00:35:24** —— 原文「延后投递:跟进会话 b11c099d 正在执行 ⇒ 等它空闲」+「投递未成(target-busy)⇒ 保留 M7=running 在队首」。被拦的 `b11c099d` 是一条**真·跟进会话**(标题 `[跟进]-会话协作自检-队列上报`,当时 `status=working`)⇒ 这一条就是**旧判据的恒真现场**。 - **新判据首次在生产出现 = 2026-10-02 01:48:10** —— 原文「忙判据读不到(cbf76e13):状态机日志里没有它的记录 ⇒ 按**没在跑**处理」。 - **00:35:24 → 09:38:22(≈ 9 小时 03 分)区间内 `target-busy` = 0 条** ⇒ 换形态后**再没恒真过**。 ### 1.3 端到端放行实证(不是合成样本,是真投递) 新判据生效后,`tmp/supervise-inbox/wakeups.jsonl` 记录 **3 次真投递成功**(`kind=上报·单条`,`http=200`,`ok=true`): - 2026-10-02 01:48:22 → `cbf76e13`(`[跟进]-会话协作自检-队列上报`)@ 端口 60346 - 2026-10-02 02:56:25 → `ba7b24e8`(`[跟进]-会话协作自检-队列上报`)@ 端口 57555 - 2026-10-02 04:06:52 → `19e6e204`(`[跟进]-会话协作自检-队列上报`)@ 端口 49765 ⇒ **投递确实到达过「活的跟进会话」**,「活 ⇒ 忙 ⇒ 恒不投」这条死结在真实链路上已解开。 ### 1.4 合成对照与自测(可复跑) 现场函数调用(只读,2026-10-02 09:37): - `_target_busy('00000000-0000-0000-0000-000000000000')` ⇒ `False`。 - `_target_busy()` 对 3 条 `completed / archived / error` 会话 ⇒ 全 `False`。 - `_target_busy()` 对跟进名册 6 条(5 条 `completed` + 1 条 `error`)⇒ 全 `False`。 - `_target_busy(6a501f20)`(本棒,库 `working` → 日志 `busy` 且新鲜)⇒ `True`(**真在跑 ⇒ 判对**)。 回归自测(`selftest.py -k <名>`,测试工作区 `tmp/selftest`,不碰生产): - 「投递忙判据:读宿主状态机日志的 busy=(⛔ 不再用 `status=='working'` 那档恒真的判据)」⇒ **PASS 1 / 6 项**。六项覆盖:末条 `busy=true` 且新鲜 ⇒ 判忙;末条 `busy=false` ⇒ 判闲;末条 `busy=true` 但已 2 小时前 ⇒ 判闲;日志无记录 ⇒ 回空串;库已终结 ⇒ 不判忙;正则用命名组、零位置组引用。 - 「投递:目标会话(跟进会话)在跑 ⇒ 必须延后(target-busy)」⇒ **PASS 1 / 2 项**。其中关键一项:伪造 `_session_busy → 'idle'` ⇒ **必须不再延后**、往下走成 `follow-not-live`。 复跑命令: ``` export COLLABD_CONFIG="E:/ProgramData/AIProject/ai1net-dsh-server/.workbuddy/collab/collabd.config.json" cd "E:/ProgramData/.workbuddy/skills/session-mechanism/scripts" "E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" selftest.py "E:/ProgramData/AIProject/ai1net-dsh-server" -k "投递忙判据" "E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" selftest.py "E:/ProgramData/AIProject/ai1net-dsh-server" -k "目标会话" ``` --- ## 二、判据层 2:当下卡点究竟在哪一层 —— **「无活跟进会话」** 09:37 现场读数(只读宿主库 `E:/ProgramData/.workbuddy/workbuddy.db`): - `sessions.status` 取值域:`completed 148`、`working 2`、`error 2`、`archived 2` —— **仍无「空闲」这一档**。 - `_live_sids()` = `['3f43ce71', '6a501f20']` —— 活会话 **2 条**,标题分别是 `接续 · 会话机制合并包 · 任务4b-4d-6` 与 `[协作]-[机制排查与修复]-S9 …` ⇒ **两条都不是 `[跟进]-` 角色**。 - `_scan_follows()` 名册 2 条:`机制排查与修复 → 6ab1463e`(`completed`)、`会话协作自检 → 57f58ecf`(`completed`)。 - `follow_for_topic()` 三问(`机制排查与修复` / `会话协作自检` / 空)⇒ **全部 `sid=""`、`why=follow-not-live`**。 - `_deliver_str()` 里 `_want == ""` ⇒ `skipped = "no-follow-session"`(`collabd.py:1558`)。 - 队列现场:待上报队首 = `S8=done`;日志 09:38:22 仍在刷「投递未成(no-follow-session)⇒ 保留 S8=done 在队首,下一轮重试」。 **层级判定**:件**有**(`S8=done` 待上报)、目标类别登记**有**(每类别各一条),但**名册里的跟进会话全是终结态** ⇒ 投递卡在「无活跟进会话」,**不是** `target-busy`(该档近 9 小时零命中)。⇒「多起一条跟进会话」不能治;**治本要求:跟进会话得是长期活的、或收完一件能被续起**。 > ⚠️ 读数会随时间漂(会话状态在动):09:37 读作 `completed 148 / working 2 / error 2 / archived 2`;09:40 复读作 `completed 149 / working 1 / error 2 / archived 2` —— 是 `3f43ce71` 由 `working` 转 `completed`,**不改变「活会话里没有 `[跟进]-`」这一结论**(两次 `_live_sids()` 均为 `['3f43ce71', '6a501f20']`)。 --- ## 三、本节点判据状态 - 层 1(判据不再恒真):**成立**。证据 = 源码形态 + 9 小时零 `target-busy` + 3 次真投递成功 + 自测 2 用例 PASS。 - 层 2(卡点定位):**成立**。证据 = 现场读数 `follow-not-live` + 名册 / 活会话清单。 - **未过项(如实报)**:**「端到端放行」此刻无法现场复现** —— 现场不存在活跟进会话 ⇒ 前置条件不成立。已有的 3 次投成是 01:48–04:06 窗口的实证;**要再验一次,须先有一条活着的 `[跟进]-…` 会话**。该前置属 S6(建齐三类会话)的持续态,⛔ 不属 S9 缺陷。 --- ## 四、复现命令(现场读数) ``` # 判读分布 grep -o "投递未成([a-z-]*)" .workbuddy/collab/logs/_collabd.log | sort | uniq -c # 判据换形态的分界点 grep -n "target-busy" .workbuddy/collab/logs/_collabd.log | tail -3 grep -n "忙判据读不到" .workbuddy/collab/logs/_collabd.log | head -3 # 投成记录(目标是不是跟进会话) tail -5 tmp/supervise-inbox/wakeups.jsonl ``` 现场判据读数(只读,自包含;⛔ 不写任何生产文件): ``` cd "E:/ProgramData/AIProject/ai1net-dsh-server" export COLLABD_CONFIG="E:/ProgramData/AIProject/ai1net-dsh-server/.workbuddy/collab/collabd.config.json" "E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" - <<'PY' import os, sys, sqlite3, time sys.stdout.reconfigure(encoding="utf-8", errors="replace") os.environ["COLLABD_CONFIG"] = r"E:/ProgramData/AIProject/ai1net-dsh-server/.workbuddy/collab/collabd.config.json" sys.path.insert(0, r"E:/ProgramData/.workbuddy/skills/session-mechanism/scripts") import collabd as C print("now", time.strftime("%Y-%m-%d %H:%M:%S"), "| SID_DEAD", sorted(C.SID_DEAD), "| SRSM_FRESH", C.SRSM_FRESH) con = sqlite3.connect("file:E:/ProgramData/.workbuddy/workbuddy.db?mode=ro", uri=True) for st, n in con.execute("select status,count(*) from sessions group by status order by 2 desc"): print(" 取值域 %-10s %d" % (st, n)) sc = C._scan_follows() print("跟进名册 sids :", [str(s)[:8] for s in (sc.get("sids") or [])]) print("跟进名册 by_topic:", {k: str(v)[:8] for k, v in (sc.get("by_topic") or {}).items()}) print("活会话 _live_sids:", sorted(str(s)[:8] for s in C._live_sids())) for tp in ["机制排查与修复", "会话协作自检", ""]: r = C.follow_for_topic({}, tp, live_sids=[g.get("sessionId") for g in (C.discover_gateways() or [])]) print("follow_for_topic(%-14s) sid=%-8s why=%s" % (tp or "(空)", (r.get("sid") or "-")[:8], r.get("why") or "-")) for sid, stt in con.execute("select id,status from sessions where status='working'"): print("working 会话 %s -> _session_busy=%r _target_busy=%s(旧判据 status=='working'=%s)" % (sid[:8], C._session_busy(sid), C._target_busy(sid), C._session_status(sid) == "working")) print("假 sid -> _target_busy =", C._target_busy("00000000-0000-0000-0000-000000000000")) for r in con.execute("select id from sessions where status in ('completed','archived','error') limit 3"): print("终结会话 %s -> _target_busy = %s" % (r[0][:8], C._target_busy(r[0]))) con.close() PY ```