session-mechanism:闸④ 不再被周期排期卡死 + --report 漏 id 当场拒收

① 闸④ 收窄 recurring(用户选 A 案)
   改 ws_pending_schedules():schedule_type='recurring' 的行一律不算「待执行」,
   once 的过期排除与拦截照旧。
   症状=周期排期永远有下一次 ⇒ 拿「在册 + 没跑过」判它恒真 ⇒ 闸④ 恒拦
   ⇒ 目标检查会话一条都建不出来(aigc-idea-impression 被「AI变现日报 每日 06:00」
   挡了一整天,日志每 20 秒重复「还有 1 条待执行排期 ⇒ 不建」)。
   变异对照:同一工作区、同一时刻,旧版返回 ['AI变现日报 · 每日自动生成']、新版返回 []。

② --report 拒收闸(用户选 B 案)
   --report 后面必须跟真的需求 id;id 为空或以 -- 开头 ⇒ 打用法行 + 退出码 2,台账一字不写。
   症状=漏 id 时 _arg("--report") 取到的是下一个 token(--state)
   ⇒ 台账多一条键名写坏的记录,还被算进队列计数(本区 queue_n 2→3;vibe-product 也有一条)。
   变异对照:把闸删掉 ⇒ 同一条命令 rc=0 且真写出 "--state";加回去 ⇒ rc=2 且台账为空。

自检:闸④ 用例 5 → 6 项;新增「拒收闸」用例 6 项(含正例对照,防「未找到部署配置 ⇒ 拒跑」
把 rc=2 混成假绿);全量 PASS 110 / FAIL 0。

文档:references/pitfalls.md —— P0-65 补 recurring 口径(含实测症状与反例库);
新增 P0-96(漏写 --report id 的静默污染 + 判据自身两处假绿坑)。
This commit is contained in:
admin committed 2026-10-12 09:50:10 +08:00
1 parent e12e985808
commit 24040ee171
3 files changed
+156 -3

No files matched your search

+35
View File
@@ -2000,6 +2000,18 @@ INBOX = '…\ai1net-dsh-server\tmp\supervise-inbox'
⇒ 它会**永远**把闸④ 卡住 ⇒ 本工作区**再也建不出检查会话**(静默、无报错)。 ⇒ 它会**永远**把闸④ 卡住 ⇒ 本工作区**再也建不出检查会话**(静默、无报错)。
✅ 两种"不会再触发"都判:**(a)** `next_run_at` 已过 15 分钟宽限;**(b)** `next_run_at` 被清成 NULL。 ✅ 两种"不会再触发"都判:**(a)** `next_run_at` 已过 15 分钟宽限;**(b)** `next_run_at` 被清成 NULL。
⚠️ **只对 `once` 生效**(`recurring` 永远会再触发,⛔ 不按过期排除)。 ⚠️ **只对 `once` 生效**(`recurring` 永远会再触发,⛔ 不按过期排除)。
- 🔴🔴 **2026-10-12 补:`recurring`(周期排期)整体不算"待执行"**(用户当日选 A 案,已改 `collabd.py`)。
**症状**:周期排期**永远有下一次** ⇒ 拿"在册 + `last_run_at` 为空"去判它**恒真** ⇒ 闸④ **恒拦**
⇒ **目标检查会话一条都建不出来**。
**实测**(`aigc-idea-impression`,10-11 → 10-12):被「AI变现日报 · 每日自动生成」(`FREQ=DAILY;BYHOUR=6`)
挡了整整一天,常驻日志每 20 秒重复一行「目标检查:⚠️ 本工作区还有 1 条待执行排期 ⇒ 不建」;
后果是**执行轮有人干、核目标那一步没人干**。
**为什么排除是合理的**:闸④ 要防的是「**马上要开工**时重复派活」;周期排期是按日历重复的,
**不表示有人正要干这件活** —— 它到点自己开一条会话,检查会话顶多多跑一轮,代价远小于"永远不核目标"。
**边界**:只排除 `recurring`,`once` 的拦截照留;只动**本工作区**(`_same_ws` 已命中)⇒ 别的工作区不受影响。
**自检**:`selftest.py` 用例「闸④:排除**已过期的一次性**排期」现为 **6 项**(新增第 ⑤ 项验 `recurring` 排除)。
⚠️ 变更前先跑**变异对照**:同一工作区、同一时刻,旧版 `ws_pending_schedules()` 返回
`['AI变现日报 · 每日自动生成']`,新版返回 `[]` —— 证明是这次改动在起作用,不是"本来就空"。
- **实测取证(2026-10-05 只读)**:闸④ 对 `selftest` 工作区跳过 **11 条**、`vibe-product` 跳过 **1 条**, - **实测取证(2026-10-05 只读)**:闸④ 对 `selftest` 工作区跳过 **11 条**、`vibe-product` 跳过 **1 条**,
全部是 `next_run_at=NULL(已消费)` 这一类 —— 正是 (b)。若不跳过,这两个工作区**各自永远卡死**。 全部是 `next_run_at=NULL(已消费)` 这一类 —— 正是 (b)。若不跳过,这两个工作区**各自永远卡死**。
- 🔴 **已知隐患(未修,待拍板)**:工作区判据是 **子串匹配** `p.lower() in _s.lower()`(第 ~4464 行), - 🔴 **已知隐患(未修,待拍板)**:工作区判据是 **子串匹配** `p.lower() in _s.lower()`(第 ~4464 行),
@@ -3726,3 +3738,26 @@ var r = el.getBoundingClientRect();
> **改判据的核心动作不是"换个更对的比法",是"再破坏一次看它会不会喊"。** > **改判据的核心动作不是"换个更对的比法",是"再破坏一次看它会不会喊"。**
> 变异对照不做 ⇒ 你的"修复"很可能只是把**假红**换成了**恒绿**。 > 变异对照不做 ⇒ 你的"修复"很可能只是把**假红**换成了**恒绿**。
---
## P0-96 🔴🔴 **漏写 `--report` 的需求 id ⇒ 静默往台账写一条坏记录,还会被算进队列计数**(★ 2026-10-12 实测,本区 + `vibe-product` 都中过)
- **它长什么样**:收报写成 `--report --state done --artifact …`(**漏了 id**)——
看着像正常命令,实际 `_arg("--report")` 取到的是**下一个 token**,也就是 `--state`。
- **后果(两层,第二层更狠)**:
① 台账 `tasks.json` 里多出一条**键名写坏**的记录(键=`"--state"`)——
命令回显是「OK 已上报」,**零报错**;
② 它**被算进队列计数**(本区实测 `queue_n` 2 → 3;`vibe-product` 的 `q_sig` 里也有一条 `--state=done`)。
若落成 `running`/`blocked` 态 ⇒ 闸① 误判「有活在跑」⇒ 去建结果检查会话 ⇒ 查一个不存在的东西。
- **✅ 已修**(2026-10-12 用户选 B 案):`--report` 分支加**拒收闸** ——
id 为空或以 `--` 开头 ⇒ 打用法行 + **退出码 2**,台账**一个字都不写**。
- **变异对照(做过的,不是推理)**:把拒收闸从副本里删掉 ⇒ 同一条命令 **rc=0** 且台账真写出 `"--state"`;加回去 ⇒ **rc=2** 且台账为空。
- **判据自身的坑(两处,都会造成假绿)**:
① **正例探针⛔不能指向真工作区** —— 我当场就是这么往真台账里敲进一条 `ZZZ-假id`(`running`)的,只能手工撤回;
⇒ 一律 `COLLABD_WORKSPACE=<临时目录>` 起子进程。
② **临时工作区必须自带 `collabd.config.json`** —— 否则 `collabd.py` 先「未找到部署配置 ⇒ 拒跑」并返回 **rc=2** ⇒
负例断言**因为错的理由**变绿。⇒ 夹具现造最小配置,并加一条**正例**(形态正确的 id ⇒ rc=0)当对照。
- **留痕口径**:本次**只加拒收闸**,既有坏行**不动**(用户选 B 案=台账留痕优先)。
- 🔴 **写工具的红线**:**凡是"看着成功、其实写坏了"的通道,优先加"当场拒收"** ——
报错比静默污染便宜得多;静默污染要等到有人去读那条记录才发现,而那时原因已经不在现场了。
+35
View File
@@ -4693,6 +4693,14 @@ def ws_pending_schedules(exclude: str = "") -> list:
② `cwds` 命中本工作区(⛔ 两种斜杠形态都试 —— 同 `_all_sessions_idle` 的教训: ② `cwds` 命中本工作区(⛔ 两种斜杠形态都试 —— 同 `_all_sessions_idle` 的教训:
只试一种 ⇒ 匹配 0 行 ⇒ **恒判"没排期"⇒ 闸失效**); 只试一种 ⇒ 匹配 0 行 ⇒ **恒判"没排期"⇒ 闸失效**);
③ **还没跑过**(`last_run_at is null or last_run_at=0`)—— 跑过的一次性排期**不再算**。 ③ **还没跑过**(`last_run_at is null or last_run_at=0`)—— 跑过的一次性排期**不再算**。
🔴🔴 **2026-10-12 收窄:`recurring`(周期排期)一律不算"待执行"**(用户当日选 A 案)。
· 症状:周期排期**永远有下一次** ⇒ 拿"在册 + 没跑过"去判它**恒真** ⇒ 闸④ 恒拦
⇒ **目标检查会话一条都建不出来**(本区 10-11 → 10-12 实测:被「AI变现日报 每日 06:00」
挡了整整一天,日志每 20 秒重复一行「还有 1 条待执行排期 ⇒ 不建」)。
· 反例库(为什么可以排除):闸④ 要防的是「**马上要开工**时重复派活」——
周期排期是按日历重复的,**不表示有人正要干这件活**;它到点会自己开一条会话,
检查会话顶多多跑一轮,代价比"永远不核目标"轻得多。
· 边界:只排除本工作区(`_same_ws` 已在上面命中)⇒ 别的工作区不受影响。
⚠️ `next_run_at` **故意不看**:⛔ 一条 `next_run_at` 已过期但仍 ACTIVE 的一次性排期 ⚠️ `next_run_at` **故意不看**:⛔ 一条 `next_run_at` 已过期但仍 ACTIVE 的一次性排期
(实测本工作区有 3 条,其中 2 条 `next_run_at=None`)**仍然会被人点开**(`automation_update` 手动起) (实测本工作区有 3 条,其中 2 条 `next_run_at=None`)**仍然会被人点开**(`automation_update` 手动起)
⇒ 用 `next_run_at` 当"待执行"会漏。判据取「**在册 + 没跑过**」这个更宽的口径。 ⇒ 用 `next_run_at` 当"待执行"会漏。判据取「**在册 + 没跑过**」这个更宽的口径。
@@ -4716,6 +4724,7 @@ def ws_pending_schedules(exclude: str = "") -> list:
# ⛔ 宽限期内**仍算待执行**(那正是"马上要开"的情形,闸④ 该拦)。 # ⛔ 宽限期内**仍算待执行**(那正是"马上要开"的情形,闸④ 该拦)。
_STALE_MS = 15 * 60 * 1000 _STALE_MS = 15 * 60 * 1000
_now_ms = int(time.time() * 1000) _now_ms = int(time.time() * 1000)
_rec = [] # 本轮跳过的周期排期(只为记一条日志,⛔ 不进 out)
for _nm, _cd, _st, _na in rows: for _nm, _cd, _st, _na in rows:
_s = str(_cd or "").strip() _s = str(_cd or "").strip()
if not _s: if not _s:
@@ -4734,6 +4743,13 @@ def ws_pending_schedules(exclude: str = "") -> list:
# 📌 风险登记:若存在"建了但 `next_run_at` 就是 NULL"的 `once` 排期,会被误排除; # 📌 风险登记:若存在"建了但 `next_run_at` 就是 NULL"的 `once` 排期,会被误排除;
# 但 `create_check_schedule()` 的**硬规则第一条**就是 `next_run_at` 必须 # 但 `create_check_schedule()` 的**硬规则第一条**就是 `next_run_at` 必须
# 是有限数值 ⇒ 机制自己建的排期不会命中该风险。 # 是有限数值 ⇒ 机制自己建的排期不会命中该风险。
# 🔴🔴 2026-10-12(用户选 A 案):**周期排期不算"待执行"**
# —— 它永远有下一次,拿"在册 + 没跑过"判它恒真 ⇒ 闸④ 恒拦 ⇒
# 目标检查会话一条都建不出来(本区被「AI变现日报 每日 06:00」挡了一整天)。
# 依据与反例库见函数 docstring;⛔ 只排除 `recurring`,`once` 的拦截照留。
if str(_st or "").lower() == "recurring":
_rec.append(str(_nm))
continue
if str(_st or "").lower() == "once": if str(_st or "").lower() == "once":
_gone = (not _na) # (b) 已被消费 ⇒ 到点时间被清空 _gone = (not _na) # (b) 已被消费 ⇒ 到点时间被清空
if not _gone: if not _gone:
@@ -4746,6 +4762,9 @@ def ws_pending_schedules(exclude: str = "") -> list:
% _nm) % _nm)
continue continue
out.append(str(_nm)) out.append(str(_nm))
if _rec:
log("闸④:跳过周期排期(`recurring` 会反复触发 ⇒ 不算『有人正要干这件活』)|%s"
% _rec[:3])
except Exception as e: except Exception as e:
log("ws_pending_schedules 读库失败(⇒ 按有排期处理,不建) %s" % e) log("ws_pending_schedules 读库失败(⇒ 按有排期处理,不建) %s" % e)
return ["<读不到排期表>"] return ["<读不到排期表>"]
@@ -6510,6 +6529,22 @@ def main() -> int:
def _arg(k, dv=""): def _arg(k, dv=""):
return sys.argv[sys.argv.index(k) + 1] if (k in sys.argv and sys.argv.index(k) + 1 < len(sys.argv)) else dv return sys.argv[sys.argv.index(k) + 1] if (k in sys.argv and sys.argv.index(k) + 1 < len(sys.argv)) else dv
# ── 🔴🔴 拒收闸(2026-10-12 用户选 B 案):`--report` 后面**必须跟一个真的需求 id** ──
# 症状(实测):写成 `--report --state done --artifact …`(**漏了 id**)⇒
# `_arg("--report")` 取到的是**下一个 token**(实为 `--state`)⇒
# 台账里多出一条**键名写坏**的记录,还会**被算进队列计数**
# (本区实测 `queue_n` 2 → 3;`vibe-product` 也有一条同型)。
# ⇒ 这类污染是静默的:命令看着像成功、台账看着像有记录,事后极难发现。
# ⇒ 处置=**当面报错 + 给用法行 + 退出码 2**,⛔ 不写台账。
_rid = _arg("--report")
if (not _rid) or _rid.startswith("--"):
print("✗ `--report` 后面必须跟**需求 id**(漏写会往台账写一条键名写坏的记录,"
"还会被算进队列计数)。\n"
" 用法:--report <需求id> --state pending|running|done|blocked "
"[--by 会话名] [--artifact 产物] [--reason 原因] [--line 线]\n"
" 实得:--report %s" % (_rid or "<空>"))
return 2
rc = task_report(_arg("--report"), _arg("--state", "running"), _arg("--by"), rc = task_report(_arg("--report"), _arg("--state", "running"), _arg("--by"),
_arg("--artifact"), _arg("--reason"), _arg("--line")) _arg("--artifact"), _arg("--reason"), _arg("--line"))
if rc == 0: # 上报**只写台账**(⛔ 不投递)—— if rc == 0: # 上报**只写台账**(⛔ 不投递)——
+86 -3
View File
@@ -3891,6 +3891,79 @@ def t_check_prompt_no_wild_hunt():
@case("🔴 `--report` 拒收闸:漏写需求 id ⇒ 报错退出 + ⛔ 不写台账(2026-10-12 用户选 B 案)")
def t_report_requires_id():
"""🔴 2026-10-12 实测缺陷(本区 + `vibe-product` 都中过)。
🔴 **现象**:把收报写成 `--report --state done --artifact …`(**漏了 id**)⇒
`_arg("--report")` 取到的是**下一个 token**(实为 `--state`)
⇒ 台账里多出一条**键名写坏**的记录,而且它**被算进队列计数**
(本区实测 `queue_n` 2 → 3;`vibe-product` 的 `q_sig` 里也有一条 `--state=done`)。
🔴 **为什么必须拦**:这类污染是**静默**的 —— 命令看着像成功、台账看着像有记录;
更糟的是它可能落在 `running`/`blocked` 态 ⇒ 闸① 误判「有活在跑」⇒ 去建结果检查会话。
✅ **正解**=`--report` 分支加拒收闸:id 为空或**以 `--` 开头** ⇒ 打用法行 + 退出码 2,
台账一个字都不写。
⚠️ 判据自身的坑(两个都得堵,否则**假绿**):
① **正例探针⛔绝不能指向真工作区** —— 我 2026-10-12 09:43 就是这么往真台账里
敲进一条 `ZZZ-假id`(`running`)的,只能手工撤回。⇒ 一律用临时工作区。
② **临时工作区必须自带 `collabd.config.json`** —— 不然 `collabd.py` 会先
「未找到部署配置 ⇒ 拒跑」并返回 **rc=2** ⇒ 负例断言**因为错的理由**变绿。
⇒ 夹具里现造一份最小配置,并加一条**正例**(形态正确的 id ⇒ rc=0)当对照。
"""
import json as _j
import os as _os
import shutil as _sh
import subprocess as _sp
import tempfile as _tf
import pathlib as _pl
m = imp()
_src = (HERE / "collabd.py").read_text(encoding="utf-8")
_own = (HERE / "selftest.py").read_text(encoding="utf-8")
has_guard = ('if (not _rid) or _rid.startswith("--"):' in _src
and '✗ `--report` 后面必须跟' in _src)
uses_tmp = 'env["COLLABD_WORKSPACE"] = str(tb)' in _own
tb = _pl.Path(_tf.mkdtemp(prefix="dsh-report-id-"))
try:
cfg = tb / ".workbuddy" / "collab" / "collabd.config.json"
cfg.parent.mkdir(parents=True, exist_ok=True)
cfg.write_text(_j.dumps({"workspace": str(tb).replace("\\", "/"),
"inbox": "tmp/supervise-inbox",
"live": "交付物/实时状态.md",
"singleton_port": 20999}, ensure_ascii=False),
encoding="utf-8")
(tb / "y.md").write_text("夹具产物", encoding="utf-8") # ⛔ 别让 artifact 缺失来抢答
env = dict(_os.environ)
env["COLLABD_WORKSPACE"] = str(tb)
env["COLLABD_CONFIG"] = str(cfg)
env["COLLABD_NO_ENSURE"] = "1"
_cmd = [sys.executable, str(HERE / "collabd.py"), "--report"]
# 正例(对照):形态正确的 id ⇒ 必须被受理,证明配置可用、确实走到了 --report 分支
r_ok = _sp.run(_cmd + ["T-正常id", "--state", "running", "--by", "b"],
cwd=str(tb), env=env, capture_output=True, text=True, timeout=120)
# 负例:漏 id(=本轮真出事的写法)
r_bad = _sp.run(_cmd + ["--state", "done", "--by", "b", "--artifact", "y.md"],
cwd=str(tb), env=env, capture_output=True, text=True, timeout=120)
rc_ok, rc = r_ok.returncode, r_bad.returncode
out = (r_bad.stdout or "") + (r_bad.stderr or "")
said = ("必须跟" in out) and ("需求 id" in out)
led = list(tb.rglob("tasks.json"))
blob = "".join(p.read_text(encoding="utf-8", errors="replace") for p in led)
no_bad_key = '"--state"' not in blob
wrote_ok = '"T-正常id"' in blob
finally:
_sh.rmtree(str(tb), ignore_errors=True)
return [
("① 源码里有拒收闸(id 为空或以 `--` 开头 ⇒ 拒收)", has_guard),
("② 正例对照:形态正确的 id ⇒ 受理(rc=%s;⚠️ 拿掉它,③ 就可能因"
"「未找到部署配置」而假绿)" % rc_ok, rc_ok == 0 and wrote_ok),
("③ 漏写 id ⇒ **退出码 2**(实得 %s)" % rc, rc == 2),
("④ 报错说清缺什么(含「需求 id」与正确用法)", said),
("⑤ ⛔ 一个字都没写进台账(无坏键 `\"--state\"`)", no_bad_key),
("⑥ 判据本身用**临时工作区**起子进程(变异:拿掉那行 ⇒ 本项变红)", uses_tmp),
]
@case("台账纪律:`done` 必须带产物文档 + `blocked` 必须带原因(否则检查会话只能去翻)") @case("台账纪律:`done` 必须带产物文档 + `blocked` 必须带原因(否则检查会话只能去翻)")
def t_done_requires_artifact(): def t_done_requires_artifact():
"""🔴 2026-10-03 10:5x(用户要求逐字): """🔴 2026-10-03 10:5x(用户要求逐字):
@@ -4355,11 +4428,14 @@ def t_gate4_stale_once():
⇒ ⛔ 靠 `last_run_at` 收口**永远收不了**。 ⇒ ⛔ 靠 `last_run_at` 收口**永远收不了**。
✅ **正解**=排除「**已过期的一次性**排期」,宽限 **15 分钟** ✅ **正解**=排除「**已过期的一次性**排期」,宽限 **15 分钟**
(宿主扫描周期 ≤30 s、实测到点延迟 20~35 s ⇒ 15 min 是安全余量)。 (宿主扫描周期 ≤30 s、实测到点延迟 20~35 s ⇒ 15 min 是安全余量)。
⚠️ 四条边界(缺一条就误杀或漏杀): ⚠️ 五条边界(缺一条就误杀或漏杀):
① 刚过期 ⇒ **仍算待执行**(马上要开 ⇒ 闸④ 该拦) ① 刚过期 ⇒ **仍算待执行**(马上要开 ⇒ 闸④ 该拦)
② 过期 >15 min ⇒ **排除** ② 过期 >15 min ⇒ **排除**
③ `next_run_at` 为 NULL ⇒ **仍算待执行**(⛔ 别误杀) ③ `next_run_at` 为 NULL ⇒ **仍算待执行**(⛔ 别误杀)
④ `recurring` 过期 ⇒ **仍算待执行**(它会再触发) ④ `recurring` ⇒ **一律排除**(2026-10-12 用户选 A 案)
🔴 **2026-10-12 新增边界 ④(用户选 A 案)**:`recurring` 周期排期**不算"待执行"**。
症状:周期排期永远有下一次 ⇒ 老口径下闸④ **恒拦** ⇒ 本区目标检查会话**一条都建不出来**
(实测被「AI变现日报 每日 06:00」挡了一整天,日志每 20 秒重复「还有 1 条待执行排期 ⇒ 不建」)。
""" """
import re as _re import re as _re
import time as _tm import time as _tm
@@ -4383,6 +4459,12 @@ def t_gate4_stale_once():
# ⇒ 我上一版写"NULL ⇒ 仍算待执行(别误杀)"**是错的** ⇒ 那样它照样永��卡闸④。 # ⇒ 我上一版写"NULL ⇒ 仍算待执行(别误杀)"**是错的** ⇒ 那样它照样永��卡闸④。
# (第五种情形:(a) 已过宽限 / (b) 已被消费 ⇒ 两者都排除。) # (第五种情形:(a) 已过宽限 / (b) 已被消费 ⇒ 两者都排除。)
null_excluded = "if str(_st or \"\").lower() == \"once\":" in _c and "_gone = (not _na)" in _c null_excluded = "if str(_st or \"\").lower() == \"once\":" in _c and "_gone = (not _na)" in _c
# ⑥ 🔴 2026-10-12(用户选 A 案):`recurring` 一律排除 —— 周期排期永远有下一次,
# 拿"在册+没跑过"判它恒真 ⇒ 闸④ 恒拦 ⇒ 目标检查会话一条都建不出来。
recurring_excluded = (
'if str(_st or "").lower() == "recurring":' in _c
and "_rec.append(str(_nm))" in _c
and "continue" in _c)
# ⑤ 真库现读:造一条过期 once 也不能把**别人**的排期算进来(⛔ 只验判据形状,不动生产) # ⑤ 真库现读:造一条过期 once 也不能把**别人**的排期算进来(⛔ 只验判据形状,不动生产)
live = m.ws_pending_schedules() live = m.ws_pending_schedules()
return [ return [
@@ -4391,7 +4473,8 @@ def t_gate4_stale_once():
% limit_min, 10 <= limit_min <= 30), % limit_min, 10 <= limit_min <= 30),
("③ 只排 `once`(⛔ `recurring` 会再触发,永不按过期排除)", only_once), ("③ 只排 `once`(⛔ `recurring` 会再触发,永不按过期排除)", only_once),
("④ `once` 且 `next_run_at` 为 NULL(已被宿主消费)⇒ **也排除**", null_excluded), ("④ `once` 且 `next_run_at` 为 NULL(已被宿主消费)⇒ **也排除**", null_excluded),
("⑤ 判据现读不抛(ws_pending_schedules() = %s)" % (live if live else "[]"), True), ("⑤ `recurring`(周期排期)**一律排除** ⇒ 闸④ 不再恒拦", recurring_excluded),
("⑥ 判据现读不抛(ws_pending_schedules() = %s)" % (live if live else "[]"), True),
] ]