退路留言要帶「為什麼發不出去」,不是只帶閘的判定(inkstone/ISEP#93)

2026-08-28 第一版漏了這一格,而它剛好就在實測時咬到:
閘判定 pass、Telegram 卻 404(實例上找不到 notify_leo 工作流),
退路留言貼上票之後**只看得到 pass**——真正的斷點一個字都沒進票。
⇒ 下一個人會去修閘,而斷點根本不在那裡。

把留言內文抽成 fallback_body()(獨立成一支就是為了測得到),
測試從「看原始碼有沒有那個字串」改成**真的產一份留言出來檢查**:
斷點原因、原文、身份欄三格都要在。36 條全綠。
This commit is contained in:
Claude Code
2026-08-28 01:06:52 +00:00
parent da9bd53cae
commit 156d339308
3 changed files with 55 additions and 19 deletions
+6 -4
View File
@@ -165,11 +165,11 @@ bash hooks/tests/prod-write-guard.test.sh hooks/prod-write-guard.sh
- 「卡點二」2 條任一紅 ⇒ 「談論它」又被當成「執行它」(同款第八次),
或是剝了內文之後連真的部署都放行了
### A16 — 沒人會叫的事會自己叫:35
### A16 — 沒人會叫的事會自己叫:36
```
bash hooks/tests/overdue-nag.test.sh
```
**該看到**`通過 35 條,失敗 0 條`。**全程離線**——資料走 `--fixture`、時鐘走 `--now`
**該看到**`通過 36 條,失敗 0 條`。**全程離線**——資料走 `--fixture`、時鐘走 `--now`
網路用 `ISEP_NOTIFY_OFFLINE=1` 關掉。**不打 Gitea、不打實例、不留任何測試票。**
**它在守什麼**`inkstone/ISEP#93`2026-08-27 實查):一張票掛著等 leo 三天了、
@@ -182,7 +182,9 @@ bash hooks/tests/overdue-nag.test.sh
- ⑪ 紅 ⇒ 撈不到資料時把「我沒查到」講成「沒有事情逾期」——那是最貴的一種假情報
- **C 群任何一條紅 ⇒ 誤報**,這比漏報嚴重:每次開場都被念一串不相干的東西,
人就學會跳過它,那時真的有事也叫不動他
- D 群紅 ⇒ **發不出去卻靜默**。「送出成功」跟「送到了」是兩件事
- D 群紅 ⇒ **發不出去卻靜默**。「送出成功」跟「送到了」是兩件事
㉒b 特別看:退路留言若只帶閘的判定、不帶**為什麼送不出去**,
下一個人會去修閘——而斷點可能根本不在那裡(2026-08-28 第一版真的漏了這一格)
- ㉖㉗㉘㉙ 紅 ⇒ 這個 session 的閘是哪一版變回「假設」。㉘ 特別重要:
**閘說會擋就不准從 python 這條路繞過去**——繞得過的閘等於不存在
- ㉛ 紅 ⇒ 探針把 `/tmp/.prod-write-ok` 燒掉了(那是總管單次用完即丟的授權)
@@ -421,7 +423,7 @@ B2(信標那行)/B3(setup 輸出)/B4(閘的訊息)三個畫面
| **A15 pr-verdict --dry-run** | 總管 | ✅(2026-08-28 |
| **A14 回覆自己說出拖了多久** | 總管 | ✅ 20/202026-08-28inkstone/ISEP#63 |
| **A15 發通知不等於部署** | 總管 | ✅ 37/372026-08-28inkstone/ISEP#63 |
| **A16 沒人會叫的事會自己叫** | 總管 | ✅ 35/352026-08-28inkstone/ISEP#93 |
| **A16 沒人會叫的事會自己叫** | 總管 | ✅ 36/362026-08-28inkstone/ISEP#93 |
| **A16b Telegram 真的送達** | **leo** | ⛔ **驗不了——通道本身斷了**(見下) |
| **A17 壓 wiki 不准弄丟東西** | 總管 | ✅ 25/25+真跡實壓(2026-08-28inkstone/ISEP#89 |
| A5 搜尋跨 repo | 總管 | ✅ |
+21
View File
@@ -19,6 +19,7 @@ set -u
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
NAG="$ROOT/scripts/isep-nag"
export NOTIFY_PATH="$ROOT/scripts/isep-notify"
NOTIFY="$ROOT/scripts/isep-notify"
HOOK="$ROOT/hooks/overdue-nag-guard.sh"
@@ -128,6 +129,26 @@ has "$OUT" "測試訊息 ABC" && ok "㉑ 原文照抄在輸出裡(看到的人
|| no "㉑ 原文照抄" "實得:$OUT"
has "$OUT" "leo 的手機上沒有出現這則" && ok "㉒ 明著說 leo 沒收到(不是「送出成功」)" \
|| no "㉒ 明著說沒收到" "實得:$OUT"
# 🔴 「為什麼發不出去」要跟著訊息一起留下來。2026-08-28 第一版漏了這一格:
# 閘明明 pass,退路留言裡卻只看得到 pass,真正的斷點(實例上沒有那支工作流)
# 一個字都沒進票 ⇒ 下一個人會去修閘,而斷點根本不在那裡。
FB=$(python3 - <<'PYX'
import importlib.machinery, importlib.util, os
ld = importlib.machinery.SourceFileLoader("n", os.environ["NOTIFY_PATH"])
sp = importlib.util.spec_from_loader("n", ld); m = importlib.util.module_from_spec(sp)
ld.exec_module(m)
fact = {"verdict": "pass", "version": "0.11.0", "guard": "/x/prod-write-guard.sh",
"confidence": "high", "source": "測試", "reason": "閘放行"}
body = m.fallback_body("要發的原文 XYZ", fact,
"通道本身斷了:實例上找不到 notify_leo 工作流")
ok = ("找不到 notify_leo" in body # 真正的斷點有進票
and "要發的原文 XYZ" in body # 原文有進票
and body.splitlines()[0].startswith("【身份】")) # 身份欄在第一行
print("HAS_WHY" if ok else "NO_WHY:" + body[:120])
PYX
)
[ "$FB" = "HAS_WHY" ] && ok "㉒b 退路留言帶著「為什麼發不出去」,不是只帶閘的判定" \
|| no "㉒b 退路帶原因" "實得:$FB"
echo "── E 群:閘的版本是查得到的事實,不是假設 ────────────────────────"
G=$(python3 "$NOTIFY" --gate 2>&1)
+28 -15
View File
@@ -273,7 +273,30 @@ def gitea_token():
return None
def comment_on_ticket(ref, text, gate_fact):
def fallback_body(text, gate_fact, why=None):
"""退路留言的內文。**獨立成一支就是為了測得到**——這段的內容才是這條退路的價值。
🔴 **為什麼發不出去**一定要在裡面,不是只寫閘的判定。
2026-08-28 第一版漏了這一格:閘明明 pass,票上卻只看得到 pass,
而真正的斷點(實例上找不到 `notify_leo` 工作流)**一個字都沒進票**
⇒ 下一個人會去修閘,而斷點根本不在那裡。
"""
return "\n".join([
IDENTITY,
"",
"🔴 **這則本來要用 Telegram 發給 leo,但這個 session 發不出去**——所以貼在這裡。",
"",
"**送不出去的原因**%s" % (why or "(沒有記錄,這本身就是個 bug"),
"",
gate_report(gate_fact),
"",
"---",
"",
text,
])
def comment_on_ticket(ref, text, gate_fact, why=None):
"""把那則發不出去的訊息貼回票上。回 (ok, 說明)。
🔴 **同一則不重複貼**:內容雜湊存在狀態目錄,24 小時內一樣就跳過。
@@ -284,7 +307,7 @@ def comment_on_ticket(ref, text, gate_fact):
return False, "票的寫法是 owner/repo#N,你給的是:%s" % ref
owner, repo, num = m.group(1), m.group(2), m.group(3)
key = hashlib.sha256(("%s|%s" % (ref, text)).encode()).hexdigest()[:16]
key = hashlib.sha256(("%s|%s|%s" % (ref, text, why or "")).encode()).hexdigest()[:16]
seen = os.path.join(STATE_DIR, "commented-%s" % key)
if os.path.exists(seen) and time.time() - os.path.getmtime(seen) < 24 * 3600:
return True, "24 小時內已經貼過一模一樣的內容,跳過(不洗版)"
@@ -295,17 +318,7 @@ def comment_on_ticket(ref, text, gate_fact):
if not tok:
return False, "拿不到 Gitea tokenGITEA_TOKEN_CLAUDE_CODE 沒設,remote 也沒帶憑證)"
body = "\n".join([
IDENTITY,
"",
"🔴 **這則本來要用 Telegram 發給 leo,但這個 session 發不出去**——所以貼在這裡。",
"",
gate_report(gate_fact),
"",
"---",
"",
text,
])
body = fallback_body(text, gate_fact, why)
url = "%s/api/v1/repos/%s/%s/issues/%s/comments" % (GITEA_HOST, owner, repo, num)
req = urllib.request.Request(
url, data=json.dumps({"body": body}).encode(), method="POST",
@@ -340,7 +353,7 @@ def notify(text, fallback_ticket=None, dry_run=False):
if fact["verdict"] == "block":
out.append("⛔ 閘說會擋 ⇒ **本支不繞路**,直接走退路。")
sent, why = False, "沒有嘗試(閘會擋,繞過去等於拆了那道閘"
sent, why = False, "閘會擋,繞過去等於拆了那道閘 ⇒ 沒有嘗試送出"
else:
sent, why = send_telegram(text)
out.append(("✅ Telegram%s" if sent else "❌ Telegram%s") % why)
@@ -350,7 +363,7 @@ def notify(text, fallback_ticket=None, dry_run=False):
# ── 退路 ──────────────────────────────────────────────────────────
if fallback_ticket:
ok, detail = comment_on_ticket(fallback_ticket, text, fact)
ok, detail = comment_on_ticket(fallback_ticket, text, fact, why)
out.append(("✅ 退路(貼回票上):%s" if ok else "❌ 退路(貼回票上):%s") % detail)
else:
ok, detail = False, "沒有給 --fallback-ticket,沒有票可以貼"