Compare commits

...
4 Commits
13 changed files with 2321 additions and 9 deletions
@@ -0,0 +1,341 @@
{
"at_jst": "2026-10-21T07:10:02.150187+09:00",
"project": "the-tree-p28-02-diag",
"task": "P28-02-DIAG",
"state": "completed",
"S": "d6ce6ddb95e38ef987f37af98f02f0531483008f",
"Q": "fdb309f3bc81529135915429d2432a6644696311",
"M": "a25ee83441a967f9979e59b1c019f8b4d9ab6e22",
"all_four_normal_gates_passed": true,
"acceptance": [
{
"id": "P28-02-D1",
"requirement_id": "P28-02",
"text": "現在の source 1407 で、代表入力 R の tick を、tick ID で結びつけた inclusive の wall 区間 I0〜I11 として、受動的な観測だけで別 PID の3回で測る(observer の費用は別に記録し、差し引かない)。",
"validation_ids": [
"p28-02-diag-unit",
"p28-02-diag-native-verify"
]
},
{
"id": "P28-02-D2",
"requirement_id": "P28-02",
"text": "tick の外で、snapshot の変換を独立の inclusive 計測(N=5、合算しない)で、restore・save・load・validator を使い捨ての scratch でだけ測り、live の全状態の一致と参照の漏れの無さを確かめる。測れない粒度は理由つきの未測定にする。",
"validation_ids": [
"p28-02-diag-unit",
"p28-02-diag-native-verify"
]
},
{
"id": "P28-02-D3",
"requirement_id": "P28-02",
"text": "経路は path_query_measured の件数と時間だけを区間に結びつけ、connector の費用とは呼ばない。machine・build・seed・RNG state・入力・区間・平均・P95・最大・標本数を記録し、欠損を 0 で補わない。",
"validation_ids": [
"p28-02-diag-unit",
"p28-02-diag-native-verify"
]
},
{
"id": "P28-02-D4",
"requirement_id": "P28",
"text": "worker の提出した S を Root が隔離した stage で native 実行し(import・parse・3 PID)、owned PID・private profile・guard・cleanup・source map 1407・全 raw と SHA を結合する。最終候補 Q で測ったとは表記しない。",
"validation_ids": [
"p28-02-diag-unit",
"p28-02-diag-native-verify"
]
}
],
"normal_gates": [
{
"kind": "validation",
"commit": "d6ce6ddb95e38ef987f37af98f02f0531483008f",
"receipt": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/052a7f7671c34228938b3a5ffcda8723-validation.json",
"bytes": 616,
"sha256": "2222eaa7d85b2d17a115e9f21e0e1250922f009bebd7165054b7212bc3e68c01"
},
"checks": [
{
"id": "p28-02-diag-unit",
"exit_code": 0,
"raw": {
"command.json": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/3c6308e284d444ffb999c7ee5de2be84/command.json",
"bytes": 725,
"sha256": "12d665768693abcd00df5eb5c1acde89936b0e9dc45bd440d312aadb427a1506"
},
"stdout.log": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/3c6308e284d444ffb999c7ee5de2be84/stdout.log",
"bytes": 61,
"sha256": "5a0a7607403c4fb2bf309b678414c030a1852f7d3ef00640dfa08b3c263eaeeb"
},
"stderr.log": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/3c6308e284d444ffb999c7ee5de2be84/stderr.log",
"bytes": 2139,
"sha256": "4f3aa4e343f8ac766a1508daca44f37612ae4d1fea33383736213298ca69b8ec"
}
}
},
{
"id": "p28-02-diag-native-verify",
"exit_code": 0,
"raw": {
"command.json": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b8811a05af03429d9c579d7c37c107b0/command.json",
"bytes": 758,
"sha256": "c1491f7bad23953db88b418651957b72aa98744132c6f65865c085a15ce2d41d"
},
"stdout.log": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b8811a05af03429d9c579d7c37c107b0/stdout.log",
"bytes": 19889,
"sha256": "40d6c5ea04dc9717be2d76e4c3540499a947e7d2c174a4f10667d9cf6ede8fe2"
},
"stderr.log": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b8811a05af03429d9c579d7c37c107b0/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
}
}
]
},
{
"kind": "review",
"commit": "d6ce6ddb95e38ef987f37af98f02f0531483008f",
"receipt": {
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/diag-independent-review-d6ce6dd-r01/actual-independent-desktop-output/result.json",
"bytes": 5723,
"sha256": "00d38f98ef950fd14aeb6f3453ac0cab058212a7631df833bb7842cd4c840f6f"
},
"findings": [
{
"severity": "low",
"message": "inter_tick_frame_wall の最初の測定 tick(tick 31)の frame_wall_before_us には、B1 の ensure_cache と layers()(create_snapshot、約80 ms)による診断の作業が入っています。diag-1 では 596708 us で、これがこの run の最大値です。phase には帰属していませんが、最大値が診断の作業で膨らんでいることは注記されていません。OPT で frame の値を使うときは、この標本を区別してください。"
},
{
"severity": "low",
"message": "scratch の SaveService.save_snapshot と load_snapshot は、プロセス全体で共有する静的な SaveService._performance_budget に record_sample します。S/K/C の外にある状態です。IMPLEMENTATION.md に開示されていて、管制が範囲の外として採用済みで、製品にそれを読む箇所はありません(grep で確認)。ただし、計測の前後の値は report に記録されていません。"
},
{
"severity": "low",
"message": "main の _ready による save policy の設定は、設定後の値(report.startup.save_policy)だけが記録されています。設定前の値は記録されていません。S の SHA は main の追加の前後で一致しています。"
},
{
"severity": "low",
"message": "検証と restore の実測は envelope のもの(SimulationSnapshot.validate と restore。数〜数十 us)だけです。Runtime 全体の restore と内部の validator は、理由つきの未測定です。SaveService の load と save の inclusive の値(約235 ms と約170 ms)には、package の検証が含まれます。OPT の対象を選ぶときに、envelope の値を Runtime の検証の費用として扱わないでください。"
},
{
"severity": "low",
"message": "RNG の状態(typed hex)は、測定が終わった後の分 662 に1回だけ記録されています。測定の開始時点(分 632)の RNG は記録されていません。seed と、PID 間の RNG の一致は確認できます。"
}
]
},
{
"kind": "integration",
"commit": "fdb309f3bc81529135915429d2432a6644696311",
"receipt": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/9d2a0477dd644e3d95e50429aabfea42-integration.json",
"bytes": 617,
"sha256": "8e1a367415d7a1b14c6e26d880abd038e08ac0ca9ddf092cc486665b4ff0236c"
},
"checks": [
{
"id": "p28-02-diag-unit",
"exit_code": 0,
"raw": {
"command.json": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/2882e333b26e4e19966b92fed2311248/command.json",
"bytes": 726,
"sha256": "bfc9f305a54f2a3955cb9a2d032952b91ac64c3e68c025ce0a7e1e8fcfe58b65"
},
"stdout.log": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/2882e333b26e4e19966b92fed2311248/stdout.log",
"bytes": 61,
"sha256": "96c24d2529b7235a768427f4ad6ff161db12a1319845ac39ac1f214200b72041"
},
"stderr.log": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/2882e333b26e4e19966b92fed2311248/stderr.log",
"bytes": 2139,
"sha256": "32cbae529b357842a04b256fd70620465f40bb00fef29b87e9eaf4cbfb28108b"
}
}
},
{
"id": "p28-02-diag-native-verify",
"exit_code": 0,
"raw": {
"command.json": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/dedc2a26e33d48ef964dda06700545ee/command.json",
"bytes": 759,
"sha256": "22d8863ed04c822d5554f439e80db97e018449dcc6b0a9b234d84166bcba73e9"
},
"stdout.log": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/dedc2a26e33d48ef964dda06700545ee/stdout.log",
"bytes": 19889,
"sha256": "40d6c5ea04dc9717be2d76e4c3540499a947e7d2c174a4f10667d9cf6ede8fe2"
},
"stderr.log": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/dedc2a26e33d48ef964dda06700545ee/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
}
}
]
},
{
"kind": "final",
"commit": "fdb309f3bc81529135915429d2432a6644696311",
"receipt": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/8ee802edc437462b852d76e09905b22c-final.json",
"bytes": 611,
"sha256": "1333c5e4b95840685d6525e7e15b7d9cade6b6802f47eb147937c17b01c273df"
},
"checks": [
{
"id": "p28-02-diag-unit",
"exit_code": 0,
"raw": {
"command.json": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b366acde4390434b900e4f0a316204fa/command.json",
"bytes": 720,
"sha256": "46b2fee761a8af338b3ba93f55e82dca71c97cc01fb994c7164ab835421e0781"
},
"stdout.log": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b366acde4390434b900e4f0a316204fa/stdout.log",
"bytes": 61,
"sha256": "e7ab4a4864135eda18fb062c3d95f11ceb533ba94ed9d44688d5de5a1f7ce45c"
},
"stderr.log": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b366acde4390434b900e4f0a316204fa/stderr.log",
"bytes": 2139,
"sha256": "55f512a407a7814b1ce136fce796bb675c50312a268b03bdfb2b4033a67e04ca"
}
}
},
{
"id": "p28-02-diag-native-verify",
"exit_code": 0,
"raw": {
"command.json": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/139959117af94b56bd09e073bcc067fa/command.json",
"bytes": 753,
"sha256": "bf6ed61fd9ec888c91d2ef773a5fd14e1f1791a16cbba71dd7b6dd6d9568489a"
},
"stdout.log": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/139959117af94b56bd09e073bcc067fa/stdout.log",
"bytes": 19889,
"sha256": "40d6c5ea04dc9717be2d76e4c3540499a947e7d2c174a4f10667d9cf6ede8fe2"
},
"stderr.log": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/139959117af94b56bd09e073bcc067fa/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
}
}
]
}
],
"traceability": {
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/traceability/P28-02-DIAG.json",
"bytes": 2996,
"sha256": "d7f3beda2f6ef111baab28f6f53537345ef6f66b9eec753bb29b548d08ce63c1"
},
"candidate_qualification": {
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_DIAG_CANDIDATE_FDB309F_QUALIFICATION.json",
"bytes": 242987,
"sha256": "f04afc857ab4c9529f2f14370668655c9a7f5cc25ef213a0c2695b3ec2ff8e4b"
},
"review": {
"model": "claude-opus-5-5",
"session_id": "local_d0a7d3d0-aaef-4e3d-8761-185e62a3ac6b",
"context": "fresh",
"actual_result": {
"path": "docs/plans/original-completion-2026-10-02/execution/p28-02/DIAG_INDEPENDENT_REVIEW_D6CE6DD.json",
"bytes": 5723,
"sha256": "00d38f98ef950fd14aeb6f3453ac0cab058212a7631df833bb7842cd4c840f6f"
},
"delivery": {
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/diag-independent-review-d6ce6dd-r01/DESKTOP_DELIVERY.json",
"bytes": 1822,
"sha256": "3c58b811fc5353082bc1271f750a0afc2cb74e90858af2b26e147132843a49c4"
},
"original_null_delivery_preserved": {
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/diag-independent-review-d6ce6dd-r01/DESKTOP_DELIVERY.null-result-sha.preserved.json",
"bytes": 1675,
"sha256": "a3afbc7f6e8da0e4f2e3a6efcaa1d78391cbb8dbcff72945be311638e82b3be3"
},
"high_medium": 0,
"low": 5,
"findings": [
{
"severity": "low",
"message": "inter_tick_frame_wall の最初の測定 tick(tick 31)の frame_wall_before_us には、B1 の ensure_cache と layers()(create_snapshot、約80 ms)による診断の作業が入っています。diag-1 では 596708 us で、これがこの run の最大値です。phase には帰属していませんが、最大値が診断の作業で膨らんでいることは注記されていません。OPT で frame の値を使うときは、この標本を区別してください。"
},
{
"severity": "low",
"message": "scratch の SaveService.save_snapshot と load_snapshot は、プロセス全体で共有する静的な SaveService._performance_budget に record_sample します。S/K/C の外にある状態です。IMPLEMENTATION.md に開示されていて、管制が範囲の外として採用済みで、製品にそれを読む箇所はありません(grep で確認)。ただし、計測の前後の値は report に記録されていません。"
},
{
"severity": "low",
"message": "main の _ready による save policy の設定は、設定後の値(report.startup.save_policy)だけが記録されています。設定前の値は記録されていません。S の SHA は main の追加の前後で一致しています。"
},
{
"severity": "low",
"message": "検証と restore の実測は envelope のもの(SimulationSnapshot.validate と restore。数〜数十 us)だけです。Runtime 全体の restore と内部の validator は、理由つきの未測定です。SaveService の load と save の inclusive の値(約235 ms と約170 ms)には、package の検証が含まれます。OPT の対象を選ぶときに、envelope の値を Runtime の検証の費用として扱わないでください。"
},
{
"severity": "low",
"message": "RNG の状態(typed hex)は、測定が終わった後の分 662 に1回だけ記録されています。測定の開始時点(分 632)の RNG は記録されていません。seed と、PID 間の RNG の一致は確認できます。"
}
]
},
"actual_native": {
"measured_source": "S",
"candidate_Q_measured": false,
"execution": {
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/native-d6ce6dd-r01/execution.json",
"bytes": 87245363,
"sha256": "d2d13503d1b84b12872b693942809921140e019a7f32ef3f60c156291a649f58"
},
"bundle": {
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/native-d6ce6dd-r01/bundle.json",
"bytes": 10496,
"sha256": "6ea47083b338a59505e8347e2f556180bf9b21a8769298a58e5b91ce0a92d4d1"
},
"transfer": {
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/NATIVE_D6CE6DD_BUNDLE_TRANSFER.json",
"bytes": 879,
"sha256": "5a14d50e19707a0ac2a2d5571ba7e3301836ee8d64af01b8062163008cd1cd44"
},
"source_files": 1407,
"all_six_owned_processes_ended": true,
"source_and_player_unchanged": true,
"cleanup_errors": []
},
"failed_ecbbb6b_preserved": {
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_NATIVE_FAILURE_ECBBB6B.json",
"bytes": 4650,
"sha256": "1229e120ff3838285b9e4c18809e8ffaa890ed351a52bc2e99789c3df096ad96"
},
"timeout_failure_preserved": {
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/diag-repair2-timeout-09ac15a-r01/MANIFEST.json",
"bytes": 7892,
"sha256": "54d300efbb70f1b4d9a786ee09b287be25ec7393b1df9379db7f76bddaa83da5"
},
"original_completed": 23,
"original_total": 88,
"packages_completed": 4,
"packages_total": 30,
"partial_credit": false,
"original_credit": 0,
"scope": "DIAG support stage only. Original P28-02 remains in_progress until actual improvement, E1-E5, repeat comparisons, required regressions/restart, whole-unit review, integration/final and ledger.",
"remaining": [
"P28-02 EQ freeze and T1 OPT implementation/acceptance",
"Six original performance budget failures",
"200 guest Operations ON maximum scene",
"Same Windows build native-device acceptance",
"Long normal play and all original game/content/material acceptance"
]
}
@@ -0,0 +1,34 @@
{
"task_id": "P28-02-DIAG",
"commit": "d6ce6ddb95e38ef987f37af98f02f0531483008f",
"status": "pass",
"summary": "独立レビュー(読取専用)。整合性: packet a3a5b01b…、schema 04a4823f…、manifest 80348632… が一致し、117件すべてが manifest の SHA と bytes に一致。S は base 8c06c3b からの差分で、diag/** の8ファイルだけ。D1: probe は tick_started・tick_phase_started・tick_completed に受動的に接続し、tick ID と13本の境界を記録する。checker は phase0 を m、それ以外を m+1 として検査し、分ではなく ID で結びつける。I0〜I11 は隣り合う境界の差で、kernel の _run_phase(emit の後に handler)と照合して SPEC の定義どおり。observer の費用は callback ごとに別に記録し、差し引かない(semantics.observer_subtracted=false)。tick の間の frame は frame_wall_before_us の別欄。native の3 PID(74945・75718・76472)すべてで、warm 30・測定30・errors 0。D2: S/K/C を各標本の前後で bytes 比較する(K と C は S の観測より前に読み、観測の後に再確認する)。C 層は、規則 (a) を cache_preparations の restored・B1・outside_tick で記録。規則 (b)(c) は、miss のときに cache_miss_in_sample とし、公開の catalog 読取から再構築して比較する。前後の完全一致も要求する。native では、全標本、outside_total、manifest_detached が S・K・C ともに一致し、miss は0。scratch には live の deep copy を渡し、保存先は user://p28_diag/scratch/ の下。detached mutation の後も S/K/C は一致し、3つの instance ID は解放後に無効。observer の接続数は before が connected−1、disconnected が before と同じ。N=5 の独立した inclusive の値で、合算や100%への換算は無い。runtime_restore・runtime_validation_internal・connector_rebuild は samples=null と理由。Runtime の initialize が autoload の GameClock/GameState に結びつくことを source で確認し、理由は妥当。管制が採用済み。起動: 準備の状態変更は set_paused×2・get_var・restore_snapshot・main の add_child と2 frame だけで、checker が静的に強制する。B0/B1/outside の S SHA、K の6項目、C、Ops=INVALID_START_TIME は3 PID で一致。D3: path は query の件数と時間だけで、connector の値は null と理由。machine Mac16,10、engine の SHA と version、commit、seed、RNG 9本の typed hex、入力 eb4f7700… を記録し、各区間の平均・P95・最大・N を出す。D4: import・parse-base・parse-wrapper(--check-only)・3 PID は、exit 0、ERROR と WARNING が0、owned cleanup 済み、PID と profile は重複なし、guard 一致、source map 1407、bundle の72ファイルが SHA 一致。measured_source は S で、candidate_Q_measured は false。正式 validation は unit 20件 OK と native-verify verified。順位は上位3(I10・I4・I11)が3回とも一致する一方、run3 の I2 と I7 の入れ替わりで rankings_agree=false。これは raw と verify の出力にそのまま記録されていて、隠されていない。ecbbb6b の parse の失敗は別に保全され、成功扱いにはなっていない。原本の加算は0(23/88、4/30)。P28-02 全体や OPT の合否は判定していない。指摘は low の注記だけ。",
"acceptance_ids": [
"P28-02-D1",
"P28-02-D2",
"P28-02-D3",
"P28-02-D4"
],
"findings": [
{
"severity": "low",
"message": "inter_tick_frame_wall の最初の測定 tick(tick 31)の frame_wall_before_us には、B1 の ensure_cache と layers()(create_snapshot、約80 ms)による診断の作業が入っています。diag-1 では 596708 us で、これがこの run の最大値です。phase には帰属していませんが、最大値が診断の作業で膨らんでいることは注記されていません。OPT で frame の値を使うときは、この標本を区別してください。"
},
{
"severity": "low",
"message": "scratch の SaveService.save_snapshot と load_snapshot は、プロセス全体で共有する静的な SaveService._performance_budget に record_sample します。S/K/C の外にある状態です。IMPLEMENTATION.md に開示されていて、管制が範囲の外として採用済みで、製品にそれを読む箇所はありません(grep で確認)。ただし、計測の前後の値は report に記録されていません。"
},
{
"severity": "low",
"message": "main の _ready による save policy の設定は、設定後の値(report.startup.save_policy)だけが記録されています。設定前の値は記録されていません。S の SHA は main の追加の前後で一致しています。"
},
{
"severity": "low",
"message": "検証と restore の実測は envelope のもの(SimulationSnapshot.validate と restore。数〜数十 us)だけです。Runtime 全体の restore と内部の validator は、理由つきの未測定です。SaveService の load と save の inclusive の値(約235 ms と約170 ms)には、package の検証が含まれます。OPT の対象を選ぶときに、envelope の値を Runtime の検証の費用として扱わないでください。"
},
{
"severity": "low",
"message": "RNG の状態(typed hex)は、測定が終わった後の分 662 に1回だけ記録されています。測定の開始時点(分 632)の RNG は記録されていません。seed と、PID 間の RNG の一致は確認できます。"
}
]
}
@@ -0,0 +1,93 @@
# 過去の blocked 記録(repair2 で解消済み)
OPUS_STARTUP_LIFECYCLE_CLARIFICATION により、以下の pause の質問も解消しました。
この文書は履歴です。現在の実装・検証状況は IMPLEMENTATION.md と UNIT_OUTPUT.txt を参照してください。
以下の「未提出」「未実行」は、当時の状態を記録したものです。
## P28-02-DIAG: repair1 起動時の pause 呼出しの適用範囲
## repair1 の判断
OPUS_D2_SCOPE_CLARIFICATION により、以前の S/K/C の比較境界の質問は
解消しました。以下の以前の記録は履歴であり、現在の阻害理由ではありません。
残る確認は、起動準備で probe が `GameClock.set_paused(true)` を呼んでよいかです。
補足の「collector の SPEC §4.3 と同じ順序」に参照される
`execution/p28-01/SPEC.md` の §4.3 手順2は、autoload 準備後、R の復元前に
この呼出しを要求しています。一方、P28-02 の SPEC §3.1 と TASK_INSTRUCTIONS は、
live を変更できる API を fixture の restore と `advance_minutes(1)` のみに
限定しています。補足は main の追加を明示していますが、probe によるこの
pause 呼出しの例外は明示していません。
`scripts/core/simulation_kernel.gd:175-178` の `set_paused(true)` は
`paused` と `_pause_request_serial` を変更します。後者は今回明示された K 層の
対象です。したがって、読取 API とみなすことはできません。
main の `_ready` による副作用は明示された main 追加に伴うものとして扱えますが、
それとは別の probe の呼出しについて、許可を独自に広げません。
必要な確定は、collector §4.3 手順2の pause を起動準備の例外として許すのか、
DIAG ではその手順を省略して restore → main 追加とするのか、の一点です。
collector の windowed 実行や速度設定を DIAG に持ち込む要求ではありません。
確認した SPEC の strip SHA は
`0420dd4d2a95db65adc685bca91b9ff686650af31675f193992d75909a676ac6`、
raw SHA は `f58378b6cb317478f9d26776d0a236804f4ac80789d93f1555133298e4328d24`。
TASK_INSTRUCTIONS の raw SHA は
`b9b635b2598aee32efac662aa21d21fc88f55fbfdc6097cf07483e7fa5d32a7d` と一致しました。
実装 S は未提出、unit と native は未実行、PASS の主張はありません。
git 書込み、他エージェント起動、Runner 操作、製品変更は行っていません。
## 以前の記録(S/K/C の質問は解消済み)
対象 base: `8c06c3b44235a52f35898b1f6263e8ee064af256`。
実装提出ではなく、仕様判断への差戻しです。受入の達成は主張しません。
## 必要な判断
SPEC §3.1・§3.3 と D2 の「live の全状態」は、
`SimulationRuntime.create_snapshot()` が返す保存対象の全状態を指すのか、
snapshot に含まれない内部状態も指すのかを確定してください。
前者なら、返された Dictionary 全体の `var_to_bytes` 完全一致を比較範囲として
明記する必要があります。後者なら、内部状態の対象と観測方法
(Object 参照、循環参照、signal 接続、性能計測状態などの扱いを含む)が必要です。
worker が独自に対象を除外して「全状態一致」と報告することはしません。
## base のソースで確認した根拠
- `scripts/core/simulation_kernel.gd:39-46` に性能計測状態、`_accumulator`、
`_skip_next_process`、handler 登録、pause serial などが存在します。
同ファイル `clock_snapshot()`(209-215行)は `absolute_minute`、`speed`、
`paused`、`snapshot_version` の4項目だけを返します。
- `scripts/core/simulation_snapshot.gd:6-14` は、この `clock_snapshot()` と
GameState の snapshot を取り込みます。
- `autoload/simulation_runtime.gd:521-585` の `create_snapshot()` は
上記 envelope に各 system の保存用 snapshot を追加します。
同ファイル134-136行の `_mvp_manifest_cache_catalog`、
`_mvp_manifest_cache_generation`、`_mvp_manifest_cache` は含まれません。
- 同ファイル560行から呼ぶ `_create_mvp_content_manifest()` は、
11189-11195行で cache hit を判定し、miss 時にはこの内部キャッシュを書き換えます。
したがって `create_snapshot()` の結果を前後で比較するだけでは、
その呼出しによる内部キャッシュの変更を検出できません。
これは実際の測定区間で cache miss が発生したという報告ではありません。
warm 30分によって cache が構築される可能性はあります。しかし、cache hit の場合も、
snapshot の bytes 一致が証明する範囲と「全状態」の要求範囲を確定する必要があります。
読み取り API としての `get_property_list()` / `get()` による独自走査を採る場合も、
参照や実行時メタデータをどこまで含めるかは、この受入境界の判断になります。
## 確認済み事項と作業状態
- AGENTS.md、SPEC.md、TASK_INSTRUCTIONS.md と RESTART CLARIFICATION_02 を読取。
- SPEC の `sha256(text.strip())` は契約値
`0420dd4d2a95db65adc685bca91b9ff686650af31675f193992d75909a676ac6` と一致。
raw-bytes SHA との差を阻害理由にはしていません。
- 許可された外部 Python 依存6ファイルの SHA は CLARIFICATION_02 D1 とすべて一致。
- tick ID による結合、inclusive 区間、observer 別記録の仕様に不足は見つかっていません。
- scratch を構成できない項目は理由つき未測定にする規定があるため、
その可否自体を今回の阻害理由にはしていません。
- 変更はこの `diag/BLOCKED.md` のみ。driver・probe・checker・verify・unit 試験は未実装。
- unit 試験、engine、正式 validation は未実行。PASS の主張はありません。
指定 tmp 配下の一時ディレクトリ作成・削除の確認のみ実施しました。
- git 書込み、他エージェント起動、Runner 操作、製品変更は行っていません。
@@ -0,0 +1,91 @@
# P28-02-DIAG repair2 実装提出
対象: P28-02-D1/D2/D3/D4。変更範囲はこの diag ディレクトリのみ。
製品、SPEC、TASK_INSTRUCTIONS、validation、予算、分母は変更していません。
git 書込み、engine 起動、他エージェント、Runner 操作は行っていません。
局所試験は engine 不使用の `python3 test_diag.py`。生出力は UNIT_OUTPUT.txt。
native import/parse/3 PID、正式 validation、独立レビューは未実行で、Runner/Root の担当です。
適用した補足(ユーザー DATA の全文):
- OPUS_D2_SCOPE_CLARIFICATION: `5775e7b512d9e90dbcfbcb181f5b64dc0fa8207e0947c0a4a6a5c9195565f9bd`
- OPUS_STARTUP_LIFECYCLE_CLARIFICATION: `967aebf27ff412f7a16ce771ef7d68abf8cfde0bb76ed19cf6dbf2817ca7acc6`
- SPEC strip SHA: `0420dd4d2a95db65adc685bca91b9ff686650af31675f193992d75909a676ac6`
## 実装上の選択
- startup は pause → R の SHA と get_var(false) → restore → pause → main → 2 frame。
main の前後、各 frame 後、B0/B1、復元直後を記録します。speed 操作や resume はありません。
- warm と measurement は各30回の advance_minutes(1)、各回後に1 frame await。
tick ID、13本の信号境界、query を全件残します。I0〜I11 は checker が差分で算出します。
tick 完了後から次の tick 開始までの wall は独立欄です。warm は順位・統計から除きます。
- wall は observer を含む inclusive 値です。callback 自身の時間を別記し、引きません。
自身の最終時刻取得後の timing record の追加・return は自己費用の計測外です。
- 各区間について平均・nearest-rank P95・最大・N、query 件数・合計・統計を再計算します。
平均 wall の降順(同値時は区間番号)による3回の順位と一致結果を保持します。
query が0件なら件数・合計は実測0、query の平均等は null と理由にします。
- Runtime、GameState、Runtime が create_snapshot を呼ぶ32 service を各 N=5 で独立計測。
service 一覧と現在の製品 source の一致も unit で検査します。合算はしません。
- 各 sample 前後の S/K/C の bytes を probe 内で完全比較し、全層の SHA、K の値、C の状態、
差分 path を report に残します。S 不一致時のみ型・key・配列順を保持する正規形で差を説明し、
dict 列挙順だけの差も失敗扱いです。診断では OPT の同等性緩和を適用しません。
- C を S の観測前に読んで、観測自体の cache 書換えを隠さない構成です。
cache miss は値を保持したまま明記し、公開 catalog 読取から manifest の一致も検査します。
前後 C 完全一致の条件を満たさなければ失敗です。返却 manifest の top-level と配列を
caller 側で変更して live が変わらないことも検査します。
- scratch は snapshot の deep copy と tree に追加しない Kernel/GameState です。
deep copy、SimulationSnapshot.validate、SimulationSnapshot.restore(envelope のみ)、
SaveService.save_snapshot/load_snapshot を各 N=5。load 用保存は計測外です。
各操作後に scratch のコンテナを変更して S/K/C が保たれることを検査し、
Kernel/GameState/budget の instance ID が解放後に無効であることを検査します。
保存先は guard 後の private user://p28_diag/scratch/ 配下です。
- Runtime 全体の restore は initialize が必要で、列挙された scratch API 外のため理由つき未測定。
Runtime 内部 validator と connector 再構築も、公開の独立観測境界がないため理由つき未測定。
envelope の値を Runtime 全体の値、query の値を connector 全体の値とは表記しません。
- SaveService は静的 API ですが、渡すデータと path は scratch だけです。静的な SaveService 自身の
診断 budget は更新されます。これは補足の S/K/C 境界には含まれません。live Kernel の budget は
全量比較の対象です。Runtime オブジェクトを scratch として起動したという主張はしません。
- observer は main と2 frame の準備後に接続します。指定4 signal に加え、autosave の発生を
記録する save_operation_completed にも受動接続し、終了前に全て元の接続一覧へ戻します。
- PID 内の C 同一性には instance ID を含めます。PID 間では ID の数値を比べず、同一 catalog を
指す関係・generation・manifest bytes を比較します。K の PID 間比較は補足に列挙された6項目
(minute、paused、pause serial、skip、snapshot version、handler 集合)です。K の全 SHA と
budget の実測時間は PID 間の同一条件にしませんが、raw と PID 内の検査には全量残します。
- 初期条件不一致は値を raw に保持して失敗し、管制判断へ戻します。実測を補正しません。
## Root の実行インターフェース(worker は実行しない)
凍結した S を checkout した環境で、以下の CLI を使います。`--check` は読取と計画の表示だけです。
`--run` は import → parse-base → parse-wrapper → diag-1 → diag-2 → diag-3 を順番に実行します。
```
python3 diag_p28_02.py --check|--run --readonly <p28-01-readonly> --fixture <reference.variant> --engine <Godot> --output <new-directory>
```
driver は6ファイルの依存 SHA を確認して collector を読取専用 import します。
RESTART の依存検査・owned raw 結合のコードを局所に適合して再利用していますが、
RESTART の Python 自体は import しません。追加 package、ネットワークは使いません。
全1407 source と提出した5コードファイルが frozen commit S と同じことを確認します。
private profile、guard、1800秒 owned timeout、audio close drain、cleanup は collector と同じ経路です。
成功時、driver が execution.json と、stage 以外の全ファイルを SHA/size で列挙した bundle.json を
作ります。これは Root による実行結果です。失敗時は execution.json を保存し、成功 bundle を作りません。
stage は Root に残し、各 phase の全 stage inventory を execution.json に記録します。
全 raw、owned receipts、guard、startup project、probe/wrapper、入力 manifest は bundle に含みます。
通常の profile ファイルも collector と同様に inventory を保存し、owned process cleanup と混同しません。
Root は出力一式(stage は省略可)を `$BUILD_OUTPUT_DIR/p28-02-diag-evidence` に置きます。
`python3 verify_diag.py --project-root .` は bundle 欠落を拒否し、全 raw/SHA/PID/guard/cleanup と
現候補の1407 source・5コードの一致を結合します。S の実測として記録し、Q の実測とは呼びません。
bundle 作成後にファイルを追加・編集すると inventory 不一致で拒否します。
原本加算は0、正式完了23/88、4/30、予算 false 6件を保持します。P27 完了は主張しません。
repair3: 最終の局所 unit を再実行して PASS(19件、0.780秒)。
## repair4: native parse の静的依存修正
- P1: probe の manifest version は、実際の Runtime の `get_script().get_script_constant_map()` から準備段階で取得します。script・key の存在と int 型を確認し、取得した値そのものを manifest 比較の期待値へ渡します。取得不能なら理由を errors に記録して終了します。値の固定や検査対象 manifest の流用はありません。
- P2: `autoload/*.gd` の class_name 宣言を確認し、probe の該当する静的参照は除去した1箇所だけでした。残る GameStateScript の preload 先、MvpContentCatalog と GuestDefinition には autoload 識別子への依存がありません。engine による compile 成功は未確認です。
- P3: checker は autoload の class_name を製品ファイルから機械的に抽出し、probe の識別子との一致を拒否します。公開読取の get_script / get_script_constant_map を既存の許可リストに追加しました。修正後 probe の通過、全抽出名の定数参照・型注釈の拒否、新しい宣言の自動検出を最小の追加 unit で確認しました。
- `python3 test_diag.py`: 20件 PASS、0.861秒。標準出力・標準エラーの生出力を UNIT_OUTPUT.txt に保存しました。変更は diag_probe.gd、check_diag.py、test_diag.py、UNIT_OUTPUT.txt、IMPLEMENTATION.md のみ。BLOCKED.md は解消済みの履歴として維持します。
- native import・parse・3 PID、正式 validation、独立レビューは提出後の Root 側で実施します。worker は engine・Runner 操作、git 書込み、製品変更、全回帰、他エージェント起動を行っていません。原本加算0、23/88、4/30、予算 false 6件を維持します。
@@ -0,0 +1,26 @@
test_autoload_class_references_rejected (__main__.DiagnosticTests) ... ok
test_bundle_missing_and_tampered (__main__.DiagnosticTests) ... ok
test_connections_and_preparation (__main__.DiagnosticTests) ... ok
test_cross_pid_only_named_kernel_fields (__main__.DiagnosticTests) ... ok
test_fixture_pause_and_int64_identity (__main__.DiagnosticTests) ... ok
test_headless_plan_and_stop_on_failure (__main__.DiagnosticTests) ... ok
test_import_closure_and_pins (__main__.DiagnosticTests) ... ok
test_missing_not_zero (__main__.DiagnosticTests) ... ok
test_nonadditive_scope (__main__.DiagnosticTests) ... ok
test_pid_local_catalog_identity (__main__.DiagnosticTests) ... ok
test_queries_and_observer_rejection (__main__.DiagnosticTests) ... ok
test_real_owned_python_child_and_raw_binding (__main__.DiagnosticTests) ... RUNTIME_SCALE_PROCESS_START {"name": "import", "pid": 70915}
ok
test_scratch_ownership (__main__.DiagnosticTests) ... ok
test_service_snapshot_inventory_matches_current_product (__main__.DiagnosticTests) ... ok
test_source_map_count_and_mutations (__main__.DiagnosticTests) ... ok
test_state_and_cache_mismatch (__main__.DiagnosticTests) ... ok
test_static_probe_and_product_mutations (__main__.DiagnosticTests) ... ok
test_three_pid_comparison (__main__.DiagnosticTests) ... ok
test_tick_id_and_minute_rejection (__main__.DiagnosticTests) ... ok
test_valid_raw_intervals (__main__.DiagnosticTests) ... ok
----------------------------------------------------------------------
Ran 20 tests in 0.861s
OK
@@ -0,0 +1,606 @@
#!/usr/bin/env python3
"""DIAG evidence validation; no engine launch."""
import ast
import copy
import hashlib
import importlib.util
import json
import math
from pathlib import Path
import re
import sys
import sysconfig
HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[5]
BASE = "docs/plans/original-completion-2026-10-02/execution/p28-01/"
DEPENDENCIES = {
"tools/run_all_tests.py": "320ca668732b4ff72fc4449998af2327d4e5c527b06896740806aaf0487282b4",
"tools/regression/owned_outer.py": "c2e3483b76db1a3ff940f23f88ea0083fc78736e874ebcdb385d766dde8866e7",
"tools/regression/owned_diagnostic_process.py": "e31e93c6e2c5ea46a7d954ef8bc0b4caec1c08b86b8aef19e93b52ef24993087",
"tools/regression/p09_owned_engine_process.py": "c007e712c281573254faa1233c2ac92e100ab87393256a6dc096b35283e43037",
BASE + "collector/collect_p28_01.py": "a43e1f963ef8505ddd5acf8bf747d9bfac22a028cbb805c0b964ee11b9b40a14",
BASE + "collector/check_p28_01_report.py": "6c0851d498ca80bdc120fad047b072255f48713f63114521f3d4dfc902b2512b",
}
LOCAL_NAMES = {"diag_p28_02.py", "check_diag.py", "verify_diag.py", "test_diag.py", "diag_probe.gd"}
LOCAL = {"docs/plans/original-completion-2026-10-02/execution/p28-02/diag/" + n for n in LOCAL_NAMES}
PHASES = ["import", "parse-base", "parse-wrapper", "diag-1", "diag-2", "diag-3"]
FIXTURE_SHA = "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd"
ENGINE_SHA = "c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf"
SEMANTICS = {"intervals": "inclusive_wall_including_observer", "observer_subtracted": False,
"breakdown": "independent_inclusive_not_additive", "path": "query_only_not_connector"}
SERVICES = ['weather', 'command_queue', 'id_generator', 'domain_events', 'world_service',
'path_service', 'mana_network_service', 'mana_reserve', 'economy_service', 'facility_upkeep',
'upkeep_settlement', 'inventory_service', 'kitchen_production', 'auto_purchase_plans',
'elevators', 'legacy_stairs', 'logistics_dispatcher', 'facility_service', 'facility_cleanliness',
'guest_service', 'staff_service', 'progression_service', 'relationship_service',
'event_service', 'authority_service', 'defense_service', 'construction_service',
'excavation_service', 'spatial_subslot_service', 'route_subslot_path_adapter',
'facility_infrastructure_projector', 'peeping_room_service']
SCRATCH = {'deep_copy', 'envelope_validate', 'envelope_restore', 'save', 'load'}
UNMEASURED = {'runtime_restore', 'runtime_validation_internal', 'connector_rebuild'}
K_FIELDS = {'absolute_minute', 'speed', 'paused', 'snapshot_version', '_skip_next_process',
'_pause_request_serial', '_abort_current_tick_requested', '_last_tick_advanced',
'_accumulator_f64_hex', 'handlers', '_sample_counts', '_totals', '_samples'}
def require(condition, message):
if not condition:
raise ValueError(message)
def sha(path):
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
def pin(path):
path = Path(path)
require(path.is_file() and not path.is_symlink(), "missing/symlink evidence")
return {"sha256": sha(path), "bytes": path.stat().st_size}
def check_dependencies(root=ROOT):
for name, expected in DEPENDENCIES.items():
require(sha(root / name) == expected, "dependency SHA: " + name)
def check_imports(text, external=None):
tree = ast.parse(text)
if external is not None:
require(external in DEPENDENCIES, "external AST closure")
# The explicitly forbidden run_all_tests.main imports are not executed.
tree.body = [node for node in tree.body if not (
isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == "main")]
allowed = {Path(p).stem for p in set(DEPENDENCIES) | LOCAL}
for node in ast.walk(tree):
if isinstance(node, (ast.Import, ast.ImportFrom)):
names = [node.module] if isinstance(node, ast.ImportFrom) else [a.name for a in node.names]
for name in names:
require(name and (standard_library(name.split(".")[0]) or name in allowed),
"import outside closure")
if isinstance(node, ast.Call):
name = node.func.id if isinstance(node.func, ast.Name) else getattr(node.func, "attr", "")
require(name not in ("__import__", "eval", "import_module"), "dynamic code forbidden")
require(name != "exec" or external == "tools/regression/owned_diagnostic_process.py",
"unapproved exec")
if name == "spec_from_file_location":
for sub in ast.walk(node):
if isinstance(sub, ast.Constant) and isinstance(sub.value, str):
for path in re.findall(r"[\w./-]+\.py", sub.value):
require(path in DEPENDENCIES or path in LOCAL or
path in {Path(p).name for p in DEPENDENCIES}, "loader outside closure")
if external and isinstance(node, ast.BinOp):
for sub in ast.walk(node):
if isinstance(sub, ast.Constant) and isinstance(sub.value, str) and sub.value.endswith(".py"):
require(sub.value in DEPENDENCIES or sub.value in {Path(p).name for p in DEPENDENCIES},
"external loader path outside closure")
def standard_library(name):
# Python 3.9 on the native host has no sys.stdlib_module_names.
spec = importlib.util.find_spec(name)
if spec is None:
return False
if spec.origin in ("built-in", "frozen"):
return True
if not spec.origin:
return False
path = Path(spec.origin).resolve()
return path.is_relative_to(Path(sysconfig.get_path("stdlib")).resolve()) and not any(
part in ("site-packages", "dist-packages") for part in path.parts)
def check_modules(modules=None, root=ROOT):
for module in (sys.modules if modules is None else modules).values():
name = getattr(module, "__file__", None)
if name:
path = Path(name).resolve()
if path.is_relative_to(root):
require(path.relative_to(root).as_posix() in set(DEPENDENCIES) | LOCAL,
"loaded project module outside closure")
def autoload_class_names(root=ROOT):
paths = sorted((root / 'autoload').glob('*.gd'))
require(paths, 'autoload scripts missing')
return {name for path in paths for name in
re.findall(r'^\s*class_name\s+([A-Za-z_][A-Za-z_0-9]*)\b', path.read_text(), re.M)}
def check_probe(text, root=ROOT):
"""Conservative finite call policy, plus mutation-site checks; native parse is separate."""
code = '\n'.join(line.split('#', 1)[0] for line in text.splitlines())
tokens = re.sub(r'"(?:\\.|[^"\\])*"', '""', code)
identifiers = set(re.findall(r'\b[A-Za-z_][A-Za-z_0-9]*\b', tokens))
require(not identifiers.intersection(autoload_class_names(root)),
'autoload class_name reference in probe')
forbidden = (r'\b(?:set_script|set_process|set_physics_process|set_deferred|callv?|call_thread_safe|'
r'call_deferred_thread_group|emit_signal|subscribe|unsubscribe|initialize|reset|'
r'clear_performance_reports|register_tick_handler|unregister_tick_handler|'
r'process_real_delta|advance_one_minute|_set_speed|_toggle_pause)\s*\(')
require(not re.search(forbidden, tokens), 'forbidden product/hook call')
require(not re.search(r'\b(?:clock|runtime|main|target)\s*(?:\.[\w]+|\[[^\n]+\])+\s*(?:[+*/-]?=)(?!=)', code),
'live property assignment')
require(not re.search(r'\b(?:set|set_indexed)\s*\(', code), 'dynamic mutation')
functions = dict(re.findall(r'^func (\w+)\([^\n]*\).*?:\n(.*?)(?=^func |\Z)', code, re.M | re.S))
require(functions and len(functions) == len(re.findall(r'^func ', code, re.M)), 'probe functions')
for name, body in functions.items():
if 'clock.set_paused(' in body:
require(name == 'begin' and body.count('clock.set_paused(true)') == 2 and
body.count('clock.set_paused(') == 2, 'pause only twice in preparation')
require(body.rfind('clock.set_paused(') < body.index('main = load('), 'pause after main')
if re.search(r'\badvance_\w+\(', body):
require(name == 'step_window' and body.count('clock.advance_minutes(1)') == 1 and
len(re.findall(r'\badvance_\w+\(', body)) == 1, 'manual stepping site')
if 'runtime.restore_snapshot(' in body:
require(name == 'begin' and body.count('runtime.restore_snapshot(snapshot)') == 1 and
body.index('runtime.restore_snapshot(') < body.index('main = load('), 'fixture restore only')
if 'SaveService.' in body or 'SimulationSnapshot.' in body:
require(name == 'scratch_samples', 'scratch APIs outside scratch')
if '.free(' in body:
require(name == 'scratch_samples' and body.count('.free(') == 2 and
'scratch_clock.free()' in body and 'scratch_state.free()' in body, 'scratch free only')
require('set_paused(false)' not in code, 'resume forbidden')
for receiver, allowed in {
'clock': {'get', 'set_paused', 'advance_minutes'},
'runtime': {'get', 'get_script', 'create_snapshot', 'restore_snapshot', 'get_save_policy'},
'main': {'get'}, 'target': {'create_snapshot'},
'SaveService': {'save_snapshot', 'load_snapshot'},
'SimulationSnapshot': {'validate', 'restore'},
}.items():
calls = re.findall(r'\b' + receiver + r'\.(\w+)\s*\(', code)
require(set(calls) <= allowed, 'unapproved call on ' + receiver)
# Private calls are forbidden; named get() reads above are the finite K/C boundary.
require(not re.search(r'\.\s*_\w+\s*\(', code), 'private product call')
require(code.count('root.add_child(main)') == 1 and code.count('root.close_requested.emit()') == 1,
'main lifecycle')
require('get_var(false)' in code and 'await process_frame' in code, 'fixture/frame lifecycle')
require('scratch: Dictionary = before.snapshot.duplicate(true)' in code and
'is_instance_id_valid(id)' in code and 'signal_inventory()' in code, 'ownership checks absent')
# Mutation receivers may not be aliased to bypass the site checks above.
for method, receiver, count in (('set_paused', 'clock', 2), ('advance_minutes', 'clock', 1),
('restore_snapshot', 'runtime', 1), ('add_child', 'root', 1)):
require(len(re.findall(r'\.\s*' + method + r'\s*\(', tokens)) == count and
len(re.findall(r'\b' + receiver + r'\.' + method + r'\s*\(', tokens)) == count,
'mutation receiver/site: ' + method)
require(len(re.findall(r'\bcall_deferred\s*\(', tokens)) == 1 and 'call_deferred("begin")' in code,
'deferred product call')
methods = set(re.findall(r'\.\s*(\w+)\s*\(', tokens))
require(methods <= {
'new', 'start', 'update', 'finish', 'hex_encode', 'append', 'get', 'resize', 'encode_double',
'sort', 'duplicate', 'get_instance_id', 'get_manifest_generation', 'create_snapshot',
'keys', 'sort_custom', 'size', 'has', 'append_array', 'build_all_guest_definitions',
'get_guest_class_ids', 'get_species_ids', 'get_staff_candidate_ids', 'get_unique_character_ids',
'get_connections', 'get_object_id', 'get_method', 'get_name', 'stringify', 'is_empty',
'get_ticks_usec', 'get_ticks_msec', 'advance_minutes', 'save_snapshot', 'validate', 'restore',
'load_snapshot', 'free', 'merge', 'get_node', 'get_environment', 'get_process_id', 'parse_string',
'set_paused', 'get_sha256', 'open', 'get_var', 'close', 'restore_snapshot', 'connect',
'instantiate', 'add_child', 'get_save_policy', 'snapshot', 'disconnect', 'emit',
'get_script', 'get_script_constant_map'
}, 'unapproved member call')
for path in re.findall(r'"(user://[^"\n]*)"', code):
require(path.startswith('user://p28_diag/scratch/'), 'nonprivate scratch path')
def integer(value, label):
require(type(value) is int and value >= 0, 'invalid nonnegative integer: ' + label)
return value
def hash_value(value):
require(isinstance(value, str) and re.fullmatch('[0-9a-f]{64}', value), 'invalid SHA256')
def stats(values):
require(values, 'missing samples cannot become zero')
for value in values:
integer(value, 'sample')
ordered = sorted(values)
return {'count': len(values), 'mean_us': sum(values) / len(values),
'p95_us': ordered[math.ceil(len(values) * .95) - 1], 'max_us': ordered[-1]}
def check_layers(value, require_paused=True):
for layer in ('S', 'K', 'C'):
hash_value(value[layer])
kernel, cache = value['kernel'], value['cache']
require(set(kernel) == K_FIELDS, 'finite K boundary')
require(type(kernel['paused']) is bool and (not require_paused or kernel['paused'] is True) and
kernel['speed'] in (1, 2, 3), 'clock not paused')
require(re.fullmatch('[0-9a-f]{16}', kernel['_accumulator_f64_hex']), 'f64 accumulator')
require(set(kernel['handlers']) == {str(i) for i in range(11)}, 'handler phases')
for names in kernel['handlers'].values():
require(names == sorted(set(names)), 'handler IDs')
for metric in ('_sample_counts', '_totals', '_samples'):
require(isinstance(kernel[metric], dict), 'budget state missing')
require(cache['filled'] is True and cache['generation'] == cache['current_generation'] and
cache['catalog_id'] == cache['current_catalog_id'], 'cache not filled')
require(isinstance(cache['catalog_id'], str) and re.fullmatch(r'-?[1-9][0-9]*', cache['catalog_id']),
'catalog identity must preserve int64 as decimal text')
hash_value(cache['bytes_sha256'])
def check_comparison(row):
before, after = row['before'], row['after']
check_layers(before)
check_layers(after)
require(row['equal'] == {'S': True, 'K': True, 'C': True}, 'S/K/C bytes differ')
require(before == after and row['differences'] == [], 'state evidence contradicts equality')
require(row['cache_miss_in_sample'] is False, 'cache changed in sample; return to governor')
require(row['cache_rebuilt_matches_catalog'] is None and row['cache_rebuild_check_reason'] == 'no_cache_miss',
'cache miss evidence contradiction')
def check_connections(value):
require(set(value) == {'tick_started', 'tick_phase_started', 'tick_completed',
'path_query_measured', 'save_operation_completed'}, 'signal boundary')
for row in value.values():
require(row['count'] == len(row['targets']), 'connection count')
for target in row['targets']:
require(set(target) == {'object', 'method', 'flags'} and str(target['object']).isdigit()
and target['method'], 'connection target')
def summarize_ticks(ticks):
values = [[] for _ in range(12)]
paths = [[] for _ in range(12)]
frames = []
for tick in ticks:
for index in range(12):
values[index].append(tick['edges'][index + 1]['at_us'] - tick['edges'][index]['at_us'])
for query in tick['queries']:
paths[query['interval']].append(query['duration_us'])
if tick['frame_wall_before_us'] is not None:
frames.append(tick['frame_wall_before_us'])
return {'intervals': {'I' + str(i): stats(values[i]) for i in range(12)},
'query_only': {'I' + str(i): {'count': len(paths[i]), 'total_us': sum(paths[i]),
'stats': stats(paths[i]) if paths[i] else None,
'reason': None if paths[i] else 'no_queries_observed'} for i in range(12)},
'inter_tick_frame_wall': stats(frames)}
def check_report(report):
require(report['schema'] == 'p28-02-diag-v1' and report['role'] in PHASES[3:], 'report schema/role')
require(report['stop_reason'] == 'complete' and report['errors'] == [], 'probe failed')
require(report['semantics'] == SEMANTICS, 'exclusive/additive/connector interpretation forbidden')
require(report['input_sha256'] == FIXTURE_SHA, 'fixture differs')
require(report['metadata']['engine_sha256'] == ENGINE_SHA and
report['metadata']['machine'] == 'Mac16,10' and report['metadata']['headless'] is True,
'engine/machine/headless condition')
require(report['metadata']['measured_source'] == 'S' and report['metadata']['candidate_Q_measured'] is False,
'S/Q attribution')
require(re.fullmatch(r'[0-9a-f]{40}', report['metadata']['commit']), 'S commit')
require(isinstance(report['seed'], str) and report['seed'].isdigit() and report['rng_state_typed_hex'],
'seed/RNG state missing')
for value in report['rng_state_typed_hex'].values():
require(isinstance(value, str) and re.fullmatch(r'(?:[0-9a-f]{2})+', value), 'typed RNG bytes')
for name in ('restored', 'B0', 'B1', 'outside_before'):
check_layers(report['baselines'][name], require_paused=name != 'restored')
for name, minute in (('B0', 602), ('B1', 632), ('outside_before', 662)):
require(report['baselines'][name]['kernel']['absolute_minute'] == minute, 'baseline minute')
require(report['startup']['ops'].get('code') == 'INVALID_START_TIME', 'main Ops result')
for name in ('before_main', 'after_main', 'frame1', 'frame2'):
check_layers(report['startup'][name])
prep = report['preparation']
require([row['operation'] for row in prep] == ['initial_pause', 'fixture_restore', 'restored_pause'],
'preparation operations')
require(prep[1]['ok'] is True and prep[1]['state'] == report['baselines']['restored'], 'fixture restore result')
for row in (prep[0], prep[2]):
require(row['K_after']['paused'] is True and row['K_after']['_pause_request_serial'] ==
row['K_before']['_pause_request_serial'] + 1, 'preparation pause serial')
ticks = report['ticks']
require(len(ticks) == 60, 'warm/measurement counts')
previous_end = None
for index, tick in enumerate(ticks):
minute = 602 + index
require(tick['id'] == index + 1 and tick['start_minute'] == minute and tick['end_minute'] == minute + 1,
'tick ID/minute continuity')
require(tick['region'] == ('warm' if index < 30 else 'measurement'), 'warm included in measurement')
edges = tick['edges']
require(len(edges) == 13 and edges[0]['name'] == 'tick_started' and
edges[-1]['name'] == 'tick_completed', 'signal edges')
require(edges[0]['minute'] == minute and edges[-1]['minute'] == minute + 1, 'edge minute')
for phase, edge in enumerate(edges[1:-1]):
require(edge['name'] == 'phase' and edge['phase'] == phase and
edge['minute'] == minute + (phase != 0), 'phase wrongly joined by minute')
times = [integer(edge['at_us'], 'edge') for edge in edges]
require(times == sorted(times), 'nonmonotonic edges')
require(tick['frame_wall_before_us'] == (times[0] - previous_end if previous_end is not None else None),
'inter-tick wall attribution')
if previous_end is not None:
integer(tick['frame_wall_before_us'], 'frame wall')
previous_end = times[-1]
for query in tick['queries']:
slot = integer(query['interval'], 'query interval')
require(slot < 12 and query['tick_id'] == tick['id'] and query['region'] == tick['region'],
'query tick/interval binding')
integer(query['duration_us'], 'query duration')
require(times[slot] <= query['at_us'] <= times[slot + 1], 'query timestamp outside interval')
for query in report['queries_outside']:
require(query['tick_id'] is None and query['interval'] is None, 'outside query attribution')
integer(query['duration_us'], 'outside query')
observations = report['observer']
for row in observations:
integer(row['duration_us'], 'observer cost')
require(row['callback'] in {'tick_started', 'tick_phase_started', 'tick_completed',
'path_query_measured', 'save_operation_completed'}, 'observer callback')
for tick in ticks:
selected = [o for o in observations if o['tick_id'] == tick['id']]
counts = {name: sum(o['callback'] == name for o in selected) for name in
('tick_started', 'tick_phase_started', 'tick_completed', 'path_query_measured')}
require(counts == {'tick_started': 1, 'tick_phase_started': 11, 'tick_completed': 1,
'path_query_measured': len(tick['queries'])}, 'missing observer costs')
measurements = report['measurements']
snapshots = {'runtime_snapshot', 'game_state_snapshot'} | {s + '_snapshot' for s in SERVICES}
require(set(measurements) == snapshots | SCRATCH | UNMEASURED, 'measurement inventory')
for name, measurement in measurements.items():
if name in UNMEASURED:
require(measurement['samples'] is None and isinstance(measurement['reason'], str) and
measurement['reason'].strip() and measurement['scope'] == 'unmeasured', 'unmeasured is not zero')
continue
require(measurement['scope'] == 'independent_inclusive' and measurement['reason'] is None and
len(measurement['samples']) == 5, 'N=5 independent samples')
for sample in measurement['samples']:
integer(sample['duration_us'], 'outside duration')
check_comparison(sample)
require(sample['before']['kernel']['absolute_minute'] == 662, 'outside-tick time advanced')
if name in snapshots:
hash_value(sample['result_sha256'])
else:
require(sample['input'] == 'live_snapshot_deep_copy' and sample['ok'] is True and
sample['detached_mutation_checked'] is True, 'scratch ownership')
require(sample['scratch_path'].startswith('user://p28_diag/scratch/') and
'..' not in sample['scratch_path'].split('/'), 'scratch private path')
require(len(sample['scratch_ids']) == len(sample['released']) == 3 and
all(value is True for value in sample['released']), 'scratch not released')
require(len(set(sample['scratch_ids'])) == 3, 'scratch IDs')
require(all(isinstance(v, str) and re.fullmatch(r'-?[1-9][0-9]*', v)
for v in sample['scratch_ids']), 'scratch int64 IDs')
check_comparison(report['outside_total'])
check_comparison(report['manifest_detached'])
for name in ('observer_before', 'observer_connected', 'observer_disconnected'):
check_connections(report[name])
require(report['observer_before'] == report['observer_disconnected'] and
report['all_connections_restored'] is True, 'observer leak')
for name in report['observer_before']:
expected = report['observer_before'][name]['count'] + 1
require(report['observer_connected'][name]['count'] == expected, 'observer connection not made')
require([v['label'] for v in report['cache_preparations']] == ['restored', 'B1', 'outside_tick'],
'cache preparation records')
for row in report['cache_preparations']:
require(row['after']['filled'] is True and row['filled_outside_sample'] is (not row['before']['filled']),
'cache preparation mismatch')
return summarize_ticks(ticks[30:])
def preparation_key(report):
"""Object IDs are PID-local identities; compare catalog relationship and contents across PIDs."""
def stable(value):
result = copy.deepcopy(value)
if isinstance(result, dict):
if set(result) == K_FIELDS:
# Lifecycle clarification names this finite cross-PID K subset.
# The full K bytes (including nondeterministic path-query wall
# budgets) remain mandatory for within-PID outside-tick checks.
return {k: result[k] for k in ('absolute_minute', 'paused', '_pause_request_serial',
'_skip_next_process', 'snapshot_version', 'handlers')}
# C's SHA contains the required local object identity. Compare its underlying
# stable generation/content fields across PID, preserving raw C SHA in report.
result.pop('C', None)
result.pop('K', None)
for key in ('catalog_id', 'current_catalog_id'):
result.pop(key, None)
return {k: stable(v) for k, v in result.items()}
if isinstance(result, list):
return [stable(v) for v in result]
return result
return stable({'input': report['input_sha256'], 'seed': report['seed'],
'rng': report['rng_state_typed_hex'], 'preparation': report['preparation'],
'baselines': {k: report['baselines'][k] for k in ('restored', 'B0', 'B1')},
'startup': report['startup'], 'cache_preparations': report['cache_preparations']})
def compare_runs(reports):
require(len(reports) == 3 and len({r['pid'] for r in reports}) == 3, 'three separate PIDs required')
summaries = [check_report(r) for r in reports]
keys = [preparation_key(r) for r in reports]
require(keys[0] == keys[1] == keys[2], 'preparation mismatch; raw values retained, return to governor')
ranks = [sorted(s['intervals'], key=lambda k: (-s['intervals'][k]['mean_us'], int(k[1:]))) for s in summaries]
return {'runs': summaries, 'mean_wall_rankings': ranks, 'rankings_agree': ranks[0] == ranks[1] == ranks[2],
'rank_method': 'descending mean inclusive wall; ties by interval index',
'outside': [{name: stats([v['duration_us'] for v in m['samples']]) if m['samples'] is not None
else {'value': None, 'reason': m['reason']} for name, m in r['measurements'].items()}
for r in reports],
'observer': [stats([o['duration_us'] for o in r['observer'] if o['region'] == 'measurement'])
for r in reports], 'semantics': SEMANTICS}
def bound_raw(folder, row):
folder = Path(folder)
require(set(row["raw"]) == {"stdout.log", "stderr.log", "engine-native.log"}, "all raw required")
for name, expected in {**row["raw"], **row["owned_files"]}.items():
require(Path(name).name == name and pin(folder / name) == expected, "raw/owned SHA mismatch")
require(set(row["owned_files"]) == {"process.json", "started.json", "ended.json",
"adapter-owned-cleanup.json", "outer-owned-finally.json"}, "owned receipts missing")
require(json.loads((folder / "process.json").read_text()) == row["process"], "process receipt mismatch")
process, capture = row["process"], row["capture"]
require(row["passed"] is True and process["exit_code"] == 0 and process["helper_exit_code"] == 0 and
process["timed_out"] is False and process["error_lines"] == [] and process["lifecycle_error"] is None and
process["external_process_or_group_signals"] == 0 and process["owned_popen_ended"] is True and
process["old_helper_engine_ended"] is True and process["start_receipt_written"] is True and
process["end_receipt_written"] is True, "owned phase failed")
require(capture["passed"] is True and capture["all_captured_ended"] is True and
capture["outer_cleanup_errors"] == [] and capture["adapter_exception"] is None and
capture["foreign_process_or_group_signals"] == 0 and
capture["all_cleanup_before_outer_receipt"] is True and capture["outer_receipt_written"] is True and
capture["outer_receipt_error"] is None and len(capture["actual_captured_pids"]) == 1, "outer capture")
actual = json.loads((folder / "outer-owned-finally.json").read_text())
require(actual == {k: v for k, v in capture.items() if k not in
("passed", "outer_receipt_written", "outer_receipt_error")}, "outer raw differs")
pid = capture["actual_captured_pids"][0]
require(process["pid"] == pid and process["argv"] == row["argv"], "process PID/argv")
for filename, key in (("started.json", "actual_start_binding"), ("ended.json", "actual_end_binding")):
binding = json.loads((folder / filename).read_text())
require(binding == process[key] and binding["pid"] == binding["actual_pid"] == pid and
binding["argv"] == binding["command"] == row["argv"] and binding["owner_pid"] > 0,
"START/END binding")
require(process["actual_start_binding"]["owner_pid"] == process["actual_end_binding"]["owner_pid"],
"START/END owners differ")
require(json.loads((folder / "adapter-owned-cleanup.json").read_text()) == process["adapter_owned_cleanup"]
and process["adapter_owned_cleanup"]["engine_ended"] is True and
process["adapter_owned_cleanup"]["errors"] == [], "adapter cleanup binding")
require(process["stdout_sha256"] == row["raw"]["stdout.log"]["sha256"] and
process["stderr_sha256"] == row["raw"]["stderr.log"]["sha256"], "process raw binding")
require(row["timeout_seconds"] == 1800 and row["diagnostics"] == [], "phase timeout/diagnostics")
raw = (folder / "stdout.log").read_text()
for name in ("stdout.log", "stderr.log", "engine-native.log"):
require(not re.search(r"SCRIPT ERROR|Parse Error|ERROR:", (folder / name).read_text()),
"raw engine error")
if row["phase"] not in PHASES[3:]:
require("P28_DIAG_REPORT=" not in raw, "parse/import report")
if row["phase"].startswith("parse"):
require("--check-only" in row["argv"], "parse not check-only")
return None
reports = [s.partition("=")[2] for s in raw.splitlines() if s.startswith("P28_DIAG_REPORT=")]
closes = [s.partition("=")[2] for s in raw.splitlines() if s.startswith("P28_DIAG_AUDIO_CLOSE=")]
require(len(reports) == len(closes) == 1, "report/audio close count")
report, close = json.loads(reports[0]), json.loads(closes[0])
require(report == row["report"] and report["role"] == row["phase"] and
capture["actual_captured_pids"] == [report["pid"]], "raw report/PID binding")
require(close.get("coordinator") is True and close.get("passed") is True and
close.get("owned_engine_pid") == report["pid"] and close.get("all_owned_targets_released") is True and
close.get("deadline_reached") is False and close.get("clock_process_held_stopped") is True and
close.get("window_input_held_disabled") is True and close.get("restored_before_quit") is False and
close.get("shared_bus_deleted") is False and close.get("settings_mutated") is False, "audio close")
check_report(report)
return report
def digest(value):
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
def check_source_maps(before, after):
require(before == after and len(before) == 1407, 'source map mismatch')
for name, ref in before.items():
require(not Path(name).is_absolute() and '..' not in Path(name).parts and
set(ref) == {'bytes', 'sha256'}, 'source map path/ref')
hash_value(ref['sha256'])
integer(ref['bytes'], 'source bytes')
def check_receipt(receipt, out):
from diag_p28_02 import COLLECTOR_PINS
out = Path(out)
require(receipt['passed'] is True and receipt['error'] is None and receipt['cleanup_errors'] == [],
'execution failed')
source = receipt['source_before']
check_source_maps(source, receipt['source_after'])
require(receipt['default_player_before'] == receipt['default_player_after'], 'player changed')
require(receipt['dependencies_before'] == receipt['dependencies_after'] == DEPENDENCIES, 'dependency pins')
require(receipt['collector_before'] == receipt['collector_after'] == COLLECTOR_PINS, 'collector changed')
require(receipt['worker_before'] == receipt['worker_after'] and set(receipt['worker_before']) == LOCAL_NAMES,
'worker changed')
require(receipt['owned_writes'] == [] and receipt['original_credit'] == 0 and
receipt['formal_completed'] == 23 and receipt['denominator'] == 88 and
receipt['packages_completed'] == 4 and receipt['package_denominator'] == 30 and
receipt['budget_false_count_unchanged'] == 6, 'scope or credit changed')
admission = receipt['admission']
require(json.loads((out / 'admission.json').read_text()) == admission, 'admission raw')
require(admission['source_map'] == source and admission['metadata']['source_map_sha256'] == digest(source)
and admission['metadata']['source_count'] == 1407 and
admission['metadata']['worker_pins'] == receipt['worker_before'], 'admission source binding')
require(admission['fixture_sha256'] == FIXTURE_SHA and admission['dependencies'] == DEPENDENCIES and
admission['collector_pins'] == COLLECTOR_PINS and admission['phases'] == PHASES,
'admission inputs')
require(set(receipt['script_pins']) == {'diag_probe.gd', 'profile_guard.gd', 'guarded.gd'}, 'script inventory')
for name, ref in receipt['script_pins'].items():
require(pin(out / name) == ref, 'executed script differs')
require(sha(out / 'diag_probe.gd') == receipt['worker_before']['diag_probe.gd'], 'probe differs from S')
require(sha(out / 'profile_guard.gd') == admission['readonly']['files']['run-r02/profile_guard.gd'],
'guard differs from pinned input')
manifest_path = out / 'root-input-manifest.json'
require(sha(manifest_path) == admission['readonly']['manifest_sha256'] ==
'9dac0a51d52087e691c5723b4f78f171f7998c78b582b2b6c4c904798e77644d', 'Root input manifest pin')
guard_refs = [r for r in json.loads(manifest_path.read_text())['files']
if r['destination'].endswith('/run-r02/profile_guard.gd')]
require(len(guard_refs) == 1 and guard_refs[0]['sha256'] == sha(out / 'profile_guard.gd'),
'guard not bound to original Root manifest')
script_path = Path(admission['planned_argv'][2][-1])
wrapper = 'extends ' + json.dumps(str(script_path.parent / 'diag_probe.gd')) + '\nconst Guard = preload(' + \
json.dumps(str(script_path.parent / 'profile_guard.gd')) + ')\nfunc _initialize() -> void:\n' + \
'\tif not Guard.activate():\n\t\tquit(97)\n\t\treturn\n\tsuper._initialize()\n'
require((out / 'guarded.gd').read_text() == wrapper, 'guard wrapper differs')
check_probe((out / 'diag_probe.gd').read_text())
rows = receipt['phases']
require([r['phase'] for r in rows] == PHASES, 'phase order/incomplete execution')
require(len(admission['planned_argv']) == len(PHASES), 'phase plan')
pids, profiles = [], []
previous = source
reports = []
for index, row in enumerate(rows):
require('--headless' in row['argv'], 'native run not headless')
require(row['argv'][:-3] == admission['planned_argv'][index] and
row['argv'][-3] == '--log-file' and row['argv'][-1] == '--verbose', 'argv differs from plan')
report = bound_raw(out / row['phase'], row)
pids.extend(row['capture']['actual_captured_pids'])
current = row['stage_inventory']
require(row['stage_before'] == digest(previous) and row['stage_after'] == digest(current),
'stage inventory digest')
require(all(current.get(k) == v for k, v in previous.items()), 'stage source changed')
added = set(current) - set(previous)
require(not added or row['phase'] == 'import' and all(
(n.endswith('.gd.uid') and n[:-4] in previous) or
(n.endswith('.import') and n[:-7] in previous) for n in added), 'unexpected stage files')
previous = current
require(row['stage_preserved'] is True and row['source_preserved'] is True and
row['player_preserved'] is True, 'phase preservation')
startup = row['startup_project']
startup_path = out / (row['phase'] + '-startup-project.godot')
require(startup['fresh_before'] is True and sha(startup_path) == startup['sha256'] and
startup['restored_sha256'] == source['project.godot']['sha256'], 'startup profile')
profiles.append(startup['profile'])
if report:
require(report['metadata'] == admission['metadata'], 'report build/machine metadata differs')
require(json.loads((out / (row['phase'] + '-report.json')).read_text()) == report, 'report copy differs')
reports.append(report)
guard = row['guard']
expected = {'verified': True, 'conditions': [True] * 5, 'conditions_checked': 5,
'actual_user_data_dir': row['runtime_profile'], 'expected_user_data_dir': row['runtime_profile'],
'globalized_probe': str(Path(row['runtime_profile']) / 'guard-probe'),
'custom_user_dir_name': row['runtime_name'], 'fresh_before_parent_initialize': True,
'profile_writes_before_guard': 0, 'project_settings_persisted': False}
require(all(type(guard.get(k)) is type(v) and guard[k] == v for k, v in expected.items()), 'guard mismatch')
guard_path = out / (row['phase'] + '-guard.json')
require(sha(guard_path) == row['guard_sha256'] and json.loads(guard_path.read_text()) == guard,
'guard raw binding')
profiles.append(row['runtime_profile'])
require(len(set(pids)) == len(pids) and len(set(profiles)) == len(profiles), 'reused PID/profile')
require({r['pid'] for r in receipt['cleanup']} == set(pids) and all(
r['engine_ended'] is True and r['outer_cleanup_errors'] == [] and
r['foreign_process_or_group_signals'] == 0 for r in receipt['cleanup']), 'cleanup mismatch')
summary = compare_runs(reports)
require(receipt['summary'] == summary, 'summary/ranking differs from raw')
return summary
@@ -0,0 +1,230 @@
#!/usr/bin/env python3
"""Root-only three-PID headless DIAG driver. --check performs no writes or launches."""
import argparse
import importlib.util
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import uuid
sys.dont_write_bytecode = True
import check_diag as check
HERE, ROOT = check.HERE, check.ROOT
COLLECTOR = check.BASE + "collector/collect_p28_01.py"
COLLECTOR_PINS = {
"collect_p28_01.py": check.DEPENDENCIES[COLLECTOR],
"check_p28_01_report.py": check.DEPENDENCIES[check.BASE + "collector/check_p28_01_report.py"],
"measure_p28_01.gd": "4acef88bce4ac77f3f5d2659d766afcc5fd139cee381118839012022772946bb",
"test_collect_p28_01.py": "99d86ea723c976c95dbd283acbb85981eabf427b30994e652002ec0e76dc4ab0",
}
def load_dependencies():
check.check_dependencies()
for name in check.DEPENDENCIES:
check.check_imports((ROOT / name).read_text(), external=name)
for name in ("diag_p28_02.py", "check_diag.py", "verify_diag.py", "test_diag.py"):
check.check_imports((HERE / name).read_text())
check.check_modules()
sys.path.insert(0, str((ROOT / COLLECTOR).parent))
spec = importlib.util.spec_from_file_location("diag_collector", ROOT / COLLECTOR)
collector = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = collector
spec.loader.exec_module(collector)
standard = collector.load_tool(collector.EXTERNAL_IMPORTS[0])
outer = collector.load_tool(collector.EXTERNAL_IMPORTS[1])
check.check_modules()
check.check_modules({"collector": collector, "standard": standard, "outer": outer, "native": outer.native})
check.require(Path(outer.native.helper["__file__"]).resolve() ==
ROOT / "tools/regression/p09_owned_engine_process.py", "exec helper closure")
return collector, standard, outer
def collector_pins():
actual = {name: check.sha((ROOT / COLLECTOR).parent / name) for name in COLLECTOR_PINS}
check.require(actual == COLLECTOR_PINS, "collector differs from Q")
return actual
def committed_source(source, commit):
"""Read-only Git operations: the 1407 files must be the actual HEAD bytes."""
tracked = subprocess.check_output(["git", "-C", str(ROOT), "ls-tree", "-r", commit], text=True)
selected = {}
for line in tracked.splitlines():
metadata, name = line.split("\t", 1)
if name.split("/", 1)[0] in ("autoload", "scripts", "scenes", "data", "tests", "assets", "tools") or \
name in ("project.godot", "export_presets.cfg", "Makefile", "AGENTS.md"):
if Path(name).name != ".DS_Store" and "__pycache__" not in Path(name).parts:
selected[name] = metadata.split()[2]
check.require(set(selected) == set(source), "source map differs from HEAD")
for name, expected in selected.items():
actual = subprocess.check_output(["git", "-C", str(ROOT), "hash-object", str(ROOT / name)], text=True).strip()
check.require(actual == expected, "dirty product source: " + name)
def plan(collector, engine, out):
stage, wrapper = out / 'stage', out / 'guarded.gd'
return [collector.argv_for(engine, stage, wrapper, True),
collector.parse_argv(engine, stage, out / 'diag_probe.gd'),
collector.parse_argv(engine, stage, wrapper)] + [
[str(engine), '--headless', '--path', str(stage), '--script', str(wrapper)] for _ in range(3)]
def schedule(rows):
for index, label in enumerate(check.PHASES):
check.require(all(row['passed'] is True for row in rows), 'previous phase failed')
yield index, label
check.require(len(rows) == index + 1 and rows[-1]['phase'] == label and rows[-1]['passed'] is True,
'phase did not complete')
def main():
parser = argparse.ArgumentParser(description=__doc__)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument('--check', action='store_true')
mode.add_argument('--run', action='store_true')
for name in ('readonly', 'fixture', 'engine', 'output'):
parser.add_argument('--' + name, type=Path, required=True)
args = parser.parse_args()
collector, standard, outer = load_dependencies()
pinned = collector_pins()
readonly, fixture, engine, out = (p.resolve() for p in (args.readonly, args.fixture, args.engine, args.output))
inputs = collector.inputs(readonly)
check.require(check.sha(fixture) == check.FIXTURE_SHA and check.sha(engine) == check.ENGINE_SHA,
'fixture/engine SHA')
check.check_probe((HERE / 'diag_probe.gd').read_text())
source = collector.source_map(standard, ROOT)
check.require(len(source) == 1407, 'source count')
commit = subprocess.check_output(['git', '-C', str(ROOT), 'rev-parse', 'HEAD'], text=True).strip()
committed_source(source, commit)
# Frozen S must contain the submitted diagnostic too, not just product files.
for name in check.LOCAL_NAMES:
relative = (HERE / name).relative_to(ROOT).as_posix()
committed = subprocess.check_output(['git', '-C', str(ROOT), 'show', commit + ':' + relative])
check.require(committed == (HERE / name).read_bytes(), 'diagnostic differs from frozen S: ' + name)
worker_pins = {name: check.sha(HERE / name) for name in sorted(check.LOCAL_NAMES)}
commands = plan(collector, engine, out)
check.require(sys.platform == 'darwin', 'reviewed native platform is macOS')
machine = subprocess.check_output(['sysctl', '-n', 'hw.model'], text=True).strip()
check.require(machine == 'Mac16,10', 'machine differs from R')
metadata = {'commit': commit, 'measured_source': 'S', 'candidate_Q_measured': False,
'engine_sha256': check.sha(engine), 'engine_version': collector.VERSION,
'machine': machine, 'headless': True, 'source_count': len(source),
'source_map_sha256': collector.digest(source), 'worker_pins': worker_pins}
admission = {'metadata': metadata, 'source_map': source, 'readonly': inputs, 'planned_argv': commands,
'fixture_sha256': check.sha(fixture), 'dependencies': check.DEPENDENCIES,
'collector_pins': pinned, 'phases': check.PHASES, 'engines_started': 0}
check.require(not out.exists(), 'new output required')
if args.check:
print(json.dumps(admission, ensure_ascii=False, indent=2))
return 0
collector.no_live_engine()
player = standard.default_player(ROOT)
out.mkdir(parents=True)
(out / 'root-input-manifest.json').write_bytes((readonly / 'ROOT_INPUT_MANIFEST.json').read_bytes())
collector.write(out / 'admission.json', admission)
rows, profiles = [], []
error = None
scripts = {}
receipt = {}
try:
stage = out / 'stage'
stage.mkdir()
for name in source:
target = stage / name
target.parent.mkdir(parents=True, exist_ok=True)
standard.copy_source(ROOT / name, target)
expected = standard.inventory(stage, stage=True)
check.require(expected == source, 'stage source differs')
probe, guard, wrapper = (out / n for n in ('diag_probe.gd', 'profile_guard.gd', 'guarded.gd'))
probe.write_bytes((HERE / probe.name).read_bytes())
guard.write_bytes((readonly / 'run-r02/profile_guard.gd').read_bytes())
wrapper.write_text('extends ' + json.dumps(str(probe)) + '\nconst Guard = preload(' +
json.dumps(str(guard)) + ')\nfunc _initialize() -> void:\n'
'\tif not Guard.activate():\n\t\tquit(97)\n\t\treturn\n\tsuper._initialize()\n')
scripts = {p.name: check.pin(p) for p in (probe, guard, wrapper)}
public = (stage / 'project.godot').read_bytes()
for index, label in schedule(rows):
check.check_dependencies()
collector_pins()
check.check_modules()
check.require(collector.source_map(standard, ROOT) == source and standard.default_player(ROOT) == player,
'source/player changed before phase')
check.require(standard.inventory(stage, stage=True) == expected and
all(check.pin(out / p) == value for p, value in scripts.items()) and
all(check.sha(HERE / p) == value for p, value in worker_pins.items()),
'stage/scripts changed')
startup_name = 'TheTree-PCK-tests-' + uuid.uuid4().hex + '-diag-startup'
runtime_name = 'TheTree-PCK-tests-' + uuid.uuid4().hex + '-diag-runtime'
startup, runtime = (standard.profile_path(n) for n in (startup_name, runtime_name))
check.require(not startup.exists() and not runtime.exists(), 'profile must be fresh')
profiles.extend([startup, runtime])
env = {'THE_TREE_PACK_PROFILE_NAME': runtime_name, 'THE_TREE_PACK_PROFILE_EXPECTED': str(runtime),
'THE_TREE_PACK_PROFILE_REPORT': str(out / (label + '-guard.json')),
'THE_TREE_DIAG_ROLE': label, 'THE_TREE_DIAG_META': json.dumps(metadata),
'THE_TREE_P28_FIXTURE': str(fixture), 'THE_TREE_P28_FIXTURE_SHA': check.FIXTURE_SHA}
before_stage = collector.digest(expected)
row = collector.run_private_engine(standard, outer, commands[index], out / label, env,
stage, public, startup_name)
row['phase'] = label
rows.append(row)
check.require(row['passed'], 'owned engine failure: ' + label)
after_stage = standard.inventory(stage, stage=True)
collector.stage_gate(expected, after_stage, importing=label == 'import')
row.update(stage_before=before_stage, stage_after=collector.digest(after_stage), stage_preserved=True,
stage_inventory=after_stage,
source_preserved=collector.source_map(standard, ROOT) == source,
player_preserved=standard.default_player(ROOT) == player)
expected = after_stage
if label in check.PHASES[3:]:
guard_path = out / (label + '-guard.json')
observation = json.loads(guard_path.read_text())
collector.guard_gate(observation, runtime, runtime_name)
row.update(runtime_profile=str(runtime), runtime_name=runtime_name,
guard=observation, guard_sha256=check.sha(guard_path))
raw = (out / label / 'stdout.log').read_text()
reports = [s.partition('=')[2] for s in raw.splitlines() if s.startswith('P28_DIAG_REPORT=')]
check.require(len(reports) == 1, 'one report required')
row['report'] = json.loads(reports[0])
collector.write(out / (label + '-report.json'), row['report'])
check.bound_raw(out / label, row)
check.require(row['source_preserved'] and row['player_preserved'] and
check.sha(fixture) == check.FIXTURE_SHA and check.sha(engine) == check.ENGINE_SHA and
all(check.pin(out / p) == value for p, value in scripts.items()), 'post-phase preservation')
except BaseException as caught:
error = repr(caught)
finally:
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
receipt = {'admission': admission, 'phases': rows, 'error': error, 'passed': error is None,
'source_before': source, 'source_after': collector.source_map(standard, ROOT),
'default_player_before': player, 'default_player_after': standard.default_player(ROOT),
'dependencies_before': check.DEPENDENCIES,
'dependencies_after': {p: check.sha(ROOT / p) for p in check.DEPENDENCIES},
'collector_before': pinned,
'collector_after': {p: check.sha((ROOT / COLLECTOR).parent / p) for p in pinned},
'script_pins': scripts, 'cleanup': cleanup, 'cleanup_errors': cleanup_errors,
'worker_before': worker_pins, 'worker_after': {p: check.sha(HERE / p) for p in worker_pins},
'profiles': {str(p): collector.profile_files(p) for p in profiles}, 'owned_writes': [],
'original_credit': 0, 'formal_completed': 23, 'denominator': 88,
'packages_completed': 4, 'package_denominator': 30, 'budget_false_count_unchanged': 6}
if receipt['passed']:
try:
receipt['summary'] = check.compare_runs([row['report'] for row in rows[3:]])
check.check_receipt(receipt, out)
except (ValueError, KeyError, TypeError, OSError) as caught:
receipt.update(passed=False, error='checker: ' + repr(caught))
collector.write(out / 'execution.json', receipt)
if receipt['passed']:
from verify_diag import seal
seal(out)
return 0 if receipt['passed'] else 1
if __name__ == '__main__':
raise SystemExit(main())
@@ -0,0 +1,440 @@
extends SceneTree
## External passive diagnostic. Root alone executes this through the profile guard.
const GameStateScript = preload("res://autoload/game_state.gd")
const SERVICES := ["weather", "command_queue", "id_generator", "domain_events", "world_service",
"path_service", "mana_network_service", "mana_reserve", "economy_service", "facility_upkeep",
"upkeep_settlement", "inventory_service", "kitchen_production", "auto_purchase_plans", "elevators",
"legacy_stairs", "logistics_dispatcher", "facility_service", "facility_cleanliness", "guest_service",
"staff_service", "progression_service", "relationship_service", "event_service", "authority_service",
"defense_service", "construction_service", "excavation_service", "spatial_subslot_service",
"route_subslot_path_adapter", "facility_infrastructure_projector", "peeping_room_service"]
const K_FIELDS := ["absolute_minute", "speed", "paused", "snapshot_version", "_skip_next_process",
"_pause_request_serial", "_abort_current_tick_requested", "_last_tick_advanced"]
var clock: Variant
var runtime: Variant
var manifest_version: Variant
var main: Variant
var report: Dictionary = {}
var ticks: Array = []
var queries_outside: Array = []
var observer: Array = []
var saves: Array = []
var active: Dictionary = {}
var interval := -1
var region := "preparation"
var next_tick_id := 0
var last_completed := -1
var connections_before: Dictionary = {}
var failed: Array = []
var finished := false
var cache_preparations: Array = []
func _initialize() -> void:
call_deferred("begin")
func hashed(data: PackedByteArray) -> String:
var context := HashingContext.new()
context.start(HashingContext.HASH_SHA256)
context.update(data)
return context.finish().hex_encode()
func demand(ok: bool, reason: String) -> void:
if not ok:
failed.append(reason)
func kernel_state() -> Dictionary:
var result := {}
for key: String in K_FIELDS:
result[key] = clock.get(key)
var f64 := PackedByteArray()
f64.resize(8)
f64.encode_double(0, float(clock.get("_accumulator")))
result["_accumulator_f64_hex"] = f64.hex_encode()
var handlers: Dictionary = clock.get("_phase_handlers")
var handler_ids := {}
for phase: int in range(11):
var names: Array = []
for key: Variant in handlers.get(phase, {}):
names.append(str(key))
names.sort()
handler_ids[str(phase)] = names
result["handlers"] = handler_ids
var budget: Variant = clock.get("performance_budget")
for key: String in ["_sample_counts", "_totals", "_samples"]:
result[key] = budget.get(key).duplicate(true)
return result
func cache_state() -> Dictionary:
var catalog: Variant = runtime.get("_mvp_manifest_cache_catalog")
var current: Variant = runtime.get("mvp_content_catalog")
var value: Dictionary = runtime.get("_mvp_manifest_cache")
return {"generation": runtime.get("_mvp_manifest_cache_generation"),
"catalog_id": str(catalog.get_instance_id()) if catalog != null else null,
"current_catalog_id": str(current.get_instance_id()), "current_generation": current.get_manifest_generation(),
"bytes_sha256": hashed(var_to_bytes(value)),
"filled": catalog == current and runtime.get("_mvp_manifest_cache_generation") == current.get_manifest_generation()}
func ensure_cache(label: String) -> void:
var before := cache_state()
if not before.filled:
runtime.create_snapshot()
cache_preparations.append({"label": label, "before": before, "after": cache_state(),
"filled_outside_sample": not before.filled})
demand(cache_state().filled, "cache preparation failed")
func layers() -> Dictionary:
# Read C/K before the S observation so a cache mutation cannot be hidden.
var k := kernel_state()
var c := cache_state()
var snapshot: Dictionary = runtime.create_snapshot()
var s := var_to_bytes(snapshot)
demand(var_to_bytes(k) == var_to_bytes(kernel_state()), "state witness changed K")
demand(var_to_bytes(c) == var_to_bytes(cache_state()), "state witness changed C")
return {"S": hashed(s), "K": hashed(var_to_bytes(k)), "C": hashed(var_to_bytes(c)),
"kernel": k, "cache": c, "snapshot": snapshot, "s_bytes": s,
"k_bytes": var_to_bytes(k), "c_bytes": var_to_bytes(c)}
func brief(value: Dictionary) -> Dictionary:
return {"S": value.S, "K": value.K, "C": value.C, "kernel": value.kernel, "cache": value.cache}
func typed(value: Variant) -> Variant:
if value is Dictionary:
var keys: Array = value.keys()
keys.sort_custom(func(a: Variant, b: Variant) -> bool: return str(a) < str(b))
var pairs: Array = []
for index: int in range(keys.size()):
if index > 0:
demand(str(keys[index - 1]) != str(keys[index]), "typed key collision")
pairs.append([typed(keys[index]), typed(value[keys[index]])])
return [typeof(value), pairs]
if value is Array:
var children: Array = []
for child: Variant in value:
children.append(typed(child))
return [typeof(value), children]
return [typeof(value), var_to_bytes(value).hex_encode()]
func diff_paths(a: Variant, b: Variant, path: String = "$") -> Array:
if var_to_bytes(a) == var_to_bytes(b):
return []
var result: Array = []
if a is Dictionary and b is Dictionary:
if var_to_bytes(typed(a)) == var_to_bytes(typed(b)):
result.append({"path": path, "kind": "dictionary_enumeration_order_only"})
var keys: Array = a.keys()
for key: Variant in b:
if not keys.has(key):
keys.append(key)
for key: Variant in keys:
var child_path := path + "/" + str(typeof(key)) + ":" + str(key)
if not a.has(key) or not b.has(key):
result.append({"path": child_path, "kind": "missing_key"})
else:
result.append_array(diff_paths(a[key], b[key], child_path))
elif a is Array and b is Array and a.size() == b.size():
for index: int in range(a.size()):
result.append_array(diff_paths(a[index], b[index], path + "/" + str(index)))
else:
result.append({"path": path, "kind": "type_or_value"})
return result
func comparison(before: Dictionary, after: Dictionary) -> Dictionary:
var equal := {"S": before.s_bytes == after.s_bytes, "K": before.k_bytes == after.k_bytes,
"C": before.c_bytes == after.c_bytes}
var differences: Array = []
if not equal.S:
differences = diff_paths(before.snapshot, after.snapshot)
var rebuilt_matches: Variant = null
if not equal.C:
# Public catalog reads reconstruct the exact finite manifest fields from
# simulation_runtime.gd:11201-11214; never call its private builder.
var catalog: Variant = runtime.get("mvp_content_catalog")
var guest_ids: Array[String] = []
for definition: GuestDefinition in catalog.build_all_guest_definitions():
guest_ids.append(String(definition.guest_type_id))
var expected := {&"manifest_version": manifest_version,
&"schema_version": MvpContentCatalog.SCHEMA_VERSION,
&"guest_class_ids": string_names(catalog.get_guest_class_ids()),
&"species_ids": string_names(catalog.get_species_ids()),
&"staff_candidate_ids": string_names(catalog.get_staff_candidate_ids()),
&"unique_character_ids": string_names(catalog.get_unique_character_ids()),
&"guest_definition_ids": guest_ids}
rebuilt_matches = var_to_bytes(expected) == var_to_bytes(runtime.get("_mvp_manifest_cache"))
demand(rebuilt_matches, "cache differs from current catalog manifest")
demand(equal.S and equal.K and equal.C, "S/K/C changed outside tick")
return {"before": brief(before), "after": brief(after), "equal": equal,
"differences": differences, "cache_miss_in_sample": not equal.C,
"cache_rebuilt_matches_catalog": rebuilt_matches,
"cache_rebuild_check_reason": "no_cache_miss" if equal.C else null}
func string_names(values: Array[StringName]) -> Array[String]:
var result: Array[String] = []
for value: StringName in values:
result.append(String(value))
return result
func signal_inventory() -> Dictionary:
var result := {}
var signals: Array = [clock.tick_started, clock.tick_phase_started, clock.tick_completed,
runtime.path_service.path_query_measured, runtime.save_operation_completed]
for sig: Signal in signals:
var items: Array = []
for connection: Dictionary in sig.get_connections():
var callback: Callable = connection.callable
items.append({"object": str(callback.get_object_id()), "method": str(callback.get_method()),
"flags": connection.flags})
items.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return JSON.stringify(a) < JSON.stringify(b))
result[str(sig.get_name())] = {"count": items.size(), "targets": items}
return result
func observe_start(minute: int) -> void:
var started := Time.get_ticks_usec()
next_tick_id += 1
demand(active.is_empty(), "overlapping ticks")
active = {"id": next_tick_id, "region": region, "start_minute": minute, "edges": [], "queries": [],
"frame_wall_before_us": started - last_completed if last_completed >= 0 else null}
active.edges.append({"name": "tick_started", "minute": minute, "at_us": started})
interval = 0
observer.append({"callback": "tick_started", "tick_id": next_tick_id, "region": region,
"duration_us": Time.get_ticks_usec() - started})
func observe_phase(phase: int, minute: int) -> void:
var started := Time.get_ticks_usec()
demand(not active.is_empty(), "phase without tick")
active.edges.append({"name": "phase", "phase": phase, "minute": minute, "at_us": started})
interval = phase + 1
observer.append({"callback": "tick_phase_started", "tick_id": next_tick_id, "region": region,
"duration_us": Time.get_ticks_usec() - started})
func observe_completed(minute: int) -> void:
var started := Time.get_ticks_usec()
active.edges.append({"name": "tick_completed", "minute": minute, "at_us": started})
active["end_minute"] = minute
ticks.append(active)
active = {}
interval = -1
last_completed = started
observer.append({"callback": "tick_completed", "tick_id": next_tick_id, "region": region,
"duration_us": Time.get_ticks_usec() - started})
func observe_query(duration_us: int) -> void:
var started := Time.get_ticks_usec()
var item := {"duration_us": duration_us, "at_us": started, "tick_id": next_tick_id if interval >= 0 else null,
"interval": interval if interval >= 0 else null, "region": region}
if interval >= 0:
active.queries.append(item)
else:
queries_outside.append(item)
observer.append({"callback": "path_query_measured", "tick_id": item.tick_id, "region": region,
"duration_us": Time.get_ticks_usec() - started})
func observe_save(result: Dictionary) -> void:
var started := Time.get_ticks_usec()
saves.append({"at_us": started, "tick_id": next_tick_id if interval >= 0 else null,
"interval": interval if interval >= 0 else null, "region": region, "result": result.duplicate(true)})
observer.append({"callback": "save_operation_completed", "tick_id": next_tick_id if interval >= 0 else null,
"region": region, "duration_us": Time.get_ticks_usec() - started})
func step_window(label: String) -> void:
region = label
for index: int in range(30):
var before: int = clock.absolute_minute
demand(clock.paused, "clock resumed")
clock.advance_minutes(1)
demand(clock.absolute_minute == before + 1 and clock.paused, "manual step did not advance one minute paused")
await process_frame
demand(clock.absolute_minute == before + 1 and clock.paused, "inter-tick frame advanced clock")
if Time.get_ticks_msec() > 1740000 or not failed.is_empty():
failed.append("window stopped")
return
func snapshot_samples(name: String, target: Variant) -> void:
var rows: Array = []
for index: int in range(5):
var before := layers()
var started := Time.get_ticks_usec()
var value: Dictionary = target.create_snapshot()
var duration := Time.get_ticks_usec() - started
var after := layers()
var row := comparison(before, after)
row["duration_us"] = duration
row["result_sha256"] = hashed(var_to_bytes(value))
rows.append(row)
report.measurements[name] = {"samples": rows, "reason": null, "scope": "independent_inclusive"}
func scratch_samples() -> void:
for operation: String in ["deep_copy", "envelope_validate", "envelope_restore", "save", "load"]:
var rows: Array = []
for index: int in range(5):
var before := layers()
var scratch: Dictionary = before.snapshot.duplicate(true)
var scratch_clock := SimulationKernel.new()
var scratch_state := GameStateScript.new()
var ids := [scratch_clock.get_instance_id(), scratch_state.get_instance_id(), scratch_clock.performance_budget.get_instance_id()]
var path := "user://p28_diag/scratch/" + operation + "-" + str(index)
if operation == "load":
var prepared: Dictionary = SaveService.save_snapshot(path, scratch)
demand(prepared.get(&"ok", false), "scratch load preparation failed")
var started := Time.get_ticks_usec()
var result: Variant
match operation:
"deep_copy": result = scratch.duplicate(true)
"envelope_validate": result = SimulationSnapshot.validate(scratch)
"envelope_restore": result = SimulationSnapshot.restore(scratch, scratch_clock, scratch_state)
"save": result = SaveService.save_snapshot(path, scratch)
"load": result = SaveService.load_snapshot(path)
var duration := Time.get_ticks_usec() - started
var ok: bool = result if result is bool else true if operation == "deep_copy" else result.get(&"ok", false)
demand(ok, "scratch operation failed: " + operation)
# Mutate only detached caller-owned containers, then destroy scratch objects.
scratch[&"game_state"][&"resources"][&"gold"] = -987654
if result is Dictionary:
result[&"diag_detached_sentinel"] = true
result = null
scratch = {}
scratch_clock.free()
scratch_state.free()
scratch_clock = null
scratch_state = null
var released: Array = []
var id_text: Array = []
for id: int in ids:
id_text.append(str(id))
released.append(not is_instance_id_valid(id))
demand(not is_instance_id_valid(id), "scratch object retained")
var after := layers()
var row := comparison(before, after)
row.merge({"duration_us": duration, "ok": ok, "scratch_path": path, "scratch_ids": id_text,
"released": released, "input": "live_snapshot_deep_copy", "detached_mutation_checked": true})
rows.append(row)
report.measurements[operation] = {"samples": rows, "reason": null, "scope": "independent_inclusive"}
report.measurements["runtime_restore"] = {"samples": null, "reason": "Runtime restore requires initialize(clock,state,seed) before restore; initialize is outside the listed scratch restore/save/load/validator API set. Envelope restore above does not restore Runtime systems.", "scope": "unmeasured"}
report.measurements["runtime_validation_internal"] = {"samples": null, "reason": "Full Runtime validation is internal to restore; no public standalone validator. Envelope validation is measured separately and is not Runtime validation.", "scope": "unmeasured"}
report.measurements["connector_rebuild"] = {"samples": null, "reason": "path_query_measured observes queries only; no passive connector rebuild timing signal.", "scope": "unmeasured"}
func begin() -> void:
clock = root.get_node("GameClock")
runtime = root.get_node("SimulationRuntime")
report = {"schema": "p28-02-diag-v1", "role": OS.get_environment("THE_TREE_DIAG_ROLE"),
"pid": OS.get_process_id(), "metadata": JSON.parse_string(OS.get_environment("THE_TREE_DIAG_META")),
"semantics": {"intervals": "inclusive_wall_including_observer", "observer_subtracted": false,
"breakdown": "independent_inclusive_not_additive", "path": "query_only_not_connector"},
"observer_note": "Callback wall through final timestamp; excludes appending its own timing record and return. Included in intervals, never subtracted.",
"measurements": {}, "preparation": [], "baselines": {}, "seed": str(runtime.world_seed)}
var k0 := kernel_state()
clock.set_paused(true)
report.preparation.append({"operation": "initial_pause", "K_before": k0, "K_after": kernel_state()})
var runtime_script: Variant = runtime.get_script()
if runtime_script == null:
failed.append("Runtime script unavailable for manifest version")
finish()
return
var constants: Dictionary = runtime_script.get_script_constant_map()
if not constants.has("MVP_CONTENT_MANIFEST_VERSION"):
failed.append("Runtime constant MVP_CONTENT_MANIFEST_VERSION missing")
finish()
return
if typeof(constants["MVP_CONTENT_MANIFEST_VERSION"]) != TYPE_INT:
failed.append("Runtime constant MVP_CONTENT_MANIFEST_VERSION must be int")
finish()
return
manifest_version = constants["MVP_CONTENT_MANIFEST_VERSION"]
var path := OS.get_environment("THE_TREE_P28_FIXTURE")
report["input_sha256"] = FileAccess.get_sha256(path)
if report.input_sha256 != OS.get_environment("THE_TREE_P28_FIXTURE_SHA"):
failed.append("fixture SHA")
finish()
return
var input := FileAccess.open(path, FileAccess.READ)
if input == null:
failed.append("fixture open")
finish()
return
var snapshot: Variant = input.get_var(false)
input.close()
var restored: bool = runtime.restore_snapshot(snapshot)
demand(restored, "fixture restore")
var restored_cache := cache_state()
ensure_cache("restored")
report.baselines["restored"] = brief(layers())
report.preparation.append({"operation": "fixture_restore", "ok": restored, "cache_before_snapshot": restored_cache,
"state": report.baselines.restored})
k0 = kernel_state()
clock.set_paused(true)
report.preparation.append({"operation": "restored_pause", "K_before": k0, "K_after": kernel_state()})
var before_main := brief(layers())
main = load("res://scenes/main/main.tscn").instantiate()
root.add_child(main)
var after_main := brief(layers())
await process_frame
var frame1 := brief(layers())
await process_frame
var frame2 := brief(layers())
report["startup"] = {"before_main": before_main, "after_main": after_main, "frame1": frame1, "frame2": frame2,
"save_policy": runtime.get_save_policy(), "ops": main.get("_operations_start_report").duplicate(true)}
demand(report.startup.ops.get(&"code", "") == "INVALID_START_TIME", "unexpected startup Ops result")
demand(clock.paused and clock.absolute_minute == 602, "B0 clock")
report.baselines["B0"] = brief(layers())
connections_before = signal_inventory()
report["observer_before"] = signal_inventory()
clock.tick_started.connect(observe_start)
clock.tick_phase_started.connect(observe_phase)
clock.tick_completed.connect(observe_completed)
runtime.path_service.path_query_measured.connect(observe_query)
runtime.save_operation_completed.connect(observe_save)
report["observer_connected"] = signal_inventory()
await step_window("warm")
ensure_cache("B1")
report.baselines["B1"] = brief(layers())
demand(clock.absolute_minute == 632, "B1 minute")
if failed.is_empty():
await step_window("measurement")
region = "outside_tick"
ensure_cache("outside_tick")
var before := layers()
report.baselines["outside_before"] = brief(before)
report["seed"] = str(runtime.world_seed)
var rng := {}
for key: Variant in runtime.get("_rng_streams"):
# Preserve int64 state without JSON floating point truncation.
rng[str(key)] = var_to_bytes(runtime.get("_rng_streams")[key].snapshot()).hex_encode()
report["rng_state_typed_hex"] = rng
if failed.is_empty():
snapshot_samples("runtime_snapshot", runtime)
snapshot_samples("game_state_snapshot", root.get_node("GameState"))
for service: String in SERVICES:
snapshot_samples(service + "_snapshot", runtime.get(service))
scratch_samples()
# Test ownership of the returned manifest without mutating live state.
var detached: Dictionary = runtime.create_snapshot()
detached[&"systems"][&"runtime"][&"catalog_manifest"][&"diag_detached_sentinel"] = true
detached[&"systems"][&"runtime"][&"catalog_manifest"][&"guest_class_ids"].append("diag_detached_sentinel")
report["manifest_detached"] = comparison(before, layers())
report["outside_total"] = comparison(before, layers())
clock.tick_started.disconnect(observe_start)
clock.tick_phase_started.disconnect(observe_phase)
clock.tick_completed.disconnect(observe_completed)
runtime.path_service.path_query_measured.disconnect(observe_query)
runtime.save_operation_completed.disconnect(observe_save)
report["observer_disconnected"] = signal_inventory()
demand(report.observer_before == report.observer_disconnected, "observer connections not restored")
report["all_connections_restored"] = connections_before == signal_inventory()
demand(report.all_connections_restored, "save observer not restored")
demand(clock.paused and clock.absolute_minute == 662, "final clock")
finish()
func finish() -> void:
if finished:
return
finished = true
report.merge({"ticks": ticks, "queries_outside": queries_outside, "observer": observer, "save_events": saves,
"cache_preparations": cache_preparations, "errors": failed, "stop_reason": "complete" if failed.is_empty() else "failed"})
print("P28_DIAG_REPORT=" + JSON.stringify(report))
if is_instance_valid(main):
main.ui_audio_window_close_drained.connect(audio_close, CONNECT_ONE_SHOT)
root.close_requested.emit()
else:
quit(1)
func audio_close(result: Dictionary) -> void:
print("P28_DIAG_AUDIO_CLOSE=" + JSON.stringify(result))
@@ -0,0 +1,359 @@
#!/usr/bin/env python3
"""Engine-free tests. All report values below are synthetic, never native evidence."""
import copy
import json
import os
import re
from pathlib import Path
import sys
import tempfile
import time
from types import SimpleNamespace
import unittest
from unittest.mock import patch
sys.dont_write_bytecode = True
import check_diag as check
import diag_p28_02 as driver
import verify_diag as verify
H = 'a' * 64
def layers(minute=662):
kernel = {name: False for name in check.K_FIELDS}
kernel.update(absolute_minute=minute, speed=1, paused=True, snapshot_version=minute,
_pause_request_serial=2, _accumulator_f64_hex='0000000000000000',
handlers={str(i): ['handler'] for i in range(11)}, _sample_counts={}, _totals={}, _samples={})
return {'S': H, 'K': H, 'C': H, 'kernel': kernel,
'cache': {'generation': 1, 'current_generation': 1, 'catalog_id': '10', 'current_catalog_id': '10',
'bytes_sha256': H, 'filled': True}}
def comparison():
return {'before': layers(), 'after': layers(), 'equal': {'S': True, 'K': True, 'C': True},
'differences': [], 'cache_miss_in_sample': False, 'cache_rebuilt_matches_catalog': None,
'cache_rebuild_check_reason': 'no_cache_miss'}
def synthetic_report(pid=1, role='diag-1'):
connections = {name: {'count': 0, 'targets': []} for name in
('tick_started', 'tick_phase_started', 'tick_completed', 'path_query_measured',
'save_operation_completed')}
connected = copy.deepcopy(connections)
for name in connected:
connected[name] = {'count': 1, 'targets': [{'object': '1', 'method': 'observer', 'flags': 0}]}
result = {'schema': 'p28-02-diag-v1', 'role': role, 'pid': pid, 'errors': [], 'stop_reason': 'complete',
'semantics': copy.deepcopy(check.SEMANTICS), 'input_sha256': check.FIXTURE_SHA,
'metadata': {'engine_sha256': check.ENGINE_SHA, 'engine_version': '4.7.2.stable.official.ed1daf0bf',
'machine': 'Mac16,10', 'headless': True, 'measured_source': 'S',
'candidate_Q_measured': False, 'commit': 'b' * 40},
'seed': '123', 'rng_state_typed_hex': {'world': '0001'},
'baselines': {name: layers(minute) for name, minute in
[('restored', 602), ('B0', 602), ('B1', 632), ('outside_before', 662)]},
'startup': {name: layers(602) for name in ('before_main', 'after_main', 'frame1', 'frame2')},
'preparation': [], 'ticks': [], 'queries_outside': [], 'observer': [], 'save_events': [],
'measurements': {}, 'outside_total': comparison(), 'manifest_detached': comparison(),
'observer_before': connections, 'observer_connected': connected,
'observer_disconnected': copy.deepcopy(connections), 'all_connections_restored': True,
'cache_preparations': [{'label': name, 'before': layers()['cache'], 'after': layers()['cache'],
'filled_outside_sample': False} for name in ('restored', 'B1', 'outside_tick')]}
result['startup']['ops'] = {'code': 'INVALID_START_TIME'}
result['startup']['save_policy'] = {'enabled': True}
for operation in ('initial_pause', 'fixture_restore', 'restored_pause'):
if operation == 'fixture_restore':
row = {'operation': operation, 'ok': True, 'state': layers(602), 'cache_before_snapshot': layers()['cache']}
else:
row = {'operation': operation, 'K_before': layers()['kernel'], 'K_after': layers()['kernel']}
row['K_before']['_pause_request_serial'] = 1
result['preparation'].append(row)
for index in range(60):
minute, at, region = 602 + index, index * 200, 'warm' if index < 30 else 'measurement'
edges = [{'name': 'tick_started', 'minute': minute, 'at_us': at}]
edges += [{'name': 'phase', 'phase': phase, 'minute': minute + (phase != 0),
'at_us': at + 10 * (phase + 1)} for phase in range(11)]
edges.append({'name': 'tick_completed', 'minute': minute + 1, 'at_us': at + 120})
query = {'duration_us': 0, 'at_us': at + 115, 'interval': 11, 'tick_id': index + 1, 'region': region}
result['ticks'].append({'id': index + 1, 'region': region, 'start_minute': minute, 'end_minute': minute + 1,
'edges': edges, 'queries': [query], 'frame_wall_before_us': 80 if index else None})
for callback in ['tick_started'] + ['tick_phase_started'] * 11 + ['tick_completed', 'path_query_measured']:
result['observer'].append({'callback': callback, 'tick_id': index + 1, 'region': region, 'duration_us': 0})
snapshots = {'runtime_snapshot', 'game_state_snapshot'} | {s + '_snapshot' for s in check.SERVICES}
for name in snapshots | check.SCRATCH:
samples = []
for index in range(5):
row = comparison()
row['duration_us'] = index
if name in snapshots:
row['result_sha256'] = H
else:
row.update(ok=True, input='live_snapshot_deep_copy', detached_mutation_checked=True,
scratch_path='user://p28_diag/scratch/' + name + '-' + str(index),
scratch_ids=['100', '101', '102'], released=[True] * 3)
samples.append(row)
result['measurements'][name] = {'samples': samples, 'reason': None, 'scope': 'independent_inclusive'}
for name in check.UNMEASURED:
result['measurements'][name] = {'samples': None, 'reason': 'synthetic unmeasurable public boundary',
'scope': 'unmeasured'}
return result
class DiagnosticTests(unittest.TestCase):
def reject(self, mutate):
report = synthetic_report()
mutate(report)
with self.assertRaises((ValueError, KeyError, TypeError)):
check.check_report(report)
def test_valid_raw_intervals(self):
summary = check.check_report(synthetic_report())
self.assertEqual(summary['intervals']['I0'], {'count': 30, 'mean_us': 10, 'p95_us': 10, 'max_us': 10})
self.assertEqual(summary['query_only']['I11']['count'], 30)
self.assertEqual(summary['query_only']['I11']['total_us'], 0)
self.assertIsNone(summary['query_only']['I0']['stats'])
self.assertEqual(summary['inter_tick_frame_wall']['mean_us'], 80)
def test_tick_id_and_minute_rejection(self):
for mutate in (lambda r: r['ticks'][0]['edges'][2].update(minute=602),
lambda r: r['ticks'][1].update(id=1),
lambda r: r['ticks'][0]['edges'].pop(2),
lambda r: r['ticks'][30].update(region='warm'),
lambda r: r['ticks'][30].update(frame_wall_before_us=0),
lambda r: r['ticks'].pop()):
with self.subTest(mutate=mutate):
self.reject(mutate)
def test_queries_and_observer_rejection(self):
for mutate in (lambda r: r['ticks'][0]['queries'][0].update(interval=10),
lambda r: r['ticks'][0]['queries'][0].update(tick_id=2),
lambda r: r['ticks'][0]['queries'][0].update(duration_us=None),
lambda r: r['observer'].pop(0),
lambda r: r['observer'][0].update(duration_us=-1),
lambda r: r['queries_outside'].append({'tick_id': 1, 'interval': None, 'duration_us': 0})):
with self.subTest(mutate=mutate):
self.reject(mutate)
def test_nonadditive_scope(self):
for key, value in [('intervals', 'exclusive'), ('observer_subtracted', True),
('breakdown', 'sum_to_100_percent'), ('path', 'connector')]:
with self.subTest(key=key):
self.reject(lambda r: r['semantics'].update({key: value}))
def test_state_and_cache_mismatch(self):
for layer in ('S', 'K', 'C'):
self.reject(lambda r: r['outside_total']['equal'].update({layer: False}))
self.reject(lambda r: r['outside_total']['after'].update({layer: 'b' * 64}))
self.reject(lambda r: r['outside_total'].update(cache_miss_in_sample=True))
self.reject(lambda r: r['outside_total']['before']['cache'].update(filled=False))
self.reject(lambda r: r['manifest_detached']['after']['kernel'].update(_samples={'frame': [1]}))
self.reject(lambda r: r['outside_total']['before']['kernel'].pop('_accumulator_f64_hex'))
def test_missing_not_zero(self):
self.assertEqual(check.stats([0] * 5)['mean_us'], 0)
for value in ([], [None], [True], [-1], [float('nan')]):
with self.subTest(value=value), self.assertRaises(ValueError):
check.stats(value)
self.reject(lambda r: r['measurements']['runtime_restore'].update(samples=[0] * 5))
self.reject(lambda r: r['measurements']['runtime_restore'].update(reason=''))
self.reject(lambda r: r['measurements']['save']['samples'].pop())
def test_scratch_ownership(self):
for mutate in (lambda s: s.update(input='live'), lambda s: s.update(released=[True, False, True]),
lambda s: s.update(scratch_path='user://saves/player'),
lambda s: s.update(detached_mutation_checked=False), lambda s: s.update(ok=False)):
self.reject(lambda r: mutate(r['measurements']['save']['samples'][0]))
def test_connections_and_preparation(self):
self.reject(lambda r: r.update(all_connections_restored=False))
self.reject(lambda r: r['observer_disconnected']['tick_started'].update(count=1))
self.reject(lambda r: r['baselines']['B1']['kernel'].update(absolute_minute=631))
self.reject(lambda r: r['preparation'][2]['K_after'].update(_pause_request_serial=1))
self.reject(lambda r: r['startup']['ops'].update(code='OK'))
def test_three_pid_comparison(self):
reports = [synthetic_report(i + 1, 'diag-' + str(i + 1)) for i in range(3)]
self.assertTrue(check.compare_runs(reports)['rankings_agree'])
reports[1]['pid'] = 1
with self.assertRaises(ValueError):
check.compare_runs(reports)
reports[1]['pid'] = 2
reports[1]['baselines']['B0']['S'] = 'b' * 64
with self.assertRaisesRegex(ValueError, 'preparation mismatch'):
check.compare_runs(reports)
def test_pid_local_catalog_identity(self):
a, b = synthetic_report(), synthetic_report(2, 'diag-2')
b['baselines']['B0']['cache'].update(catalog_id='20', current_catalog_id='20')
b['baselines']['B0']['C'] = 'b' * 64
self.assertEqual(check.preparation_key(a), check.preparation_key(b))
b['baselines']['B0']['cache']['bytes_sha256'] = 'c' * 64
self.assertNotEqual(check.preparation_key(a), check.preparation_key(b))
def test_cross_pid_only_named_kernel_fields(self):
a, b = synthetic_report(), synthetic_report(2, 'diag-2')
b['baselines']['B1']['kernel']['_totals'] = {'path_query': 999}
b['baselines']['B1']['kernel']['_samples'] = {'path_query': [999]}
b['baselines']['B1']['K'] = 'b' * 64
self.assertEqual(check.preparation_key(a), check.preparation_key(b))
b['baselines']['B1']['kernel']['snapshot_version'] += 1
self.assertNotEqual(check.preparation_key(a), check.preparation_key(b))
def test_fixture_pause_and_int64_identity(self):
report = synthetic_report()
report['baselines']['restored']['kernel']['paused'] = False
report['preparation'][1]['state']['kernel']['paused'] = False
report['baselines']['B0']['cache'].update(catalog_id='-9223372000000000001',
current_catalog_id='-9223372000000000001')
check.check_report(report)
self.reject(lambda r: r['baselines']['B0']['kernel'].update(paused=False))
def test_static_probe_and_product_mutations(self):
source = (check.HERE / 'diag_probe.gd').read_text()
check.check_probe(source)
mutations = [source.replace('clock.set_paused(true)', 'clock.set_paused(false)', 1),
source.replace('clock.advance_minutes(1)', 'clock.advance_minutes(2)'),
source + '\nfunc illegal():\n\tclock.advance_minutes(1)\n',
source + '\nfunc illegal():\n\truntime.initialize(clock, null)\n',
source + '\nfunc illegal():\n\truntime.spawn_guest()\n',
source + '\nfunc illegal():\n\truntime.set_script(null)\n',
source + '\nfunc illegal():\n\tclock.paused = false\n',
source + '\nfunc illegal():\n\truntime.economy_service.spend({})\n',
source + '\nfunc illegal():\n\tvar alias = runtime\n\talias.restore_snapshot({})\n',
source.replace('user://p28_diag/scratch/', 'user://saves/'),
source + '\nfunc illegal():\n\truntime.restore_snapshot({})\n',
source + '\nfunc illegal():\n\tSaveService.load_snapshot("user://x")\n']
for mutation in mutations:
with self.subTest(mutation=mutation[-100:]), self.assertRaises(ValueError):
check.check_probe(mutation)
def test_autoload_class_references_rejected(self):
source = (check.HERE / 'diag_probe.gd').read_text()
check.check_probe(source)
names = check.autoload_class_names()
self.assertTrue(names)
for name in names:
for statement in (f'var dependency = {name}.MVP_CONTENT_MANIFEST_VERSION',
f'var dependency: {name}'):
with self.subTest(statement=statement), self.assertRaisesRegex(
ValueError, 'autoload class_name reference'):
check.check_probe(source + '\nfunc illegal():\n\t' + statement + '\n')
# Discover new product declarations without maintaining a name list.
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / 'autoload').mkdir()
(root / 'autoload/new_service.gd').write_text('class_name NewAutoloadService\nextends Node\n')
self.assertEqual(check.autoload_class_names(root), {'NewAutoloadService'})
with self.assertRaisesRegex(ValueError, 'autoload class_name reference'):
check.check_probe(source + '\nvar dependency: NewAutoloadService\n', root)
def test_service_snapshot_inventory_matches_current_product(self):
source = (check.ROOT / 'autoload/simulation_runtime.gd').read_text()
body = source.split('func create_snapshot() -> Dictionary:', 1)[1].split('\nfunc restore_snapshot', 1)[0]
names = set(re.findall(r'\b(\w+)\.create_snapshot\(\)', body))
self.assertEqual(names, set(check.SERVICES))
def test_source_map_count_and_mutations(self):
source = {'scripts/f%d.gd' % i: {'bytes': 1, 'sha256': H} for i in range(1407)}
check.check_source_maps(source, copy.deepcopy(source))
for mutation in (lambda m: m.pop('scripts/f0.gd'),
lambda m: m['scripts/f0.gd'].update(sha256='b' * 64)):
after = copy.deepcopy(source)
mutation(after)
with self.assertRaises(ValueError):
check.check_source_maps(source, after)
def test_import_closure_and_pins(self):
check.check_dependencies()
for name in check.LOCAL_NAMES - {'diag_probe.gd'}:
check.check_imports((check.HERE / name).read_text())
for text in ('import unauthorized_project_module', 'from restart_pair import main',
'eval("1")', '__import__("os")',
'importlib.util.spec_from_file_location("x", "other.py")'):
with self.subTest(text=text), self.assertRaises(ValueError):
check.check_imports(text)
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
for name in check.DEPENDENCIES:
target = root / name
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes((check.ROOT / name).read_bytes())
check.check_dependencies(root)
(root / next(iter(check.DEPENDENCIES))).write_text('changed')
with self.assertRaises(ValueError):
check.check_dependencies(root)
with self.assertRaises(ValueError):
check.check_modules({'bad': SimpleNamespace(__file__=str(check.ROOT / 'tools/unknown.py'))})
def test_headless_plan_and_stop_on_failure(self):
collector, _, _ = driver.load_dependencies()
commands = driver.plan(collector, Path('/engine'), Path('/output'))
self.assertEqual(len(commands), 6)
self.assertTrue(all('--headless' in c for c in commands))
self.assertTrue(all('--check-only' in c for c in commands[1:3]))
rows = []
iterator = driver.schedule(rows)
self.assertEqual(next(iterator), (0, 'import'))
rows.append({'phase': 'import', 'passed': False})
with self.assertRaises(ValueError):
next(iterator)
def test_bundle_missing_and_tampered(self):
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp)
with self.assertRaisesRegex(ValueError, 'bundle missing'):
verify.verify(check.ROOT, path)
(path / 'raw.log').write_text('raw')
verify.seal(path)
(path / 'raw.log').write_text('tampered')
with self.assertRaisesRegex(ValueError, 'inventory/SHA'):
verify.verify(check.ROOT, path)
def test_real_owned_python_child_and_raw_binding(self):
collector, _, outer = driver.load_dependencies()
child_text = (
'import os,sys,json,time,pathlib\n'
'p=pathlib.Path(sys.argv[0]).parent\n'
'identity={"pid":os.getpid(),"ppid":os.getppid(),"pgid":os.getpgrp(),'
'"args":" ".join([sys.executable]+sys.argv)}\n'
'q=p/"identity.tmp"; q.write_text(json.dumps(identity)); q.replace(p/"identity")\n'
'pathlib.Path(sys.argv[sys.argv.index("--log-file")+1]).write_text("native raw\\n")\n'
'print("synthetic Python child; no Godot",flush=True)\n'
'print("Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org",flush=True)\n'
'time.sleep(.15)\n')
with tempfile.TemporaryDirectory() as tmp:
base = Path(tmp)
script = base / 'child.py'
script.write_text(child_text)
def diagnostic(command, folder, timeout, env, **kwargs):
def probe(pid):
deadline = time.monotonic() + 2
while time.monotonic() < deadline:
if (base / 'identity').exists():
identity = json.loads((base / 'identity').read_text())
if identity['pid'] == pid:
return identity
time.sleep(.005)
raise RuntimeError('identity unavailable')
return outer.native.run_owned_diagnostic(command, folder, timeout, env, probe_fn=probe, **kwargs)
def run_phase(command, folder, timeout, env, **kwargs):
return outer.run_phase(command, folder, 5, env, diagnostic_fn=diagnostic, **kwargs)
with patch.object(collector, 'live_engines', return_value=[]):
row = collector.run_engine(SimpleNamespace(run_phase=run_phase), [sys.executable, str(script)],
base / 'import', {})
row['phase'] = 'import'
self.assertTrue(row['passed'], row)
check.bound_raw(base / 'import', row)
bad = copy.deepcopy(row)
bad['capture']['actual_captured_pids'] = [row['process']['pid'] + 1]
with self.assertRaises(ValueError):
check.bound_raw(base / 'import', bad)
(base / 'import/stdout.log').write_text('changed')
with self.assertRaises(ValueError):
check.bound_raw(base / 'import', row)
cleanup, errors = outer.finish_captured(outer.OWNED)
self.assertEqual(errors, [])
self.assertTrue(all(r['engine_ended'] for r in cleanup))
if __name__ == '__main__':
unittest.main(verbosity=2)
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Join Root's actual native bundle; absent or synthetic evidence never passes."""
import argparse
import json
import os
from pathlib import Path
import sys
sys.dont_write_bytecode = True
import check_diag as check
def inventory(directory):
result = {}
for path in sorted(Path(directory).rglob('*')):
check.require(not path.is_symlink(), 'bundle symlink')
relative = path.relative_to(directory).as_posix()
# The stage is retained by Root; its full inventories are in execution.json.
# Bundle files include every raw/owned receipt, every script and admission.
if relative == 'bundle.json' or relative.startswith('stage/'):
continue
if path.is_file():
result[relative] = check.pin(path)
return result
def seal(directory):
"""Called by the Root driver only after owned cleanup and checker completion."""
manifest = {'schema': 'p28-02-diag-bundle-v1', 'files': inventory(directory)}
with (Path(directory) / 'bundle.json').open('x') as stream:
json.dump(manifest, stream, indent=2, sort_keys=True)
def verify(project_root, directory):
project_root, directory = Path(project_root).resolve(), Path(directory).resolve()
check.require(directory.is_dir() and (directory / 'bundle.json').is_file(), 'actual native bundle missing')
manifest = json.loads((directory / 'bundle.json').read_text())
check.require(manifest['schema'] == 'p28-02-diag-bundle-v1' and manifest['files'] == inventory(directory),
'bundle inventory/SHA mismatch')
execution = json.loads((directory / 'execution.json').read_text())
check.check_dependencies(project_root)
from diag_p28_02 import load_dependencies
collector, standard, _ = load_dependencies()
check.require(execution['source_before'] == execution['source_after'] and len(execution['source_before']) == 1407,
'source map')
check.require(collector.source_map(standard, project_root) == execution['source_before'],
'candidate source inventory differs from measured S')
for name, ref in execution['source_before'].items():
path = project_root / name
check.require(path.resolve().is_relative_to(project_root) and check.pin(path) == ref,
'candidate product source differs from measured S: ' + name)
local = project_root / check.HERE.relative_to(check.ROOT)
for name, ref in execution['worker_before'].items():
check.require(name in check.LOCAL_NAMES and check.sha(local / name) == ref,
'candidate diagnostic differs from measured S')
for name in check.LOCAL_NAMES - {'diag_probe.gd'}:
check.check_imports((local / name).read_text())
check.check_modules()
return check.check_receipt(execution, directory)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--project-root', type=Path, required=True)
args = parser.parse_args()
try:
check.require(os.environ.get('BUILD_OUTPUT_DIR'), 'BUILD_OUTPUT_DIR required')
directory = Path(os.environ['BUILD_OUTPUT_DIR']) / 'p28-02-diag-evidence'
summary = verify(args.project_root, directory)
print(json.dumps({'status': 'verified', 'measured_source': 'S', 'candidate_Q_measured': False,
'original_credit': 0, 'summary': summary}, ensure_ascii=False))
return 0
except (ValueError, KeyError, TypeError, OSError) as error:
print('FAIL: ' + str(error), file=sys.stderr)
return 1
if __name__ == '__main__':
raise SystemExit(main())
@@ -243,7 +243,7 @@
"facility_value_contracts_validation.json": "bf490a9ca585c48fc318e76c4a069ef244827d828d46e6415399f7710590dd16",
"performance_audit.json": "0b630b15a392c3e0366f1362bfe4ddacb968f915f54549f82e4d6fa315384762",
"progress_contract.json": "ccbdfce8c8365d5c13f59531955708834cce45acd0e136561c95295dfdb5df77",
"progress_snapshot.json": "403a7dd3dfc3bffcee1cbcd4b9053e55d1d1f60b6de83a10e8d58a1762e60f1a",
"progress_snapshot.json": "928ed4db2681923afee341d8752f713e62c6858f70084170b95d0f0cd2bc0f1d",
"progress_validator_guard_checks.json": "72ee513fcf1cad85c24bfd0a4b472f425f6acf117928ece0f6b41e29018da627",
"progress_validator_guard_checks.r01.json": "14c26adffa5a1938241db4912e50ea54b660641ee6fee2e412d467dce14d0519",
"requirement_coverage.json": "03b10e4da367579db502d0671a6e0dda36fd385111c6f1c0084acaff7605f593",
@@ -253,6 +253,6 @@
"validate_facility_value_contracts.py": "91640e2a76cd6cc9186f7b85c0bf379b24b6cac0d48962815235ee628e7ae43e",
"validate_plan.py": "4973d3c2797289d266f5c6e7c36f3d2c31931b23a1022eab3d94328267bcc1b4",
"validator_update_checks.json": "f997d0f3de2cea29550840b1d5cb23647d64f81329de46758551e2b6c8fecc27",
"work_packages.json": "0fb2ce46295f15beef6b1baf861ee5da967d79c46cae5bc7d0c407b5d37acf5e"
"work_packages.json": "19d2d64f2684c75998a0df7b8c93572626f4366e0739a024a0a5e27d1aca6f42"
}
}
@@ -1,9 +1,9 @@
{
"schema_version": 1,
"integrated_at_jst": "2026-10-21T06:35:33.265223+09:00",
"integrated_at_jst": "2026-10-21T07:10:02.150187+09:00",
"scope": "原30パッケージ/88成果、正式完了23成果・4パッケージ。部分点なし。P28-01全条件を独立レビュー/通常final/実DB結合で正式完了。次P28-02未完。全ゲーム/同一Windows最終受入は未完。",
"source_head": "8b5d0d46fa968070c38a3949788c41fd53770347",
"source_authority_note": "修正2checkpoint直前main8b5d0d4。P28-01正式23/88は保持。P28-02-DIAGの初回・修正1差戻しは実失敗として全文/SHA保全。実Opus284が起動/準備/測定境界を6段階で一括具体化、通常repair2/条件付きRUN後、同じAstra/Wが実装中。登録base8c06/SPEC/TASK/baseline・原30/88不変、実native/受入未実施、原加算0。",
"source_head": "fdb309f3bc81529135915429d2432a6644696311",
"source_authority_note": "P28-02-DIAGの実Sを測定しQのsource/settings/diagnostic bytes一致資格を確認。全4通常ゲートと実DB結合完了、DIAG補助加算0。旧sourceのcheckpointはGit履歴と元証拠に保持。",
"immutable_history": {
"baseline.json": {
"sha256": "750bb3d313cc5e2ce0d4c79e563a995abd7a78f991dee1dd75c627877b02cd7b",
@@ -12240,5 +12240,11 @@
"bytes": 5764,
"sha256": "8993d6d4cca969e686f53c14ecb55d7e128ed19d1e13baf893a51559602f18e7"
}
}
},
"current_diagnostic_formal_completion": {
"path": "docs/plans/original-completion-2026-10-02/execution/p28-02/DIAG_FORMAL_COMPLETION_FDB309F.json",
"bytes": 18478,
"sha256": "7d83ec0e19e0c12ecefebc5ab0bdffb20743ed34c6144251a08b83b315b09ecf"
},
"current_p28_02_summary": "P28-02-DIAG補助はS d6ce6dd→Q fdb309f、native全6PID/3測定・全source1407/保存保全PASS、正式unit/nativeverifyとfresh独立Opus全4条件PASS(high/medium0・low5)、通常integration/final・実DB/traceability照合で正式完了。原成果加算0。次は実OpusのT1 snapshot変換・コピー選定に従い、EQで変更前E1-E5を固定し、OPT実装・同条件反復比較・RESTART・正確最終全roster・whole-unit横断6レビュー/通常統合finalまで進める。原P28-02は未完。旧parse失敗/900timeoutは保全。正式23/88・4/30、部分点なし。性能予算false6・200客OpsON/同一Windows/長期/全ゲームは別未達。"
}
@@ -18073,7 +18073,7 @@
},
"legacy_state": "planned",
"state_scope": "package-level; completed means whole package accepted; partial implementation is not completion",
"current_summary": "P28-01はSddde6f8→Qae9ae47、全9条件独立レビューと通常4ゲート・実DBjoinで正式完了。P28-02はS ecbbb6bを19局所unit PASSで正式提出後、Root native importはPASS、parse-baseで診断probeの静的autoloadクラス参照によるGameClock依存compile errorをstrict拒否。実exit0を成功にせず、2PID終了/cleanup0/source/player全不変、後続4phase/bundle未実行を保全。実Desktop Opus293の原因確認と通常API失敗記録→repair4/条件付きRUNにより同Astra/同Wでprobe・checker・最小unitだけ修正中(d0d7fa35、PID70447、Rootexec16528)。新S/3PID実測・正式unit/nativeverify・独立全4review・候補資格/通常統合finalは未達。DIAG原加算0、OPTと原whole-unit受入まで未完。 正式23/88・4/30・部分点なし。予算false6・200客OpsON/Windows/長期/全ゲームは別未達。",
"current_summary": "P28-01は正式完了。P28-02-DIAG補助はS d6ce6dd→Q fdb309f、native全6PID/3測定・全source1407/保存保全PASS、正式unit/nativeverifyとfresh独立Opus全4条件PASS(high/medium0・low5)、通常integration/final・実DB/traceability照合で正式完了。原成果加算0。次は実OpusのT1 snapshot変換・コピー選定に従い、EQで変更前E1-E5を固定し、OPT実装・同条件反復比較・RESTART・正確最終全roster・whole-unit横断6レビュー/通常統合finalまで進める。原P28-02は未完。旧parse失敗/900timeoutは保全。正式23/88・4/30、部分点なし。性能予算false6・200客OpsON/同一Windows/長期/全ゲームは別未達。",
"remaining_deliverables": [
{
"id": "P28-01",
@@ -18300,10 +18300,17 @@
"scope": "実main db9d613の正規全9phase測定。native全9終了/Root160結合PASS。R x1/x3予算false、Oは50cells/客0..2のみ。別PID/原wholeunit/Windows未受入、原加算0。",
"linked_at_jst": "2026-10-21T01:04:05.061475+09:00",
"current_game_acceptance_inferred": false
},
{
"path": "docs/plans/original-completion-2026-10-02/execution/p28-02/DIAG_FORMAL_COMPLETION_FDB309F.json",
"bytes": 18478,
"sha256": "7d83ec0e19e0c12ecefebc5ab0bdffb20743ed34c6144251a08b83b315b09ecf",
"scope": "DIAG support four conditions formally completed; original P28-02 remains incomplete; original credit zero",
"linked_at_jst": "2026-10-21T07:10:02.150187+09:00"
}
],
"progress_summary": "S ecbbb6bを19局所unit PASSで正式提出後、Root native importはPASS、parse-baseで診断probeの静的autoloadクラス参照によるGameClock依存compile errorをstrict拒否。実exit0を成功にせず、2PID終了/cleanup0/source/player全不変、後続4phase/bundle未実行を保全。実Desktop Opus293の原因確認と通常API失敗記録→repair4/条件付きRUNにより同Astra/同Wでprobe・checker・最小unitだけ修正中(d0d7fa35、PID70447、Rootexec16528)。新S/3PID実測・正式unit/nativeverify・独立全4review・候補資格/通常統合finalは未達。DIAG原加算0、OPTと原whole-unit受入まで未完。",
"updated_at_jst": "2026-10-21T06:35:33.265223+09:00",
"progress_summary": "P28-02-DIAG補助はS d6ce6dd→Q fdb309f、native全6PID/3測定・全source1407/保存保全PASS、正式unit/nativeverifyとfresh独立Opus全4条件PASS(high/medium0・low5)、通常integration/final・実DB/traceability照合で正式完了。原成果加算0。次は実OpusのT1 snapshot変換・コピー選定に従い、EQで変更前E1-E5を固定し、OPT実装・同条件反復比較・RESTART・正確最終全roster・whole-unit横断6レビュー/通常統合finalまで進める。原P28-02は未完。旧parse失敗/900timeoutは保全。正式23/88・4/30、部分点なし。性能予算false6・200客OpsON/同一Windows/長期/全ゲームは別未達。",
"updated_at_jst": "2026-10-21T07:10:02.150187+09:00",
"historical_summary_before_integrated_full9_20261021": "既採用e561 Pathを保持。source1273最大規模・Operations OFFの30分/保存再開を診断し25918+293parse/0。no-route候補の全15byte対照は一致したがmacro因果改善不足で不採用。Operations ON/新1277性能/Windows描画/長期プレイは未受入。",
"historical_before_P28_01_formal_completion": {
"progress_summary": "旧1273時点の限定軽量化・不採用記録を保持。現在の製品source1407(83b170aと同一)の実main db9d613全9測定で、代表R x1平均479.210ms/P95745.365ms/最大4809.099ms、x3P955062.697ms/最大5891.466msの予算未達を確定。O小入力の合格を最大負荷へ拡張しない。原P28-01の別PID再開・wholeunit受入を先に閉じ、同等性と失敗拒否を固定してから実測上位の改善へ進む。本単位の新規最適化/受入は未実施。",