「直すものが空になった」の緑は、古い焼き物の上の緑だった
The green of "nothing left to fix" was green on top of stale baked data
D26-4-T52-stale-bake-false-negative・技術・日本語入力でプレイする仕組み
2026-08-06 の夜、「既知の未解決が空になった」と記録しました。
翌日、焼いたベクトルを作り直してから同じ検査を流すと、凍結したプレイ記録 6 本のうち 4 本がクリアできませんでした。
検査が見ていたのは、言い換えを足す前に焼いた表でした。
この回の位置と、前後の回#
区切り: T50〜T57 失敗の名前(8 回のうち 3 回目)
先に読むとよい回: T08(二つの点を混ぜる)
次に読む回: T53(変更に追いつかない検査)
名前 — stale artifact#
- 呼び名
- stale artifact による false negative(古い生成物のせいで、回帰があるのに検査が緑になる)
- 日本語
- 古い成果物による見逃し
- 初出
- stale artifact という語の初出は確かめられませんでした
- 既にある枠組み
- ビルドシステムの correctness と minimality(Andrey Mokhov, Neil Mitchell, Simon Peyton Jones, "Build Systems à la Carte: Theory and Practice", Journal of Functional Programming)
stale artifact とは#
入力から作る生成物(artifact)が、入力より古いまま使われることです。
ビルドなら、ソースを直したのにオブジェクトファイルを作り直さずにリンクする。
キャッシュなら、元のデータが変わったのに古い値を返す。
機械学習なら、特徴量の定義を変えたのに、前に計算した特徴量の表で評価する。
テストにとって怖いのは、これが false negative(見逃し)を作ることです。
検査は生成物を見ます。
生成物が古ければ、検査は「変更の前の世界」を確かめて緑を返します。
回帰は、変更の後の世界にしかありません。
ビルドシステムの研究は、この問題に定義を与えています。
Build Systems à la Carte は、ビルドシステムを小さな Haskell の実装で並べ、
correctness(結果が、全部を作り直したのと同じであること)と、
minimality(依存が変わった作業だけを走らせること)を定義しました。
stale artifact は、correctness が破れた状態です。
make は、生成物と入力のファイルの更新時刻を比べて、古ければ作り直します。
時刻の代わりに入力の中身のハッシュを比べる方式もあります。
どちらも「生成物の新しさを、使う前に機械で確かめる」という対処です。
うちの生成物: vectors.bin#
照合は 2 段です(T08)。
文字の n-gram で引く段と、文の埋め込み(multilingual-e5-small)で引く段。
言い換えの辞書の全文を、あらかじめ埋め込みにしておくのが adv-sim bake で、
結果が Game/adv/baked/vectors.bin です。
この vectors.bin は git の管理外です(大きいので、各自が焼く)。
つまり worktree ごとに別の実体があり、言い換えを 1 本足しても、焼き直さない限り古いままです。
古い vectors.bin の上で、新しく足した言い換えはどう扱われるか。
埋め込みの段に、その言い換えのベクトルがありません。
足した意図は文字の段だけで戦い、埋め込みの段では存在しないのと同じになります。
照合は落ちません。点が少し違うだけです。
例外も出ません。
うちで起きたこと — stale artifact#
2026-08-06、AI に初見で遊ばせた第 8 回の指摘を直した commit(d50bdfb6)の本文は、「known-open が空に」で終わっています。
既知の未解決の欠陥が 0 になった、という意味です。
その版で第 9 回の 3 人が遊び、3 人ともクリアしました。
2026-08-07、第 9 回の締めの検証で、Game/adv を adv-sim bake し直してから検査を流しました。
記録(Docs/Reports/2026/08/07/07 §3)の数字です。
- golden dev 171/172(前は 172/172。1 問が隣の意図に食われる)
- 自然文だけで解く自動プレイがクリア不能
- 判定の「宙に浮いた手掛かり」20(上限 1)
- 凍結したプレイ記録 6 本を流し直すと、4 本がクリア不能
原因は、前日までに新しく足した意図の言い換えが、古い bake では埋め込みを持たず、文字の段だけで競っていたことでした。
焼き直して埋め込みが効いた途端、推理決定の局面の僅差が、広い範囲でひっくり返りました。
足した受け皿が、隣の意図の決め手の文を吸うようになったのです。
そして、その局面の受け皿の 1 つに、言い換えとして入れてはいけない文(自分の注記に「決め手はここに書かない」とある所に、決め手の言い方が 4 本)が入っていました。
古い bake では、その 4 本は弱くて目立たなかった。
その日の commit(4af1f801)の本文はこう書いています。
「d50bdfb の green 申告は stale bake 依存だった」。
修正は 5 点で、どれも意図の境界の言い直し(除外の語・状態の条件・手帳の文)でした。
本文の最後の一言は「『known-open を空にできた日』の実体が stale bake の上の申告だったのは苦い発見だが」。
報告の教訓の 1 番目は「green 申告は bake 込みでしか成立しない。データを変えたら adv-sim bake → スイート、をコミット前に必ず 1 セットで回す」でした。
直したこと・いまの仕組み — stale artifact#
規約だけでは止まりませんでした。
直した順に書きます。
2026-08-07: 規約にした。
アーキテクチャの文書(§9)に「bake 鮮度の罠」の節を置きました。いまもこう書いてあります。
vectors.bin は gitignore で worktree ごとに別実体。
言い換えを1本足しただけでも焼き直さないと、検証が古い表を見て緑になる。
stale な bake は回帰を隠す。マージ先でも必ず焼き直すこと。
2026-08-11(c29efe5d): 測る道具にした。
文書の数字を 1 コマンドで出す adv-sim metrics を作り、その中で鮮度を見るようにしました。
作品フォルダの中の .json と .jsonl のうち、いちばん新しい更新時刻と、vectors.bin の更新時刻を比べます。
vectors.bin の方が古ければ「古い」と出して、終了コード 1 を返します。
// 鮮度: データより古い bake は回帰を隠して緑になる(08/07 実証)。
std::string NewestPath;
const auto Newest = NewestWriteTime(Data, Data / "baked", &NewestPath);
bStale = fs::last_write_time(VecPath, Ec) < Newest;
J["bake"]["stale"] = bStale;
J["bake"]["newest"] = NewestPath; // 何が vectors.bin より新しいか
2026-08-19(944bed2d): 1 コマンドの経路に入れた。
8 日のあいだ、鮮度を判定できるのは metrics だけで、合否のゲートも毎周のループも metrics を呼んでいませんでした。
commit の本文の言い方では「番犬は飼っていたが庭に出していなかった」。
ゲートの機械判定の一番先で metrics を回し、古ければ FAIL にしました。
いまはテストの集計(Scripts/adv-test-summary.ps1)の回帰テストの最初の行がこれです。
--- bake 鮮度 — 一番先に見る(古い baked/ は回帰を隠して緑になる)---
...
elseif ($mCode -ne 0) { Row 'FAIL' Red 'bake' "$mLine — $Exe bake --data $Work で焼き直す" }
配る物の側にも同じ考えが入っています。
ブラウザ版のサーバは、作品データと bake とモデルのハッシュを束ねた組(manifest)を起動時に照合し、違えば起動しません。
UE のパッケージも、焼く前に鮮度を見ます(アーキテクチャの文書 §1)。
名前どおりでない所・まだ残っている弱さ — stale artifact#
起きたことは、名前どおりの stale artifact による false negative です。
生成物が入力より古く、検査は緑で、回帰が隠れていました。
ただ、いまの見張りは make と同じ「更新時刻」の比較で、中身は見ていません。
そこから来る弱さが 3 つあります。
一つ目、時刻は中身の代わりにすぎません。
git の checkout や、ファイルを写す道具は、更新時刻を変えたり保ったりします。
中身が同じでも新しく見え、中身が違っても古く見えることがありえます。
配る物の manifest はハッシュを比べていますが、開発中の検査は時刻のままです。
二つ目、何を「入力」と数えるかを、除外の一覧で決めています。
作品フォルダの .json・.jsonl を全部数え、焼く入力でないものを名前で外す作りです。
今日(10/29)この worktree で metrics を流すと、鮮度は「古い」と出ました。
vectors.bin(10/27 13:20)より新しいのは、予告編の台本(trailer/ の 3 ファイル、10/26〜28 に足したもの)だけでした。
言い換えと台本の最後の変更は 10/10 で、bake は中身としては新しい。
見逃しを防ぐ見張りが、今度は無関係な変更で鳴っています。
これは逆向きの失敗で、名前は別にあります(T55)。
三つ目、metrics --json は、古くても終了コード 0 を返します。
テキストの出力では 1 を返すので、いまの集計(テキストで呼んでいる)は正しく止まります。
けれど JSON で呼ぶ道具を新しく書けば、終了コードだけでは古さが分からず、bake.stale の欄を読む必要があります。
見張りの出口が 2 つあって、片方だけが吠える形です。
検索語#
stale artifact false negative
build systems a la carte correctness minimality
incremental build stale outputs
cache invalidation stale data testing
港の係船柱は、『霧走町 流氷に眠る』の第一章で何度でも調べられます。調べるたびに同じ答えが返るのは、焼いた表と台本が同じ版だからです。ブラウザで確かめてみてください。プロフィールのリンクから。