Files
ISEP/hooks/kbdb-api-wall-guard.sh
Leo c2638668e3 ISEP 0.1.0:環境設定收成一個 plugin,本機與雲端共用一份
leo 2026-08-20:「同一個 plugin 你用,薄殼也用,保證兩邊同步」
              「我要你幫雲端做薄殼,永遠都有問題,你要做的就是這組設定
                你自己可以 dogfooding」

搬進來:41 支 hook(51 條註冊)/7 支 command/2 支 skill/23 支腳本。
不搬 .env、wiki、docs——那些是知識不是環境。

51 條 hook 路徑全部從 $CLAUDE_PROJECT_DIR/.claude/hooks/ 改成 ${CLAUDE_PLUGIN_ROOT}/hooks/,
零漏網。那正是薄殼一直壞掉的根:雲端 cwd 不是真身,寫死路徑就斷。

尚未驗證:Claude Code 能不能從私有 Gitea repo 裝 marketplace(要憑證)。
下一步就是在本機實際裝一次,通了才動雲端 bootstrap.sh。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 11:41:46 +08:00

226 lines
13 KiB
Bash
Executable File
Raw Permalink 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.
#!/bin/bash
# PreToolUse hook — KBDB 是 API-as-Wall:零 SQL、永不加表(L3 硬攔截)
#
# 【leo 2026-08-07 立】原話:
# 「確認執行記錄在 D1 是用 template 不是建表?」
# 「**KBDB 本來就只能用 API,如果他用 SQL 就是錯,現在居然還能建表,如何鎖住?**」
# 「它應該要被攔,**你也應該要被攔**,你要思考怎麼做不會再有錯誤指令。」
#
# 為什麼存在(2026-08-07 事故實錄,兩層都失守):
# ① **總管派工的指令本身違規**——寫成「新增 D1 migration 建執行紀錄表」。
# 病根:總管用**基礎設施的詞(D1)**思考,而鐵律掛在**領域的詞(KBDB)**上
# ⇒ `Skill(kbdb)` 的觸發詞對不上 ⇒ 整條鐵律沒被載入。
# 而且查了 Cloudflare 官方的 D1 額度(外部事實),**卻沒查自家那顆 D1 是誰**
# ——三個 bindingCREDENTIALS_DBANALYTICS_DBkbdb 的 DB)全指向同一顆 `arcrun-kbdb`。
# ② **subagent 照著做,而且更進一步直接 `ANALYTICS_DB.prepare()` 下原生 SQL**。
# 它沒建表(對),但繞過 API 牆讀寫 KBDB 的 D1,同樣違規。
# ⇒ 靠「我記得有這條鐵律」會失守,因為**想不到要載入它的時候,它就不存在**。
# 這個 hook 不看你記不記得,只看你寫了什麼。
#
# 攔三種違規:
# A. 對 KBDB 下 DDLCREATE/ALTER/DROP TABLE)——「永不加表」
# B. 對綁在 arcrun-kbdb 的 D1 binding 下原生 SQL.prepare/.exec/.batch)——「零 SQL」
# C. 新增一個 database_name = arcrun-kbdb 的 [[d1_databases]] binding——「別再多開一道門」
#
# 豁免:該行尾加 `kbdb-sql-ok`(留痕,commit 說明理由)。
# 既存違規(credentials.ts / auth-dispatcher.ts 等)不在本 hook 的射程內——
# 它只擋**這次寫進去的內容**,不回頭掃全 repo。既存的收拾另案處理。
#
# 誠實限制:regex 偵測有偽陰/偽陽,擋得住「照常識寫」的違規,擋不住刻意改名混淆。
# 價值是「讓正解成為阻力最小的路 + 留痕可審」,不是技術防偽。絕不聲稱不可能繞過。
set -euo pipefail
INPUT=$(cat)
# ── Bash 分支:擋「繞過檔案、直接用命令列對 KBDB 下 DDL/SQL」──────────
# 2026-08-07 leo 第 1 條指令「封鎖所有寫表的路」——只擋 Write/Edit 是不夠的,
# `wrangler d1 execute arcrun-kbdb --command "CREATE TABLE ..."` 一樣建得出表。
if command -v jq >/dev/null 2>&1; then
BASH_CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty')
else
BASH_CMD=""
fi
if [ -n "${BASH_CMD:-}" ]; then
# 🔴 2026-08-07 修正:第一版做「整條命令字串比對」,結果**第四次誤擋總管**——
# 誤擋的是 `git commit -m "...引用了那個命令字面..."`:那是在**描述**這件事,
# 不是在執行它。同一天已被自己的閘誤擋三次(Task 分支),這是第四次。
# ⇒ 只在**命令位置**才算數:字串出現在開頭,或緊接在 && ; | 換行之後。
# 引號內、heredoc 內、commit message 內的同樣字面一律不算。
# 判斷邏輯住在獨立檔(見該檔開頭:內嵌逃逸讓這道閘改一次壞一次)
BASH_VERDICT=$(printf '%s' "$BASH_CMD" \
| python3 "$(dirname "$0")/kbdb_cmd_check.py" 2>/dev/null || echo "OK")
if [ "$BASH_VERDICT" = "BAD" ]; then
cat >&2 <<'EOB'
🧱 kbdb-api-wall-guardBash):偵測到繞過 API 牆、直接對那顆資料庫執行 SQL 的命令。
🔴 D38leo 2026-08-07):**任何東西禁止用 SQL 語句存取資料,一律 API。**
KBDB API-as-Wall(鐵律全文在 cypher-executor/src/routes/kbdb-proxy.ts 檔頭,
leo 2026-06-14 立)。命令列直打繞過牆,**這條路 2026-08-07 起封鎖**。
✅ 正確做法
· 新資料類型 → seed 一列 template
(範本:kbdb/migrations/0003_library_map.sql
🔴 0004_execution_log_template.sql 是**反例**,不要照抄——它是 D91 影子表本人
· 讀寫資料 → 走 kbdb HTTP API(慣例見 cypher-executor/src/routes/kbdb-proxy.ts
🔴 **不加表不等於合規**:資料要寫成完整 record 展開,
**不准打包進 metadata_json**D91seed 一列 template 只是半套)
真有例外:命令裡加註 `kbdb-sql-ok`(留痕),並在 commit 說明理由。
EOB
exit 2
fi
exit 0
fi
if command -v jq >/dev/null 2>&1; then
TASK_PROMPT=$(printf '%s' "$INPUT" | jq -r '.tool_input.prompt // empty')
else
TASK_PROMPT=""
fi
if [ -n "${TASK_PROMPT:-}" ]; then
# 🔴 2026-08-07 第三次修正:Task 分支從「硬擋」改成「注入提示」。
#
# 為什麼改(實錄):這條分支上線後**連續三次誤擋總管正確的派工 prompt**——
# ① 「不准另開表」(禁止語境)② 「回到三張核心表」(驗法敘述)
# ③ 引用 leo 的警告原話(在講「不要建」,卻被當成「在叫人建」)
#
# ⇒ 判準(總管當天得出,寫下來免得又忘):
# **自然語言的閘容易兩頭失準,硬擋會擋住正確的工作。
# 機械可判的是「產物」(SQL/DDL/binding),不是「指令的措辭」。**
# ⇒ **指令層用提示(本分支,exit 0 注入 context),產物層用硬擋
# Write/Edit 分支與 Bash 分支,exit 2)。** 兩層合起來防線才完整。
#
# 提示仍有價值,因為**時機對**——它在派工送出的當下出現,
# 而不是像 subagent-wiki-guard 那樣「指令已經寫完才附在工具結果裡」。
if command -v jq >/dev/null 2>&1; then
jq -n --arg g "$(cat <<'EOG'
🧱 你正在派工。送出前對照資料層的兩條鐵律(2026-08-07 總管親手違反過):
【D38leo 2026-08-07)】**任何東西禁止用 SQL 語句存取資料,一律 API。**
唯一例外是資料層 worker 自己(`kbdb/src/`、`kbdb/migrations/`)——它就是那面牆。
KBDB 只有 entries / templates **兩張**核心表,**永遠不加新的**;
新資料類型 seed 一列 template。
🪦 **`entry_values` 已於 2026-08-15 廢除**migration `0007`leo:「我希望它不見,
這對我來說是恥辱柱」)。指標改用 entries 上的 `src_id``rel_id``dst_id` 欄位。
還沒更新的實例身上可能仍有那張表——**看到它=那台還沒跟上,不是它還合法**。
現成範本:`kbdb/migrations/0003_library_map.sql`
🔴 `0004_execution_log_template.sql` 是**反例**,不要照抄——它是 D91 影子表本人
🔴 **不加表不等於合規**:資料要寫成完整 record 展開,**不准打包進 metadata_json**
D91D93seed 一列 template 只是半套,而三道防線都只在數表,這個病從它們中間走過去)
【派工鐵律】**寫目的,不寫做法**(頂層 CLAUDE.md 規則三點五)
❌ 直接指定實作(例如叫人去做某種 schema 異動)
——2026-08-07 總管真的這樣寫過,一句話同時違反兩條鐵律,subagent 照做,兩層一起錯。
✅ 寫「要達成什麼 + 怎麼驗 + 紅線」,把實作留給那個 repo 的人格自己查。
理由:**指令越具體,收工方越不會質疑**——你不是那個 repo 的專家,
別替它決定實作,那等於把自己的無知固化成命令。
【leo 原話】「先前測試 Haiku 就可以成功寫 Arcrun 工作流,
**但你這個 Opus 卻下錯指令,問題在你這裏**。」
——差別不在模型能力,在 haiku 每次被強制注入「先看 skill 清單」,而你沒有。
📌 產物層另有硬閘(本 hook 的 Write/Edit 與 Bash 分支)會擋 DDL、牆外原生 SQL、
繞過 API 直打那顆資料庫的命令。這裡只提醒,不阻擋。
EOG
)" '{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":$g}}'
fi
exit 0
fi
if command -v jq >/dev/null 2>&1; then
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
CONTENT=$(printf '%s' "$INPUT" | jq -r '[.tool_input.content, .tool_input.new_string] | map(select(. != null)) | join("\n")')
else
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
CONTENT=$(printf '%s' "$INPUT")
fi
[ -z "${FILE_PATH:-}" ] && exit 0
[ -z "${CONTENT:-}" ] && exit 0
# 只管 arcrun / arcrun-rag 這兩個會碰到 KBDB 的地方(其他 repo 沒有 KBDB,別誤擋)
case "$FILE_PATH" in
*matrix/arcrun/*|*products/arcrun-rag/*|*kbdb*) ;;
*) exit 0 ;;
esac
# 去掉帶豁免標記的行後再驗
SCAN=$(printf '%s' "$CONTENT" | grep -v 'kbdb-sql-ok' || true)
[ -z "${SCAN:-}" ] && exit 0
VIOLATION=""
# ── A. DDL:永不加表 ────────────────────────────────────────────────
if printf '%s' "$SCAN" | grep -qiE '(CREATE|ALTER|DROP)[[:space:]]+TABLE'; then
VIOLATION="${VIOLATION}
── A. 偵測到 DDLCREATE/ALTER/DROP TABLE
KBDB 只有兩張核心表:**entries / templates****永遠不加新 table**。
`entry_values` 已於 2026-08-15 廢除,migration `0007`;指標改走 entries 上的
`src_id``rel_id``dst_id`。舊實例身上還看得到它=那台沒跟上,不是它還合法。)
(權威來源:kbdb/migrations/0001_base.sql。**注意別把概念名當表名**——
BlockTemplateSlot 是「三層概念」;entries 是萬用主表,靠 entry_type
區分 blocktemplateslotprojectworkflowrecipe_stat…;
2026-08-07 本 hook 初版把概念名寫成表名,會害人去找不存在的表。)
所有資料結構都用 Block + Template + Slot 三層概念實現,落在那三張表上。
✅ 正確做法:建一個 template,每筆資料是它的 record。
去讀既有的 template\`triplet\` 是現成範本),照它的形狀做,不要新增第二套。"
fi
# ── B. D38 零 SQL:牆外任何地方都不准下原生 SQL ──────────────────────
# 【leo 2026-08-07 立 D38】「**任何東西禁止用 SQL 語句存取資料,一律 API。**」
# 範圍不只 KBDB——唯一的例外是「資料層 worker 自己」(kbdb 就是那面牆,牆內當然用 SQL)。
#
# 🔴 2026-08-07 補漏:舊版只比對 `CREDENTIALS_DB.prepare(`**抓不到別名**——
# 而 `auth-dispatcher.ts:66,70` 正是 `const db = env.CREDENTIALS_DB;` 然後 `db.prepare(...)`
# ⇒ 舊規則對真實的違規現場完全無效。改成「牆外任何 .prepare/.exec/.batch 都擋」。
IS_WALL_INTERNAL=0
case "$FILE_PATH" in
*matrix/arcrun/kbdb/src/*|*matrix/arcrun/kbdb/migrations/*) IS_WALL_INTERNAL=1 ;;
esac
if [ "$IS_WALL_INTERNAL" -eq 0 ] \
&& printf '%s' "$SCAN" | grep -qE '\.[[:space:]]*(prepare|exec|batch)[[:space:]]*\('; then
VIOLATION="${VIOLATION}
── B. 偵測到原生 SQL.prepare / .exec / .batch),而這個檔在「牆外」
🔴 **D38leo 2026-08-07):任何東西禁止用 SQL 語句存取資料,一律走 API。**
唯一例外是資料層 worker 自己(\`kbdb/src/\`、\`kbdb/migrations/\`)——它就是那面牆。
✅ 正確做法:走 HTTP API。
先讀 \`cypher-executor/src/routes/kbdb-proxy.ts\` 看既有呼叫慣例
(認證怎麼帶、owner_id 怎麼注入),照著走,不要新增第二套。
📌 別名也算:\`const db = env.XXX_DB; db.prepare(...)\` 與直接寫一樣是違規
2026-08-07 舊版規則就是漏在這裡)。"
fi
# ── C. 別再多開一道門:新增指向 arcrun-kbdb 的 D1 binding ────────────
if printf '%s' "$SCAN" | grep -q 'd1_databases' && printf '%s' "$SCAN" | grep -q 'arcrun-kbdb'; then
VIOLATION="${VIOLATION}
── C. 偵測到新增一個指向 arcrun-kbdb 的 [[d1_databases]] binding
🔴 有 D1 binding 就能下任意 SQL——**這正是 2026-08-07 那次能建表的原因**。
現況已經有三個 binding 指向同一顆(CREDENTIALS_DBANALYTICS_DBkbdb 的 DB),
**不要再多開第四個**。
✅ 正確做法:走 kbdb HTTP API;真的需要獨立儲存 → 開一顆**不叫 arcrun-kbdb** 的 D1
並在 pending-changes 提案說明為什麼(那是架構決策,要負責人裁)。"
fi
[ -z "$VIOLATION" ] && exit 0
cat >&2 <<EOF
🧱 kbdb-api-wall-guard:這次寫入違反 KBDB 的兩條鐵律。
$VIOLATION
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【鐵律全文】KBDB API-as-Wall
· **零 SQL**:一律走 kbdb 的 HTTP API,不准直接碰它的 D1
· **永不加表**:只有 **entries / templates** 兩張;新結構用 template + record
`entry_values` 2026-08-15 廢除,見上)
【為什麼這條特別容易被繞過】(2026-08-07 實錄,總管與 subagent 兩層都失守)
想事情時用的是**基礎設施的詞**(D1/資料表/migrationschema),
而鐵律掛在**領域的詞**(KBDB)上 ⇒ 詞對不上,skill 就不會被載入。
⇒ **碰到「D1」時的第一個動作,是先問「這顆 D1 是誰的」**,
不是先想「要建什麼表」。三個 binding 全指向 arcrun-kbdb。
【真的有例外】該行尾加 \`kbdb-sql-ok\`(留痕),並在 commit 訊息說明理由。
EOF
exit 2