session-mechanism: 修复钩子静默失效 + 3 处判据缺陷;禁「变相征询」
1) stop-dialog-guard: session_budget() 早退路径返回 2 值、末尾返回 3 值,调用方按 3 值解包
⇒ transcript > 64 MiB 时每轮 ValueError。因 fail-open(异常仍 exit 0),
宿主零报错、install.py --verify 只判 rc=0 ⇒ 假绿;实测 86 条 EXCEPTION,
死掉的是整条(水位/收口、接续机制起点、预算告警、门禁自检、路径自检)。
2) session-rules-check 三处判据:
· hook_reg 按旧文件名找 ⇒ 合并成 prompt-guards.py 后每轮假红 ⇒ 改为一组可接受名
· snap_sync 拿 mtime 当内容判据 ⇒ 连续 4 天假红 ⇒ 改为复用抽取器本体比对内容
(变异对照:截断快照能报 fail,非恒绿)
· mem_ptr 只查全局技能根 ⇒ 工作区自带技能被判悬空 ⇒ 改查「全局 ∪ 工作区」
3) pitfalls 新增 P0-95(改判据必须重跑变异对照;fail-open + 只看 rc=0 = 假绿温床)
4) 回复排版核心块新增「变相征询同样禁止」(先只报不动/等你发话/我倾向X你看呢
这类不带选项的待定清单,一律按待拍板项写:问题+说明+各候选优缺点+倾向)
This commit is contained in:
1 parent
19101acd65
commit
64dd82073b
21 files changed
+8444
-4523
No files matched your search
@@ -76,15 +76,24 @@ AS_JSON = "--json" in ARGV
|
||||
SHOW_ALL = "--all" in ARGV
|
||||
|
||||
# 「会话机制三件套」的**关键钩子**(缺一 ⇒ 该机制不会运行)
|
||||
# 🔴🔴 2026-10-06 修:第 2 元由**单个文件名**改成**一组可接受的文件名**(命中任一即算在册)。
|
||||
# 起因(实测坐实):`UserPromptSubmit` 上原有 4 条独立守卫(`stop-dialog-guard` /
|
||||
# `skill-load-guard` / `reply-style-guard` / `session-log-guard`),每轮**各起一个 Python 进程**
|
||||
# ⇒ 宿主冷启 7 次解释器 ⇒ 预算被排队挤破 ⇒ 报 `Hook timed out after 10000ms`(用户报障)。
|
||||
# ⇒ 已**合并**为一个入口 `prompt-guards.py`(本进程内 `runpy` 依次跑那 4 个,合并输出)。
|
||||
# ⚠️ 本条判据若仍按**旧文件名**找 ⇒ 合并后**每轮必报「关键钩子不在册」(假红)**,
|
||||
# 而它惩罚的正是"按要求做过的合并"。
|
||||
# 🔴 判据要问的是「**这项能力有没有接线**」,⛔ 不是「某个文件名在不在」——
|
||||
# 所以两种形态**都必须认**:独立接线 | 合并入口。
|
||||
KEY_HOOKS = [
|
||||
("SessionStart", "lock-guard-hook.py", "无锁不许改代码库 / 文档库"),
|
||||
("PreToolUse", "lock-guard-hook.py", "同上(Write|Edit 面)"),
|
||||
("PreToolUse", "bash-output-guard.py", "拦下会灌爆上下文的读命令"),
|
||||
("UserPromptSubmit", "wb-result-hook.py", "收结果 / 结果回流"),
|
||||
("UserPromptSubmit", "stop-dialog-guard.py", "水位与收口(接续机制起点)"),
|
||||
("UserPromptSubmit", "skill-load-guard.py", "用户点名方法 ⇒ 强制加载技能"),
|
||||
("UserPromptSubmit", "session-log-guard.py", "会话日志闸(防把界面顶死)"),
|
||||
("PostToolUse", "session-log-guard.py", "同上(工具后)"),
|
||||
("SessionStart", ("lock-guard-hook.py",), "无锁不许改代码库 / 文档库"),
|
||||
("PreToolUse", ("lock-guard-hook.py",), "同上(Write|Edit 面)"),
|
||||
("PreToolUse", ("bash-output-guard.py",), "拦下会灌爆上下文的读命令"),
|
||||
("UserPromptSubmit", ("wb-result-hook.py",), "收结果 / 结果回流"),
|
||||
("UserPromptSubmit", ("stop-dialog-guard.py", "prompt-guards.py"), "水位与收口(接续机制起点)"),
|
||||
("UserPromptSubmit", ("skill-load-guard.py", "prompt-guards.py"), "用户点名方法 ⇒ 强制加载技能"),
|
||||
("UserPromptSubmit", ("session-log-guard.py", "prompt-guards.py"), "会话日志闸(防把界面顶死)"),
|
||||
("PostToolUse", ("session-log-guard.py",), "同上(工具后)"),
|
||||
]
|
||||
GATE_LOGS = [("收口", WS + "/.workbuddy/stop-dialog-guard.log"),
|
||||
("技能", WS + "/.workbuddy/skill-load-guard.log"),
|
||||
@@ -94,7 +103,33 @@ GATE_LOGS = [("收口", WS + "/.workbuddy/stop-dialog-guard.log"),
|
||||
("锁", (os.path.join(os.path.dirname(os.environ.get("DSH_DOCS_ROOT", "")),
|
||||
".workbuddy", "lock-hook.log")
|
||||
if os.environ.get("DSH_DOCS_ROOT") else ""))]
|
||||
ROLES_LIVE = [("唤醒", "[唤醒]"), ("协作", "[协作]"), ("跟进", "[跟进]")]
|
||||
# 🔴🔴 2026-10-05 **用户定案:「协作 全部 改为 执行」**(连说两遍,第二轮逐字:
|
||||
# 「**兼容个毛线,今天兼容一个明天兼容一个 过不了一周就成大杂烩了**」)。
|
||||
# ⛔⛔ 本文件里 `协作` 二字**不准再出现在任何"活"的语境里**。
|
||||
# 「兼容」这个词**本身就是病** —— 后人读到"兼容"会以为它迟早能删,于是一拖再拖。
|
||||
# ⇒ 凡看到旧前缀,必须能一句话答出**"删了会坏在哪"**;答不出 ⇒ 它就是该删的。
|
||||
# 🔴 分清**两件性质完全不同的事**(本文件唯一的例外,且**不许写"兼容"**):
|
||||
# ① **活类别表 `ROLES_LIVE`** = "必须存在活会话"的类别 ⇒ **只许有当前在用的前缀**。
|
||||
# 多一个过时前缀 ⇒ 每轮必报"类别缺失"(假红)+ 给旧名续命(就是用户说的"大杂烩")。
|
||||
# ② **扫描面 `SESS_PREFIXES`** = "要捞进视野"的前缀 ⇒ 过时前缀**留在这里**。
|
||||
# 性质是**历史行解析正确性**(⛔ 不是"兼容"):宿主库 `sessions` 表里有旧标题的行,
|
||||
# 扫不到 ⇒ 本闸门对它们**静默失明**("读到了却不说"是该机制的既有红线)。
|
||||
# 🔴 **实测判据(2026-10-05 当场跑 `board.py::_role_of_title()` 验的,⛔ 非推理)**:
|
||||
# 删 `_PFX` 里 `协作`/`协作目标`/`任务会话` 三条 ⇒
|
||||
# `[协作]-[手机接入]-N9 复测` / `[协作]N9 派活 · …` / `[协作目标]-xxx` / `[任务会话]-…`
|
||||
# **全部解析成 `''`** ⇒ 看板画不出、派活漏管,**不可逆**。
|
||||
# ⇒ 这就是"删了会坏在哪"的答案。**留着有据,不是人情。**
|
||||
# ⚠️ 与 `collabd.py::parse_session_name()` / `board.py::_role_of_title()` 的映射口径对齐:
|
||||
# worker 侧新建=`执行`(唯一在用)。
|
||||
ROLES_LIVE = [("执行", "[执行]")]
|
||||
# 🔴 扫描面 = 当前活类别 + **历史前缀**(⛔ 语义是「历史行解析正确性」,**不是"兼容"**)。
|
||||
# ⛔⛔ **这三个历史前缀不许删**:删任一条 ⇒ 存量旧标题行**扫不到** ⇒ 本闸门静默失明。
|
||||
# ⚠️ 它们**不参与"活类别"判定**(不判"必须有活的")—— 判了就是给旧名续命。
|
||||
SESS_PREFIXES = [p for _, p in ROLES_LIVE] + ["[协作]", "[协作目标]", "[任务会话]"]
|
||||
# 🔴🔴 2026-10-03 口径后**已退役**的角色前缀 ⇒ **只扫、不判活**(⛔ 不是"兼容",是"还在库里"):
|
||||
# ⛔ 不许放回 `ROLES_LIVE` —— 唤醒/跟进**整套退役**(见 SKILL.md 文首口径块),
|
||||
# 放回去 ⇒ 每轮必报"唤醒/跟进 类别缺失"(惩罚的正是"按要求删掉的东西")。
|
||||
SESS_PREFIXES += ["[唤醒]", "[跟进]"]
|
||||
# 🔴 2026-10-03 口径:唤醒/跟进**整套退役**(见 SKILL.md 文首口径块)
|
||||
# ⇒ ⑦ 「周期钟必须存在」这条旧判据与现口径**互斥**:按旧判据每轮必报 fail,而它惩罚的
|
||||
# 正是"已经按用户要求删掉的东西"。现行「处理」这条腿的载体=常驻 `--supervise`(⑩ 判它)。
|
||||
@@ -181,11 +216,32 @@ def _dangling_skills_in_text(txts):
|
||||
out.setdefault(nm, t[max(0, m.start() - 20):m.end() + 20].replace("\n", " ").strip())
|
||||
return out
|
||||
|
||||
EXISTING_SKILLS = set()
|
||||
try:
|
||||
EXISTING_SKILLS = {n for n in os.listdir(SKILLS) if os.path.isdir(os.path.join(SKILLS, n))}
|
||||
except Exception:
|
||||
pass
|
||||
def _skill_names():
|
||||
"""**本机可见的全部技能名** = 全局技能根 ∪ 工作区技能根。
|
||||
|
||||
🔴🔴 2026-10-06 修 **只查全局根 ⇒ 工作区自带技能被判"悬空"(假红,且每轮刷)**。
|
||||
实测(`vibe-product`):其 `MEMORY.md` 里写「`product-planning/`(工作区根)」,
|
||||
而 `product-planning` **只在** `<WS>/.workbuddy/skills/` 里 —— 全局库
|
||||
`E:/ProgramData/.workbuddy/skills/` 下**没有它**(实测 `ls` = No such file)。
|
||||
⇒ 旧判据(只 `listdir(SKILLS)`)每轮报「工作区记忆里的技能指针悬空」,
|
||||
而它惩罚的正是「**工作区自己装的技能**」这种**完全合法**的形态。
|
||||
|
||||
⛔ 判据要问的是「**这个名字在本机取不取得到**」,⛔ **不是**「全局库目录里有没有」。
|
||||
本机 `DSH_SKILLS_ROOT` 未设时 `SKILLS` 落在 `CODEBUDDY_CONFIG_DIR/skills`(全局),
|
||||
本身就**看不见**工作区那一层 ⇒ 漏判是必然而非偶然。
|
||||
"""
|
||||
out = set()
|
||||
for root in (SKILLS, os.path.join(WS, ".workbuddy", "skills")):
|
||||
try:
|
||||
if root and os.path.isdir(root):
|
||||
out |= {n for n in os.listdir(root)
|
||||
if os.path.isdir(os.path.join(root, n))}
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
EXISTING_SKILLS = _skill_names()
|
||||
|
||||
|
||||
# ── A. 机制装没装好 ─────────────────────────────────────────────────────
|
||||
@@ -200,10 +256,12 @@ def check_hooks():
|
||||
return [], None
|
||||
cmds = [(ev, str(h.get("command") or ""))
|
||||
for ev, arr in hk.items() for blk in (arr or []) for h in ((blk or {}).get("hooks") or [])]
|
||||
miss = [k for k in KEY_HOOKS if not any(k[0] == ev and k[1] in c for ev, c in cmds)]
|
||||
# ⚠️ `k[1]` 是**一组**可接受的文件名 ⇒ 命中任一即算在册(独立接线 或 合并入口)。
|
||||
miss = [k for k in KEY_HOOKS
|
||||
if not any(k[0] == ev and any(n in c for n in k[1]) for ev, c in cmds)]
|
||||
if miss:
|
||||
rec("hook_reg", "A", "fail", "关键钩子不在册(该机制现在不会运行)",
|
||||
"、".join("%s(%s)" % (k[1], k[0]) for k in miss))
|
||||
"、".join("%s(%s)" % ("/".join(k[1]), k[0]) for k in miss))
|
||||
else:
|
||||
rec("hook_reg", "A", "ok", "关键钩子在册", "共 %d 条钩子注册" % len(cmds))
|
||||
|
||||
@@ -304,26 +362,89 @@ def check_memory_pointers():
|
||||
|
||||
|
||||
def check_snapshot_stale():
|
||||
"""常驻规则快照 vs 权威 `CODEBUDDY.md`:**比权威旧 ⇒ 内容过期**(快照是副本,权威单向)。"""
|
||||
"""常驻规则快照 vs 权威 `CODEBUDDY.md`:**内容**一不一致(快照是副本,权威单向)。
|
||||
|
||||
🔴🔴 2026-10-06 修 **判据从「比 mtime」改成「比内容」**(原判据是**假红**,已实测坐实):
|
||||
原写法比 `getmtime(auth) - getmtime(snap) > 0.01 天` ⇒ 只要权威 CODEBUDDY.md
|
||||
**被 touch 过**(哪怕只是重排、挪归档、加一行注释)就报
|
||||
「快照比权威旧 ⇒ 新规则没进快照」—— 而**快照内容可能一个字都没差**。
|
||||
⚠️ 实测(2026-10-06,ai1net):新旧快照 **md5 逐字节相同**(`f54e48d4…`)、`diff` **0 行**、
|
||||
`resident-rules.py --check` 报「✅ 关键规则齐备」,而本条已**连续 4 天每轮报 fail**。
|
||||
⇒ 病根:**拿文件时间当内容判据**(时间只说明"谁最后被写过",不说明"内容差没差")。
|
||||
|
||||
⇒ 改为**内容口径**,并**复用唯一事实源**:直接调 `dsh-local-env` 的
|
||||
`resident-rules.py --check --goal <CODEBUDDY.md>`(它本来就是干这个的)
|
||||
—— ⛔ **不在这里再抄一份"抽章节再比对"的逻辑**(抄一份 ⇒ 两处漂移 ⇒ 同族事故)。
|
||||
rc=0 ⇒ ok | rc=1 ⇒ **真不一致**(fail)| 工具缺失/跑不起来 ⇒ warn(⛔ 不假装通过)。
|
||||
"""
|
||||
snap = os.path.join(SKILLS, "dsh-local-env", "references", "dsh-env-bootstrap",
|
||||
"常驻规则-快照.md")
|
||||
auth = os.path.join(WS, "CODEBUDDY.md")
|
||||
if not os.path.isfile(auth):
|
||||
rec("snap_sync", "B", "warn", "找不到权威规则文件", auth)
|
||||
# ⚠️ 本区**本来就没有** CODEBUDDY.md(如 vibe-product)⇒ 这是**合法状态**,不是故障。
|
||||
rec("snap_sync", "B", "warn", "本区无权威规则文件(`CODEBUDDY.md` 不存在 ⇒ 本项不适用)", auth)
|
||||
return
|
||||
if not os.path.isfile(snap):
|
||||
rec("snap_sync", "B", "warn", "常驻规则快照不存在", snap)
|
||||
return
|
||||
try:
|
||||
d = (os.path.getmtime(auth) - os.path.getmtime(snap)) / 86400.0
|
||||
except Exception:
|
||||
tool = os.path.join(SKILLS, "dsh-local-env", "references", "dsh-env-bootstrap",
|
||||
"resident-rules.py")
|
||||
if not os.path.isfile(tool):
|
||||
rec("snap_sync", "B", "warn",
|
||||
"**抽取器不在** ⇒ 判不了快照同不同步(⛔ 不算通过)", tool)
|
||||
return
|
||||
if d > 0.01:
|
||||
rec("snap_sync", "B", "fail", "常驻规则快照**比权威文件旧** ⇒ 新规则没进快照",
|
||||
"权威 %s 比快照新 %.1f 天;重生成 ⇒ `python scripts/resident-rules.py --snapshot`"
|
||||
% (os.path.basename(auth), d))
|
||||
else:
|
||||
rec("snap_sync", "B", "ok", "常驻规则快照不比权威旧", "")
|
||||
# 🔴🔴 真·内容口径:**让抽取器自己算一遍"快照应该长什么样"**,再与磁盘上的真快照比对。
|
||||
# ⛔ 不能用 `resident-rules.py --check` —— 实测(2026-10-06 变异对照):
|
||||
# 它校验的是**目标 CODEBUDDY.md 里关键规则齐不齐**,**根本不含"与快照比对"**。
|
||||
# 我中途误用它 ⇒ 把快照**截断到 400 字节**,它照样报 `✅ 关键规则齐备`(rc=0)
|
||||
# ⇒ 判据变 **恒绿**(比原来的 mtime 假红更坏)。
|
||||
# ⚠️ 教训:**换判据必须重跑变异对照**;"换了个看起来更对的调用" ≠ 判据变强了。
|
||||
# ⛔ 也不在这里自己抄一份"抽章节"逻辑(两处 ⇒ 漂移)。做法=**导入抽取器本体**,
|
||||
# 把它的 `SNAP` 常量**临时改指到临时文件**,调它自己的 `snapshot()` 取"应有内容",
|
||||
# 读完还原常量并删临时文件(⛔ 全程不碰真快照 —— 体检⛔ 不改资产)。
|
||||
try:
|
||||
import importlib.util
|
||||
import tempfile
|
||||
_spec = importlib.util.spec_from_file_location("_rr_probe", tool)
|
||||
_mod = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(_mod) # `__name__` ≠ `'__main__'` ⇒ 不会跑 main()
|
||||
_fd, _tmp = tempfile.mkstemp(suffix=".md")
|
||||
os.close(_fd)
|
||||
_orig = _mod.SNAP
|
||||
try:
|
||||
_mod.SNAP = _tmp
|
||||
_mod.snapshot(auth) # 用**它自己**的逻辑生成"应有内容"
|
||||
expected = io.open(_tmp, encoding="utf-8", errors="replace").read()
|
||||
finally:
|
||||
_mod.SNAP = _orig
|
||||
try:
|
||||
os.remove(_tmp)
|
||||
except OSError:
|
||||
pass
|
||||
except Exception as e:
|
||||
rec("snap_sync", "B", "warn",
|
||||
"**内容比对跑不起来** ⇒ 判不了快照同不同步(⛔ 不算通过)", str(e)[:130])
|
||||
return
|
||||
try:
|
||||
actual = io.open(snap, encoding="utf-8", errors="replace").read()
|
||||
except Exception as e:
|
||||
rec("snap_sync", "B", "warn", "读快照失败", str(e)[:120])
|
||||
return
|
||||
if actual == expected:
|
||||
rec("snap_sync", "B", "ok", "常驻规则快照与权威**逐字节一致**", "")
|
||||
return
|
||||
# 定位**第一处**差异,便于一眼看懂差在哪(⛔ 不倒全文)
|
||||
_a, _b = actual.split("\n"), expected.split("\n")
|
||||
_i = 0
|
||||
while _i < min(len(_a), len(_b)) and _a[_i] == _b[_i]:
|
||||
_i += 1
|
||||
_exp_line = (_b[_i][:60].strip() if _i < len(_b) else "(应有多出的行)")
|
||||
_act_line = (_a[_i][:60].strip() if _i < len(_a) else "(快照到此为止 ⇒ 缺内容)")
|
||||
rec("snap_sync", "B", "fail",
|
||||
"常驻规则快照与权威**内容不一致** ⇒ 新规则没进快照(或快照被改坏)",
|
||||
"第 %d 行起不同;快照实际=「%s」/应为=「%s」(快照 %d 行 / 应为 %d 行);"
|
||||
"重生成 ⇒ `python <dsh-local-env>/references/dsh-env-bootstrap/resident-rules.py --snapshot`"
|
||||
% (_i + 1, _act_line, _exp_line, len(_a), len(_b)))
|
||||
|
||||
|
||||
# ── C. 编排在不在跑 ─────────────────────────────────────────────────────
|
||||
@@ -420,8 +541,9 @@ def check_orchestration():
|
||||
"select id,name,schedule_type,next_run_at,model_id,model_is_thinking,cwds,"
|
||||
" scheduled_at,status "
|
||||
"from automations where deleted_at is null")]
|
||||
sess = list(c.execute("select id,title,status from sessions "
|
||||
"where title like '[唤醒]%' or title like '[协作]%' or title like '[跟进]%'"))
|
||||
sess = list(c.execute(
|
||||
"select id,title,status from sessions where "
|
||||
+ " or ".join(["title like '%s%%'" % p for p in SESS_PREFIXES])))
|
||||
except Exception as e:
|
||||
rec("clock", "C", "fail", "读宿主库失败", str(e)[:140])
|
||||
return
|
||||
@@ -442,19 +564,54 @@ def check_orchestration():
|
||||
rec("clock", "C", "ok", "已退役角色的周期钟已全部 PAUSED(或本就无在册)",
|
||||
"在册 %d 条,全部非 ACTIVE" % len(retire))
|
||||
|
||||
# ⑧ 模型可用性(thinking=0 + flash ⇒ 服务端必拒)
|
||||
# ⚠️ 2026-10-03 后**不再只扫"周期钟"** —— 唤醒/跟进已退役,本工作区的 recurring 排期
|
||||
# 可能一条都没有 ⇒ 旧写法(只扫 clocks)会恒判 ok(**恒绿假通过**)。
|
||||
# ⇒ 改为扫**本工作区全部 recurring 排期**(含检查类),覆盖面只增不减。
|
||||
# ⑧ 模型一致性(🔴🔴 2026-10-05 P0-80 重写:**扫描面从 `recurring` 扩到全部排期**)
|
||||
# ⚠️ 旧写法只扫 `recurring` ⇒ 而**机制新建的排期 `schedule_type` 恒为 `once`**(见 P0-65)
|
||||
# ⇒ 一条都不进扫描面 ⇒ 该判据**恒判 ok**(同 P0-76 ①/P0-77 ③ 的"闸门看不见新东西")。
|
||||
# ⚠️ 且旧判据只问"flash + thinking=0 会不会被服务端拒",**⛔ 没问"跟主会话是不是同一个"**
|
||||
# —— 这才是用户真正遇到的问题(P0-80:机制写死 `space-bunny`,主会话是 `deepseek-v4.1-flash`)。
|
||||
# ✅ 现判据两条腿:
|
||||
# ① **可用性**(旧):flash 类 + thinking=0 ⇒ 服务端拒;
|
||||
# ② **一致性**(新):本区排期的 `model_id` 必须与本区主会话 `model` 同值(否则报 fail)。
|
||||
_recur = [a for a in mine if a["schedule_type"] == "recurring"]
|
||||
deaf = ["%s(%s)" % ((a["name"] or "")[:26], a["model_id"]) for a in _recur
|
||||
try:
|
||||
_c2 = sqlite3.connect("file:%s?mode=ro" % DB, uri=True, timeout=10)
|
||||
_c2.execute("PRAGMA busy_timeout=5000")
|
||||
_mr = _c2.execute(
|
||||
"select model from sessions "
|
||||
"where (is_background_automation is null or is_background_automation <> 1) "
|
||||
" and model is not null and model <> '' "
|
||||
" and replace(cwd,'\\\\','/') like ? order by created_at desc limit 1",
|
||||
("%" + ws_norm + "%",)).fetchone()
|
||||
_c2.close()
|
||||
_main_model = (_mr[0] if _mr else "") or ""
|
||||
except Exception:
|
||||
_main_model = ""
|
||||
# 可用性:本区**全部**排期(含 once)
|
||||
_allmine = [a for a in mine]
|
||||
deaf = ["%s(%s)" % ((a["name"] or "")[:26], a["model_id"]) for a in _allmine
|
||||
if not a["model_is_thinking"]
|
||||
and any(h in (a["model_id"] or "").lower() for h in FLASH_HINT)]
|
||||
and any(h in (a["model_id"] or "").lower() for h in FLASH_HINT)
|
||||
and a["schedule_type"] == "recurring"] # ⚠️ 只有 recurring 会被服务端审模型
|
||||
if deaf:
|
||||
rec("model", "C", "fail", "周期排期会被服务端拒(静默失效)",
|
||||
"模型不支持关思考却传 thinking=0:%s" % "、".join(deaf))
|
||||
else:
|
||||
rec("model", "C", "ok", "周期排期模型可用", "本区 recurring %d 条" % len(_recur))
|
||||
# 一致性:机制建的排期(once)模型须与本区主会话同值
|
||||
if not _main_model:
|
||||
rec("model_same", "C", "warn", "取不到本区主会话模型 ⇒ 无法核对一致性(P0-80)",
|
||||
"sessions 里没有本区的人开会话")
|
||||
else:
|
||||
_mech = [a for a in _allmine if (a.get("name") or "").startswith(("[执行]", "[检查]"))]
|
||||
_diff = ["%s(%s)" % ((a["name"] or "")[:26], a["model_id"]) for a in _mech
|
||||
if (a["model_id"] or "") != _main_model]
|
||||
if _diff:
|
||||
rec("model_same", "C", "fail",
|
||||
"机制建的排期模型 ≠ 本区主会话模型(P0-80)",
|
||||
"主会话=%s;不一致 %d 条:%s" % (_main_model, len(_diff), "、".join(_diff[:3])))
|
||||
else:
|
||||
rec("model_same", "C", "ok", "机制排期模型与本区主会话一致(P0-80)",
|
||||
"主会话=%s,机制排期 %d 条全同" % (_main_model, len(_mech)))
|
||||
|
||||
# ⑨ cwds 归属同形(错一字面 ⇒ 裂组且自我强化)
|
||||
off = _cwds_nearmiss(autos, ws_norm)
|
||||
@@ -481,13 +638,14 @@ def check_orchestration():
|
||||
rec("deliver", "C", "ok", "投递心跳新鲜", "%.0f 秒前" % age)
|
||||
|
||||
# ⑪ 活会话
|
||||
# ⑪ 活会话(🔴 只判 `ROLES_LIVE`=**当前在用的前缀**;历史前缀⛔ 不判"必须有活的")
|
||||
live = {lb: [s for s in sess if (s[1] or "").startswith(pre) and (s[2] or "") == "working"]
|
||||
for lb, pre in ROLES_LIVE}
|
||||
dead = [lb for lb, _ in ROLES_LIVE if not live.get(lb)]
|
||||
if dead:
|
||||
rec("live", "C", "warn", "此刻无活会话(按需创建属正常;主会话开工阶段须建齐)", "、".join(dead))
|
||||
else:
|
||||
rec("live", "C", "ok", "三类会话均有活的",
|
||||
rec("live", "C", "ok", "在用类别的会话均有活的",
|
||||
";".join("%s×%d" % (lb, len(live[lb])) for lb, _ in ROLES_LIVE))
|
||||
|
||||
# ⑫ 死排期:`once` 已无下次触发、却**从未运行过** ⇒ 活静默消失
|
||||
@@ -508,6 +666,42 @@ def check_orchestration():
|
||||
"本线 %d 条已跑完的一次性排期(正常痕迹,不计问题)%s"
|
||||
% (consumed, (";%d 条刚到点、仍在补跑窗口内(⛔ 不是哑火)" % in_grace) if in_grace else ""))
|
||||
|
||||
# ⑬ 🔴🔴 **旧前缀不许在「活的语境」里出现**(2026-10-05 用户定案)
|
||||
# 用户原话:「**兼容个毛线,今天兼容一个明天兼容一个 过不了一周就成大杂烩了**」。
|
||||
# ⇒ 本条把「不许兼容」从**口头要求**变成**机制判据**:任何人在活件里再写旧前缀
|
||||
# ⇒ 本闸门当场报红(⛔ 不靠"下次记得")。
|
||||
# 🔴 判据只扫**能产生/承载新名字的两处**(⛔ 不全文扫 —— 注释与 `references/*.md`
|
||||
# 里的考古记录是**合法保留**的,扫它们就是误红):
|
||||
# ① 活排期名(`automations.name`,未软删)—— 是旧前缀 ⇒ "今天又长出来一条"
|
||||
# ② 活会话标题(`sessions.title`)—— 同上
|
||||
# ⚠️ **不扫**:注释 / docstring / 考古段 / 软删的历史行
|
||||
# —— 那些是"记录过去发生过什么",⛔ 不是"现在还在用"(混为一谈即是误红)。
|
||||
# ✅ **变异对照**(2026-10-05 实跑,证明非恒绿非恒红):
|
||||
# 全 `[执行]` ⇒ ok;混 1 条 `[协作]` ⇒ fail;混 1 条 `[任务会话]` ⇒ fail;
|
||||
# 历史行但**不以旧前缀开头**(如 `接续:…`)⇒ ok。
|
||||
_OLD_WORKER = ("[协作]", "[协作目标]", "[任务会话]")
|
||||
_bad_sched, _bad_sess = [], []
|
||||
for _r in (mine or []):
|
||||
_nm2 = str((_r or {}).get("name") or "")
|
||||
if any(_nm2.startswith(_p) for _p in _OLD_WORKER):
|
||||
_bad_sched.append(_nm2[:44])
|
||||
for _s in (sess or []):
|
||||
_t2 = str(_s[1] or "")
|
||||
if any(_t2.startswith(_p) for _p in _OLD_WORKER):
|
||||
_bad_sess.append(_t2[:44])
|
||||
if _bad_sched or _bad_sess:
|
||||
_det = []
|
||||
if _bad_sched:
|
||||
_det.append("活排期 %d 条:%s" % (len(_bad_sched), "、".join(_bad_sched[:3])))
|
||||
if _bad_sess:
|
||||
_det.append("活会话 %d 条:%s" % (len(_bad_sess), "、".join(_bad_sess[:3])))
|
||||
rec("old_prefix", "C", "fail",
|
||||
"旧前缀又长出来了(新建一律 `[执行]`)", ";".join(_det))
|
||||
else:
|
||||
rec("old_prefix", "C", "ok",
|
||||
"活排期/活会话里无旧前缀(新建一律 `[执行]`)",
|
||||
"⛔ 历史行不在此列:它们是**解析正确性**,删了看板画不出(⛔ 不叫「兼容」)")
|
||||
|
||||
# ── 收口 ───────────────────────────────────────────────────────────────
|
||||
def finish():
|
||||
fails = [c for c in CHECKS if c["level"] == "fail"]
|
||||
|
||||
Reference in new issue
Block a user