--- name: ship-check description: | 改完任何會影響用戶的東西之後、說「做完了」之前必讀(改雲端 worker/portal/daemon/ workflow/installer 都算)。也在下列時機自動載入:要打包 App、要出貨、要推 bundle、 要送 MS Store、leo 問「可以測了嗎」「版本為什麼沒變」「更新了嗎」「封測者拿得到嗎」。 核心判準:**版本號是 leo 唯一的驗收介面**——portal 版本卡看雲端、daemon 檢查更新看桌面; 版本沒動=他無從判斷你做了什麼=等於沒交付,而「我在某台實例 wrangler deploy 過了」不算。 收齊:兩條版本線的差別/重打 bundle(最常漏,要 grep 複驗改動真的進去)/ 改 workflow 要重編預編圖/三支機械閘+把 DMG/zip 真的打開檢查/寫 changelog(用戶語言)/ D20 開閘出貨/purge jsDelivr/從 leo 會看的那兩處抓實際畫面複驗。 附「常見漏掉的」實撞表與收工前五問。 --- # /ship-check — 改完東西後,讓 leo 看得到版本變了 > **這支解什麼病**(leo 2026-08-05 原話): > 「對人來說,**我雲端看 portal 有沒有更新,本地看 daemon 有沒有更新**, > 這個更新機制都寫好了,然後你說你改了這麼多居然版本一樣,還要去找怎麼做, > **這個太危險了**。」 > > 🔴 **核心判準:版本號是 leo 唯一的驗收介面。** > 版本沒動 = 他無從判斷你做了什麼 = 你等於沒交付。 > 「我在某台實例 `wrangler deploy` 過了」**不算**——那只改了那一台。 --- ## ⚠️ 這支 skill 自己的失效模式(先讀這段) leo 2026-08-05:「**你寫完一個 skill 然後每個我要提醒你,表示這個 skill 無效**」 **根因**:我寫這支時是**憑印象列步驟**,沒有真的走一遍使用者的路 ⇒ 於是「DMG 打開長什麼樣」「Info.plist 版本對不對」這些**只有真的打開才看得到**的東西全漏了, 每一條都要 leo 問「你檢查了嗎」才補。 **因此本 skill 的鐵律**: 1. **每一條檢查都要有可貼的實測輸出**——寫不出指令的條目就是還沒想清楚,不要列。 2. **凡是「使用者會看到的東西」,一律真的打開來看**(掛載 DMG、解開 zip、抓網頁內容), 不是確認檔案存在、不是看腳本說成功。 3. **被 leo 問出來的缺口,當場補進這支 skill**——否則下次還是靠他記得。 (本檔的 3.5、2.9 兩段都是這樣補進來的,日期都記著。) --- ## 什麼時候跑 **改完任何會影響用戶的東西之後**(雲端 worker/portal/daemon/workflow), 在說「做完了」之前。不是收工才跑。 --- ## 🚚 雲端線出貨=**一個指令**,不要照下面的步驟手工做(2026-08-08 起) > leo 2026-08-08:「**每次做一樣的事,結果會打錯實例,就是你的出貨閘是錯的。 > 寫對的應該每次都機械式的做同一件事,寫錯位置也會被它修正。**」 > 「這個出貨閘門就是廢的,它應該要像 GitHub Actions 一樣 CI/CD。」 ```bash cd products/arcrun-rag node installer/scripts/ship.mjs --list # 有哪些目標 node installer/scripts/ship.mjs --target stage # 預演:建+算版本+報線上差距 node installer/scripts/ship.mjs --target stage --confirm # 真的走完 stage node installer/scripts/ship.mjs --target prod --confirm # prod(先 stage,且需 leo 開閘) ``` **九個步驟固定不變**:`preflight → build → version → commit → push → pin → deploy → purge → verify` 每一步只有「執行/跳過(附機械理由)」兩種結果,**斷了後面一步都不跑**。 它已經替你做掉的事(**所以下面 §2〜§6 的手工步驟不要再照著做一遍**): | 以前靠人記得 | 現在 | |---|---| | 記得重打 bundle | `build` 步驟**每次都重打**(含 `build-ui-bundle.mjs`——它跟 `build-bundles.mjs` 是**兩支**,只跑一支就是 portal 改動送不出去) | | 記得換釘子、且**兩處都要換** | `pin` 步驟同時寫 `wrangler.toml` 的 vars 與 `worker.js` 常數 | | 記得 purge jsDelivr | `purge` 步驟(prod 才需要),驗到收斂為止 | | 記得複驗線上 | `verify` 步驟**真的把 daemon 下載下來算 sha256** | | 手打 `--bundles <路徑>`(會打錯) | 🔴 **已移除**。目標只能 `--target`,座標全來自 `installer/ship.targets.json` | 🔴 **目標打錯打不進去**:本機 clone 的 `origin` 與登錄簿不符 ⇒ 當場擋; `CLOUDFLARE_ACCOUNT_ID` 一律由登錄簿覆蓋,不吃環境裡飄來的值; `prod` 沒有 leo 的 `.github-armed` ⇒ preflight 就斷。 📌 **stage 驗證章由管線自己蓋**(`--target stage --confirm` 成功才寫 `/tmp/.stage-verified`)。 **不要用手 `touch`**——手蓋的章證明不了任何事,那正是這道閘想擋的東西。 ⚠️ 桌面線(打包 App/DMG/exe)**還沒併進管線**,仍照下面 §3、§3.5 手工做, 做完把產物放進 bundles repo 的 `daemon/`,再跑 `ship.mjs`(它會驗版本與 sha 對不對得上)。 --- ## 兩條版本線(先分清楚在講哪一條) | 線 | 版本號 | leo 從哪看 | 出貨鏈 | |---|---|---|---| | **雲端** | `manifest.release`(如 `1.4.11`) | **portal 設定頁的版本卡** | 重打 bundle → 推 bundles repo → release 自動 bump → 換安裝器釘子 → 部署安裝器 | | **桌面** | `manifest.daemon.version`(如 `v0.18.4`) | **daemon 的「檢查更新」** | 打包 App → 放進 bundles `daemon/` → 改 `manifest.daemon` → purge jsDelivr | ⚠️ 兩條**各自獨立**。改 kbdb 不會讓 daemon 版本動,反之亦然。 --- ## 步驟 ### 0. 先查記憶(別重蹈覆轍) ```bash grep -rn "版本號\|出貨\|release" system-dev/wiki/decisions-summary.md | head ``` 必讀 **D39「版本號由內容算出來,不由人宣告」**: - `MAJOR.MINOR` 在 `RELEASE_LINE` 檔(人只在大改版動) - **`PATCH` 由機器決定**:內容指紋一變 +1、沒變不動(重跑不虛增) - **真相源只有 `manifest.release` 一處**,installer/landing/portal 全是讀者 > ⇒ **「改了 code 但 release 沒 bump」= bundle 沒重打包**,不是版本機制壞了。 ### 1. 判斷這次改動影響哪條線 ```bash git -C matrix/arcrun log --oneline -5 # 雲端 worker(cypher/kbdb/portal…) git -C products/arcrun-rag log --oneline -5 # daemon/安裝器/workflow ``` - 動到 `matrix/arcrun` 的 worker 或 `console-ui/public/portal/` → **雲端線** - 動到 `collector/`(含 `cmd/arcrun-app/`) → **桌面線** - 動到 `workflows/*.yaml` → **要重編 workflows.json**(見步驟 2.5) ### 2. 雲端線:重打 bundle(**這步最常漏**) ```bash cd products/arcrun-rag ARCRUN_REPO_ROOT=../../matrix/arcrun node installer/scripts/build-bundles.mjs --out / ``` **複驗你的改動真的進去了**(不要只看腳本說成功): ```bash grep -c "<你改的關鍵字>" /tier2//index.js # 例:改 embed 模型 → grep -c "bge-m3" 應 > 0,且舊模型應為 0 ``` > 🔴 實撞(2026-08-05):我改了 kbdb 的 embed 模型並 `wrangler deploy` 到 youlin, > 但**沒重打 bundle** ⇒ bundles repo 裡仍是 `bge-base-en` > ⇒ **新用戶安裝/既有用戶重裝都拿到舊的**,而 leo 看到的 release 仍是 1.4.11。 ### ⛔ 2.9 bundle 沒重打之前,**要主動叫 leo 別按「立即更新」** **這是最危險的狀態**,比「沒出貨」更糟: - 你直接 `wrangler deploy` 到某台實例 ⇒ **那台**是新的 - 但 bundle 還是舊的 ⇒ leo 按 portal 的「**立即更新**」=**從舊 bundle 重裝** - ⇒ **他的實例會被你剛修好的東西「降級」回舊版** 實例(2026-08-05,leo 說「我要去更新實例驗證」時攔下): kbdb 已直推 `bge-m3`(1024 維)+已建 1024 維 Vectorize index, 但 bundle 裡仍是 `bge-base-en`(768) ⇒ 一按更新,kbdb 退回舊模型, **與 1024 維 index 對不上 ⇒ 中文語意搜尋直接壞掉**。 📌 **判準**:只要你「直推過實例」但「還沒重打 bundle」, **主動說一句「先別按立即更新,會裝回舊的」**——不要等 leo 自己踩到。 兩者狀態不一致的期間,**降級風險是你造成的,說清楚是你的責任**。 ### 🔁 2.7 動到安裝/更新/認證的話:**stage 要走三段,不准只測 update** (leo 2026-08-14 立,全文見頂層 `decisions-summary.md` **D84 之二**) > leo 原話:「**以後你的 stage 測試除了測 update 還要用 uninstaller 刪除後再模擬第一次安裝。**」 ``` ① 測 update(既有 stage 實例) ② 用 uninstaller 拆掉 ③ 從乾淨狀態模擬第一次安裝 ``` 🔴 **只做 ① 不算測過。** stage 實例是長期存在的,它身上有所有舊 binding 與舊 secret ⇒ **它天生就是「既有實例」,天生看不到全新用戶會撞的坑。** **這條的代價是實際發生過的**(2026-08-14 一天三個坑,全是只驗 update 會漏掉的): - 安裝器從沒種過 `CF_SECRETS_API_TOKEN` ⇒ **全新用戶建不出第一個帳號**(`Arcrun#119`) - 中心 KV 說「你裝過了」而帳號上什麼都沒有 ⇒ **重裝死結**(`Arcrun#120`) - 撞牆訊息叫用戶去跑一件做不到的事(`Arcrun#121`) **每一個都在既有實例上測不出來**,因為既有實例會沿用舊資源把缺口蓋住。 ⚠️ **uninstaller 還沒做出來之前**,這條走不完 ⇒ 出貨時要**明講「③ 沒驗」**, 不准因為「工具還沒有」就跳過不提。 ### 2.5 改過 workflow 的話 ```bash CYPHER_BASE=<實例 cypher URL> CYPHER_NS= \ node installer/scripts/compile-workflows.mjs ``` ⚠️ **flow 變了就不能沿用舊的預編圖**,腳本會 `exit 1` 擋住(這個閘是對的)。 不帶 `CYPHER_BASE` 就跑=直接失敗,別繞過它。 ### 3. 桌面線:打包 App ```bash cd products/arcrun-rag/collector/cmd/arcrun-app VERSION=vX.Y.Z bash build-mac.sh && bash build-dmg.sh # Mac VERSION=vX.Y.Z bash build-win.sh # Windows(Mac 上可交叉編譯) ``` **三支機械閘必須全過**(交貨前): ```bash for s in check-cis.sh check-render.sh check-tray.sh; do bash "$s" || echo "❌ $s"; done ``` **實跑驗證產物**(不是看檔案存在):Mac 用 `open` 真的跑一次; 或用同綑的 collector 跑 `direct --once` 確認你的修復在裡面。 #### 3.5 🔴 **把 DMG/zip 真的打開,用「使用者第一次看到的樣子」檢查** > leo 2026-08-05 連問三次才問出來的東西——**列步驟不算驗,要真的開起來看**。 > 「我問你 Mac 打包是不是一個 folder 打開可以把 dmg 拖到 application 去?**你檢查了嗎?**」 ```bash # Mac:掛載 DMG,看使用者會看到什麼 hdiutil attach dist/Arcrun-vX.Y.Z.dmg -nobrowse -quiet -mountpoint /tmp/dmgchk ls /tmp/dmgchk/ # 應**剛好兩項**:Arcrun.app + Applications 捷徑 ls /tmp/dmgchk/Arcrun.app/Contents/MacOS/ # ⚠️ 2026-08-14 訂正:現在是**單一二進位**,只有 arcrun-app /usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" \ /tmp/dmgchk/Arcrun.app/Contents/Info.plist # ← 必須是本次版本,**不是 1.0.0** /usr/libexec/PlistBuddy -c "Print :LSUIElement" \ /tmp/dmgchk/Arcrun.app/Contents/Info.plist # 應為 true(不佔 Dock) codesign -v /tmp/dmgchk/Arcrun.app # 無輸出=簽章有效 hdiutil detach /tmp/dmgchk -quiet # Windows:解開 zip 看內容 unzip -l dist/ArcrunRAG-win-unsigned-vX.Y.Z.zip # 應有 Arcrun.exe + arcrun-collector.exe ``` **逐項判準**: | 檢查 | 為什麼 | 漏掉的後果 | |---|---|---| | DMG 內剛好兩項 | 這就是「拖進 Applications」的標準畫面 | 沒有捷徑 ⇒ 使用者在下載資料夾直接開 ⇒ **更新會蓋錯位置**(t184) | | ~~`arcrun-collector` 同綑~~ **已過期(2026-08-14 訂正)** | 以前 daemon 是獨立二進位;**現在 collector 內建在 `arcrun-app` 裡**(v0.18.27 實測:`strings` 抓得到 `LoadDirectConfig`/`saveDirectConfig`,direct 模式符號 18 處) | 🔴 **照舊版檢查會發出「你的 App 缺件」的假警報**——總管 08-14 差點對 leo 發出來。要驗「同步能力在不在」改用 `strings` 抓 direct 模式符號 | | `CFBundleShortVersionString` | Finder/「關於」顯示的版本 | **永遠顯示 1.0.0** ⇒ 用戶無從判斷自己是不是新版 | | `LSUIElement=true` | 常駐小工具不該佔 Dock | 行為與設計不符 | | `codesign -v` | 改過 bundle 內容必重簽 | launchd 拒開 | > 🔴 實撞(2026-08-05):`CFBundleShortVersionString` 一直是 **`1.0.0`**—— > `wails.json` 沒有 `info.productVersion`,而 `wails build` **沒有 CLI 旗標**可指定。 > 解法已寫進 `build-mac.sh`/`build-win.sh`:建置前把版本寫進 `wails.json` 的 `info`、 > 建完用 `trap` 還原(不污染版控)。 ### 4. 寫 changelog(**leo 點名要的**) `products/arcrun-rag/docs-site/src/content/docs/help/changelog.md` 格式照既有的(**寫給用戶看,不是寫 commit message**): ```markdown ## 📋 出貨對照表:**改了什麼 → 要動什麼**(leo 2026-08-08:「寫清楚不要再改錯」) > 這張表是**查的**,不是用判斷的。08-08 一整天的錯全是「憑印象決定要重打什麼」。 > **每次出貨逐列對,有碰到就一定要做。** | 你改了什麼 | 要重打/重部署 | 版本線 | 要一起改的文件 | 最後落在哪 | |---|---|---|---|---| | **daemon Go 程式** | 三平台打包(mac/win/msix) | **daemon 版本線** | `changelog.md` **必寫** | 用戶自己的電腦 | | **portal/console-ui 前端** | `build-ui-bundle.mjs` | **雲端 release** | 有畫面變化就要更 docs | 每個實例的 `arcrun-rag-ui` | | **cypher / kbdb / registry / mcp** | `build-bundles.mjs` | **雲端 release** | — | 每個實例的對應 worker | | **安裝器 `worker.js`** | 部署安裝器(帶 `--config`) | 安裝器自己 | — | `install.arcrun.dev` | | **landing** | 部署 landing | — | — | `rag.arcrun.dev` | | **說明文件** | `npm run build` → **`dist`→`deploy/docs`** → 部署 | — | — | `rag.arcrun.dev/docs` | | **錯誤分類/新的失敗原因** | — | — | 🔴 **FAQ 一定要同步** | docs 站 | 🔴 **兩個最常漏的(都有 08-08 實據)**: - `build-bundles.mjs` **不會重建 `arcrun-rag-ui`**(它在「沿用非本腳本產」清單裡) ⇒ 只跑一支=**靜默出半套**。portal 文案傳不出去整整一天就是這個。 - docs 的 `dist → deploy/docs` **沒有腳本**,漏了它 `wrangler deploy` 會「成功」但只上傳 0.35 KiB。 ### 版本修改內容(`changelog.md`)怎麼寫 `changelog.md` 是**版本號的單一真相源**,也是使用者在「版本與更新」畫面看到的內容。 - **一行一句,講「你會看到什麼不一樣」**,不是 commit 訊息翻譯 - 🔴 **要短。細節去 docs 讀**(leo 08-08 看到 v0.18.24 的更新說明是一整面文字牆: 「**不要這麼長的散文,簡短講改了什麼,細節去 docs 讀**」) - **不要帶 markdown 粗體**——那個欄位是純文字,`**` 會原樣顯示給使用者看 - 沒有對應版本的段落就**不准打包**(`changelog-section.sh --check` 會擋) ### FAQ 什麼時候一定要動 - **新增/改變任何「使用者會看到的失敗原因分類」** ⇒ FAQ 必須同步 (分類收斂成有限幾類,畫面只出分類與份數,**細節全在 FAQ**——見 t214) - **改了安裝或更新的做法** ⇒ 對應的說明頁必須同步 (08-08 實據:Windows 說明還寫「解壓縮 zip、裡面兩個 exe」,而我們早就只出單一 `.exe`) 📌 **判準:使用者會因為這次改動而問出新問題嗎?會 → FAQ 要有答案。** ### 什麼地方 → 修哪個實例(別再搞混) > 🔴 **AI 的工作迴圈只有兩件事**(leo 2026-08-08 原話): > 「**實際上你是在 uncle6 修改 bundle,然後你自己在 youlin stage 安裝新版 bundle。**」 > ⇒ **改 staging bundle(uncle6)→ 裝進 youlin 的 stage 環境 → 自己測**。這一圈可以無限重複。 > **「uncle6 不碰」指的是 prod 那一側,不是 staging bundle**——別把自己的工作區也擋掉。 | 這個東西 | 住在誰的帳號 | AI 能不能動 | |---|---|---| | **staging bundle repo** | uncle6(Gitea) | ✅ **這是 AI 的工作區**,隨便改隨便推 | | **youlin 的 stage 環境** | youlin | ✅ **AI 的測試場**,隨便裝、壞了自己修 | | **prod bundle repo** | uncle6(GitHub) | 🔴 要 leo arm,且只准「**原封提升**」不准重打 | | **prod 的安裝器/landing/docs 部署** | uncle6 | 🔴 部署=出貨動作,要 leo 開閘 | | **youlin 的 prod 環境** | youlin | 👤 leo 用來驗「用戶裝 prod 拿到什麼」 | | **geek6688(出貨機/第一個顧客)** | geek6688 | 🔴 **不准拿它當測試場**;但**發佈後總管要手動把它更新到最新**——見 `ops-facts.md` §出貨線。**出貨機比貨舊是錯的**(08-12 實撞:停在 1.4.29、落後 12 版) | | `rag-demo.arcrun.dev` | uncle6 | 🪦 **已退場**,任何文件/腳本不得再導向 | 📌 **一句話界線**:**staging 側全是我的、prod 側全是 leo 的閘。** ### 🖥️ 三台機器各自幹嘛(leo 2026-08-12 定義,08-14 再次口頭確認) > **youlin =測試機|geek6688 =出貨機|uncle6 =總部機** - **youlin =測試場**:**它就是拿來拆的**。裝壞了、要清空重來,**不必問、不必猶豫** ——把它當成珍貴資產而不敢動,等於讓「只有全新用戶會撞的坑」永遠測不到(D84 之二)。 唯一要守的是**時序**:確認沒有別條線正踩在上面。 - **geek6688 =出貨機/第一個顧客**:**不當測試場**,但**發佈後要更新到最新**(總管手動)。 - **uncle6 =總部機**:bundle 的家。staging 側是總管的工作區,**prod 側是 leo 的閘**。 📌 **總管 2026-08-14 實錯**:leo 交辦拆 youlin 時,總管去問「有沒有資料是重裝也長不回來的」 ——**而這個答案 `ops-facts.md:29` 早就寫著了**。leo 當場點破:「**stage 不就是用來測的?**」 ⇒ **對測試機的謹慎不是美德,是把該做的驗證擋在門外。** ## 🗺️ 出貨流程(leo 2026-08-08 深夜定死,不得再自創路徑) > leo 原話:「**uncle6 的 bundle 要裝在 youlin 的 stage 環境,沒問題後才推 prod 改裝在 > youlin 的 prod 環境,所以 uncle6 的 bundle 要 2 套,youlin 也要兩套, > 就像把同樣程式碼打到不同分支一樣。**」 > 「**CF 可以這樣建立分離的 stage,不是在 prod 環境測 stage。**」 ### 矩陣(兩軸,四格,缺一格就會像 08-08 那樣炸) ``` 產物來源(uncle6) 安裝目標(youlin) stage → staging bundle repo → youlin 的 stage 環境 prod → prod bundle repo → youlin 的 prod 環境 ``` **同一份程式碼打到兩條線,像 git 的兩個分支——彼此不共用任何會互相污染的東西。** ### 六步,順序不准跳 ``` ① 改 code → commit + push gitea ② 打 bundle → 版本號由內容指紋算出;內容變了版本一定變 ⚠️ 要跑兩支:build-bundles.mjs + build-ui-bundle.mjs (前者不會重建 arcrun-rag-ui,漏跑=靜默出半套) ③ 推 staging bundle → Gitea(不碰 GitHub,無 D20 閘) ④ 裝進 youlin 的 stage 環境 → AI 自己測:登入、首頁、匯出診斷檔(瀏覽器實載) ⑤ 👤 **leo 去 youlin 的 stage 環境**,看修復版**是不是真的修好了** → 他說可以,跑 scripts/stage-ok.sh ⑥ 👤 leo 跑 scripts/github-arm.sh → **把 staging bundle「原封提升」成 prod bundle**(uncle6 內部:staging repo → prod repo) ⑦ 👤 leo 在 **youlin 的 prod 環境**裝一次新版 prod 內容 → 驗的是「**用戶從 prod 安裝器裝,會拿到什麼**」這條路本身 ⑧ 之後任何人從 prod 安裝器裝就拿到新版;leo 想在哪抽查都行(geek6688 或任何實例) ``` 🔴 **youlin 為什麼一定要兩套環境**(leo 2026-08-08:「**然後我可以在 youlin 測新版的 prod 內容安裝**」): - **stage 環境** → 驗「修復到底修好了沒」(發佈前的閘,第 ⑤ 步) - **prod 環境** → 驗「用戶裝 prod 會拿到什麼」(發佈後的第一手驗證,第 ⑦ 步) ⇒ 兩件事驗的東西不同,**不能共用同一套環境**—— 共用就會變成 2026-08-08 那樣:為了測而動到正在用的那套,把要驗的東西弄壞。 🔴 **⑥ 是「提升」不是「重打」**(leo 2026-08-08 原話: 「**arm 推的是 uncle6 把 stage 的 bundle 推到 prod 的 bundle**」) ⇒ **prod 的產物必須與 stage 上被驗過的那一份逐位元相同**。 一旦重跑一次 build 才推 prod,**stage 驗過的東西就不是 prod 上的東西**, ⑤ 那一關的意義當場歸零。 ⇒ 驗收方式:提升後比對兩邊的**指紋/sha**,不同就是做錯了。 這正是 leo 的比喻「**就像把同樣程式碼打到不同分支一樣**」——搬,不是再做一次。 🔴 **stage 永遠在 youlin,這是固定的**(leo 2026-08-08 原話: 「**5 是我去 youlin stage 環境看你的修復版是否真的修復,就可以發佈,發佈以後, 我可以在任意地方試 prod,但 stage 一定在 youlin。**」) ⇒ **核實點固定在 stage**,不是「找另一台乾淨的機器來驗」。 prod 發佈後在哪裡試都行,那是**發佈後的抽查**,不是發佈前的閘。 **兩把鑰匙都在 leo 手上(⑤⑥),AI 造不出來,這是刻意的。** ### CF 官方機制(查證出處,不是憑記憶) `https://developers.cloudflare.com/workers/wrangler/environments/` - 用 `[env.NAME]` 宣告環境;部署 `npx wrangler deploy --env NAME` - **Worker 名字自動變成 `-`**(例:`arcrun-cypher-executor-stage`) - 🔴 官方原文:**「Non-inheritable keys are configurable at the top-level, but cannot be inherited by environments and must be specified for each environment.」** ⇒ **KV/D1/vars 一律不繼承,每個環境必須各自宣告**——這正是環境真正分離的地方, 也是「漏宣告就會共用到 prod 資源」的風險點。 ### 🔴 為什麼要把流程寫死(08-08 一整天的代價) 那天出貨是**用手拼的**:手動 cp 產物 → 手動改 manifest → 手動 sed 換兩處釘子 → 手動 deploy。 後果四連發,全部有實據: - 改了 portal 文案,**bundle 版本沒動** ⇒ 改動永遠送不出去 - 版本沒動 ⇒ 安裝器判「同版整批跳過」⇒ **重裝也修不好**(leo 白重裝一次) - 兩台都宣稱 `1.4.22` **程式碼卻不同**(一台有診斷端點、一台 404) - 為了測 stage 而手動部署,**繞過安裝器注入** ⇒ leo 的 portal 畫面壞、登入斷 ⇒ **他晚上要驗收時,發現要驗的東西被驗收流程本身弄壞了** ⇒ leo:「**每次做一樣的事,結果會打錯實例⋯⋯寫對的應該每次都機械式的做同一件事, 寫錯位置也會被它修正。**」**這條流程就是那個「機械式」的定義,不准再自創。** ## 🚦 第 0 步:**先上 stage,不准直達 prod**(leo 2026-08-08 立,封測期起) > leo:「現在因為**開始封測**,不直接打到 prod,而是先打到昨天建的 stage⋯⋯ > 因為**推 prod 就發佈了**,雖然現在人不多,但**要謹慎**。」 🔴 **這一段是補進來的,因為本 skill 原本從頭到尾寫的是 prod** (github-arm → GitHub `arcrun-rag-bundles` → jsDelivr/raw 驗證), **沒有任何一步是「先上 stage 驗過再上 prod」**;而 D20 那道閘擋的是「寫 GitHub」, 不是「未經 stage 就發佈」⇒ 光「知道有 stage」對行為零作用。 順序(**不准跳**): 1. 打 **staging** bundle → 推 Gitea `arcrun-rag-bundles-staging` (不碰 GitHub,**無 D20 閘**,不需要 leo) 2. 用 staging 安裝器實裝到測試實例 `https://arcrun-rag-installer-staging.uncle6-me.workers.dev` 3. 走一次**封測者真的會走的那條路**,貼實測輸出(不是 HTTP 200) 4. 過了才做下面的 prod 步驟 ⚠️ **身分**:leo 2026-07-25 令「測試一律用 youlin,別拿 leo21c 當探針(會製造假信號)」 ⇒ 部署前先 `acr whoami`。 ⚠️ **stage 與 prod 內容範圍不一樣**(daemon 安裝檔、README、core 顆數) ⇒ 見下面「別再整包蓋」那段,**不要拿 staging manifest 整包覆蓋 prod**。 📌 stage 判準(leo 08-07):stage **不需要 custom domain**,`*.workers.dev` 就好—— 「僅預設網域不准標 ✅」的目的是「用戶拿不到=沒交付」,而 stage 的用戶就是 leo 與總管。 不掛 custom domain 反而是優點(沒人會誤入、不被索引、不會被當正式網址傳出去)。 🔧 機械閘:`.claude/hooks/stage-before-prod-guard.sh` (6 小時內沒 stage 驗證紀錄 ⇒ 擋掉 prod bundle repo/github-arm/publish-github)。 📖 stage 救過一次的實例:見頂層 `system-dev/wiki/status.md` 「同一次測試照出一個會炸掉所有用戶的回歸(stage 第一次真的救了我們)」。 ## vX.Y.Z(YYYY-MM-DD) **建議更新**——一句話說「這版解決你什麼問題」。 - 🔴 **最重要那項**:用戶語言描述症狀與結果 - 其他項… ``` 判準:**用戶讀得懂「這對我有什麼差別」**。 不要寫「修 t195 的 401」,要寫「修好『一個檔失敗就卡住整個資料夾』」。 ### 5. 出貨(**需 leo 開 D20 閘**) ```bash # leo 親跑(AI 不得代跑) scripts/github-arm.sh "出貨 <說明>" 30 ``` 開閘後: ```bash cd products/arcrun-rag node installer/scripts/ship.mjs --bundles # 先 dry-run 看待辦 node installer/scripts/ship.mjs --bundles --confirm # 真出貨 ``` `ship.mjs` 會依序做:推 bundle → 換安裝器釘子 → 部署安裝器/landing → **purge jsDelivr**。 ⚠️ **purge 一次可能不夠**,要驗到收斂(腳本自己會重試,別提前宣稱完成)。 ### 5.5 說明文件站(**2026-08-08 補:它不在任何腳本裡,最容易整批過期**) 🔴 **`ship.mjs` 完全不管 docs**(`grep docs` = 0 命中)。docs 是獨立的 `arcrun-docs` worker, 沒人手動部署它就會**一直停在上一次**——leo 2026-08-08 抓到:Windows 安裝說明還寫著 「解壓縮 zip、裡面有兩個 exe」和「MSIX 要開開發人員模式」,而**我們早就只出單一 `.exe`**。 ```bash cd docs-site npm run build # → dist/ rm -rf deploy && mkdir -p deploy/docs && cp -R dist/. deploy/docs/ # ⚠️ 見下 npx wrangler deploy ``` ⚠️ **`dist → deploy/docs` 這一步沒有腳本,只活在人的記憶裡**(08-08 實撞): `wrangler.toml` 的 `[assets] directory = "./deploy"`,而 Astro 建到 `dist`。 **漏了它,`wrangler deploy` 會「成功」但只上傳 0.35 KiB(等於什麼都沒換)。** **複驗要抓畫面內容,不是看部署訊息**: ```bash curl -s "https://rag.arcrun.dev/docs/start/install-windows/?cb=$RANDOM" \ | grep -oE "<這次該出現的字串>" ``` 📌 **判準:凡是改動會讓說明文件過期的出貨,docs 就是出貨的一部分。** 版本、安裝方式、畫面長相變了 ⇒ 一起改、一起部署、一起複驗。 ### 5.8 🔴 驗前端=**用瀏覽器真的載一次**,`curl | grep` 不算(leo 2026-08-08 立) > leo 原話:「**你的環境有 web,你應該用 web 驗,你已經開啓了卻沒有完成,你要把這個列入規定。**」 **為什麼 `curl | grep` 是假驗證**(08-08 實撞,leo 抓到而不是我發現): `curl` 拿到的是 **HTML 原始碼**——它**不執行 JS、不載入 `config.js`、不發 API 請求**。 所以我 grep 到文案就宣稱「前端驗過」,而使用者實際打開看到的是整條紅色錯誤: 「設定檔沒載入(config.js),這個頁面連不到你的服務」。**那個畫面我一次都沒看到。** ⇒ **判準升級**: `HTTP 200 不算驗過` → `grep 到字串也不算驗過` → **只有「瀏覽器載入後看起來能用」才算**。 ``` mcp__Claude_Browser__preview_start {url: "<用戶會走的網址>"} mcp__Claude_Browser__computer {action: "screenshot"} ← 看畫面,不是看原始碼 mcp__Claude_Browser__read_console_messages {onlyErrors: true} ← JS 有沒有炸 ``` **要看的是**:有沒有錯誤橫幅/該有的資料是不是還停在「載入中…」「查詢中…」/console 有沒有紅字。 ⚠️ **順帶一個會騙人的坑(同日實撞)**:`curl` 加 `?cb=$RANDOM` **繞不掉 Cloudflare 邊緣快取**—— `/config.js` 一直回舊的空值,直到加 `-H "Cache-Control: no-cache"` 才看到真值。 ⇒ 用 curl 查線上狀態時,**沒加 no-cache 就可能是在驗快取,不是在驗線上**。 ### 6. 複驗:**從 leo 會看的那兩個地方** ```bash # ① 雲端線:portal 版本卡讀的是安裝器 /api/latest curl -s https://install.arcrun.dev/api/latest # → "release" 應等於 manifest.release # ② 桌面線:daemon「檢查更新」讀的是 jsDelivr(**不是 raw**) curl -s "https://cdn.jsdelivr.net/gh/youlinhsieh/arcrun-rag-bundles@main/manifest.json" \ | python3 -c "import json,sys; print(json.load(sys.stdin)['daemon']['version'])" # ③ 用戶下載的固定檔名真的抓得到、且 sha 與 manifest 相符 curl -sI "https://raw.githubusercontent.com/youlinhsieh/arcrun-rag-bundles/main/daemon/ArcrunRAG-mac.dmg" ``` 🔴 **`raw` 是新的不代表 `jsDelivr` 是新的**——daemon 讀 jsDelivr, 2026-08-05 實撞:push 完 raw 立刻新版、jsDelivr 仍吐 v0.15.7,purge 後第 2 次才收斂。 --- ## 收工前自問(答不出來就是還沒做完) 1. **leo 打開 portal,版本卡會顯示新號碼嗎?** 不會 → 雲端線沒出貨完 2. **leo 按 daemon「檢查更新」,會看到新版嗎?** 不會 → 桌面線沒出貨完 3. **新用戶現在安裝,拿到的是我改的那份嗎?** 不確定 → 回步驟 2 複驗 bundle 內容 4. **changelog 有這一版嗎?** 沒有 → 用戶不知道你改了什麼 5. 以上任一是「否」→ **不准說「完成」,要說「已改,未送達」** --- ## 常見漏掉的(實撞記錄) | 漏掉的 | 後果 | 日期 | |---|---|---| | 沒重打 bundle | release 不 bump、新用戶拿舊版 | 08-05 | | 沒 purge jsDelivr | 用戶按「檢查更新」像沒反應 | 08-02 | | 沒改 `manifest.daemon` | 「檢查更新」回「manifest 沒有 daemon 版本欄位」全體失效 | 08-02 | | 只推 bundle 沒換釘子 | 安裝器仍裝舊 bundle | 08-01 | | 🔴 **釘子只改了常數,沒改 `[vars]`** | **部署成功但釘子沒換**——`api/latest` 與 install 頁全是舊版,白部署一次 | 08-07 | | 用 `rsync --delete` 覆蓋 prod bundle repo | **砍掉 prod 才有的 `daemon/`(20 個安裝檔)與 `README.md`**,封測者下載不到 daemon | 08-07(推之前 `git status` 抓到) | | 用 staging 的 manifest 整包蓋 prod | **core 從 5 顆變 24 顆**——prod 只裝 5 顆是懶載設計,會讓每個新用戶多裝 19 顆 worker | 08-07(同一個坑 `bbb433e` 有前科) | | 用 `git worktree` 打 bundle | `0/4 bundled`——worktree 沒有 `.component-builds/` 的預編 wasm(gitignore 產物) | 08-07 | ### 🔴 換釘子:真身是 `[vars]`,不是常數(08-07 實撞,白部署一次) ```js // installer/oauth-prototype/worker.js:74 bundleBase(env) = env.BUNDLE_BASE ? env.BUNDLE_BASE : DEFAULT_BUNDLE_BASE ``` ⇒ **`wrangler.toml` 的 `[vars] BUNDLE_BASE` 優先於 `worker.js` 的常數。** - **兩處都要改**:`wrangler.toml` 的 `[vars] BUNDLE_BASE`+`BUNDLE_BUILT`、 以及 `worker.js` 的 `DEFAULT_BUNDLE_BASE` - **為什麼會漏**:08-07 把「指向」從常數搬進設定(為了 stage/prod 用同一套機制), **但記載該機制的文件沒跟著改** ⇒ 照舊文件做 = 改了一半 = 等於沒改 - 🔑 **可推廣**:**改了機制,就要改記載那個機制的文件**—— 否則下一個人(包括你自己)照過期文件做,會得到「做完了但沒生效」 ### ✅ 覆蓋 prod bundle repo 的正確動作(08-07 定,別再整包蓋) ```bash git reset --hard HEAD && git clean -fd # 先回到 prod 原狀 cp -R //. / # 逐顆覆蓋,不用 rsync --delete # manifest:只換 prod core 清單裡「原本就有」的那幾顆 + release/built/source git add -A git diff --cached --diff-filter=D --name-only # ← 護欄:刪除檔案數必須是 0 ``` **判準:staging 與 prod 的內容範圍不一樣(daemon 安裝檔、README、core 顆數),不能整包蓋。** | changelog 沒寫 | 用戶不知道能不能/要不要更新 | 08-05(停在 v0.15.7,v0.18.x 全空) | | portal 下載連結沒跟著換 | DMG 打好了但按鈕還給 zip | 08-05 |