Compare commits

...

7 Commits

Author SHA1 Message Date
Leo e45faef8a2 test(D20): 補 #119 缺的 subagent 維度測試,並查出一個殘留同形狀破口(inkstone/ISEP#119)
背景:2026-09-01 一個 subagent 把分支推上 GitHub 沒經過解保險,同時總管這端擋得住,
看似「subagent 端失效」。查證後:
- 官方文件(https://code.claude.com/docs/en/hooks)明講 PreToolUse hook 對 subagent
  與主線是同一批,只是 input 多帶 agent_id/agent_type ⇒ 閘無從只擋一端。
- github-contact-guard.sh 從不讀那兩個欄位,所以「一端有效一端失效」不會出在身分判斷上。
- 當天的洞是同一族的 cwd 病(inkstone/ISEP#109/#125):閘拿 hook 自己的 cwd 解 origin。
  2026-09-07(49a7145)接上 lib/push_target_dir.py 後,**字面路徑**的形狀已修好,
  且已由 scripts/test-github-contact-guard.sh 的 A39 cwd 群覆蓋。

本輪交付(都驗過):
1. 在既有的 scripts/test-github-contact-guard.sh(唯一真相源,不另開平行測試)尾端加 7 條
   runsub:同一條指令帶 agent_id(subagent)與不帶(總管)各跑一次,判決必須相同。
   涵蓋推 GitHub(含 cd/-C 字面路徑原形狀)擋、推 Gitea/讀取放行。26→33,實測 33/33。
2. 回歸性驗過:把新測試跑舊閘(49a7145^)→ 09-01 的兩條 cd/-C 場景紅(漏擋),
   證明這組測試真的抓得住它要抓的病。
3. docs/TESTING.md A39 段更新條數、失敗判讀,並記下下面這個殘留破口。

🔴 查出並實測(含雲端實跑)一個殘留的同形狀破口,未修(見下):
   push 目標用**未展開 shell 變數**表示時(git -C "$VAR" push/cd "$VAR" && git push),
   hook 收到的是展開前字串,push_target_dir 對 $VAR 回「不確定」⇒ 退回 session cwd 解 remote。
   關鍵不對稱:main-and-prod-push-guard 遇「不確定」退回「要戳記」=擋(fail-safe);
   github-contact-guard 預設「證明不了 github 就放行」⇒ cwd 是 Gitea 時,真的推 GitHub 的
   -C "$VAR" 會漏擋。雲端實跑(本 session 即 CHILD_SESSION=1):git -C "$VAR/gh-repo" push
   未被 D20 擋,一路打到 git proxy 才被 403 擋(那是 proxy 不是 D20);本機沒有 proxy 後手。
   不修的理由:fail-closed 會誤攔「用變數推 Gitea」,屬「誤攔比漏擋嚴重」的取捨,
   應另開票由總管/leo 裁(附建議修法:push_target_dir 區分「無指令」與「指令不可解」,
   後者 github-contact 改擋並印『改用字面路徑』的出路——出路走得通、可測)。

同形狀家族盤點(實跑驗過,回應「還有哪些閘是這個形狀」):
- github-contact-guard.sh:字面路徑已修;殘留變數形狀破口(見上,另票)。本輪補 subagent 維度測試。
- main-and-prod-push-guard.sh:已修(push_target_dir),且遇不確定 fail-safe,cross-repo 測試 19/19。
- line-needs-own-worktree.sh:cwd 維度已修(checkout_target_dir),測試 110/110。
- not-my-branch-guard.sh:★仍是同形狀破口★——git -C "$PROJ" 讀 hook 自己的目錄;
  NOT_MY_BRANCH_OK=1 前綴 PreToolUse 收不到(逃生口結構性失效)。實測:subagent cd/-C
  進別的 repo 對 held 分支 commit → 放行(該擋)。屬 commit 閘、非本票 D20 範圍,
  mistakes.md 早標「另報」,建議另開票(需 commit-target 解析器+認得字面前綴)。

僅動測試腳本與文件,未動任何會被載入的檔(hooks/commands/skills/agents/plugin.json 皆未動)
⇒ 不改變任何閘的行為,plugin.json 未動,版本待總管定。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TGxitYq49FzYC7EFkbhzF5
2026-09-18 18:02:40 +00:00
Leo 8a3802c4b7 release: v0.30.10——雲端環境產物分檔,開機腳本不再混進分隔線
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 20:16:19 +08:00
Leo 09769a0dd1 雲端環境產物拆成兩個檔:變數檔只有 KEY=VALUE、開機腳本檔逐位元組=cloud-setup-script.sh(+README)
2026-09-18 實撞:兩段同檔、「════」分隔線被一起貼進 Setup script 欄 ⇒ 雲端開機 exit 127,
cloud-worker 每一趟在開機就死(cse_013LPaaD47m4arFzLjGvSZnN)。新增 V3(變數檔每行都是 KEY=VALUE)、
V4(開機腳本檔逐位元組相同);test-make-cloud-env.sh 15/15。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 20:16:19 +08:00
Leo 3bc2147e36 release: v0.30.9——雲端環境 v7(補回 Telegram 兩把與 ISEP_DRAIN)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 20:00:24 +08:00
Leo 6047dff71b 雲端環境 v7:收回現行雲端有、v1–v6 漏收的 TELEGRAM_CHAT_ID/TELEGRAM_BOT_TOKEN(MIRA_ALLOWED 點名放行,B8 其餘照擋)與固定值 ISEP_DRAIN=1
leo 09-18 貼出現行雲端環境才發現。已驗:chat id 與 leo 貼的相同;bot getMe=總管名片王(雲端工人發訊那支)。test-make-cloud-env.sh 13/13(B8x 反例照樣抓得到)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 20:00:24 +08:00
Leo 147fdb1482 release: v0.30.8——雲端環境變數清單有版本(v6),產物放桌面
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 19:44:47 +08:00
Leo ef11871ba4 雲端環境變數清單有版本了:docs/cloud-env-versions.tsv 記每版 key,產生器印版本與差異、改清單沒升版就拒絕,產物預設放桌面(v6:+ARCRUN_NS_UNCLE6/GITHUB_MIRROR_TOKEN/GITHUB_ACCOUNT_NAME)
leo 2026-09-18:「你這裡記錄每版的 credential 的 key,但 value 自動產生,我就貼上去,你要有版本,因為這件事做了很多次,但你都沒記錄版本」
v1–v5 由 git 歷史還原(f855d82、ca9ea99、7aef943、cb310e9、dd86fef)。test-make-cloud-env.sh 13/13(新增 V1 檔頭版本、V2 不一致拒絕)。
(inkstone/ISEP#158、inkstone/InkStoneCo#98 c7739)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 19:44:40 +08:00
6 changed files with 136 additions and 21 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "isep",
"description": "InkStone Environment Plugin —— leo 的 Claude Code 環境唯一真相源:62 支機械閘(88 條註冊,白話盤點見 docs/hooks-inventory.md)、7 支 slash command、2 支 skill、8 位有名字的工人(agents/,見 docs/governance/worker-roster.md)、65 支腳本,外加治理規範與標籤真相源。本機與雲端裝同一份,沒有子集。",
"version": "0.30.7",
"version": "0.30.10",
"keywords": [
"inkstone",
"guardrails",
+16 -3
View File
@@ -1017,12 +1017,13 @@ bash hooks/tests/isep-presence-beacon.test.sh # ㉗–㉝ 那七條
**真實跑(2026-09-07**:信標不帶 fixture 打真 Gitea(匿名)→ 安靜(v0.24.0 tagrelease 都在),
留下 `.isep-chain-ok-v0.24.0` 快取;`release-check` 在本分支 → `v0.24.0 ✅✅✅ ←`、三個裁定不補的版本標 ⏭、exit 0。
### A39 — D20 閘判「push 到哪」看的是指令實際會推的那個 repo26
### A39 — D20 閘判「push 到哪」看的是指令實際會推的那個 repo,且 subagent 與總管同判:33
```
bash scripts/test-github-contact-guard.sh
```
**該看到**`26/26 通過`。離線; 12 條自己開兩顆 git repo`shell` 的 origingithub.com、
`body` 的 origin 是 Gitea 的形狀),payload 帶 `cwd`,跟 hook 真的會收到的一樣
**該看到**`33/33 通過`。離線;中間 12 條自己開兩顆 git repo`shell` 的 origingithub.com、
`body` 的 origin 是 Gitea 的形狀),payload 帶 `cwd`,跟 hook 真的會收到的一樣
最後 7 條(`inkstone/ISEP#119`)把同一條指令帶 `agent_id`(subagent)與不帶(總管)各跑一次,兩者判決必須相同。
**它在守什麼**`inkstone/ISEP#109` → comment 66292026-09-07 實撞):雲端薄殼的 cwd 是 GitHub 那份,
總管站在那裡 `git -C <InkStoneCo> push origin x`InkStoneCo 的 originGitea),舊版拿 **hook 收到的 cwd**
@@ -1036,6 +1037,18 @@ bash scripts/test-github-contact-guard.sh
- 「該擋」5 條任一紅 ⇒ D20 有洞:`-C``cd` 指到 GitHub 那份的 push 沒被攔
- `(cd body && true); git push` 那條紅 ⇒ 子殼的 cd 外洩了(08-11 穿透的形狀)
- 前 14 條紅 ⇒ 舊有判準(heredoc/引號裡的散文/讀取放行)被改壞
- subagent 那 7 條任一「subagent≠總管」⇒ 這支閘開始讀 `agent_id``agent_type` 分身分了:
D20 對 subagent 與總管本該一視同仁(`inkstone/ISEP#119`),一旦分了岔就是 09-01 那種「一端有效一端失效」的破口回來了
🔴 **已知殘留破口(`inkstone/ISEP#119` 查出、實測+雲端實跑驗過,待裁)**push 目標若用
**未展開的 shell 變數**表示(`git -C "$VAR" push``cd "$VAR" && git push`),PreToolUse hook 收到的是
**展開前**的字串,`push_target_dir.py` 對 `$VAR` 一律回「不確定」⇒ 退回 **session 的 cwd** 解 remote。
`main-and-prod-push-guard` 遇到「不確定」是退回「要有戳記」=**擋**(fail-safe);
`github-contact-guard` 的預設方向相反——「證明不了是 github 就放行」⇒ cwd 是 Gitea 時,
一個真的推 GitHub 的 `-C "$VAR"` 會**漏擋**。這個不對稱就是殘留的同形狀破口。
上面測試用的都是**字面絕對路徑**(解得出來 ⇒ 擋),所以測試全綠**不代表**這個變數形狀已補。
雲端有 git proxy 當後手(未授權的 github repo 一律 403),本機沒有 ⇒ 本機風險較高。
修法有代價(fail-closed 會誤攔「用變數推 Gitea」,需配「改用字面路徑」的出路),屬 `誤攔比漏擋` 的取捨,另票裁。
### A37 — 雲端第一則 prompt 的主線就是現在這條,不是兩週前的舊線:31 條
```
+11
View File
@@ -0,0 +1,11 @@
# 雲端環境(claude.ai Cloud environment)變數清單的版本紀錄——**只記名字,不記值**。
# leo 2026-09-18:「你這裡記錄每版的 credential 的 key,但 value 自動產生,我就貼上去,你要有版本,因為這件事做了很多次,但你都沒記錄版本」
# 產生器 scripts/make-cloud-env.sh 讀本檔最後一版的號碼印在產物檔頭,並列出與前一版的差異。
# 欄位:版本<TAB>日期<TAB>key(逗號分隔,順序=NEEDED<TAB>為什麼變
v1 2026-08-20 GITEA_TOKEN_CLAUDE_CODE 首版:雲端 bootstrap 與 plugin 安裝(f855d82
v2 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE 雲端拿得到 youlin 的 CF tokenISEP#115 c5526ca9ea99
v3 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688 leo「youlin 和 geek6688 都全給」(ISEP#115 c554155427aef943
v4 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY 出貨三站實際部署到 uncle6ISEP#115 c5577cb310e9
v5 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY,ARCRUN_NS_YOULIN 出貨線實例座標(Arcrun#195dd86fefv0.21.0
v6 2026-09-18 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY,ARCRUN_NS_YOULIN,ARCRUN_NS_UNCLE6,GITHUB_MIRROR_TOKEN,GITHUB_ACCOUNT_NAME 雲端要能出 stageproduncle6 namespaceGitHub 鏡像兩把(InkStoneCo#98 c7739ISEP#158
v7 2026-09-18 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY,ARCRUN_NS_YOULIN,ARCRUN_NS_UNCLE6,GITHUB_MIRROR_TOKEN,GITHUB_ACCOUNT_NAME,TELEGRAM_CHAT_ID,TELEGRAM_BOT_TOKEN,ISEP_DRAIN 收回雲端現行卻不在 v1v6 的三個:TELEGRAM_CHAT_IDTELEGRAM_BOT_TOKEN(通知出口,值在 polaris/mira/.env,點名放行)+ISEP_DRAIN=1 固定值(leo 09-18 貼出現行雲端環境才發現漏收)
1 # 雲端環境(claude.ai Cloud environment)變數清單的版本紀錄——**只記名字,不記值**。
2 # leo 2026-09-18:「你這裡記錄每版的 credential 的 key,但 value 自動產生,我就貼上去,你要有版本,因為這件事做了很多次,但你都沒記錄版本」
3 # 產生器 scripts/make-cloud-env.sh 讀本檔最後一版的號碼印在產物檔頭,並列出與前一版的差異。
4 # 欄位:版本<TAB>日期<TAB>key(逗號分隔,順序=NEEDED)<TAB>為什麼變
5 v1 2026-08-20 GITEA_TOKEN_CLAUDE_CODE 首版:雲端 bootstrap 與 plugin 安裝(f855d82)
6 v2 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE 雲端拿得到 youlin 的 CF token(ISEP#115 c5526,ca9ea99)
7 v3 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688 leo「youlin 和 geek6688 都全給」(ISEP#115 c5541/5542,7aef943)
8 v4 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY 出貨三站實際部署到 uncle6(ISEP#115 c5577,cb310e9)
9 v5 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY,ARCRUN_NS_YOULIN 出貨線實例座標(Arcrun#195,dd86fef/v0.21.0)
10 v6 2026-09-18 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY,ARCRUN_NS_YOULIN,ARCRUN_NS_UNCLE6,GITHUB_MIRROR_TOKEN,GITHUB_ACCOUNT_NAME 雲端要能出 stage/prod:uncle6 namespace+GitHub 鏡像兩把(InkStoneCo#98 c7739;ISEP#158)
11 v7 2026-09-18 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY,ARCRUN_NS_YOULIN,ARCRUN_NS_UNCLE6,GITHUB_MIRROR_TOKEN,GITHUB_ACCOUNT_NAME,TELEGRAM_CHAT_ID,TELEGRAM_BOT_TOKEN,ISEP_DRAIN 收回雲端現行卻不在 v1–v6 的三個:TELEGRAM_CHAT_ID/TELEGRAM_BOT_TOKEN(通知出口,值在 polaris/mira/.env,點名放行)+ISEP_DRAIN=1 固定值(leo 09-18 貼出現行雲端環境才發現漏收)
+65 -16
View File
@@ -5,7 +5,7 @@
# 誤觸時怎麼關: 這支不擋任何東西。不想產生就別跑;產物在版控外,刪掉即可。
#
# 用法:bash scripts/make-cloud-env.sh
# 產物:~/.claude/cloud-env/<日期>.txt(權限 600**不在任何 repo 裡**
# 產物:~/Desktop/cloud-env-<版本>-<日期>.txt(權限 600**不在任何 repo 裡**;版本紀錄 docs/cloud-env-versions.tsv
# 本腳本只寫「變數名字」,值在執行當下才從既有 .env 讀出來寫進產物 —— 值不進版控、不進對話。
set -euo pipefail
@@ -44,6 +44,28 @@ NEEDED=(
# 只有 namespace 是機敏 ⇒ 走環境變數。原本只住在 ~/.arcrun/config.yaml 的 api_key
# 那個檔在家目錄、不隨 clone 走 ⇒ 雲端永遠拿不到。
ARCRUN_NS_YOULIN # youlin 實例的 namespaceX-Arcrun-API-Key
ARCRUN_NS_UNCLE6 # uncle6 實例的 namespace:出貨第 1、19、20 站派給 uncle6 工作流(inkstone/InkStoneCo#98 → comment 7739
# ── prod 出貨把產物推上 GitHub 鏡像(第 14、21、22 站)──
# 🔴 名字進清單 ≠ 雲端可以自由寫 GitHub`github-contact-guard.sh` 照樣擋,
# 寫入仍要 D20 開閘(保險檔落在哪由 inkstone/ISEP#157 處理)。
# 進清單是因為「拿不到這兩個名字」會讓 prod 那半在雲端**連開閘之後都走不下去**。
GITHUB_MIRROR_TOKEN # GitHub 鏡像 repo 的寫入 token(走標頭,不進網址)
GITHUB_ACCOUNT_NAME # 上面那把的帳號名(組鏡像 repo 路徑用,不是機密)
# ── 雲端工人的通知出口(v7,2026-09-18:leo 貼出現行雲端環境才發現 v1–v6 漏收)──
# 雲端走 Bot API 直送(`scripts/isep-notify`leo21c 的 notify_leo 已於 08-14 消失,InkStoneCo#110
TELEGRAM_CHAT_ID # 收訊的 chat id(不是機密)
TELEGRAM_BOT_TOKEN # 通知 bot 的 token
)
# 🔓 例外:值只住在 polaris/mira/.env、但**不是**打得到 leo21c 的憑證——點名放行,其他照擋(B8)
# Telegram 兩把是通知 bot,不碰任何 CF 帳號;雲端 09-18 前就已手動設了同樣的值。
MIRA_ALLOWED=(TELEGRAM_CHAT_ID TELEGRAM_BOT_TOKEN)
# ── 固定值(不是金鑰,產生器直接寫)──
FIXED=(
"ISEP_DRAIN=1" # 雲端工人=接票環境:ticket pick 自帶 --pool、佇列閘不准有票就收工(ISEP#130)
)
#
# 🔴 這份清單的邊界——加一把之前先問「這把打得到哪個實例」,不是「這把方便嗎」
@@ -91,23 +113,42 @@ lookup() { # $1=變數名 → 印出值(找不到就空)
return 1
}
# 預設丟 ~/.claude/cloud-env(權限 700)。要放別的地方= OUT_DIR=~/Desktop bash scripts/make-cloud-env.sh
OUT_DIR="${OUT_DIR:-$HOME/.claude/cloud-env}"
# ── 版本(leo 2026-09-18:「你要有版本,因為這件事做了很多次,但你都沒記錄版本」)──
# 真相源=docs/cloud-env-versions.tsv(只記 key,不記值)。最後一列=現行版。
# 🔴 NEEDED 跟最後一列的 key 不一致 ⇒ 拒絕產生:改了清單就必須在 tsv 加一版。
VERSIONS="$(cd "$(dirname "$0")/.." && pwd)/docs/cloud-env-versions.tsv"
[ -f "$VERSIONS" ] || { echo "🔴 找不到 $VERSIONS" >&2; exit 1; }
CUR_LINE=$(grep -v '^#' "$VERSIONS" | grep -v '^[[:space:]]*$' | tail -1)
PREV_LINE=$(grep -v '^#' "$VERSIONS" | grep -v '^[[:space:]]*$' | tail -2 | head -1)
VER=$(printf '%s' "$CUR_LINE" | cut -f1); VER_DATE=$(printf '%s' "$CUR_LINE" | cut -f2)
VER_KEYS=$(printf '%s' "$CUR_LINE" | cut -f3); VER_WHY=$(printf '%s' "$CUR_LINE" | cut -f4)
PREV_VER=$(printf '%s' "$PREV_LINE" | cut -f1); PREV_KEYS=$(printf '%s' "$PREV_LINE" | cut -f3)
FIXED_NAMES=(); for kv in "${FIXED[@]}"; do FIXED_NAMES+=("${kv%%=*}"); done
NEEDED_JOINED=$(IFS=,; printf '%s' "${NEEDED[*]} ${FIXED_NAMES[*]}" | tr ' ' ',')
if [ "$NEEDED_JOINED" != "$VER_KEYS" ]; then
echo "🔴 NEEDED 與 $VERSIONS 最後一版($VER)的 key 不一致——改了清單就要在 tsv 加一列新版本再跑。" >&2
echo " NEEDED$NEEDED_JOINED" >&2
echo " $VER $VER_KEYS" >&2
exit 2
fi
ADDED=$(comm -13 <(tr , '\n' <<<"$PREV_KEYS" | sort) <(tr , '\n' <<<"$VER_KEYS" | sort) | paste -sd, -)
REMOVED=$(comm -23 <(tr , '\n' <<<"$PREV_KEYS" | sort) <(tr , '\n' <<<"$VER_KEYS" | sort) | paste -sd, -)
# 預設丟桌面(leo 2026-09-18 指定)。要放別的地方= OUT_DIR=<目錄> bash scripts/make-cloud-env.sh
OUT_DIR="${OUT_DIR:-$HOME/Desktop}"
mkdir -p "$OUT_DIR"
# 只在「這個目錄是我們自己造的預設位置」時才收緊權限——
# OUT_DIR 可被覆寫,不該對使用者指定的既有目錄(例如 ~/Desktop)動權限。
[ "$OUT_DIR" = "$HOME/.claude/cloud-env" ] && chmod 700 "$OUT_DIR"
OUT="$OUT_DIR/$(date +%Y%m%d-%H%M%S).txt"
OUT="$OUT_DIR/cloud-env-${VER}-$(date +%Y%m%d-%H%M).txt"
SETUP="$(cd "$(dirname "$0")/.." && pwd)/docs/cloud-setup-script.sh"
[ -f "$SETUP" ] || { echo "🔴 找不到 $SETUP" >&2; exit 1; }
MISSING=()
# 🔴 兩個檔分開產(2026-09-18 實撞):原本兩段放同一檔、中間用「════」分隔,
# leo 把分隔線一起貼進 Setup script 欄 ⇒ 雲端開機 exit 127「'════…': command not found」,
# 每一趟 cloud-worker 在開機就死。⇒ 變數一個檔(只有 KEY=VALUE)、開機腳本一個檔(逐位元組=cloud-setup-script.sh),
# **兩個檔都整份貼**,沒有任何要人自己裁切的地方。
SETUP_OUT="${OUT%.txt}-setup-script.sh"
{
echo "claude.ai → Cloud environments → 你的環境。下面兩塊各自貼進對應欄位。"
echo "產生時間:$(date '+%Y-%m-%d %H:%M')"
echo
echo "════════ ① Environment variables(一行一個,名字與值分開填)════════"
for n in "${NEEDED[@]}"; do
if v=$(lookup "$n"); then
echo "$n=$v"
@@ -116,12 +157,20 @@ MISSING=()
MISSING+=("$n")
fi
done
echo
echo "════════ ② Setup script(整段貼,一字不改)════════"
cat "$SETUP"
for kv in "${FIXED[@]}"; do echo "$kv"; done
} > "$OUT"
chmod 600 "$OUT"
cp "$SETUP" "$SETUP_OUT"
chmod 600 "$OUT" "$SETUP_OUT"
{
echo "claude.ai → Cloud environments → 你的環境(${VER}${VER_DATE}${VER_WHY}"
echo "${PREV_VER} 多:${ADDED:-(無)}|少:${REMOVED:-(無)}"
echo "① Environment variables 欄:整份貼 $(basename "$OUT")"
echo "② Setup script 欄:整份貼 $(basename "$SETUP_OUT")(一字不改,不要加任何東西)"
echo "貼完刪掉這三個檔。"
} > "${OUT%.txt}-README.txt"
echo "✅ 產生完成:$OUT"
echo " 版本:${VER}(比 ${PREV_VER} 多:${ADDED:-}|少:${REMOVED:-}"
echo " 變數 ${#NEEDED[@]} 個|找不到值的 ${#MISSING[@]}${MISSING[*]:-}"
echo " 🔴 這個檔含金鑰真身:貼完就刪(rm '$OUT'),它刻意不在任何 repo 裡。"
echo " 開機腳本:$SETUP_OUT"
echo " 🔴 含金鑰真身:貼完就刪(rm '${OUT%.txt}'*),它刻意不在任何 repo 裡。"
+28
View File
@@ -71,6 +71,34 @@ runc 0 "$W/body/sub" 'git push origin x'
runc 0 "$W/shell" "cd $W && git -C body push origin x"
runc 0 "$W/body" "(cd $W/shell && true); git push origin x"
echo "── subagent 走同一條判斷:payload 帶 agent_id/agent_type 拿到跟總管一模一樣的判決(inkstone/ISEP#119)──"
# 為什麼這群非有不可(inkstone/ISEP#119):2026-09-01 一個 subagent 把分支推上 GitHub
# 沒經過解保險,同時總管這端擋得住——表面像「subagent 端失效」。但官方文件講明:
# subagent 的 tool 呼叫照樣觸發同一批 PreToolUse hook,只是 input 多帶 agent_id/agent_type
# https://code.claude.com/docs/en/hooks)。這支閘從頭到尾不讀那兩個欄位 ⇒ 它**無從**
# 只擋總管或只擋 subagent。當天真正的洞是同一族的 cwd 病(上面 ISEP#109 那組):
# subagent 站在薄殼、靠 cd/-C 推它自己那顆 origingithub 的 repo,舊版在錯的目錄解 origin
# ⇒ 放行。這群把「帶 agent_id 的 payload 判決 == 不帶的判決」釘死,證明沒有 subagent 專屬破口。
runsub(){ # $1=want $2=cwd $3=cmd —— 同一條指令跑兩次:帶 agent_id(subagent)與不帶(總管),
# 兩次都必須等於 want,才算「subagent 與總管走同一條判斷、拿同一個結果」。
local want="$1" cwd="$2" cmd="$3" a b
a=$(printf '%s' "{\"tool_name\":\"Bash\",\"cwd\":$(python3 -c 'import json,sys;print(json.dumps(sys.argv[1]))' "$cwd"),\"agent_id\":\"agt_119\",\"agent_type\":\"isep-hand\",\"tool_input\":{\"command\":$(python3 -c 'import json,sys;print(json.dumps(sys.argv[1]))' "$cmd")}}" \
| bash "$H" >/dev/null 2>&1; echo $?)
b=$(printf '%s' "{\"tool_name\":\"Bash\",\"cwd\":$(python3 -c 'import json,sys;print(json.dumps(sys.argv[1]))' "$cwd"),\"tool_input\":{\"command\":$(python3 -c 'import json,sys;print(json.dumps(sys.argv[1]))' "$cmd")}}" \
| bash "$H" >/dev/null 2>&1; echo $?)
if [ "$a" = "$want" ] && [ "$b" = "$want" ]; then PASS=$((PASS+1)); printf ' ✅ '; else FAIL=$((FAIL+1)); printf ' ❌ '; fi
printf 'want=%s subagent=%s 總管=%s cwd=%s %.48s\n' "$want" "$a" "$b" "${cwd#$W/}" "$cmd"
}
echo " · subagent 推 GitHub(含 09-01 的 cd/-C 原形狀)→ 擋,且與總管同判"
runsub 2 "$W/shell" 'git push origin x'
runsub 2 "$W/body" "cd $W/shell && git push origin x"
runsub 2 "$W/body" "git -C $W/shell push origin x"
runsub 2 "$W/shell" 'gh api repos/example/example/issues'
echo " · subagent 推 Gitea/讀取 → 放行,且與總管同判(沒有為了擋 subagent 而擋過頭)"
runsub 0 "$W/body" 'git push origin x'
runsub 0 "$W/shell" "git -C $W/body push origin x"
runsub 0 "$W/shell" 'git clone https://github.com/example/example.git'
echo
echo "$PASS/$((PASS+FAIL)) 通過"
[ "$FAIL" -eq 0 ]
+15 -1
View File
@@ -35,6 +35,7 @@ NEEDED=()
while IFS= read -r l; do NEEDED+=("$l"); done < <(
sed -n '/^NEEDED=(/,/^)/p' "$S" | sed -E '1d;$d; s/[[:space:]]*#.*$//; s/[[:space:]]//g' | grep -v '^$')
[ "${#NEEDED[@]}" -gt 0 ] || { echo "🔴 從 $S 讀不出 NEEDED,測試本身壞了"; exit 1; }
MIRA_ALLOWED=($(sed -n 's/^MIRA_ALLOWED=(\(.*\))/\1/p' "$S"))
echo "清單(只有名字):${NEEDED[*]}"
echo
@@ -59,6 +60,19 @@ chk 0 "$miss" "A3 每個名字都帶著值出現在 ① 區塊"
printf '%s\n' "$OUT_LINE" | grep -q "找不到值的 0 個" \
&& ok "A4 收尾那行說「找不到值的 0 個」" || bad "A4 收尾計數不對:$OUT_LINE"
# 版本(leo 2026-09-18):產物檔頭帶現行版本號;清單改了沒升版要拒絕
grep -q "v[0-9]" "${F%.txt}-README.txt" && ok "V1 README 寫著版本號" || bad "V1 README 沒有版本號"
# 2026-09-18 實撞:兩段同檔、分隔線被貼進 Setup script 欄 ⇒ 雲端開機 exit 127。分檔後兩件事要守住:
bad_lines=$(grep -cvE '^[A-Z][A-Z0-9_]*=' "$F")
chk 0 "$bad_lines" "V3 變數檔每一行都是 KEY=VALUE(沒有分隔線、說明文字)"
cmp -s "${F%.txt}-setup-script.sh" "$(cd "$(dirname "$S")/.." && pwd)/docs/cloud-setup-script.sh" \
&& ok "V4 開機腳本檔與 docs/cloud-setup-script.sh 逐位元組相同" || bad "V4 開機腳本檔被加料了"
VT="$(cd "$(dirname "$S")/.." && pwd)/docs/cloud-env-versions.tsv"; VB="$VT.bak-test"
cp "$VT" "$VB"; printf 'v999\t2099-01-01\tNOPE_ONLY\t測試用\n' >> "$VT"
INKSTONE_ROOT="$FAKE" OUT_DIR="$OUTD" bash "$S" >/dev/null 2>&1; rc=$?
mv "$VB" "$VT"
chk 2 "$rc" "V2 清單與最後一版 key 不一致時拒絕產生(離開碼 2)"
# 缺值時不准假綠:拿掉第一個,它要變成 <🔴 …> 且計數變 1
grep -v "^${NEEDED[0]}=" "$FAKE/.env" > "$FAKE/.env.tmp" && mv "$FAKE/.env.tmp" "$FAKE/.env"
OUT2=$(INKSTONE_ROOT="$FAKE" OUT_DIR="$OUTD" bash "$S" 2>&1)
@@ -104,7 +118,7 @@ else
if r=$(real_src "$n"); then
len="${r%% *}"; src="${r#* }"
printf ' %-38s → 找得到(長度 %s)來自 %s\n' "$n" "$len" "${src#$BASE/}"
case "$src" in */polaris/mira/.env) prod=$((prod+1)); echo " 🔴 ↑ 這是 leo21c 現役正式環境那份 .env";; esac
case "$src" in */polaris/mira/.env) if printf '%s\n' "${MIRA_ALLOWED[@]}" | grep -qx "$n"; then echo " 🔓 ↑ 點名放行(MIRA_ALLOWED:通知 bot,不碰 CF"; else prod=$((prod+1)); echo " 🔴 ↑ 這是 leo21c 現役正式環境那份 .env"; fi;; esac
else
printf ' %-38s → 🔴 這台拿不到\n' "$n"; gone=$((gone+1))
fi