# 検証プロファイル (Verify Profile)

TIAS Shot（WinForms / .NET Framework 4.8 / x64 の舌診撮影ソフト）の検証プロファイル．verifier は本ファイルだけを頼りに検証する．

経緯: 2026-09-18 時点では「verifier はハードウェア結合層（カメラ・UI・スレッド）を担保できない」としてプロファイルを作らなかった．その後，ヘッドレスのセルフテストモード（`TIASshot.exe --selftest`，`SELFTEST` ビルドフラグ）で起動時の依存解決・プレビュー経路・校正〜保存の一部を実機なしで自動確認できるようになったため，本プロファイルを作成した．

## 種別・検証手段 (Target & Method)

- デスクトップアプリ（WinForms）．**画面操作・カメラ操作はしない**．
- ランナー `scripts/verify/run_selftest.py` が，クリーンビルド（`Debug|x64`）→ 既存テスト（NUnit）→ `TIASshot.exe --selftest` を実行し，終了コードと結果ファイルで判定する．
- セルフテストの 3 項目（実行ファイルに組み込み済み．verifier がシナリオを書く必要はない）:

| ID | 確認内容 | 実コードを通す範囲 | 代替している部分 |
| --- | --- | --- | --- |
| `deps` | 起動時にアセンブリ解決エラー（FileLoadException / FileNotFoundException）が出ない | `TIASshot.exe.config` のバインディングリダイレクトと bin の DLL バージョンの一致，本体参照の推移的ロード，bin 内の全管理 DLL のロード，OpenCvSharp（ネイティブ DLL・Mat 演算・`Mat.AsSpan`（System.Memory）・ArUco），`BitmapConverter` と `BitmapUtil.FastClone` の往復，`config.xml`・TCC CSV の読み込み，**`Form1` の実コンストラクタ**（`InitializeComponent`） | `Form1_Load`（カメラ・光源への接続）は実行しない |
| `preview` | プレビュー経路（カメラコールバック → UI 所有コピー（FastClone）→ 表示用 Bitmap）が動く | `CameraBase`（コンストラクタ・`BootCheck`・`ProcessCalibrationFrame`），`Form1.ShowImage`・`MainRefreshTimer_Tick`（実タイマー），`PreviewMonitor`（実コンストラクタ・`UpdateImage`・`RefreshTimer_Tick`）．入力 Bitmap 解放後も表示コピーが有効であること（所有権モデル）を確認 | カメラは偽カメラ（`SelfTest/SelfTestCamera`．Lucam のコールバック手順を再現）．フォームは非表示・`InitializeComponent` を呼ばないヘッドレス版コンストラクタ．副ディスプレイの存在は模擬 |
| `calib-save` | 校正〜保存が一通り動く | `ChartDetector`（ArUco 検出・固定判定・ホモグラフィ・マスク），`CameraBase.CalcTcc`・`ColorCorrector`（変換行列算出・CSV 保存），`CameraBase.ShotOne` → `RunShotLoop` → `SaveThread` → `SaveImages`（sRGB 変換画像の保存）．保存物の読み戻し・行列の有限性・変換後の色票が参照値に戻ることを確認 | チャートは合成画像（ArUco と色票をコードで描画）．ゲイン反映は偽カメラが画像へ乗算して模擬．保存先は一時フォルダ |

## 検証対象の環境 (Target Environments)

- ローカルの接続先: このマシンのみ（ビルド・実行ファイル・一時フォルダ）．ネットワーク上のサービスは使わない（`--full` 時の `nuget restore` による nuget.org からのパッケージ取得のみ例外）
- ローカル以外で許可する環境: なし
- 接続を禁止する環境（拒否リスト）: 実機カメラ（Lumenera / ImagingSource）・光源（シリアルポート）・本番の撮影データフォルダ（`config.xml` の `File/SaveFolder`，既定 `C:\TIAS_Data`）．セルフテストはこれらに触れない設計であり，verifier も触れない
- 条件付きで許可する環境: 実機カメラ・光源・シリアルポートは，**ユーザーが会話の中で実機テストを明示的に合図したとき（オーケストレーターから「実機テストの合図: あり」と渡されたとき）に限り**，後述「実機テスト」節のランナー（`scripts/verify/run_hwtest.py`）経由でだけ接続してよい．本番の撮影データフォルダは実機テストでも触れない（保存先は一時フォルダに差し替わる）

## 実行コマンド (Run Command)

リポジトリルートで実行する．

| 用途 | コマンド |
| --- | --- |
| 通常（コード変更の確認） | `python scripts/verify/run_selftest.py` |
| 依存更新（NuGet パッケージ・バインディングリダイレクトの変更）の確認 | `python scripts/verify/run_selftest.py --full` |

- ランナーの `--no-clean`（Debug 出力を消さない）・`--skip-tests`（既存テストを省略）は開発時の短縮用であり，**判定には使わない**（verifier は上表のコマンドだけを使う）
- `--full` は `packages` を退避して `nuget restore` からやり直す（`git clone` 直後と同じ状態．ENV_04「クリーン環境での検証」）．復元に失敗した場合は退避した `packages` を自動で戻す．`packages` の退避（改名）自体に失敗した場合（Visual Studio が使用中等）は `packages` を変更せずに終了コード 2 で止まる
- 出力先: `.verify/selftest/run-<日時>/`（`.gitignore` 済み．実行ごとに別ディレクトリ）
- 出力されるもの: `summary.json`（各ステップの成否）・`build.log`・`test.log`・`restore.log`（`--full` 時）・`selftest-result.txt`（セルフテストの詳細．UTF-8 BOM 付き）
- ランナーの終了コード: `0`=全て合格，`1`=不合格あり（ビルド失敗・テスト失敗・セルフテスト失敗），`2`=前提条件を満たさない（→「検証不能」）
- セルフテスト単体の終了コード（`summary.json` の `selftest_exit_code`）: `0`=全項目合格，`1`=不合格あり，`2`=セルフテスト自体の異常，`3`=タイムアウト（5 分．ハングの疑い）

### 判定の読み方

- `selftest-result.txt` は項目ごとに `[PASS] <ID>: ...` / `[FAIL] <ID>: ...` の見出し行と，その下の詳細行（`  - ` 情報，`  WARN:` 参考，`  FAIL:` 失敗理由）で構成され，最終行が `RESULT: PASS (3/3)` または `RESULT: FAIL (...)` になる
- `FAIL:` 行には例外型・メッセージ・対象アセンブリ名（`[assembly: ...]`）・Fusion ログの要点（`[fusion: ...]`）・スタックの先頭が入る．依存の問題はここで特定できる
- 判定は**終了コードと結果ファイル**で行う．`TIASshot.exe` の標準出力は OS のコードページ（cp932）で出るため，直接実行すると文字化けすることがある（ランナーは標準出力を使わない）

## 実機テスト（ユーザーの合図があるときだけ）(Hardware Test)

[PLAN_06](../docs/03_PLAN/PLAN_06_実機自動テスト計画.md) の実機テスト（`TIASshot.exe --hwtest`，`HWTEST` ビルドフラグ）．実際の `Form1` を表示し，指定した機種のカメラと光源を操作して検査する．

### 実行してよい条件

- **ユーザーが会話の中で実機テストを明示的に合図したとき（オーケストレーターから「実機テストの合図: あり」と渡されたとき）だけ**実行する．撮影の本番中に機材を操作しないため
- 合図が無ければ実機テストは実行せず，該当する確認を「**検証不能（合図なし）**」と報告する（セルフテスト等，他の検証は通常どおり行う）．合図を推測で補わない．「動作確認して」等の一般的な依頼は合図とみなさない
- 合図は 1 回の検証の依頼ごとに有効とする．以前の会話・別のタスクでの合図を流用しない

### 実行コマンド

リポジトリルートで実行する．**対象は ImagingSource だけ**で，Lumenera の実機テストは行わない（PLAN_06．Lumenera は解像度が相対的に低く今後あまり使わない見込みのため）．ユーザーが明示的に Lumenera の検査を指示した場合に限り `--camera lucam` を使う．

| 用途 | コマンド |
| --- | --- |
| Lumenera を検査する（通常は行わない．ユーザーの明示指示があるときだけ） | `python scripts/verify/run_hwtest.py --camera lucam` |
| ImagingSource を検査する | `python scripts/verify/run_hwtest.py --camera iscam` |

- `--with-chart`（チャートが必要な検査．PLAN_06 フェーズ 2）は未実装．指定すると機材に触れずに終了コード 2 になるため使わない
- `--no-clean`（Debug 出力を消さない）は開発時の短縮用であり，判定には使わない
- ランナーは Debug|x64 をビルドしてから `TIASshot.exe --hwtest --camera <機種> --hwtest-out <結果ファイル>` を実行ファイルのフォルダで実行する．NUnit テスト・セルフテストは実行しない
- 出力先: `.verify/hwtest/run-<日時>/`（`summary.json`・`build.log`・`hwtest-result.txt`（UTF-8 BOM 付き））
- ランナーの終了コード: `0`=全項目合格，`1`=不合格あり（ビルド失敗・検査の不合格・タイムアウト），`2`=前提条件を満たさない（→「検証不能」．ツール未導入・TIASshot 起動中・指定機種が見つからない・引数不正・`--with-chart`）
- 実機テスト単体の終了コード（`summary.json` の `hwtest_exit_code`）: `0`=全項目合格，`1`=不合格あり，`2`=実機テスト自体の異常（引数不正・指定機種が見つからない・未実装の指定），`3`=タイムアウト（各項目の上限または全体 10 分．ハングの疑い），`null`=ランナーが 720 秒で打ち切った

### 検査項目

| ID | 確認内容 | 測定値と判定（しきい値は ImagingSource の実測（PLAN_06 フェーズ 1）から決めた．結果ファイルの表記は「暫定」のまま） |
| --- | --- | --- |
| `connect` | 指定した機種だけに接続でき，機種名・シリアル番号が取れる | 列挙した台数，`Connect()` の成否と所要時間，機種名・シリアル番号 |
| `preview` | プレビューが流れる | 最初のフレームまでの時間，フレームレート（下限 10 fps），本モニタへの表示，平均輝度と飽和画素の割合 |
| `exposure-gain` | 露光・ゲインの設定がフレームに効く | 先に基準の明るさを探す（元の露光に最も近い 2^n 秒の段に合わせ，平均輝度 30〜220 かつ飽和画素の割合 1% 以下になるまで露光を 1 段（`ExposureLogStep`．時間で 2 倍・半分）ずつ動かす．最大 12 段）．露光は基準から 1 段下げ・上げ（比が下げ 0.8 以下・上げ 1.25 以上），ゲインは範囲の幅の ±10%（差が 3 階調以上．片側の向きだけ期待どおりでも合格）で判定．ImagingSource は絶対値（`VCDAbsoluteValueProperty`．秒・dB）で操作する．元の値に戻ったことを読み戻しで確認（相対誤差 1% 以内） |
| `light` | 光源が点灯・消灯し，閉じた後に開き直せる | 照明ボタンでの点灯・消灯の平均輝度の差（100 階調以上），`LightSource.Close` → `Open` の成否，開き直した後の点灯の効き |
| `close-preview` | プレビュー中に閉じる処理を呼んでもすぐ戻る | 閉じる処理（`Form1_FormClosing`）の所要時間（上限 1000 ms）・例外の有無・後始末の順序（closing → timer → camera → light）・閉じた後 1 秒間に届いたフレーム数（2 以下） |
| `reconnect` | 切断した後に再接続できる | 2 回目の `Connect()` の成否・シリアル番号の一致・再接続後のフレームの到着（5 フレーム以上） |
| `cleanup` | 後始末と書き込み禁止の確認 | 露光・ゲインを戻す → カメラ切断 → 光源のポートを閉じる（消灯はしない），の成否．実行ファイルのフォルダの `*.xml`（`config.xml`・デバイス状態ファイル）の内容が変わっていないこと，本番の保存先の今日のフォルダが変わっていないこと |

### 判定の読み方

- `hwtest-result.txt` はセルフテストと同じ書式（`[PASS] <ID>: ...` / `[FAIL] <ID>: ...` の見出し行，`  - ` 情報・`  WARN:` 参考・`  FAIL:` 失敗理由の詳細行，最終行 `RESULT: PASS (7/7)` または `RESULT: FAIL (...)`）．冒頭に暫定しきい値の一覧（`thresholds (暫定)`）と校正の扱い（`校正: 停止（チャート不要の検査…）`）が出る
- **測定値（フレームレート・平均輝度・所要時間）は PASS / FAIL にかかわらず詳細行に出る**．暫定しきい値で FAIL した場合は，測定値を報告に必ず書く（しきい値の決め直しに使う）
- タイムアウト時は，止まった項目の見出しの下に途中までの測定値と `FAIL: タイムアウト: ...（実行中のステップ: ...）` が出る．どの SDK 呼び出しで止まったかはステップ名で特定する
- 指定機種が見つからない場合は `RESULT: FAIL (camera not found: <機種>)`（終了コード 2）．機材の接続を人間に依頼する

### 前提条件

| 前提条件 | 確認方法 | 満たされない場合 |
| --- | --- | --- |
| ユーザーの合図がある | オーケストレーターからの指示に「実機テストの合図: あり」がある | 実行せず「検証不能（合図なし）」と報告する |
| 撮影の本番中でない・TIASshot が起動していない | ランナーが `tasklist` で確認する（起動中なら終了コード 2） | 検証不能とし，人間に TIASshot の終了を依頼する（**自分でプロセスを止めない**） |
| 指定機種のカメラと光源が開発 PC に接続されている | `connect` の詳細行（列挙した台数）・`light` の詳細行（ポートの有無） | カメラが無ければ検証不能（終了コード 2）．光源が無ければ `light` が FAIL になる．機材の接続を人間に依頼する |
| カメラの前の穴に**カラーチャート（または白板）がはめてある**（穴が空だと暗く，明るさの変化を評価しにくい）．チャート不要の検査では校正は止まる（結果ファイル冒頭に `校正: 停止（チャート不要の検査…）`） | 人間に確認する．`preview`・`light` の詳細行 `校正は止まっています` | はめていなければ人間にはめてもらってから再実行する．`preview`・`light` が `FAIL: 校正を止めているはずなのに校正が…` なら校正の停止が効いていない不具合として報告する（露光・ゲイン・カメラ本体のホワイトバランスが乱れている可能性あり） |
| セルフテストの前提（Visual Studio / Build Tools・`packages/`・IC Imaging Control 3.5） | 上記「前提条件と確認方法」と同じ | 同上 |

### 既知のノイズ

- 平均輝度・フレームレートは照明・被写体・カメラの個体差で変わる．判定は変化の向きと量で行い，絶対値は判定しない
- 実測（ImagingSource DFK 33UX178・カラーチャートをはめた状態）: フレームレート 13〜31 fps（実行ごとのばらつきが大きい），光源の点灯と消灯の差 149〜196，閉じる処理 75〜221 ms．これより大きく外れたら，しきい値より先に照明・チャート・機材の状態を疑う
- 飽和は「254 以上の画素の割合が 1% を超える」で判定する（平均輝度では判定しない）．上げた向きの結果が飽和した場合，または真っ暗（平均輝度 5 以下）の場合，その向きの確認は省かれる（詳細行に「確認しない」と出る）．両方とも省かれると FAIL になる．基準探しで中間の明るさにできなかった場合も FAIL になる
- 露光・ゲインが設定範囲の端にあると，その向きの設定が変わらず確認が省かれる（詳細行に「反映されませんでした」と出る）
- `exposure-gain` の `WARN: 元に戻した後の平均輝度が最初と離れています` は参考（判定は設定値の読み戻しで行う）
- 実際のフォームが表示される（副ディスプレイがあればプレビューモニタも開く）．メモリ観測ウィンドウ（`MEMMONITOR`）は開かない

### 後始末

- 実機テストは終了時（失敗時も）に「変えた露光・ゲインを元に戻す → カメラ切断 → 光源のポートを閉じる」を行い，`cleanup` 項目に結果が出る．光源は消灯しない（アプリの終了時と同じく点いたまま残るのが仕様．上流 v1.7 と同じ）．`light` 項目は点灯・消灯・再点灯を検査し，最後は点灯した状態で終わる
- タイムアウト・未処理例外の場合は光源のポートだけを緊急に閉じる（消灯はしない．カメラには触れない．露光・ゲインは次の接続で設定ファイルの値に戻る）．`WARN: 緊急の後始末: 光源のポートのクローズが 5 秒以内に終わりません` が出たら，ポートが開いたままの可能性を人間に報告する（光源が点いたままなのは仕様であり報告不要．`light` 項目の途中（消灯中）で止まった場合は消灯したまま残りうる）
- 書き込み先は `%TEMP%\TIASshot-hwtest\work-<日時>-<PID>\`（一時設定ファイル `config.hwtest.xml`．チャート不要の検査では校正を止めるため校正データは書かれない）と `.verify/hwtest/run-<日時>/` だけ．合格時と前提を満たさずに終わった場合（終了コード 2）は `work-*` を自動削除し（`--hwtest-keep` 指定時は残す），不合格（1）・タイムアウト（3）時は残して結果ファイルの末尾に `work dir kept: <パス>` と出る．残った `work-*` の有無とパスを報告の「後始末」に書く
- 同梱の `config.xml`・デバイス状態ファイル（`{DeviceName}.xml`）・本番の保存先には書き込まない（`cleanup` で確認する）

## 前提条件と確認方法 (Preconditions)

| 前提条件 | 確認方法 | 満たされない場合 |
| --- | --- | --- |
| Visual Studio 2022 または Build Tools（MSBuild・vstest）が導入済み | ランナーが `vswhere` で自動検出する（見つからなければ終了コード 2） | 検証不能とし，`docs/02_ENV/ENV_02_環境構築手順.md` に従った導入を人間に案内する |
| NuGet パッケージが復元済み（`packages/`） | `ls packages` | `python scripts/verify/run_selftest.py --full` を実行してよい（`nuget.exe` が無ければ検証不能とし `setup.ps1` の実行を人間に案内する） |
| IC Imaging Control 3.5 .NET Library が導入済み（ビルドに必要．実機カメラは不要） | ビルドの成否（`build.log` に `TIS.Imaging` の参照エラーが無いこと） | 検証不能とし，ENV_02 の SDK 導入手順を人間に案内する |
| `TIASshot.exe` やテストが実行中でない（Debug 出力を削除できる） | ランナーがクリーンするのは Debug 出力（`TIASshot/` と `TIASshot.Tests/` の `bin/x64/Debug`・`obj/x64/Debug`）のみで，`bin/x64/Release` 等ほかの構成の出力には触れない．削除に失敗すると終了コード 2 で止まる | 検証不能とし，人間にアプリ・Visual Studio のデバッグ実行の終了を依頼する（**自分でプロセスを止めない**） |

## 認証方法・ロール (Auth & Roles)

- 認証なし

## テストデータの規約 (Test Data Rules)

- セルフテストが書き込むのは次の 2 か所だけ:
  - `%TEMP%\TIASshot-selftest\work-<日時>-<PID>\`（一時設定ファイル・校正データ・撮影画像）．**合格時は自動削除**，不合格時は証拠として残し，結果ファイルの末尾に `work dir kept: <パス>` と出る
  - `.verify/selftest/run-<日時>/`（ランナーのログと結果）
- 同梱の `config.xml` は変更しない（セルフテストは複製して保存先・参照パス・効果音を差し替えた一時設定を使う）．本番の保存先（`C:\TIAS_Data` 等）には書き込まない
- 不合格時に残った `work-*` フォルダの有無とパスを，報告の「後始末」に書く（削除は人間の判断に任せてよい）

## 待ち方・操作の癖 (Waiting & Selectors)

- ランナーは同期実行で，完了まで待つだけでよい（通常 10〜30 秒．`--full` はパッケージ取得で数分）
- セルフテストはウィンドウを表示しない．画面の確認手段は無く，スクリーンショットも出ない
- `Release` 構成にはセルフテストが含まれない（`SELFTEST` フラグは Debug 系のみ）．Release の exe に `--selftest` を渡すと通常起動（UI 表示・カメラ接続）になるため，実行しない

## 既知のノイズ (Known Noise)

- `deps` の `リソース系の AssemblyResolve n 件（サテライトリソースの探索．判定対象外）`: 日本語カルチャのサテライトリソース（`TIASshot.resources`）を探して見つからないのは通常動作
- `deps` の `redirect System.ValueTuple → 4.0.3.0: bin に DLL なし`: .NET Framework 4.7.1 以降は mscorlib に統合されており bin に置かれないのが正常
- カメラ SDK（`TIS.*`・`lumenera.*`）のロード失敗は `WARN:`（判定対象外）．SDK 未導入の環境では出うる
- `preview` / `calib-save` の所要時間は環境で変わる（判定条件ではない）

## 禁止操作 (Prohibited Operations)

- `--selftest` を付けない `TIASshot.exe` の起動（UI が開き，実機カメラ・光源（シリアルポート）へ接続を試みる）
  - 例外: ユーザーの合図があるときの `python scripts/verify/run_hwtest.py --camera <lucam|iscam>`（上記「実機テスト」節）．`TIASshot.exe --hwtest` の直接実行・合図なしの実行・`Release` の exe での実行はしない
- `config.xml`（リポジトリ内・bin 内とも）や本番の保存先フォルダ（`C:\TIAS_Data` 等）の編集・削除
- 自分が起動していないプロセスの停止（人間が起動した TIASshot・Visual Studio のデバッグ実行を止めない）
- `packages.bak-*` 以外のリポジトリ内ディレクトリの手動削除・改名（`packages` の退避と復元はランナーの `--full` だけが行う）
- ランナーを通さない独自のビルド・実行手順の組み立て（コマンドは本ファイルに書かれたものだけを使う）
- 失敗注入（bin 内 DLL の一時除去・`TIASshot.exe.config` の改ざん等）や `TIASshot.exe --selftest` の直接実行．セルフテストが依存の問題を検出できるかの確認が必要な場合は，人間が `docs/02_ENV/ENV_04_開発コマンド.md` の手順で行う（verifier には許可しない）

## シナリオのテンプレート (Scenario Template)

本プロジェクトでは **verifier がシナリオを書く必要はない**（確認項目は `TIASshot.exe --selftest` に組み込み済みで，引数で項目を選ぶ仕組みも無い）．verifier の作業は次のとおり．

```text
1. 変更内容に応じてコマンドを選ぶ
   - NuGet パッケージ・App.config（バインディングリダイレクト）・csproj の参照を変えた → --full
   - それ以外のコード変更 → 通常
2. python scripts/verify/run_selftest.py [--full] を実行する
3. 出力ディレクトリの summary.json と selftest-result.txt を Read で開き，
   - 各ステップ（clean / restore / build / tests / selftest）の成否
   - 各項目（deps / preview / calib-save）の [PASS]/[FAIL] と，FAIL: 行・WARN: 行の内容
   を確認する．PASS でも，今回の変更に関係する項目の詳細行（ロードされた DLL のバージョン等）を必ず見る
4. FAIL の場合は FAIL: 行（例外型・アセンブリ名・Fusion ログ）を証拠として報告する
   （build.log / test.log の該当箇所も併記する）
```

変更がセルフテストの 3 項目で観測できない領域（下記「自動で確認できない範囲」）にある場合は，その部分を「未確認」として人間に回す．

## 自動で確認できない範囲（人間が確認する項目）(Out of Scope)

セルフテストが PASS でも，以下は確認していない．変更がこれらに関わる場合は「人間が確認すべき残り項目」に具体的に書く．

- **実カメラ SDK との結合**: Lucam / IScam の接続・パラメータ設定・フレームコールバック・撮影（Snap・Sink 切り替え）・ROI 切り出し・白板撮影（`WHITEBOARD`）
- **実プレビュー表示**: 実ウィンドウへの描画・表示の滑らかさ・副ディスプレイでの表示位置・F9 トグル（`MEMMONITOR`）
- **実チャートの検出性能**: 実物の舌診チャートを実照明・実レンズで撮った画像での ArUco 検出・固定判定・マスク位置（合成チャートは理想条件）
- **画像の見た目**: 校正後の sRGB 画像の色・露出・ホワイトバランスが臨床的に妥当か（セルフテストは合成チャートでの数値一致のみ）
- **長時間のメモリ / GDI**: 長時間プレビュー・連続撮影でのメモリリーク・GDI ハンドル枯渇（`docs/06_TEST/TEST_03_メモリ修正効果計測.md` の手順）
- 光源（シリアル通信）の制御，効果音の再生，エラー時のダイアログ表示（セルフテストでは記録のみに差し替えている）
- `Release` 構成の実行（セルフテストを含まないため，起動確認は人間が行う）

## 実行基盤の要件 (Runner Requirements)

- 決定的な処理（ツールの検出・bin/obj の削除・パッケージ復元・ビルド・テスト・セルフテスト実行・結果の集計）はランナー `scripts/verify/run_selftest.py` が行う．verifier は判断（結果の読み取り・報告）のみ行う
- 実行ごとに `.verify/selftest/run-<日時>/` へ出力し，前回の証拠を上書きしない
- ランナーは自分が起動したプロセス（MSBuild・vstest・TIASshot.exe）以外を停止しない．セルフテストは内部のウォッチドッグ（5 分）でハング時も終了コード 3 で終わる
- 前提を満たさない場合は破壊的な復旧を試みず，終了コード 2 と理由を出して終了する
- セルフテストの実装は `TIASshot/SelfTest/` と `TIASshot/UI/*.SelfTest.cs`（`#if SELFTEST`），ビルドフラグは `docs/02_ENV/ENV_06_ビルド構成とビルドフラグ.md` を参照する
