EQ_S4_STAGE_PLAN（P28-02-EQ 段階 S4。最後の段階。EQ_STAGED_IMPLEMENTATION_PLAN r01/r02 の S4 を、実際の API と S1〜S3b の結合の要件に合わせて具体化する。SPEC、TASK、6条件、eq/references、eq/registration、補足 r01/r02/r03、段階の計画 r01/r02、S3 の計画 r01/r02、S3b の計画 r01/r02 は変更しない）

0. 共通
- 最初に eq/PROGRESS.md と eq/IMPLEMENTATION.md を読む。特に、S2 と S3a の「S4 must ...」の全ての箇条と、末尾の「S4 binding requirements」を、S4 の要件の一覧として最初に書き出し、各要件をどのコードと試験で満たしたかを IMPLEMENTATION.md に対応表で記録する。
- S1〜S3b の型・path・record schema・API・コード（check_eq.join_report、check_report、check_observation、check_lifecycle、strict_json、compare_versions.compare_reports など）を、そのまま使う。再設計しない。S4 が新しく作るのは eq/run_eq.py と eq/verify_eq.py と、その試験。
- 既存の119件の試験の本体、全ての失敗の履歴（S3b の attempt 02/03/07/08 を含む）、旧 run 7da027a の指示の誤りの記録は、消さない。書き直さない（失敗した試験が求める修正を除く）。
- 凍結した依存の API は、RootB/S4_FROZEN_DEPENDENCY_API_NOTES_R01.json（SHA 682a0d9a04978411691e9a8291b46a9ddc6eeb621d38c0f699a756e47be0084b）の要約に従う。collector（execution/p28-01/collector/collect_p28_01.py、SHA a43e1f96…）と restart_pair.py（SHA 538302ee…）は、読取専用で import して使う。複製や書き換えはしない。

1. eq/run_eq.py（Root が実行する driver。SPEC §2 の argv のまま）
1.1 --check（engine を起動しない）
- 引数と pin を検証して、実行の計画（7つの phase の argv、環境変数、出力の path、profile の名前）を JSON で出す。process は起動しない。
- 検証する pin: fixture の SHA（eb4f7700…）、engine の SHA（c7cccbf8…）、製品の source map の SHA（651f7036…、1407件の全ての相対 path・bytes・SHA を source_map(standard, root) で取る。件数だけにしない）、machine、EQ のファイル（6つ）の SHA、project.godot の元の bytes の SHA、設定・marker・policy の SHA。
- inputs(readonly) で、元の manifest の SHA と、元の絶対の destination の9件を検証する。複製した manifest の root で代用しない。
- --output の dir が既に存在すれば拒否する（上書きしない）。

1.2 --run の phase（この順序で、それぞれ1回）
- import: argv_for(engine, stage, wrapper, importing=True)。stage_gate(before, after, importing=True)。
- parse-base、parse-wrapper: parse_argv(engine, stage, target) の --check-only。stage_gate(before, after)。
- R-A、R-B、O-A、O-B: S1〜S3b で決まった R と O の起動の規則で、測定の wrapper を headless で起動する。
- 各 phase は run_private_engine(standard, outer, command, folder, environment, stage, public, name) で行う。public は project.godot の元の bytes、name は startup の profile の名前、folder は未作成の新しい dir。戻り値の owned row の全体を記録する。
- guard: parent の initialize の前に有効になること。startup と runtime の profile は、phase ごとに新しく一意の名前にする。環境変数 THE_TREE_PACK_PROFILE_NAME / EXPECTED / REPORT と、role（R/O）、fixture の path と SHA を結びつける。guard_gate(value, runtime: Path, name) の name は runtime の profile の名前。
- stage_gate と guard_gate は、成功で None、失敗で例外。戻り値を bool として扱わない（試験で拒否する）。
- profile_files(directory) は、存在しない dir で空の dict を返すが、それを fresh であることや、証拠の保存の成功の証明にしない。fresh であることは guard の受領で、保存の成功は下の 1.3 の複製と inventory で証明する。
- 各 phase で、collector の raw3（stdout、stderr、exit）と owned5 の受領、実際の PID と開始・終了の時刻、strict diagnostic（collector の規則のまま）を保存する。
- report の role（R/O）と、native の phase（R-A など）と、実際の PID を、driver が明示的に結びつけて記録する（role を phase 名から推測しない）。
- finally: 既定の player の設定・source・project.godot・guard・PID の後始末を、collector の既存の処理のまま保持し、後始末の結果（残った process 0、残った profile の扱い）を記録する。

1.3 証拠の複製と結合（owned の process の終了の後）
- R-A〜O-B の各 phase で、private profile の全ての証拠を複製し、全件の bytes と SHA を記録する: report（stdout の P28_02_EQ_REPORT= の1行）、lifecycle.json（measurement、e3、e3_b1、e45 を含む）、audio-close.json、events.json、observation、全ての e3-NN.json（00〜17）、全ての e45-NN.json（00〜20）、package の証拠。restart.copy_states は参考とし、EQ 自身の正確な名前の inventory で結びつける。欠けた・余分な・変わったファイルは拒否する。
- lifecycle の inventory が自分の hash と audio の受領を含まないことに従い、lifecycle.json と audio-close.json の SHA を、それぞれ独立に結びつける。lifecycle か audio が欠けている、または error を含む場合は、stdout の report があっても失敗にする。
- raw の JSON は strict_json で読む。各 phase で:
  - check_observation（raw の全 bytes と SHA の witness）
  - check_lifecycle（guard の受領、準備と後始末、S3a と S3b の分）
  - check_report(raw, measurement=lifecycle.measurement, e3_evidence=…, b1_record=…) に lifecycle.e45 を渡す
  - report.scenarios が E3 の18件 + E4/E5 の21件（E4 の5件 + E5 の16件）= 39件で、順序と区間の連続が正しいこと
- check_eq.join_report で raw を結合してから、compare_versions.compare_reports を呼ぶ。raw と、結合した出力と、join_facts の sidecar と proof と、checker の結果を、別々のファイルとして、それぞれの SHA とともに保持する。
- 同じ版の決定性: R-A と R-B、O-A と O-B を、それぞれ別に比べ、SPEC §3.2 の版の間の比較の差が0であることを確かめる。R と O を互いに比べない。
- 既存の不具合（latest_snapshot の aliasing、cached_digest_aliasing を含む）は、その結果のまま bundle に入れる。PASS に補わない。
- 全ての複製と結合の後に、bundle の manifest（全ファイルの相対 path・bytes・SHA）を書き、最後に seal（manifest の SHA）を書く。seal の後に書き込みをしない。

1.4 bundle の配置と失敗
- 7つの phase と 1.3 の全ての検査が成功した場合だけ、bundle を --output の下に作る。Root がそれを $BUILD_OUTPUT_DIR/p28-02-eq-evidence に、bytes をそのまま置く（verify_eq.py は場所を変えない）。
- 失敗した場合は、bundle を作らず、raw と失敗の記録（どの phase の、どの検査か）を --output に残し、0 以外の終了コードで終わる。失敗を成功に書き換えない。
- 表記: この記録は「S_eq の native」であって、Q で測ったものではない。source・設定・入力・marker・policy・engine・環境・EQ の同一性が証明された場合だけ、Q_eq での再利用ができる（SPEC §5）。bundle にこの区別を書く。

2. eq/verify_eq.py（正式の validation p28-02-eq-native-verify）
- --project-root . で、$BUILD_OUTPUT_DIR/p28-02-eq-evidence の bundle を読む。BUILD_OUTPUT_DIR が無い、bundle が無い、seal が無い場合は失敗にする。
- seal と manifest を検証し、欠けた・余分な・変わったファイルを拒否する。
- 結合した出力を信用せず、raw から 1.3 の全ての検査（strict_json、check_observation、check_lifecycle、check_report、join_report、compare_reports、39件、R と O の別々の決定性、既存の不具合の保持）をやり直し、bundle の記録と一致することを確かめる。
- pin（fixture、engine、source map 1407 の全件、machine、EQ のファイルの SHA、project・設定・marker・policy）が、--project-root の実際のファイルと一致することを確かめる。EQ のファイルの SHA は、検証の時点の W の実際のファイルと比べる。
- 7つの phase の全てで、raw3、owned5、PID の開始と終了、strict diagnostic、guard、後始末（残り0）がそろっていることを確かめる。
- 結果を1つの JSON で出し、成功だけ終了コード 0。

3. 試験（test_eq.py に追加。engine を使わない）
- bundle の正常（合成の bytes で作った bundle が verify の論理で通ること。これは native の証明ではないと試験の名前と出力に書く）。
- 拒否:
  - bundle・seal・manifest の欠落、余分なファイル、変わったファイル、seal の後の書込み
  - lifecycle・audio・e3-NN・e45-NN の欠落、error を含む lifecycle
  - 39件でない scenario、順序の違い、E4/E5 の21件の欠落
  - guard_gate・stage_gate の戻り値の bool としての扱い
  - 存在しない profile の空の dict を fresh や保存の成功とすること
  - 複製した manifest の root での代用、destination の9件の不一致
  - startup と runtime の profile の名前の取り違え、profile の再利用
  - role と phase と PID の結びつきの欠落や矛盾
  - raw3・owned5・PID の開始と終了の欠落、strict diagnostic の違反、後始末の残り
  - R と O の交差の比較、決定性の差が0でないのに合格にすること
  - join の前の compare、raw と結合した出力の混同、sidecar の欠落
  - 既存の不具合を PASS にすること、aliasing の記録の欠落
  - source map の件数だけの検査、1407件の1つの不一致、pin の不一致
  - Q で測ったという表記
  - --output の既存の dir への上書き、失敗の時の bundle の作成
- 既存の119件を含めて、python3 -B test_eq.py の全件 PASS まで直し、生出力と SHA を保存する。失敗した試行の生出力も残す。

4. 終わり（正式な提出）
- PROGRESS.md と IMPLEMENTATION.md（S4 の要件の対応表を含む）を更新する。
- 局所の全件 PASS を確かめた後、正式な JSON で提出する（status は blocked にしない）。提出は局所の unit の証拠であり、native の実行・受け入れた S・Q での測定・製品の PASS とは表記しない。6条件は、Root の native と両方の validation と fresh の独立した Opus のレビューまで保留のまま。
- worker は、engine の起動、run_eq.py --run の実行、git への書込み、製品の変更、全回帰をしない。正式の validation（bundle の無い状態の verify_eq.py を含む）を、試しに実行して失敗させない。commit と gate は Runner が通常どおり行う。
- 通常の実装上の判断は、記録して進める。

5. 提出の後の順序（Root と Runner。worker の範囲の外）
- Root が、凍結した S で native の7つの phase（import、parse-base、parse-wrapper、R-A、R-B、O-A、O-B）を run_eq.py --run で1回行い、成功した bundle を $BUILD_OUTPUT_DIR/p28-02-eq-evidence に置く。
- 両方の validation（p28-02-eq-unit、p28-02-eq-native-verify）。
- fresh の、別の Opus のセッションによる、6条件の全てのレビュー。
- 通常の prepare、候補の資格の確認、承認、integration、final。その後に OPT と whole-unit の gate。
- 原本の 23/88・4/30、原本の条件、課金、権限、900秒は変えない。
