Compare commits
7
Commits
3086af2ccd
...
0aee05e1d9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aee05e1d9 | ||
|
|
3ac2cedc8d | ||
|
|
954528a7c5 | ||
|
|
e524ff2924 | ||
|
|
5e4d96d910 | ||
|
|
bc703c9f6f | ||
|
|
f2a1249440 |
+147
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"at": 1792524526.738174,
|
||||
"helper_task": "P28-01-RESTART",
|
||||
"project": "the-tree-p28-01-restart",
|
||||
"state": "completed",
|
||||
"all4_normal_gates_passed": true,
|
||||
"acceptance_ids": [
|
||||
"P28-01-RS1",
|
||||
"P28-01-RS2",
|
||||
"P28-01-RS3",
|
||||
"P28-01-RS4",
|
||||
"P28-01-RS5",
|
||||
"P28-01-RS6",
|
||||
"P28-01-RS7",
|
||||
"P28-01-RS8"
|
||||
],
|
||||
"helper_acceptance": {
|
||||
"completed": 8,
|
||||
"total": 8
|
||||
},
|
||||
"S": "954528a7c5a0e27e2e50b642373168fa0a550aba",
|
||||
"Q": "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2",
|
||||
"M": "3086af2ccd970ddee271a184214dad037bd1d6af",
|
||||
"approval": {
|
||||
"task_id": "P28-01-RESTART",
|
||||
"candidate": "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2",
|
||||
"main_base": "3086af2ccd970ddee271a184214dad037bd1d6af",
|
||||
"owner": "Codex Root acting under existing user explicit main/push permission and delegation of candidate judgment; exact Q3ac2ced verified in ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json SHA5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343; not a new human SHA-specific statement.",
|
||||
"created": 1792524415.091752
|
||||
},
|
||||
"normal_evidence_join": [
|
||||
{
|
||||
"id": 8,
|
||||
"task_id": "P28-01-RESTART",
|
||||
"kind": "validation",
|
||||
"commit_sha": "954528a7c5a0e27e2e50b642373168fa0a550aba",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/runs/335b64bb98804e9bb39e353e9bd017e2-validation.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "52b59357dca6888896212feb40fde6d154c45b4493e399ddbd316290132975b8",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p28-01-restart-unit",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/runs/8b7f60346fc1460b81fa6c9078fd34fc",
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"bytes": 743,
|
||||
"sha256": "0bdf9f0c8e885154a4f5c91cdd065c87a198512f6d05be136f6f4dd273ed1f39"
|
||||
},
|
||||
"stdout.log": {
|
||||
"bytes": 178,
|
||||
"sha256": "6b7ff3a63727394e801467b7a75bc60c81c712838c7f9d6c1ff8bb4a3e0ee30a"
|
||||
},
|
||||
"stderr.log": {
|
||||
"bytes": 2497,
|
||||
"sha256": "e8b19c5284b54009e4afbfd06dc605ebe63fea13c287590d0d930f60588fe8e6"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"task_id": "P28-01-RESTART",
|
||||
"kind": "review",
|
||||
"commit_sha": "954528a7c5a0e27e2e50b642373168fa0a550aba",
|
||||
"artifact": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/actual-output-after-receiver-timeout/result.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300",
|
||||
"checks": []
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"task_id": "P28-01-RESTART",
|
||||
"kind": "integration",
|
||||
"commit_sha": "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/runs/aa8aad481aa346c8a3358847a21f431e-integration.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "85caa375586e707d3c736042b45b68acad8ab2821b6f52753dde62c3d6ed75f3",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p28-01-restart-unit",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/runs/f1e305c37cd9498ab3210d4aae76b074",
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"bytes": 744,
|
||||
"sha256": "699b9c7e76930685a98eb6468a4a4e0bffbb6c08602330f947c179c8613e0c8b"
|
||||
},
|
||||
"stdout.log": {
|
||||
"bytes": 172,
|
||||
"sha256": "d40088aec669a9bbcad7191068385673beb897b9f4ef1557a5b2858cef3b5ccd"
|
||||
},
|
||||
"stderr.log": {
|
||||
"bytes": 2497,
|
||||
"sha256": "6df4c67bfbf0419620b40ef379fa125e2be762ad4d3f34e5c19ad44182dad5e9"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"task_id": "P28-01-RESTART",
|
||||
"kind": "final",
|
||||
"commit_sha": "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/runs/67f2fc17aca343bb9a92dd7d04b0f2b6-final.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "856450b4401b9c3bb29688937d66dcdc5965adb014c6b5c4149a25b3c7f18308",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p28-01-restart-unit",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/runs/f0f3945d5b9b41438f8d771326134572",
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"bytes": 738,
|
||||
"sha256": "b2bd6b6162ed297828f5c0a0fe7cab9858c332f55f78670a0361e1ad6d40e323"
|
||||
},
|
||||
"stdout.log": {
|
||||
"bytes": 175,
|
||||
"sha256": "ebbafcedb3072d5593fabca1b15aa440ed536383df3b55ac8aad979a8dd9745f"
|
||||
},
|
||||
"stderr.log": {
|
||||
"bytes": 2497,
|
||||
"sha256": "35b7c82b3c6ebfdf0e2ad458d64afe26514b4c1db297c140eb7aac1eb2a7da86"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"traceability_path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/traceability/P28-01-RESTART.json",
|
||||
"traceability_sha256": "1febc7a16824fd8701a01e9d2d9f4482ee99e7a744742dd8b4267d06049c9ce4",
|
||||
"native_execution_sha256": "c1ec566402896f9f3fdefdfc268de6ed0bc46b8a37a9eb6bfb252b0cc4ebb514",
|
||||
"native_Root243_sha256": "6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb",
|
||||
"candidate_qualification_sha256": "5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343",
|
||||
"independent_reviewer_session": "local_251adf7f-dd2e-4726-9141-fff58a45eb14",
|
||||
"actual_result_sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300",
|
||||
"review_high_medium": 0,
|
||||
"review_low": 3,
|
||||
"receiver_timeout_kept_failed": true,
|
||||
"new_reviewer_inference_on_rejoin": false,
|
||||
"CLI_auth_recovered": false,
|
||||
"original_credit": 0,
|
||||
"original_completed": 22,
|
||||
"original_total": 88,
|
||||
"original_P28_01_whole_unit_accepted": false,
|
||||
"remaining_original_gate_plan": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/P28-01_WHOLE_UNIT_GATE_PLAN_DESKTOP_233.md"
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"status": "draft r02; not registered, dispatched or run. Root commits gate/SPEC.md and gate/TASK_INSTRUCTIONS.md to main and fills base_commit with that actual SHA.",
|
||||
"assignment": {
|
||||
"implementation": {
|
||||
"worker": "codex",
|
||||
"model": "gpt-6-astra",
|
||||
"reason": "Bounded in code size but evidence-heavy: exact SHA/source-map/scope bindings across 7 evidence groups with strong rejection tests. Astra has the full collector/RESTART context and a reliable record here; OpenCode previously failed on tool truncation in this repository."
|
||||
},
|
||||
"independent_review": {
|
||||
"model": "claude-opus-5-5",
|
||||
"context": "fresh",
|
||||
"transport": "separate Desktop session (CLI Claude auth expired); async delivery per REVIEW_DELIVERY_PLAN.md"
|
||||
},
|
||||
"run_decision": "Desktop governor gives the actual RUN decision after registration, in the 3-key commander schema"
|
||||
},
|
||||
"register_project": {
|
||||
"project": "the-tree-p28-01-gate",
|
||||
"repo": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部",
|
||||
"worktrees": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-01-gate",
|
||||
"baseline": [
|
||||
{
|
||||
"id": "P28-01-G0",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "原work: 実Runtimeの20,000マス・200客・50職員・150施設を基準にTick、経路、snapshot、描画、保存を測る。原acceptance: 機種、ビルド、シード、速度、測定区間、平均・P95・最大を残す。",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G1",
|
||||
"requirement_id": "P28",
|
||||
"text": "原本条件とUI操作が一致",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G2",
|
||||
"requirement_id": "P28",
|
||||
"text": "Runtimeの実利用・会計・行動へ接続",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G3",
|
||||
"requirement_id": "P28",
|
||||
"text": "途中保存・再起動で再現",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G4",
|
||||
"requirement_id": "P28",
|
||||
"text": "異常/境界/中断を検証",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G5",
|
||||
"requirement_id": "P28",
|
||||
"text": "関連回帰PASS",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G6",
|
||||
"requirement_id": "P28",
|
||||
"text": "要件別ログとソースハッシュを保存",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-U1",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "補助(原本の条件ではない): 未達と、実施していない分岐を、合格に数えずに開示する。",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-U2",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "補助(原本の条件ではない): 再利用する各証拠が、それ自身の source・設定・engine・marker・入力・範囲に正確に適用できる。",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
}
|
||||
],
|
||||
"validations": {
|
||||
"p28-01-gate-unit": {
|
||||
"argv": [
|
||||
"{python}",
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/gate/test_verify_p28_01_evidence.py"
|
||||
]
|
||||
},
|
||||
"p28-01-gate-verify": {
|
||||
"argv": [
|
||||
"{python}",
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/gate/verify_p28_01_evidence.py",
|
||||
"--project-root",
|
||||
"."
|
||||
]
|
||||
}
|
||||
},
|
||||
"validation_note": "p28-01-gate-verify requires the real E1-E7 bundle at $BUILD_OUTPUT_DIR/p28-01-gate-evidence and fails when it or BUILD_OUTPUT_DIR is absent; it writes its per-condition JSON to $BUILD_OUTPUT_DIR/p28-01-gate-verify.json and stdout (no argv placeholder needed). Synthetic unit PASS alone never accepts."
|
||||
},
|
||||
"register_task_contract": {
|
||||
"id": "P28-01-GATE",
|
||||
"title": "原P28-01 whole-unit 受入ゲート(既存証拠の結合)",
|
||||
"requirement_ids": [
|
||||
"P28-01",
|
||||
"P28"
|
||||
],
|
||||
"acceptance_ids": [
|
||||
"P28-01-G0",
|
||||
"P28-01-G1",
|
||||
"P28-01-G2",
|
||||
"P28-01-G3",
|
||||
"P28-01-G4",
|
||||
"P28-01-G5",
|
||||
"P28-01-G6",
|
||||
"P28-01-U1",
|
||||
"P28-01-U2"
|
||||
],
|
||||
"spec_ref": "docs/plans/original-completion-2026-10-02/execution/p28-01/gate/SPEC.md",
|
||||
"base_commit": "TBD: actual main SHA that adds gate/SPEC.md and gate/TASK_INSTRUCTIONS.md",
|
||||
"worker": "codex",
|
||||
"allowed_paths": [
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/gate/**"
|
||||
],
|
||||
"depends_on": [],
|
||||
"resource_locks": [
|
||||
"p28-01-gate"
|
||||
],
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
],
|
||||
"max_repair_attempts": null
|
||||
},
|
||||
"evidence_bundle": {
|
||||
"place_under": "$BUILD_OUTPUT_DIR/p28-01-gate-evidence/<repo-relative path>",
|
||||
"files": "SPEC.md §3 E1-E7 artifacts/ entries, byte copies, read-only; docs/ entries are read from the checkout",
|
||||
"required_for": [
|
||||
"validation",
|
||||
"integration",
|
||||
"final"
|
||||
]
|
||||
},
|
||||
"policy_note": "New project: align max_llm_runs to null under the existing owner delegation with the same owner script protocol, recorded honestly.",
|
||||
"credit": "Gate completion is the only step that moves P28-01 to 23/88; P28 package stays in_progress.",
|
||||
"unchanged": [
|
||||
"original 30/88",
|
||||
"budgets",
|
||||
"product source map 1407",
|
||||
"collector Q and RESTART Q bytes",
|
||||
"permissions/auth/billing"
|
||||
]
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# P28-01-GATE 独立レビューの非同期配送(判断)
|
||||
|
||||
**判断: 採用します。** 推論は1回だけで、Runner 本体、権限、認証、課金は変更しません。
|
||||
|
||||
1. **凍結(受信待ちはまだ始めない)**: 正式な validation が合格した後、Root は Runner の通常の手順で、review の W・.review-material・packet・schema・target を作ります。それらを凍結し、packet の SHA と manifest の SHA を `READY_FOR_DESKTOP_REVIEW.json` に記録します。900秒の受信待ちは、まだ始めません。
|
||||
2. **独立レビュー(1回)**: Desktop の管制が、新しい別セッション(claude-opus-5-5、fresh、読取専用、engine と試験は無し)を起動します。担当は packet と資料の SHA を自分で確かめ、schema に一致する結果だけを `DESKTOP_RESULT.json` に新規保存します。管制は、担当のセッションが終わった後に `DESKTOP_DELIVERY.json` を最後に作ります。
|
||||
3. **受け入れ(1回)**: Root は delivery ができた後で、通常の review の受信を1回だけ実行します。その前に、次の条件を照合します。
|
||||
- 同じ凍結 W であること、資料の全 SHA、packet の bytes、S、schema、target が一致すること。
|
||||
- 未編集の結果を、通常の check_schema → review の判定 → evidence/state に通すこと。
|
||||
- 一致しない場合は取り込まず、管制に戻します。
|
||||
4. **記録**: Desktop の独立セッションの ID と結果の SHA、CLI の認証が切れていたこと(回復とはしない)、受信の失敗があればそれを、事実として記録します。
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"authority": "Existing user instructed complete original plan, actual Desktop Opus266/268 gate design, Root delegated ordinary implementation/acceptance judgment; not new human SHA-specific statement",
|
||||
"governor_session": "local_a7f85421-3eed-4482-8030-c0dca669573a",
|
||||
"model": "claude-opus-5-5",
|
||||
"role": "commander",
|
||||
"implementation_model_chosen": "gpt-6-astra",
|
||||
"adopted_from": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/p28-whole-unit-gate-draft-3ac2ced-r01",
|
||||
"files": [
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/gate/SPEC.md",
|
||||
"sha256": "e2bff09a16db9e8cf2646eac7fcc48b1fe7704fbf35ace34b7dab67ce5d4fde2"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/gate/TASK_INSTRUCTIONS.md",
|
||||
"sha256": "246f80d101c27d472e2ebfd21b348ba27fd5a8129385c6fc4fa7fc52dcce43fa"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/gate/REGISTRATION_PROPOSAL.json",
|
||||
"sha256": "6b154161b3b05c24683ee6a0f92b957be33867b5fa125b20326973dd9bad17c5"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/gate/REVIEW_DELIVERY_PLAN.md",
|
||||
"sha256": "464fe1135930862bd21315b8649311e0a4042d7ce03f19e5b6c5fdd54d3475cf"
|
||||
}
|
||||
],
|
||||
"original_conditions_unchanged": true,
|
||||
"original_denominator": [
|
||||
30,
|
||||
88
|
||||
],
|
||||
"formal_completed": 22,
|
||||
"partial_credit": false,
|
||||
"baseline_count": 9,
|
||||
"original_work_acceptance_and_cross6": 7,
|
||||
"evidence_quality_support_conditions": 2,
|
||||
"real_bundle_validation_required": true,
|
||||
"synthetic_unit_alone_never_accepts": true,
|
||||
"gate_registered": false,
|
||||
"base_commit": "Registration will resolve actual commit containing these exact files; proposal TBD is not an execution value.",
|
||||
"G3_independent_adjudication_required": true,
|
||||
"no_engine_full9_192_reexecution": true,
|
||||
"spec_draft_label": "Immutable commander draft is adopted by this record without editing its bytes."
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
# P28-01-GATE 仕様(原 P28-01 whole-unit 受入ゲート、spec_ref の草案 r02)
|
||||
|
||||
- 状態: 草案です。Root が原本の条件と受入を照合し、この文書を `docs/plans/original-completion-2026-10-02/execution/p28-01/gate/SPEC.md` として main に commit します。その実 SHA を base にしてから、通常の登録をします。数値や条件を独自に変えるものではありません。
|
||||
- 前提: Desktop の計画(`P28-01_WHOLE_UNIT_GATE_PLAN_DESKTOP_233.md`、`bc222542…`)と、Root の対応表(`P28_WHOLE_UNIT_GATE_BINDING_FOLLOWUP_954528A.json`)を、現在の正式記録に合わせて具体化したものです。
|
||||
|
||||
## 1. 原本の条件(変更しない)
|
||||
|
||||
- 成果単位 P28-01「現HEADの性能基準」(`work_packages.json` の P28.remaining_deliverables[0])
|
||||
- work: 実Runtimeの20,000マス・200客・50職員・150施設を基準にTick、経路、snapshot、描画、保存を測る。
|
||||
- acceptance: 機種、ビルド、シード、速度、測定区間、平均・P95・最大を残す。
|
||||
- P28 の横断条件6件(cross_cutting_acceptance の原文):
|
||||
1. 原本条件とUI操作が一致
|
||||
2. Runtimeの実利用・会計・行動へ接続
|
||||
3. 途中保存・再起動で再現
|
||||
4. 異常/境界/中断を検証
|
||||
5. 関連回帰PASS
|
||||
6. 要件別ログとソースハッシュを保存
|
||||
- 分母は 30/88 で、部分点はありません。このゲートが完了した場合に限り、P28-01 を 22→23 とします。P28 パッケージは in_progress のままです。
|
||||
|
||||
## 2. baseline(9件。原本の原文は G0 と G1〜G6 の7件。U1/U2 は証拠の品質を保つための補助)
|
||||
|
||||
| id | 内容 |
|
||||
|---|---|
|
||||
| G0 | 原 work と原 acceptance(§1 の原文) |
|
||||
| G1〜G6 | 横断条件1〜6(§1 の原文) |
|
||||
| U1 | 未達と、実施していない分岐を、合格に数えずに開示する |
|
||||
| U2 | 再利用する各証拠が、それ自身の source・設定・engine・marker・入力・範囲に正確に適用できること(全証拠に共通の1つの入力を仮定しない) |
|
||||
|
||||
U1 と U2 は原本の条件ではありません。「原本の9条件」として扱ってはいけません。
|
||||
|
||||
## 3. 結合する証拠(path は repository からの相対。SHA256 は完全な値)
|
||||
|
||||
Root は E1〜E7 の全ファイルを、bytes をそのまま `$BUILD_OUTPUT_DIR/p28-01-gate-evidence/<repository からの相対 path>` に読取専用で置きます。`docs/` の下のものは、validation の checkout から直接読みます。
|
||||
|
||||
**E1 collector の正式完了**
|
||||
- `docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_FORMAL_COMPLETION_0F53A67.json` `2886547ae2ccd000a85cf8374304f292f85ed2899d9ebe9818833600dc9ff55f`
|
||||
- `docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_INDEPENDENT_REVIEW_5DB97D7.json` `23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41`
|
||||
- 検査: task が completed で、4つの gate がすべて pass。Q `0f53a67210227207efa63045de88a9a7c6e45409`。collector の4ファイル(`execution/p28-01/collector/*`)が、base の bytes と一致すること。
|
||||
|
||||
**E2 統合版の全9 phase(実際の commit は `db9d6132a8349928560dc4a4a99306d27a81e332`。新しい Q での実測ではない)**
|
||||
- `artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/execution.json` `a4d10e9b1a75a17b981792265338d64fac61178cb6f87dd646c5b486292db73c`
|
||||
- 同じ dir の `run/admission.json` `c4a3b5ba7a2b6427e55a6f85f037ded08e0c6bfd8c0a766647747ea9cc94378d`
|
||||
- 同じ dir の `ROOT_RAW_ACCEPTANCE_JOIN.json` `a76082f767e0a0d52cb70e2f66fa371b62a88754e9be319da3c08d4b48e78ba0`(`docs/…/INTEGRATED_FULL9_MEASUREMENT_DB9D613.json` も同じ SHA)
|
||||
- 検査:
|
||||
- admission の commit と source map は1407件(`651f7036…`)。
|
||||
- R0/P1/P2/O0/P3/P4 の各 report に、`metadata.machine`、`metadata.build`、seed、speed、区間(開始/終了のゲーム分、wall 時刻、frame 数)、平均・P95・最大・標本数がある。
|
||||
- R の登録は 20,000/200/50/150 で、稼働は100施設。P2 の warm は 31分(150分と書き換えない)。
|
||||
- **描画の CPU は P1〜P4 だけで取得されている**(件数 P1 150、P2 50、P3 2398、P4 745)。R0 と O0 は、CPU も GPU も null で、理由は `zero_from_api_on_gl_compatibility_unsupported_or_unmeasured`。GPU は全 phase で null と同じ理由。
|
||||
|
||||
**E3 r02(経路・snapshot・保存・読込。headless・Ops OFF・x1 の観測だけ)**
|
||||
- dir `artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/`:
|
||||
- `run-r02/execution.json` `553971f55635e529b48634ef86ad7d4198c8bafe04d5a6a5ef778a4de0439fc1`
|
||||
- `run-r02/readiness.json` `da93aecdd2c1648952e2a80382acb7e3f30cfe48a7e5df982315791161fedde2`
|
||||
- `run-r02/after/evidence/report.json` `f1d447937158a884a1b287c3b5be38b6e73b73c5d748f9576401877402dabe1b`
|
||||
- `run-r02/after/measure.stdout.log` `294730160536fc862ca657654fbae290b874acc65b77a6d24aaccd9a859907f7`
|
||||
- `run-r02/before/evidence/report.json` `a070c061ca783aaec37152bf66ad6b6e6dc69b80d9c4c024eceab916d4c5d4e9`
|
||||
- 実行した script: `run-r02/measure.gd` `95e5967835cb05ad96e8e6f257689d0bd831792aa9c228c022e3af81448ff76a`、`run-r02/guarded.gd` `c9deebb39ee643d312a77bae64f74cf6851697bb31036fc56d67dd7c4037ee09`、`run-r02/profile_guard.gd` `597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f`、`collect_perf.py` `43f3e42900a670957a01cfcaca7e485809527b79912a358917c935c57ea9c063`
|
||||
- 入力: `artifacts/original-completion-2026-10-02/p28-current1299-runtime-scale-build-candidate-native-r01/fixture/reference.variant` `eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd`
|
||||
- 検査(1407 の source の一致だけで済ませない):
|
||||
- execution の after/measure phase の report の SHA が `f1d44793…` で、PID が 46264。report は raw の stdout の `P14_03_PERF_REPORT=` の行と同じ。
|
||||
- report の `input_sha256` が入力の SHA と同じ。`prepared_state_sha256`(`9ca8f809…`)、`migrated_input_state_sha256`、`running_state_sha256` を記録する。`counts_prepared` は 20,000/200/50/150。
|
||||
- engine は readiness の `engine_sha256` `c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf`(4.7.2-stable official)。probe は `95e59678…`、runner は `43f3e429…`。hardware は `Mac16,10`・10・10・25769803776。
|
||||
- source: readiness の after `83b170a4c7176761b0b18d8e9292017d990e80a9`(1407件)の source map が、E4 の selected_source_map と一致し、それが現在の source map とも一致する。
|
||||
- speed は x1(`advance_one_minute_wall_ms_x1`)。区間と標本数は、各統計の count(x1 300、経路 cold 245 など)。統計は平均・P95・最大。
|
||||
- seed: r02 の report には seed の欄がありません。値を作らず、入力の SHA が E2 の R と同じであることと、E2 の R の report の seed(28012026)を根拠として記録します。
|
||||
- marker: r02 は回帰ではないので、marker 契約は適用外。理由を記録します。
|
||||
|
||||
**E4 正式192(関連回帰)**
|
||||
- dir `artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/`: `runner-input.json` `5bc9760c0c114a80a3c9e93634e733fc8fa93d4eeea5d8bcfda85d8e4eebd094`、`runner-ended.json` `705fb2c12007bab125548742dc8d9b07918e30cae3a63c7d89e965c43b8b5f7b`、`all-tests.json` `31da4c4672f4321469183996e12fb23064b3772a69e8330052ab3e2ea66044f1`
|
||||
- marker 契約: `docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json` `0ff86a20891da66d84295f0c66ae7517ef586b54a1a668acb7778a450d473cd2`
|
||||
- 検査: 192/192、22,198 checks、失敗0。input の marker_contract_sha256 と engine_sha256 が一致する。selected_source_map が、validation の checkout の現在の product source map(`tools/run_all_tests.source_refs`)と完全一致する。
|
||||
|
||||
**E5 RESTART の正式完了**
|
||||
- `artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/RESTART_3AC2CED_FORMAL_DB_JOIN.json` `5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be`
|
||||
- `artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json` `5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343`
|
||||
- 検査: completed 8/8、4つの gate がすべて pass。S `954528a7c5a0e27e2e50b642373168fa0a550aba`、Q `3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2`、M `3086af2ccd970ddee271a184214dad037bd1d6af`。承認は、既存の委任に基づくもの(新しい人間の発言ではない)。
|
||||
|
||||
**E6 RESTART の native(最終 S)**
|
||||
- `artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/execution.json` `c1ec566402896f9f3fdefdfc268de6ed0bc46b8a37a9eb6bfb252b0cc4ebb514`
|
||||
- 同じ dir の `ROOT_RAW_ACCEPTANCE_JOIN.json` `6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb`
|
||||
- 検査:
|
||||
- 7 phase とも PASS。writer と reader の PID がすべて異なる。
|
||||
- `restart/probe/*` の4ファイルが、base(= Q `3ac2ced` 以降の main)の bytes と一致する(再利用の条件)。
|
||||
- R/O とも K=30 で、毎分の typed SHA が一致。改ざんは CHECKSUM_MISMATCH で拒否される。O の390分の記録は ID 9〜24 が連番で、counter 24。
|
||||
|
||||
**E7 独立レビュー**
|
||||
- `artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/DESKTOP_RESULT.json` `b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300`(delivery `90b6943d86bf370599c3a8629c57a69806374a5863ce18957a7b7d696a5be9fd`)
|
||||
- 以前の changes_requested: `independent-review-bc703c9-changes-requested-r01/result.json` `f2e67cb2adbef9c9294ec1e879da70b500fa27d2220d9ae1b9fb50c96cbdc3b2`
|
||||
- 検査: 最終が pass であること。low 3件を消さずに記録すること。
|
||||
|
||||
各証拠は、それぞれの source・設定・engine・marker・入力・範囲にだけ適用します。たとえば r02 の値を windowed の値と言い換えたり、full9 を Q での実測と言い換えたりしません。
|
||||
|
||||
## 4. 条件表(verifier は結合を確かめ、whole-unit の独立レビューが判定する)
|
||||
|
||||
| 条件 | 根拠 | 範囲と未達 |
|
||||
|---|---|---|
|
||||
| G0 work | Tick と描画(CPU は P1〜P4)は E2。経路・snapshot・保存・読込は E3 | 稼働していた施設は100。GPU は null。E3 は headless・Ops OFF・x1 |
|
||||
| G0 acceptance | E2 と E3 の各 report の欄 | 記録の項目がそろえば成立する。予算の合否は条件に含まれない |
|
||||
| G1 | collector C1/C9(`_set_speed`→`_process`、main)と RESTART RS1(UI での保存と読込) | 測定の範囲は Mac(Mac16,10)。Windows での最終受入は未達のまま |
|
||||
| G2 | 既定の autoload の世界、O の実際の Ops ON、RESTART の実際の継続 | O は小さな入力(客0〜2)。200客での Ops ON は上限100のため不可 |
|
||||
| G3 | RESTART RS1〜RS5(E5、E6)と、下の明示した証拠 | 下の判断材料を参照。最終的な判定は whole-unit の独立レビューが行う |
|
||||
| G4 | collector の停止契約と phase の失敗の記録、RESTART の改ざん・停止・境界 | 実施していない分岐は U1 で開示する |
|
||||
| G5 | E4 を再利用(source と marker の一致)。collector と RESTART の unit validation | 製品が変わったら、全 roster をやり直す |
|
||||
| G6 | 全 receipt の SHA、Root の結合、verifier の manifest | — |
|
||||
|
||||
**G3 の判断材料**(これらは証拠の提示であって、合否はそこから導きません):
|
||||
- E7 の最終 review の RS5 の判断(「overload は条件付きの分岐で、実施されていないことを開示し、保持されるとは主張しない」)と、low の1件目(「0でない backlog の保持は実施していない」)。
|
||||
- 製品の契約: `scripts/core/simulation_kernel.gd:217-231`(`restore_clock` が `_accumulator` を0に戻し、`_skip_next_process` を立てる)と、snapshot の範囲(`autoload/simulation_runtime.gd:521-583`)。1分未満の accumulator は、保存される状態の外にある。
|
||||
- 「発生しなかった」ことは、保存できたことの証明ではありません。独立レビューは、原本の条件「途中保存・再起動で再現」が、overload で止まって backlog が0でない状態の保存と、1分未満の実時間の位相までを要求するかどうかを、原文と上の証拠から判断します。要求すると判断した場合は、未達として changes_requested にします。
|
||||
|
||||
## 5. U1: 開示する未達(合否には含めない。P28 / P28-02 / P28-03 に残す)
|
||||
|
||||
- 予算の未達: P1 x1 平均 479.210 ms > 4、P1 stall 最大 4809.099 ms > 50、P2 x3 P95 5062.697 ms > 12、P2 stall 最大 5891.466 ms > 50。
|
||||
- 200客での Operations ON(不可)。O の P3/P4 の合格は、小さな入力に限られる。
|
||||
- GPU 時間は null(gl_compatibility)。R0 と O0 は CPU も null。
|
||||
- 同一の Windows 実機、長期の操作感とバランス、ゲーム全体の受入。
|
||||
- RESTART の low 3件と、G3 の判断の結果。
|
||||
|
||||
## 6. 成果物(worker。allowed_paths は `docs/plans/original-completion-2026-10-02/execution/p28-01/gate/**` のみ)
|
||||
|
||||
- `verify_p28_01_evidence.py`: engine を使わない verify-only の検査です。§3 の全証拠を、`EVIDENCE_BUNDLE_MANIFEST.json`(全ファイルの bytes と SHA)と、上に固定した SHA で結合します。
|
||||
- `--project-root .` は必須。`--evidence-root` を省略すると、`$BUILD_OUTPUT_DIR/p28-01-gate-evidence` を使います。どちらも無い場合、または証拠が欠けている場合は、**失敗**にします(スキップしない)。
|
||||
- 条件ごとの結果の JSON を stdout に出力し、`$BUILD_OUTPUT_DIR` が設定されていれば `$BUILD_OUTPUT_DIR/p28-01-gate-verify.json` にも書き出します(`--json <path>` で、書出し先を明示的に指定することもできます)。source の木には書き込みません。
|
||||
- 失敗は、構造化した検査の失敗として返します(traceback で終わらせない)。
|
||||
- `EVIDENCE_BUNDLE_MANIFEST.json`、`GATE_CONDITIONS.json`(§4 と §5 の機械可読版。各条件の根拠になる path と SHA、範囲、未達)。
|
||||
- `test_verify_p28_01_evidence.py`: 合成した証拠を使う正常試験と拒否試験。
|
||||
|
||||
## 7. verifier と試験が必ず拒否するもの
|
||||
|
||||
- manifest の外のファイル、SHA の不一致、証拠の欠落、改ざん。
|
||||
- 範囲を外れた適用:
|
||||
- full9 の commit を Q と表記すること
|
||||
- r02 を windowed の値と言い換えること
|
||||
- r02 の seed を作ること
|
||||
- R0/O0 の CPU を取得済みとすること
|
||||
- 予算の false を合格として扱うこと。GPU の null を0や合格に置き換えること。
|
||||
- R の稼働数(100施設)を登録数として扱うこと。O の小さな入力を「200客」と表記すること。
|
||||
- RESTART の4つの gate のどれかが欠けていること。probe の bytes が Q と一致しないこと。
|
||||
- E4 の source map または marker が、現在のものと一致しないこと。
|
||||
- U1 の項目が欠けていること。U1/U2 を原本の条件として数えること。G3 を「発生しなかった」だけで合格とすること。
|
||||
|
||||
## 8. 対象外
|
||||
|
||||
- engine、全192、full9、collector、RESTART の再実行。
|
||||
- 製品、依存、予算値、原本の分母の変更。Runner 本体、権限、認証、課金の変更。
|
||||
@@ -0,0 +1,13 @@
|
||||
# P28-01-GATE worker 指示
|
||||
|
||||
仕様は同じディレクトリの `SPEC.md` です。この指示と仕様は変更しないでください。
|
||||
|
||||
- 作るもの(allowed_paths `docs/plans/original-completion-2026-10-02/execution/p28-01/gate/**` の中だけ): `verify_p28_01_evidence.py`、`EVIDENCE_BUNDLE_MANIFEST.json`、`GATE_CONDITIONS.json`、`test_verify_p28_01_evidence.py`。
|
||||
- 実装する範囲: SPEC の §3 の全結合、§4 と §5 の条件表と開示、§7 の拒否のすべて(それぞれに拒否試験を置く)。
|
||||
- 証拠は、Root が `$BUILD_OUTPUT_DIR/p28-01-gate-evidence/<repository からの相対 path>` に読取専用で置きます。manifest は、その実際の bundle を読んで作ります。SPEC §3 に固定した SHA と食い違うファイルがあれば、manifest に入れずに blocked にして報告します。
|
||||
- validation は2つです。`p28-01-gate-unit`(合成した証拠の試験)と、`p28-01-gate-verify`(実際の bundle に対して `verify_p28_01_evidence.py --project-root .` を実行するもの。結果の JSON は `$BUILD_OUTPUT_DIR/p28-01-gate-verify.json` と stdout)。実際の bundle が無い場合、verify は必ず失敗します。合成の試験の PASS だけで受入にはなりません。
|
||||
- 試験には、欠損、改ざん、範囲を外れた適用(SPEC §7)を拒否するものを含めます。
|
||||
- 外部の import は標準ライブラリだけにします。製品の Python は import しません。必要なら、`tools/run_all_tests.py` の `source_refs` だけを読取専用で使ってかまいません。
|
||||
- しないこと: engine の起動、全回帰、製品やほかの task のファイルの変更、commit。
|
||||
- 提出前に、engine を使わない `python3 test_verify_p28_01_evidence.py` を局所で実行して全件 PASS を確かめ、生出力をそのまま報告します。一時ディレクトリは `BUILD_OUTPUT_DIR` か `tempfile.gettempdir()` の下に作り、source には書き込みません。
|
||||
- 承認された範囲の中の通常の実装上の選択は、worker の判断でかまいません。決めきれない不足は、具体的な箇所を示した場合に限り blocked にします。
|
||||
+735
@@ -0,0 +1,735 @@
|
||||
#!/usr/bin/env python3
|
||||
"""RESTART evidence validation; typed state comparisons execute in Godot."""
|
||||
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[6]
|
||||
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 = {BASE + "restart/probe/" + n for n in
|
||||
("restart_pair.py", "check_restart.py", "test_restart.py", "restart_probe.gd")}
|
||||
SIDECAR = "p28_restart/evidence/report_full.json"
|
||||
TIMINGS = ("duration_us", "runtime_duration_us")
|
||||
SAVE_EVENTS = {
|
||||
"SAVE_CHECKPOINT_SUCCEEDED", "SAVE_CHECKPOINT_FAILED", "SAVE_LOADED", "SAVE_LOAD_FAILED",
|
||||
"SAVE_MIGRATION_SUCCEEDED", "SAVE_MIGRATION_FAILED", "SAVE_RECOVERY_LOADED",
|
||||
"SAVE_RECOVERY_CANDIDATE_LOADED", "SAVE_RECOVERY_FAILED",
|
||||
}
|
||||
PHASES = ["import", "parse-base", "parse-wrapper", "R-writer", "R-reader", "O-writer", "O-reader"]
|
||||
CROSS = [
|
||||
{"condition": "原本条件とUI操作が一致", "applicable": False, "reference": "collector C1/C2",
|
||||
"reason": "既存collectorのUI経路受入を参照。このtaskは再開の比較を担当する。"},
|
||||
{"condition": "Runtimeの実利用・会計・行動へ接続", "applicable": False, "reference": "collector C2/C3",
|
||||
"reason": "既存collectorの実Runtime観測を参照。製品の接続は変更しない。"},
|
||||
{"condition": "途中保存・再起動で再現", "applicable": True, "reference": "RS1/RS2/RS3/RS4/RS5",
|
||||
"reason": "別PIDのR/O writerとreaderで適用する。"},
|
||||
{"condition": "異常/境界/中断を検証", "applicable": False, "reference": "collector C5/C6/C7",
|
||||
"reason": "横断受入は既存collectorを参照。再開固有の異常と境界はRS5で検証する。"},
|
||||
{"condition": "関連回帰PASS", "applicable": False, "reference": "collector C8",
|
||||
"reason": "既存collectorの正式回帰証拠を参照。workerは全回帰を実行しない。"},
|
||||
{"condition": "要件別ログとソースハッシュを保存", "applicable": False, "reference": "collector C6/C7/C9",
|
||||
"reason": "既存collectorの証拠契約を参照。再開のraw結合はRS7で保存する。"},
|
||||
]
|
||||
|
||||
|
||||
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 check_probe(text):
|
||||
# Focused guard for direct Variant inference only, not GDScript compilation.
|
||||
# Root's actual Godot parse remains required.
|
||||
lines = [raw.split("#", 1)[0] for raw in text.splitlines()]
|
||||
variant_names = set()
|
||||
variant_functions = {"field"}
|
||||
for line in lines:
|
||||
declaration = re.match(r"^var\s+(\w+)\s*:\s*Variant\b", line)
|
||||
if declaration:
|
||||
variant_names.add(declaration[1])
|
||||
declaration = re.match(r"^func\s+(\w+)\s*\(.*\)\s*->\s*Variant\b", line)
|
||||
if declaration:
|
||||
variant_functions.add(declaration[1])
|
||||
function = None
|
||||
blocks = []
|
||||
pauses = 0
|
||||
watchdog_calls = 0
|
||||
for raw in text.splitlines():
|
||||
line = raw.split("#", 1)[0]
|
||||
inferred = re.match(r"^\s*var\s+\w+\s*:=\s*(\w+)\b(.*)", line)
|
||||
if inferred:
|
||||
require(not (inferred[1] in variant_names or
|
||||
(inferred[1] in variant_functions and inferred[2].lstrip().startswith("("))),
|
||||
"direct Variant inference requires an explicit type")
|
||||
match = re.match(r"^func (\w+)\(", line)
|
||||
if match:
|
||||
function = match[1]
|
||||
if function == "deterministic_step":
|
||||
blocks.append([])
|
||||
if function == "deterministic_step":
|
||||
blocks[-1].append(line)
|
||||
require(not re.search(r"set_paused\s*\(\s*false|\bclear\s*\(|spawn_guest|SimulationRuntime\.new|\binitialize\s*\(|enable_production_|_toggle_pause", line),
|
||||
"forbidden probe API")
|
||||
if re.search(r"\badvance_", line):
|
||||
require(function == "deterministic_step" and line.strip() == "GameClock.advance_minutes(1)",
|
||||
"advance outside deterministic_step")
|
||||
if "set_paused(" in line:
|
||||
require(function == "on_tick" and line.strip() == "GameClock.set_paused(true)", "pause outside tick")
|
||||
pauses += 1
|
||||
if "Time." in line:
|
||||
require(function == "deadline_timer_seconds" and
|
||||
line.strip() == "return Time.get_ticks_msec() / 1000.0", "timing API outside watchdog")
|
||||
watchdog_calls += 1
|
||||
# Literal names are used only by the finite evidence redaction routine.
|
||||
timing_line = line.strip() == 'for key: String in ["duration_us", "runtime_duration_us"]:'
|
||||
require(not re.search(r"\b\w+_(?:us|ms)\b|Performance\.|get_performance_report", line)
|
||||
or (function == "redact_payload" and timing_line),
|
||||
"performance instrumentation")
|
||||
require(len(blocks) == 1 and pauses == 1 and watchdog_calls == 1, "deterministic/tick/watchdog functions required")
|
||||
block = "\n".join(blocks[0])
|
||||
require(block.count("GameClock.advance_minutes(1)") == 1 and
|
||||
"io_ready" in block and "GameClock.paused" in block, "step admission")
|
||||
|
||||
|
||||
def event_id(counter):
|
||||
require(type(counter) is int and counter >= 0, "counter type")
|
||||
return "domain_event.%012d" % (counter + 1)
|
||||
|
||||
|
||||
def transform(witness, event):
|
||||
"""Consistency oracle for recorded IO paths, not a Godot state decoder."""
|
||||
result = copy.deepcopy(witness)
|
||||
eid = event_id(witness["counter"])
|
||||
ids, recent = witness["published_ids"], witness["recent_events"]
|
||||
require(event["event_id"] == eid, "IO event ID")
|
||||
require(ids == sorted(set(ids)) and eid not in ids and len(recent) <= 256, "event history")
|
||||
require(len({x["event_id"] for x in recent}) == len(recent) and
|
||||
all(x["event_id"] in ids for x in recent), "recent IDs")
|
||||
result["counter"] += 1
|
||||
result["published_ids"] = sorted(ids + [eid])
|
||||
result["recent_events"] = (recent[1:] if len(recent) == 256 else recent) + [event]
|
||||
return result
|
||||
|
||||
|
||||
def exact(row):
|
||||
require(row["method"] == "var_to_bytes" and row["equal"] is True and row["diff_paths"] == [],
|
||||
"typed bytes differ")
|
||||
require(row["excluded_paths"] == [], "state path exclusion")
|
||||
require(re.fullmatch("[0-9a-f]{64}", row["expected_sha256"]) and
|
||||
row["actual_sha256"] == row["expected_sha256"], "typed comparison contradiction")
|
||||
|
||||
|
||||
def check_io(row, kind):
|
||||
require(row["kind"] == kind and row["signal_count"] == 1, "one IO signal required")
|
||||
signal, latest, ev = row["signal_payload"], row["latest"], row["event"]
|
||||
require(signal == latest == ev["payload"], "signal/latest/event mismatch")
|
||||
require(row["signal_projection"] == "_save_event_payload" and row["payload_bytes_equal"] is True,
|
||||
"typed payload comparison")
|
||||
success = kind != "tamper"
|
||||
require(type(signal["ok"]) is bool and signal["ok"] is success and
|
||||
((signal["code"] == "OK") is success), "IO result")
|
||||
require(signal["operation"] == ("save" if kind == "save" else "load"), "operation")
|
||||
require(signal["save_path"].startswith("user://p28_restart/") and ".." not in signal["save_path"],
|
||||
"private IO path")
|
||||
require(row["return_kind"] == ("bool" if kind == "save" else "void"), "return kind")
|
||||
require(row["return_value"] is signal["ok"] if kind == "save" else row["return_value"] is None,
|
||||
"return/signal mismatch")
|
||||
expected_type = {"save": "SAVE_CHECKPOINT_SUCCEEDED", "load": "SAVE_LOADED",
|
||||
"tamper": "SAVE_LOAD_FAILED"}[kind]
|
||||
require(ev["event_type"] == expected_type and ev["source_service"] == "save_service" and
|
||||
ev["entity_ids"] == [] and ev["causation_command_id"] == 0, "event shape")
|
||||
require(ev["absolute_minute"] == row["minute_after"] == row["minute_before"], "IO minute")
|
||||
require(row["after"] == transform(row["before"], ev), "not X(P,event)")
|
||||
require(row["dropped"] == (row["before"]["recent_events"][0]
|
||||
if len(row["before"]["recent_events"]) == 256 else None), "wrong dropped head")
|
||||
exact(row["comparison"])
|
||||
if success:
|
||||
require(signal["saved_absolute_minute"] == row["minute_after"] and bool(signal["save_id"]),
|
||||
"save identity")
|
||||
require(row["paused_after"] is True, "IO clock not paused")
|
||||
else:
|
||||
failure = row["failure"]
|
||||
require(failure["preload_pause_restored"] is True and
|
||||
failure["loaded_clock_paused"] is row["paused_before"] and
|
||||
row["paused_after"] is row["paused_before"], "tamper pause")
|
||||
if failure["recovery_report"] is not None:
|
||||
require(failure["recovery_report"]["restore_committed"] is False, "tamper restore")
|
||||
require(type(failure["migration_attempted"]) is bool, "migration observation")
|
||||
|
||||
|
||||
def save_event(event):
|
||||
return isinstance(event, dict) and event.get("source_service") == "save_service" and \
|
||||
event.get("event_type") in SAVE_EVENTS
|
||||
|
||||
|
||||
def payload_paths(report):
|
||||
"""The complete W2 closure; no recursive payload-name exemption."""
|
||||
result = {}
|
||||
for i, row in enumerate(report["io"]):
|
||||
prefix = "/report/io/%d" % i
|
||||
for name in ("signal_payload", "latest"):
|
||||
result[prefix + "/" + name] = row[name]
|
||||
events = [(prefix + "/event", row["event"]), (prefix + "/dropped", row["dropped"])]
|
||||
for side in ("before", "after"):
|
||||
events.extend((prefix + "/" + side + "/recent_events/%d" % j, event)
|
||||
for j, event in enumerate(row[side]["recent_events"]))
|
||||
for path, event in events:
|
||||
if save_event(event):
|
||||
result[path + "/payload"] = event["payload"]
|
||||
for k, row in enumerate(report["minutes"]):
|
||||
if save_event(row["replacement"]):
|
||||
result["/report/minutes/%d/replacement/payload" % k] = row["replacement"]["payload"]
|
||||
return result
|
||||
|
||||
|
||||
def walk(value, path=""):
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
pointer = path + "/" + key.replace("~", "~0").replace("/", "~1")
|
||||
yield pointer, key, child
|
||||
yield from walk(child, pointer)
|
||||
elif isinstance(value, list):
|
||||
for i, child in enumerate(value):
|
||||
yield from walk(child, path + "/" + str(i))
|
||||
|
||||
|
||||
def redact_report(verbatim):
|
||||
"""Also validates the full sidecar report before producing its unique public form."""
|
||||
full = json.loads(json.dumps(verbatim))
|
||||
allowed = payload_paths(full)
|
||||
for path, key, _ in walk(full, "/report"):
|
||||
require(key != "timing_redactions", "reserved marker in verbatim evidence")
|
||||
require(not key.endswith(("_us", "_ms")) or
|
||||
(path.rsplit("/", 1)[0] in allowed and key in TIMINGS), "timing outside W2 closure")
|
||||
require(key not in ("performance", "statistics", "fps", "budget_verdict"),
|
||||
"sidecar performance use")
|
||||
for path, payload in allowed.items():
|
||||
require(isinstance(payload, dict) and all(k in payload for k in TIMINGS), "both timing keys required")
|
||||
payload["timing_redactions"] = [
|
||||
{"original_key": key, "sidecar_ref": path + "/" + key} for key in TIMINGS]
|
||||
for key in TIMINGS:
|
||||
del payload[key]
|
||||
check_no_metrics(full)
|
||||
return full
|
||||
|
||||
|
||||
def resolve_pointer(sidecar, pointer):
|
||||
require(pointer.startswith("/report/"), "pointer outside report")
|
||||
value = sidecar
|
||||
for part in pointer[1:].split("/"):
|
||||
part = part.replace("~1", "/").replace("~0", "~")
|
||||
value = value[int(part)] if isinstance(value, list) else value[part]
|
||||
return value
|
||||
|
||||
|
||||
def json_sha(value):
|
||||
# JSON numbers are represented uniformly across Godot's JSON float parser
|
||||
# and Python. Fixed decimal form is valid JSON; keys and strings remain verbatim.
|
||||
def encode(v):
|
||||
if isinstance(v, dict):
|
||||
require(len({str(k) for k in v}) == len(v), "canonical_key_collision")
|
||||
require(all(type(k) is str for k in v), "canonical JSON requires text keys")
|
||||
return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + encode(v[k])
|
||||
for k in sorted(v)) + "}"
|
||||
if isinstance(v, list):
|
||||
return "[" + ",".join(encode(x) for x in v) + "]"
|
||||
if type(v) in (int, float):
|
||||
require(math.isfinite(v), "nonfinite JSON")
|
||||
return format(v, ".17f")
|
||||
return json.dumps(v, ensure_ascii=False, separators=(",", ":"), allow_nan=False)
|
||||
return hashlib.sha256(encode(value).encode()).hexdigest()
|
||||
|
||||
|
||||
def canonical_event(event, eid):
|
||||
return {"event_id": eid, "replaced_for_cross_pid_compare": True} \
|
||||
if event["event_id"] == eid else event
|
||||
|
||||
|
||||
def check_sidecar(public, sidecar):
|
||||
require(isinstance(sidecar, dict) and set(sidecar) == {"report", "at_390_event_verbatim"},
|
||||
"sidecar root/missing")
|
||||
check_no_metrics(public)
|
||||
full = sidecar["report"]
|
||||
require(full.get("stop_reason") != "canonical_key_collision", "canonical_key_collision")
|
||||
require(public == redact_report(full), "public redaction differs from W2")
|
||||
restored = copy.deepcopy(public)
|
||||
for path, payload in payload_paths(restored).items():
|
||||
expected = [{"original_key": k, "sidecar_ref": path + "/" + k} for k in TIMINGS]
|
||||
require(payload.pop("timing_redactions") == expected, "redaction marker/pointer mismatch")
|
||||
for marker in expected:
|
||||
payload[marker["original_key"]] = resolve_pointer(sidecar, marker["sidecar_ref"])
|
||||
require(restored == full, "entire restored report differs")
|
||||
events = sidecar["at_390_event_verbatim"]
|
||||
require(isinstance(events, list), "at390 verbatim array")
|
||||
observation = full.get("at_390")
|
||||
summaries = observation["events"] if observation is not None else []
|
||||
require(len(events) == len(summaries), "at390 verbatim count")
|
||||
for event, summary in zip(events, summaries):
|
||||
for path, key, _ in walk(event):
|
||||
require(key != "timing_redactions" and key not in
|
||||
("performance", "statistics", "fps", "budget_verdict"), "at390 forbidden field")
|
||||
require(not key.endswith(("_us", "_ms")) or
|
||||
(save_event(event) and path == "/payload/" + key and key in TIMINGS),
|
||||
"at390 timing outside SAVE payload")
|
||||
if save_event(event):
|
||||
require(all(k in event["payload"] for k in TIMINGS), "at390 both timing keys")
|
||||
require(set(summary) == {"event_id", "event_type", "absolute_minute",
|
||||
"raw_event_sha256", "canonical_sha256"}, "at390 summary shape")
|
||||
require(all(event[k] == summary[k] for k in ("event_id", "event_type", "absolute_minute")),
|
||||
"at390 verbatim order/metadata")
|
||||
require(json_sha(event) == summary["raw_event_sha256"] and
|
||||
json_sha(canonical_event(event, full["event_id"])) == summary["canonical_sha256"],
|
||||
"at390 raw/canonical SHA")
|
||||
return full
|
||||
|
||||
|
||||
def check_deadline(value, complete=True):
|
||||
require(set(value) == {"limit_seconds_from_engine_start", "engine_elapsed_at_begin_seconds",
|
||||
"timer_seconds"}, "deadline shape")
|
||||
elapsed, timer = value["engine_elapsed_at_begin_seconds"], value["timer_seconds"]
|
||||
require(type(elapsed) in (int, float) and type(timer) in (int, float) and
|
||||
math.isfinite(elapsed) and math.isfinite(timer) and elapsed >= 0 and
|
||||
value["limit_seconds_from_engine_start"] == 1740 and timer == 1740 - elapsed,
|
||||
"engine elapsed deadline formula")
|
||||
require(not complete or timer > 0, "expired deadline")
|
||||
|
||||
|
||||
def overload_case(report):
|
||||
backlog = report["paused_idle"]["before"]["backlog"]
|
||||
overloads = report["overloads"]
|
||||
require(isinstance(overloads, list) and type(backlog) is int and backlog >= 0, "overload facts")
|
||||
expected = {"exercised": bool(overloads and backlog > 0),
|
||||
"overloads": len(overloads), "backlog_at_save": backlog}
|
||||
require(report["overload_case"] == expected and
|
||||
type(report["overload_case"]["exercised"]) is bool and
|
||||
set(report["overload_case"]) == set(expected), "overload disclosure")
|
||||
for _, key, value in walk(report):
|
||||
if key in ("nonzero_backlog_preserved", "overload_persistence",
|
||||
"across_restart_backlog_persistence_claimed"):
|
||||
require(value is False, "unsupported overload/persistence claim")
|
||||
return expected
|
||||
|
||||
|
||||
def coverage(reports):
|
||||
detail = {r["role"]: overload_case(r) for r in reports if r["role"].endswith("writer")}
|
||||
return {"overload_nonzero_backlog":
|
||||
"exercised_recorded" if detail and all(x["exercised"] for x in detail.values()) else "not_exercised",
|
||||
"per_role": detail, "across_restart_backlog_persistence_claimed": False}
|
||||
|
||||
|
||||
def check_no_arrival_diagnostic(report):
|
||||
require(report["role"] == "O-writer" and report["boundary"] is None and
|
||||
report["boundary_reason"] == report["stop_reason"] == "no_arrival_within_cap",
|
||||
"no-arrival reason/null")
|
||||
require(report["ui_ticks"] and
|
||||
report["ui_ticks"][-1]["clock"] - report["start_minute"] == 120 and
|
||||
all(t["guest_auto_count"] == 0 and t["guest_auto_ids"] == [] for t in report["ui_ticks"]),
|
||||
"no-arrival cap/facts")
|
||||
return True
|
||||
|
||||
|
||||
def check_report(report, sidecar=None):
|
||||
check_no_metrics(report)
|
||||
require(sidecar is not None, "sidecar required")
|
||||
report = check_sidecar(report, sidecar)
|
||||
if report["stop_reason"] == "no_arrival_within_cap":
|
||||
check_no_arrival_diagnostic(report)
|
||||
require(report["schema"] == "p28-restart-v1" and report["stop_reason"] == "complete", "incomplete probe")
|
||||
require(report["role"] in PHASES[3:] and type(report["pid"]) is int and report["pid"] > 0, "role/PID")
|
||||
require(report["main_added"] is True and report["k_target"] == 30 and report["actual_k"] == 30, "main/K")
|
||||
require(report["comparison_scope"] == "complete_typed_var_to_bytes" and report["excluded_paths"] == [],
|
||||
"partial digest/path exclusion")
|
||||
require(report["transfer_before_guard_writes"] == 0 and report["probe_deadline_seconds"] == 1740,
|
||||
"profile/deadline")
|
||||
check_deadline(report["probe_deadline"])
|
||||
writer = report["role"].endswith("writer")
|
||||
kinds = ["save"] if writer else ["tamper", "load"]
|
||||
require(len(report["io"]) == len(kinds), "IO sequence")
|
||||
for row, kind in zip(report["io"], kinds):
|
||||
check_io(row, kind)
|
||||
if writer:
|
||||
overload_case(report)
|
||||
exact(report["package_matches_before"])
|
||||
require(report["package_matches_before"]["actual_sha256"] == report["states"]["P"]["sha256"],
|
||||
"P not package snapshot")
|
||||
else:
|
||||
require(report["failure_event_absent"] is True and report["transfer_verified"] is True,
|
||||
"failed event survived/transfer not verified")
|
||||
io = report["io"][-1]
|
||||
require(report["event_id"] == io["event"]["event_id"] and report["saved_minute"] == io["minute_after"],
|
||||
"continuation IO identity")
|
||||
require(report["paused_idle"]["before"] == report["paused_idle"]["after"], "paused clock consumed backlog")
|
||||
require(report["paused_idle"]["before"]["paused"] is True, "paused observation")
|
||||
exact(report["paused_idle"]["comparison"])
|
||||
rows = report["minutes"]
|
||||
require(len(rows) == 30, "actual K not 30")
|
||||
previous, lost = report["saved_minute"], False
|
||||
for row in rows:
|
||||
require(row["before"] == previous and row["after"] == previous + 1, "K counts actual minute advances")
|
||||
require(row["paused"] is True and row["backlog_before"] == row["backlog_after"], "step consumed backlog")
|
||||
require(row["scope"] == "complete_typed_var_to_bytes" and row["excluded_paths"] == [], "partial state")
|
||||
require(re.fullmatch("[0-9a-f]{64}", row["sha256"]), "minute SHA")
|
||||
if row["replacement"] is not None:
|
||||
require(not lost and row["replacement"] == io["event"] and row["replacement_count"] == 1,
|
||||
"replacement must be unchanged in-window E")
|
||||
else:
|
||||
lost = True
|
||||
require(row["replacement_count"] == 0, "replacement after E left window")
|
||||
require(0 <= row["recent_count"] <= 256, "recent limit")
|
||||
require(len(row["recent_ids"]) == len(set(row["recent_ids"])) == row["recent_count"] and
|
||||
row["recent_ids"].count(report["event_id"]) == row["replacement_count"],
|
||||
"replacement does not match actual window")
|
||||
previous = row["after"]
|
||||
require(report["final_minute"] == previous, "final minute")
|
||||
for name, entry in report["states"].items():
|
||||
require(re.fullmatch("[0-9a-f]{64}", entry["sha256"]) and entry["bytes"] > 0 and
|
||||
entry["path"] == "p28_restart/evidence/" + name + ".bin", "state binding")
|
||||
required = {"P", "after_save", "final"} if writer else {"P", "B", "after_tamper", "after_load", "final"}
|
||||
require(set(report["states"]) == required, "missing complete state files")
|
||||
require(report["states"]["after_save" if writer else "after_load"]["sha256"] ==
|
||||
io["comparison"]["actual_sha256"], "IO state file differs")
|
||||
if not writer:
|
||||
require(report["states"]["after_tamper"]["sha256"] == report["io"][0]["comparison"]["actual_sha256"],
|
||||
"tamper state file differs")
|
||||
if report["role"].startswith("O-"):
|
||||
observation = report["at_390"]
|
||||
require(isinstance(observation, dict) and observation["clock"] == {"absolute_minute": 390},
|
||||
"at390 missing/clock")
|
||||
require(isinstance(observation["operations"], dict) and
|
||||
type(observation["domain_event_counter"]) is int and
|
||||
type(observation["published_ids_count"]) is int and
|
||||
observation["domain_event_counter"] >= observation["published_ids_count"] >= 0,
|
||||
"at390 report/counter")
|
||||
require(isinstance(observation["processed_arrival_minutes"], list) and
|
||||
observation["processed_arrival_minutes"] == sorted(set(observation["processed_arrival_minutes"])) and
|
||||
all(type(m) is int and m <= 390 for m in observation["processed_arrival_minutes"]),
|
||||
"at390 processed minutes")
|
||||
ids = observation["guest_auto_ids"]
|
||||
require(isinstance(ids, list) and ids == sorted(set(ids)) and
|
||||
type(observation["guest_auto_count"]) is int and
|
||||
len(ids) == observation["guest_auto_count"] and
|
||||
all(x.startswith("guest-auto-") for x in ids), "at390 guests")
|
||||
require(all(type(e["absolute_minute"]) is int and
|
||||
report["saved_minute"] < e["absolute_minute"] <= 390
|
||||
for e in observation["events"]), "at390 event window")
|
||||
event_ids = [e["event_id"] for e in observation["events"]]
|
||||
require(len(event_ids) == len(set(event_ids)), "at390 duplicate event")
|
||||
at_rows = [r for r in rows if r["after"] == 390]
|
||||
require(len(at_rows) == 1 and at_rows[0]["operations_observation"] ==
|
||||
{k: v for k, v in observation.items() if k != "events"}, "at390 minute consistency")
|
||||
require(all(e in at_rows[0]["recent_ids"] for e in event_ids), "at390 event window IDs")
|
||||
boundary = report["boundary"]
|
||||
require(boundary is not None and boundary["guest_auto_count"] > 0, "arrival not processed")
|
||||
require(len(boundary["guest_auto_ids"]) == len(set(boundary["guest_auto_ids"])) ==
|
||||
boundary["guest_auto_count"], "arrival IDs")
|
||||
require(all(x.startswith("guest-auto-") for x in boundary["guest_auto_ids"]), "arrival identity")
|
||||
require(boundary["clock"] == report["saved_minute"] and boundary["processed_arrival_minutes"],
|
||||
"arrival boundary not processed")
|
||||
if writer:
|
||||
require(0 < report["saved_minute"] - report["start_minute"] <= 120, "arrival cap")
|
||||
ticks = report["ui_ticks"]
|
||||
require(ticks and ticks[-1] == boundary and all(t["guest_auto_count"] == 0 for t in ticks[:-1]),
|
||||
"not first arrival tick")
|
||||
next_boundary = report["next_boundary"]
|
||||
added = [m for m in next_boundary["after"] if m not in next_boundary["before"]]
|
||||
require(next_boundary["before"] == boundary["processed_arrival_minutes"] and
|
||||
added == [next_boundary["minute"]] and next_boundary["processed_once"] is True and
|
||||
next_boundary["minute"] > boundary["clock"] and
|
||||
next_boundary["minute"] <= report["final_minute"], "next arrival missing/duplicate")
|
||||
minutes = next_boundary["after"]
|
||||
require(minutes == sorted(set(minutes)), "duplicate processed arrivals")
|
||||
require([r["minute"] for r in next_boundary["arrival_reports"]] == minutes,
|
||||
"arrival report duplicates/omissions")
|
||||
else:
|
||||
require(report["boundary"] is None and report["next_boundary"] is None and
|
||||
report["at_390"] is None, "R Operations boundary")
|
||||
return True
|
||||
|
||||
|
||||
def check_no_metrics(value):
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
require(not key.endswith(("_us", "_ms")) and key not in
|
||||
("performance", "statistics", "fps", "budget_verdict"), "performance field forbidden")
|
||||
check_no_metrics(child)
|
||||
elif isinstance(value, list):
|
||||
for child in value:
|
||||
check_no_metrics(child)
|
||||
|
||||
|
||||
def canonical_observation(observation, eid):
|
||||
result = copy.deepcopy(observation)
|
||||
if result is not None:
|
||||
result["events"] = [
|
||||
{"event_id": eid, "replaced_for_cross_pid_compare": True,
|
||||
"canonical_sha256": e["canonical_sha256"]} if e["event_id"] == eid else e
|
||||
for e in result["events"]]
|
||||
return result
|
||||
|
||||
|
||||
def check_pair(writer, reader, writer_sidecar=None, reader_sidecar=None):
|
||||
check_report(writer, writer_sidecar)
|
||||
check_report(reader, reader_sidecar)
|
||||
writer, reader = writer_sidecar["report"], reader_sidecar["report"]
|
||||
require(writer["role"].replace("writer", "reader") == reader["role"] and writer["pid"] != reader["pid"],
|
||||
"pair roles/PID")
|
||||
require(writer["event_id"] == reader["event_id"] and writer["saved_minute"] == reader["saved_minute"],
|
||||
"pair IO identity")
|
||||
require(writer["states"]["P"] == reader["states"]["P"], "package P differs")
|
||||
require(writer["io"][0]["before"] == reader["io"][1]["before"], "package history differs")
|
||||
for left, right in zip(writer["minutes"], reader["minutes"]):
|
||||
require(all(left[k] == right[k] for k in
|
||||
("before", "after", "sha256", "scope", "replacement_count", "recent_count")),
|
||||
"cross-PID complete typed state mismatch")
|
||||
require(writer["boundary"] == reader["boundary"] and writer["next_boundary"] == reader["next_boundary"],
|
||||
"Operations boundary differs")
|
||||
require(canonical_observation(writer["at_390"], writer["event_id"]) ==
|
||||
canonical_observation(reader["at_390"], reader["event_id"]), "at390 pair differs")
|
||||
|
||||
|
||||
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_RESTART_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_RESTART_REPORT=")]
|
||||
closes = [s.partition("=")[2] for s in raw.splitlines() if s.startswith("P28_RESTART_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, bound_sidecar(folder.parent, row))
|
||||
return report
|
||||
|
||||
|
||||
def bound_sidecar(out, row):
|
||||
binding = row["sidecar"]
|
||||
require(set(binding) == {"path", "sha256", "bytes"} and binding["path"] == SIDECAR,
|
||||
"sidecar binding/path")
|
||||
expected = {k: binding[k] for k in ("sha256", "bytes")}
|
||||
path = Path(out) / (row["phase"] + "-states") / "report_full.json"
|
||||
require(pin(path) == expected and pin(Path(row["runtime_profile"]) / SIDECAR) == expected,
|
||||
"sidecar SHA/private profile binding")
|
||||
return json.loads(path.read_text())
|
||||
|
||||
|
||||
def check_receipt(receipt, out):
|
||||
from restart_pair import COLLECTOR_PINS, check_transfer
|
||||
out = Path(out)
|
||||
require(receipt["passed"] is True and receipt["error"] is None and receipt["cleanup_errors"] == [],
|
||||
"execution failed")
|
||||
require(receipt["source_before"] == receipt["source_after"] and len(receipt["source_before"]) == 1407,
|
||||
"source map mismatch")
|
||||
require(receipt["default_player_before"] == receipt["default_player_after"], "ordinary 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"]) ==
|
||||
{"restart_pair.py", "restart_probe.gd", "check_restart.py", "test_restart.py"}, "worker changed")
|
||||
require(receipt["cross_cutting"] == CROSS, "cross-cutting record")
|
||||
require(receipt["original_credit"] == 0 and receipt["windows_verified"] is False and
|
||||
receipt["operations_200_guests_verified"] is False, "scope expanded")
|
||||
rows = receipt["phases"]
|
||||
require([r["phase"] for r in rows] == PHASES, "phase order/incomplete execution")
|
||||
pids = []
|
||||
for row in rows:
|
||||
report = bound_raw(out / row["phase"], row)
|
||||
pids.extend(row["capture"]["actual_captured_pids"])
|
||||
require(row["stage_before"] == row["stage_after"] if row["phase"] != "import"
|
||||
else row["stage_preserved"] is True, "stage changed")
|
||||
require(row["source_preserved"] is True and row["player_preserved"] is True, "phase preservation")
|
||||
startup = row["startup_project"]
|
||||
require(startup["fresh_before"] is True and sha(startup["path"]) == startup["sha256"], "startup profile")
|
||||
if 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")
|
||||
for name, evidence in report["states"].items():
|
||||
require(pin(out / (row["phase"] + "-states") / (name + ".bin")) ==
|
||||
{k: evidence[k] for k in ("sha256", "bytes")}, "state bytes SHA")
|
||||
require(receipt["profiles"][row["runtime_profile"]].get(SIDECAR) ==
|
||||
{k: row["sidecar"][k] for k in ("sha256", "bytes")}, "sidecar absent from file map")
|
||||
require(len(set(pids)) == len(pids), "reused PID")
|
||||
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")
|
||||
for start in (3, 5):
|
||||
check_pair(rows[start]["report"], rows[start + 1]["report"],
|
||||
bound_sidecar(out, rows[start]), bound_sidecar(out, rows[start + 1]))
|
||||
transfer = out / (rows[start]["phase"][0] + "-transfer")
|
||||
manifest_path = transfer / "manifest.json"
|
||||
manifest = json.loads(manifest_path.read_text())
|
||||
check_transfer(transfer, manifest)
|
||||
require(rows[start + 1]["report"]["transfer_manifest_sha256"] == sha(manifest_path),
|
||||
"reader manifest SHA")
|
||||
for phase_index, kinds in ((start, ("good",)), (start + 1, ("good", "tampered"))):
|
||||
profile = rows[phase_index]["runtime_profile"]
|
||||
for kind in kinds:
|
||||
for name, expected in manifest["files"][kind].items():
|
||||
relative = "p28_restart/" + kind + "/" + name
|
||||
require(receipt["profiles"][profile].get(relative) == expected,
|
||||
"private package differs from transfer")
|
||||
for path, expected in receipt["script_pins"].items():
|
||||
require(sha(path) == expected, "executed script changed")
|
||||
check_probe((out / "restart_probe.gd").read_text())
|
||||
require(sha(out / "restart_probe.gd") == receipt["worker_before"]["restart_probe.gd"],
|
||||
"executed probe differs from submitted source")
|
||||
require(receipt["owned_writes"] == [], "probe/driver wrote owned folder")
|
||||
require(receipt["coverage"] == coverage([bound_sidecar(out, r)["report"] for r in rows[3:]]),
|
||||
"overload coverage/persistence claim")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("execution", type=Path)
|
||||
args = parser.parse_args()
|
||||
check_receipt(json.loads(args.execution.read_text()), args.execution.parent)
|
||||
print("P28_RESTART_CHECK=PASS")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Root-only four-PID RESTART 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_restart 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 ("restart_pair.py", "check_restart.py", "test_restart.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("restart_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 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 / "restart_probe.gd"),
|
||||
collector.parse_argv(engine, stage, wrapper)] + [
|
||||
collector.argv_for(engine, stage, wrapper) for _ in range(4)]
|
||||
|
||||
|
||||
def schedule(rows):
|
||||
for index, label in enumerate(check.PHASES):
|
||||
check.require(all(row["passed"] is True for row in rows), "previous phase failed")
|
||||
if label.endswith("reader"):
|
||||
check.require(rows and rows[-1]["phase"] == label.replace("reader", "writer") and
|
||||
rows[-1].get("report", {}).get("stop_reason") == "complete",
|
||||
"reader requires successful writer")
|
||||
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 safe_inventory(directory):
|
||||
directory = Path(directory)
|
||||
check.require(directory.is_dir() and not directory.is_symlink(), "package missing/symlink")
|
||||
result = {}
|
||||
for path in sorted(directory.rglob("*")):
|
||||
check.require(not path.is_symlink(), "package symlink")
|
||||
if path.is_file():
|
||||
result[path.relative_to(directory).as_posix()] = check.pin(path)
|
||||
check.require(result, "empty package")
|
||||
return result
|
||||
|
||||
|
||||
def create_transfer(source, destination, write):
|
||||
"""Copy only after writer exit; never pre-populate the reader profile."""
|
||||
before = safe_inventory(source)
|
||||
check.require("world.bin" in before and not destination.exists(), "transfer admission")
|
||||
destination.mkdir()
|
||||
shutil.copytree(source, destination / "good")
|
||||
check.require(safe_inventory(destination / "good") == before, "good copy differs")
|
||||
shutil.copytree(destination / "good", destination / "tampered")
|
||||
world = destination / "tampered/world.bin"
|
||||
data = bytearray(world.read_bytes())
|
||||
check.require(data, "empty world")
|
||||
offset = len(data) // 2
|
||||
data[offset] ^= 1
|
||||
world.write_bytes(data)
|
||||
manifest = {"files": {"good": before, "tampered": safe_inventory(destination / "tampered")},
|
||||
"tamper": {"file": "world.bin", "offset": offset, "xor": 1,
|
||||
"before_sha256": before["world.bin"]["sha256"], "after_sha256": check.sha(world)}}
|
||||
write(destination / "manifest.json", manifest)
|
||||
check_transfer(destination, manifest)
|
||||
check.require(safe_inventory(source) == before, "writer package changed")
|
||||
return manifest
|
||||
|
||||
|
||||
def check_transfer(directory, manifest):
|
||||
check.require(set(manifest["files"]) == {"good", "tampered"}, "transfer kinds")
|
||||
for kind in ("good", "tampered"):
|
||||
check.require(safe_inventory(directory / kind) == manifest["files"][kind], "transfer SHA mismatch")
|
||||
good = (directory / "good/world.bin").read_bytes()
|
||||
bad = (directory / "tampered/world.bin").read_bytes()
|
||||
tamper = manifest["tamper"]
|
||||
check.require(len(good) == len(bad) > 0 and tamper["offset"] == len(good) // 2 and tamper["xor"] == 1,
|
||||
"tamper location")
|
||||
expected = bytearray(good)
|
||||
expected[len(good) // 2] ^= 1
|
||||
check.require(bytes(expected) == bad and tamper["before_sha256"] == check.sha(directory / "good/world.bin")
|
||||
and tamper["after_sha256"] == check.sha(directory / "tampered/world.bin"), "tamper bytes")
|
||||
for name, value in manifest["files"]["good"].items():
|
||||
if name != "world.bin":
|
||||
check.require(manifest["files"]["tampered"].get(name) == value, "extra tamper difference")
|
||||
check.require(set(manifest["files"]["good"]) == set(manifest["files"]["tampered"]), "tamper inventory")
|
||||
|
||||
|
||||
def fresh_profile(path):
|
||||
check.require(not path.exists(), "profile must be fresh; pre-guard writes forbidden")
|
||||
|
||||
|
||||
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 bind_report(out, label, row):
|
||||
text = (out / label / "stdout.log").read_text()
|
||||
reports = [s.partition("=")[2] for s in text.splitlines() if s.startswith("P28_RESTART_REPORT=")]
|
||||
check.require(len(reports) == 1, "one raw report required")
|
||||
row["report"] = json.loads(reports[0])
|
||||
copy_states(out, row)
|
||||
check.bound_raw(out / label, row)
|
||||
return row["report"]
|
||||
|
||||
|
||||
def copy_states(out, row):
|
||||
folder = out / (row["phase"] + "-states")
|
||||
folder.mkdir()
|
||||
profile = Path(row["runtime_profile"])
|
||||
source = profile / check.SIDECAR
|
||||
row["sidecar"] = {"path": check.SIDECAR, **check.pin(source)}
|
||||
shutil.copyfile(source, folder / "report_full.json")
|
||||
check.require(check.pin(source) == check.pin(folder / "report_full.json"), "sidecar copy differs")
|
||||
for name, expected in row["report"]["states"].items():
|
||||
source = profile / expected["path"]
|
||||
check.require(source.resolve().is_relative_to(profile.resolve()) and
|
||||
check.pin(source) == {k: expected[k] for k in ("bytes", "sha256")}, "state binding")
|
||||
shutil.copyfile(source, folder / (name + ".bin"))
|
||||
check.require(check.pin(source) == check.pin(folder / (name + ".bin")), "state copy differs")
|
||||
|
||||
|
||||
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) == collector.FIXTURE_SHA and check.sha(engine) == collector.ENGINE_SHA,
|
||||
"fixture/engine SHA")
|
||||
check.check_probe((HERE / "restart_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)
|
||||
worker_pins = {name: check.sha(HERE / name) for name in
|
||||
("restart_pair.py", "restart_probe.gd", "check_restart.py", "test_restart.py")}
|
||||
commands = plan(collector, engine, out)
|
||||
admission = {"commit": commit, "source_map": source, "readonly": inputs, "planned_argv": commands,
|
||||
"engine_sha256": check.sha(engine), "fixture_sha256": check.sha(fixture),
|
||||
"dependencies": check.DEPENDENCIES, "collector_Q": pinned,
|
||||
"worker_pins": worker_pins,
|
||||
"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
|
||||
check.require(sys.platform == "darwin", "reviewed native platform is macOS")
|
||||
collector.no_live_engine()
|
||||
check.require(shutil.disk_usage(out.parent).free >= 1200000000, "disk space")
|
||||
player = standard.default_player(ROOT)
|
||||
out.mkdir(parents=True)
|
||||
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 ("restart_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 = {str(p): check.sha(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.sha(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 + "-restart-startup"
|
||||
runtime_name = "TheTree-PCK-tests-" + uuid.uuid4().hex + "-restart-runtime"
|
||||
startup, runtime = (standard.profile_path(n) for n in (startup_name, runtime_name))
|
||||
fresh_profile(startup)
|
||||
fresh_profile(runtime)
|
||||
profiles.extend([startup, runtime])
|
||||
transfer = out / (label[0] + "-transfer")
|
||||
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_RESTART_ROLE": label, "THE_TREE_RESTART_K": "30",
|
||||
"THE_TREE_RESTART_TRANSFER": str(transfer),
|
||||
"THE_TREE_RESTART_TRANSFER_MANIFEST": str(transfer / "manifest.json"),
|
||||
"THE_TREE_P28_FIXTURE": str(fixture), "THE_TREE_P28_FIXTURE_SHA": collector.FIXTURE_SHA}
|
||||
if label.endswith("reader"):
|
||||
check_transfer(transfer, json.loads((transfer / "manifest.json").read_text()))
|
||||
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,
|
||||
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))
|
||||
report = bind_report(out, label, row)
|
||||
collector.write(out / (label + "-report.json"), report)
|
||||
if label.endswith("writer"):
|
||||
create_transfer(runtime / "p28_restart/good", transfer, collector.write)
|
||||
else:
|
||||
check.require(report["transfer_manifest_sha256"] == check.sha(transfer / "manifest.json"),
|
||||
"reader transfer manifest binding")
|
||||
check.check_pair(rows[-2]["report"], report, check.bound_sidecar(out, rows[-2]),
|
||||
check.bound_sidecar(out, row))
|
||||
else:
|
||||
check.bound_raw(out / label, row)
|
||||
check.require(row["source_preserved"] and row["player_preserved"] and
|
||||
check.sha(fixture) == collector.FIXTURE_SHA and
|
||||
all(check.sha(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": [],
|
||||
"cross_cutting": check.CROSS, "original_credit": 0,
|
||||
"windows_verified": False, "operations_200_guests_verified": False}
|
||||
try:
|
||||
receipt["coverage"] = check.coverage([
|
||||
check.bound_sidecar(out, row)["report"] for row in rows if "sidecar" in row])
|
||||
except (ValueError, KeyError, TypeError, OSError) as caught:
|
||||
receipt["coverage"] = {"overload_nonzero_backlog": "not_exercised",
|
||||
"error": repr(caught)}
|
||||
receipt.update(passed=False, error=receipt["error"] or "coverage: " + repr(caught))
|
||||
if receipt["passed"]:
|
||||
try:
|
||||
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)
|
||||
return 0 if receipt["passed"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+603
@@ -0,0 +1,603 @@
|
||||
extends SceneTree
|
||||
## Root-owned execution only. All files are written after Guard.activate().
|
||||
var GameClock: Variant
|
||||
var runtime: Variant
|
||||
var main: Variant
|
||||
var report: Dictionary = {}
|
||||
var signals: Array = []
|
||||
var key_forms: Dictionary = {}
|
||||
var role := ""
|
||||
var start_minute := 0
|
||||
var selected_tick := false
|
||||
var overloaded := false
|
||||
var io_ready := false
|
||||
var finished := false
|
||||
var E := ""
|
||||
var package: Dictionary = {}
|
||||
var failure_event: Dictionary = {}
|
||||
var boundary: Variant = null
|
||||
var at_390_event_verbatim: Array = []
|
||||
var at_390_pending: Array = []
|
||||
var redaction_ok := true
|
||||
var canonical_key_collision := false
|
||||
const SAVE_EVENTS := ["SAVE_CHECKPOINT_SUCCEEDED", "SAVE_CHECKPOINT_FAILED", "SAVE_LOADED",
|
||||
"SAVE_LOAD_FAILED", "SAVE_MIGRATION_SUCCEEDED", "SAVE_MIGRATION_FAILED",
|
||||
"SAVE_RECOVERY_LOADED", "SAVE_RECOVERY_CANDIDATE_LOADED", "SAVE_RECOVERY_FAILED"]
|
||||
|
||||
func canonical_json(value: Variant) -> String:
|
||||
if value is Dictionary:
|
||||
var keys: Array = value.keys()
|
||||
keys.sort_custom(func(a, b): return str(a) < str(b))
|
||||
for index: int in range(1, keys.size()):
|
||||
if str(keys[index - 1]) == str(keys[index]):
|
||||
canonical_key_collision = true
|
||||
# Unwind serialization before emitting failure evidence.
|
||||
call_deferred("finish", "canonical_key_collision")
|
||||
return ""
|
||||
var parts: PackedStringArray = []
|
||||
for key: Variant in keys:
|
||||
parts.append(JSON.stringify(str(key)) + ":" + canonical_json(value[key]))
|
||||
return "{" + ",".join(parts) + "}"
|
||||
if value is Array:
|
||||
var parts: PackedStringArray = []
|
||||
for child: Variant in value:
|
||||
parts.append(canonical_json(child))
|
||||
return "[" + ",".join(parts) + "]"
|
||||
if value is int or value is float:
|
||||
return "%.17f" % float(value)
|
||||
return JSON.stringify(value)
|
||||
|
||||
func event_summary(event: Dictionary) -> Dictionary:
|
||||
var canonical: Dictionary = {"event_id": E, "replaced_for_cross_pid_compare": true} \
|
||||
if field(event, "event_id") == E else event
|
||||
return {"event_id": field(event, "event_id"), "event_type": field(event, "event_type"),
|
||||
"absolute_minute": field(event, "absolute_minute"),
|
||||
"raw_event_sha256": bytes_hash(canonical_json(event).to_utf8_buffer()),
|
||||
"canonical_sha256": bytes_hash(canonical_json(canonical).to_utf8_buffer())}
|
||||
|
||||
func observe_390() -> void:
|
||||
if not role.begins_with("O-") or GameClock.absolute_minute != 390 or report.at_390 != null:
|
||||
return
|
||||
var observed := operations_observation()
|
||||
var state := witness(runtime.create_snapshot())
|
||||
report.at_390 = {"clock": {"absolute_minute": GameClock.absolute_minute},
|
||||
"operations": observed.operations, "processed_arrival_minutes": observed.processed_arrival_minutes,
|
||||
"guest_auto_ids": observed.guest_auto_ids, "guest_auto_count": observed.guest_auto_count,
|
||||
"published_ids_count": state.published_ids.size(), "domain_event_counter": state.counter,
|
||||
"events": []}
|
||||
at_390_pending = state.recent_events.duplicate(true)
|
||||
if report.has("saved_minute"):
|
||||
bind_390_events()
|
||||
|
||||
func bind_390_events() -> void:
|
||||
if report.at_390 == null:
|
||||
return
|
||||
at_390_event_verbatim = []
|
||||
report.at_390.events = []
|
||||
for event: Dictionary in at_390_pending:
|
||||
var minute: int = field(event, "absolute_minute")
|
||||
if minute > report.saved_minute and minute <= 390:
|
||||
at_390_event_verbatim.append(event.duplicate(true))
|
||||
report.at_390.events.append(event_summary(event))
|
||||
|
||||
func deadline_timer_seconds() -> float:
|
||||
return Time.get_ticks_msec() / 1000.0
|
||||
|
||||
func field(d: Variant, name: String) -> Variant:
|
||||
if d is Dictionary:
|
||||
if d.has(StringName(name)):
|
||||
key_forms[name] = "StringName"
|
||||
return d[StringName(name)]
|
||||
if d.has(String(name)):
|
||||
key_forms[name] = "String"
|
||||
return d[String(name)]
|
||||
key_forms[name] = "missing_product_field"
|
||||
return null
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("begin")
|
||||
|
||||
func bytes_hash(data: PackedByteArray) -> String:
|
||||
var context := HashingContext.new()
|
||||
context.start(HashingContext.HASH_SHA256)
|
||||
context.update(data)
|
||||
return context.finish().hex_encode()
|
||||
|
||||
func runtime_part(snapshot: Dictionary) -> Dictionary:
|
||||
return field(field(snapshot, "systems"), "runtime")
|
||||
|
||||
func witness(snapshot: Dictionary) -> Dictionary:
|
||||
var part := runtime_part(snapshot)
|
||||
var events: Dictionary = field(part, "domain_events")
|
||||
return {"counter": field(field(field(part, "id_generator"), "counters"), "domain_event"),
|
||||
"published_ids": field(events, "published_ids").duplicate(true),
|
||||
"recent_events": field(events, "recent_events").duplicate(true)}
|
||||
|
||||
func different_paths(a: Variant, b: Variant, path := "$", depth := 0) -> Array:
|
||||
var result: Array = []
|
||||
if var_to_bytes(a) == var_to_bytes(b):
|
||||
return result
|
||||
if depth >= 4 or typeof(a) != typeof(b):
|
||||
return [path]
|
||||
if a is Dictionary:
|
||||
for key: Variant in a:
|
||||
if not b.has(key):
|
||||
result.append(path + "." + str(key))
|
||||
else:
|
||||
result.append_array(different_paths(a[key], b[key], path + "." + str(key), depth + 1))
|
||||
for key: Variant in b:
|
||||
if not a.has(key):
|
||||
result.append(path + "." + str(key))
|
||||
elif a is Array and a.size() == b.size():
|
||||
for index in range(a.size()):
|
||||
result.append_array(different_paths(a[index], b[index], path + "[%d]" % index, depth + 1))
|
||||
else:
|
||||
result.append(path)
|
||||
# Typed container/key/order differences can exist without value differences.
|
||||
if result.is_empty():
|
||||
result.append(path)
|
||||
return result
|
||||
|
||||
func compare(expected: Dictionary, actual: Dictionary) -> Dictionary:
|
||||
var a := var_to_bytes(expected)
|
||||
var b := var_to_bytes(actual)
|
||||
return {"method": "var_to_bytes", "equal": a == b, "expected_sha256": bytes_hash(a),
|
||||
"actual_sha256": bytes_hash(b), "excluded_paths": [],
|
||||
"diff_paths": different_paths(expected, actual)}
|
||||
|
||||
func record_state(name: String, value: Dictionary) -> bool:
|
||||
var path := "user://p28_restart/evidence/" + name + ".bin"
|
||||
if DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(path).get_base_dir()) != OK:
|
||||
return false
|
||||
var data := var_to_bytes(value)
|
||||
var file := FileAccess.open(path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return false
|
||||
file.store_buffer(data)
|
||||
file.close()
|
||||
report.states[name] = {"path": path.trim_prefix("user://"), "bytes": data.size(),
|
||||
"sha256": bytes_hash(data)}
|
||||
return FileAccess.get_sha256(path) == report.states[name].sha256
|
||||
|
||||
func on_save(result: Dictionary) -> void:
|
||||
signals.append(result.duplicate(true))
|
||||
|
||||
func auto_ids() -> Array:
|
||||
var ids: Array = []
|
||||
for guest: Variant in runtime.guest_service.get_guests():
|
||||
if str(guest.guest_id).begins_with("guest-auto-"):
|
||||
ids.append(str(guest.guest_id))
|
||||
ids.sort()
|
||||
return ids
|
||||
|
||||
func operations_observation() -> Dictionary:
|
||||
var ids := auto_ids()
|
||||
var processed: Array = []
|
||||
var arrivals: Array = []
|
||||
if runtime.operations_coordinator != null:
|
||||
var state: Dictionary = runtime.operations_coordinator.create_snapshot()
|
||||
processed = field(state, "processed_arrival_minutes").duplicate()
|
||||
arrivals = runtime.operations_coordinator.get_arrival_reports().duplicate(true)
|
||||
return {"clock": GameClock.absolute_minute, "guest_auto_ids": ids, "guest_auto_count": ids.size(),
|
||||
"operations": runtime.get_operations_report(), "processed_arrival_minutes": processed,
|
||||
"arrival_reports": arrivals}
|
||||
|
||||
func on_tick(minute: int) -> void:
|
||||
# During deterministic stepping the observation is made after advance returns.
|
||||
if not io_ready:
|
||||
observe_390()
|
||||
if io_ready or finished or selected_tick:
|
||||
return
|
||||
var observed := operations_observation()
|
||||
if role == "O-writer":
|
||||
report.ui_ticks.append(observed)
|
||||
var stop: bool = (role == "R-writer" and minute - start_minute >= 10) or \
|
||||
(role == "O-writer" and (observed.guest_auto_count > 0 or minute - start_minute >= 120))
|
||||
if stop:
|
||||
GameClock.set_paused(true)
|
||||
selected_tick = true
|
||||
|
||||
func on_overload(backlog: int) -> void:
|
||||
overloaded = true
|
||||
report.overloads.append({"minute": GameClock.absolute_minute, "backlog": backlog})
|
||||
|
||||
func clock_observation() -> Dictionary:
|
||||
return {"minute": GameClock.absolute_minute, "paused": GameClock.paused,
|
||||
"backlog": GameClock.get_backlog_minutes(),
|
||||
"accumulator_hex": PackedFloat64Array([GameClock.get("_accumulator")]).to_byte_array().hex_encode()}
|
||||
|
||||
func begin() -> void:
|
||||
GameClock = root.get_node("GameClock")
|
||||
runtime = root.get_node("SimulationRuntime")
|
||||
role = OS.get_environment("THE_TREE_RESTART_ROLE")
|
||||
report = {"schema": "p28-restart-v1", "role": role, "pid": OS.get_process_id(),
|
||||
"main_added": false, "k_target": 30, "actual_k": 0, "io": [], "states": {},
|
||||
"minutes": [], "ui_ticks": [], "overloads": [], "boundary": null, "next_boundary": null,
|
||||
"at_390": null,
|
||||
"comparison_scope": "complete_typed_var_to_bytes", "excluded_paths": [],
|
||||
"transfer_before_guard_writes": 0, "transfer_verified": false,
|
||||
"probe_deadline_seconds": 1740}
|
||||
var elapsed := deadline_timer_seconds()
|
||||
var interval := 1740.0 - elapsed
|
||||
report["probe_deadline"] = {"limit_seconds_from_engine_start": 1740,
|
||||
"engine_elapsed_at_begin_seconds": elapsed, "timer_seconds": interval}
|
||||
if interval <= 0:
|
||||
finish("probe_deadline")
|
||||
return
|
||||
create_timer(interval).timeout.connect(func() -> void: finish("probe_deadline"))
|
||||
if OS.get_environment("THE_TREE_RESTART_K") != "30" or role not in ["R-writer", "R-reader", "O-writer", "O-reader"]:
|
||||
finish("invalid_environment")
|
||||
return
|
||||
# No await before main: the default autoload clock cannot process here.
|
||||
if role == "R-writer":
|
||||
var fixture := OS.get_environment("THE_TREE_P28_FIXTURE")
|
||||
if FileAccess.get_sha256(fixture) != OS.get_environment("THE_TREE_P28_FIXTURE_SHA"):
|
||||
finish("fixture_sha")
|
||||
return
|
||||
var file := FileAccess.open(fixture, FileAccess.READ)
|
||||
if file == null:
|
||||
finish("fixture_missing")
|
||||
return
|
||||
var value: Variant = file.get_var(false)
|
||||
file.close()
|
||||
if not value is Dictionary or not runtime.restore_snapshot(value):
|
||||
finish("fixture_restore")
|
||||
return
|
||||
main = load("res://scenes/main/main.tscn").instantiate()
|
||||
root.add_child(main)
|
||||
report.main_added = true
|
||||
report["startup"] = {"start_report": main.get("_operations_start_report").duplicate(true),
|
||||
"operations": runtime.get_operations_report(), "clock": clock_observation()}
|
||||
if role != "R-writer" and not GameClock.paused:
|
||||
finish("main_did_not_pause")
|
||||
return
|
||||
var start_result: Dictionary = report.startup.start_report
|
||||
var operations_enabled: Variant = field(report.startup.operations, "enabled")
|
||||
if role == "R-writer":
|
||||
if operations_enabled != false or field(start_result, "code") != "INVALID_START_TIME":
|
||||
finish("unexpected_R_start")
|
||||
return
|
||||
elif operations_enabled != true or field(start_result, "ok") != true or GameClock.absolute_minute != 360:
|
||||
finish("unexpected_fresh_start")
|
||||
return
|
||||
runtime.save_operation_completed.connect(on_save)
|
||||
GameClock.tick_completed.connect(on_tick)
|
||||
GameClock.simulation_overloaded.connect(on_overload)
|
||||
start_minute = GameClock.absolute_minute
|
||||
report["start_minute"] = start_minute
|
||||
if role.ends_with("writer"):
|
||||
main.call("_set_speed", 1)
|
||||
while not GameClock.paused and not finished:
|
||||
await process_frame
|
||||
if finished:
|
||||
return
|
||||
if not selected_tick and not overloaded:
|
||||
finish("unexpected_ui_stop")
|
||||
return
|
||||
if role == "O-writer" and auto_ids().is_empty():
|
||||
report["boundary"] = null
|
||||
report["boundary_reason"] = "no_arrival_within_cap" if GameClock.absolute_minute - start_minute >= 120 else "stopped_before_arrival"
|
||||
finish(report.boundary_reason)
|
||||
return
|
||||
if not writer_save():
|
||||
finish("save_comparison_failed")
|
||||
return
|
||||
else:
|
||||
if not import_transfer():
|
||||
finish("transfer_failed")
|
||||
return
|
||||
if not reader_load():
|
||||
finish("load_comparison_failed")
|
||||
return
|
||||
report["event_id"] = E
|
||||
report["saved_minute"] = GameClock.absolute_minute
|
||||
bind_390_events()
|
||||
boundary = operations_observation() if role.begins_with("O-") else null
|
||||
report["boundary"] = boundary
|
||||
var idle_before: Dictionary = runtime.create_snapshot()
|
||||
var clock_before := clock_observation()
|
||||
await process_frame
|
||||
await process_frame
|
||||
if finished:
|
||||
return
|
||||
var idle_after: Dictionary = runtime.create_snapshot()
|
||||
report["paused_idle"] = {"before": clock_before, "after": clock_observation(),
|
||||
"comparison": compare(idle_before, idle_after)}
|
||||
if not report.paused_idle.comparison.equal or report.paused_idle.before != report.paused_idle.after:
|
||||
finish("paused_backlog_consumed")
|
||||
return
|
||||
io_ready = true
|
||||
for _index in range(30):
|
||||
if not deterministic_step():
|
||||
finish("no_tick_advance_or_invalid_state")
|
||||
return
|
||||
# Keep the event loop responsive to deadline/audio while remaining paused.
|
||||
await process_frame
|
||||
if finished:
|
||||
return
|
||||
if not record_state("final", runtime.create_snapshot()):
|
||||
finish("state_write_failed")
|
||||
return
|
||||
report["final_minute"] = GameClock.absolute_minute
|
||||
if role.begins_with("O-"):
|
||||
if report.at_390 == null:
|
||||
finish("at_390_not_reached")
|
||||
return
|
||||
var last := operations_observation()
|
||||
var added: Array = []
|
||||
for minute: Variant in last.processed_arrival_minutes:
|
||||
if minute not in boundary.processed_arrival_minutes:
|
||||
added.append(minute)
|
||||
report["next_boundary"] = {"minute": added[0] if added.size() == 1 else null,
|
||||
"processed_once": added.size() == 1, "before": boundary.processed_arrival_minutes,
|
||||
"after": last.processed_arrival_minutes, "arrival_reports": last.arrival_reports,
|
||||
"guest_auto_ids": last.guest_auto_ids}
|
||||
if added.size() != 1:
|
||||
finish("next_arrival_boundary_missing_or_duplicate")
|
||||
return
|
||||
finish("complete")
|
||||
|
||||
func io_proof(base: Dictionary, after: Dictionary, kind: String, returned: Variant) -> bool:
|
||||
var row := {"kind": kind, "signal_count": signals.size(),
|
||||
"return_kind": "bool" if kind == "save" else "void", "return_value": returned}
|
||||
report.io.append(row)
|
||||
if signals.size() != 1:
|
||||
return false
|
||||
var signal_result: Dictionary = signals[0]
|
||||
var payload: Dictionary = runtime.call("_save_event_payload", signal_result)
|
||||
var latest: Dictionary = runtime.latest_save_operation_result.duplicate(true)
|
||||
row["signal_projection"] = "_save_event_payload"
|
||||
row["signal_payload"] = payload
|
||||
row["latest"] = latest
|
||||
row["payload_bytes_equal"] = var_to_bytes(payload) == var_to_bytes(latest)
|
||||
var before := witness(base)
|
||||
var after_witness := witness(after)
|
||||
var eid := "domain_event.%012d" % (int(before.counter) + 1)
|
||||
var found: Array = []
|
||||
for event: Dictionary in after_witness.recent_events:
|
||||
if field(event, "event_id") == eid:
|
||||
found.append(event)
|
||||
if found.size() != 1 or before.published_ids.has(eid):
|
||||
return false
|
||||
var event: Dictionary = found[0]
|
||||
row["event"] = event
|
||||
row["before"] = before
|
||||
row["after"] = after_witness
|
||||
row["minute_before"] = field(field(base, "clock"), "absolute_minute")
|
||||
row["minute_after"] = field(field(after, "clock"), "absolute_minute")
|
||||
row["paused_before"] = field(field(base, "clock"), "paused")
|
||||
row["paused_after"] = field(field(after, "clock"), "paused")
|
||||
row["dropped"] = before.recent_events[0] if before.recent_events.size() == 256 else null
|
||||
var expected := base.duplicate(true)
|
||||
var part := runtime_part(expected)
|
||||
var counters: Dictionary = field(field(part, "id_generator"), "counters")
|
||||
counters["domain_event"] = int(before.counter) + 1
|
||||
var events: Dictionary = field(part, "domain_events")
|
||||
var ids: Array = field(events, "published_ids")
|
||||
ids.append(eid)
|
||||
ids.sort()
|
||||
var recent: Array = field(events, "recent_events")
|
||||
if recent.size() > 256:
|
||||
return false
|
||||
if recent.size() == 256:
|
||||
recent.pop_front()
|
||||
recent.append(event.duplicate(true))
|
||||
row["comparison"] = compare(expected, after)
|
||||
var expected_type := {"save": "SAVE_CHECKPOINT_SUCCEEDED", "load": "SAVE_LOADED", "tamper": "SAVE_LOAD_FAILED"}
|
||||
var ok: bool = row.payload_bytes_equal and row.comparison.equal and \
|
||||
var_to_bytes(field(event, "payload")) == var_to_bytes(payload) and \
|
||||
field(event, "event_type") == expected_type[kind] and field(event, "source_service") == "save_service" and \
|
||||
field(event, "entity_ids").is_empty() and field(event, "causation_command_id") == 0 and \
|
||||
row.minute_before == row.minute_after and field(event, "absolute_minute") == row.minute_after
|
||||
if kind == "tamper":
|
||||
var recovery: Variant = field(signal_result, "recovery_report")
|
||||
var migration: Variant = field(signal_result, "migration_attempted")
|
||||
row["failure"] = {"preload_pause_restored": field(signal_result, "preload_pause_restored"),
|
||||
"loaded_clock_paused": field(signal_result, "loaded_clock_paused"), "recovery_report": recovery,
|
||||
"migration_attempted": migration == true}
|
||||
failure_event = event.duplicate(true)
|
||||
return ok and field(payload, "ok") == false and field(payload, "code") != "OK" and \
|
||||
row.failure.preload_pause_restored == true and row.failure.loaded_clock_paused == row.paused_before and \
|
||||
row.paused_before == row.paused_after and (recovery == null or field(recovery, "restore_committed") == false)
|
||||
E = eid
|
||||
return ok and field(payload, "ok") == true and field(payload, "code") == "OK" and \
|
||||
(returned == true if kind == "save" else returned == null) and row.paused_after == true and \
|
||||
field(payload, "saved_absolute_minute") == row.minute_after
|
||||
|
||||
func writer_save() -> bool:
|
||||
var before: Dictionary = runtime.create_snapshot()
|
||||
var backlog: int = GameClock.get_backlog_minutes()
|
||||
report["overload_case"] = {"exercised": not report.overloads.is_empty() and backlog > 0,
|
||||
"overloads": report.overloads.size(), "backlog_at_save": backlog}
|
||||
signals = []
|
||||
var returned: bool = main.call("_save_manual_at_path", "user://p28_restart/good", "P28 RESTART")
|
||||
var after: Dictionary = runtime.create_snapshot()
|
||||
var loaded := SaveService.load_snapshot("user://p28_restart/good")
|
||||
if field(loaded, "ok") != true:
|
||||
return false
|
||||
package = field(loaded, "snapshot")
|
||||
report["package_matches_before"] = compare(before, package)
|
||||
if not record_state("P", package) or not record_state("after_save", after):
|
||||
return false
|
||||
return report.package_matches_before.equal and io_proof(package, after, "save", returned)
|
||||
|
||||
func import_transfer() -> bool:
|
||||
var transfer := OS.get_environment("THE_TREE_RESTART_TRANSFER")
|
||||
var manifest_path := OS.get_environment("THE_TREE_RESTART_TRANSFER_MANIFEST")
|
||||
if not transfer.is_absolute_path() or not manifest_path.is_absolute_path():
|
||||
return false
|
||||
var manifest: Variant = JSON.parse_string(FileAccess.get_file_as_string(manifest_path))
|
||||
if not manifest is Dictionary:
|
||||
return false
|
||||
for kind: String in ["good", "tampered"]:
|
||||
var target := "user://p28_restart/" + kind
|
||||
if DirAccess.dir_exists_absolute(target):
|
||||
return false
|
||||
for relative: String in manifest.files[kind]:
|
||||
if relative.is_absolute_path() or ".." in relative.split("/") or "\\" in relative:
|
||||
return false
|
||||
var source := transfer.path_join(kind).path_join(relative)
|
||||
var expected: Dictionary = manifest.files[kind][relative]
|
||||
if FileAccess.get_sha256(source) != expected.sha256:
|
||||
return false
|
||||
var data := FileAccess.get_file_as_bytes(source)
|
||||
if data.size() != int(expected.bytes):
|
||||
return false
|
||||
var destination := target.path_join(relative)
|
||||
if DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(destination).get_base_dir()) != OK:
|
||||
return false
|
||||
var file := FileAccess.open(destination, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return false
|
||||
file.store_buffer(data)
|
||||
file.close()
|
||||
if FileAccess.get_sha256(destination) != expected.sha256:
|
||||
return false
|
||||
report["transfer_manifest_sha256"] = FileAccess.get_sha256(manifest_path)
|
||||
report["transfer_verified"] = true
|
||||
return true
|
||||
|
||||
func reader_load() -> bool:
|
||||
var loaded := SaveService.load_snapshot("user://p28_restart/good")
|
||||
if field(loaded, "ok") != true:
|
||||
return false
|
||||
package = field(loaded, "snapshot")
|
||||
var before: Dictionary = runtime.create_snapshot()
|
||||
if not record_state("P", package) or not record_state("B", before):
|
||||
return false
|
||||
signals = []
|
||||
main.call("_load_manual_at_path", "user://p28_restart/tampered", true)
|
||||
var failed: Dictionary = runtime.create_snapshot()
|
||||
if not record_state("after_tamper", failed) or not io_proof(before, failed, "tamper", null):
|
||||
return false
|
||||
signals = []
|
||||
main.call("_load_manual_at_path", "user://p28_restart/good", true)
|
||||
var after: Dictionary = runtime.create_snapshot()
|
||||
if not record_state("after_load", after) or not io_proof(package, after, "load", null):
|
||||
return false
|
||||
report["failure_event_absent"] = true
|
||||
for event: Dictionary in witness(after).recent_events:
|
||||
if var_to_bytes(event) == var_to_bytes(failure_event):
|
||||
report.failure_event_absent = false
|
||||
return report.failure_event_absent
|
||||
|
||||
func deterministic_step() -> bool:
|
||||
if not io_ready or not GameClock.paused:
|
||||
return false
|
||||
var before: int = GameClock.absolute_minute
|
||||
var backlog: int = GameClock.get_backlog_minutes()
|
||||
GameClock.advance_minutes(1)
|
||||
var after: int = GameClock.absolute_minute
|
||||
if after != before + 1:
|
||||
return false
|
||||
observe_390()
|
||||
var state: Dictionary = runtime.create_snapshot()
|
||||
var normalized := state.duplicate(true)
|
||||
var recent: Array = field(field(runtime_part(normalized), "domain_events"), "recent_events")
|
||||
var replacement: Variant = null
|
||||
var count := 0
|
||||
var recent_ids: Array = []
|
||||
for index in range(recent.size()):
|
||||
recent_ids.append(field(recent[index], "event_id"))
|
||||
if field(recent[index], "event_id") == E:
|
||||
replacement = recent[index].duplicate(true)
|
||||
recent[index] = {"event_id": E, "replaced_for_cross_pid_compare": true}
|
||||
count += 1
|
||||
if count > 1:
|
||||
return false
|
||||
report.minutes.append({"before": before, "after": after, "paused": GameClock.paused,
|
||||
"backlog_before": backlog, "backlog_after": GameClock.get_backlog_minutes(),
|
||||
"sha256": bytes_hash(var_to_bytes(normalized)), "scope": "complete_typed_var_to_bytes",
|
||||
"excluded_paths": [], "replacement": replacement, "replacement_count": count,
|
||||
"recent_count": recent.size(), "recent_ids": recent_ids})
|
||||
report.actual_k += after - before
|
||||
if role.begins_with("O-") and after == 390:
|
||||
var observation: Dictionary = report.at_390.duplicate(true)
|
||||
observation.erase("events")
|
||||
report.minutes[-1]["operations_observation"] = observation
|
||||
return GameClock.paused and backlog == GameClock.get_backlog_minutes()
|
||||
|
||||
func is_save_event(event: Variant) -> bool:
|
||||
return event is Dictionary and field(event, "source_service") == "save_service" and \
|
||||
field(event, "event_type") in SAVE_EVENTS
|
||||
|
||||
func redact_payload(payload: Dictionary, pointer: String) -> void:
|
||||
if payload.has("timing_redactions"):
|
||||
redaction_ok = false
|
||||
return
|
||||
var markers: Array = []
|
||||
for key: String in ["duration_us", "runtime_duration_us"]:
|
||||
if not payload.has(key):
|
||||
redaction_ok = false
|
||||
return
|
||||
markers.append({"original_key": key, "sidecar_ref": pointer + "/" + key})
|
||||
payload.erase(key)
|
||||
payload["timing_redactions"] = markers
|
||||
|
||||
func redact_event(event: Variant, pointer: String) -> void:
|
||||
if is_save_event(event):
|
||||
redact_payload(event.payload, pointer + "/payload")
|
||||
|
||||
func public_report(full: Dictionary) -> Dictionary:
|
||||
var result: Dictionary = full.duplicate(true)
|
||||
for i in range(result.io.size()):
|
||||
var row: Dictionary = result.io[i]
|
||||
var pointer := "/report/io/%d" % i
|
||||
for name: String in ["signal_payload", "latest"]:
|
||||
if row.has(name):
|
||||
redact_payload(row[name], pointer + "/" + name)
|
||||
for name: String in ["event", "dropped"]:
|
||||
if row.has(name):
|
||||
redact_event(row[name], pointer + "/" + name)
|
||||
for side: String in ["before", "after"]:
|
||||
if row.has(side):
|
||||
for j in range(row[side].recent_events.size()):
|
||||
redact_event(row[side].recent_events[j], pointer + "/" + side + "/recent_events/%d" % j)
|
||||
for k in range(result.minutes.size()):
|
||||
redact_event(result.minutes[k].replacement, "/report/minutes/%d/replacement" % k)
|
||||
return result
|
||||
|
||||
func public_has_forbidden(value: Variant) -> bool:
|
||||
if value is Dictionary:
|
||||
for key: Variant in value:
|
||||
if str(key).ends_with("_" + "us") or str(key).ends_with("_" + "ms") or \
|
||||
str(key) in ["performance", "statistics", "fps", "budget_verdict"]:
|
||||
return true
|
||||
if public_has_forbidden(value[key]):
|
||||
return true
|
||||
elif value is Array:
|
||||
for child: Variant in value:
|
||||
if public_has_forbidden(child):
|
||||
return true
|
||||
return false
|
||||
|
||||
func finish(reason: String) -> void:
|
||||
if finished:
|
||||
return
|
||||
if canonical_key_collision:
|
||||
reason = "canonical_key_collision"
|
||||
finished = true
|
||||
report["stop_reason"] = reason
|
||||
report["key_forms"] = key_forms
|
||||
var sidecar_path := "user://p28_restart/evidence/report_full.json"
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(sidecar_path).get_base_dir())
|
||||
var file := FileAccess.open(sidecar_path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
quit(1)
|
||||
return
|
||||
file.store_string(JSON.stringify({"report": report, "at_390_event_verbatim": at_390_event_verbatim}, "", true, true))
|
||||
file.close()
|
||||
# Partial failure evidence stays in the sidecar. Never emit a leaking public payload.
|
||||
var public: Dictionary = public_report(report)
|
||||
if not redaction_ok or public_has_forbidden(public) or public.is_empty():
|
||||
print("P28_RESTART_EVIDENCE_FAILURE=" + reason)
|
||||
else:
|
||||
print("P28_RESTART_REPORT=" + JSON.stringify(public, "", true, true))
|
||||
if is_instance_valid(main) and main.is_inside_tree():
|
||||
main.ui_audio_window_close_drained.connect(emit_audio_close, CONNECT_ONE_SHOT)
|
||||
get_root().close_requested.emit()
|
||||
else:
|
||||
quit(1)
|
||||
|
||||
func emit_audio_close(result: Dictionary) -> void:
|
||||
print("P28_RESTART_AUDIO_CLOSE=" + JSON.stringify(result))
|
||||
+876
@@ -0,0 +1,876 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Engine-free positive and adversarial controls. No native acceptance claimed."""
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
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_restart as check
|
||||
import restart_pair as driver
|
||||
|
||||
DATA = b"synthetic typed bytes fixture (not engine evidence)"
|
||||
HASH = hashlib.sha256(DATA).hexdigest()
|
||||
|
||||
|
||||
def comparison():
|
||||
return {"method": "var_to_bytes", "equal": True, "diff_paths": [], "excluded_paths": [],
|
||||
"expected_sha256": HASH, "actual_sha256": HASH}
|
||||
|
||||
|
||||
def history(count=256):
|
||||
ids = ["domain_event.%012d" % n for n in range(1, count + 1)]
|
||||
return {"counter": count, "published_ids": ids,
|
||||
"recent_events": [{"event_id": eid, "event_type": "OLD", "payload": {"n": n}}
|
||||
for n, eid in enumerate(ids)]}
|
||||
|
||||
|
||||
def io(kind, minute=612, count=256):
|
||||
before = history(count)
|
||||
payload = {"ok": kind != "tamper", "code": "OK" if kind != "tamper" else "CORRUPT_WORLD",
|
||||
"operation": "save" if kind == "save" else "load",
|
||||
"save_path": "user://p28_restart/" + ("tampered" if kind == "tamper" else "good"),
|
||||
"save_id": "synthetic", "saved_absolute_minute": minute,
|
||||
"duration_us": 124, "runtime_duration_us": 57}
|
||||
event = {"event_id": check.event_id(count),
|
||||
"event_type": {"save": "SAVE_CHECKPOINT_SUCCEEDED", "load": "SAVE_LOADED",
|
||||
"tamper": "SAVE_LOAD_FAILED"}[kind],
|
||||
"source_service": "save_service", "entity_ids": [], "causation_command_id": 0,
|
||||
"absolute_minute": minute, "payload": copy.deepcopy(payload)}
|
||||
return {"kind": kind, "signal_count": 1, "signal_payload": payload, "latest": copy.deepcopy(payload),
|
||||
"event": event, "signal_projection": "_save_event_payload", "payload_bytes_equal": True,
|
||||
"return_kind": "bool" if kind == "save" else "void",
|
||||
"return_value": True if kind == "save" else None,
|
||||
"before": before, "after": check.transform(before, event),
|
||||
"dropped": before["recent_events"][0] if count == 256 else None,
|
||||
"comparison": comparison(), "minute_before": minute, "minute_after": minute,
|
||||
"paused_before": True, "paused_after": True,
|
||||
"failure": {"preload_pause_restored": True, "loaded_clock_paused": True,
|
||||
"migration_attempted": False, "recovery_report": {"restore_committed": False}}}
|
||||
|
||||
|
||||
def report(role="R-writer", pid=100):
|
||||
writer, ops = role.endswith("writer"), role.startswith("O-")
|
||||
minute = 361 if ops else 612
|
||||
rows = [io("save", minute)] if writer else [io("tamper", 360, 3), io("load", minute)]
|
||||
names = ["P", "after_save", "final"] if writer else ["P", "B", "after_tamper", "after_load", "final"]
|
||||
event = rows[-1]["event"]
|
||||
boundary = {"clock": minute, "guest_auto_ids": ["guest-auto-00000001"], "guest_auto_count": 1,
|
||||
"operations": {"enabled": True}, "processed_arrival_minutes": [360],
|
||||
"arrival_reports": [{"minute": 360}]} if ops else None
|
||||
next_boundary = {"minute": 390, "processed_once": True, "before": [360], "after": [360, 390],
|
||||
"arrival_reports": [{"minute": 360}, {"minute": 390}],
|
||||
"guest_auto_ids": ["guest-auto-00000001", "guest-auto-00000002"]} if ops else None
|
||||
clock = {"minute": minute, "paused": True, "backlog": 121, "accumulator_hex": "00"}
|
||||
result = {"schema": "p28-restart-v1", "role": role, "pid": pid, "stop_reason": "complete",
|
||||
"main_added": True, "k_target": 30, "actual_k": 30, "comparison_scope": "complete_typed_var_to_bytes",
|
||||
"excluded_paths": [], "transfer_before_guard_writes": 0, "probe_deadline_seconds": 1740,
|
||||
"probe_deadline": {"limit_seconds_from_engine_start": 1740,
|
||||
"engine_elapsed_at_begin_seconds": 12.25, "timer_seconds": 1727.75},
|
||||
"overloads": [],
|
||||
"overload_case": {"exercised": False, "overloads": 0, "backlog_at_save": 121},
|
||||
"io": rows, "package_matches_before": comparison(), "failure_event_absent": True,
|
||||
"transfer_verified": True, "event_id": event["event_id"], "saved_minute": minute,
|
||||
"start_minute": 360 if ops else 602, "final_minute": minute + 30,
|
||||
"states": {n: {"path": "p28_restart/evidence/" + n + ".bin", "bytes": len(DATA), "sha256": HASH}
|
||||
for n in names},
|
||||
"paused_idle": {"before": clock, "after": copy.deepcopy(clock), "comparison": comparison()},
|
||||
"boundary": boundary, "next_boundary": next_boundary, "ui_ticks": [copy.deepcopy(boundary)] if ops else [],
|
||||
"minutes": [{"before": minute + n, "after": minute + n + 1, "paused": True,
|
||||
"backlog_before": 121, "backlog_after": 121, "sha256": HASH,
|
||||
"scope": "complete_typed_var_to_bytes", "excluded_paths": [],
|
||||
"replacement": copy.deepcopy(event) if n < 5 else None,
|
||||
"replacement_count": 1 if n < 5 else 0, "recent_count": 256,
|
||||
"recent_ids": history(255)["published_ids"] +
|
||||
([event["event_id"]] if n < 5 else ["domain_event.000000000256"])}
|
||||
for n in range(30)], "at_390": None}
|
||||
if ops:
|
||||
result["at_390"] = {"clock": {"absolute_minute": 390}, "operations": {"enabled": True},
|
||||
"processed_arrival_minutes": [360],
|
||||
"guest_auto_ids": ["guest-auto-00000001"], "guest_auto_count": 1,
|
||||
"domain_event_counter": 300, "published_ids_count": 300, "events": []}
|
||||
result["minutes"][28]["operations_observation"] = {
|
||||
k: copy.deepcopy(v) for k, v in result["at_390"].items() if k != "events"}
|
||||
return result
|
||||
|
||||
|
||||
def evidence(full, events=None):
|
||||
sidecar = {"report": copy.deepcopy(full), "at_390_event_verbatim": copy.deepcopy(events or [])}
|
||||
return check.redact_report(full), sidecar
|
||||
|
||||
|
||||
def validate_report(full):
|
||||
public, sidecar = evidence(full)
|
||||
return check.check_report(public, sidecar)
|
||||
|
||||
|
||||
def validate_pair(writer, reader):
|
||||
public_writer, sw = evidence(writer)
|
||||
public_reader, sr = evidence(reader)
|
||||
return check.check_pair(public_writer, public_reader, sw, sr)
|
||||
|
||||
|
||||
def summarize(event, eid):
|
||||
return {**{k: event[k] for k in ("event_id", "event_type", "absolute_minute")},
|
||||
"raw_event_sha256": check.json_sha(event),
|
||||
"canonical_sha256": check.json_sha(check.canonical_event(event, eid))}
|
||||
|
||||
|
||||
def write(path, value):
|
||||
with Path(path).open("x") as stream:
|
||||
json.dump(value, stream)
|
||||
|
||||
|
||||
def audio(pid):
|
||||
return {"coordinator": True, "passed": True, "owned_engine_pid": pid,
|
||||
"all_owned_targets_released": True, "deadline_reached": False,
|
||||
"clock_process_held_stopped": True, "window_input_held_disabled": True,
|
||||
"restored_before_quit": False, "shared_bus_deleted": False, "settings_mutated": False}
|
||||
|
||||
|
||||
def synthetic_receipt(out):
|
||||
rows = []
|
||||
profiles = {}
|
||||
source_package = out / "synthetic-package"
|
||||
source_package.mkdir()
|
||||
(source_package / "world.bin").write_bytes(DATA)
|
||||
(source_package / "manifest.json").write_text("{}")
|
||||
transfers = {kind: driver.create_transfer(source_package, out / (kind + "-transfer"), write)
|
||||
for kind in ("R", "O")}
|
||||
for index, label in enumerate(check.PHASES):
|
||||
folder = out / label
|
||||
folder.mkdir()
|
||||
pid = 100 + index
|
||||
process = {"exit_code": 0, "helper_exit_code": 0, "timed_out": False, "error_lines": [],
|
||||
"lifecycle_error": None, "external_process_or_group_signals": 0,
|
||||
"owned_popen_ended": True, "old_helper_engine_ended": True,
|
||||
"start_receipt_written": True, "end_receipt_written": True}
|
||||
capture = {"passed": True, "all_captured_ended": True, "actual_captured_pids": [pid],
|
||||
"outer_cleanup_errors": [], "adapter_exception": None, "foreign_process_or_group_signals": 0,
|
||||
"all_cleanup_before_outer_receipt": True, "outer_receipt_written": True,
|
||||
"outer_receipt_error": None, "cleanup": []}
|
||||
write(folder / "outer-owned-finally.json", {k: v for k, v in capture.items() if k not in
|
||||
("passed", "outer_receipt_written", "outer_receipt_error")})
|
||||
for filename, key in (("started.json", "actual_start_binding"), ("ended.json", "actual_end_binding")):
|
||||
binding = {"pid": pid, "actual_pid": pid, "owner_pid": 99,
|
||||
"argv": ["fake", "--check-only"], "command": ["fake", "--check-only"]}
|
||||
write(folder / filename, binding)
|
||||
process[key] = binding
|
||||
process["adapter_owned_cleanup"] = {"engine_ended": True, "errors": []}
|
||||
write(folder / "adapter-owned-cleanup.json", process["adapter_owned_cleanup"])
|
||||
process.update(pid=pid, argv=["fake", "--check-only"])
|
||||
raw = ""
|
||||
row = {"phase": label, "passed": True, "process": process, "capture": capture,
|
||||
"timeout_seconds": 1800, "diagnostics": [], "argv": ["fake", "--check-only"],
|
||||
"stage_before": HASH, "stage_after": HASH, "stage_preserved": True,
|
||||
"source_preserved": True, "player_preserved": True}
|
||||
startup = out / (label + "-startup")
|
||||
startup.write_bytes(DATA)
|
||||
row["startup_project"] = {"fresh_before": True, "path": str(startup), "sha256": HASH}
|
||||
if index >= 3:
|
||||
observed = report(label, pid)
|
||||
if label.endswith("reader"):
|
||||
observed["transfer_manifest_sha256"] = check.sha(out / (label[0] + "-transfer/manifest.json"))
|
||||
public, sidecar = evidence(observed)
|
||||
row["report"] = public
|
||||
raw = "P28_RESTART_REPORT=" + json.dumps(public) + "\nP28_RESTART_AUDIO_CLOSE=" + json.dumps(audio(pid)) + "\n"
|
||||
profile = str(out / (label + "-private"))
|
||||
row.update(runtime_profile=profile, runtime_name=label)
|
||||
profiles[profile] = {}
|
||||
for kind in (("good",) if label.endswith("writer") else ("good", "tampered")):
|
||||
for name, value in transfers[label[0]]["files"][kind].items():
|
||||
profiles[profile]["p28_restart/" + kind + "/" + name] = value
|
||||
guard = {"verified": True, "conditions": [True] * 5, "conditions_checked": 5,
|
||||
"actual_user_data_dir": profile, "expected_user_data_dir": profile,
|
||||
"globalized_probe": str(Path(profile) / "guard-probe"), "custom_user_dir_name": label,
|
||||
"fresh_before_parent_initialize": True, "profile_writes_before_guard": 0,
|
||||
"project_settings_persisted": False}
|
||||
guard_path = out / (label + "-guard.json")
|
||||
write(guard_path, guard)
|
||||
row.update(guard=guard, guard_sha256=check.sha(guard_path))
|
||||
states = out / (label + "-states")
|
||||
states.mkdir()
|
||||
sidecar_path = Path(profile) / check.SIDECAR
|
||||
sidecar_path.parent.mkdir(parents=True)
|
||||
write(sidecar_path, sidecar)
|
||||
(states / "report_full.json").write_bytes(sidecar_path.read_bytes())
|
||||
row["sidecar"] = {"path": check.SIDECAR, **check.pin(sidecar_path)}
|
||||
profiles[profile][check.SIDECAR] = check.pin(sidecar_path)
|
||||
for name in observed["states"]:
|
||||
(states / (name + ".bin")).write_bytes(DATA)
|
||||
for name, content in (("stdout.log", raw), ("stderr.log", ""), ("engine-native.log", "native\n")):
|
||||
(folder / name).write_text(content)
|
||||
row["raw"] = {n: check.pin(folder / n) for n in ("stdout.log", "stderr.log", "engine-native.log")}
|
||||
process.update(stdout_sha256=row["raw"]["stdout.log"]["sha256"],
|
||||
stderr_sha256=row["raw"]["stderr.log"]["sha256"])
|
||||
write(folder / "process.json", process)
|
||||
row["owned_files"] = {n: check.pin(folder / n) for n in
|
||||
("process.json", "started.json", "ended.json", "adapter-owned-cleanup.json", "outer-owned-finally.json")}
|
||||
rows.append(row)
|
||||
probe = out / "restart_probe.gd"
|
||||
probe.write_bytes((check.HERE / probe.name).read_bytes())
|
||||
source = {str(i): {"bytes": 1, "sha256": HASH} for i in range(1407)}
|
||||
worker = {n: check.sha(check.HERE / n) for n in
|
||||
("restart_pair.py", "restart_probe.gd", "check_restart.py", "test_restart.py")}
|
||||
return {"passed": True, "error": None, "cleanup_errors": [], "phases": rows,
|
||||
"source_before": source, "source_after": copy.deepcopy(source),
|
||||
"default_player_before": {}, "default_player_after": {},
|
||||
"dependencies_before": check.DEPENDENCIES.copy(), "dependencies_after": check.DEPENDENCIES.copy(),
|
||||
"collector_before": driver.COLLECTOR_PINS.copy(), "collector_after": driver.COLLECTOR_PINS.copy(),
|
||||
"worker_before": worker, "worker_after": worker.copy(), "profiles": profiles,
|
||||
"cross_cutting": copy.deepcopy(check.CROSS), "original_credit": 0,
|
||||
"windows_verified": False, "operations_200_guests_verified": False,
|
||||
"coverage": check.coverage([report("R-writer"), report("O-writer")]),
|
||||
"script_pins": {str(probe): check.sha(probe)}, "owned_writes": [],
|
||||
"cleanup": [{"pid": 100 + n, "engine_ended": True, "outer_cleanup_errors": [],
|
||||
"foreign_process_or_group_signals": 0} for n in range(7)]}
|
||||
|
||||
|
||||
class RestartTests(unittest.TestCase):
|
||||
@staticmethod
|
||||
def here_inventory():
|
||||
return {str(path.relative_to(check.HERE)): hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
for path in check.HERE.rglob("*") if path.is_file()}
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.inventory_before = cls.here_inventory()
|
||||
|
||||
def setUp(self):
|
||||
# Check after every test, including failures, independently of test order.
|
||||
self.addCleanup(lambda: self.assertEqual(self.here_inventory(), self.inventory_before))
|
||||
|
||||
def test_zz_here_inventory_unchanged(self):
|
||||
# unittest's default ordering places this after all twelve existing tests.
|
||||
self.assertIn("test_restart.py", self.inventory_before)
|
||||
self.assertEqual(self.here_inventory(), self.inventory_before)
|
||||
|
||||
def actual_canonical_sidecar(self):
|
||||
relative = Path("native-e524ff2-canonical-failure-r01/report_full.json")
|
||||
roots = [check.ROOT / "build",
|
||||
check.ROOT.parent / ".runtime" / "P28-01-RESTART" / "build"]
|
||||
if os.environ.get("BUILD_OUTPUT_DIR"):
|
||||
roots.insert(0, Path(os.environ["BUILD_OUTPUT_DIR"]))
|
||||
path = next((root / relative for root in roots if (root / relative).is_file()), None)
|
||||
self.assertIsNotNone(path, "required retained native-e524ff2 sidecar is missing")
|
||||
raw = path.read_bytes()
|
||||
self.addCleanup(lambda: self.assertEqual(path.read_bytes(), raw))
|
||||
return json.loads(raw)
|
||||
|
||||
def test_z1_actual_text_key_order_hash(self):
|
||||
sidecar = self.actual_canonical_sidecar()
|
||||
events = sidecar["at_390_event_verbatim"]
|
||||
self.assertEqual(len(events), 16)
|
||||
first = events[0]
|
||||
self.assertEqual(first["event_id"], "domain_event.000000000009")
|
||||
self.assertEqual(first["absolute_minute"], 362)
|
||||
self.assertEqual(first["payload"],
|
||||
{"old_state": 1, "reason": "ENTRY_COMPLETED", "state": 2})
|
||||
self.assertEqual(check.json_sha(first),
|
||||
"9ca80bec0c605ecd288f7671952d30fe66c67f291c5a618ea54623f07394edb9")
|
||||
for key in ("raw_event_sha256", "canonical_sha256"):
|
||||
self.assertEqual(sidecar["report"]["at_390"]["events"][0][key],
|
||||
"f28afa0899d7efea59ab99ae06276ba354779a73cac705fa4801162f27c592d7")
|
||||
with self.assertRaisesRegex(ValueError, "at390 raw/canonical SHA"):
|
||||
check.check_sidecar(check.redact_report(sidecar["report"]), sidecar)
|
||||
|
||||
def test_z1_all_actual_event_bindings(self):
|
||||
original = self.actual_canonical_sidecar()
|
||||
sidecar = copy.deepcopy(original)
|
||||
full = sidecar["report"]
|
||||
self.assertEqual(len(sidecar["at_390_event_verbatim"]), 16)
|
||||
self.assertEqual(len(full["at_390"]["events"]), 16)
|
||||
for event, summary in zip(sidecar["at_390_event_verbatim"], full["at_390"]["events"]):
|
||||
summary["raw_event_sha256"] = check.json_sha(event)
|
||||
summary["canonical_sha256"] = check.json_sha(
|
||||
check.canonical_event(event, full["event_id"]))
|
||||
self.assertEqual(sidecar["at_390_event_verbatim"], original["at_390_event_verbatim"])
|
||||
self.assertEqual(check.check_sidecar(check.redact_report(full), sidecar), full)
|
||||
for index in range(16):
|
||||
with self.subTest(index=index):
|
||||
changed = copy.deepcopy(sidecar)
|
||||
changed["report"]["at_390"]["events"][index]["raw_event_sha256"] = "0" * 64
|
||||
with self.assertRaisesRegex(ValueError, "at390 raw/canonical SHA"):
|
||||
check.check_sidecar(check.redact_report(changed["report"]), changed)
|
||||
|
||||
def test_z2_collision_rejected(self):
|
||||
for ambiguous in ({1: "numeric", "1": "text"},
|
||||
{"nested": [{1: "numeric", "1": "text"}]}):
|
||||
with self.subTest(value=ambiguous), self.assertRaisesRegex(
|
||||
ValueError, "canonical_key_collision"):
|
||||
check.json_sha(ambiguous)
|
||||
full = report()
|
||||
full["stop_reason"] = "canonical_key_collision"
|
||||
public, sidecar = evidence(full)
|
||||
with self.assertRaisesRegex(ValueError, "canonical_key_collision"):
|
||||
check.check_report(public, sidecar)
|
||||
public["stop_reason"] = "complete"
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_report(public, sidecar)
|
||||
|
||||
def rejects(self, original, mutations, validator):
|
||||
for name, mutation in mutations.items():
|
||||
with self.subTest(rejection=name):
|
||||
value = copy.deepcopy(original)
|
||||
mutation(value)
|
||||
with self.assertRaises((ValueError, KeyError, TypeError)):
|
||||
validator(value)
|
||||
|
||||
def test_rs1_signal_bool_void(self):
|
||||
for kind in ("save", "load", "tamper"):
|
||||
check.check_io(io(kind), kind)
|
||||
self.rejects(io("save"), {
|
||||
"true only": lambda x: x.pop("signal_count"),
|
||||
"no signal": lambda x: x.update(signal_count=0),
|
||||
"duplicate signal": lambda x: x.update(signal_count=2),
|
||||
"bool differs": lambda x: x.update(return_value=False),
|
||||
"latest differs": lambda x: x["latest"].update(code="FAILED"),
|
||||
"payload typed mismatch": lambda x: x.update(payload_bytes_equal=False),
|
||||
"state mismatch": lambda x: x["comparison"].update(equal=False),
|
||||
}, lambda x: check.check_io(x, "save"))
|
||||
self.rejects(io("load"), {
|
||||
"void success": lambda x: x.update(return_value=True),
|
||||
"void coerced bool": lambda x: x.update(return_kind="bool"),
|
||||
"missing signal": lambda x: x.update(signal_count=0),
|
||||
}, lambda x: check.check_io(x, "load"))
|
||||
|
||||
def test_rs2_exact_event_transform(self):
|
||||
for kind in ("save", "load", "tamper"):
|
||||
for count in (0, 255, 256):
|
||||
check.check_io(io(kind, count=count), kind)
|
||||
self.rejects(io(kind), {
|
||||
"two events": lambda x: x["after"]["recent_events"].append(copy.deepcopy(x["event"])),
|
||||
"different E": lambda x: x["event"].update(event_id="domain_event.000009999999"),
|
||||
"counter": lambda x: x["after"].update(counter=x["before"]["counter"] + 2),
|
||||
"missing ID": lambda x: x["after"]["published_ids"].pop(0),
|
||||
"duplicate ID": lambda x: x["after"]["published_ids"].append(x["event"]["event_id"]),
|
||||
"unsorted IDs": lambda x: x["after"]["published_ids"].reverse(),
|
||||
"drop tail": lambda x: x["after"].update(recent_events=x["before"]["recent_events"][:-1] + [x["event"]]),
|
||||
"wrong dropped record": lambda x: x.update(dropped=x["before"]["recent_events"][-1]),
|
||||
"excluded path": lambda x: x["comparison"].update(excluded_paths=["systems.runtime.economy"]),
|
||||
"diff hidden": lambda x: x["comparison"].update(diff_paths=["$.clock"]),
|
||||
"digest only": lambda x: x["comparison"].update(method="SimulationDigest"),
|
||||
"bytes SHA disagreement": lambda x: x["comparison"].update(actual_sha256="f" * 64),
|
||||
}, lambda x: check.check_io(x, kind))
|
||||
|
||||
def test_clarification01_tamper(self):
|
||||
self.rejects(io("tamper"), {
|
||||
"failure event ignored": lambda x: x.update(after=x["before"]),
|
||||
"failure ok": lambda x: x["signal_payload"].update(ok=True),
|
||||
"failure OK code": lambda x: x["signal_payload"].update(code="OK"),
|
||||
"not restored": lambda x: x["failure"].update(preload_pause_restored=False),
|
||||
"paused differs": lambda x: x.update(paused_after=False),
|
||||
"loaded pause differs": lambda x: x["failure"].update(loaded_clock_paused=False),
|
||||
"restore committed": lambda x: x["failure"]["recovery_report"].update(restore_committed=True),
|
||||
"migration unrecorded": lambda x: x["failure"].pop("migration_attempted"),
|
||||
}, lambda x: check.check_io(x, "tamper"))
|
||||
self.rejects(report("R-reader"), {
|
||||
"failed event retained": lambda x: x.update(failure_event_absent=False),
|
||||
"backlog consumed": lambda x: x["paused_idle"]["after"].update(backlog=120),
|
||||
"reader not paused": lambda x: x["io"][1].update(paused_after=False),
|
||||
}, validate_report)
|
||||
|
||||
def test_rs3_actual_k_full_typed_cross_pid(self):
|
||||
writer, reader = report(), report("R-reader", 101)
|
||||
validate_pair(writer, reader)
|
||||
self.rejects(reader, {
|
||||
"K call count": lambda x: x["minutes"][0].update(after=x["saved_minute"]),
|
||||
"different K": lambda x: x.update(actual_k=29),
|
||||
"missing minute": lambda x: x["minutes"].pop(),
|
||||
"partial digest": lambda x: x["minutes"][0].update(scope="SimulationDigest"),
|
||||
"minute SHA": lambda x: x["minutes"][0].update(sha256="f" * 64),
|
||||
"wrong replacement": lambda x: x["minutes"][0]["replacement"].update(event_id="other"),
|
||||
"two replacements": lambda x: x["minutes"][0].update(replacement_count=2),
|
||||
"after window": lambda x: x["minutes"][6].update(replacement=x["io"][-1]["event"], replacement_count=1),
|
||||
"excluded path": lambda x: x["minutes"][0].update(excluded_paths=["clock"]),
|
||||
"same PID": lambda x: x.update(pid=100),
|
||||
}, lambda x: validate_pair(writer, x))
|
||||
|
||||
def test_rs4_rs5_arrival_boundary(self):
|
||||
writer, reader = report("O-writer"), report("O-reader", 101)
|
||||
validate_pair(writer, reader)
|
||||
self.rejects(writer, {
|
||||
"boundary only": lambda x: x["boundary"].update(guest_auto_count=0),
|
||||
"null arrival": lambda x: x.update(boundary=None),
|
||||
"unprocessed": lambda x: x["boundary"].update(processed_arrival_minutes=[]),
|
||||
"not first tick": lambda x: x["ui_ticks"].insert(0, copy.deepcopy(x["boundary"])),
|
||||
"duplicate guest": lambda x: x["boundary"]["guest_auto_ids"].append("guest-auto-00000001"),
|
||||
"next missing": lambda x: x["next_boundary"].update(processed_once=False),
|
||||
"next duplicate": lambda x: x["next_boundary"]["after"].append(420),
|
||||
"cap exceeded": lambda x: x.update(start_minute=200),
|
||||
}, validate_report)
|
||||
|
||||
def test_d1_import_closure(self):
|
||||
check.check_dependencies()
|
||||
check.check_modules()
|
||||
for name in ("restart_pair.py", "check_restart.py", "test_restart.py"):
|
||||
check.check_imports((check.HERE / name).read_text())
|
||||
for text in ("import forbidden_project_module", "from tools.strict_test_markers import main",
|
||||
"importlib.util.spec_from_file_location('x', 'tools/evil.py')",
|
||||
"__import__('tools.evil')", "exec('import tools.evil')"):
|
||||
with self.subTest(text=text), self.assertRaises(ValueError):
|
||||
check.check_imports(text)
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_modules({"bad": SimpleNamespace(__file__=str(check.ROOT / "tools/evil.py"))})
|
||||
for path in check.DEPENDENCIES:
|
||||
original = check.sha
|
||||
with self.subTest(pin=path), patch.object(check, "sha", side_effect=lambda p:
|
||||
"0" * 64 if Path(p) == check.ROOT / path else original(p)), self.assertRaises(ValueError):
|
||||
check.check_dependencies()
|
||||
|
||||
def test_d2_order_and_check_plan(self):
|
||||
collector, _, _ = driver.load_dependencies()
|
||||
commands = driver.plan(collector, Path("/engine"), Path("/out"))
|
||||
self.assertEqual(len(commands), 7)
|
||||
self.assertIn("--import", commands[0])
|
||||
self.assertIn("--check-only", commands[1])
|
||||
self.assertIn("--check-only", commands[2])
|
||||
rows = []
|
||||
for _, label in driver.schedule(rows):
|
||||
rows.append({"phase": label, "passed": True, "report": {"stop_reason": "complete"}})
|
||||
self.assertEqual([r["phase"] for r in rows], check.PHASES)
|
||||
rows = []
|
||||
iterator = driver.schedule(rows)
|
||||
for _ in range(4):
|
||||
_, label = next(iterator)
|
||||
rows.append({"phase": label, "passed": True, "report": {"stop_reason": "complete"}})
|
||||
rows[-1]["passed"] = False
|
||||
with self.assertRaises(ValueError):
|
||||
next(iterator)
|
||||
rows[-1]["passed"] = True
|
||||
rows[-1]["report"]["stop_reason"] = "failed"
|
||||
iterator = driver.schedule([])
|
||||
next(iterator)
|
||||
with self.assertRaises(ValueError):
|
||||
next(iterator)
|
||||
|
||||
def test_d3_transfer_and_fresh_profiles(self):
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR") or None) as temporary:
|
||||
base = Path(temporary)
|
||||
source = base / "source"
|
||||
source.mkdir()
|
||||
(source / "world.bin").write_bytes(b"0123456789")
|
||||
(source / "manifest.json").write_text("{}")
|
||||
target = base / "transfer"
|
||||
manifest = driver.create_transfer(source, target, write)
|
||||
driver.check_transfer(target, manifest)
|
||||
self.assertEqual((target / "tampered/world.bin").read_bytes(), b"0123446789")
|
||||
driver.fresh_profile(base / "new-profile")
|
||||
with self.assertRaises(ValueError):
|
||||
driver.fresh_profile(source)
|
||||
self.rejects(manifest, {
|
||||
"SHA": lambda x: x["files"]["good"]["world.bin"].update(sha256="f" * 64),
|
||||
"offset": lambda x: x["tamper"].update(offset=0),
|
||||
"xor": lambda x: x["tamper"].update(xor=2),
|
||||
}, lambda x: driver.check_transfer(target, x))
|
||||
(target / "tampered/manifest.json").write_text("changed")
|
||||
with self.assertRaises(ValueError):
|
||||
driver.check_transfer(target, manifest)
|
||||
|
||||
def test_d4_d5_d7_probe_static(self):
|
||||
text = (check.HERE / "restart_probe.gd").read_text()
|
||||
check.check_probe(text)
|
||||
snippets = ["GameClock.advance_minutes(1)", "GameClock.set_paused(false)",
|
||||
"x.clear()", "spawn_guest()", "SimulationRuntime.new()", "initialize()",
|
||||
"enable_production_checkpoint()", "main._toggle_pause()", "GameClock.set_paused(true)",
|
||||
"var elapsed_us = 1", "Time.get_ticks_usec()"]
|
||||
for snippet in snippets:
|
||||
with self.subTest(snippet=snippet), self.assertRaises(ValueError):
|
||||
check.check_probe(text + "\nfunc bad():\n\t" + snippet + "\n")
|
||||
for replacement in ("GameClock.advance_minutes(2)", "Time.get_ticks_usec()",
|
||||
"GameClock.advance_one_minute()"):
|
||||
with self.subTest(step=replacement), self.assertRaises(ValueError):
|
||||
check.check_probe(text.replace("GameClock.advance_minutes(1)", replacement))
|
||||
|
||||
def test_y2_direct_variant_inference_guard(self):
|
||||
text = (check.HERE / "restart_probe.gd").read_text()
|
||||
check.check_probe(text)
|
||||
snippets = [
|
||||
"var x := GameClock.get_backlog_minutes()",
|
||||
'var y := field(d,"k")',
|
||||
"var x := runtime.create_snapshot()",
|
||||
"var x := main",
|
||||
"var x := other_variant.value",
|
||||
"var x := other_field(d)",
|
||||
]
|
||||
declarations = "\nvar other_variant: Variant\nfunc other_field(d) -> Variant:\n\treturn d\n"
|
||||
for snippet in snippets:
|
||||
with self.subTest(snippet=snippet), self.assertRaisesRegex(ValueError, "direct Variant inference"):
|
||||
check.check_probe(text + declarations + "\nfunc bad():\n\t" + snippet + "\n")
|
||||
for snippet in (
|
||||
"var x: int = GameClock.get_backlog_minutes()",
|
||||
'var y: Variant = field(d,"k")',
|
||||
"var x := 1",
|
||||
"var x := witness(d)",
|
||||
"var x := GameClock_extra.get_backlog_minutes()",
|
||||
):
|
||||
with self.subTest(allowed=snippet):
|
||||
check.check_probe(text + "\nfunc allowed():\n\t" + snippet + "\n")
|
||||
|
||||
def test_rs7_rs8_full_receipt_rejections(self):
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR") or None) as temporary:
|
||||
out = Path(temporary)
|
||||
receipt = synthetic_receipt(out)
|
||||
check.check_receipt(receipt, out)
|
||||
self.rejects(receipt, {
|
||||
"source map": lambda x: x["source_after"].pop("0"),
|
||||
"ordinary player": lambda x: x["default_player_after"].update(changed=True),
|
||||
"guard": lambda x: x["phases"][3]["guard"].update(profile_writes_before_guard=1),
|
||||
"raw SHA": lambda x: x["phases"][3]["raw"]["stdout.log"].update(sha256="f" * 64),
|
||||
"PID": lambda x: x["phases"][3]["capture"].update(actual_captured_pids=[999]),
|
||||
"owned write": lambda x: x["owned_writes"].append("R-writer/report.json"),
|
||||
"cleanup": lambda x: x["cleanup"][0].update(engine_ended=False),
|
||||
"stage": lambda x: x["phases"][1].update(stage_after="f" * 64),
|
||||
"collector": lambda x: x["collector_after"].pop("measure_p28_01.gd"),
|
||||
"dependency": lambda x: x["dependencies_after"].update({"tools/run_all_tests.py": "f" * 64}),
|
||||
"cross-cutting": lambda x: x["cross_cutting"].pop(),
|
||||
"source count": lambda x: (x["source_after"].pop("1"), x["source_before"].pop("1")),
|
||||
"phase order": lambda x: x["phases"].reverse(),
|
||||
"foreign signal": lambda x: x["phases"][0]["capture"].update(foreign_process_or_group_signals=1),
|
||||
}, lambda x: check.check_receipt(x, out))
|
||||
(out / "R-reader-states/B.bin").write_bytes(b"changed")
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_receipt(receipt, out)
|
||||
|
||||
def test_d6_raw_report_and_audio(self):
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR") or None) as temporary:
|
||||
out = Path(temporary)
|
||||
receipt = synthetic_receipt(out)
|
||||
row = receipt["phases"][3]
|
||||
folder = out / "R-writer"
|
||||
original = (folder / "stdout.log").read_text()
|
||||
for text in ("", original + original, original.split("P28_RESTART_AUDIO_CLOSE=")[0],
|
||||
original.replace('"owned_engine_pid": 103', '"owned_engine_pid": 999')):
|
||||
(folder / "stdout.log").write_text(text)
|
||||
row["raw"]["stdout.log"] = check.pin(folder / "stdout.log")
|
||||
with self.assertRaises(ValueError):
|
||||
check.bound_raw(folder, row)
|
||||
|
||||
def test_d2_d6_real_owned_outer_fake_child(self):
|
||||
collector, _, outer = driver.load_dependencies()
|
||||
child = (
|
||||
"import os,sys,json,time,pathlib\n"
|
||||
"mode=sys.argv[1]\n"
|
||||
"identity={'pid':os.getpid(),'ppid':os.getppid(),'pgid':os.getpgrp(),"
|
||||
"'args':' '.join([sys.executable]+sys.argv)}\n"
|
||||
"path=pathlib.Path(sys.argv[0]).parent/(mode+'-identity')\n"
|
||||
"tmp=path.with_suffix('.tmp'); tmp.write_text(json.dumps(identity)); tmp.replace(path)\n"
|
||||
"pathlib.Path(sys.argv[sys.argv.index('--log-file')+1]).write_text('native raw\\n')\n"
|
||||
"print('Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org',flush=True)\n"
|
||||
"time.sleep(0.15)\n"
|
||||
"if mode=='timeout': time.sleep(5)\n"
|
||||
"sys.exit(3 if mode=='exit' else 0)\n")
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR") or None) as temporary:
|
||||
base = Path(temporary)
|
||||
script = base / "fake-child"
|
||||
script.write_text(child)
|
||||
def diagnostic(command, folder, timeout, env, **kwargs):
|
||||
def probe(pid):
|
||||
path = base / (command[2] + "-identity")
|
||||
deadline = time.monotonic() + 2
|
||||
while time.monotonic() < deadline:
|
||||
if path.exists():
|
||||
identity = json.loads(path.read_text())
|
||||
if identity["pid"] == pid:
|
||||
return identity
|
||||
time.sleep(.005)
|
||||
raise RuntimeError("fake child identity unavailable")
|
||||
return outer.native.run_owned_diagnostic(command, folder, timeout, env, probe_fn=probe, **kwargs)
|
||||
for mode in ("ok", "exit", "timeout"):
|
||||
folder = base / mode
|
||||
def run_phase(command, destination, limit, env, **kwargs):
|
||||
self.assertEqual(limit, 1800)
|
||||
return outer.run_phase(command, destination, .3 if mode == "timeout" else 5,
|
||||
env, diagnostic_fn=diagnostic, **kwargs)
|
||||
def guarded_write(path, value):
|
||||
self.assertFalse(Path(path).is_relative_to(folder), "driver writes owned folder")
|
||||
write(path, value)
|
||||
def real_phase(*args, **kwargs):
|
||||
kwargs["dump_fn"] = write
|
||||
return run_phase(*args, **kwargs)
|
||||
with patch.object(collector, "live_engines", return_value=[]), \
|
||||
patch.object(collector, "write", side_effect=guarded_write):
|
||||
row = collector.run_engine(SimpleNamespace(run_phase=real_phase),
|
||||
[sys.executable, str(script), mode], folder, {})
|
||||
row["phase"] = "import"
|
||||
self.assertEqual(row["passed"], mode == "ok", row)
|
||||
self.assertTrue(row["capture"]["all_captured_ended"])
|
||||
if mode == "ok":
|
||||
check.bound_raw(folder, row)
|
||||
else:
|
||||
with self.assertRaises(ValueError):
|
||||
check.bound_raw(folder, row)
|
||||
_, errors = outer.finish_captured(outer.OWNED)
|
||||
self.assertEqual(errors, [])
|
||||
|
||||
def test_v1_at390_required_and_pair_consistency(self):
|
||||
writer, reader = report("O-writer"), report("O-reader", 101)
|
||||
validate_pair(writer, reader)
|
||||
self.rejects(writer, {
|
||||
"absent": lambda x: x.pop("at_390"),
|
||||
"null": lambda x: x.update(at_390=None),
|
||||
"not reached": lambda x: x.update(stop_reason="at_390_not_reached"),
|
||||
"clock": lambda x: x["at_390"]["clock"].update(absolute_minute=391),
|
||||
"counter type": lambda x: x["at_390"].update(domain_event_counter="300"),
|
||||
"row differs": lambda x: x["minutes"][28]["operations_observation"].update(guest_auto_count=2),
|
||||
}, validate_report)
|
||||
for mutate in (lambda x: x.pop("at_390"), lambda x: x.update(at_390=None)):
|
||||
w, r = copy.deepcopy(writer), copy.deepcopy(reader)
|
||||
mutate(w)
|
||||
mutate(r)
|
||||
with self.assertRaises((ValueError, KeyError)):
|
||||
validate_pair(w, r)
|
||||
different = copy.deepcopy(reader)
|
||||
different["at_390"]["operations"]["population"] = 3
|
||||
different["minutes"][28]["operations_observation"]["operations"]["population"] = 3
|
||||
validate_report(different)
|
||||
with self.assertRaises(ValueError):
|
||||
validate_pair(writer, different)
|
||||
with self.assertRaises((ValueError, KeyError)):
|
||||
validate_pair(writer, {**reader, "at_390": None})
|
||||
|
||||
def test_v2_all_seven_families_restore_verbatim(self):
|
||||
full = report()
|
||||
# T4 and T6 are conditional: an earlier SAVE event is the dropped head.
|
||||
old = io("save", minute=600, count=0)["event"]
|
||||
row = full["io"][0]
|
||||
row["before"]["recent_events"][0] = old
|
||||
row["dropped"] = copy.deepcopy(old)
|
||||
row["after"] = check.transform(row["before"], row["event"])
|
||||
public, sidecar = evidence(full)
|
||||
check.check_report(public, sidecar)
|
||||
paths = check.payload_paths(public)
|
||||
for path in ("/report/io/0/signal_payload", "/report/io/0/latest",
|
||||
"/report/io/0/event/payload", "/report/io/0/before/recent_events/0/payload",
|
||||
"/report/io/0/after/recent_events/255/payload", "/report/io/0/dropped/payload",
|
||||
"/report/minutes/0/replacement/payload"):
|
||||
self.assertEqual(paths[path]["timing_redactions"], [
|
||||
{"original_key": key, "sidecar_ref": path + "/" + key} for key in check.TIMINGS])
|
||||
self.assertNotIn("duration_us", paths[path])
|
||||
self.assertEqual(public["io"][0]["after"]["recent_events"][0],
|
||||
full["io"][0]["after"]["recent_events"][0])
|
||||
self.assertEqual(check.check_sidecar(public, sidecar), full)
|
||||
for kind in ("save", "load", "tamper"):
|
||||
full_row = io(kind)
|
||||
check.check_io(full_row, kind)
|
||||
self.rejects(sidecar, {
|
||||
"root extra": lambda x: x.update(extra={}),
|
||||
"performance use": lambda x: x["report"].update(statistics={"average": 1}),
|
||||
"outside closure": lambda x: x["report"].update(elapsed_ms=1),
|
||||
"nested timing": lambda x: x["report"]["io"][0]["event"]["payload"].update(nested={"latency_us": 1}),
|
||||
"missing timing": lambda x: x["report"]["io"][0]["latest"].pop("duration_us"),
|
||||
"tamper value": lambda x: x["report"]["io"][0]["latest"].update(code="CHANGED"),
|
||||
"nonSAVE": lambda x: x["report"]["io"][0]["before"]["recent_events"][0].update(event_type="OLD"),
|
||||
"wrong service": lambda x: x["report"]["io"][0]["before"]["recent_events"][0].update(source_service="other"),
|
||||
}, lambda x: check.check_sidecar(public, x))
|
||||
self.rejects(public, {
|
||||
"leaked key": lambda x: x["io"][0]["signal_payload"].update(duration_us=124),
|
||||
"hidden payload leak": lambda x: x.update(payload={"hidden_ms": 1}),
|
||||
"extra marker": lambda x: x["io"][0]["latest"]["timing_redactions"].append(
|
||||
{"original_key": "other", "sidecar_ref": "/report/other"}),
|
||||
"missing marker": lambda x: x["io"][0]["latest"].pop("timing_redactions"),
|
||||
"one marker": lambda x: x["io"][0]["latest"]["timing_redactions"].pop(),
|
||||
"wrong pointer": lambda x: x["io"][0]["latest"]["timing_redactions"][0].update(
|
||||
sidecar_ref="/report/io/0/signal_payload/duration_us"),
|
||||
"unresolved pointer": lambda x: x["io"][0]["latest"]["timing_redactions"][0].update(
|
||||
sidecar_ref="/report/io/999/latest/duration_us"),
|
||||
"extra marker field": lambda x: x["io"][0]["latest"]["timing_redactions"][0].update(extra=1),
|
||||
"nonSAVE marker": lambda x: x["io"][0]["after"]["recent_events"][0]["payload"].update(
|
||||
timing_redactions=copy.deepcopy(x["io"][0]["latest"]["timing_redactions"])),
|
||||
"restore differs": lambda x: x.update(pid=999),
|
||||
}, lambda x: check.check_sidecar(x, sidecar))
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_report(public)
|
||||
# The raw equality check must see differences hidden by public redaction.
|
||||
tampered = copy.deepcopy(sidecar)
|
||||
tampered["report"]["io"][0]["latest"]["duration_us"] += 1
|
||||
self.assertEqual(check.redact_report(tampered["report"]), public)
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_report(public, tampered)
|
||||
|
||||
def test_v2_at390_full_array_binding(self):
|
||||
full = report("O-writer")
|
||||
events = [
|
||||
{"event_id": "domain_event.%012d" % n, "event_type": "ARRIVAL",
|
||||
"absolute_minute": 380 + i, "source_service": "operations", "payload": {"count": i + 1}}
|
||||
for i, n in enumerate((250, 251))]
|
||||
full["at_390"]["events"] = [summarize(e, full["event_id"]) for e in events]
|
||||
public, sidecar = evidence(full, events)
|
||||
check.check_report(public, sidecar)
|
||||
self.rejects(sidecar, {
|
||||
"missing array": lambda x: x.pop("at_390_event_verbatim"),
|
||||
"missing entry": lambda x: x["at_390_event_verbatim"].pop(),
|
||||
"extra entry": lambda x: x["at_390_event_verbatim"].append(copy.deepcopy(events[0])),
|
||||
"reordered": lambda x: x["at_390_event_verbatim"].reverse(),
|
||||
"raw differs": lambda x: x["at_390_event_verbatim"][0]["payload"].update(count=7),
|
||||
"nonSAVE timing": lambda x: x["at_390_event_verbatim"][0]["payload"].update(duration_us=1),
|
||||
}, lambda x: check.check_sidecar(public, x))
|
||||
for field in ("raw_event_sha256", "canonical_sha256"):
|
||||
changed = copy.deepcopy(full)
|
||||
changed["at_390"]["events"][0][field] = "f" * 64
|
||||
pub, sc = evidence(changed, events)
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_sidecar(pub, sc)
|
||||
changed = copy.deepcopy(full)
|
||||
changed["at_390"]["events"][0]["canonical_sha256"] = check.json_sha(
|
||||
{"event_id": events[0]["event_id"], "replaced_for_cross_pid_compare": True})
|
||||
pub, sc = evidence(changed, events)
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_sidecar(pub, sc)
|
||||
# D5 operates on an entire E element, including type and timing differences.
|
||||
# This exercises the binding independently of the strict > saved_minute filter.
|
||||
canonical_observations = []
|
||||
raw_shas = []
|
||||
for kind in ("save", "load"):
|
||||
raw_event = io(kind, 361)["event"]
|
||||
raw_event["payload"]["duration_us"] += 1 if kind == "load" else 0
|
||||
value = copy.deepcopy(full)
|
||||
value["at_390"]["events"] = [summarize(raw_event, value["event_id"])]
|
||||
pub, sc = evidence(value, [raw_event])
|
||||
check.check_sidecar(pub, sc)
|
||||
canonical_observations.append(check.canonical_observation(value["at_390"], value["event_id"]))
|
||||
raw_shas.append(value["at_390"]["events"][0]["raw_event_sha256"])
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_report(pub, sc) # E at saved_minute is outside this observation's window.
|
||||
self.assertNotEqual(raw_shas[0], raw_shas[1])
|
||||
self.assertEqual(*canonical_observations)
|
||||
# All other event fields and canonical hashes remain significant cross PID.
|
||||
reader = report("O-reader", 101)
|
||||
reader["at_390"]["events"] = copy.deepcopy(full["at_390"]["events"])
|
||||
rp, rs = evidence(reader, events)
|
||||
check.check_pair(public, rp, sidecar, rs)
|
||||
changed_events = copy.deepcopy(events)
|
||||
changed_events[0]["payload"]["count"] = 9
|
||||
reader["at_390"]["events"] = [summarize(e, reader["event_id"]) for e in changed_events]
|
||||
rp, rs = evidence(reader, changed_events)
|
||||
check.check_report(rp, rs)
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_pair(public, rp, sidecar, rs)
|
||||
|
||||
def test_v2_receipt_sidecar_binding_rejections(self):
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR") or None) as temporary:
|
||||
out = Path(temporary)
|
||||
receipt = synthetic_receipt(out)
|
||||
check.check_receipt(receipt, out)
|
||||
self.rejects(receipt, {
|
||||
"unbound": lambda x: x["phases"][3].pop("sidecar"),
|
||||
"wrong SHA": lambda x: x["phases"][3]["sidecar"].update(sha256="f" * 64),
|
||||
"missing map": lambda x: x["profiles"][x["phases"][3]["runtime_profile"]].pop(check.SIDECAR),
|
||||
"wrong path": lambda x: x["phases"][3]["sidecar"].update(path="other.json"),
|
||||
"coverage omitted": lambda x: x.pop("coverage"),
|
||||
"false coverage": lambda x: x["coverage"].update(overload_nonzero_backlog="exercised_recorded"),
|
||||
"persistence claim": lambda x: x["coverage"].update(across_restart_backlog_persistence_claimed=True),
|
||||
}, lambda x: check.check_receipt(x, out))
|
||||
path = out / "R-writer-states/report_full.json"
|
||||
saved = path.read_bytes()
|
||||
path.write_bytes(b"{}")
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_receipt(receipt, out)
|
||||
path.unlink()
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_receipt(receipt, out)
|
||||
path.write_bytes(saved)
|
||||
private = Path(receipt["phases"][3]["runtime_profile"]) / check.SIDECAR
|
||||
private.write_bytes(b"{}")
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_receipt(receipt, out)
|
||||
|
||||
def test_v3_overload_is_observation_not_persistence(self):
|
||||
for count, backlog in ((0, 0), (0, 121), (1, 0), (1, 121)):
|
||||
full = report()
|
||||
full["overloads"] = [{"minute": 612, "backlog": backlog}] * count
|
||||
full["paused_idle"]["before"]["backlog"] = backlog
|
||||
full["paused_idle"]["after"]["backlog"] = backlog
|
||||
full["overload_case"] = {"exercised": bool(count and backlog > 0),
|
||||
"overloads": count, "backlog_at_save": backlog}
|
||||
validate_report(full)
|
||||
observed = check.coverage([full])
|
||||
self.assertEqual(observed["overload_nonzero_backlog"],
|
||||
"exercised_recorded" if count and backlog else "not_exercised")
|
||||
self.assertFalse(observed["across_restart_backlog_persistence_claimed"])
|
||||
self.rejects(full, {
|
||||
"missing disclosure": lambda x: x.pop("overload_case"),
|
||||
"wrong exercised": lambda x: x["overload_case"].update(exercised=not bool(count and backlog)),
|
||||
"wrong count": lambda x: x["overload_case"].update(overloads=count + 1),
|
||||
"wrong backlog": lambda x: x["overload_case"].update(backlog_at_save=backlog + 1),
|
||||
"consumption": lambda x: x["paused_idle"]["after"].update(backlog=backlog + 1),
|
||||
"persistence claim": lambda x: x.update(across_restart_backlog_persistence_claimed=True),
|
||||
"nonzero claim": lambda x: x.update(nonzero_backlog_preserved=True),
|
||||
}, validate_report)
|
||||
writer, reader = report(), report("R-reader", 101)
|
||||
reader["paused_idle"]["before"]["backlog"] = 0
|
||||
reader["paused_idle"]["after"]["backlog"] = 0
|
||||
for row in reader["minutes"]:
|
||||
row.update(backlog_before=0, backlog_after=0)
|
||||
validate_pair(writer, reader) # No across-restart accumulator persistence requirement.
|
||||
|
||||
def test_v4_no_arrival_is_diagnostic_failure(self):
|
||||
full = report("O-writer")
|
||||
full.update(boundary=None, boundary_reason="no_arrival_within_cap",
|
||||
stop_reason="no_arrival_within_cap", next_boundary=None, io=[], minutes=[], actual_k=0)
|
||||
full["ui_ticks"] = [{"clock": n, "guest_auto_ids": [], "guest_auto_count": 0}
|
||||
for n in range(361, 481)]
|
||||
self.assertTrue(check.check_no_arrival_diagnostic(full))
|
||||
with self.assertRaisesRegex(ValueError, "incomplete probe"):
|
||||
validate_report(full)
|
||||
self.rejects(full, {
|
||||
"missing reason": lambda x: x.pop("boundary_reason"),
|
||||
"wrong reason": lambda x: x.update(boundary_reason="complete"),
|
||||
"not null": lambda x: x.update(boundary={}),
|
||||
"short cap": lambda x: x["ui_ticks"].pop(),
|
||||
}, check.check_no_arrival_diagnostic)
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR") or None) as temporary:
|
||||
out = Path(temporary)
|
||||
receipt = synthetic_receipt(out)
|
||||
row = receipt["phases"][5]
|
||||
full["pid"] = row["report"]["pid"]
|
||||
public, sidecar = evidence(full)
|
||||
row["report"] = public
|
||||
text = "P28_RESTART_REPORT=" + json.dumps(public) + "\nP28_RESTART_AUDIO_CLOSE=" + json.dumps(audio(full["pid"])) + "\n"
|
||||
(out / "O-writer/stdout.log").write_text(text)
|
||||
row["raw"]["stdout.log"] = check.pin(out / "O-writer/stdout.log")
|
||||
row["process"]["stdout_sha256"] = row["raw"]["stdout.log"]["sha256"]
|
||||
(out / "O-writer/process.json").write_text(json.dumps(row["process"]))
|
||||
row["owned_files"]["process.json"] = check.pin(out / "O-writer/process.json")
|
||||
for path in (out / "O-writer-states/report_full.json",
|
||||
Path(row["runtime_profile"]) / check.SIDECAR):
|
||||
path.write_text(json.dumps(sidecar))
|
||||
binding = check.pin(out / "O-writer-states/report_full.json")
|
||||
row["sidecar"] = {"path": check.SIDECAR, **binding}
|
||||
receipt["profiles"][row["runtime_profile"]][check.SIDECAR] = binding
|
||||
with self.assertRaisesRegex(ValueError, "incomplete probe"):
|
||||
check.check_receipt(receipt, out)
|
||||
|
||||
def test_v5_engine_elapsed_deadline(self):
|
||||
for elapsed in (0, .25, 37, 1739.5):
|
||||
full = report()
|
||||
full["probe_deadline"].update(engine_elapsed_at_begin_seconds=elapsed, timer_seconds=1740-elapsed)
|
||||
validate_report(full)
|
||||
value = report()["probe_deadline"]
|
||||
self.rejects(value, {
|
||||
"old relative timer": lambda x: x.update(timer_seconds=1740),
|
||||
"wrong limit": lambda x: x.update(limit_seconds_from_engine_start=1800),
|
||||
"negative elapsed": lambda x: x.update(engine_elapsed_at_begin_seconds=-1, timer_seconds=1741),
|
||||
"missing elapsed": lambda x: x.pop("engine_elapsed_at_begin_seconds"),
|
||||
"expired": lambda x: x.update(engine_elapsed_at_begin_seconds=1740, timer_seconds=0),
|
||||
"nonfinite": lambda x: x.update(engine_elapsed_at_begin_seconds=float("inf")),
|
||||
}, check.check_deadline)
|
||||
check.check_deadline({**value, "engine_elapsed_at_begin_seconds": 1750, "timer_seconds": -10}, complete=False)
|
||||
text = (check.HERE / "restart_probe.gd").read_text()
|
||||
for altered in (text + "\nfunc forbidden():\n\tTime.get_ticks_msec()\n",
|
||||
text.replace("deadline_timer_seconds() ->", "other_watchdog() ->"),
|
||||
text.replace("GameClock.advance_minutes(1)", "Time.get_ticks_msec()"),
|
||||
text + "\nfunc forbidden():\n\tTime.get_unix_time_from_system()\n"):
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_probe(altered)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
@@ -243,7 +243,7 @@
|
||||
"facility_value_contracts_validation.json": "bf490a9ca585c48fc318e76c4a069ef244827d828d46e6415399f7710590dd16",
|
||||
"performance_audit.json": "0b630b15a392c3e0366f1362bfe4ddacb968f915f54549f82e4d6fa315384762",
|
||||
"progress_contract.json": "ccbdfce8c8365d5c13f59531955708834cce45acd0e136561c95295dfdb5df77",
|
||||
"progress_snapshot.json": "40c8b516e053323ec79ed5cba348ff20a628bd541254961eec95fe05c44655c0",
|
||||
"progress_snapshot.json": "3c9175a894f639fd8fc6795c421a9f38742bab1f18d45c58fec3d65296d80613",
|
||||
"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": "7e3213bea7a2d2b3d7b09a9e218c9dfbc2b8bd3489965c9c7785dfa05ae1daa7"
|
||||
"work_packages.json": "26ae4834d412f88743cd3bcff1d891edc597b1e357244139b3fd8ba9d90fd9f1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"integrated_at_jst": "2026-10-21T03:10:19.441649+09:00",
|
||||
"integrated_at_jst": "2026-10-21T04:37:36.598744+09:00",
|
||||
"scope": "原30パッケージ/88成果、正式完了22成果・4パッケージ。部分点なし。現在P28-01は未完、collectorの補助成果は原加算0。全ゲーム/同一Windows版の最終受入は未完。",
|
||||
"source_head": "464cc26fd301c66ecc10d2167f613f814e36fc47",
|
||||
"source_authority_note": "台帳更新直前main464cc26。製品1407/collector4不変、full9と正式192/22198は全適用条件一致時再利用。RESTART e524正式21PASS/native Owriter canonicalSHA失敗を保持。実型診断でStringName非文字順を確定し、同Astra repair8 S954528aは正式24PASS。実Desktop Opus257が全raw/diffを読んで新S native7を承認、Rootexec34944で実行中。原22/88・4/30、P28加算0。CLI auth失敗をDesktop成功と混同しない。",
|
||||
"source_head": "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2",
|
||||
"source_authority_note": "台帳更新直前mainQ3ac2ced。RESTART S954528a→Q3ac2ced正式8/8全4通常ゲートPASS。実native7/全raw243/PID/sidecar結合、独立freshDesktop Opus5.5全8PASS/highmedium0/low3を実証。受信900秒失敗は保持し、同じ実結果だけ正確凍結入力で通常reviewへ1回再接続。CLI認証回復とは扱わない。原22/88・4/30、P28-01 whole-unit未完。",
|
||||
"immutable_history": {
|
||||
"baseline.json": {
|
||||
"sha256": "750bb3d313cc5e2ce0d4c79e563a995abd7a78f991dee1dd75c627877b02cd7b",
|
||||
@@ -5256,8 +5256,24 @@
|
||||
"title": "現HEADの性能基準",
|
||||
"state": "in_progress",
|
||||
"completed": false,
|
||||
"reusable_evidence": "docs/plans/original-completion-2026-10-02/execution/p28-01/INTEGRATED_FULL9_MEASUREMENT_DB9D613.json",
|
||||
"remaining": "collector/full9済。現RESTART S954528a正式24PASS、native7実行中。全raw結合/独立all8/通常final/原wholeunitゲート未完。原加算0。",
|
||||
"reusable_evidence": [
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/RESTART_FORMAL_COMPLETION_3AC2CED.json",
|
||||
"bytes": 6366,
|
||||
"sha256": "5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"bytes": 70643,
|
||||
"sha256": "6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json",
|
||||
"bytes": 235149,
|
||||
"sha256": "5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343"
|
||||
}
|
||||
],
|
||||
"remaining": "collector/full9/RESTART正式完了。残りは原whole-unit証拠ゲート・独立レビュー・通常integration/final・台帳のみ。原加算0。",
|
||||
"original_completed": 22,
|
||||
"original_total": 88
|
||||
},
|
||||
@@ -9735,8 +9751,8 @@
|
||||
"formal_count": 22,
|
||||
"original_delivery_units": 88,
|
||||
"partial_credit": false,
|
||||
"selection_status": "P28-01-COLLECTORはQ0f53a67で正式completed9/9・全4ゲートPASS。次は統合版全9本測定、別PIDRESTART、原whole-unit受入。",
|
||||
"pending_decisions": "本測定の実停止/不足があれば実Opus判断。RESTARTは承認済案をhelperfinal後に通常登録。Windows等の原未達を保持し未確定値を捏造しない。"
|
||||
"selection_status": "P28-01-COLLECTOR正式9/9、実db9d613全9測定、RESTART Q3ac2ced正式8/8完了。現在は原P28-01 whole-unit証拠ゲートを1件閉じる。",
|
||||
"pending_decisions": "実Opus266のwhole-unit草案を原条件・証拠と照合し通常登録。G3適用判断は別Opusレビュー。未回答ゲーム仕様は確定しない。"
|
||||
},
|
||||
"historical_regression_before_P08_02_formal_closure": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-final-candidate-r32-combined-root-acceptance-r01.json",
|
||||
@@ -10722,40 +10738,24 @@
|
||||
"whole_goal_accepted": false
|
||||
},
|
||||
"current_workflow_handoff": {
|
||||
"project": "the-tree-p06-02-audit-astra",
|
||||
"task": "P06-02-AUDIT-CONTINUE",
|
||||
"controller_title": "ゲーム開発作業",
|
||||
"controller_local_session": "local_a7f85421-3eed-4482-8030-c0dca669573a",
|
||||
"actual_model": "Opus5.5 medium",
|
||||
"desktop_read_write_roundtrip_verified": true,
|
||||
"project": "the-tree-p28-01-restart",
|
||||
"task": "P28-01-RESTART",
|
||||
"state": "completed",
|
||||
"submission": "3084add460e0fe0b6b32ae4396c9f698c81e0358",
|
||||
"candidate": "e53858a55cff5a8035c71fd799c02c7e75c0a5dd",
|
||||
"original_completed": 17,
|
||||
"submission": "954528a7c5a0e27e2e50b642373168fa0a550aba",
|
||||
"candidate": "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2",
|
||||
"evidence": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/RESTART_FORMAL_COMPLETION_3AC2CED.json",
|
||||
"bytes": 6366,
|
||||
"sha256": "5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be"
|
||||
},
|
||||
"helper_credit": 0,
|
||||
"original_completed": 22,
|
||||
"original_total": 88,
|
||||
"audit_credit": 0,
|
||||
"controller_title": "The Tree 開発管制",
|
||||
"controller_local_session": "local_16f7c88b-81d7-4a43-ad0d-b9c00aa6d6a5",
|
||||
"desktop_read_roundtrip": true,
|
||||
"desktop_prepare_job": "e447020934ba4bcb89c03f925635a2ae",
|
||||
"desktop_integrate": "auto mode denied before launch",
|
||||
"root_integrate_job": "e19be36c0d6247e98e85262300e95a75",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p06-controller-handoff-20261019/9c611769a9b14537b640c7f05edd43cc-validation.json",
|
||||
"sha256": "74c6cd065997be5aaac9cdb72ce48c1ad417757f0c482a8fecec1278c44143f1"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p06-controller-handoff-20261019/d4918e5d32d34052b552a6ab21bb2a0b-result.json",
|
||||
"sha256": "41254556695ad2763d7f596586e51d2d741d3dc05003283928cdd3a163078cda"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p06-controller-handoff-20261019/2ab8fcedb7cf4467895b62fa14cc62ad-integration.json",
|
||||
"sha256": "111c4db9f841a5122329f5c48f6391816513bad45619fe87751be221182b35fb"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p06-controller-handoff-20261019/0ec6d584092f4d6995a8120e0a29c2b0-final.json",
|
||||
"sha256": "958817589cdba9c145e7c71f1c1230625bcfe6eea3616cdb55963f14281c2f31"
|
||||
}
|
||||
],
|
||||
"remaining": "P06 whole-unit gameplay acceptance, unapproved production mana policy, P14 warp dependency and all recorded cross-cutting conditions remain open",
|
||||
"next": "Persistent Claude Desktop controller decides next original P06 acceptance batch from completed audit; Root maintains approvals/ledger. No duplicate temporary commander."
|
||||
"next": "the-tree-p28-01-gate/P28-01-GATE; actual Desktop266 draft; not registered yet"
|
||||
},
|
||||
"historical_workflow_before_desktop_handoff_20261019": {
|
||||
"source_thread": "01a14f6a-b6a1-7fd3-bd45-0f84bbef77be",
|
||||
@@ -10929,7 +10929,7 @@
|
||||
"both_budget_passed": false,
|
||||
"full9_measurement": false
|
||||
},
|
||||
"next": "Same actual repair7handle then newS formal/native/independentall8review and normalintegration/final; originalwholeunitgate233 follows only afterwards",
|
||||
"next": "原P28-01 whole-unitゲートを実Opus草案から照合・登録し、証拠検証→独立レビュー→通常統合final→台帳まで閉じる。",
|
||||
"same_Windows_verified": false,
|
||||
"full9_measurement": {
|
||||
"actual_commit": "db9d6132a8349928560dc4a4a99306d27a81e332",
|
||||
@@ -10945,6 +10945,48 @@
|
||||
"GPU_unavailable": true
|
||||
},
|
||||
"restart": {
|
||||
"project": "the-tree-p28-01-restart",
|
||||
"task": "P28-01-RESTART",
|
||||
"state": "completed",
|
||||
"submitted": "954528a7c5a0e27e2e50b642373168fa0a550aba",
|
||||
"candidate": "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2",
|
||||
"main_base": "3086af2ccd970ddee271a184214dad037bd1d6af",
|
||||
"repairs": 8,
|
||||
"acceptance_completed": 8,
|
||||
"acceptance_total": 8,
|
||||
"all4_normal_gates_passed": true,
|
||||
"completion_proof": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/RESTART_FORMAL_COMPLETION_3AC2CED.json",
|
||||
"bytes": 6366,
|
||||
"sha256": "5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be"
|
||||
},
|
||||
"native_root_join": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"bytes": 70643,
|
||||
"sha256": "6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb"
|
||||
},
|
||||
"candidate_qualification": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json",
|
||||
"bytes": 235149,
|
||||
"sha256": "5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343"
|
||||
},
|
||||
"independent_reviewer_session": "local_251adf7f-dd2e-4726-9141-fff58a45eb14",
|
||||
"independent_review_sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300",
|
||||
"high_medium": 0,
|
||||
"low": 3,
|
||||
"receiver900_failure_retained": true,
|
||||
"new_review_inference_on_rejoin": false,
|
||||
"CLI_auth_recovered": false,
|
||||
"implementation_model": "gpt-6-astra",
|
||||
"review_model": "claude-opus-5-5",
|
||||
"original_credit": 0,
|
||||
"remaining": [
|
||||
"原P28-01のwork/acceptance/横断6を保持するwhole-unit証拠ゲート(実Opus266草案を照合中)・独立Opusレビュー・通常prepare/Root委任候補承認/integration/final・台帳。engine/full9/192の重複実行なし。",
|
||||
"G3の非0backlog未発生とprivate accumulator非保存の適用判断を理由付きで独立レビュー。開示のみで再現証明へ補完しない。",
|
||||
"予算未達4件はP28-02/03に保持、200客OpsON未検証・GPU null理由・同一Windows・長期操作感・全ゲーム最終受入は未達。"
|
||||
]
|
||||
},
|
||||
"historical_restart_before_formal_3ac2ced": {
|
||||
"project": "the-tree-p28-01-restart",
|
||||
"task": "P28-01-RESTART",
|
||||
"base": "2132bbe3d39a0f1b69a97c731bb09213bf9d7a91",
|
||||
@@ -11240,5 +11282,41 @@
|
||||
"Normalprepare/exactQRootdelegatedapproval/integration/final",
|
||||
"OriginalP28-01wholeunitgate9 sameoriginalconditions/independentreview/final/ledger beforecredit"
|
||||
]
|
||||
},
|
||||
"historical_workflow_handoff_before_P28_01_restart_completion": {
|
||||
"project": "the-tree-p06-02-audit-astra",
|
||||
"task": "P06-02-AUDIT-CONTINUE",
|
||||
"state": "completed",
|
||||
"submission": "3084add460e0fe0b6b32ae4396c9f698c81e0358",
|
||||
"candidate": "e53858a55cff5a8035c71fd799c02c7e75c0a5dd",
|
||||
"original_completed": 17,
|
||||
"original_total": 88,
|
||||
"audit_credit": 0,
|
||||
"controller_title": "The Tree 開発管制",
|
||||
"controller_local_session": "local_16f7c88b-81d7-4a43-ad0d-b9c00aa6d6a5",
|
||||
"desktop_read_roundtrip": true,
|
||||
"desktop_prepare_job": "e447020934ba4bcb89c03f925635a2ae",
|
||||
"desktop_integrate": "auto mode denied before launch",
|
||||
"root_integrate_job": "e19be36c0d6247e98e85262300e95a75",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p06-controller-handoff-20261019/9c611769a9b14537b640c7f05edd43cc-validation.json",
|
||||
"sha256": "74c6cd065997be5aaac9cdb72ce48c1ad417757f0c482a8fecec1278c44143f1"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p06-controller-handoff-20261019/d4918e5d32d34052b552a6ab21bb2a0b-result.json",
|
||||
"sha256": "41254556695ad2763d7f596586e51d2d741d3dc05003283928cdd3a163078cda"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p06-controller-handoff-20261019/2ab8fcedb7cf4467895b62fa14cc62ad-integration.json",
|
||||
"sha256": "111c4db9f841a5122329f5c48f6391816513bad45619fe87751be221182b35fb"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p06-controller-handoff-20261019/0ec6d584092f4d6995a8120e0a29c2b0-final.json",
|
||||
"sha256": "958817589cdba9c145e7c71f1c1230625bcfe6eea3616cdb55963f14281c2f31"
|
||||
}
|
||||
],
|
||||
"remaining": "P06 whole-unit gameplay acceptance, unapproved production mana policy, P14 warp dependency and all recorded cross-cutting conditions remain open",
|
||||
"next": "Persistent Claude Desktop controller decides next original P06 acceptance batch from completed audit; Root maintains approvals/ledger. No duplicate temporary commander."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18066,7 +18066,7 @@
|
||||
},
|
||||
"legacy_state": "planned",
|
||||
"state_scope": "package-level; completed means whole package accepted; partial implementation is not completion",
|
||||
"current_summary": "P28-01 collector補助9/9は全4正式ゲート完了。実main db9d613の正規全9測定が正常終了し、R/O x1/x3・描画CPU/提出・機種/build/seed/区間/平均P95最大を取得。原条件の保存再開は別PID RESTARTを実装中(Opus判断/Astra、8AC未受入)。原22/88・4/30を維持。代表Rのx1/x3/stall予算、GPU取得、200客OpsON、同一Windows/長期/全ゲーム受入は未達を保持。",
|
||||
"current_summary": "P28-01 collector補助9/9とRESTART補助8/8が全4通常ゲートで正式完了。実db9d613全9測定とS954528a別PID保存再開の全rawを保持し、Q3ac2cedの製品1407/collector4/probe4への適用を実照合。原P28-01はwork/acceptance/横断6の最終証拠ゲートと独立whole-unitレビュー・通常final・台帳が残るため未完、原22/88・4/30・部分点なし。予算未達/200客OpsON/GPU null/同一Windows/長期は別未達を保持。",
|
||||
"remaining_deliverables": [
|
||||
{
|
||||
"id": "P28-01",
|
||||
@@ -18167,17 +18167,34 @@
|
||||
"scope": "S954528a正式24PASS/native7 in progress and exact prior failure diagnosis; no game acceptance",
|
||||
"linked_at_jst": "2026-10-21T03:59:44.437107+09:00",
|
||||
"current_game_acceptance_inferred": false
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/RESTART_FORMAL_COMPLETION_3AC2CED.json",
|
||||
"bytes": 6366,
|
||||
"sha256": "5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be",
|
||||
"scope": "RESTART補助成果のみ8/8正式完了、原P28-01加算0",
|
||||
"linked_at_jst": "2026-10-21T04:37:36.598744+09:00"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"bytes": 70643,
|
||||
"sha256": "6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb",
|
||||
"scope": "S954528a実native7全raw243照合、原whole-unitレビュー前"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json",
|
||||
"bytes": 235149,
|
||||
"sha256": "5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343",
|
||||
"scope": "Q3ac2cedの製品1407/probe4/実nativeとfull9適用範囲照合"
|
||||
}
|
||||
],
|
||||
"updated_at_jst": "2026-10-21T03:59:44.437107+09:00",
|
||||
"progress_summary": "実db9d613で正規9phase import/parse2/R0/P1/P2/O0/P3/P4完走、全9ownedPID終了・native診断/cleanup0・source1407/collector4/通常player保全。Root160 raw/source/PID/profile結合PASS。R登録20,000マス/200客/50職員/150施設(稼働100施設)、P1はwarm30+測定120、P2は実warm31+測定120。P1 kernel平均479210us対4000、P2 x3P95 5062697us対12000、stall最大4809099/5891466us対50000でfalse。Oは通常開始50マス/客0..2/職員0/施設0の適用範囲だけ。長区間CPU描画を取得(P1平均3.68465ms/P2平均5.36198ms)、GPUはnullと理由。経路/snapshot/保存読込はSHA束縛済r02を限定再利用。独立Opus管制は測定情報充足を実読確認。RESTART旧bc703c9独立H1/M2/L2と5E parse失敗を保全。e524ff2正式21PASS/native Owriter raw/canonicalSHA失敗、実型diagnosticでStringName keys.sort非文字順を確定(数値書式一致)。実Desktop Opus249/253指示で同Astra repair8 S954528aを3ファイル最小修正、正式24test5.429sPASS。実Opus257が全rawと差分を読み、正確Sの登録native7をRootexec34944で開始。全raw/source/PID/sidecar結合、独立all8review、通常integration/final、原wholeunit受入前の加算0。",
|
||||
"updated_at_jst": "2026-10-21T04:37:36.598744+09:00",
|
||||
"progress_summary": "P28-01 collector補助9/9とRESTART補助8/8が全4通常ゲートで正式完了。実db9d613全9測定とS954528a別PID保存再開の全rawを保持し、Q3ac2cedの製品1407/collector4/probe4への適用を実照合。原P28-01はwork/acceptance/横断6の最終証拠ゲートと独立whole-unitレビュー・通常final・台帳が残るため未完、原22/88・4/30・部分点なし。予算未達/200客OpsON/GPU null/同一Windows/長期は別未達を保持。 正式24試験を保持、native R/O K30全状態typed一致・改ざん拒否・実arrival390分16件ID9〜24連番。非0backlog未実施/private accumulator非保存は非主張、独立low3保持。旧bc703c9/5E/e524失敗・受信900秒失敗は失敗のまま。",
|
||||
"historical_progress_summary_before_20261020": "現1273へPath micro軽量化を限定採用。53 whole typed pairs/120同一呼出return比較と全168回帰を確認。最大Runtime混合負荷/Operations enabled/Windows性能は未受入。旧900秒timeout/未測定値を維持。\n【限定採用前の証拠時点の履歴】\n実Runtime最大負荷診断は準備BUILDで900.024秒timeout(-15)。目標実数/保存/別process/計測に未到達。setup span候補を準備し原因特定を継続。 setup区間観測の別実行も900.018秒timeout。429→430分の公開tickは268.723秒、601→602分は未完。内部hotspot・最終target数・保存・性能測定は未確定。 現1237 counter r04もBUILD900.07856秒timeout(-9)、430CONSTRUCTION268.326934秒。inclusive Path累積は重複合算/差引禁止、602LOGISTICS未閉・最終測定null。",
|
||||
"remaining_gaps": [
|
||||
"S954528a正式24PASS、native7実行中。完走後Root全raw/source/player/PID/fullsidecar照合、独立Opus全8review、通常integration/final必須。全て同一Sに束縛し、旧失敗を合格へ補完しない。",
|
||||
"上記5指摘を含め正式UI保存/別PID厳格読込/全状態IO差分限定/K30継続/改ざん原子的拒否/実Ops arrival境界を正確最終Sで受入",
|
||||
"原P28-01 work/acceptance/横断6条件の証拠ゲート・whole-unit独立レビュー・final・台帳",
|
||||
"性能予算未達をP28/P28-02に保持:P1 x1mean479.210ms>4ms・stallmax4809.099ms>50ms、P2 x3P955062.697ms>12ms・stallmax5891.466ms>50ms",
|
||||
"200客Operations ONは未検証(通常Oは客0..2)、GPU時間はnullと理由を保持。同一Windows/長期操作感/バランス/全ゲーム最終受入は未達"
|
||||
"原P28-01のwork/acceptance/横断6を保持するwhole-unit証拠ゲート(実Opus266草案を照合中)・独立Opusレビュー・通常prepare/Root委任候補承認/integration/final・台帳。engine/full9/192の重複実行なし。",
|
||||
"G3の非0backlog未発生とprivate accumulator非保存の適用判断を理由付きで独立レビュー。開示のみで再現証明へ補完しない。",
|
||||
"予算未達4件はP28-02/03に保持、200客OpsON未検証・GPU null理由・同一Windows・長期操作感・全ゲーム最終受入は未達。"
|
||||
],
|
||||
"historical_summary_before_integrated_full9_20261021": "r02のheadless/OpsOFF/x1/300分、代表20,000マス/200客/50職員/150施設の既存証拠は対象区間を限定して再利用する(外側advance wall826.6msをKernel4ms予算へ比較しない)。新collectorはS5db97d7を独立Opus全9PASS、高/中0・low3記録、Q0f53a67で通常integration/finalまで完了した。新37合成試験は構造検証、実fresh R0は602→612分/4PID正常終了/Guard・audio・source/player保全を確認し、別31チェックで原rawを照合。実Kernel標本10件からx1平均379427us対4000us、stall最大466395us対50000usでfalseを記録した。これは事前診断で本測定ではない。全R/O・x1/x3・描画/区間/統計と別PID保存再開・原全条件の証拠/独立レビュー/受入まで原成果を完了扱いにしない。"
|
||||
},
|
||||
@@ -18513,7 +18530,7 @@
|
||||
],
|
||||
"cross_cutting_acceptance_scope": "Completion requirements only, not achieved results. Apply each to relevant slices; non-applicable views require a recorded reason.",
|
||||
"initial_revision_state": "in_progress",
|
||||
"updated_at_jst": "2026-10-21T00:28:26.609424+09:00",
|
||||
"updated_at_jst": "2026-10-21T04:37:36.598744+09:00",
|
||||
"execution_progress": {
|
||||
"current1277_checkpoint": {
|
||||
"source_files": 1273,
|
||||
|
||||
Reference in New Issue
Block a user