合格は「どの版で測ったか」とひとまとまり。版が進めば、緑は NOT-RUN に戻る
A pass is bundled with the version it was measured on: when the version moves, green returns to NOT-RUN
D24-3-T47-evidence-bound-to-version・技術・日本語入力でプレイする仕組み
UE の実機で、タイトルから推理の決定を経てエンディングまで通ることを、2026-10-01 に確かめました。
台帳にはその行が PASS で残っていますが、今日の版では NOT-RUN です。測った後に、テスト対象に触れる commit が 110 本入ったからです。
昨夜の T46 は「どの台本を、どの合格ラインで回すか」の目録でした。
今日は、回した結果を「どの版の結果か」と一緒に持つ仕組みです。
この回の位置と、前後の回#
区切り: T34〜T49 確かめ方(16 回のうち 14 回目)
先に読むとよい回: T46(検査の目録を作品に置く)
次に読む回: T48(AI に初見で遊ばせる)
名前 — assurance case#
いちばん近い既存の枠組み: assurance case(保証ケース)
- 主張(claim)を、論証(argument)と証拠(evidence)で支える構造を書いたもの。安全に関わる分野で使われる
- 書き方の代表: Goal Structuring Notation(GSN)。Tim Kelly と Rob Weaver の "The Goal Structuring Notation – A Safety Argument Notation"(DSN 2004 Workshop on Assurance Cases)がよく引かれます(書誌を確かめただけで、GSN の初出をこの論文とは言っていません)
うちの名前の対照表での呼び名: evidence ledger(証拠の台帳)
- この語は、検索しても定まった技法名・規格の語としては見つかりませんでした。個々の道具やプロジェクトの呼び名として出るだけです
- うちの使い方は「assurance case の運用に近い」で、版に縛る規則は独自です
状態の名前: NOT-RUN(ISTQB の not run)。テスト計画で「現在の版に対して、そのテストの結果が無い(結果はあるが古い版のもの、または未実施)」と定義し、別名(失効・stale など)は使わないと決めています
一般の解説 — assurance case#
assurance case(保証ケース)とは、主張(claim)を、論証(argument)と証拠(evidence)で支える構造を書いたものです。
assurance case は、「このシステムは安全だ」「この要求は満たされている」という主張を、木の形に割っていきます。
上の主張を下の主張に分け(論証)、葉は証拠(試験の結果・解析・審査の記録)で支えます。
GSN では、主張・戦略・証拠・前提・文脈などを決まった図形で描きます。
よく知られた弱点の一つが、証拠の新しさです。
証拠は、ある版のシステムに対して取ったものです。システムが変われば、証拠が今の版を支えているかは分からなくなります。
しかし木の上では、証拠の丸は緑のまま残ります。誰かが「この試験は古い版のものだ」と言わない限り、主張は支えられているように見え続けます。
ソフトの開発の言葉で言えば、成果物の新しさの問題です。
ビルドの道具は、入力が変われば出力を作り直します。試験の結果も、被検査物が変われば「作り直す(測り直す)」べき出力のはずですが、ふつうの試験の記録にはその依存が書かれていません。
うちの台帳 — assurance case#
回帰テストのような機械で回る試験は、毎回今の版で回すので、この問題は起きません。
問題になるのは、毎回は回せない試験です。うちでは二種類あります。
- 後続レベル(L2〜L4): UE の実機(L2)・人のスモーク(L3)・Steam の体験版(L4)。台帳は作品フォルダの acceptance.json
- 探索的テスト(L1 の一部): AI に初見で遊ばせる回(T48)。結果は exploratory-test.json と、回ごとのフォルダの cycle.json
台帳の行はこうです(UE 実機の行。詳細の文は省きます)。
{
"id": "cond3.ue_runtime",
"group": "後続レベル(L2〜L4・テスト計画 §8)",
"criterion": "条件3 UE 実機で タイトル→入力UI→章1→推理決定→エンディング が通る",
"verdict": "PASS",
"measuredAt": "2026-10-01",
"measuredAtCommit": "72fc528e",
"evidence": "Docs/Reports/2026/10/01/03-l2-ue-runtime.md / 実行: Scripts/adv-test-ue.ps1 -Work Game/adv(→ Scripts/adv-verify-ue.ps1)",
"logs": [ "Saved/Logs/AdvUe-clear-20261001-151123.log", … ]
}
verdict は 4 値だけです。台帳の注記から写します。
"verdict は PASS / WARN / FAIL / UNMEASURED の4値のみ。",
" PASS 条件を満たした。evidence に出典(報告書の節まで)と生ログを必ず書く",
" WARN 17 §6 の判定ルールでいう『単発の好み・記録のみ』。合格を止めない",
" FAIL 合格を止める",
" UNMEASURED まだ測っていない。合格を名乗れない(『問題が無い』ではない)",
NOT-RUN は、台帳には書きません。書くのは「何で測ったか」(measuredAtCommit)だけで、NOT-RUN かどうかは、読むたびにスクリプトが今の版と比べて決めます。
今日の台帳は 3 行で、PASS 1・UNMEASURED 2 です。
版との比べ方 1: 測った後の commit を数える#
テストのサマリ(Scripts/adv-test-summary.ps1)は、PASS の行ごとにこう判定します(写す)。
'PASS' {
$n = -1
$mc = [string](Get-AdvJsonField $r 'measuredAtCommit') # 測っていない行には無いことがある
if (-not [string]::IsNullOrWhiteSpace($mc)) {
$c = Invoke-AdvGit $Root (@('rev-list', '--count', "$mc..HEAD", '--') + $Watch)
if ($null -ne $c) { $n = [int]$c }
}
if ($n -lt 0) { Row 'ERROR' Red $r.id "$($r.criterion) ← 測定コミットが不明・照合できない"; $Led.Error++ }
elseif ($n -gt 0 -or $DirtySut -gt 0) {
$why = if ($n -gt 0) { "測定後に $n コミット" } else { "未コミットの変更あり" }
Row 'NOT-RUN' Yellow $r.id "$($r.criterion) ← 現在の版の結果ではない($why)。この工程を測り直す"; $Led.NotRun++
} else { Row 'PASS' Green $r.id $r.criterion; $Led.Pass++ }
}
- 測った commit から HEAD までに、テスト対象($Watch)に触れた commit が 1 本でもあれば NOT-RUN
- テスト対象に未コミットの変更があっても NOT-RUN
- 測った commit が書いていない・git で引けないなら ERROR(PASS とは読まない)
- WARN・FAIL・UNMEASURED は、版を見ずにそのまま出す
テスト対象の範囲は、作品の試験の設定(exploratory-test.json の watch)が一か所で決めます。
"watch": ["Game/adv", "Tools/adv-sim", "Plugins/KaedeAdv/Source/KaedeAdv",
":(exclude)Game/adv/runs", ":(exclude)Game/adv/checks", ":(exclude)Game/adv/probes-archive",
":(exclude)Game/adv/acceptance.json", ":(exclude)Game/adv/judge.json",
":(exclude)Game/adv/exploratory-test.json", ":(exclude)Game/adv/golden/holdout-usage.jsonl",
":(exclude)Game/adv/test-attempts.json"]
(読みやすく改行しました。)
作品データと、CLI のシミュレータと、UE の ADV の核。そこから、検査の台本・検査のスクリプト・台帳そのもの・記録の類を除きます。台帳を除くのは、PASS を書き込む commit が、その PASS を NOT-RUN にしてしまわないためです。
今日の値です。
$ git rev-list --count 72fc528e..HEAD -- <watch>
110
リポジトリ全体では 757 本の commit が入っていて、テスト対象に触れたのはそのうち 110 本でした。なので、UE 実機の行は今日、NOT-RUN です。
この仕組みは、測った日に一度働いています。
2026-10-01 の 1 回目の測定は 00a2899f で PASS でした。その後、同じ日にハルの日常会話の変更が入ったので、それを含む 72fc528e で測り直して、もう一度 PASS を書いています。git の履歴に、その 2 本の commit が並んでいます。
版との比べ方 2: テスト対象の中身の指紋#
探索的テストの結果は、別のやり方で版に縛っています。
回を始めるときに、開始記録(*.start.json)を書きます。
function New-AdvStartRecord([string]$Root, [string]$Work, [string]$Session, [string[]]$Watch, [string]$Exe) {
$DataDir = Join-Path $Root $Work
return [ordered]@{
session = $Session
work = $Work
startedAt = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ss')
commit = [string](Invoke-AdvGit $Root @('rev-parse', 'HEAD'))
testItem = (Get-AdvTestItemDigest $Root $Watch)
dirty = @(Get-AdvTestItemDirty $Root $Watch)
exe = (Get-AdvFileDigest $Exe)
criteria = (Get-AdvCriteriaDigest $DataDir)
runtime = (Get-AdvRuntimeSet $DataDir $Exe)
}
}
testItem は、commit の番号ではなく、テスト対象の中身の指紋です。
# watch は git の pathspec(除外つき)。**コミットの sha ではなくツリーの内容**をダイジェストにする——
# 文書だけのコミットでは変わらず、テスト対象のどのファイルが変わっても変わる。
function Get-AdvTestItemDigest([string]$Root, [string[]]$Watch) {
$lines = @(Invoke-AdvGit $Root (@('ls-files', '-s', '--') + $Watch))
if ($null -eq $lines) { return $null }
$canon = ($lines | ForEach-Object { ($_ -replace '\s+', ' ').Trim() } | Sort-Object) -join "`n"
return (Get-AdvSha256 $canon).Substring(0, 16)
}
git ls-files -s は、ファイルごとに「モード・中身のハッシュ・パス」を出します。それを並べ替えて SHA-256 にし、先頭 16 文字を取ります。
ほかに、シミュレータの実行ファイルの指紋(exe)・合格基準の指紋(criteria)・焼いたベクトルと実際に読んだ全ファイルの指紋(runtime)も記録します。
結果の側は、こう判定します。
elseif ($Cycle.testItem) {
$currentItem = Get-AdvTestItemDigest $Root $Watch
if ($currentItem) { $Changes = if ($currentItem -eq $Cycle.testItem) { 0 } else { 1 } }
指紋が違えば、その回の結果は今の版の結果ではないので NOT-RUN です。表示は NOT-RUN(last=第N回@sha:PASS,changes=N) の形で、直近の結果を添えます。
今日の値です。最後に記録した第84回(2026-10-10)の指紋は 8d985bee4d5f9265、今日の版は bdfb25db9d4e2b6a でした。違うので、探索的テストも NOT-RUN です。第84回の版から今日までに、テスト対象に触れた commit は 52 本ありました。
二つのやり方の違い#
一つ目(commit を数える)は、テスト対象に触れた commit が 1 本でもあれば NOT-RUN です。変更して、元に戻しても、数は残ります。
二つ目(中身の指紋)は、中身が同じなら一致します。戻せば元の結果が生きます。代わりに、実行ファイル・合格基準・焼いたベクトルは、別の指紋で見る必要があります(開始記録に全部ある)。
どちらも「緑を、測った版の外へ持ち出さない」ための仕掛けで、厳しさが少し違います。
どこまで止めるか#
テスト計画は、NOT-RUN の効き方をレベルで分けています。
- 探索的テストの NOT-RUN は、L1 の終わりを止める(L1 の終了基準は「探索的テストが現在の版で PASS」を含む)
- 後続レベルの台帳の NOT-RUN・FAIL・未測定は、L1 を止めない。リリースの判定にだけ効く
- NOT-RUN を「問題なし」と読まない。未測定を「問題なし」と読まない
NOT-RUN は状態の表示で、命令ではありません。名前の対照表の失敗の表に、NOT-RUN を「やり直せ」と読んで、発見・修正・NOT-RUN・再探索が閉じなかった経緯が残っています。いまは、次に何をするかはテストのサマリの next= が決めます。
名前どおりでない所 — assurance case#
assurance case の形(主張・論証・証拠の木)は持っていません。台帳は、主張(criterion)と証拠(evidence・logs)を 1 行に並べた平らな表です。論証の層はありません。
版に縛る規則(測った commit や中身の指紋と比べて NOT-RUN にする)は、名前の対照表のとおり独自です。名前のある枠組みに寄せる利益は小さいとして、対照表は据え置きにしています。
evidence ledger という呼び名も、定まった名前ではありません。
弱点と、採らなかった案 — assurance case#
- テスト対象の範囲(watch)を広く取るほど、関係のない変更でも NOT-RUN になります。今日の 110 本には、ブラウザ版の案内ページ(Game/adv/web/landing.html)だけを変えた commit も入っています。UE 実機の結果には関係しない変更です。範囲の取り方で起きた失敗は T55 で書きます
- 狭く取ると、本当に関係のある変更を見落とします。どちらに倒しても、範囲は人が決めています
- PASS の行の evidence は散文とログのパスです。ログが Saved/ にあると、git では追えず、消えれば証拠は文だけになります
- NOT-RUN になった行を測り直すのは人(と生成AI)の作業です。UE 実機のように、測り直しに時間がかかるものほど NOT-RUN のまま残ります
採らなかった案:
- PASS の行に期限(日付)を付ける。時間がたっても版が同じなら結果は変わらないし、1 日で版が変わることもある。縛る相手は時計ではなく版
- NOT-RUN を台帳に書き込む。書けば、書いた時点の判断がまた古くなる。読むたびに比べる方を採った
- 後続レベルの NOT-RUN で L1 を止める。UE 実機の測り直しが毎回の作業に入り、内部の試験が回らなくなる
検索語#
assurance case evidence
Goal Structuring Notation GSN Kelly
ISTQB test status not run
test result provenance commit
『霧走町 流氷に眠る』の第一章は、ブラウザで無料で遊べます。プロフィールのリンクから。