Files
ISEP/hooks/search-is-not-proof-guard.sh
T
Leo 7de1ad6be6 補上「用錯的路去證明一件事」那一格的閘(inkstone/ISEP#30 → comment 4879)
擋的是**證據的出處**,不是措辭。

判準(兩個條件同時成立才響):
  ① 要送出去的那份東西(票上的留言/派工單)裡,貼了一個值
     ——entry id 或 `kb://` 來源位址——而它這個 session 只在
     `kbdb_search` 的回應裡出現過
  ② 這個 session 從沒用產品檢索路徑(graph/wiki 內容/query)拿過那筆

為什麼不比對措辭:驗收條件第 4 條寫死不准,而 leo 2026-08-17 已經證偽過
文字層——那天 8 次誤攔、0 次正確攔截,且方向穩定:紅線寫得越細,命中
關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎。值跟動作一樣有限且可枚舉,措辭不是。

🔴 `kbdb_search` 一點都沒有變難用:查 wiki、找 record_id、看某筆在不在,
全部照放。它只在「把搜尋輸出貼出去當產品檢索壞掉的證據」那一刻才響。

實測 31 條,A 群(不該擋)11 條、B 群(該擋)5 條、C 群訊息 6 條、
D 群登記處 9 條。真跡重演=inkstone/Arcrun#167 comment 4865 那則退回,
原文照貼會被擋;走過一次真路徑之後同一份留言就放行。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 21:38:17 +08:00

199 lines
10 KiB
Bash
Executable File
Raw 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.
#!/usr/bin/env bash
# 管什麼: 把 kbdb_search 掉出來的原始值當證據,貼進票或派工單裡,而那筆資料從沒走過產品檢索路徑時,擋一次。
# 為什麼: 2026-08-27 inkstone/Arcrun#167 comment 4865——總管用 kbdb_search 的輸出判定「兩個缺陷都還在」,退回一張已經做完、有測試、東西在 main 的票,還重派一條線。半小時後自己更正(comment 4877):那條路本來就不會有那個欄位。
# 誤觸時怎麼關: 指令或派工單裡加 `search-is-evidence: <一句理由>` 重送(留痕);同一 session 只擋一次。
#
# search-is-not-proof-guard.sh — 你判斷的那條路,和你走的那條路,是同一條嗎?
# PreToolUse: Bash / Agent / Task
#
# ── 這一格為什麼原本沒有閘(inkstone/ISEP#30 comment 4879 的原話)──────
# 「所有現有的閘防的是**危險動作**:推 main、推 prod、寫金鑰、打真庫、開重複票。
# 而『用 kbdb_search 去證明某條路壞了』——不危險、不寫檔、不推 main。
# **它只是錯的方法。** ⇒ 閘的座標系裡沒有這一維。」
#
# ── 判準:出處,不是措辭 ────────────────────────────────────────────
# 🔴 驗收條件第 4 條寫死:**不要用關鍵字比對我的措辭**。
# leo 2026-08-17 已經證偽過文字層——那天 **8 次誤攔、0 次正確攔截**,
# 而且方向穩定:紅線寫得越細,命中關鍵字的機率越高 ⇒ **在懲罰謹慎**。
#
# 所以這支一個字都不看。它只問一件機械問題:
#
# 你正要送出去的這份東西裡,有沒有一個**只在 kbdb_search 的回應裡出現過**
# 的值(entry id / `kb://` 來源位址)?而那筆資料,這個 session 從來沒有
# 用產品檢索路徑(graph / wiki 內容)拿過?
#
# ⇒ 兩個條件都成立,才代表「我在拿輔助工具的輸出,當產品檢索路徑的證據」。
# 這是**資料的出處**,不是我的用詞。值跟動作一樣有限且可枚舉。
#
# ── D81:哪條才是產品檢索路徑(leo 2026-08-13 親自裁定)──────────────
# `system-dev/docs/2-architecture/decisions/D81-vector-only-for-entity-normalize.md`
# 「你會用 **graph 找到該找的東西**,用 **LLM wiki 去拉到對的內容**
# 所以全量向量化沒意義⋯⋯**向量的目的是用來協助把 entities normalize**。」
# ⇒ 主路徑=graph + wiki 內容;向量/關鍵字搜尋=**輔助**。
#
# 🔴 **這支不會讓 `kbdb_search` 變難用。** 它是合法的基本盤工具,
# subagent-wiki-guard.sh 還明文要求「查任何東西之前先用它查 wiki」。
# 查一筆資料在不在、找 record_id、翻 wiki 找判例——**全部照放**,
# 因為那些用途不會把 entry id 貼進票裡當證據。
#
# ── 真跡(本閘的規格,Arcrun#167 comment 4865 原文照貼)───────────────
# $ kbdb_search(q="小果 AFTEE 詐貸") → count 5
# { "id": "e_b77f23a1-b467-4aa4-883c-58a051ab8e66", … }
# "metadata_json": "{\"source\":\"kb://小果被AFTEE詐貸.pdf#8\", … }"
# ⇒ 那則退回裡**同時**有 entry id 和 `kb://` 位址,兩類指紋都命中,
# 而整個 session 沒有任何一次 graphwiki 內容的呼叫。
# ⇒ 半小時後 comment 4877:「上一則退回是錯的;真正的缺口是那支工具 MCP 沒有」。
# **那半小時裡,一條線被停掉、一張做完的票被退回、一份工單被重寫。**
#
# ── 誠實邊界(不補,補了就變成文字層)────────────────────────────────
# 1. **改寫過的證據抓不到**——「我看到出處還是 `../` 開頭」這種轉述沒有指紋。
# 但房規本來就要求貼原始輸出(規則四之一、派工鐵律之三),
# 而貼了原始輸出就會被抓到。**不貼的那條路由別的閘管,不歸這支。**
# 2. **只認 KBDB 這一族**。別的「錯的驗證路徑」(拿 portal 畫面當 MCP 的證據——
# 08-27 同一天的另一半)這支看不到,因為那不經過工具呼叫。
# 3. 想不出乾淨判準的地方就留白,不硬做。
# `.claude/branch-holds.md` 檔頭那句:**永遠在響的警報,等於訓練人忽略這個警報。**
set -uo pipefail
INPUT=$(cat)
SID=$(printf '%s' "$INPUT" | python3 -c "
import sys, json
try: print(json.load(sys.stdin).get('session_id','') or 'nosid')
except Exception: print('nosid')
" 2>/dev/null || echo nosid)
SEARCH="/tmp/.kbdb-evidence-search-$SID"
# 這個 session 從來沒用搜尋拿過任何東西 ⇒ 這支沒有立場說話,最便宜的出口放在最前面
[ -s "$SEARCH" ] || exit 0
WARNED="/tmp/.search-is-not-proof-warned-$SID"
[ -f "$WARNED" ] && exit 0 # 同一 session 只擋一次,不鬼打牆
# 🔴 env 要 export,不能掛在 `printf` 前面——那樣只餵到 printfpython3 收不到
export SEARCH_FILE="$SEARCH"
export WALKED_FILE="/tmp/.kbdb-evidence-walked-$SID"
HIT=$(printf '%s' "$INPUT" | python3 -c '
import json, os, re, sys
try:
d = json.load(sys.stdin)
except Exception:
raise SystemExit # 讀不到 payload 就不擋(那不是使用者的錯)
tool = d.get("tool_name") or ""
ti = d.get("tool_input") or {}
if not isinstance(ti, dict):
raise SystemExit
# ── ① 這是不是一個「往外送」的動作?──────────────────────────────
# 往外送 = 這份文字會變成別人的前提:貼進票、關票、記裁決、或發包給另一條線。
# 只在自己腦袋裡看搜尋結果,永遠不會走到這裡。
body, kind = "", ""
if tool in ("Agent", "Task"):
body = ti.get("prompt") or ""
kind = "派工單"
elif tool == "Bash":
cmd = ti.get("command") or ""
if not cmd:
raise SystemExit
outward = False
if re.search(r"\bticket\s+(say|close|decide|new|subtask|handback)\b", cmd):
outward = True
if re.search(r"issues/\d+(/comments)?\b", cmd) and re.search(r"\b(POST|PATCH)\b", cmd):
outward = True
if not outward:
raise SystemExit
body, kind = cmd, "票上的留言"
# 內文常常放在 -F <檔> 裡,指令本身看不到 ⇒ 跟 comment-carries-task-guard 同一招
m = re.search(r"[\s](?:-F|--file)[\s]+(\S+)", cmd)
if m and os.path.isfile(m.group(1)):
try:
body += "\n" + open(m.group(1), encoding="utf-8", errors="replace").read()
except Exception:
pass
else:
raise SystemExit
if not body:
raise SystemExit
# 逃生口(留痕:它會留在指令歷史/派工單上)
if "search-is-evidence:" in body:
raise SystemExit
def load(p):
try:
with open(p, encoding="utf-8") as f:
return {ln.strip() for ln in f if ln.strip()}
except Exception:
return set()
searched = load(os.environ["SEARCH_FILE"])
walked = load(os.environ["WALKED_FILE"])
# ── ② 只在搜尋裡出現過、真路徑沒走過的那些值 ─────────────────────
# 走過真路徑的(graph / wiki 內容 / query)從候選裡扣掉——
# **搜完再去走一次真路徑,本來就是對的做法,不該被罰。**
only_search = searched - walked
if not only_search:
raise SystemExit
# ── ③ 這些值有沒有真的被貼進要送出去的那份東西裡 ──────────────────
hits = sorted(v for v in only_search if v in body)
if not hits:
raise SystemExit
print(kind + "\t" + "\t".join(hits[:3]))
' 2>/dev/null) || exit 0
[ -n "${HIT:-}" ] || exit 0
KIND=$(printf '%s' "$HIT" | cut -f1)
VALS=$(printf '%s' "$HIT" | cut -f2- | tr '\t' '\n' | sed 's/^/ /')
date +%s > "$WARNED"
cat >&2 <<EOF
🧭 你正要把 **kbdb_search 掉出來的原始值**,當成證據送進${KIND}裡。
被貼過去的值(這個 session 只在 kbdb_search 的回應裡出現過):
$VALS
而這個 session **沒有用產品檢索路徑拿過這幾筆**——
graph\`kbdb_graph_neighbors\`)/wiki 內容(\`kbdb_get_record\`)/\`kbdb_query\` 一次都沒有。
【D81leo 2026-08-13 親自裁定】
「你會用 **graph 找到該找的東西**,用 **LLM wiki 去拉到對的內容**⋯⋯
**向量的目的是用來協助把 entities normalize**。」
⇒ 主路徑=graph + wiki 內容。**搜尋是輔助,不是產品檢索路徑。**
🔴 這不是說 \`kbdb_search\` 不能用——它是合法的基本盤工具,查 wiki 本來就該用它。
不能做的是**拿它的輸出,去斷定產品檢索那條路是好是壞**。
【它花掉過多少(inkstone/Arcrun#1672026-08-27)】
12:34 comment 4865 用 kbdb_search 的輸出判定「兩個缺陷都還在」→ 退回一張
**已經做完、有測試、東西在 main** 的票,並重派一條線
13:08 comment 4877 總管自己更正:「上一則退回是錯的;
真正的缺口是**那支工具 MCP 根本沒有**」
⇒ 真相是那次修的是另一支工具的欄位,\`kbdb_search\` 本來就不會有它。
**在用一條錯的路,反覆確認一件錯的事**——而真正的缺口一直沒被看到。
【送出前先回答這一題】
**你要下結論的那條路,和你剛才實際走的那條路,是同一條嗎?**
(08-27 那則退回自己就寫著:「我當時驗的是 portal 的來源顯示,
但這張票的驗收條件是 MCP client 問『原文在哪』。**兩條不同的路。**」)
【出路,挑一條】
① 走一次真的那條路再下結論 —— \`kbdb_graph_neighbors\` / \`kbdb_get_record\` / \`kbdb_query\`
走過之後同一筆資料就不再命中本閘(走過的會從候選裡扣掉)。
② **那條路的工具根本不存在** ⇒ **那個才是你要回報的東西**(4877 就是這樣收的)。
照實寫「產品檢索路徑上少一支工具」,不要改用搜尋去補位再下判斷。
③ 真的只是引用一筆資料、不是在斷定檢索好壞
⇒ 加 \`search-is-evidence: <一句理由>\` 重送(留痕)。
同一 session 只擋這一次。
EOF
exit 2