Files
dsh_ai1net_server/交付物/S9-目标忙判据复核-20261002.md
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

9.7 KiB
Raw Permalink Blame History

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 <WS> -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