fix(restart-gate): 閘門改問行程還在不在,不問代號見過沒有

判準原本是「這個工作階段代號我沒見過=行程是新起的」。那個等式不
成立:還沒重啟的工作階段自己生出來的子行程,拿到的也是沒見過的代
號,於是替人把閘門放下了,而人一次都沒重啟。

這台機器上真的發生過。部署掛上的閘門兩分鐘後就不見了,那段時間有
三個子工作階段冒出來,收尾那句「請重新啟動」於是只剩人自己記得。
排程那條路碰巧沒踩到,因為 cron 條目帶著 JSC_CLI=cron,清的是別
一份——巧合擋下來的,不是判準擋下來的。

反方向也會答錯:續接原代號的 resume,行程確實換過了,舊寫法卻連
問都不會問。

所以 require 一併記下掛上閘門時的工作階段代號與那一支 CLI 的行程
代號,清除只認行程存活:還活著就不清,走了就清。追不到行程代號時
退回結束記號,要求代號換了而且舊階段寫出過 .end。核對命令名不只
看行程還在,因為行程代號會被回收。

判定整段搬到 restart-gate.sh,session-timer.sh 只負責問。冒煙那
一組原本把舊語意寫成斷言,改成逐條驗三條清除路徑,並加一條驗
require 真的把欄位寫下來——少了那個欄位會無聲退回相容路徑,而每
一條行為斷言照樣全綠。
This commit is contained in:
2026-09-07 14:25:08 +08:00
parent e0de77230f
commit 8150ca8722
6 changed files with 179 additions and 33 deletions
+44 -4
View File
@@ -1416,7 +1416,7 @@ if [ "$action" = smoke ]; then
SMOKE_EXPECT_HOOK=18
SMOKE_EXPECT_MODEL=12
SMOKE_EXPECT_WP=6
SMOKE_EXPECT_RS=16
SMOKE_EXPECT_RS=21
SMOKE_EXPECT_WG=21
SMOKE_EXPECT_VG=13
SMOKE_EXPECT_SN=10
@@ -1720,6 +1720,19 @@ if [ "$action" = smoke ]; then
"$(printf '%s' "$_out" | tr '\n' ' ' | cut -c1-200)" >> "$smoke_out"
fi
}
# 狀態檔裡有沒有記到那個欄位也要比。清除的判準整個掛在 session 與 pid 兩個欄位上:require
# 少寫了它們,清除那一邊會退回「認不出來就清」的相容路徑,也就是改版前那個會被子行程清掉的
# 行為——而每一條行為斷言照樣全綠,因為那條相容路徑本來就該清。
smoke_rs_key() { # $1=情境 $2=狀態檔 $3=鍵名
smoke_n_rs=$((smoke_n_rs + 1))
if [ -n "$(sed -n "s/^$3=//p" "$2" 2>/dev/null | head -n1)" ]; then
printf '[jsc] restart-gate.sh(%s):%s 有值,與預期相同。\n' "$1" "$3" >> "$smoke_out"
else
smoke_fails=$((smoke_fails + 1))
printf '[jsc] restart-gate.sh(%s):%s 沒有值,清除判定會退回相容路徑而被子行程清掉:%s\n' \
"$1" "$3" "$2" >> "$smoke_out"
fi
}
# 狀態檔在不在也要比:一支 CLI 一份的重點就在「該留的留、該刪的刪」,只看結束碼看不出來。
smoke_rs_file() { # $1=情境 $2=狀態檔 $3=exist 或 absent
smoke_n_rs=$((smoke_n_rs + 1))
@@ -1742,6 +1755,7 @@ if [ "$action" = smoke ]; then
JSC_HOME="$rs_home" JSC_CLI="$cli" \
sh "$HOOKS/restart-gate.sh" require update hooks cli </dev/null 2>/dev/null
smoke_rs_file "require 寫出當前 CLI 那一份" "$rs_dir/$cli" exist
smoke_rs_key "require 記下掛上閘門時的工作階段" "$rs_dir/$cli" session
smoke_rs_case "當前 CLI 那份存在,技能 jsc-sdlc:implement" jsc-sdlc:implement deny
smoke_rs_case "當前 CLI 那份存在,豁免技能 jsc-cli:deploy" jsc-cli:deploy 0
smoke_rs_case "當前 CLI 那份存在,豁免技能 jsc-gitea:wiki" jsc-gitea:wiki 0
@@ -1749,13 +1763,39 @@ if [ "$action" = smoke ]; then
smoke_rs_case "當前 CLI 那份存在,豁免技能 jsc-meta:skill-check" jsc-meta:skill-check 0
smoke_rs_case "逃生門 JSC_RESTART_GATE=off" jsc-sdlc:implement 0 off
smoke_rs_case "取不到技能名" "" 0
# 清除機制:session-timer.sh 判定為新工作階段時會呼叫 restart-gate.sh clear。
# 這裡走的就是那條路徑(暫時 $JSC_HOME 底下沒有起始檔,等同行程新起的第一次)。
# 清除機制:session-timer.sh 在工作階段開始時呼叫 restart-gate.sh clear,清不清由那一邊
# 看行程存活決定。三條路徑各驗一次,理由見 hooks/restart-gate.sh 的「清除時機」。
#
# 一、還沒重啟的工作階段生出來的子行程:工作階段代號沒見過,但 require 記到的那個行程還
# 活著(就是現在跑冒煙的這一個)。閘門必須留著。這一條是實測抓到的洞:改版前它會被清掉,
# 於是部署收尾那句「請重新啟動」沒人再說得出口,而人一次都沒重啟。
JSC_HOME="$rs_home" JSC_CLI="$cli" JSC_SESSION_ID=smoke-child \
sh "$HOOKS/session-timer.sh" start </dev/null 2>/dev/null
smoke_rs_file "還沒重啟,子行程清不掉自己那一份" "$rs_dir/$cli" exist
smoke_rs_case "子行程清不掉,照樣擋下" jsc-sdlc:implement deny
# 二、記到的行程真的走了。拿一個剛結束並回收過的行程代號來寫,比寫死一個「應該不存在」的
# 號碼可靠:那種號碼哪天被別的行程佔走,這條斷言就會反過來變成偽陽性。
( exit 0 ) & rs_dead=$!
wait "$rs_dead" 2>/dev/null || true
printf 'at=%s\nmode=update\ndomains=hooks cli\ncli=%s\nsession=smoke-gone\npid=%s\n' \
"$(now_iso)" "$cli" "$rs_dead" > "$rs_dir/$cli" 2>/dev/null
JSC_HOME="$rs_home" JSC_CLI="$cli" JSC_SESSION_ID=smoke-restart \
sh "$HOOKS/session-timer.sh" start </dev/null 2>/dev/null
smoke_rs_file "新工作階段開始後清掉自己那一份" "$rs_dir/$cli" absent
smoke_rs_file "記到的行程走了就清掉自己那一份" "$rs_dir/$cli" absent
smoke_rs_file "清除不動別支 CLI 那一份" "$rs_dir/$rs_other" exist
smoke_rs_case "清除後放行" jsc-sdlc:implement 0
# 三、追不到行程代號時退回結束記號(CLI 的命令名對不上代號的那幾支走這條)。要求「代號換了」
# 而且「舊的那個工作階段寫出過結束記號」兩件事同時成立,所以先驗少了結束記號會留著。
mkdir -p "$rs_home/sessions" 2>/dev/null || true
printf 'at=%s\nmode=update\ndomains=hooks cli\ncli=%s\nsession=smoke-noend\npid=\n' \
"$(now_iso)" "$cli" > "$rs_dir/$cli" 2>/dev/null
JSC_HOME="$rs_home" JSC_CLI="$cli" JSC_SESSION_ID=smoke-noend-child \
sh "$HOOKS/session-timer.sh" start </dev/null 2>/dev/null
smoke_rs_file "追不到行程代號又沒有結束記號,留著" "$rs_dir/$cli" exist
now_epoch > "$rs_home/sessions/smoke-noend.end" 2>/dev/null
JSC_HOME="$rs_home" JSC_CLI="$cli" JSC_SESSION_ID=smoke-noend-next \
sh "$HOOKS/session-timer.sh" start </dev/null 2>/dev/null
smoke_rs_file "追不到行程代號但有結束記號,清掉" "$rs_dir/$cli" absent
# 舊格式的單一狀態檔(過渡相容):沒有 per-CLI 資訊,所以一律擋,clear 一併刪掉。
printf 'at=%s\nmode=update\ndomains=hooks\ncli=%s\n' "$(now_iso)" "$rs_other" \
> "$rs_home/restart-required" 2>/dev/null