#!/bin/bash # check-no-instance-names.sh — 機械閘 #2:框架範本不得混入實例專名 # # 出處:《分離導入規格》第六節第 2 條、第七節第四題 Gherkin。 # Scenario: 框架混入實例名被 CI 擋 # Given 範本檔新增一行含 "arcrun" # When CI 執行 # Then 檢查 fail 並指出檔案與行號 # # 為什麼需要它(不是潔癖,是分層會自己塌): # L1 框架的判準是「換一家公司照樣成立」。一旦範本裡寫著某個實例的專案名, # 下一個裝這套 template 的人就會讀到一段對他不成立的規則——而他無從分辨 # 「這條是普世方法論」還是「這是別人家的家規」。分層一旦糊掉就回不去了。 # # ── 豁免機制(為什麼要有)───────────────────────────── # 本閘上線時,範本區已經有 22 行命中(8 個檔)。若不給豁免就硬上線,唯一的 # 通關方式會是「把 arcrun 改寫成『某工作流引擎』」——**資訊消失了,問題還在** # (假性清理)。所以:真正該搬走的東西標豁免、記在帳上,隨 policy pack 一起搬; # 純粹是註解舉例的,當場改寫成通用敘述。 # 豁免有兩級: # · 行級 `sdt-instance-name-ok` → 該行豁免。用在「檔案本身是框架的,只有這行欠著」。 # · 檔級 `sdt-instance-name-ok-file` → 整檔豁免(寫在檔首 30 行內,需附理由)。 # 用在「整支本來就是 L2 政策包產物、只是還沒搬走」——逐行標 13 次是雜訊不是紀律。 # # 誠實限制:這支只認「字面出現」。它擋不了「把實例假設寫成通用語氣」 # (例如寫「你的工作流引擎會在遠端執行」其實只有某一家成立)。 # 它是底線不是萬能——真正的分層判斷仍要人/CC 動腦。 # # 用法: # bash scripts/check-no-instance-names.sh # CI 模式,有未豁免命中 → exit 1 # bash scripts/check-no-instance-names.sh --baseline # 只列清單(含已豁免),永遠 exit 0 # set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$REPO_ROOT" NAMES_FILE="scripts/instance-names.txt" SCAN_DIR="template" EXEMPT_MARK="sdt-instance-name-ok" EXEMPT_FILE_MARK="sdt-instance-name-ok-file" EXEMPT_HEAD_LINES=30 # 檔級豁免標記必須寫在檔首這麼多行內(強迫它顯眼、不許藏在檔尾) MODE="ci" [ "${1:-}" = "--baseline" ] && MODE="baseline" if [ ! -f "$NAMES_FILE" ]; then echo "❌ 找不到黑名單設定檔:$NAMES_FILE" >&2 exit 1 fi if [ ! -d "$SCAN_DIR" ]; then echo "❌ 找不到範本區:$SCAN_DIR/" >&2 exit 1 fi # ── 讀黑名單 → 組成 grep 的 alternation pattern ─────────────── PATTERN="" while IFS= read -r line; do case "$line" in ''|'#'*) continue ;; esac term="$(printf '%s' "$line" | tr -d '[:space:]')" [ -z "$term" ] && continue if [ -z "$PATTERN" ]; then PATTERN="$term"; else PATTERN="$PATTERN|$term"; fi done < "$NAMES_FILE" if [ -z "$PATTERN" ]; then echo "⚠️ 黑名單是空的,本閘等同關閉($NAMES_FILE)" exit 0 fi # ── 掃描 ──────────────────────────────────────────── # policy pack 不受此限(L2 本來就是一家之言)。目前尚無 policy pack 目錄, # 先把慣例路徑寫在這裡,W3 建立時自動生效。 HITS="$(grep -rniE "$PATTERN" "$SCAN_DIR" 2>/dev/null \ | grep -v "^$SCAN_DIR/policy-packs/" || true)" VIOLATIONS=0 EXEMPTED=0 VIOL_LINES="" EXEMPT_LINES="" if [ -n "$HITS" ]; then while IFS= read -r hit; do [ -z "$hit" ] && continue hit_file="${hit%%:*}" # 檔級豁免:整支本來就是 L2 產物,檔首宣告過就整檔記帳 if [ -f "$hit_file" ] && head -n "$EXEMPT_HEAD_LINES" "$hit_file" 2>/dev/null | grep -q "$EXEMPT_FILE_MARK"; then EXEMPTED=$((EXEMPTED + 1)) EXEMPT_LINES="${EXEMPT_LINES}${hit}"$'\n' continue fi # 行級豁免:檔案是框架的,只有這行欠著 if printf '%s' "$hit" | grep -q "$EXEMPT_MARK"; then EXEMPTED=$((EXEMPTED + 1)) EXEMPT_LINES="${EXEMPT_LINES}${hit}"$'\n' else VIOLATIONS=$((VIOLATIONS + 1)) VIOL_LINES="${VIOL_LINES}${hit}"$'\n' fi done <<< "$HITS" fi # ── 輸出 ──────────────────────────────────────────── if [ "$MODE" = "baseline" ]; then echo "════════════════════════════════════════════════" echo "📊 實例專名基線報表($SCAN_DIR/)" echo "════════════════════════════════════════════════" echo "未豁免(違規):$VIOLATIONS 行" [ -n "$VIOL_LINES" ] && printf '%s' "$VIOL_LINES" | cut -c1-160 | sed 's/^/ ❌ /' echo "已豁免(記在帳上,待搬遷):$EXEMPTED 行" [ -n "$EXEMPT_LINES" ] && printf '%s' "$EXEMPT_LINES" | cut -c1-160 | sed 's/^/ ⏳ /' exit 0 fi if [ "$VIOLATIONS" -gt 0 ]; then echo "════════════════════════════════════════════════" >&2 echo "❌ 框架範本混入實例專名($VIOLATIONS 行)" >&2 echo "════════════════════════════════════════════════" >&2 printf '%s' "$VIOL_LINES" | cut -c1-200 | sed 's/^/ /' >&2 cat >&2 <