
第一天的標題是:我的退休試算上線一年,我檢查過,它「沒問題」?
那個引號現在可以回答了。
Python 實作 shadow/calc_fixed.py 從第十五天起就存在,經過後面十五天所有的尺。今天做的是把它接回受測物那支 HTML,原版不動,另存 v2。
| 缺陷 | 改法 | 依據 | 哪一天確認 |
|---|---|---|---|
| A 少算一個 (1+r) | 折現與提領改期初,第一期對應退休當年 | PRD-02/03 | Day 4 推導;Day 25 對帳關係在舊版上紅,比值精確等於 1.04 |
| A' 圖表夾 0 | 移除 Math.max(0, ...),圖表逐項等於真實餘額 |
PRD-03/12 | Day 26 同一條斷言在舊版第三年紅:0.00 對 −850 萬 |
| B 壽命倒置 | 年齡順序不成立即拋錯,表單層阻斷 | PRD-04 | Day 3 驗屍 |
| C 大筆支出無上界 | 限定退休至壽命區間,區間外不計入並明示 | PRD-05/11 | Day 3 驗屍;Day 13 界外位置會紅 |
| D 靜默補零 | parseNumber 不再 || 0,列出哪一欄不是數字 |
PRD-04 | Day 13 指出它在抽取邊界外 |
| 通膨基準年 | 勞保勞退改鎖各自的請領年齡 | PRD-10 | Day 9 起兩次裁決 |
每一項都不是今天才發現的。今天只是把三十天前就該做的事做掉。
表格裡有一列跟其他五列不一樣。 缺陷 D 的 parseNumber 在 HTML 第 215 行,第十三天就寫過它在影子模型的抽取邊界外:所以底下四件驗收,沒有一件看得到它。差分比的是計算核心,快照、golden、關係也都是。v2 改了那一行,但它的驗證只有手動:開瀏覽器、把欄位清空、看有沒有跳出「以下欄位不是數字」。這一列的「修好了」,證據等級跟其他五列不同,照實標。
年報酬率直接除以 12 當月利率。Day 3 就寫了:這不是缺陷,是模型假設的簡化,實測讓累積資產多算六十一萬。Day 9 閘門一裁決保留,理由與代價都在紀錄裡。
這一篇不推翻那個裁決。 變異體目錄裡有一個 M03,方向是「把 /12 改成更正確的有效月利率」,而測試殺得掉它,代表測試已經把這個簡化鎖成規格了。要改,得回閘門一重新裁決,不是在修復日順手改掉。
移植本身也是程式,會錯。第一幕就是這樣開始的。
四件事同時成立才准部署:
一、舊快照紅在預期處。 第七天鎖舊版行為的那套測試,拿去對修復版跑:
27 failed in 0.14s
二十三組快照全紅,四條鎖缺陷的測試全紅。二十三組裡二十一組紅的原因是缺陷 A:折現改成期初,每一組的目標金額都變了。另外兩組是壽命倒置的案例,修復版直接拋錯——那是缺陷 B。鎖 C 和 A' 的那幾組同時被兩個修復碰到。全部紅是這批案例的性質,不是通則;反過來說,有任何一組沒紅才要追:是那組本來就不受影響,還是修復漏了它。
二、新規格測試全綠。 照 PRD v1.2 寫的二十三組(二十一組鎖數值、兩組必須拋錯):
23 passed in 0.03s
三、四條關係在新舊兩版符合預期。 修復版全綠,舊版紅在對帳那條:
FAILED test_mr04_funded_exactly_closes_the_gap[legacy]
isclose(-28813.50481278846, 0.0, abs_tol=0.001)
1 failed, 19 passed
同一個 −28,813.50,跟 Day 25 抓到缺陷 A 那天一模一樣。
四、JS 跟 Python 是同一個東西。 反向差分:Python 是規格的實作,JS 是移植,兩邊餵同樣的輸入比輸出。容許誤差沿用第一幕差分測試的 1e-9,跑前定跑完不調,不符時預設是移植錯。
反向差分|505 組輸入|rel_tol=1e-09
比對 2005 項(3 純量 + balances_raw 序列)
不符:0
第一版跑了 500 組全是合法輸入,拋錯路徑一組都沒走到——就是前兩天那個病。補五組故意違規的(年齡相等、壽命倒置、金額負值、請領年齡負值、大筆支出負值),五組兩邊同步拋錯。
M14 從第二十天活到第二十八天,十份 AI 產物、142 條關係,沒有一條碰得到。
原因跟 M09 一樣,前天寫過:不是斷言的問題,是二十三組案例的 other_income 全是零。而規格早在第九天就裁決過「退休後收入隨通膨調升」,第十三天又改過一次基準年。決定做了兩次,測試沒有一組讓那個欄位非零。
Day 25 推過四類關係都碰不到它,寫了一句「要殺它需要一條關於『通膨率與收入怎麼交互』的關係。那不是套公式套得出來的,得有人真的想過」。
那個洞察只有一句話:收入若隨通膨調升,它會抵銷一部分通膨對目標金額的影響。
寫成關係:通膨率從 2% 升到 4%,有收入時目標金額的增幅,應該小於沒有收入時的增幅。收入若固定不動,兩個增幅會相等:扣的是同一個常數。
calc_fixed:Δ有收入 14,621,896 < Δ無收入 18,277,370 成立
注入 M14: Δ有收入 18,277,370 = Δ無收入 18,277,370 不成立
注入之後兩個數字完全相等。那就是「收入沒有隨通膨調升」的簽名。
這條關係不需要知道目標金額是多少。它需要的只是一組非零的收入,和一句想清楚的話。
逐條跑十四個變異體:
test_prd10_indexed_income_offsets_part_of_inflation 殺掉 1 M14
它只殺 M14,其他十三個一個都碰不到。一條關係對一個錯法:那正是「領域洞察」長的樣子:不是抓得多,是抓得準。
三十天問的是這個。答案不是「人」兩個字,那太廉價。量出來的是分工:
| 人做什麼 | 為什麼只有人能做 |
|---|---|
| 造尺 | 變異體目錄、斷言檢核器、親緣表,每一把都是人設計的,也都有人設計的盲區,出廠時要附 |
| 讀原始碼 | 斷言的兩邊是不是同一段程式算的,只有翻開實作才知道。AI 看不到實作 |
| 做決定 | 規格沒說的事測不出來,只能決定。八十筆決定裡十七筆是這種 |
AI 補得動的那一格是輸入涵蓋——M09 那條 pytest.raises。窮舉、不嫌煩、不會因為「這種情況不可能發生」就跳過。那正是人最容易偷懶的地方。
至於第一天那個引號。當時的「檢查過」是第二天拆開的三招:只走了一條路、挑了一組會讓缺陷消失的數字、請作者當裁判。三十天後看,那三件事沒有一件叫檢查。驗證的成本沒有跟著產出的成本一起降——這是第一幕量到的第一件事,也是最後一天還在的那件。
修復版的 HTML 是最後一天才寫的,而 Python 版第十五天就有了。中間隔了十五天。
不是拖延。是那十五天在做的事:造尺、量 AI、找沒有答案時的方法——每一天都在確認「修好」這兩個字到底要什麼條件才成立。而且第十五天那版 calc_fixed.py 自己就在圖表餘額上加了一個 max(0.0, b)——跟缺陷 A' 同一個東西,是後來才用 PRD-12 拔掉的。第十五天就接回去,我會得到一支帶著同一塊遮羞布的計算機,跟一年前那支一模一樣。
三十天前那支也「看起來對」。
這些數字不能拿來做什麼
/12 保留是裁決不是修復,代價已明列驗收腳本、逐項結果、事前預期在 manifest.yaml 與 verify.sh。
只帶走一件事
「修好了」不是測試全綠。是舊快照紅在預期處、新規格全綠、關係在新舊兩版各得其所、移植跟原本逐位元對齊,四件事同時成立。
少一件,你得到的是一支看起來對的程式。
系列完結。repo:https://github.com/eyelash500/2026_ironman_test_ai/tree/day30