7de1ad6be6
擋的是**證據的出處**,不是措辭。
判準(兩個條件同時成立才響):
① 要送出去的那份東西(票上的留言/派工單)裡,貼了一個值
——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>
199 lines
10 KiB
Bash
Executable File
199 lines
10 KiB
Bash
Executable File
#!/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 沒有任何一次 graph/wiki 內容的呼叫。
|
||
# ⇒ 半小時後 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` 前面——那樣只餵到 printf,python3 收不到
|
||
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\` 一次都沒有。
|
||
|
||
【D81|leo 2026-08-13 親自裁定】
|
||
「你會用 **graph 找到該找的東西**,用 **LLM wiki 去拉到對的內容**⋯⋯
|
||
**向量的目的是用來協助把 entities normalize**。」
|
||
⇒ 主路徑=graph + wiki 內容。**搜尋是輔助,不是產品檢索路徑。**
|
||
🔴 這不是說 \`kbdb_search\` 不能用——它是合法的基本盤工具,查 wiki 本來就該用它。
|
||
不能做的是**拿它的輸出,去斷定產品檢索那條路是好是壞**。
|
||
|
||
【它花掉過多少(inkstone/Arcrun#167,2026-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
|