HRS Decision Board · 2026-07-29
方向對 · 執行細節不可照做

把 HITL 板搬上邊緣,
別把整個腦袋一起搬上去

ANTIGRAVITY 提的 24/7 Cloudflare Pages 遷移,結論是對的。但它那句「把 repo 根目錄設為 build output directory」,會讓 1799 個檔案——含六處明文憑證、soul.md、投資邏輯板、額度儀表板——同時變成公開可 curl。

稽核 CacoCh Agent @ SS2 · 來源 ANTIGRAVITY · 狀態 待決議

← 回 HITL 提案板
八格 = 八個查核面 · 明度由淺至深 = 嚴重度遞增 · 點格子跳到下方對應分頁
本案主題
HITL 提案板
24/7 邊緣遷移

SS1 依賴 → Cloudflare Pages
hitl.superportia.dev

裁決:方向對,執行細節不可照做

01 · Context

今天的 HITL 板,命懸 SS1 的電源鍵

要解的問題很單純:夏哥在外面想看提案板,得先確認家裡那台 Mac 醒著。

現況鏈路

瀏覽器 / curl ↓ Cloudflare DNS (superportia.dev) ↓ Cloudflare Tunnel ← launchd/com.superportia.cloudflared-cacoch.plist ↓ http://127.0.0.1:8789 ↓ uvicorn cacoch.server:app ← cacoch/server.py ↓ Host == "hitl.superportia.dev" → /hitl/ ↓ StaticFiles mount → superportia-ops/hitl/

整條鏈裡有三個必須同時活著的環節。任何一個掛掉,外網就是 502。

三個單點在哪

  • SS1 電源:關機或睡眠即斷。這是最常觸發的。
  • cloudflaredlaunchd/com.superportia.cloudflared-cacoch.plist,tunnel UUID 16855065-…。ingress 規則在 ~/.cloudflared/config.yml——不在 repo 裡,換機器就得重建。
  • uvicorncacoch.server:app127.0.0.1:8789。repo 裡有兩份指向同一 port 的 plist(cacoch-servercacoch-center),疑似新舊未清理,本身就是潛在衝突源。
值得注意:ingress 設定檔沒進版控,所以「SS1 這台機器」本身是不可複製的基礎設施。這也是遷移的附帶好處之一。

目標狀態

變成跟 2026-taxi.pages.dev 同一個模式:

Agent (SS1/SS2) → git push → GitHub ↓ GitHub Actions (paths: hitl/** portal/**) ↓ build-pages.sh → dist/ ← 白名單 + 憑證掃描 ↓ wrangler pages deploy → Cloudflare Edge ↓ hitl.superportia.dev (兩台電腦全關也照開)

ANTIGRAVITY 的方向就是這個,完全正確。分歧只在「dist/ 裡放什麼」這一步——見 06 分頁。

Finding E · 確認

只發布 hitl + portal,暴露面完全不變

這一格是整個方案能成立的地基:風險來自「多發布了什麼」,不是「發布這件事」。

證據

很多人會直覺以為「搬上 Pages = 變公開」。實際上這兩個路徑今天就已經是公開、免登入的:

來源內容
AGENTS.md:175「Agent URL: hitl.superportia.dev/hitl/(無 CF Access)」
cacoch-selftest.sh:104註記 no CF Access,直接 curl 拿 200
.claude/rules/L3-archive/corrections.md:1100實測:cacoch. 302 到 Access 登入頁,hitl. 200 直開
cacoch/server.py/hitl mount 沒有任何 auth middleware

換言之:只要有人知道網址(或掃到 DNS record),今天就能看到整個板與所有提案。遷到 Pages 不會讓這件事變得更糟或更好。

private repo 保護的是原始碼,不是網站

xysb-lab/superportia-ops 確認為 PRIVATEgh repo view 回報 isPrivate: true)。但這兩件事在 Cloudflare Pages 的設計裡是分開的

private 管得到嗎
建置端(Pages clone 你的 repo)管得到——需要授權才讀得到私有原始碼。2026-Taxi 就是這樣跑的。
發布端(Pages 服務出的靜態檔)管不到——輸出目錄下的東西一律公開可 curl。

所以 private 這件事,讓 Track A 憑證的急迫性從 P0 降到 P1(目前只有 repo 授權者看得到),但對 Finding A 沒有任何緩解作用

為什麼不乾脆加 Cloudflare Access

技術上可以在 Pages 專案前面掛 Access,變成要登入才能看。但那會直接違背 hitl. 子網域當初存在的理由。

.claude/rules/L3-archive/corrections.md:1100 記錄了這段歷史:曾經誤導夏哥去 cacoch.superportia.dev/hitl/,實測 302 到 Access 登入頁;正確的是 hitl.superportia.dev/hitl/,200 直開。這個子網域就是為了讓 agent 不用處理 Access token 也能 curl 才切出來的。

結論:hitl 這條路必須維持公開。也正因如此,白名單 dist/ 是唯一解——不能靠登入牆兜底。
Finding B · 前置工項

「相對路徑相容性已提前驗證通過」——這句不成立

提案宣稱已檢查過、無 file 協定與 localhost 硬編。實測仍有三處,而且還牽出第二個沒人提的風險。

違規清單

檔案:行違規git
portal/hrs-ict-killzones-tfo-50k-proposal.html:417,424,431圖片 src 指向本機絕對路徑(file 協定)未追蹤
portal/hrs-think9-3tier-tutorial-proposal.html:389,396,403同上未追蹤
portal/sector-heatmap/decision-log-20260709.html:265連結指向 localhost 的 8788 埠已追蹤 · 已修

諷刺的是,這三種樣式正是 scripts/hrs-gate.sh:148-162 明文要擋的——閘門早就寫好了,只是這些頁在閘門生效前就入庫了。

附帶發現:這支閘門在 SS2 上因為 Git Bash 會把 / 開頭的參數改寫成 Windows 路徑,第 4–7 項檢查(registry / ssotNote / 相對 url / cover)恆為 FAIL,等於從來沒真的驗過任何一張卡。本次已於 scripts/hrs-gate.sh 修正(加 MSYS_NO_PATHCONV=1)。

衍生風險:未追蹤的頁在遷移當下會靜默消失

SS1 目前用 StaticFiles 直接掛檔案系統,所以沒進 git 的頁面照樣服務得出去。Cloudflare Pages 讀的是 git

$ git ls-files --others --exclude-standard portal/ portal/hrs-ict-killzones-tfo-50k-proposal.html ← 遷移後 404 portal/hrs-think9-3tier-tutorial-proposal.html ← 遷移後 404

這兩頁遷移當下會從活著變成 404,而且不會有任何錯誤訊息告訴你——CI 綠燈、部署成功、頁面消失。這類故障最難事後追。

對策

  1. decision-log-20260709.html:265 的 localhost 連結 — 已改為 /portal/sector-heatmap/
  2. 那兩個未追蹤頁逐一決定:要留就把圖片搬進 repo、路徑改 /portal/…、然後 git add;要廢就明確刪掉,不要留在檔案系統裡當幽靈。
  3. 把這三種樣式的檢查放進發布管線 — 已內建於 scripts/build-pages.sh 的 fail-fast 掃描。
  4. 遷移前跑一次 bash scripts/hrs-gate.sh audit,現在閘門修好了,這次的結果才有意義。
Finding C · 要改道

漏評估:「批准」按鈕會直接失效

提案只檢查了 hitl/index.html。那頁確實 100% 靜態相容——但真正會批准的不是那頁。

呼叫鏈

hitl/index.html:213 唯一的網路呼叫是 fetch('/hitl/hitl-registry.json'),純唯讀;拖拽與看板狀態全存 localStorage。這頁搬過去完全沒問題。

會回寫 registry 的是另一頁:

portal/cacoch-desktop-notifier-20260623.html:1003,1031 ↓ POST /api/hitl/approve | POST /api/hitl/update cacoch/server.py:993-1056 ↓ item["status"] = payload.new_status ↓ os.replace(tmp, hitl-registry.json) ← 原子寫入 ↓ _backup_registry() ← 備份到 hitl/backups/

哪些會壞

功能Pages 上的狀態
看板瀏覽、拖拽、分類、封面✅ 正常(純前端 + localStorage)
匯出 JSON、我的暫存、重設為預設✅ 正常
POST /api/hitl/approve(改狀態為已批准)❌ 404,沒有後端
POST /api/hitl/update(改 nextAction / feedback / stage)❌ 404
自動備份到 hitl/backups/❌ 隨 API 一起消失

注意 hitl/index.html:190STATUS_ICON 仍會正確顯示「已批准」等狀態——它只是顯示 registry 給的值。所以壞掉不會很明顯,會像是「按了沒反應」。

Track C 改道

Phase 1(本次):批准 = 改 hitl-registry.jsongit push,15–30 秒全球生效。SS1 在線時仍可用原本的 API,只是改成寫完自動 commit,讓兩條路收斂到同一個 SSoT。好處是批准動作有 git 歷史可追,比現在的原子寫入+時間戳備份更清楚。

Phase 2(另案,本次不做):加 Cloudflare Pages Functions + GitHub API 寫回,才能在手機上直接點批准。要處理 token 保管與 CSRF,範圍比這次大,所以拆開。

如果夏哥平常就是在電腦前批准,Phase 1 其實已經夠用,Phase 2 可以無限期延後。
Finding D · 危險步驟

漏評估:SS1 的 tunnel 不能整條拆

原提案 Step 3 寫「刪除/停用 SS1 上舊有的 tunnel 綁定」。照字面執行會誤殺兩個服務。

三個網域共用同一顆 origin

cacoch/server.py:63-73 用 Host header 分流,同一顆 uvicorn127.0.0.1:8789)同時服務:

Host導向保護
hitl.superportia.dev/hitl/ 提案板無(刻意)
cacoch.superportia.devportal 首頁 / cacoch 手帳CF Access
a.nqio.devhierarchy board

另外 api.superportia.dev / ubi.superportia.dev 走的是不同的 ubi-mcp named tunnel,n8n.superportia.dev 在 Hetzner VPS 上獨立跑,這三個不受影響。

誤刪的後果

  • 停掉 launchd/com.superportia.cloudflared-cacoch.plistcacoch.superportia.deva.nqio.dev 一起 502。
  • 刪掉 tunnel 本身(cloudflared tunnel delete)→ 不可逆,UUID 與 CNAME 全部要重建,而 ingress 設定檔 ~/.cloudflared/config.yml 不在 repo 裡,沒有備份可還原。
  • 停掉 uvicorn → 連帶失去 POST /api/hitl/approve(見 04 分頁)。
最糟的組合:先刪 tunnel、才發現 ingress 設定沒版控。那台 Mac 上的基礎設施是不可複製的,這一步沒有 undo。

正確步驟

  1. 只改一筆 DNS:把 hitl.superportia.dev 的 CNAME 從 <uuid>.cfargotunnel.com 改指 Pages 專案。
  2. 不動 tunnel 程序、不動 ~/.cloudflared/config.yml、不動 cacoch.a.nqio.dev 的紀錄。
  3. 順手把 ~/.cloudflared/config.yml 複製一份進 repo(去識別化後)當災難復原文件——這是目前唯一沒版控的關鍵設定。
  4. 驗證:curl -sI https://cacoch.superportia.dev/ 仍回 302(Access 登入頁)= 沒被波及。
Finding A · 阻斷級

「repo root 當 build output」= 1799 個追蹤檔全部公開

Cloudflare Pages 會把 output directory 底下的東西原封不動當靜態網站發佈——沒有 auth、沒有過濾。設成 repo 根目錄,等於把整包貼到公網。

憑證

git ls-files 回報 1799 個檔案。裡面有還在用、可直接盜用的明文憑證。此頁本身是公開頁面,所以只列類型與位置,不列任何值:

位置憑證類型
scripts/market-analyst/run_pipeline.sh:7DeepSeek API key(硬編)
scripts/telegram-antigravity-bridge.py:34Telegram Bot Token(預設 fallback)
launchd/com.superportia.telegram-antigravity-bridge.plist:19同一組 Token,第二處
mcp_config.json:22Google OAuth Client Secret
mcp_config.json:89Supabase access token
.mcp.json:10內部 CLOUD_UB token

另有 89 處類似樣式的命中屬誤判sk-notification-…sk-cost-gate-… 這類任務代稱),已逐一排除,不列入上表。

repo 是 private 也擋不住這一條。private 決定「誰能讀 git」,Pages 的發布產物則是公開的。設成 repo root,任何人都能直接 curl/mcp_config.json/soul.md/quota-dashboard.html,而且吃 CDN 快取。

治理與記憶

soul.mdconstitution/(FLOOR、LANDMINES)、50-Decisions/memory/(session checkpoints、verified knowledge)、data/usage-log.jsonl(逐任務成本與 token 用量)。

backups/ 底下 74 個檔案把上述治理文件重複備份了五到六份,檔名本身還帶出 SS1 的 macOS 帳號與完整家目錄結構(~/.claude~/.codex~/.gemini~/.hermes)。

根目錄 14 個被追蹤的儀表板

檔案公開後暴露什麼
quota-dashboard.html六大 AI 平台額度、燒用速度、自動化排程
investment-logic-board.html個人投資邏輯藍圖
claude-command-chain.html五殼二橋指揮鏈拓樸(opsec)
cacoch-fleet-ssot.html / cacoch-crossover-topology.html艦隊架構與交叉拓樸
council_report_2026*.html × 5決策議會原始思維紀錄

為什麼 .gitignore 擋不住

現行 .gitignore 擋的是 .envgcp-oauth.keys.json*.keys.json——也就是「常見憑證檔名樣式」。它擋不住的正好是洩漏落點的兩類:

  • 寫死在一般設定檔裡的 key/value 型 secretmcp_config.json.mcp.json 的檔名長得完全無害,不符合任何憑證命名樣式。
  • 內部治理文件soul.mdconstitution/backups/ 從來就不被視為「機密檔案」,也沒人想過它們會變成網頁。
所以修正方案不是「補 .gitignore」——那還是靠人記得。是改成白名單:預設什麼都不發布,只有明確列出的才進 dist/。加上一道「命中即 exit 1」的掃描當第二層。
Risk · 不可逆

「5 分鐘完成轉移」的代價

技術上確實可以在五分鐘內按完那幾個按鈕。問題是第六分鐘。

時間線

T+0 Pages 專案建立,output directory = repo root T+30s 首次建置完成,1799 個檔案上線 T+1m Cloudflare 邊緣節點開始快取 T+數小時 secret 掃描機器人索引到 /mcp_config.json T+? 憑證被使用。你不會收到通知。

最後一行是重點:憑證被盜用沒有告警。DeepSeek 額度被燒、Telegram bot 被接管、Supabase 資料被讀,都要等你自己發現。

為何不可逆

  • 刪檔止不了血。CDN 快取、爬蟲副本、secret 掃描服務的資料庫都已經有副本了。把 Pages 專案刪掉只是讓你自己看不到。
  • 唯一的補救是全部輪替——六組憑證逐一 revoke 重發,而其中有兩組(Telegram token)散在兩個檔案裡,漏一個就等於沒做。
  • opsec 損失無法輪替。指揮鏈拓樸、額度節奏、投資邏輯藍圖外流之後,沒有「重新發一組」這回事。

急迫性評級

項目若照原提案做目前實況(repo private)
六處憑證P0 · 已洩漏,立刻輪替P1 · 排程處理
治理文件 / 儀表板P0 · 不可逆外流P3 · 僅 repo 授權者可見
白名單管線P1 · 遷移的前置條件

右欄是今天的狀態。那六處憑證現在就已經在 git 歷史裡了——repo 是 private 所以急迫性降一級,但只要 repo 曾被任何人 clone 過、或未來誤設為 public,風險就回來。

所以憑證處理與遷移脫鉤、可以先做。這就是中央格「② 先只做 A」那個選項的意義。
Plan · 修正方案

發布白名單產出目錄,不是發布 repo

一句話原則:Pages 只發布一個 dist/,內容嚴格等於今天已經公開的那兩個路徑。

superportia-ops 1799 tracked files hitl/ · portal/ soul · constitution memory · backups config secrets build-pages.sh whitelist copy + secret scan 命中即 exit 1 dist/ hitl/ + portal/ 僅此而已 Pages 24/7 repo 的其餘部分永遠不會進入 Cloudflare —— 不是靠設定擋,是根本沒上傳

Track A · 憑證(P1,可獨立先做)

  1. 六個位置的憑證逐一 revoke 並重發,程式改讀環境變數($DEEPSEEK_API_KEY 等)。
  2. Telegram token 記得兩個檔案都要改scripts/telegram-antigravity-bridge.py:34launchd/…bridge.plist:19
  3. mcp_config.json.mcp.jsonlitellm_config.yaml 加入 .gitignoregit rm --cached,各附一份 .example 範本。
  4. scripts/vault-autocommit.sh 裡已經有偵測憑證外洩的 regex——把它接進 pre-commit,讓它對自己所在的 repo 也生效。
這一 Track 完全不碰網站,做完也不會有任何外部變化。是純粹的止血。

Track B · 白名單管線

  1. scripts/build-pages.sh已完成)產出:
    dist/ ├── index.html ← 轉址到 /hitl/ ├── hitl/ ← 僅 index.html · hitl-registry.json │ card.schema.json · covers/ └── portal/ ← 全量(今日已公開,維持現狀)
    來源清單由 git ls-files 決定,不是掃檔案系統——Pages 讀 git,未追蹤檔的落差在 build 期就會現形。排除所有 *.bak*hitl/backups/
  2. fail-fast 掃描已完成):dist/ 內若出現 file 協定、localhost、憑證樣式、或白名單哨兵(mcp_config.json / soul.md / memory/ 等),直接 exit 1。實跑:223 檔 18M,零命中。
  3. 處理那兩個未追蹤檔(見 03 分頁)。
  4. .github/workflows/deploy-pages.yml,用 wrangler action 直傳 dist,不用 Pages 的 GitHub 整合。可用 paths: [hitl/**, portal/**] 過濾,避開免費版每月 500 次構建上限;repo 其餘部分連 clone 都不會進 Cloudflare。
  5. DNS 只改一筆hitl.superportia.dev 改指 Pages。其餘見 05 分頁。

Track C · 批准流程

Phase 1 走 git push,Phase 2(Pages Functions)另案。細節見 04 分頁。

驗收清單

檢查期望
bash scripts/build-pages.sh只有 hitl/ + portal/ + index.html,掃描 exit 0
bash scripts/hrs-gate.sh audit0 FAIL
curl -sI …/hitl/200
curl -sI …/mcp_config.json404 ← 最關鍵
curl -sI https://cacoch.superportia.dev/302(未被波及)
關閉 SS1,手機開板與任一提案頁全部正常
倒數第三條是這整個方案的單一判準。/mcp_config.json 回 404,才代表白名單真的生效;回 200 就代表白名單沒接上,必須立刻下線並輪替憑證。
「可行性 100%」講的是能不能做到
不是做了之後會怎樣兩者之間的距離,就是 1799 個檔案。

這次真正的收穫不是「Pages 能不能用」——它當然能用。是發現我們有一個已經寫好、但套用不完全的閘門:hrs-gate.sh 早就在擋 file 協定與 localhost 硬編,可是 portal 裡仍有三個頁面帶著這些東西活到今天;vault-autocommit.sh 裡有一段偵測 API key 外洩的 regex,而它所在的這個 repo 就躺著六處明文憑證。更狠的是,這支閘門在 SS2 上因為 Git Bash 的路徑轉換,第 4–7 項檢查恆為 FAIL——它根本沒真的驗過任何一張卡(本次已修)。

規範存在,不等於規範生效。所以修正方案裡最重要的不是 dist/ 目錄本身,是那個「命中即 exit 1」的掃描步驟——把「記得排除敏感檔案」這件需要紀律的事,換成一道機器擋板。