diff --git a/cmd/arcrun-app/daemon-version.py b/cmd/arcrun-app/daemon-version.py index 1c1b745..ab6f5ba 100755 --- a/cmd/arcrun-app/daemon-version.py +++ b/cmd/arcrun-app/daemon-version.py @@ -237,10 +237,28 @@ def main(): nxt = (major, minor, 0) else: nxt = (prev[0], prev[1], prev[2] + 1) - # 🔴 新版一律**裸號**(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 + # 🔴 這裡**還是吐 `v`**,而 leo 2026-08-17 要的是「不要 v」。這不是漏掉,是**擋住了**: + # + # 這一行往下走 `heading` 寫進 changelog、`print(version)` 餵給 build-*.sh 的 + # `${VERSION}` ⇒ 檔名(`Arcrun-v0.18.x.dmg`)與 `manifest.daemon.version` + # ⇒ `install.arcrun.dev/api/latest` 的 `daemon.version`。 + # **leo 看到並質問的那個 `v`,就是從這一行長出去的。** + # + # 2026-08-18 實際試著改成裸號,當場撞到:**那個 `v` 目前身兼「哪一條版本線」的 + # 判別器**。三支閘(daemon-freshness/daemon-in-bundle-gate/ship.mjs 的 + # daemon-sync)+`daemon-notes.changelogRelFor()` 都靠「有沒有 v」認出 + # 「這份 changelog 到底是不是 daemon 的」。一改裸號,daemon 的閘就開始把雲端那條 + # `## 1.4.47(…)` 當成 daemon 版本撈走 ⇒ `daemon-in-bundle-gate.test.mjs` + # 的 ①⑮⑯ 三支轉紅,而它們釘的正是 D95 第二輪剛修好的病 + # (出貨線指到錯的 changelog 卻**安靜放行**)。 + # + # ⇒ 要落到產生端,得**先給那三支閘一個替代判別器**(建議拿 `DAEMON_LINE` + # 宣告的線去比對,例 `^## v?0\.18\.\d+(`——同時擋掉「錯的檔」與「錯的線」, + # 且不依賴外觀)。那會動到 `topReleasedVersion()` 等對外簽章、要跨 installer/ + # 與 collector/ 一起改,與正在進行的 collector 拆 repo 相衝。 + # ⇒ 交接說明見 `inkstone/arcrun-rag#88`。**在那之前不要單獨把這一行改裸**, + # 單獨改的下場是「打包出新版執行檔,manifest 卻宣稱是舊版」——靜默、且 21 站全綠。 + version = "v%d.%d.%d" % nxt if stamp: check_or_record(version, True) diff --git a/cmd/arcrun-app/daemon-version.test.mjs b/cmd/arcrun-app/daemon-version.test.mjs index 166ac45..02783db 100644 --- a/cmd/arcrun-app/daemon-version.test.mjs +++ b/cmd/arcrun-app/daemon-version.test.mjs @@ -1,12 +1,21 @@ /** * daemon-version.test.mjs — 證明**產生端**吐的是裸號(inkstone/arcrun-rag#88) * - * 🔴 為什麼這支測試存在,而不是改一個字串常數就算: - * leo 2026-08-17 定「對外號就是三個數字,不要 v」,規約寫進了 - * InkStoneCo `system-dev/wiki/ops-facts.md`——然後 leo 隔天問「剛剛不是說不要 v?」, + * 🔴 **這支測試現在釘的是「還沒做到」,不是「做到了」**(誠實優先於好看)。 + * + * leo 2026-08-17 定「對外號就是三個數字,不要 v」;隔天他問「剛剛不是說不要 v?」, * 因為 `install.arcrun.dev/api/latest` 照舊回 `daemon.version = "v0.18.28"`。 - * ⇒ **規約只活在 wiki,產生號碼的那條路照舊帶 v。** - * 這支測試釘住的就是那條路:不是「常數長什麼樣」,是「**下一次產生版本號時它是裸的**」。 + * 那個 `v` 的產地就是本目錄的 `daemon-version.py`。 + * + * 2026-08-18 實際改成裸號,當場撞到:**那個 `v` 目前身兼「哪一條版本線」的判別器** + * (三支閘+`changelogRelFor()` 靠它認出「這份 changelog 是不是 daemon 的」)。 + * 一改裸號,daemon 的閘就把雲端那條 `## 1.4.47(…)` 當成 daemon 版本撈走 + * ⇒ `installer/scripts/daemon-in-bundle-gate.test.mjs` 的 ①⑮⑯ 轉紅, + * 而它們釘的正是 D95 第二輪剛修好的「指到錯的 changelog 卻安靜放行」。 + * + * ⇒ 所以本檔的測試**斷言的是現況(一律吐 `v`)**,並把「為什麼還不能改」釘成 + * 一支會說話的測試。等替代判別器就位(見 `daemon-version.py` 那段註解與 + * `inkstone/arcrun-rag#88`),把 ①②③ 的期望值改成裸號,這支就變成驗收。 * * 為什麼住在 `collector/` 而不是 `installer/scripts/`: * 被測的 `daemon-version.py` 是 collector 自己的版本產生器(D95 第一輪把它的三個輸入 @@ -49,41 +58,46 @@ function runProducer({ changelog, line = '0.18', stamp = false }) { } const UNRELEASED = '## 下一版(未發佈)\n\n- 修了一件事\n\n'; -const OLD_V = '## v0.18.28(2026-08-16)\n\n- 舊的那一版,當初是帶 v 寫的\n'; -const NEW_BARE = '## 0.18.29(2026-08-18)\n\n- 新的那一版,裸號\n'; +const OLD_V = '## v0.18.28(2026-08-16)\n\n- 舊的那一版\n'; -test('① 升版:下一個版本號是裸的(leo 2026-08-17「不要 v」)', () => { +test('① 升版:**目前**仍吐帶 v 的版本號(leo 要的裸號尚未落到產生端)', () => { const { version } = runProducer({ changelog: UNRELEASED + OLD_V }); - assert.equal(version, '0.18.29'); - assert.ok(!version.startsWith('v'), `產生端還在吐帶 v 的版本號:${version}`); + assert.equal(version, 'v0.18.29'); + // 🔴 這一行是給未來的人看的:改成裸號之前,先讀 daemon-version.py 裡 + // `version = "v%d.%d.%d" % nxt` 上方那段——單獨改這裡會讓 manifest 說謊。 }); -test('② 升版並戳章:寫進 changelog 的那一行也是裸的(下一輪讀到的就是裸號)', () => { +test('② 升版並戳章:寫進 changelog 的標題也帶 v(兩邊一致,不會自我矛盾)', () => { const { version, changelogAfter } = runProducer({ changelog: UNRELEASED + OLD_V, stamp: true }); - assert.equal(version, '0.18.29'); - assert.match(changelogAfter, /^## 0\.18\.29(\d{4}-\d{2}-\d{2})$/m); - assert.ok(!/^## v0\.18\.29(/m.test(changelogAfter), 'changelog 被戳成帶 v 的標題'); + assert.equal(version, 'v0.18.29'); + assert.match(changelogAfter, /^## v0\.18\.29(\d{4}-\d{2}-\d{2})$/m); }); -test('③ 換版本線:新線第一版也是裸的,不是只有 patch 遞增那條路被修', () => { +test('③ 換版本線:新線第一版(同樣帶 v)', () => { const { version } = runProducer({ changelog: UNRELEASED + OLD_V, line: '0.19' }); - assert.equal(version, '0.19.0'); + assert.equal(version, 'v0.19.0'); }); -test('④ 重打同一版:沿用當初的寫法,**不回頭改已發出的版本**(紅線)', () => { - // 病:若這裡吐 `0.18.28`,每一台裝著 v0.18.28 的機器都會看到一個內容一樣卻宣稱是新版的更新 - // ——selfupdate.go 的 newerThanCurrent() 是字串不等於就提示。 +test('④ 重打同一版:冪等,不虛增(原有行為,未動)', () => { const { version } = runProducer({ changelog: OLD_V }); assert.equal(version, 'v0.18.28'); }); -test('⑤ 過渡會自己過期:頂上那段已是裸號時,重打也吐裸號(不必記例外)', () => { - const { version } = runProducer({ changelog: NEW_BARE + OLD_V }); - assert.equal(version, '0.18.29'); +test('⑤ 🔴 判別器還在:daemon 的判斷式**不可以**撈到雲端那條線的版號', () => { + // 這支就是「為什麼還不能改裸號」的本體。`v` 目前是兩條版本線唯一的區分方式: + // installer/scripts/daemon-notes.mjs 的 DAEMON_RELEASED_RE 只認 `^## vX.Y.Z(`。 + // 放寬成 v? 之後,`## 1.4.47(…)` 會被 daemon 的閘當成 daemon 版本。 + const CLOUD = '## 1.4.47(2026-08-15)\n\n- 雲端那條線\n'; + const strict = /^## (v\d+\.\d+\.\d+)(/m; + const loose = /^## (v?\d+\.\d+\.\d+)(/m; + assert.equal(CLOUD.match(strict), null, '嚴格版正確地認不出雲端版號'); + assert.equal(CLOUD.match(loose)[1], '1.4.47', '放寬版會撈到雲端版號 ⇒ 這就是那三支閘轉紅的原因'); }); -test('⑥ 讀得懂歷史:整份 changelog 都是舊的帶 v 寫法時,仍算得出下一版(且裸)', () => { - const onlyOld = UNRELEASED + '## v0.18.28(2026-08-16)\n\n- a\n\n## v0.18.27(2026-08-15)\n\n- b\n'; - const { version } = runProducer({ changelog: onlyOld }); - assert.equal(version, '0.18.29'); +test('⑥ 產生端與那條判斷式對得上(兩份實作靠測試對齊,不靠註解)', () => { + const { version } = runProducer({ changelog: UNRELEASED + OLD_V, stamp: true }); + const { changelogAfter } = runProducer({ changelog: UNRELEASED + OLD_V, stamp: true }); + const strict = /^## (v\d+\.\d+\.\d+)(/m; + assert.equal(changelogAfter.match(strict)[1], version, + '產生端戳出來的標題,必須被 installer 那三支閘的判斷式讀得回同一個版本'); });