session-mechanism:加「锁根不得落进技能包」红线判据

按决策方法论自决(加判据属"实现路径",⛔ 不列入待拍板)。

新增 `session-rules-check.py::check_lockroot_in_pkg()`,注册进体检 A 组:
查 `<技能包>/05-交接单/` 与 `<技能包>/scripts/05-交接单/` 是否被创建
(三个根都查:脚本所在包根、全局技能根下的 session-mechanism、本区技能根下的同名目录)。

为什么是红线:`lock/handoff-guard.sh` 与 `lock/preflight-lock.sh` 的 `DOCS_ROOT`
原先回落到 `$_SM_HERE/..`(=技能包自己的 `scripts/`)⇒ 取不到文档库根时锁目录长在包里
(另一环境实测已出现 `<技能包>/scripts/05-交接单/.locks/`)。后果两条:污染只读发布物;
换工作区后锁跟错地方 ⇒ 域锁静默失效。
两处回落实已改为「取不到就报错 + 留痕 + 退出码 2」(9b839e7 / 22799fc);本条是兜底判据。

变异对照(⛔ 证明它抓得住、不是恒绿):
· 常态 ⇒ `✓ [A] 锁根未落进技能包`
· `mkdir -p <技能包>/scripts/05-交接单` 后 ⇒ `✗ [A] 锁根落进了技能包 ⇒ …`
· `rmdir` 复原 ⇒ 回到 ✓

⛔ 未做成 selftest 自动化用例:那要求真在技能包里造目录,用例中断会把 `05-交接单`
永久留在包里(不可逆污染)。⇒ 保留为判据 docstring 里可手工复现的步骤。

验收:语法 OK;全量自测 PASS 110 / FAIL 0。
This commit is contained in:
admin committed 2026-10-15 22:50:46 +08:00
1 parent 9b839e7a02
commit 74b407ccba
1 file changed
+46
@@ -532,6 +532,51 @@ def _once_zombie(mine, ran, grace_min=30.0):
return never, consumed, unver, in_grace return never, consumed, unver, in_grace
def check_lockroot_in_pkg():
"""🔴 锁根**不得落进技能包**(2026-10-15 立 · 红线)。
为什么是红线:`lock/handoff-guard.sh` 与 `lock/preflight-lock.sh` 的 `DOCS_ROOT`
原先回落到 `$_SM_HERE/..`(=**技能包自己的 `scripts/`**)⇒ 取不到文档库根时,
锁目录直接长在技能包里(另一环境实测已出现 `<技能包>/scripts/05-交接单/.locks/`)。
后果两条:① 污染技能包(它是只读发布物);② 换工作区后锁跟着错地方 ⇒ **域锁静默失效**。
✅ 现行:两处回落实已改成「取不到就如实报错 + 留痕 + 退出码 2」;
本条是**兜底判据** —— 万一将来又有人改回"包内回落",这里要能报出来。
✅ 变异对照(2026-10-15 实测,⛔ 证明它抓得住,不是恒绿):
1. `mkdir -p <技能包>/scripts/05-交接单`
2. 跑 `python session-rules-check.py --ws <本区> --all` ⇒ 输出
`✗ [A] 锁根落进了技能包 ⇒ … —— <技能包>/scripts/05-交接单`
3. `rmdir <技能包>/scripts/05-交接单` 复原 ⇒ 回到 `✓ [A] 锁根未落进技能包`
⚠️ 为什么不把它做成 `selftest.py` 的自动化用例:那样必须**真在技能包里造目录**,
用例中途被打断就会**把一个 `05-交接单` 永久留在包里**(不可逆污染)——
风险大于收益。⇒ 保留为上面这段**可手工复现**的步骤。
"""
roots = []
seen = set()
for r in (os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
os.path.join(SKILLS, "session-mechanism") if SKILLS else "",
os.path.join(WS, ".workbuddy", "skills", "session-mechanism") if WS else ""):
if not r:
continue
r = os.path.normpath(r)
if r not in seen:
seen.add(r)
roots.append(r)
bad = []
for r in roots:
for rel in (("05-交接单",), ("scripts", "05-交接单")):
if os.path.isdir(os.path.join(r, *rel)):
bad.append("%s/%s" % (r, "/".join(rel)))
if bad:
rec("lockroot_in_pkg", "A", "fail",
"锁根落进了技能包 ⇒ 锁长在只读发布物里,且换工作区后会跟错地方(域锁静默失效)",
"、".join(sorted(set(bad))))
else:
rec("lockroot_in_pkg", "A", "ok", "锁根未落进技能包", "")
return bad
def check_orchestration(): def check_orchestration():
if not os.path.isfile(DB): if not os.path.isfile(DB):
rec("clock", "C", "fail", "读不到宿主库", DB) rec("clock", "C", "fail", "读不到宿主库", DB)
@@ -786,6 +831,7 @@ def main():
check_memory_pointers() check_memory_pointers()
check_snapshot_stale() check_snapshot_stale()
check_orchestration() check_orchestration()
check_lockroot_in_pkg()
return finish() return finish()