雲端要拿得到票、主線與通知:白名單住 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
This commit is contained in:
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user