diff --git a/session-mechanism/install.py b/session-mechanism/install.py index 7ff8b58..a9f32ea 100644 --- a/session-mechanism/install.py +++ b/session-mechanism/install.py @@ -359,6 +359,17 @@ def write_roots(ws: Path, docs: Path | None, code: str | None, dry: bool) -> Non lines.append("%s=%s" % (_k, os.environ[_k].replace(os.sep, "/"))) if docs: lines.append(f"DSH_DOCS_ROOT={docs.as_posix()}") + else: + # 🔴 2026-10-15 修(第 11 条禁令):解析不到文档库时**如实报错 + 留痕**。 + # 原行为是**整行不生成、不报错、不留痕** ⇒ 锁类脚本读不到该键 ⇒ + # `handoff-guard.sh` 回落到**技能包自己的 `scripts/`** ⇒ 锁根落进技能包 + # (D 盘那台实测已在 `<技能包>/scripts/05-交接单/.locks` 长出一把锁)。 + # ⛔ 不许静默省略整行 —— 静默与"本机真没有文档库"表现完全一样,最难查。 + log("⛔ 解析不到文档库根:`DSH_DOCS_ROOT` 未设、`roots.env` 里也没有、启发式也没找到" + "「同层同时有 05-交接单 与 07-scripts」的目录 ⇒ **本次不写** `DSH_DOCS_ROOT`。" + "⚠️ 后果:锁类脚本(`lock/handoff-guard.sh` / `hooks/lock-guard-hook.py` / " + "`collabd.py` 的域锁)取不到锁根。" + "✅ 本区确实没有 dsh 文档库时 ⇒ 把**本工作区根**写进 `env/roots.env` 的 `DSH_DOCS_ROOT`。") if code: lines.append(f"DSH_CODE_REPO={code.as_posix()}") text = "\n".join(lines) + "\n" diff --git a/session-mechanism/scripts/collabd.py b/session-mechanism/scripts/collabd.py index d1a230b..020cc19 100644 --- a/session-mechanism/scripts/collabd.py +++ b/session-mechanism/scripts/collabd.py @@ -3965,18 +3965,32 @@ def ds_active_domain_locks() -> dict: """ out = {} _me = str(os.environ.get("DSH_SESSION_NAME") or "").strip() - # 锁根=文档库的 `05-交接单/.locks`;找不到就按 `DSH_CODE_REPO` 推一次 + # 🔴🔴 锁根=文档库的 `05-交接单/.locks`。**键名必须与写侧逐字同源:`DSH_DOCS_ROOT`** + # (写侧六处:`lock/handoff-guard.sh:46`/`lock/preflight-lock.sh:30`/`lock/handoff-status.py:40`/ + # `lock/orphan-lock.py:203`/`hooks/lock-guard-hook.py:74`/`session-rules-check.py:107`) + # 2026-10-15 修:原读 `DSH_LOCKROOT`/`DSH_DOCS_REPO` —— 这两个键**全包只此一处**读, + # `env/roots.env.example` 里也没有 ⇒ 读写两侧**永不重合** ⇒ 读侧恒判「锁根定位不到」。 + # ✅ 现行:`DSH_LOCKROOT` 保留为**高位兼容别名**(有值时优先),⛔ 不再读 `DSH_DOCS_REPO`。 _cands = [] - if os.environ.get("DSH_LOCKROOT"): + if os.environ.get("DSH_LOCKROOT"): # 兼容别名(高位) _cands.append(os.environ["DSH_LOCKROOT"]) - _dr = os.environ.get("DSH_DOCS_REPO") or "" + _dr = (os.environ.get("DSH_DOCS_ROOT") or "").strip() # ← 与写侧同源 if _dr: _cands.append(os.path.join(_dr, "05-交接单", ".locks")) - for _p in (r"D:\github\dsh_shenxian\dsh-server-docs\05-交接单\.locks", - "D:/github/dsh_shenxian/dsh-server-docs/05-交接单/.locks"): - _cands.append(_p) root = next((c for c in _cands if os.path.isdir(c)), "") if not root: + # ⛔ 不再回落到"猜的盘符"(第 11 条禁令):如实报错 + 留痕。 + # 静默扫空与"本机真没有"表现完全一样,是最难查的一类假象。 + try: + _lg = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "logs", "_collabd.log") + _lg = os.path.normpath(_lg) + os.makedirs(os.path.dirname(_lg), exist_ok=True) + with open(_lg, "a", encoding="utf-8", errors="replace") as _f: + _f.write("[%s] ⛔ 域锁锁根定位不到:`DSH_DOCS_ROOT` 为空或该目录不存在" + "(候选=%r;⛔ 不再回落到猜的盘符)\n" + % (time.strftime("%Y-%m-%d %H:%M:%S"), _cands)) + except Exception: + pass return {"__unreadable__": "(锁根定位不到)"} try: for nm in os.listdir(root): diff --git a/session-mechanism/scripts/hooks/lock-guard-hook.py b/session-mechanism/scripts/hooks/lock-guard-hook.py index 191b3fb..7f5077f 100644 --- a/session-mechanism/scripts/hooks/lock-guard-hook.py +++ b/session-mechanism/scripts/hooks/lock-guard-hook.py @@ -11,8 +11,8 @@ lock-guard-hook.py — 「无锁不许改库」的强制钩子(WorkBuddy PreTo 作用域(**刻意收窄:对其他项目零影响**) ──────────── 仅当 `Write` / `Edit` 的目标路径落在下列根之内,才做锁判定;其余一律放行: - · 文档库 (默认 E:\\ProgramData\\AIProject\\ai1net-dsh-server\\dsh-server-docs) - · 代码库 (默认 D:\\github\\dsh_shenxian) + · 文档库 (取自本包 `env/roots.env`,由 `install.py` 生成) + · 代码库 (默认=文档库的上一级) ⚠️ 例外:路径中含 `.workbuddy` 目录段的一律放行 —— 会话记忆 / 自动化工作数据属“运行态数据”,不是“仓库内容”。(2026-09-13 实测:「代码仓三方同步(DSH)」自动化写自己的 memory/*.md 时被本钩子 deny,属误伤) 判据 @@ -69,9 +69,26 @@ except Exception: sys.stderr.write('[env] 无法导入 _env.py ⇒ 环境定位不可信\n') raise -# ⚠️ 文档库根:env/roots.env 优先;末位兜底**故意保留字面量** —— 锁守卫一旦静默解析不到, -# 就会"看起来在守、其实没守"(比指错更危险)。换机器请让 install.py 写 roots.env。 -DOCS_ROOT = os.environ.get("DSH_DOCS_ROOT") or r"D:\github\dsh_shenxian\dsh-server-docs" +# ⚠️ 文档库根:env/roots.env 优先。 +# 🔴 2026-10-15 改(用户令「包内代码不许留盘符字面量」· 第 11 条禁令): +# 删掉原末位兜底 `or r"D:\github\..."`。它自以为在防「静默没守」,实际制造两个更坏的后果 —— +# ① 那个盘符是**本机写死的**,换机器或文档库搬家后该目录不存在 ⇒ 照样「看起来在守、其实没守」 +# (D 盘那台实测:兜底路径不存在,锁守卫静默放行); +# ② 与本包第 11 条禁令直接冲突。 +# ✅ 现行:解析不到 ⇒ **如实报错 + 留痕 + 放行**(fail-open,⛔ 不拦用户提交),⛔ 不猜路径。 +DOCS_ROOT = (os.environ.get("DSH_DOCS_ROOT") or "").strip() +if not DOCS_ROOT: + try: + _lg = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "logs", + "lock-guard.err.log") + _lg = os.path.normpath(_lg) + os.makedirs(os.path.dirname(_lg), exist_ok=True) + with open(_lg, "a", encoding="utf-8", errors="replace") as _f: + _f.write("[%s] ⛔ 锁守卫取不到 `DSH_DOCS_ROOT`(env/roots.env 缺该键)⇒ 本轮**放行**" + "(fail-open)。⛔ 未回落到猜的盘符。\n" % time.strftime("%Y-%m-%d %H:%M:%S")) + except Exception: + pass + sys.exit(0) # ⛔ 不留盘符:代码库=文档库的上一级(`/dsh-server-docs` 的约定) CODE_REPO = os.environ.get("DSH_CODE_REPO") or (os.path.dirname(DOCS_ROOT) if DOCS_ROOT else "") LOCK_DIR = os.path.join(DOCS_ROOT, "05-交接单", ".exec-lock") diff --git a/session-mechanism/scripts/lock/handoff-guard.sh b/session-mechanism/scripts/lock/handoff-guard.sh index bc64b0a..d5e6d47 100644 --- a/session-mechanism/scripts/lock/handoff-guard.sh +++ b/session-mechanism/scripts/lock/handoff-guard.sh @@ -43,7 +43,21 @@ if [ -f "$_SM_PKG/env/roots.env" ]; then elif [ -f "$_SM_PKG/roots.env" ]; then . "$_SM_PKG/roots.env" fi -ROOT="${DSH_DOCS_ROOT:-$(cd "$_SM_HERE/.." && pwd)}" +# 🔴 2026-10-15 修(第 11 条禁令 + D 盘实测事故): +# 原写 `ROOT="${DSH_DOCS_ROOT:-$(cd "$_SM_HERE/.." && pwd)}"` —— 回落值是 +# `$_SM_HERE/..` = **本技能包自己的 `scripts/`** ⇒ 锁根落进技能包 +# (实测已在 `<技能包>/scripts/05-交接单/.locks` 长出一把锁,本该落文档库)。 +# ✅ 现行:取不到 ⇒ **如实报错 + 留痕 + 退出码 2**,⛔ 不回落到包内、⛔ 不猜路径。 +if [ -z "${DSH_DOCS_ROOT:-}" ]; then + _SM_ERR="${_SM_PKG}/logs/handoff-guard.err.log" + mkdir -p "$(dirname "$_SM_ERR")" 2>/dev/null || true + printf '%s ⛔ 取不到 DSH_DOCS_ROOT(本包 env/roots.env 缺该键)⇒ 退出码 2\n' \ + "$(date '+%Y-%m-%d %H:%M:%S')" >> "$_SM_ERR" 2>/dev/null || true + printf '⛔ 取不到文档库根(DSH_DOCS_ROOT)—— 锁根无法定位 ⇒ 停手(退出码 2)。\n' >&2 + printf ' ✅ 修法:把本工作区根写进 <技能包>/env/roots.env 的 DSH_DOCS_ROOT,或跑 install.py --apply。\n' >&2 + exit 2 +fi +ROOT="$DSH_DOCS_ROOT" cd "$ROOT" || exit 2 LOCKDIR="$ROOT/05-交接单/.doing-" LOCKEXEC="$ROOT/05-交接单/.exec-lock"