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

129 lines
9.7 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.
# 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
```