#!/bin/bash # component-guard.sh — 零件/service-binding 建立保險(D27/D28,2026-07-06) # 背景:AI 一再自建 domain 零件(km_wiki_card_parse)、錯用 service binding(drainer 修 1042)。 # 靠 wiki 規則防不住(leo:「這規定說幾次都沒用」)→ 仿 D20 github-contact-guard 做硬閘: # 把「建零件 / 加 service binding」當武器,平時機械擋(exit 2),要人類跑 scripts/component-arm.sh # 顯式解保險(限時,且解時該過 docs/component-pr-review-standard.md)才放行。 # n8n 哲學(leo):現成零件 + 通用 code 零件 + cypher workflow 就夠了 → 預設根本不碰「建零件」那條路,就不會亂想。 # 誠實限制(同 github-guard mindset §7):AI 技術上可繞過;本 hook 價值=擋手滑 + 留審計軌跡 + 逼你回頭用積木, # 絕不聲稱「不可能繞過」。最後一段靠 D27/D28 鐵律與自律。 INPUT=$(cat) read_field() { printf '%s' "$INPUT" | python3 -c "import json,sys try: d=json.load(sys.stdin);ti=d.get('tool_input',{}) print(ti.get('$1','')) except: print('')" 2>/dev/null; } FP=$(read_field file_path) [ -z "$FP" ] && exit 0 BODY="$(read_field content)$(read_field new_string)" PROJ="${CLAUDE_PROJECT_DIR:-$(pwd)}" ARM="$PROJ/.component-armed" # 解保險窗口(限時,仿 github-arm):arm 檔內時間戳,30 分鐘內放行 if [ -f "$ARM" ]; then ARMED=$(tr -dc '0-9' < "$ARM" 2>/dev/null); NOW=$(date +%s) if [ -n "$ARMED" ] && [ $(( NOW - ARMED )) -lt 1800 ] 2>/dev/null; then exit 0; fi fi HIT="" if printf '%s' "$FP" | grep -qiE 'wrangler\.(toml|jsonc?)$' && printf '%s' "$BODY" | grep -qE '\[\[services\]\]|(^|[^_a-zA-Z])services[[:space:]]*='; then HIT="service binding([[services]])— D28 禁令:跨-worker 編排走 cypher binding,不走 service binding" elif printf '%s' "$FP" | grep -qE 'registry/components/[^/]+/(component\.contract\.ya?ml|main\.(go|ts|mjs))$'; then HIT="建/改命名零件(registry/components/…)— D27:一次性邏輯用通用 code 零件,別鑄 domain 零件" fi [ -z "$HIT" ] && exit 0 { echo "❌ BLOCKED by component-guard($HIT)" echo "" echo "預設就用「我給你的積木」:現有零件 + 通用 code 零件(inline JS) + cypher workflow(cypher binding 串多 worker/零件)。" echo "這些對絕大多數需求已足夠。碰到這道閘,先回頭問:現成積木真的拼不出來嗎?(多半可以)" echo "真的需要新零件 / service binding(罕見)→ 要能過 docs/component-pr-review-standard.md,且人類顯式跑 scripts/component-arm.sh 解保險。" } >&2 LOG="$PROJ/system-dev/docs/3-specs/autonomy-dispatch/component-guard-log.md" printf '| %s | 擋(%s) | `%s` |\n' "$(date '+%F %T')" "${HIT%%—*}" "$(printf '%s' "$FP" | head -c 120)" >> "$LOG" 2>/dev/null exit 2