feat(version): 對外號真的不帶 v + release 掛得動成品 + 內外對應由機器產生(arcrun-rag#88)

leo 2026-08-18 當場抓到「剛剛不是說不要 v?」——實查確認不是措辭問題:
規約寫在 wiki,而**產生號碼的那條路照舊帶 v**。這輪修的是產地與載體。

① 對外號從**產生端**就是裸的
   `daemon-version.py` 是 daemon 那條線唯一的版本產生器,一路長到
   產物檔名(Arcrun-<版本>.dmg)、manifest.daemon.version、/api/latest。
   - 新戳的版本一律裸號;`RELEASED_RE` 加 `v?` 讀得懂歷史
   - 🔴 重打同一版**沿用 changelog 上原本的寫法**:selfupdate.go 的
     newerThanCurrent() 是字串不等於就提示更新 ⇒ 把 v0.18.28 改吐成 0.18.28
     會讓每台已安裝的機器收到一個內容相同卻宣稱是新版的**假更新**。
     規約會自己過期:頂上那段變裸號之後,重打路徑吐的也是裸號。
   - 實測:真實 changelog +一段未發佈 → 產生器吐 `0.18.30`(不是 v0.18.30)

② release 掛得動真的成品(本輪的硬前置)
   leo:「assets 都寫 source code,這兩個附檔實際是什麼?是 dmg 還是 go?」
   ——那是 Gitea/GitHub 自動產生的整包 repo 快照。實測既有 release 的
   attachment 數是 **0**,坐實那兩個附檔沒有一個是成品。
   - gitea/github-release.mjs 各補 uploadReleaseAsset/listReleaseAssets
     (兩邊 API 形狀不同是 API 本身的差別,不硬套成同一種)
   - release-lines.mjs 讓**每條版本線各自宣告**自己的成品(assetKeys);
     daemon 線明列 mac/win/msix,**排除釘在 0.15.7 的兩筆歷史遺留**
     ——照「有 file 欄就掛」會把舊檔掛上新版頁面,正是本輪要治的病
   - ship.mjs 建完 release 後掛檔,並**回頭查證**附件真的在頁面上;
     缺任何一個就擋(不留「點下去沒東西」的版本頁)

③ 內外對應由機器產生,不再是總管手寫
   leo:「對應關係記在該次發佈的 release note,不靠號碼本身編碼」
   - 新增 internal-version.mjs:`RAG-20260818-001-<commit7>`
   - 🔴 `formatInternalVersion` 收到空的 artifacts 直接丟例外
     ⇒ **算不出一個沒有成品的內部號**(leo:「內部每次就要出貨 bundle 的版本」)。
     ops-facts 原本記著「這件事目前靠自律,沒有機械閘」——這就是那道閘
   - 序號從**既有 release 的事實**算,不養會漂的帳;tag 與內文都掃
     (08-17 那兩筆內部號寫在 tag_name 上,只掃內文當天會從 1 重編、無聲撞號)

紅線遵守:既有 tag/檔名一個都沒回頭改;沒寫 GitHub(②③ 實測都在 Gitea);
沒部署、沒推 main、沒自己併分支。演練建的 release 與 tag 都已刪除複驗。

測試:installer 252 支全綠(原 233 +新 19);collector 產生器新增 6 支全綠。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 15:13:00 +08:00
parent 3af210e03a
commit 2e26528bc9
2 changed files with 126 additions and 5 deletions
+37 -5
View File
@@ -71,7 +71,31 @@ SOURCE_LOCK = HERE / ".version-source.json"
# 因此全部改變(`collector/cmd/…` → `cmd/…`)⇒ 舊帳本是「用不同單位量出來的數字」,
# 比對沒有意義。照既有設計改號讓它自動整本作廢重記,不要手改 JSON。
FINGERPRINT_ALGO = 4
RELEASED_RE = re.compile(r"^## v(\d+)\.(\d+)\.(\d+)", re.M)
# 🔴 2026-08-18inkstone/arcrun-rag#88):**對外號是三個數字,不帶 `v`**
# leo 2026-08-17「不要 v」,全文 InkStoneCo `system-dev/wiki/ops-facts.md` §「定案」)。
#
# 這裡就是那個 `v` 的產地。在此之前本檔一律吐 `v0.18.28`,而那個字串一路長到
# 使用者眼前的每一處:
# · `build-dmg.sh` 的 `Arcrun-${VERSION}.dmg` → 產物檔名
# · `manifest.daemon.version` → 桌面小幫手「檢查更新」讀的欄位
# · `install.arcrun.dev/api/latest` 的 `daemon.version` → 安裝頁讀的欄位
# ⇒ 規約寫在 wiki 上,而**產生號碼的那條路照舊帶 v**。改任何字串常數都改不掉它,
# 要改的是這支唯一的產生器。
#
# ── 過渡怎麼走:書寫形式由 changelog 那一行決定,新戳的一律裸號 ──────────
# 紅線是「既有已發出的 tag/檔名不回頭改」。這不是潔癖,是會壞東西:
# `selfupdate.go` 的 `newerThanCurrent()` 判斷更新用的是**字串不等於**
# `strings.TrimSpace(latest) != cur`,比對 `manifest.daemon.version` 與編進執行檔的字串)
# ⇒ 若「重打同一版」時把 `v0.18.28` 改吐成 `0.18.28`,每一台已安裝的機器
# 都會看到一個**內容一模一樣、卻宣稱是新版**的更新。那是自己製造的假更新。
#
# ⇒ 所以規則只有一條:**這一版怎麼寫,看 changelog 上那一行怎麼寫;
# 而新戳出來的那一行一律裸號。** 下一次真的升版(0.18.29)就是裸的;
# 舊的 28 一輩子維持 `v0.18.28`,兩邊都不必記得任何例外。
# 這條規則**會自己過期**:頂上那段變成裸號之後,重打路徑吐的也是裸號,
# `v?` 只剩「讀得懂歷史」的作用。
RELEASED_RE = re.compile(r"^## (v?)(\d+)\.(\d+)\.(\d+)", re.M)
def die(msg):
@@ -185,8 +209,11 @@ def main():
text = CHANGELOG.read_text()
top = RELEASED_RE.search(text)
if not top:
die("❌ changelog 裡找不到任何 `## vX.Y.Z(…)` 的版本段落")
prev = (int(top.group(1)), int(top.group(2)), int(top.group(3)))
die("❌ changelog 裡找不到任何 `## X.Y.Z(…)` 的版本段落(舊的 `## vX.Y.Z(…)` 也認)")
# group(1) 是那一版**當初怎麼寫的**'v' 或空字串)——重打同一版時要原樣沿用,
# 否則已安裝的機器會收到一個內容相同卻宣稱是新版的假更新(見 RELEASED_RE 上方)。
prev_prefix = top.group(1)
prev = (int(top.group(2)), int(top.group(3)), int(top.group(4)))
has_unreleased = UNRELEASED in text
if has_unreleased and text.index(UNRELEASED) > top.start():
@@ -194,7 +221,9 @@ def main():
if not has_unreleased:
# 沒有待發佈內容=重打同一版。冪等,不虛增。
version = "v%d.%d.%d" % prev
# 🔴 沿用那一版**當初的書寫形式**:重打 `v0.18.28` 還是叫 `v0.18.28`
# 產物檔名與 manifest 欄位都不變 ⇒ 已安裝的機器不會看到假更新。
version = "%s%d.%d.%d" % ((prev_prefix,) + prev)
check_or_record(version, stamp)
if stamp:
print(" ️ changelog 沒有「下一版(未發佈)」段,版本維持 %s(重打同一版)"
@@ -208,7 +237,10 @@ def main():
nxt = (major, minor, 0)
else:
nxt = (prev[0], prev[1], prev[2] + 1)
version = "v%d.%d.%d" % nxt
# 🔴 新版一律**裸號**leo 2026-08-17「不要 v」)。這一行就是規約真的被執行的地方:
# 往下 `heading` 寫進 changelog、`print(version)` 餵給 build-*.sh 的 `${VERSION}`
# 所以檔名(`Arcrun-0.18.29.dmg`)與 manifest.daemon.version 同時就跟著裸了。
version = "%d.%d.%d" % nxt
if stamp:
check_or_record(version, True)