
EvalArc の diff、eval-health、hillclimb-review、hillclimb-run に --output を付けると、自己完結したレポートフォルダーが書き出されます。中身は、すべての入力ファイルのバイト単位の複製と、全オプションを記録した params.json です。フォルダーを受け取った人は evalarc verify でオフラインに計算をやり直し、保存されたレポートが本当にその入力とオプションから得られたものかを確認できます。本記事では commit 8b134e2 に固定した実装・ドキュメント・回帰テストに基づき、フォルダーに何が保存されるか、verify がどう再計算するか、終了コード 0・1・2 がそれぞれ何に答えるか、そしてこの確認で証明できないことを説明します。
本サイトの EvalArc:スコアが上がったとき、エージェントはどこで退行したのか ではツールそのものを、モデルのアップグレードをレビューする:計画全体の一致は 15/24 から 19/24 へ、それでも EvalArc のゲートが不合格とする理由 では公開された記録からアップグレード判定を再計算する方法を紹介しました。本記事が扱うのは別の作業です。自分で計算したレポートを他の人に渡すとき、相手はそれが改変されておらず入力とも食い違っていないことをどう確かめるのか、という点です。以下の動作の説明はすべてソースとテストファイル([O]、プロジェクト自身のコード)に基づきます。テストのアサーションはコードに書かれた期待値であり、本サイトではテストを実行していません。
フォルダーに保存されるもの
evidence.py のモジュール docstring と docs/verification.md によると、フォルダーには次のものが入ります。
- 入力のバイト複製:結果ファイル、
split.json、cases.json、そしてスキャンされたハーネスファイル。ハーネスファイルはharness/0000、harness/0001……の順に保存され、それぞれのファイル名、スキャンラベル、SHA-256 がparams.jsonに記録されます。ハーネスの複製が合計 16 MiB を超えると、書き込みはエラーになります。 params.json:schema はevalarc.report-params.v1で、kind(diff、eval-health、hillclimb-reviewのいずれか)と、その実行のすべてのオプションを持ちます。たとえば diff のmax_cost_ratio、cost_metric、require_generalization、leak_min_chars、eval-health のsaturation、min_effect、ordered、plan_attempts、hillclimb-review のobjective、stall_after、min_steps、labelsです。- 保存されたレポート本体:
diff.json、health.json、hillclimb.jsonのいずれか。
入力を複製するとき、コードは読み取ったバイトが期待される SHA-256 と一致するかを確認し、一致しなければ読み取り中にファイルが変わったと報告します。フォルダー内のパスはすべて相対パスなので、移動やアーカイブができます。レポートには元のファイル名が記録されるため、複製の名前を変えても検証できます。
verify はどう再計算するか
evalarc verify はディレクトリ、またはその中の diff.json、health.json、hillclimb.json を受け取ると、まず識別できる params.json があるかを確認し、あればレポートフォルダーとして扱います。手順は次のとおりです。
params.jsonの schema とkindを確認し、レポートの schema がそのkindに対応していることを確かめます。- 各結果ファイルの複製を読み、その SHA-256 をレポートに記録された入力の識別情報と比較します。ハーネスの複製は
params.jsonの記録と比較します。絶対パス、..を含むパス、シンボリックリンクはすべて拒否されます。 - 記録されたオプションで
compute_diff、compute_health、compute_hillclimbのいずれかを呼び出します。モジュール docstring によれば、レポートを書くコマンドと verify は同じcompute_*関数を呼ぶので、両者が食い違うことはありません。 - すべての階層から
created_atとevalarc_versionのフィールドを除いたうえで、再計算の結果と保存されたレポートを比較します。少しでも違えば失敗です。
成功すると、schema が evalarc.verification.v1 の結果を出力します。内容は verified、kind、passed、レポートの種類ごとの要約(diff は gate_passed と blocking_changes、eval-health は healthy と warnings、hillclimb-review は recommendation と各ステップの decisions)、読み取った各ファイルの SHA-256 とバイト数、そして scope の説明です。
リポジトリの例でひととおり試す
回帰テスト tests/test_report_verify.py は、リポジトリ同梱のサンプルデータから 3 つのフォルダーを作ります。テストはコマンドラインのエントリー関数 main([...]) を直接呼んでいます。以下はそれに対応するコマンドラインで、固定したコミットのリポジトリのルートで実行します。
git clone https://github.com/noteflowai/evalarc.git
cd evalarc
git checkout 8b134e2374333adb54ace0aa81f7068e1c3b6881
これらのファイルにはインストール手順がありません。コミットをチェックアウトしても固定されるのはサンプルデータとソースコードで、PATH 上の evalarc は固定されません。実行する evalarc が同じコミットのものかを自分で確認してください。そうでないと結果が変わる可能性があります。
evalarc diff examples/results-diff/inspect/baseline.json \
examples/results-diff/inspect/current.json \
--held-out examples/results-diff/inspect/split.json \
--harness examples/results-diff/inspect/harness \
--max-cost-ratio 2 --cost-metric duration \
--output out/diff
evalarc eval-health examples/results-diff/inspect/baseline.json \
examples/results-diff/inspect/current.json \
--cases examples/results-diff/inspect/cases.json \
--min-effect 0.05 --ordered --plan-attempts 4 \
--output out/health
# テストは results/ 内の 0 で始まる JSON をファイル名順に渡す
evalarc hillclimb-review examples/hillclimb-review/results/0*.json \
--held-out examples/hillclimb-review/split.json \
--objective cost \
--harness examples/results-diff/inspect/harness \
--output out/climb
テストは毎回新しい一時ディレクトリに書き出します。この 3 つのコマンド自体の終了コードは確認せず、続く verify の結果だけをアサートしています。
evalarc verify out/diff --json # テストのアサーション:終了コード 0、kind=diff、passed=false
evalarc verify out/health --json # テストのアサーション:終了コード 0、kind=eval-health、passed=false
evalarc verify out/climb --json # テストのアサーション:終了コード 0、kind=hillclimb-review、passed=true
evalarc verify out/diff --json --require-accepted # テストのアサーション:終了コード 1
evalarc verify out/health --json --require-accepted # テストのアサーション:終了コード 1
evalarc verify out/climb --json --require-accepted # テストのアサーション:終了コード 0
テストはさらに、3 つの結果すべての files に params.json が含まれること、out/climb/hillclimb.json を直接渡しても 0 が返ること、hillclimb-run の例の出力フォルダーを verify に渡しても 0 が返ることもアサートしています。
終了コード 0・1・2 は何に答えるか
| 終了コード | 条件 | 答える問い |
|---|---|---|
| 0(既定) | 再計算の結果が保存されたレポートと一致 | 記録は自己矛盾がないか。ゲートが不合格でも、eval health が警告を出していても 0 |
1(--require-accepted 指定時) | 記録は一致するが passed が偽 | 結論は受け入れられるか |
| 2 | 入力・オプション・レポートが食い違う、またはフォルダーが不正 | 証拠そのものに矛盾や欠落がある。JSON 出力は verified: false |
verify.py は fully_resolved と accepted の両方を同じ passed に設定します。ドキュメントでは --require-resolved も同じ終了コードの規則に従うとされていますが、本記事が参照した tests/test_report_verify.py は、レポートフォルダーに対するその動作をテストしていません。
passed の意味はレポートの種類によって異なります。diff では、ゲートが合格し、汎化チェックを必須にした場合はそれも合格し、コスト上限を設定した場合はコストゲートも合格することです。eval-health では healthy が真であること(ドキュメントの表現では警告がないこと)です。hillclimb-review では merge_recommended が真、つまりマージが推奨されることです。テストの diff と eval-health のフォルダーは、既定の検証では 0、--require-accepted を付けると 1 になります。これがまさに要点で、「レポートは改変されていない」と「レポートの結論が合格である」は別のことです。
テストはまた、フォルダーを 1 か所ずつ改変し、verify がそれを検出することをアサートしています。
| 改変 | テストでの操作 | テストのアサーション |
|---|---|---|
| 判定の書き換え | hillclimb.json の 2 番目のステップの decision を keep に変更 | 終了コード 2、エラーに “differs from the recomputed” を含む |
| 入力の複製の書き換え | diff フォルダーの current.json にある最初の "C" を "I" に置換 | 終了コード 2、エラーに “differs from the input the report recorded” を含む |
| ハーネスの複製の書き換え | harness/0000 を clean prompt で上書き | 終了コード 2、エラーに “harness file that was scanned” を含む |
| オプションの変更 | eval-health の params.json で saturation を 0.5 に変更 | 終了コード 2 |
| フォルダーの移動 | climb フォルダーを別の場所へ移動 | 終了コード 0 |
docs/verification.md の “Consistency, acceptance and resolution” の節には、--require-accepted は suite にのみ適用されるという記述が残っています。これは以前の表現です。同じドキュメントのレポートフォルダーの節、verify が passed を accepted に対応づける実装、そして上記のテストは、いずれもこれを 3 種類のフォルダーに拡張しています。
制約と注意点
- verify の出力にある
scopeが範囲を明示しています。保存された入力のバイトと記録されたオプションから再計算し、保存されたレポートと比較するだけで、元のツールの採点は再実行せず、入力の出所を認証せず、HTML と Markdown は確認しません。 - 結果ファイルの複製の期待ハッシュは保存されたレポートに、ハーネスの複製のハッシュは params.json に記録されており、どちらも同じフォルダー内にあります。ハッシュ検査は結果ファイルとハーネスの複製の変更を検出します。split、cases、またはオプションの変更は、再計算の出力が変わる場合にだけ検出されます。入力・ハッシュ・レポートをまとめて整合するように書き換えられた場合は防げません。ドキュメントの言葉では、示し合わせた捏造でも内部的には矛盾がないことがあり、実行の証拠が必要なときは信頼できる採点を再実行すべきです。
- 比較では
evalarc_versionが無視され、再計算には検証する側がインストールした実装が使われます。作成側と検証側のバージョンが同じかどうかは別途確認が必要で、保存された入力のバイトはそれを示しません。 - 上記の終了コードとエラーメッセージはテストのアサーションであり、本サイトがテストを実行して観察した結果ではありません。本記事は性能、実際のユーザー、完全なセキュリティ監査については何も主張しません。
- 本記事は commit
8b134e2に基づきます。以降のバージョンでは、コマンド、フィールド、終了コードが異なる可能性があります。
リンク
- レポートフォルダーの書き込みと再計算:src/evalarc/evidence.py(commit 8b134e2)
- verify のエントリーポイント:src/evalarc/verify.py(commit 8b134e2)
- 回帰テスト:tests/test_report_verify.py(commit 8b134e2)
- ドキュメント:docs/verification.md(commit 8b134e2)
- 本サイトの関連記事:EvalArc:スコアが上がったとき、エージェントはどこで退行したのか · モデルのアップグレードをレビューする:計画全体の一致は 15/24 から 19/24 へ、それでも EvalArc のゲートが不合格とする理由



