Files
ISEP/scripts/gitea-arm-to-github-armed.sh
T
Leo 5d938f5d3a 票上回碼解的保險寫回專案根,出貨線讀得到(inkstone/ISEP#156)
gitea-arm-to-github-armed.sh 原本用腳本自己的上一層決定 .github-armed 寫在哪;
plugin 發佈後那是 ~/.claude/plugins/cache/inkstone/isep/<ver>/,
於是保險寫進快取、印「已解」、ship.mjs 與各閘讀不到(arcrun-rag#202 c7598)。

改寫到 gitea_arm_proj_dir(${CLAUDE_PROJECT_DIR:-$(pwd)}),與
main-and-prod-push-guard/github-contact-guard/stage-before-prod-guard 讀的位置、
以及 check 自己讀 token/待核請求的位置一致。擋下時印的 request 路徑改絕對路徑。

測試:hooks/tests/gitea-arm-to-github-armed.test.sh 18/18;修之前 11 條紅。
TESTING.md 補 A42。版本待總管定。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:58:50 +08:00

76 lines
4.8 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.
#!/bin/sh
# gitea-arm-to-github-armed.sh — 把「leo 在 Gitea 票上的核准」轉成出貨線認得的 `.github-armed`
#
# 🔴 這支存在的理由(2026-08-13 實撞):
# arm 機制上線當天,總管把它接到了 `main-and-prod-push-guard.sh`(推 main 那道閘),
# **但出貨線 `ship.mjs` 的 preflight 只認舊的 `.github-armed`**
# ⇒ leo 在票上回了碼、`gitea-arm-check.sh` 回 ARMED**出貨照樣斷在第 1 站**。
# ⇒ 總管審 PR 時查了「機制本身安不安全」,**沒查「它有沒有接到真正擋路的地方」**。
# 同款形狀:做好了一半,而那一半不是會擋住人的那半。
#
# 🔴 這支**不是**繞過 D20,它是同一道閘的另一個入口:
# `.github-armed` 代表的權威來自「leo 親自做了一個機器做不到的動作」。
# 舊入口=他在終端機打字;新入口=他在 Gitea 票上回覆一組碼。
# **兩者的不可偽造性來源不同,但都不在機器手上**:
# · 終端機:機器碰不到那個鍵盤
# · Gitea:機器寫不出作者是 `Leo` 的留言(2026-08-13 身分分離完成,總管親手攻擊驗過)
#
# ⛔ **唯一的安全條件**:本檔**只在 `gitea-arm-check.sh` 成功時**才寫出保險。
# 那支是 fail-closed 的(沒回覆/過期/已消耗/作者不是 Leo/Gitea 不通 → 全部 exit 1),
# 而且核准者與頻道票**寫死在 `lib/gitea-arm-common.sh`**,環境變數改不動。
# ⇒ 拿掉那個條件,這支就變成「AI 自己批准自己發佈」——**那正是 D20 要擋的事**。
set -eu
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
# shellcheck source=lib/gitea-arm-common.sh
. "$SCRIPT_DIR/lib/gitea-arm-common.sh"
# ── 保險寫在哪:**讀的人在哪讀,就寫在哪**inkstone/ISEP#1562026-09-17 實撞)──────
#
# 🔴 舊版寫 `ROOT=$(dirname $0)/..`(腳本自己的上一層)。ISEP 還住在 InkStoneCo 裡時兩者剛好
# 同一個位置;**改成 plugin 發佈後,腳本住在 `~/.claude/plugins/cache/inkstone/isep/<ver>/`**
# ⇒ 保險被寫進 plugin 快取資料夾,印「✅ 保險已解」,而出貨線與每一道閘都讀不到
# ⇒ leo 回了碼、出貨照樣卡住,總管手動把檔案搬過去才通(inkstone/arcrun-rag#202 c7598)。
# **一句「已解」卻沒解,比單純失敗更糟——它讓 leo 以為自己的動作生效了。**
#
# ⇒ 改用 `gitea_arm_proj_dir`(=`${CLAUDE_PROJECT_DIR:-$(pwd)}`),理由是**跟讀的人對齊**:
# · main-and-prod-push-guard.shgithub-contact-guard.shstage-before-prod-guard.sh
# 全部讀 `${CLAUDE_PROJECT_DIR:-$(pwd)}/.github-armed`
# · ship.mjs 讀 `InkStoneCo/.github-armed`(總管 session 的 CLAUDE_PROJECT_DIR 就是它)
# · 本檔自己的核准來源(`gitea-arm-check.sh` 讀的 token 與待核請求)本來就住在這裡
# ⇒ 專案根猜錯時 check 會先因為讀不到 `.env`/待核請求而 fail-closed**不會寫到錯的地方**
# 不要再改回「腳本的位置」:腳本住哪是安裝方式決定的,保險住哪是讀的人決定的,兩者無關。
ARMED="$(gitea_arm_proj_dir)/.github-armed"
# ── 唯一的授權來源:leo 在 Gitea 票上的回覆 ────────────────────────────
# 不捕捉、不吞錯——check 失敗就讓它把原因印到 stderr 並中止本支。
# 🔴 `--consume` 是必要的(2026-08-16 起 check 預設唯讀):本檔會**寫出保險**,
# 那就是「放行」本身 ⇒ 這一次核准必須當場作廢,否則同一組碼能重複解保險。
OUT=$("$SCRIPT_DIR/gitea-arm-check.sh" --consume) || {
echo "" >&2
echo "⛔ 沒有取得 leo 的核准 ⇒ 不寫保險(fail-closed" >&2
# 印絕對路徑:相對的 `scripts/…` 會對貼上那個人的 cwd 解析,站在 InkStoneCo 會叫到舊複本(ISEP#112 同病)
echo " 先跑:$SCRIPT_DIR/gitea-arm-request.sh <票號> \"<要做什麼>\",把代碼給 leo 回在那張票上。" >&2
exit 1
}
# `.github-armed` 的格式(照 scripts/github-arm.sh:93):
# 第 1 行:到期 epoch/第 2 行:任務說明/第 3 行:armed_at=…
MISSION=$(printf '%s' "$OUT" | sed -n 's/^ARMED: //p' | head -1)
[ -n "$MISSION" ] || MISSION="gitea-arm 核准,未帶說明)"
NOW=$(date +%s)
EXPIRY=$((NOW + 1800)) # 30 分鐘,與 gitea-arm-request 的時效一致
printf '%s\n%s\n%s\n' \
"$EXPIRY" \
"$MISSION" \
"armed_at=$(date '+%Y-%m-%d %H:%M:%S')(來源:Gitea 票 leo 核准,非終端機)" \
> "$ARMED"
echo "✅ 保險已解(來源:leo 在 Gitea 票上的核准)"
echo " 寫在:$ARMED"
echo " 任務:$MISSION"
echo " 有效至:$(date -r "$EXPIRY" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$EXPIRY")"
echo " 提前上保險:rm $ARMED"