From 971996f6d572042b863f1f3fbb974d7f4cbf25b1 Mon Sep 17 00:00:00 2001 From: Takeshi Date: Wed, 21 Oct 2026 01:21:00 +0900 Subject: [PATCH] P28-01-RESTART: Track SPEC_CLARIFICATION_02 (6-file import closure, execution contract) Frozen SPEC/DRAFT/baseline/contract unchanged; original acceptance RS1-RS8 unchanged. Co-Authored-By: Claude Opus 5.5 --- .../p28-01/restart/SPEC_CLARIFICATION_02.md | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 docs/plans/original-completion-2026-10-02/execution/p28-01/restart/SPEC_CLARIFICATION_02.md diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/restart/SPEC_CLARIFICATION_02.md b/docs/plans/original-completion-2026-10-02/execution/p28-01/restart/SPEC_CLARIFICATION_02.md new file mode 100644 index 0000000..a56fd5c --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/restart/SPEC_CLARIFICATION_02.md @@ -0,0 +1,84 @@ +# SPEC_CLARIFICATION_02(P28-01-RESTART:依存の閉包と実行契約の確定) + +- 対象: base `2132bbe3d39a0f1b69a97c731bb09213bf9d7a91` 上の SPEC.md / DRAFT.md / TASK_INSTRUCTIONS.md と、SPEC_CLARIFICATION_01 です。 +- 変えないもの: 元の受入 RS1〜RS8、baseline、contract、spec hash。製品、セキュリティ、権限は変更しません。 +- この文書の役割: repair1 run `facdc513fdd54a5e9899ecc7e4afb817` が blocked になった件(依存の閉包)に答えます。あわせて、残っている実行上の曖昧さを1回でまとめて確定します。 +- **これ以外の実装上の選択**(関数の分け方、名前、データ構造、ログの書式など)は、承認された範囲の中での worker の通常の判断です。原本の仕様の不足として blocked にしないでください。 + +## D1. import の閉包(完全な列挙。それ以外は禁止) + +import してよい project の Python は、次の6ファイルだけです(Root の読取専用の監査 `RESTART_DEPENDENCY_CLOSURE_READONLY.json`、SHA `06fdecc471a56d0edab9f58a68b33c433b1a5ab9d9db38024bd8e97e66166766` の6件と一致。第7のファイルはありません)。どれも読取専用で使い、変更しません。SHA は base 時点の値で、実行時に照合します。 + +| ファイル | SHA256 | 区分 | +|---|---|---| +| `tools/run_all_tests.py` | `320ca668732b4ff72fc4449998af2327d4e5c527b06896740806aaf0487282b4` | 承認済み | +| `tools/regression/owned_outer.py` | `c2e3483b76db1a3ff940f23f88ea0083fc78736e874ebcdb385d766dde8866e7` | 承認済み | +| `tools/regression/owned_diagnostic_process.py` | `e31e93c6e2c5ea46a7d954ef8bc0b4caec1c08b86b8aef19e93b52ef24993087` | 推移(owned_outer.py:9-10 が import 時に読み込む) | +| `tools/regression/p09_owned_engine_process.py` | `c007e712c281573254faa1233c2ac92e100ab87393256a6dc096b35283e43037` | 推移(owned_diagnostic_process.py:10-14 が SHA を照合してから exec する) | +| `docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py` | `a43e1f963ef8505ddd5acf8bf747d9bfac22a028cbb805c0b964ee11b9b40a14` | 承認済み | +| `docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py` | `6c0851d498ca80bdc120fad047b072255f48713f63114521f3d4dfc902b2512b` | 推移(collect_p28_01.py:16 が読み込む) | + +- **読み込み方**: collector は `importlib.util.spec_from_file_location` で読み込みます。その前に、collector のディレクトリを `sys.path` の先頭に追加します(collect_p28_01.py:16 の `from check_p28_01_report import ...` を解決するため)。run_all_tests.py と owned_outer.py は、collector の `load_tool(EXTERNAL_IMPORTS[i])` で取得してかまいません。 +- **使わないもの**: run_all_tests.py の `main()` の中で読み込まれる `tools/strict_test_markers.py`、`tools/regression/diagnostic_gate.py`、`ordinary_proof_gate.py`、`audio_evidence_gate.py` は使いません。run_all_tests.py の `main()` も呼びません。 +- **標準ライブラリ**: 通常どおり import してかまいません。新しいものの導入、third-party の package、ネットワークは禁止です。 +- **checker の検査**: 上の6ファイル以外の project の `.py` を import しようとしたら、拒否します。拒否試験も置きます。 + - 方法は2つ組み合わせます。AST で import 文と、`spec_from_file_location` / `exec` の文字列の path を走査することと、実行時に `sys.modules` の `__file__` を照合することです。 + - 6ファイルの SHA が1つでも違えば、起動の前に拒否します。 + +## D2. CLI と入力 + +- `restart_pair.py --check|--run --readonly --fixture --engine --output <新しいdir>`。collector と同じ形で、`--check` は計画の argv を出力するだけです。 +- guard は `/run-r02/profile_guard.gd` を使い、wrapper は collector と同じ形(`extends ""` + `Guard.activate()`)で生成します。Root はこの project の allocation にも、`build/p28-01-readonly` を同じ SHA で置きます。 +- 実行の順序は固定です: import → parse-base → parse-wrapper → R-writer → R-reader → O-writer → O-reader。 + - 各 phase は collector の `run_private_engine`(新しい private profile、owned、上限 1800 s)で実行します。 + - parse は collector の L2 と同じ規則です(`--check-only`、exit 0、SCRIPT ERROR/Parse Error/ERROR が無いこと)。 + - reader は、対応する writer が成功したときだけ起動します。どこかが失敗したら、そこで run 全体を止めます。 +- 役割と条件は env で渡します。`THE_TREE_PACK_PROFILE_*` は collector と同じものを使います。追加は `THE_TREE_RESTART_ROLE`、`THE_TREE_RESTART_K=30`、`THE_TREE_RESTART_TRANSFER=<絶対path>`、`THE_TREE_RESTART_TRANSFER_MANIFEST=` です。 + +## D3. 保存 package の受け渡し(profile を事前に書き換えない) + +- guard は、runtime profile が事前に新しいことと、guard より前の書込みが0であることを要求します。そのため、reader の profile に事前にファイルを置くことは**禁止**です。 +- writer の probe が正規の UI で保存した package を、driver が engine の終了後に writer の private user:// から `out/-transfer/good/` へ複製します。全ファイルの SHA を manifest に記録します。 +- 改ざん用のコピー `out/-transfer/tampered/` は、good の複製の `world.bin` の offset `len//2` の1バイトを XOR 0x01 したものです。offset と、改ざん前後の SHA を記録します。 +- reader の probe は guard が有効になった後で、transfer を絶対 path から読み、自分の `user://p28_restart/good/` と `user://p28_restart/tampered/` へ書き出します。manifest の SHA と一致しなければ、止めて失敗にします。そのあと、UI の読込で `user://` の path を使います。 + +## D4. 一時停止と保存の時点(実際の UI/Clock の経路) + +- 時間は実際の Clock(`_set_speed(1)` → `SimulationKernel._process`)だけで進めます。 +- 保存の時点は、`GameClock.tick_completed(absolute_minute)`(simulation_kernel.gd:9、:154)に受動接続して判定します。 + - R の writer: 開始から10分以上進んだ tick。 + - O の writer: 実際の arrival が >0 になった後の最初の tick。上限は120分です。 + - その signal の handler の中で `GameClock.set_paused(true)` を呼びます。kernel は tick ごとに paused を確かめるので、catch-up の途中でも止まります。 + - `set_paused(true)` は時間を進めない一時停止なので、許可します。`set_paused(false)` は従来どおり禁止です。`_toggle_pause` は使いません(Ops の acknowledge に分岐するため)。 +- overload で先に止まった場合は、その状態のまま保存します(RS5 の止まった状態での保存)。 +- arrival の判定は、guest_id が `guest-auto-` で始まる客(simulation_runtime.gd:2963、:3421)の実数(>0)と、`get_operations_report()` を併記して記録します。 + +## D5. 比較の実行場所と記録(役割分担) + +- 状態を正確に比べるのは probe です(GDScript、同じ engine の中)。 + - X(P, ev)、X(B, ev_fail)(CLARIFICATION_01)、各分の比較は、`var_to_bytes` の完全一致で判定します。 + - 一致しなかった場合は、差のある path の一覧(depth ≤ 4)を出力します。 +- 大きな状態(R は約11 MB)を全分保存することは避けます。記録するのは次のものです。 + - 全 bytes のファイル: P、writer の保存直後、reader の改ざん試行の前(B)、試行の後、読込直後、継続 K の最後の状態。 + - 毎分の値: 全状態の `var_to_bytes` の SHA256。 +- writer と reader の各分の照合: 両側とも、event_id E の要素1件(窓の中にある間)を、同じ正規形 `{"event_id": E, "replaced_for_cross_pid_compare": true}` に置き換えます。その後の全状態 bytes の SHA256 が、全分で一致することを要求します。両側の E の要素の全内容は記録します。この置き換えは DRAFT §3.3 d の規則と同じもので、広げてはいません。 +- SimulationDigest は参考の記録にだけ使い、一致の判定には使いません。全 bytes の SHA256 は bytes の完全一致の代わりとして扱います。 +- Python の checker が確かめるのは、report の形式・矛盾・SHA の結合と、両 PID の毎分の SHA の一致、そして D1/D7 の静的検査です(Godot の値の再構成はしません)。probe の実経路は静的レビューで確かめ、実際の実行は Root の owned PID と raw の結合で確かめます。 + +## D6. 終了と診断 + +- main を追加した phase は、collector の K1 と同じく `get_root().close_requested.emit()` で終了し、`P28_RESTART_AUDIO_CLOSE` の1行を要求します。 +- report は stdout の `P28_RESTART_REPORT=` ちょうど1行です。PID の結合、raw の SHA、owned folder へ書き込まないこと、は collector と同じです。 +- probe の自己停止は 1740 s です。 + +## D7. 静的検査(restart probe 用。SPEC §6 を具体化) + +- UI 区間で許可するもの: `_set_speed`、`_save_manual_at_path`、`_load_manual_at_path`、`GameClock.set_paused(true)`(tick 完了の handler の中だけ)、`close_requested.emit()`、受動的な connect。 +- 決定性区間: 単一の `func deterministic_step()` の中の `GameClock.advance_minutes(1)` だけを許可します。呼んでよいのは、保存か読込が成功し、clock が止まった後だけです。 +- 禁止: 決定性区間の外の `advance_`、`set_paused(false)`、`clear(`、`spawn_guest`、`SimulationRuntime.new`、`initialize(`、`enable_production_`、`_toggle_pause`、決定性区間の中の `Time.get_ticks_usec`。性能値の欄も禁止です。 + +## D8. 試験(test_restart.py) + +- SPEC §4 と CLARIFICATION_01 C4 のすべてに加えて、D1〜D7 それぞれに、正常試験と拒否試験を1つ以上置きます。 +- 試験は engine を使いません。合成した report と、collector の J2 と同じ「本物の owned_outer に偽の子プロセス」で動かします。 +- 例: 閉包の外の import、6ファイルの SHA の違い、事前に profile へ書き込むこと、transfer の SHA の不一致、`set_paused(false)` や `_toggle_pause`、毎分の SHA の不一致、E 以外の置き換え、writer の失敗の後に reader を起動すること。