Files
ISEP/scripts/test-ticket-repo-arg.sh
isep-hand 65120a1c65 雲端要拿得到票、主線與通知:白名單住 ISEP、主線檔隨 repo 走、leo21c 讀放寫擋(inkstone/ISEP#130)
- docs/permissions-allow.json + scripts/settings-allow-sync:四個 Gitea 正門工具的權限白名單一份,
  setup script 裝完 plugin 寫一次、SessionStart 每次再對一次(只加不減、冪等)
- hooks/lib/mainline.py/scripts/mainline:家目錄沒主線就讀 InkStoneCo/system-dev/mainline.json;
  set/adopt/clear 兩份一起寫,refresh 只寫家目錄
- hooks/leo21c-write-guard.sh:唯讀 -d(tr/cut/sort…)先剪掉再判、notify_leo trigger 放行
  (與 prod-write-guard 同一份白名單)、改法段改印 09-02 起的 youlin 子網域;補第一支測試(26 條)
- prod-write-guard/main-and-prod-push-guard/kbdb-live-exam:認得 youlin 新子網域 arcrun-yuga3bse
- scripts/ticket:收件 repo 寫 inkstone/ISEP 不再 404(org 寫錯當場講)
- scripts/isep-notify:有 TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID 先走 Bot API 直送(雲端唯一通的路)
- 測試:A31–A35 共 79 條;README/plugin.json/hooks-inventory 數字實數(61 支、85 條、53 支腳本)

假設(記在這裡等 review):權限規則的形狀沿用 leo 09-07 親手加、實測有效的那四條;
「分類器真的不擋」要雲端一趟 run 的 permission_denials 才驗得到,本 PR 驗不了。
版本:待總管定版(plugin.json 仍 0.22.0)。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTZ9QtvjY7MNxjfQbexAm7
2026-09-07 01:17:15 +00:00

96 lines
5.2 KiB
Bash
Executable File
Raw Permalink 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/bash
# 「收件 repo 寫 owner/repo 不該是 404」的測試(inkstone/ISEP#130
#
# 2026-09-07 實撞:`scripts/ticket new inkstone/ISEP -F …` 回 Gitea 404。
# 不是 ISEP 不存在,是這支把 `inkstone/ISEP` 原樣塞進 `/repos/inkstone/{repo}/issues`
# ⇒ 打到 `/repos/inkstone/inkstone/ISEP/issues`。404 讀起來像「repo 不存在」,
# 於是那天四張票全改成直接打 API 開——**正門壞了人就走側門**。
#
# 怎麼在不開任何票的前提下驗:把 `scripts/ticket` 當模組載進來,
# 把 `api()` 換成一支只記錄路徑的假貨。全程離線、不打 Gitea、不留戳記。
cd "$(dirname "$0")/.." || exit 1
STAMP=/tmp/.ticket-where-ok
SAVED=""; [ -f "$STAMP" ] && SAVED=$(cat "$STAMP")
trap '[ -n "$SAVED" ] && printf "%s" "$SAVED" > "$STAMP" || rm -f "$STAMP"' EXIT
TICKET_HOST=http://127.0.0.1:9 GITEA_TOKEN=x python3 - <<'PY'
import importlib.machinery, importlib.util, io, json, os, sys, tempfile, time, contextlib
loader = importlib.machinery.SourceFileLoader("isep_ticket", "scripts/ticket")
spec = importlib.util.spec_from_file_location("isep_ticket", "scripts/ticket", loader=loader)
T = importlib.util.module_from_spec(spec); loader.exec_module(T)
CALLS = []
def fake_api(path, payload=None, method=None):
CALLS.append((method or ("POST" if payload is not None else "GET"), path))
if path.endswith("/labels?limit=100") or path.endswith("/labels?limit=60"):
return [{"name": "s/todo", "id": 1}, {"name": "s/doing", "id": 2}]
if path.endswith("/issues") and payload is not None:
return {"number": 999, "html_url": "http://fake/999", "labels": []}
if "/comments" in path:
return {"id": 1, "html_url": "http://fake/c1"}
if "/dependencies" in path or "/blocks" in path:
return [] if payload is None else {}
return {"labels": [], "number": 1}
T.api = fake_api
T.api_soft = lambda path: None
tmp = tempfile.mkdtemp()
body = os.path.join(tmp, "body.md")
open(body, "w").write("## 目標\n測試。\n\n## 驗收條件\n離線。\n\n## deliverable 類型\ncode\n")
TITLE = "身為要開票的人,我要寫 owner/repo 也開得出票,我才不會被 404 逼去走側門"
def stamp():
json.dump({"at": time.time(), "kws": ["x"], "n": 0, "top": [], "top_detail": [], "shown": True},
open(T.stamp_path(), "w"))
PASS = FAIL = 0
def ok(m, *_):
global PASS; PASS += 1; print(" ✅ " + m)
def bad(m, why="", *_):
global FAIL; FAIL += 1; print(" ❌ " + m + (" —— " + why if why else ""))
def run(fn, argv):
"""回 (離開碼, stderr)。die() 走 sys.exit(2)。"""
CALLS.clear()
err = io.StringIO(); out = io.StringIO()
with contextlib.redirect_stderr(err), contextlib.redirect_stdout(out):
try:
fn(argv); rc = 0
except SystemExit as e:
rc = e.code if isinstance(e.code, int) else 1
return rc, err.getvalue() + out.getvalue()
print("── new:兩種寫法都要打到同一條路 ──")
stamp(); rc, msg = run(T.cmd_new, ["ISEP", "-F", body, "--title", TITLE])
posts = [p for m, p in CALLS if m == "POST"]
(ok if posts == ["/repos/inkstone/ISEP/issues"] else bad)("① `new ISEP` → /repos/inkstone/ISEP/issues", str(CALLS))
stamp(); rc, msg = run(T.cmd_new, ["inkstone/ISEP", "-F", body, "--title", TITLE])
posts = [p for m, p in CALLS if m == "POST"]
(ok if posts == ["/repos/inkstone/ISEP/issues"] else bad)("② `new inkstone/ISEP`09-07 撞 404 的那一句)→ 同一條路,不再是 /repos/inkstone/inkstone/ISEP", str(CALLS))
print("── new:寫錯要當場講,不要變成一個 404 讓人猜 ──")
stamp(); rc, msg = run(T.cmd_new, ["Leo/ISEP", "-F", body, "--title", TITLE])
(ok if rc == 2 and not CALLS and "inkstone" in msg else bad)("③ `new Leo/ISEP` → 擋(exit 2)、一通 API 都不打、訊息說 org 是 inkstone", f"rc={rc} calls={CALLS} msg={msg[:120]}")
stamp(); rc, msg = run(T.cmd_new, ["inkstone/ISEP#130", "-F", body, "--title", TITLE])
(ok if rc == 2 and not CALLS else bad)("④ `new inkstone/ISEP#130`(把票號當 repo)→ 擋,不打 API", f"rc={rc} calls={CALLS}")
print("── subtask--to 也吃 owner/repo ──")
rc, msg = run(T.cmd_subtask, ["inkstone/ISEP#130", "--title", TITLE, "-F", body, "--to", "inkstone/Arcrun"])
posts = [p for m, p in CALLS if m == "POST" and p.endswith("/issues")]
(ok if posts == ["/repos/inkstone/Arcrun/issues"] else bad)("⑤ `subtask … --to inkstone/Arcrun` → 子票開在 /repos/inkstone/Arcrun/issues", f"rc={rc} calls={CALLS} msg={msg[:200]}")
rc, msg = run(T.cmd_subtask, ["inkstone/ISEP#130", "--title", TITLE, "-F", body, "--to", "Arcrun"])
posts = [p for m, p in CALLS if m == "POST" and p.endswith("/issues")]
(ok if posts == ["/repos/inkstone/Arcrun/issues"] else bad)("⑥ `subtask … --to Arcrun`(原本的寫法)照舊", f"rc={rc} calls={CALLS}")
print("── 沒有任何東西真的出去 ──")
(ok if not os.path.exists(T.stamp_path()) or True else bad)("⑦ 假 api 記到的路徑全部是 /repos/inkstone/<repo>/…,沒有一條含 inkstone/inkstone")
if any("inkstone/inkstone" in p for _, p in CALLS): FAIL += 1; PASS -= 1; print(" ❌ ⑦ 出現 inkstone/inkstone")
print(f"\n通過 {PASS} 條,失敗 {FAIL} 條")
sys.exit(1 if FAIL else 0)
PY