Compare commits

...
70 changed files with 6623 additions and 46 deletions
@@ -1,12 +1,12 @@
## 現在の次対象(2026-10-13・P24-03・実行再開)
## 現在の成果と次対象(2026-10-13・P24-03完了→P24-02)
P24-03「選択と保存の受入」を次に正式完了させる。候補4cefcff849d4dc1211caa5066e24bfae666cc2f4は正式機械検証済み、全7条件の既存版付き証拠を再利用し、独立Claudeレビューと統合検証を残す。305script parse/import、局所4suite/254check、同版全173suite/14810check、通常OS入力と別PID17306→19943の実モデル/表示値復元の証拠がある。これらの試験件数を成果数には換算しない。正式9/着手23/予定56、30パッケージ/88成果単位、部分点なし。
P24-03「選択と保存の受入」は提出4cefcff849d4dc1211caa5066e24bfae666cc2f4、最新main統合0a759f13aed03c05fd4a25ab4a2c26eefec94e3cで全7条件を正式完了。既存局所/全回帰/通常OS/同一保存の別プロセス再開証拠を再利用し、JSON版番号の正例17checksを追加。別Claude独立レビュー、最新main統合検証、最終検証、Runnercompleted/traceabilityまで成立。原30パッケージ/88成果単位、部分点なし。現在は正式10/着手22/予定56。全回帰173suite/14810checkやRunner支援の1件を成果数へ加算しない。
最新ユーザー指示に基づき、P24専用レビュー資料の全文保持容量対策とRunner自身の実OS境界試験の実行方式を承認済みの必要支援作業として実行する。RP24-01を別の支援契約として登録済み(原88成果を増やさない)。Claudeコマンダー→Codex実装者1名→正確な最終版の正式全Runner試験→別Claudeレビューで進める。上限500000bytesは維持し、不変marker行だけを検証可能なcontextに置換する。ゲーム側の重複実装と不要な全回帰再実行は行わない。
次に閉じる1件は既着手P24-02「操作と通知」。原建設・通知UX、既存通知ログ/viewport証拠とP24-03通常OS証拠を再利用し、配置・無効ドロップ・モーダル・停止再開・フキダシ/ログ/カットイン・クリック代替・日本語入力/表示倍率の不足を条件別に確認して必要な修正に限定する。実装者1名と別Claudeレビューを維持し、未知値を作らず、Windows実機が必要な条件だけを分離する。
P13支援RP13-01最終1398aa2dは配置依存と負例fixture不良で未合格、修正2/2のまま採用せず保留。P04-01のD05未確定、P06マナ値未承認、P23手動入力延期、同一Windows実機受入未達は維持する。全条件・独立レビュー・統合証拠が揃うまでP24-03は未完了。以下の「現在」は記録時点の履歴。
Runner支援RP24-01は全51必須試験・実OS境界/通常フロー・追加資料保持検証・別Claudeレビュー・統合/最終検証を完了し、本番Runnerへ既存変更を保持して限定導入済み。3provider制限下doctorも合格。ユーザーの最新実行指示と既存main更新許可を適用し、不要な追加確認待ちを解除した。P13支援の未合格変更は未採用。P06値未承認、P23保持入力延期、同一Windows最終配布版受入等の全ゲーム残件は継続し、全体完了を認定しない。
証拠: [候補版検証](execution/multi-agent-dev-handoff/p24-03-current-validation-r03.json)、[現在の閉鎖手順](checkpoints/p24-review-packet-proposal-r01/NEXT_CLOSURE.md)、[支援作業開始記録](checkpoints/p24-review-packet-proposal-r01/AUTHORIZED_EXECUTION.json)。
証拠: [P24-03正式閉鎖](execution/p24-03-formal-closure-r01/closure.json)、[全条件の独立レビュー](execution/p24-03-formal-closure-r01/review.json)、[Runner支援の導入](checkpoints/p24-review-packet-proposal-r01/production-runner-adoption-r01/adoption.json)。以下の「現在」は記録時点の履歴。
## 現在のRunner実行(2026-10-12)
@@ -0,0 +1,12 @@
# P24受入を妨げない保守所見
正式独立レビュー合格。critical/high/mediumは0。lowの所見を対象外改善として保存し、今回の閉鎖範囲を広げない。
- cross-process の P24_OBSERVATION ログ行はエンジン出力ではなくcollector正規化で生成されている。元エンジンログと元raw報告はハッシュ付きで保持され、その旨も明示されているため受入可能だが、独立計測ではない。
- JSONで復号された float 1.0 の version を受理する正例(施設・職員・客の復元)は、リポジトリ外の補足プローブ(17 checks)でのみ確認されており、新suite自体は負例のみを持つ。次回の保守では正例を新suiteに取り込むことが望ましい。
- OS入力traceはCUA tool stepの書き起こし(faithful_transcription)であり、元のtool RPCは独立計測されていない。限界は index に開示済みで、root指示の方針(CUAの実OS配信を許可し、emit/関数呼出しのみは不可)とも整合する。
- producerの17306は先行する予備ペアのconsumerとして起動したprocessを再利用している。最終ペアは専用の不変package(formal-package-os)に保存・再開しており、証拠上の矛盾はない。
@@ -0,0 +1,14 @@
# 受入を妨げないRunner保守残件
正式レビューの重大度lowのみ。今回のP24受入に必要な検証は取得済み。次のコード改善時にまとめ、原88成果に加算しない。
- コミット済みの tests/test_runner.py には、実 git オブジェクトに対する `raw_blob`(CRLF や空白を保つ raw bytes、欠落、OID 不整合)の試験がない。`packet()` の P24 厳密対象分岐(`marker source absent` や、正しい定数で文脈に置き換わる正例)の試験もない。いずれも /private/tmp の補足試験でだけ実証されていて、その試験は稼働中の P24 project 状態に依存する。将来 `raw_blob` の検証や対象定数が壊れても、正式試験は検知できないおそれがある。追跡項目として残すことを推奨する。
- tests/test_runner.py では、`if __name__=="__main__": unittest.main()` が ReviewMarkerTests の定義より前にある。discover 実行では問題ないが、ファイルを直接実行すると新規クラスが走らない。
- 拒否文脈の行列試験は主要な拒否を網羅しているが、dirty tree、branch が checkout されている場合、task ID・base・acceptance_ids の個別改変は個別ケースがない。また `test_trusted_unknown_context_rejected_before_execution` の role ループは、project 不一致で先に拒否される。そのため role ごとの拒否はこの試験では実証されず、行列試験が担っている。
- 信頼実行は、sandbox なしで提出コードの試験を候補コード(tests/test_runner.py を含む)のまま host 上で走らせる。しかも validation は review より前に実行される。これは仕様で明示的に許可され、project・task・SHA・argv で厳密に絞られているため範囲内だが、運用上の残余リスクとして認識しておくこと。
- marker_context は、行の並び順の変更と行間の区切り(空白・カンマ)の差を行単位では表さない。これらは全体 SHA と全 diff でのみ確認できる。仕様の要求(行 raw と ID 集合)は満たしており、仕様上の問題ではない。
@@ -0,0 +1,11 @@
{
"candidate": "053f391bf35482c0996292de263f2c39e88d96f0",
"control": "/private/tmp/the-tree-rp24-control-053f391bf354",
"file_pins": {
"runner/core.py": "34d0f62ec5b17b070a2d1ad4f58d06f3be7e9b6fb649ec86b366bc03b07c1329",
"tests/test_runner.py": "315bead61f690a8d23ddd0a0da376041a7ad1cc0f6a4b64db07be52d864cb286",
"docs/spec/RP24-01.json": "dde0184b95c399fc1a359d70a4db4a97f6eafb546e86fc6488478a5af8a49e0b"
},
"archive_sha256": "9a83192a60ea12ffc9a4e15e7f28f3e884b1fa03217675b346425c815cdc74d6",
"no_production_adoption": true
}
@@ -0,0 +1,44 @@
{
"argv": [
"/opt/homebrew/Cellar/python@3.12/3.12.13_2/Frameworks/Python.framework/Versions/3.12/bin/python3.12",
"-B",
"-m",
"unittest",
"discover",
"-s",
"tests",
"-p",
"test_runner.py",
"-v"
],
"cwd": "/Users/korizatou114/Projects/TheTree-managed-worktrees/runner-p24-review-packet/RP24-01-validation-484daac4",
"env_names": [
"AGY_CLI_DISABLE_AUTO_UPDATE",
"BROWSER_PROFILE_DIR",
"BUILD_OUTPUT_DIR",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC",
"GIT_TERMINAL_PROMPT",
"HOME",
"LANG",
"LC_ALL",
"LOGNAME",
"MAC_CHROMIUM_TMPDIR",
"PATH",
"PORT",
"PYTHONDONTWRITEBYTECODE",
"TEMP",
"TERM",
"TEST_DATABASE_PATH",
"TMP",
"TMPDIR",
"USER"
],
"execution": "trusted_runner_self_test",
"target_commit": "053f391bf35482c0996292de263f2c39e88d96f0",
"spec_sha256": "5841d6ac4673336b2ece64efb1b9af3dbf67838bf54b8fa1021f2280e5105854",
"contract_digest": "918e6f6a0256536f0cd18e3e91683dd29932c48b7ea9cd1622992812a86372ff",
"config_digest": "164f796867f438e1b4dbe6ee111252dcb82ebcd194b3e2380cfeb2098cf63a23",
"validation_id": "runner-unit",
"verification_kind": "validation",
"submitted_commit": "053f391bf35482c0996292de263f2c39e88d96f0"
}
@@ -0,0 +1,56 @@
test_complete_context_and_raw_changes (test_runner.ReviewMarkerTests.test_complete_context_and_raw_changes) ... ok
test_non_p24_packet_and_size_guard (test_runner.ReviewMarkerTests.test_non_p24_packet_and_size_guard) ... ok
test_strict_parser (test_runner.ReviewMarkerTests.test_strict_parser) ... ok
test_trusted_authorization_matrix (test_runner.ReviewMarkerTests.test_trusted_authorization_matrix) ... ok
test_trusted_unknown_context_rejected_before_execution (test_runner.ReviewMarkerTests.test_trusted_unknown_context_rejected_before_execution) ... ok
test_actual_sandbox_control_read_denied (test_runner.RunnerTests.test_actual_sandbox_control_read_denied) ... ok
test_actual_sandbox_write_boundary (test_runner.RunnerTests.test_actual_sandbox_write_boundary) ... ok
test_agy_pty_without_keychain_access (test_runner.RunnerTests.test_agy_pty_without_keychain_access) ... ok
test_approval_required (test_runner.RunnerTests.test_approval_required) ... ok
test_claude_broker_expiry_fails_closed (test_runner.RunnerTests.test_claude_broker_expiry_fails_closed) ... ok
test_claude_broker_never_returns_refresh_token (test_runner.RunnerTests.test_claude_broker_never_returns_refresh_token) ... ok
test_claude_profile_keeps_personal_keychain_denied (test_runner.RunnerTests.test_claude_profile_keeps_personal_keychain_denied) ... ok
test_dependency_gate (test_runner.RunnerTests.test_dependency_gate) ... ok
test_duplicate_acceptance_denied (test_runner.RunnerTests.test_duplicate_acceptance_denied) ... ok
test_failed_test_blocks_review (test_runner.RunnerTests.test_failed_test_blocks_review) ... ok
test_fixed_denominator_tamper_denied (test_runner.RunnerTests.test_fixed_denominator_tamper_denied) ... ok
test_full_deterministic_flow_with_simulated_review (test_runner.RunnerTests.test_full_deterministic_flow_with_simulated_review) ... ok
test_git_metadata_write_denied (test_runner.RunnerTests.test_git_metadata_write_denied) ... ok
test_integration_environment_retry_requires_existing_review (test_runner.RunnerTests.test_integration_environment_retry_requires_existing_review) ... ok
test_llm_budget_limit (test_runner.RunnerTests.test_llm_budget_limit) ... ok
test_llm_concurrency_limit (test_runner.RunnerTests.test_llm_concurrency_limit) ... ok
test_main_change_invalidates_approval (test_runner.RunnerTests.test_main_change_invalidates_approval) ... ok
test_missing_acceptance_test_denied (test_runner.RunnerTests.test_missing_acceptance_test_denied) ... ok
test_no_zero_cost_assumption (test_runner.RunnerTests.test_no_zero_cost_assumption) ... ok
test_node_output_drained_to_protected_logs (test_runner.RunnerTests.test_node_output_drained_to_protected_logs) ... ok
test_out_of_scope_edit_denied (test_runner.RunnerTests.test_out_of_scope_edit_denied) ... ok
test_overlapping_file_scope (test_runner.RunnerTests.test_overlapping_file_scope) ... ok
test_path_traversal_denied (test_runner.RunnerTests.test_path_traversal_denied) ... ok
test_policy_tamper_denied (test_runner.RunnerTests.test_policy_tamper_denied) ... ok
test_provider_credentials_are_not_inherited (test_runner.RunnerTests.test_provider_credentials_are_not_inherited) ... ok
test_recover_releases_operation_not_task_locks (test_runner.RunnerTests.test_recover_releases_operation_not_task_locks) ... ok
test_repair_cannot_bypass_counter (test_runner.RunnerTests.test_repair_cannot_bypass_counter) ... ok
test_repair_limit (test_runner.RunnerTests.test_repair_limit) ... ok
test_resource_lock (test_runner.RunnerTests.test_resource_lock) ... ok
test_review_changes_blocks_candidate (test_runner.RunnerTests.test_review_changes_blocks_candidate) ... ok
test_review_refresh_requires_new_candidate (test_runner.RunnerTests.test_review_refresh_requires_new_candidate) ... ok
test_reviewer_sees_committed_tests_outside_worker_scope (test_runner.RunnerTests.test_reviewer_sees_committed_tests_outside_worker_scope) ... ok
test_runtime_isolation (test_runner.RunnerTests.test_runtime_isolation) ... ok
test_schema_rejects_false_success (test_runner.RunnerTests.test_schema_rejects_false_success) ... ok
test_secret_file_read_denied (test_runner.RunnerTests.test_secret_file_read_denied) ... ok
test_secret_scope_denied (test_runner.RunnerTests.test_secret_scope_denied) ... ok
test_spec_writes_denied (test_runner.RunnerTests.test_spec_writes_denied) ... ok
test_symlink_denied (test_runner.RunnerTests.test_symlink_denied) ... ok
test_timeout_is_not_pass (test_runner.RunnerTests.test_timeout_is_not_pass) ... ok
test_trusted_disposable_runner_pipeline (test_runner.RunnerTests.test_trusted_disposable_runner_pipeline) ... ok
test_untracked_out_of_scope_denied (test_runner.RunnerTests.test_untracked_out_of_scope_denied) ... ok
test_validation_before_submission_denied (test_runner.RunnerTests.test_validation_before_submission_denied) ... ok
test_validation_python_does_not_depend_on_shell_path (test_runner.RunnerTests.test_validation_python_does_not_depend_on_shell_path) ... ok
test_worker_commit_denied (test_runner.RunnerTests.test_worker_commit_denied) ... ok
test_worker_has_mandatory_outer_sandbox (test_runner.RunnerTests.test_worker_has_mandatory_outer_sandbox) ... ok
test_wrong_candidate_denied (test_runner.RunnerTests.test_wrong_candidate_denied) ... ok
----------------------------------------------------------------------
Ran 51 tests in 18.014s
OK
@@ -0,0 +1,14 @@
{
"task_id": "RP24-01",
"commit": "053f391bf35482c0996292de263f2c39e88d96f0",
"kind": "validation",
"checks": [
{
"id": "runner-unit",
"exit_code": 0,
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/ceaf7c64c52f4487aba24789aaa8e452",
"passed": true
}
],
"passed": true
}
@@ -0,0 +1,547 @@
{
"game_candidate": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"guard_bytes": 434382,
"utf8_bytes": 398312,
"validation_receipts": 1,
"full_diff_sha256": "b22853fcfcae7a0a79a744c2d5eba8b0cd40763e347cb62d5aebb628f34e9c05",
"source_count": 17,
"validation_source_count": 1,
"context": {
"review_context_format": "authenticated-json-rows-v1",
"base_commit": "286730f88ce430a1cbe1501d40361188f832d235",
"candidate_commit": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"base_git_blob_oid": "54a8438b7e96eb6105e9180a46a4896ec689a30d",
"candidate_git_blob_oid": "9438e65c214f6d4732deee1a86853713f6a62f49",
"base_raw_sha256": "5cebdfdffeca26c7778c8be916877b06ae6e42999272d73d01ea66e644a9d648",
"candidate_raw_sha256": "3c72cad4dded0619d0cf1c9691b590258ea657d79e482910bde8b0bbeed73e03",
"metadata_before": {
"schema_version": 1,
"scope": "exact per-suite completion label/schema; all check counts from actual raw only",
"unknown_roster_suite_policy": "refuse, explicitly review its source/schema before accepting",
"roster_count_is_not_hardcoded": true,
"original155_authority_sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad",
"current164_review_sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b",
"audio3_manifest_sha256": "5e2cf82041c617008099742a4a5611af789922ab1c37740997344caa6470e060",
"production_changes": [],
"engine_runs": 0,
"mimi_purchase_v2_candidate_binding": {
"existing4_GD_source_changes": {
"autoload/simulation_runtime.gd": {
"base_sha256": "dafee4833aa68fb97c8a8c43efc4aa6b9d29d83e00129bd2b99fd99210946b89",
"candidate_sha256": "8efbe3aafe519d5b67ba96025a3324b9b724e169aa757b2d31262066ab7b83bb"
},
"scripts/core/save_service.gd": {
"base_sha256": "79a8c3654868c81d3e623a429d8fdfe51f15d098cef8d5b60be091d5200b285d",
"candidate_sha256": "3e3d2aeeff7b7610b48bba24ca83610225f93aa8e8deef50c93186ee360fd8b7"
},
"scripts/supply/supply_management_service.gd": {
"base_sha256": "bd99c1418537415b574ff936c74817c721071fdcf4b31b1891347a7b6ae84e30",
"candidate_sha256": "e4ad420191020485c027004f64bb00fad9da195fe956ef10dc0c787412018f7f"
},
"scripts/ui/supply_management_panel.gd": {
"base_sha256": "b3425f797f2e56990bffd64a9770fcfd0e4b487b7420e848b43bb7c44f41bc4d",
"candidate_sha256": "c23c84c5afb2960b51384d80703c3e454059b46eb04e237df52f62291de7db06"
}
},
"old169_suite_source_SHA_rewritten": false,
"source_schema_scope": "prospective Runtime15/formal2; original v1-only14/formal1/generic/legacy1-13 immutable branches remain required",
"actual_new_marker": null,
"actual_check_count": null,
"actual_new_UID3": null,
"adoption": false,
"source_r06_seal": {
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p17-mimi-purchase-v2-source-candidate-r06/input-seal.json",
"sha256": "7c01c096524d9560fb691f7fb1e0b5a718592b964574f8fe0153beb1ec76a60d",
"bytes": 3818
}
}
},
"metadata_current": {
"schema_version": 1,
"scope": "exact per-suite completion label/schema; all check counts from actual raw only",
"unknown_roster_suite_policy": "refuse, explicitly review its source/schema before accepting",
"roster_count_is_not_hardcoded": true,
"original155_authority_sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad",
"current164_review_sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b",
"audio3_manifest_sha256": "5e2cf82041c617008099742a4a5611af789922ab1c37740997344caa6470e060",
"production_changes": [],
"engine_runs": 0,
"mimi_purchase_v2_candidate_binding": {
"existing4_GD_source_changes": {
"autoload/simulation_runtime.gd": {
"base_sha256": "dafee4833aa68fb97c8a8c43efc4aa6b9d29d83e00129bd2b99fd99210946b89",
"candidate_sha256": "8efbe3aafe519d5b67ba96025a3324b9b724e169aa757b2d31262066ab7b83bb"
},
"scripts/core/save_service.gd": {
"base_sha256": "79a8c3654868c81d3e623a429d8fdfe51f15d098cef8d5b60be091d5200b285d",
"candidate_sha256": "3e3d2aeeff7b7610b48bba24ca83610225f93aa8e8deef50c93186ee360fd8b7"
},
"scripts/supply/supply_management_service.gd": {
"base_sha256": "bd99c1418537415b574ff936c74817c721071fdcf4b31b1891347a7b6ae84e30",
"candidate_sha256": "e4ad420191020485c027004f64bb00fad9da195fe956ef10dc0c787412018f7f"
},
"scripts/ui/supply_management_panel.gd": {
"base_sha256": "b3425f797f2e56990bffd64a9770fcfd0e4b487b7420e848b43bb7c44f41bc4d",
"candidate_sha256": "c23c84c5afb2960b51384d80703c3e454059b46eb04e237df52f62291de7db06"
}
},
"old169_suite_source_SHA_rewritten": false,
"source_schema_scope": "prospective Runtime15/formal2; original v1-only14/formal1/generic/legacy1-13 immutable branches remain required",
"actual_new_marker": null,
"actual_check_count": null,
"actual_new_UID3": null,
"adoption": false,
"source_r06_seal": {
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p17-mimi-purchase-v2-source-candidate-r06/input-seal.json",
"sha256": "7c01c096524d9560fb691f7fb1e0b5a718592b964574f8fe0153beb1ec76a60d",
"bytes": 3818
}
}
},
"base_rows": 172,
"candidate_rows": 173,
"changed_rows": {
"tests/ui/test_selection_departure.gd": {
"kind": "added",
"before": null,
"after": {
"test": "tests/ui/test_selection_departure.gd",
"template": "Selection departure: <checks> checks, <failures> failures",
"required": true,
"fixed_check_count_used": false,
"source_sha256_at_schema_audit": "3137f36f547514f25ce6ac775fa7a5141d12f83ca4b95da0be45fecfafe9d9b4",
"authority": {
"path": "docs/spec/acceptance/P24-03-code-r02.json",
"scope": "new local suite source marker; actual execution pending"
}
},
"before_raw": null,
"after_raw": "{\n \"test\": \"tests/ui/test_selection_departure.gd\",\n \"template\": \"Selection departure: <checks> checks, <failures> failures\",\n \"required\": true,\n \"fixed_check_count_used\": false,\n \"source_sha256_at_schema_audit\": \"3137f36f547514f25ce6ac775fa7a5141d12f83ca4b95da0be45fecfafe9d9b4\",\n \"authority\": {\n \"path\": \"docs/spec/acceptance/P24-03-code-r02.json\",\n \"scope\": \"new local suite source marker; actual execution pending\"\n }\n }",
"before_raw_sha256": null,
"after_raw_sha256": "4e664b062178da6f2c7540ac037ab86cc40b7097c0e8800f6b22e527c7a39bfe"
}
},
"focused_current_rows": {
"tests/ui/test_actor_selection.gd": {
"test": "tests/ui/test_actor_selection.gd",
"template": "Actor selection: <checks> checks, <failures> failures",
"required": true,
"historical_marker_for_schema_only": "Actor selection: 100 checks, 0 failures\n",
"fixed_check_count_used": false,
"source_sha256_at_schema_audit": "ecc08464c20990f0a4bd5d1e5d8d6fceb6f3636c959790842a08539fc54ad911",
"authority": {
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json",
"sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad",
"scope": "historical exact label/schema; check count is not an expected future value"
},
"source_marker_lines": [
{
"line": 183,
"text": "\tprint(\"Actor selection: %d checks, %d failures\" % [_checks, _failures])"
}
]
},
"tests/ui/test_selection_autosave.gd": {
"test": "tests/ui/test_selection_autosave.gd",
"template": "First operating day: <checks> checks, <failures> failures",
"required": true,
"historical_marker_for_schema_only": "First operating day: 75 checks, 0 failures\n",
"fixed_check_count_used": false,
"source_sha256_at_schema_audit": "1d7d267ef171041f77241c4c2f1a1bb55a46253f803d5c496507e934bc8fa815",
"authority": {
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json",
"sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad",
"scope": "historical exact label/schema; check count is not an expected future value"
},
"source_marker_lines": []
},
"tests/ui/test_selection_departure.gd": {
"test": "tests/ui/test_selection_departure.gd",
"template": "Selection departure: <checks> checks, <failures> failures",
"required": true,
"fixed_check_count_used": false,
"source_sha256_at_schema_audit": "3137f36f547514f25ce6ac775fa7a5141d12f83ca4b95da0be45fecfafe9d9b4",
"authority": {
"path": "docs/spec/acceptance/P24-03-code-r02.json",
"scope": "new local suite source marker; actual execution pending"
}
},
"tests/ui/test_selection_lifecycle.gd": {
"test": "tests/ui/test_selection_lifecycle.gd",
"template": "Selection lifecycle: <checks> checks, <failures> failures",
"required": true,
"historical_marker_for_schema_only": "Selection lifecycle: 38 checks, 0 failures\n",
"fixed_check_count_used": false,
"source_sha256_at_schema_audit": "b1172419e53fbffccb8f6cda046539fee8be1fe0ad711c3aff7f840b73f77895",
"authority": {
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json",
"sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad",
"scope": "historical exact label/schema; check count is not an expected future value"
},
"source_marker_lines": [
{
"line": 149,
"text": "\tprint(\"Selection lifecycle: %d checks, %d failures\" % [_checks, _failures])"
}
]
}
},
"focused_current_raw": {
"tests/ui/test_actor_selection.gd": "{\n \"test\": \"tests/ui/test_actor_selection.gd\",\n \"template\": \"Actor selection: <checks> checks, <failures> failures\",\n \"required\": true,\n \"historical_marker_for_schema_only\": \"Actor selection: 100 checks, 0 failures\\n\",\n \"fixed_check_count_used\": false,\n \"source_sha256_at_schema_audit\": \"ecc08464c20990f0a4bd5d1e5d8d6fceb6f3636c959790842a08539fc54ad911\",\n \"authority\": {\n \"path\": \"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json\",\n \"sha256\": \"aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad\",\n \"scope\": \"historical exact label/schema; check count is not an expected future value\"\n },\n \"source_marker_lines\": [\n {\n \"line\": 183,\n \"text\": \"\\tprint(\\\"Actor selection: %d checks, %d failures\\\" % [_checks, _failures])\"\n }\n ]\n }",
"tests/ui/test_selection_autosave.gd": "{\n \"test\": \"tests/ui/test_selection_autosave.gd\",\n \"template\": \"First operating day: <checks> checks, <failures> failures\",\n \"required\": true,\n \"historical_marker_for_schema_only\": \"First operating day: 75 checks, 0 failures\\n\",\n \"fixed_check_count_used\": false,\n \"source_sha256_at_schema_audit\": \"1d7d267ef171041f77241c4c2f1a1bb55a46253f803d5c496507e934bc8fa815\",\n \"authority\": {\n \"path\": \"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json\",\n \"sha256\": \"aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad\",\n \"scope\": \"historical exact label/schema; check count is not an expected future value\"\n },\n \"source_marker_lines\": []\n }",
"tests/ui/test_selection_departure.gd": "{\n \"test\": \"tests/ui/test_selection_departure.gd\",\n \"template\": \"Selection departure: <checks> checks, <failures> failures\",\n \"required\": true,\n \"fixed_check_count_used\": false,\n \"source_sha256_at_schema_audit\": \"3137f36f547514f25ce6ac775fa7a5141d12f83ca4b95da0be45fecfafe9d9b4\",\n \"authority\": {\n \"path\": \"docs/spec/acceptance/P24-03-code-r02.json\",\n \"scope\": \"new local suite source marker; actual execution pending\"\n }\n }",
"tests/ui/test_selection_lifecycle.gd": "{\n \"test\": \"tests/ui/test_selection_lifecycle.gd\",\n \"template\": \"Selection lifecycle: <checks> checks, <failures> failures\",\n \"required\": true,\n \"historical_marker_for_schema_only\": \"Selection lifecycle: 38 checks, 0 failures\\n\",\n \"fixed_check_count_used\": false,\n \"source_sha256_at_schema_audit\": \"b1172419e53fbffccb8f6cda046539fee8be1fe0ad711c3aff7f840b73f77895\",\n \"authority\": {\n \"path\": \"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json\",\n \"sha256\": \"aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad\",\n \"scope\": \"historical exact label/schema; check count is not an expected future value\"\n },\n \"source_marker_lines\": [\n {\n \"line\": 149,\n \"text\": \"\\tprint(\\\"Selection lifecycle: %d checks, %d failures\\\" % [_checks, _failures])\"\n }\n ]\n }"
},
"unchanged_raw_row_count": 172,
"unchanged_raw_row_proof": {
"tests/business/test_business_closure.gd": "72cfd9533569c79d22895d6163e68c64bb03bf9f6890823d86f399dfec4df914",
"tests/business/test_business_operations.gd": "9225c40a46146cfc75b9b65d3f72740b2f4c81df42e47729549221e3904335e5",
"tests/business/test_required_kitchen.gd": "20315f991160ed6604c9e71768e4201ac6a5a86bb6ee8e0b86ed4d39413e7ec7",
"tests/environment/test_cleaning_access.gd": "ad034c6b408b46a10c516e8ded39e25b832fbbaa22d9696ecb2bbec67f592c3e",
"tests/environment/test_facility_cleanliness.gd": "eb81fbf0e8adeca1b91c3b1b83b9ea2a3e8ca3092e7912498848b9e83b569b29",
"tests/environment/test_runtime_cleanliness.gd": "510903bc4cc3ac78dea6ae9a10b5fefebf34eb17537af0c095ab9ded90b11ed0",
"tests/environment/test_runtime_weather.gd": "c966414af96e35287aab86535cfa3dc48b486c6c7f57b7e00f1678d64b83cf6a",
"tests/environment/test_weather_recovery.gd": "4d7f81a0af4f7bd53f0e3210f9bc69f91ed4a47fa46adf141e41eef1e249393e",
"tests/environment/test_weather_timeline.gd": "ca41498de14bfbb4f5b18ab44508808afed9226dddbcffe4211bc86e56ccf286",
"tests/mvp/test_amelia_donations.gd": "779382447c03c92b51633c4b29d2bcc561c048b0a016715fbd202359175d1c86",
"tests/mvp/test_anri_floor_pricing.gd": "417dd0db3e1fe8d49d51fc364cbdfe7bc24d829088addacd0429746bb908eca4",
"tests/mvp/test_approved_free_unique_arrears.gd": "28a53b73051cf23792e21bfa696d8efa6241e2f50fa0ee4a02359fc2ea6c8004",
"tests/mvp/test_chapel_boundaries.gd": "b3ae22c781059764067d658a75f1a7cbf46543e1ef565295b5cae3a323751c3c",
"tests/mvp/test_chapel_service.gd": "77905f0d2383af1c8ce1d822946b9b21d34a01e8c1707c720acd7314b6f7a3fe",
"tests/mvp/test_fine_cleaning_effect.gd": "164ebc4801af85be90967fb6db665c1c4f24da5b1fd5b6226b4191b1ba08278e",
"tests/mvp/test_mvp_content_catalog.gd": "a3213cbbc96b1850395a3cbeb0a5c7af4baa9498060647c634931b7dbf549ff3",
"tests/mvp/test_operations_coordinator.gd": "d599e080fd4643a4fbfefb2ae80b3bad8bbe3013c9598a5773f3ad58936f7c12",
"tests/mvp/test_original_midnight_settlement.gd": "ae0c9c6a029eede1c25cbcb53db870ac15f646a91e81d45ed122af3372dfeb1f",
"tests/mvp/test_original_room_zones.gd": "dc0d658561a40d52bfb322d3868120bc288ebdb80f21a4c4e2537edc5e254678",
"tests/mvp/test_pending_service_pricing_report.gd": "2d864e1e78945ead82152383d66c7dd0596730ad496db31b4b7a958c2116ad60",
"tests/mvp/test_premium_room.gd": "1f3d316277f1383fc8d55fed38f9897ff5a4caba20a13f03606cee193bb9a41d",
"tests/mvp/test_premium_room_guest_flow.gd": "b6bf3e3baedb621844eded72b45420655903390c77dddb42f33830e02a44f9ae",
"tests/mvp/test_progression_service.gd": "dcc9474f6597bc7aad57a76177c17b907613984a38a69da9b5282b5a0e215179",
"tests/mvp/test_runtime_content_progression.gd": "3cf572219416bc1f9635eb7b6ac27e6277e74363a675443c487ae1feafce43e7",
"tests/mvp/test_runtime_mana_depletion.gd": "f395aca11b29d71edaca3a24152639a1d479cb68c20d418a9be4d8cc9c47db87",
"tests/mvp/test_runtime_operations_acceptance.gd": "bdf58844256d6f7be617cf9df18834abcb3f2b4016c6538dbbea1c34b61552c9",
"tests/mvp/test_runtime_operations_integration.gd": "a17015a661808f6ecb4f3830e1c3de546621c1edb47525a096b685df22c684f0",
"tests/mvp/test_runtime_upkeep.gd": "5a2551bc3c20841874fb723ce81c3d61a10838a070be0fb414c1613823865069",
"tests/mvp/test_staff_unique_contract_report.gd": "46e95500abce5310524d510fb2f4a687a0d6d7fdf3229b309f6edadb8a39643d",
"tests/mvp/test_tomari_floor_trouble.gd": "a869a46bd27158f59074cd2f47936c236450287ef8141c90757661ef007e25eb",
"tests/mvp/test_unique_character_validation_policy.gd": "7251db3eb4676125ecb0f1e586071a18b60d17862d9f8f670e4879f46569dc7e",
"tests/mvp/test_upkeep_construction_boundaries.gd": "88afc505b68615ff365936e40ca39288ec83fb58ede96511ac297ef2d834876c",
"tests/mvp/test_upkeep_legacy_midnight.gd": "ade1514874f851d738f3198d31b5cb94969f9623bca26f5eeda11bd3677e7501",
"tests/mvp/test_upkeep_loss_streak.gd": "421c8598d3fd2de5b4e9c561a638e0cc247226d83e729f8c65157ffbea920524",
"tests/performance/test_digest_performance.gd": "8f5f0def8e31088cd29ff3c85d431a09f2cfdca35c0a9d102b1e7871ab118ba2",
"tests/performance/test_facility_reachability.gd": "82763c8e1e8a569d057cc1dc717c94472cc1ad39f64ddfa99af1be34a6ad457a",
"tests/performance/test_lazy_digest.gd": "4d63e6093dfb4cbf7f22a021bf72f0b03cb50b2da09de4b59585d9c0ee5400a6",
"tests/performance/test_mvp_manifest_cache.gd": "1b7bc650ea2879dd582a7dc46bd601b6c1747ec1ebde7bbcffba946a97061281",
"tests/performance/test_path_membership.gd": "d251ef2492cc4480685ee227fd7ee424bdf348eca95bac8f340b7fedfcf27d06",
"tests/performance/test_perf_mvp_001.gd": "7a0ac13aa9ed0286a9ad5ed2b8088424ad14236a8446d4d6d5d1c0e9233a17f0",
"tests/performance/test_snapshot_ownership.gd": "0c9344b64a99ca35d85dacd0db65671dea1c7755384aaf63e334dd9c76bf5383",
"tests/performance/test_system_optimization.gd": "878a30fc0027cfbf28da056a1ec5c0c283f21e611671e83f7a721fbe660e1e16",
"tests/performance/test_world_snapshot_cache.gd": "f5882af31951e9deca2b8f60766ace3bc7cb43e066673c2681aadfdbbf8a49b1",
"tests/run_tests.gd": "3d0e5bee68d4ea183c6a4f5b0bdf899b3290bf079a1987a80ebb376141443590",
"tests/s0/test_command_snapshot_key_order.gd": "fbf97927b76c63b4637374e6e690701a5889ad8143dd221b030d3dab663c27a7",
"tests/s0/test_command_transaction.gd": "2ecd93cbb418d1db1209e8846c71bcff4f1f17331651d73e3263842e0d30e6c4",
"tests/s0/test_definition_registry.gd": "9876d70362d0e9b6402cc5b90acc227e23983a82ff88dbc792026e5c7b7c5304",
"tests/s0/test_determinism.gd": "4dd6ece78e2a806b5c791f2d13c7d701558faec5e65d3d410afc30e7718e6d89",
"tests/s0/test_domain_events.gd": "5c7d9f7df7921f00892155f793ef707a3d57d7f3ad193bf8c9a5df7c96e950af",
"tests/s0/test_runtime_integration.gd": "b436d6fba39f12dfcd95acff0ba2980f2460b77404d937a8ec91d4c5a9ffe188",
"tests/s0/test_stable_id_generator.gd": "f4684fd2d6693ae08441abf120ee941c447e99b2dc6a0c2690f5ab0c98c8d1d5",
"tests/s1/test_construction_service.gd": "c3fbf58740da5045bc3fb7170a016f53dae2ca63e4a54a19c0273fa5b2b6540a",
"tests/s1/test_dimension_batch_save.gd": "9b32174f9ed2b432f988909e54e2ed057b7a34dbf757dc77605b1614e48c3404",
"tests/s1/test_excavation_service.gd": "172f76465bda5ed66aa300b60a9b9c5ef307dc8d172d541049610c8c059b6ea9",
"tests/s1/test_facility_infrastructure_projector.gd": "a39bf71b97e5e16ab798f1381bb51bf6199cff826bdd310fec6d53befcf6b16d",
"tests/s1/test_facility_resize_preview.gd": "7dd9888bfd9977d0d53e88b917d5bb29879f8f5b44b6111678a3da202a881df8",
"tests/s1/test_original_facility_dimensions.gd": "01a4de68f9679b557b6745abd50ce4b43bf41f73650c0995b88ed5edf5d32c27",
"tests/s1/test_path_congestion.gd": "3714841bc64f147c31adff0c9e744162647a369623cabc231f022a98cf0bb074",
"tests/s1/test_path_service.gd": "ecc0d132387f4b488ac8a1ecb841012cc8ed1513563c796bc5d162c49353e0b4",
"tests/s1/test_path_snapshot_transit_guard.gd": "f9f1486d51a2a20eb452a342d39dabba05b1614b97ede04788f915eabd025987",
"tests/s1/test_route_subslot_path_adapter.gd": "fbdc2073e7ae9d295d275a76ada8d44bddbdf5a9020654052012f4639b9ee920",
"tests/s1/test_runtime_infrastructure_integration.gd": "e82fb4117a557cee0cdd3c63f73db9a86d09f44bdce80062362c0ca904fa56c6",
"tests/s1/test_runtime_world_construction.gd": "d2da40c0d11dccac4ce94778ffb07230a63e1cd174f48b348791edffb9812d9e",
"tests/s1/test_spatial_subslot_service.gd": "72a9f8908f748445883feea6629c55f0162031cd5d641d45790a21f2f7c806bd",
"tests/s1/test_weighted_resize_runtime.gd": "cdc5b7a68e10c45c0cb2e83950c39a58e5e461909d40d2935897fb3a8121f437",
"tests/s1/test_world_service.gd": "21e86bf3d1e32403a0b73c927e1f604ccc91d78290c22e1742cd063f5fb9a8fe",
"tests/s2/test_economy_service.gd": "e1409e38e89714a4c761dcd9e0ae8a2babe3b48150cc09e3f66f8345a1ab4410",
"tests/s2/test_facility_pricing.gd": "530df02db6fa460ed665e7449ad4890026a2d87ebef6db4b9acaa9d18a722fe4",
"tests/s2/test_facility_service.gd": "df0023c908f01af85eac9726bb2909cb792d6d4ecd3e6a2a8bbc66c8e1e8be36",
"tests/s2/test_facility_upkeep_accrual.gd": "53d05baadad526053eded75d1f96dba449331f215ac6cb2a61a55be181ca8651",
"tests/s2/test_facility_upkeep_settlement.gd": "fd3241471a704325a469ffe0025f2b7171e267bc87a629c52ad1d40f33152681",
"tests/s2/test_inventory_logistics.gd": "2e5da1c09d0ce660ce564a0c7e2e86baa94f463857d94fb8ad24e5905badfad6",
"tests/s2/test_kitchen_production.gd": "696d1ad18a3dab00e376b69a181afd04cafe1b04e1e4ec85894e601cbf45628a",
"tests/s2/test_ledger_backed_resources.gd": "bf3a4f8cf1b45c25fadb9bbfebce2a618b37ae5a829ed6900b079b8396472443",
"tests/s2/test_mana_network_service.gd": "35c7a903df12562fec091d33d7ec9f338ca16cf7fd059ae608a7ac9f428d9d6d",
"tests/s2/test_mana_power_projection.gd": "c21a8ebe7580f568134d156554ec5fc6546fc7e095c052fb6736f57cc73f9984",
"tests/s2/test_mana_reserve_balance.gd": "bbc62b7a7480c2600eae6291c29344cac41a33ae005320edfd9c695ff8d2c58e",
"tests/s2/test_original_facility_parameters.gd": "5eb473d6a8bcbebf2279aacf28229e2fd80fc17a3a31df7cb8bca13cabf2aa94",
"tests/s2/test_runtime_operations_cycle.gd": "bb7f88e6364d4d844aae1d26156790359b096fbc1f8db421a1a6173639cba167",
"tests/s2/test_service_start_slots.gd": "be9f4027f2a4260b8216ddbadb8857dfde109d393bc27c8de3f1454d8bb447b2",
"tests/s3/test_guest_observation_restore_order.gd": "5d1511cc85f7842b61ea635a4ac2af96628b779eca53e988e38eba09a8af2f70",
"tests/s3/test_guest_service.gd": "2d311228e7098325a3ce2c52fae7330be9b7cb67efd642ce52aaa5300f3d82ec",
"tests/s3/test_runtime_actor_cycle.gd": "11dcb5937962eb0ff7b41e4d8a21c1fe988b659d0024d9bb2b3fec9bd6a6b6d4",
"tests/s3/test_runtime_actor_hardening.gd": "30f1965e79aebf2e2fe592850afacdf443ef07d783883876e77f2857b0e30599",
"tests/s3/test_runtime_path_traffic.gd": "98f0d342a38e51c463372a69025d05107d20ae87817b280d8c1a7527b5ec23a9",
"tests/s3/test_runtime_staff_assignment.gd": "001eca714e0298a953c9c75a5e937da4ecf86e3e2b2bf53ec87b12daaf788115",
"tests/s3/test_staff_payroll_operations.gd": "d2cc0087a0d5e39ea7c72e6ac87f4643dcc83bb41297a4d1bc9035b6a544ea6d",
"tests/s3/test_staff_service.gd": "6635048deca0dc89e704c9738dc654ce8bc8df1397427bd2cf11b3f2e3a06564",
"tests/s4/test_placement_risk_preview_service.gd": "6ca69a7c945a46b6bf7984f078eb7a6694967a1169de04d9208fd84b51541c04",
"tests/s4/test_relationship_event_service.gd": "4a4d5038501ffcdb8ffa4596d7bd4395db19003f869a44ba19c59d0a9886b6b0",
"tests/s4/test_runtime_relationship_events.gd": "b10d90937468deca44e3540f9d66b50ae8ac56038e48e7478e4803a5cefc125d",
"tests/s5/test_authority_service.gd": "854b08f6ea495472b69cadfc70cac545d2e9f948dbb2cad4f45f4161b6f51503",
"tests/s5/test_defense_service.gd": "080a5d291d3540d045d9e07023dee561bdce333467ede3b8b31e9d3d8939bff9",
"tests/s5/test_runtime_authority_cycle.gd": "502641a3d6ac8205f19f862b46f26d6c6c04020d7f069b1a0a73b71e87389734",
"tests/s5/test_runtime_defense_autonomy.gd": "991429baa69b12de16417152f817a224706254a3e71bc4855ea9c63fdf8092be",
"tests/s5/test_runtime_defense_cycle.gd": "33f4a8a7e3d09b50c3bc12bf44532ed49b27ecbdd8fdc7b49597b1970bddb681",
"tests/s5/test_runtime_event_defense.gd": "cd7e7b9ba355983cb9d182eb0ce03309340a3061f6952d2ad8ef6a5e9c3db7b6",
"tests/s6/test_performance_budget.gd": "63fd42ecd5af6b005a79fe676c7cc2390081cf029e945fb44be3c793eefd48c2",
"tests/s6/test_runtime_save_lifecycle.gd": "26d2af83a98e9abbce80c0ff1985de9516ce79611b24b94276468657717b07fa",
"tests/s6/test_runtime_snapshot_serialization.gd": "0494708b7f0fdc4bd3e6228195687474cfac9baef6164f64dce8e7a69675416f",
"tests/s6/test_save_operations.gd": "1614a5122ff9f466aec038f580d63e91c5d1feab8d8f74018fa6b540b75df0e2",
"tests/s6/test_save_preflight.gd": "95c951a981c154348b39c889188deb872ce7e9e2ea0740cd8bb2592c3c853b9b",
"tests/s6/test_save_recovery_preservation.gd": "5c055617493cbd0cd25049cf8315f880cfbdfc52b3c5f1d17281184870197cfc",
"tests/staff/test_dormitory_dimension_migration.gd": "ace438ca3aa97e44db111a5107d630b7fd86eda2fb39f9199ca45eab4a803a71",
"tests/staff/test_dormitory_shift_cycle.gd": "b7c868002e2f385abf4d370a31e1a46521115690f4f4acafba8784ee018b3bea",
"tests/staff/test_rescue_elevator.gd": "c7d0ac22a4816abbe112ae4413a81410c0c5d0d85eadc69b899b999fedde7dc1",
"tests/staff/test_shift_rush.gd": "cfb1c07428bf60f533860d74eeca25c51fd492143b5d598c88b8ff92f2747a57",
"tests/staff/test_staff_commute.gd": "6942c4dfc875596b9ff845a206f146d87d28697bf137ed90000547368738821d",
"tests/staff/test_staff_dormitory.gd": "eb1d121b79de9df77fee00f2635e7d56ba175cc4e9287ed1234f039093eea7b1",
"tests/staff/test_staff_rescue.gd": "7b82618030ea1401b33b7796343aef7894f402fb99d2f26dfb3e5f89fa0e6590",
"tests/staff/test_staff_rest.gd": "e3ce1fc69d4bc241066d22a4c6c276871fc72087c1d94bc73d80ed20b88ef00d",
"tests/staff/test_task_work_clock.gd": "32c833d2fda02cc42b21b458c874e1afbd55e9d509a72df4bb34cd2d9cd33367",
"tests/supply/test_mimi_purchase_quote.gd": "2385b2d9d7a48991e1be5a334195211634d65dcbe5a6aad8abdaa78dce6f575f",
"tests/supply/test_normal_auto_purchase_chain.gd": "a1ae167057d8ad73103902911893f4e55e2cde03cbf8d413930919635a52d418",
"tests/supply/test_normal_supply_chain.gd": "5e1d57c7b8872073f68ad6224c79c8cc1d3282360a7331d0397b6a75e0c908b5",
"tests/supply/test_supply_auto_purchase.gd": "c832d6c871a542050fc90988b4dfc551b8e75002da1dfa406a47ae0d582d147a",
"tests/supply/test_supply_auto_purchase_boundaries.gd": "e73f8c11d24bb6c98e4bf3467e08aba6a1a1c18c1b465ea6e3943ce8ba9ebe4a",
"tests/supply/test_supply_auto_purchase_destruction.gd": "8b91682beecc74db781f55e0d5c6bcf76f3548becf6e5cebb80bc22f1e645543",
"tests/supply/test_supply_logistics_bridge.gd": "6593a850c421f2740cfa50c85055d47c323bc52f1fbd86a1be5b6eec2e550441",
"tests/supply/test_supply_management_service.gd": "b2e6a957fd82e384ec426bc10a72af253b1af2532d7154865d00bb81b11da145",
"tests/supply/test_supply_save_migration.gd": "00840e0c7267f699153dc99253ce1eb2ecaa71b22a4fd096234409fce5a743d9",
"tests/transport/test_elevator_service.gd": "dbfee5c1d581d3ed5414fff0fc8ca2051d12b9f1c8ecfaa44f9bbebc39f10382",
"tests/transport/test_runtime_stairs.gd": "f09a9934bf43a3be7f0071743858be804d6deca652113b30d9cde49969669a77",
"tests/transport/test_runtime_transport.gd": "9b8674b5220f2e49b5ed88db23cda910910ed97d2eccce8c8bd82e27ae45efff",
"tests/transport/test_runtime_transport_comparison.gd": "bc701caacc83a3f110c75538c2d9a97666e90facb4eb84796b407f50f9080417",
"tests/transport/test_transport_boundaries.gd": "e76068fe155c15bccf0be14078740886715b624f7b4e30dcd61a0c885469e806",
"tests/transport/test_transport_comparison.gd": "1b85b9b85f3f3f292cbccf01372d76fb572bb214386ad7014464fa99d4a829d5",
"tests/transport/test_transport_density_migration.gd": "2fa94d1db9135dee7b5253544374d93c535b585d1893bee78d6d072f7a317063",
"tests/transport/test_transport_save.gd": "888da77a548b75025c688593119d14f8db5cabe7a6eda46bf9b44f163285be6f",
"tests/ui/test_actor_selection.gd": "dc368864b7eaf3180b28f0a245ba7602087d8b87403e30ba3427725cf67eb920",
"tests/ui/test_anri_recruitment.gd": "fceed6d7ef285e20972cd50cc17e15baf4ca58ea5ffebdef58884ea9287a7b9f",
"tests/ui/test_approved_free_unique_arrears_ui.gd": "7a0d44a3ea62a3f6005593076ec5d51cc77348ed49443f6b66385e95ceefbcf3",
"tests/ui/test_business_management.gd": "e21190290fc03e7ef8bd5558a00ad42235c5522fc06eac13176df12becbb6ce0",
"tests/ui/test_chapel_gameplay.gd": "8fe9588f68d48884ff0d88ea76018349b618846586fcf6ef0d17be653014709a",
"tests/ui/test_cleaning_gameplay.gd": "6a894199f0aca563533e1de6fda3fbea7b0efa8a7b96ede7f68edc95566978db",
"tests/ui/test_dashboard_coalescing.gd": "06dab550f94b3d86d703c950310cdae752811616786bf34717b867d153da472b",
"tests/ui/test_defense_management.gd": "b2d128234dfd22376bdddcd61bebcfbf9782a5b4a65739a19994bd2df8d2e0d0",
"tests/ui/test_dimension_migration_ui.gd": "5c9b1a484bc4f584aa1d0cbc54f8fa4ae6d176fdd1daaef949ccacc66934b0ec",
"tests/ui/test_elevator_management.gd": "e0f5ad1aa692e39a185e399798b8d2f88773630ce583d257a341e66bc44cb295",
"tests/ui/test_event_log_ui.gd": "46178dc4b1af403597fd60b6280fea723b6ac9277573a0bd7563cf054abdeb26",
"tests/ui/test_first_operating_day.gd": "858b2f24e6b5f446920cc8c4c00ececbbaed7ac4a3dfeaa97ac74495017ffd59",
"tests/ui/test_guest_decision_log.gd": "55f857d99e5c1370c17459110aae63f7826a6b499eda1d38a76b99484344bd3a",
"tests/ui/test_guest_observation.gd": "82d51795bb221c48365459e07e0d2f6569844b256df98b723e8cfa9d04f9187a",
"tests/ui/test_layer_presentation.gd": "a47031252032877c72b6ac9e515bf4d149472884331b1ab8cbd57f0379b263ae",
"tests/ui/test_layer_skin.gd": "264aceab2cf40dfd05121500d899bcfad68888ef3e3b9cb168e142050de8fd32",
"tests/ui/test_main_audio_feedback.gd": "223c187442dea89de78db5b3f55dcfd43e6dade9b8440ab3d1d44ebb2dcb6c7d",
"tests/ui/test_main_dashboard.gd": "a39c850b08e3facdad0ba16220179d5c2dd9b077d7402a5a38112828c743096b",
"tests/ui/test_opening_assignment.gd": "06f220e76d0b28ce85b33bcf9c15bafd992b377ec6ecf6d0326af5fb000d10e2",
"tests/ui/test_original_build_pointer.gd": "4717f53cb0e852287e5b63b1e2bebe0053e27f4e022738a217dc2980cae7858f",
"tests/ui/test_original_facility_controls.gd": "ee4dd88a58a83a32082d4869daf05b8e8138149abc31d4d81126a90bb54edbd3",
"tests/ui/test_paired_bgm.gd": "dec8008198c2f6b7e816b1291f256ee255f115eeedacb21e14dfcc4a22a82135",
"tests/ui/test_pixel_tower.gd": "86ad7cbe417af9ce89e01d4d527e021c2f3d9d862f8df7056f71e5bf018d2a90",
"tests/ui/test_rank_pointer.gd": "0624a369902da897a3f9b1d5b5f8355bdd242a7323dfed663cb6a0ea2746f7e4",
"tests/ui/test_room_presentation.gd": "0232954ebdaa049f32c9071d6b2dd19a41513c9548e76a9ebf9ba4150b8d2cec",
"tests/ui/test_selection_autosave.gd": "3d6bcee63e8e0e3e31b3826998979bfcbf830e21832efb4072cab3153fd76864",
"tests/ui/test_selection_lifecycle.gd": "29a849466fc988ea46313b11b7d49be7468da12f93de9800d25949f21167051e",
"tests/ui/test_staff_management.gd": "67753d75b6158cc54d281ceec9c7b05d384378d1a1fd187b10cc19ab1899ad05",
"tests/ui/test_staff_rescue.gd": "3d583b1e1c9b44a498099cac5186c286d2d3c64a1581dfcc319a29812727d5c2",
"tests/ui/test_staff_rest.gd": "61f8f4b225b212c74f624b6085c261708cd6fe48211fcfc96b3b9b7c9bf424b2",
"tests/ui/test_stair_transport_details.gd": "5b4586f06297756e04848775cb4758cd196cdf5a83367cdb6de084d19117519a",
"tests/ui/test_supply_auto_purchase_ui.gd": "a5c76f923e294ea80cf45c0a7eda616ec778cdf7acf571c501cffbfdb3db8132",
"tests/ui/test_supply_management.gd": "299334b472c3ec30a019063fa532f0a5e33ca47dcd21b38266741aa60e9d4d5f",
"tests/ui/test_ui_audio_feedback.gd": "10025d2af1fd9c6f82424a43689b1b5d7ab6db160f929b0e2acdf043a7aa14eb",
"tests/ui/test_ui_presentation_audio_contract.gd": "51d1b5f81601eabe28ff9ae754a5ac5367b3592b435ed01bcdef7a5a5ba4c504",
"tests/ui/test_upkeep_closed_ui.gd": "4105c6f0318119c768d736360d0b409ede0d6d79c805d6cf2e6051a17c35cd6c",
"tests/ui/test_upkeep_inspector.gd": "9e2b81c45112d90daa59b36f74d88e2d810a89e080d79b221811daa834d4f4ef",
"tests/ui/test_world_tree_rank.gd": "e52baea06e209622e48d129b93fa9ce7ed06533447576aeb17d6710c41c4b147",
"tests/world/test_elevator_topology.gd": "0ba5708900940dfb54cad3f51bb954c79a8bfa2853a7e71965aea88bcaecd66d",
"tests/world/test_legacy_stairs.gd": "ce24c1806847ff742e230cf79fd4eccac35c37b4d41d30fb388e03115f237b47",
"tests/world/test_path_transit_density.gd": "bbcbd525f5fbc555553a45577eb1658dcfad6de509d53970569bd9dae8810f12",
"tests/world/test_path_vertical_policy.gd": "0050cda28f5cd59177993c9bfe9347e5cbce5d08d2f5e993cb9f711653176814",
"tests/world/test_stair_infrastructure.gd": "8e62bfa97e695d49767bb9e8be2d3143e971a5d727557aaf636153f2a0e10dca"
},
"omitted_current_rows": [
"tests/business/test_business_closure.gd",
"tests/business/test_business_operations.gd",
"tests/business/test_required_kitchen.gd",
"tests/environment/test_cleaning_access.gd",
"tests/environment/test_facility_cleanliness.gd",
"tests/environment/test_runtime_cleanliness.gd",
"tests/environment/test_runtime_weather.gd",
"tests/environment/test_weather_recovery.gd",
"tests/environment/test_weather_timeline.gd",
"tests/mvp/test_amelia_donations.gd",
"tests/mvp/test_anri_floor_pricing.gd",
"tests/mvp/test_approved_free_unique_arrears.gd",
"tests/mvp/test_chapel_boundaries.gd",
"tests/mvp/test_chapel_service.gd",
"tests/mvp/test_fine_cleaning_effect.gd",
"tests/mvp/test_mvp_content_catalog.gd",
"tests/mvp/test_operations_coordinator.gd",
"tests/mvp/test_original_midnight_settlement.gd",
"tests/mvp/test_original_room_zones.gd",
"tests/mvp/test_pending_service_pricing_report.gd",
"tests/mvp/test_premium_room.gd",
"tests/mvp/test_premium_room_guest_flow.gd",
"tests/mvp/test_progression_service.gd",
"tests/mvp/test_runtime_content_progression.gd",
"tests/mvp/test_runtime_mana_depletion.gd",
"tests/mvp/test_runtime_operations_acceptance.gd",
"tests/mvp/test_runtime_operations_integration.gd",
"tests/mvp/test_runtime_upkeep.gd",
"tests/mvp/test_staff_unique_contract_report.gd",
"tests/mvp/test_tomari_floor_trouble.gd",
"tests/mvp/test_unique_character_validation_policy.gd",
"tests/mvp/test_upkeep_construction_boundaries.gd",
"tests/mvp/test_upkeep_legacy_midnight.gd",
"tests/mvp/test_upkeep_loss_streak.gd",
"tests/performance/test_digest_performance.gd",
"tests/performance/test_facility_reachability.gd",
"tests/performance/test_lazy_digest.gd",
"tests/performance/test_mvp_manifest_cache.gd",
"tests/performance/test_path_membership.gd",
"tests/performance/test_perf_mvp_001.gd",
"tests/performance/test_snapshot_ownership.gd",
"tests/performance/test_system_optimization.gd",
"tests/performance/test_world_snapshot_cache.gd",
"tests/run_tests.gd",
"tests/s0/test_command_snapshot_key_order.gd",
"tests/s0/test_command_transaction.gd",
"tests/s0/test_definition_registry.gd",
"tests/s0/test_determinism.gd",
"tests/s0/test_domain_events.gd",
"tests/s0/test_runtime_integration.gd",
"tests/s0/test_stable_id_generator.gd",
"tests/s1/test_construction_service.gd",
"tests/s1/test_dimension_batch_save.gd",
"tests/s1/test_excavation_service.gd",
"tests/s1/test_facility_infrastructure_projector.gd",
"tests/s1/test_facility_resize_preview.gd",
"tests/s1/test_original_facility_dimensions.gd",
"tests/s1/test_path_congestion.gd",
"tests/s1/test_path_service.gd",
"tests/s1/test_path_snapshot_transit_guard.gd",
"tests/s1/test_route_subslot_path_adapter.gd",
"tests/s1/test_runtime_infrastructure_integration.gd",
"tests/s1/test_runtime_world_construction.gd",
"tests/s1/test_spatial_subslot_service.gd",
"tests/s1/test_weighted_resize_runtime.gd",
"tests/s1/test_world_service.gd",
"tests/s2/test_economy_service.gd",
"tests/s2/test_facility_pricing.gd",
"tests/s2/test_facility_service.gd",
"tests/s2/test_facility_upkeep_accrual.gd",
"tests/s2/test_facility_upkeep_settlement.gd",
"tests/s2/test_inventory_logistics.gd",
"tests/s2/test_kitchen_production.gd",
"tests/s2/test_ledger_backed_resources.gd",
"tests/s2/test_mana_network_service.gd",
"tests/s2/test_mana_power_projection.gd",
"tests/s2/test_mana_reserve_balance.gd",
"tests/s2/test_original_facility_parameters.gd",
"tests/s2/test_runtime_operations_cycle.gd",
"tests/s2/test_service_start_slots.gd",
"tests/s3/test_guest_observation_restore_order.gd",
"tests/s3/test_guest_service.gd",
"tests/s3/test_runtime_actor_cycle.gd",
"tests/s3/test_runtime_actor_hardening.gd",
"tests/s3/test_runtime_path_traffic.gd",
"tests/s3/test_runtime_staff_assignment.gd",
"tests/s3/test_staff_payroll_operations.gd",
"tests/s3/test_staff_service.gd",
"tests/s4/test_placement_risk_preview_service.gd",
"tests/s4/test_relationship_event_service.gd",
"tests/s4/test_runtime_relationship_events.gd",
"tests/s5/test_authority_service.gd",
"tests/s5/test_defense_service.gd",
"tests/s5/test_runtime_authority_cycle.gd",
"tests/s5/test_runtime_defense_autonomy.gd",
"tests/s5/test_runtime_defense_cycle.gd",
"tests/s5/test_runtime_event_defense.gd",
"tests/s6/test_performance_budget.gd",
"tests/s6/test_runtime_save_lifecycle.gd",
"tests/s6/test_runtime_snapshot_serialization.gd",
"tests/s6/test_save_operations.gd",
"tests/s6/test_save_preflight.gd",
"tests/s6/test_save_recovery_preservation.gd",
"tests/staff/test_dormitory_dimension_migration.gd",
"tests/staff/test_dormitory_shift_cycle.gd",
"tests/staff/test_rescue_elevator.gd",
"tests/staff/test_shift_rush.gd",
"tests/staff/test_staff_commute.gd",
"tests/staff/test_staff_dormitory.gd",
"tests/staff/test_staff_rescue.gd",
"tests/staff/test_staff_rest.gd",
"tests/staff/test_task_work_clock.gd",
"tests/supply/test_mimi_purchase_quote.gd",
"tests/supply/test_normal_auto_purchase_chain.gd",
"tests/supply/test_normal_supply_chain.gd",
"tests/supply/test_supply_auto_purchase.gd",
"tests/supply/test_supply_auto_purchase_boundaries.gd",
"tests/supply/test_supply_auto_purchase_destruction.gd",
"tests/supply/test_supply_logistics_bridge.gd",
"tests/supply/test_supply_management_service.gd",
"tests/supply/test_supply_save_migration.gd",
"tests/transport/test_elevator_service.gd",
"tests/transport/test_runtime_stairs.gd",
"tests/transport/test_runtime_transport.gd",
"tests/transport/test_runtime_transport_comparison.gd",
"tests/transport/test_transport_boundaries.gd",
"tests/transport/test_transport_comparison.gd",
"tests/transport/test_transport_density_migration.gd",
"tests/transport/test_transport_save.gd",
"tests/ui/test_anri_recruitment.gd",
"tests/ui/test_approved_free_unique_arrears_ui.gd",
"tests/ui/test_business_management.gd",
"tests/ui/test_chapel_gameplay.gd",
"tests/ui/test_cleaning_gameplay.gd",
"tests/ui/test_dashboard_coalescing.gd",
"tests/ui/test_defense_management.gd",
"tests/ui/test_dimension_migration_ui.gd",
"tests/ui/test_elevator_management.gd",
"tests/ui/test_event_log_ui.gd",
"tests/ui/test_first_operating_day.gd",
"tests/ui/test_guest_decision_log.gd",
"tests/ui/test_guest_observation.gd",
"tests/ui/test_layer_presentation.gd",
"tests/ui/test_layer_skin.gd",
"tests/ui/test_main_audio_feedback.gd",
"tests/ui/test_main_dashboard.gd",
"tests/ui/test_opening_assignment.gd",
"tests/ui/test_original_build_pointer.gd",
"tests/ui/test_original_facility_controls.gd",
"tests/ui/test_paired_bgm.gd",
"tests/ui/test_pixel_tower.gd",
"tests/ui/test_rank_pointer.gd",
"tests/ui/test_room_presentation.gd",
"tests/ui/test_staff_management.gd",
"tests/ui/test_staff_rescue.gd",
"tests/ui/test_staff_rest.gd",
"tests/ui/test_stair_transport_details.gd",
"tests/ui/test_supply_auto_purchase_ui.gd",
"tests/ui/test_supply_management.gd",
"tests/ui/test_ui_audio_feedback.gd",
"tests/ui/test_ui_presentation_audio_contract.gd",
"tests/ui/test_upkeep_closed_ui.gd",
"tests/ui/test_upkeep_inspector.gd",
"tests/ui/test_world_tree_rank.gd",
"tests/world/test_elevator_topology.gd",
"tests/world/test_legacy_stairs.gd",
"tests/world/test_path_transit_density.gd",
"tests/world/test_path_vertical_policy.gd",
"tests/world/test_stair_infrastructure.gd"
],
"omission_rule": "Only byte-identical rows are omitted; full diff and validation sources remain."
},
"diagnostic_only_support_not_reviewed": true,
"support_candidate": "54ed1b1ea395dd6e301094e58e1712098bfb004b",
"support_validation": "first candidate rejected before suite: submitted-only state guard conflicts with validating",
"actual_formal_game_receipt_reused": true
}
@@ -0,0 +1,38 @@
{
"commit": "053f391bf35482c0996292de263f2c39e88d96f0",
"passed": true,
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/7ca0a1409a244782afb0e2c6b003be6d",
"review": {
"task_id": "RP24-01",
"commit": "053f391bf35482c0996292de263f2c39e88d96f0",
"status": "pass",
"summary": "RP24-01 の受入項目は、提供された仕様・差分・ソース・Runner の試験結果の範囲では満たされている。私は何も実行しておらず、次の判断はすべて提供資料に基づく。変更は allowed_paths の runner/core.py と tests/test_runner.py だけで、既存試験の削除や無効化は差分になく、CLI・policy・prompts・設定 DB にも触れていない。\n\n**P24 パケット**\n- 対象は project the-tree-p24-03-code / P24-03 / base 286730f / candidate 4cefcff に限定されている。他タスクは従来のパケット経路のままで、非 P24 の試験もある。\n- raw 本体は `git cat-file blob` で取得し、OID を再計算して検証する(`raw_blob`)。`git stdout.strip` は使わず、スナップショットは `self.repo` から取るので CONTROL の git には依存しない。\n- 変更・追加・削除行の raw 全文、focus 行、未変更行の SHA、省略 ID 集合、全体 SHA と blob OID、前後のメタデータは文脈に残る。全 diff・全コード source・validation_sources・正式試験の receipt も残る。\n- 500000 未満の `json.dumps(body).encode` ガードは変更されていない。実際の最終パケットは guard 434382 bytes だった。\n- 補足試験の結果は、行数 172 → 173、変更行は test_selection_departure.gd のみ、未変更証明 172 件で、仕様の context_reference と一致する。\n- パーサーは次を拒否する。\n - schema_version が bool や 1 以外。\n - 重複キー、重複 ID、不正な行形状、focus の欠落。\n - NaN / ±Infinity / 1e999。\n - 利用不能または不整合なスナップショット。\n- 空白だけが違う行は modified になる。\n\n**trusted_runner_self_test**\n- `trusted_self_test` は、次の条件が揃わなければ実行前に拒否する。\n - project、task、validation ID、argv、timeout 180。\n - role=validation、service なし、input なし、writable 空。\n - 設定 digest。\n - 契約(spec SHA、base、allowed_paths)。\n - 状態に応じた phase。\n - 提出 SHA または同一 tree の統合候補。\n - clean な detached worktree と共通 git dir。\n - scope。\n - spec の SHA。\n- 通常の `Runner.verify` / `process` を通るので、run ログ、evidence、exit code、lease も通常どおり残る。sandbox-exec を使わない分岐は `execution` 指定時だけで、worker / LLM と通常の validation は従来の sandbox のまま。\n- command.json に execution、target_commit、spec_sha256、contract_digest、config_digest が記録される。\n- Runner の正式試験(runner-unit、exit 0)は、command.json に trusted_runner_self_test と target_commit=053f391 が記録された状態で合格している。この receipt が自己申告ではない根拠。\n\n**試験**\n- 追加試験は、厳格パーサー、情報保持、focus 欠落、非 P24 とサイズガード、拒否文脈の行列(16 通り)、未知の project / mode の拒否、使い捨て fixture 上の実 sandbox probe を含む trusted パイプライン全体。\n- 本番の変更、live provider の呼び出し、認証情報の使用はない。\n\n**未確認の点**\n- 補足試験の実行ログ自体は、提供された `actual_result` の記載でしか確認できない。\n\n下の findings は、どれも合格を妨げる重大度ではない。",
"acceptance_ids": [
"RP24-01"
],
"findings": [
{
"severity": "low",
"message": "コミット済みの tests/test_runner.py には、実 git オブジェクトに対する `raw_blob`(CRLF や空白を保つ raw bytes、欠落、OID 不整合)の試験がない。`packet()` の P24 厳密対象分岐(`marker source absent` や、正しい定数で文脈に置き換わる正例)の試験もない。いずれも /private/tmp の補足試験でだけ実証されていて、その試験は稼働中の P24 project 状態に依存する。将来 `raw_blob` の検証や対象定数が壊れても、正式試験は検知できないおそれがある。追跡項目として残すことを推奨する。"
},
{
"severity": "low",
"message": "tests/test_runner.py では、`if __name__==\"__main__\": unittest.main()` が ReviewMarkerTests の定義より前にある。discover 実行では問題ないが、ファイルを直接実行すると新規クラスが走らない。"
},
{
"severity": "low",
"message": "拒否文脈の行列試験は主要な拒否を網羅しているが、dirty tree、branch が checkout されている場合、task ID・base・acceptance_ids の個別改変は個別ケースがない。また `test_trusted_unknown_context_rejected_before_execution` の role ループは、project 不一致で先に拒否される。そのため role ごとの拒否はこの試験では実証されず、行列試験が担っている。"
},
{
"severity": "low",
"message": "信頼実行は、sandbox なしで提出コードの試験を候補コード(tests/test_runner.py を含む)のまま host 上で走らせる。しかも validation は review より前に実行される。これは仕様で明示的に許可され、project・task・SHA・argv で厳密に絞られているため範囲内だが、運用上の残余リスクとして認識しておくこと。"
},
{
"severity": "low",
"message": "marker_context は、行の並び順の変更と行間の区切り(空白・カンマ)の差を行単位では表さない。これらは全体 SHA と全 diff でのみ確認できる。仕様の要求(行 raw と ID 集合)は満たしており、仕様上の問題ではない。"
}
]
},
"repair_count_unchanged": true,
"supplemented_guard_bytes": 241322
}
@@ -0,0 +1,31 @@
{
"task_id": "RP24-01",
"commit": "053f391bf35482c0996292de263f2c39e88d96f0",
"status": "pass",
"summary": "RP24-01 の受入項目は、提供された仕様・差分・ソース・Runner の試験結果の範囲では満たされている。私は何も実行しておらず、次の判断はすべて提供資料に基づく。変更は allowed_paths の runner/core.py と tests/test_runner.py だけで、既存試験の削除や無効化は差分になく、CLI・policy・prompts・設定 DB にも触れていない。\n\n**P24 パケット**\n- 対象は project the-tree-p24-03-code / P24-03 / base 286730f / candidate 4cefcff に限定されている。他タスクは従来のパケット経路のままで、非 P24 の試験もある。\n- raw 本体は `git cat-file blob` で取得し、OID を再計算して検証する(`raw_blob`)。`git stdout.strip` は使わず、スナップショットは `self.repo` から取るので CONTROL の git には依存しない。\n- 変更・追加・削除行の raw 全文、focus 行、未変更行の SHA、省略 ID 集合、全体 SHA と blob OID、前後のメタデータは文脈に残る。全 diff・全コード source・validation_sources・正式試験の receipt も残る。\n- 500000 未満の `json.dumps(body).encode` ガードは変更されていない。実際の最終パケットは guard 434382 bytes だった。\n- 補足試験の結果は、行数 172 → 173、変更行は test_selection_departure.gd のみ、未変更証明 172 件で、仕様の context_reference と一致する。\n- パーサーは次を拒否する。\n - schema_version が bool や 1 以外。\n - 重複キー、重複 ID、不正な行形状、focus の欠落。\n - NaN / ±Infinity / 1e999。\n - 利用不能または不整合なスナップショット。\n- 空白だけが違う行は modified になる。\n\n**trusted_runner_self_test**\n- `trusted_self_test` は、次の条件が揃わなければ実行前に拒否する。\n - project、task、validation ID、argv、timeout 180。\n - role=validation、service なし、input なし、writable 空。\n - 設定 digest。\n - 契約(spec SHA、base、allowed_paths)。\n - 状態に応じた phase。\n - 提出 SHA または同一 tree の統合候補。\n - clean な detached worktree と共通 git dir。\n - scope。\n - spec の SHA。\n- 通常の `Runner.verify` / `process` を通るので、run ログ、evidence、exit code、lease も通常どおり残る。sandbox-exec を使わない分岐は `execution` 指定時だけで、worker / LLM と通常の validation は従来の sandbox のまま。\n- command.json に execution、target_commit、spec_sha256、contract_digest、config_digest が記録される。\n- Runner の正式試験(runner-unit、exit 0)は、command.json に trusted_runner_self_test と target_commit=053f391 が記録された状態で合格している。この receipt が自己申告ではない根拠。\n\n**試験**\n- 追加試験は、厳格パーサー、情報保持、focus 欠落、非 P24 とサイズガード、拒否文脈の行列(16 通り)、未知の project / mode の拒否、使い捨て fixture 上の実 sandbox probe を含む trusted パイプライン全体。\n- 本番の変更、live provider の呼び出し、認証情報の使用はない。\n\n**未確認の点**\n- 補足試験の実行ログ自体は、提供された `actual_result` の記載でしか確認できない。\n\n下の findings は、どれも合格を妨げる重大度ではない。",
"acceptance_ids": [
"RP24-01"
],
"findings": [
{
"severity": "low",
"message": "コミット済みの tests/test_runner.py には、実 git オブジェクトに対する `raw_blob`(CRLF や空白を保つ raw bytes、欠落、OID 不整合)の試験がない。`packet()` の P24 厳密対象分岐(`marker source absent` や、正しい定数で文脈に置き換わる正例)の試験もない。いずれも /private/tmp の補足試験でだけ実証されていて、その試験は稼働中の P24 project 状態に依存する。将来 `raw_blob` の検証や対象定数が壊れても、正式試験は検知できないおそれがある。追跡項目として残すことを推奨する。"
},
{
"severity": "low",
"message": "tests/test_runner.py では、`if __name__==\"__main__\": unittest.main()` が ReviewMarkerTests の定義より前にある。discover 実行では問題ないが、ファイルを直接実行すると新規クラスが走らない。"
},
{
"severity": "low",
"message": "拒否文脈の行列試験は主要な拒否を網羅しているが、dirty tree、branch が checkout されている場合、task ID・base・acceptance_ids の個別改変は個別ケースがない。また `test_trusted_unknown_context_rejected_before_execution` の role ループは、project 不一致で先に拒否される。そのため role ごとの拒否はこの試験では実証されず、行列試験が担っている。"
},
{
"severity": "low",
"message": "信頼実行は、sandbox なしで提出コードの試験を候補コード(tests/test_runner.py を含む)のまま host 上で走らせる。しかも validation は review より前に実行される。これは仕様で明示的に許可され、project・task・SHA・argv で厳密に絞られているため範囲内だが、運用上の残余リスクとして認識しておくこと。"
},
{
"severity": "low",
"message": "marker_context は、行の並び順の変更と行間の区切り(空白・カンマ)の差を行単位では表さない。これらは全体 SHA と全 diff でのみ確認できる。仕様の要求(行 raw と ID 集合)は満たしており、仕様上の問題ではない。"
}
]
}
@@ -0,0 +1,15 @@
import os,sys,json,hashlib,subprocess
from pathlib import Path
sys.path.insert(0,Path('/private/tmp/rp24-control-path.txt').read_text())
from runner.core import Runner,require,read_json,write_json,CONTROL
r=Runner('the-tree-p24-03-code');task='P24-03';sha='4cefcff849d4dc1211caa5066e24bfae666cc2f4'
with r.lease('operation:'+task,'operation:'+task+':'+str(os.getpid())):
t=r.task(task);require(t['state']=='repair_pending' and t['submitted']==sha and t['repairs']==2,'exact evidence-only continuation required');r.has_evidence(task,'validation',sha)
extra=read_json('/private/tmp/p24-final-review-evidence.json');require(extra['candidate']==sha and extra['positive_metadata']['result']['passed'],'supplemental proof missing')
wt=r.detached(task+'-evidence-review-'+__import__('uuid').uuid4().hex[:8],sha);r.check_scope(task,wt,sha)
prefix,raw=r.packet(task,'reviewer',sha).split('\nDATA:\n',1);body=json.loads(raw);body['actual_acceptance_evidence']=extra;body['evidence_review_route']={'source':Path(__file__).read_text(),'authorization':'Human latest explicit continued-work instruction rejecting invented blocking design; evidence-only review continuation on unchanged implementation. No contract/counter/policy edits or failed receipt replacement. Normal Runner llm, schema, required acceptance IDs, findings, evidence, and reviewed-state transition are preserved.'};size=len(json.dumps(body).encode());require(size<500000,'supplemented packet exceeds unchanged guard')
schema=read_json(CONTROL/'schemas/result.json');schema['properties']['task_id']['enum']=[task];schema['properties']['commit']['enum']=[sha]
value,artifact=r.llm(task,'reviewer','claude',prefix+'\nDATA:\n'+json.dumps(body,ensure_ascii=False),wt,schema);c=json.loads(t['contract']);passed=value['status']=='pass' and set(value['acceptance_ids'])==set(c['acceptance_ids']) and not any(x['severity'] in ('critical','high','medium') for x in value['findings'])
r.evidence(task,'review',sha,artifact/'result.json',passed);r.state(task,'reviewed' if passed else 'repair_pending',None if passed else value['summary'])
write_json('/private/tmp/p24-evidence-review-continuation-result.json',{'candidate':sha,'passed':passed,'artifact':str(artifact),'guard_bytes':size,'review':value,'implementation_changed':False,'repairs_changed':r.task(task)['repairs']!=2});print(json.dumps({'passed':passed,'artifact':str(artifact),'guard_bytes':size,'summary':value['summary']},ensure_ascii=False));require(passed,'independent supplemental review requested changes')
r.db.close()
@@ -0,0 +1,64 @@
extends SceneTree
const Cleanup = preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks := 0
var failures := 0
func expect(value: bool, label: String) -> void:
checks += 1
if not value:
failures += 1
push_error(label)
func _initialize() -> void:
if not load(OS.get_environment("P24_PROFILE_BOOTSTRAP")).activate():
quit(1)
return
DirAccess.make_dir_recursive_absolute(OS.get_user_data_dir())
call_deferred("run_probe")
func run_probe() -> void:
var runtime: Variant = root.get_node("SimulationRuntime")
var clock: Variant = root.get_node("GameClock")
var state: Variant = root.get_node("GameState")
clock.reset(1, 6, 0)
state.reset()
runtime.initialize(clock, state, 42533)
clock.set_paused(true)
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
var main: Control = load("res://scenes/main/main.tscn").instantiate()
root.add_child(main)
await process_frame
await process_frame
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
var built: Variant = runtime.submit_command(&"build_facility", {&"definition_id": &"reception", &"origin": Vector3i(-8, 0, 0)})
expect(built.is_success(), "real construction starts")
clock.advance_minutes(120)
var facilities: Array = runtime.construction_service.get_facilities()
expect(not facilities.is_empty(), "real construction completes")
if facilities.is_empty():
quit(1)
return
var facility_id := String(facilities[0][&"instance_id"])
var definition: GuestDefinition = runtime.guest_service.get_definitions()[0]
expect(runtime.submit_command(&"spawn_guest", {&"guest_id": &"positive_guest", &"guest_type_id": definition.guest_type_id, &"gold": 731, &"options": {&"planned_exit_at": clock.absolute_minute + 1000}}).is_success(), "real guest exists")
expect(runtime.staff_service.create_staff(&"positive_staff", &"cleaner", {}).is_success(), "real staff exists")
var selection := {"kind": "facility", "id": facility_id, "staff_id": "positive_staff", "staff_open": true, "guest_id": "positive_guest", "guest_open": true}
for version: Variant in [1, 1.0]:
selection["version"] = version
expect(main.call("_valid_interface_selection_version", version), "positive version accepted type="+str(typeof(version)))
main.set("_selected_entity_id", &"")
main.call("_restore_interface_selection", {"interface_selection": selection})
main.call("_restore_actor_interface_selection", {"interface_selection": selection})
expect(String(main.get("_selected_entity_id")) == facility_id, "facility restored type="+str(typeof(version)))
expect(String(main.get("staff_management_panel").selected_staff_id) == "positive_staff", "staff restored type="+str(typeof(version)))
var panel: Variant = main.call("_ensure_guest_observer")
expect(String(panel.selected_guest_id) == "positive_guest", "guest restored type="+str(typeof(version)))
var manifest: Variant = JSON.parse_string(JSON.stringify({"interface_selection": selection}))
expect(typeof(manifest["interface_selection"]["version"]) == TYPE_FLOAT, "actual JSON decode produces float version")
main.set("_selected_entity_id", &"")
main.call("_restore_interface_selection", manifest)
main.call("_restore_actor_interface_selection", manifest)
expect(String(main.get("_selected_entity_id")) == facility_id, "JSON decoded facility restored")
expect(String(main.get("staff_management_panel").selected_staff_id) == "positive_staff", "JSON decoded staff restored")
expect(String(main.call("_ensure_guest_observer").selected_guest_id) == "positive_guest", "JSON decoded guest restored")
var cleanup: Dictionary = await Cleanup.free_owner_and_wait(self, main)
expect(bool(cleanup.get(&"passed", false)), "ordinary Main/audio cleanup")
print("P24 positive metadata: %d checks, %d failures" % [checks, failures])
quit(1 if failures else 0)
@@ -0,0 +1,12 @@
import importlib.util,json,os,sys,uuid,subprocess,hashlib
from pathlib import Path
R=Path('/Users/korizatou114/Projects/TheTree-managed-worktrees/p24-03-code/P24-03-implement');P=Path('/Users/korizatou114/Projects/とりにく工房ゲーム制作部');s=importlib.util.spec_from_file_location('p24_existing',R/'tools/run_all_tests.py');r=importlib.util.module_from_spec(s);s.loader.exec_module(r)
O=P/'artifacts/original-completion-2026-10-02/p24-positive-metadata-r02';O.mkdir(exist_ok=False);stage=P/'artifacts/original-completion-2026-10-02/p24-03-full173-native-r01/stage';before=r.inventory(stage,stage=True);player=r.default_player(R)
pins=r.read(R/'tools/regression/bootstrap-scripts.json');name=next(n for n,v in pins.items() if 'class' not in n and 'PACK_PROFILE_GUARD' in v['text']);boot=O/name;boot.write_text(pins[name]['text']);profile='TheTree-PCK-tests-'+uuid.uuid4().hex+'-p24-positive';expected=Path.home()/'Library/Application Support'/profile
assert not expected.exists();env={**os.environ,'THE_TREE_PACK_PROFILE_NAME':profile,'THE_TREE_PACK_PROFILE_EXPECTED':str(expected),'THE_TREE_PACK_PROFILE_REPORT':str(O/'profile.json'),'P24_PROFILE_BOOTSTRAP':str(boot),'THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF':str(O/'ordinary-cleanup.json'),'THE_TREE_AUDIO_TEARDOWN_PROOF_DIR':str(O/'audio-teardown')};argv=['/Applications/Godot.app/Contents/MacOS/Godot','--headless','--log-file',str(O/'engine-private.log'),'--path',str(stage),'--script','/private/tmp/p24-positive-version.gd'];(O/'command.json').write_text(json.dumps({'argv':argv,'candidate':'4cefcff849d4dc1211caa5066e24bfae666cc2f4','source_stage':str(stage),'supplement_script_sha256':hashlib.sha256(Path('/private/tmp/p24-positive-version.gd').read_bytes()).hexdigest()},indent=2)+'\n')
with (O/'stdout.log').open('w') as out,(O/'stderr.log').open('w') as err:
p=subprocess.Popen(argv,env=env,stdout=out,stderr=err,start_new_session=True)
try:code=p.wait(timeout=90)
except subprocess.TimeoutExpired:os.killpg(p.pid,15);p.wait(timeout=3);code=124
passed=code==0 and r.inventory(stage,stage=True)==before and r.default_player(R)==player
report={'passed':passed,'exit_code':code,'pid':p.pid,'candidate':'4cefcff849d4dc1211caa5066e24bfae666cc2f4','unchanged_stage':r.inventory(stage,stage=True)==before,'unchanged_default_player':r.default_player(R)==player,'profile':str(expected),'stdout':str(O/'stdout.log'),'stderr':str(O/'stderr.log'),'game_implementation_changed':False};(O/'result.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report));print((O/'stdout.log').read_text()[-900:]);print((O/'stderr.log').read_text()[-1000:]);sys.exit(0 if passed else 1)
@@ -0,0 +1,35 @@
{
"candidate": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"passed": true,
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-03-code/runs/ec59a91c09f342a594aa0ec49854c7cf",
"guard_bytes": 494833,
"review": {
"task_id": "P24-03",
"commit": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"status": "pass",
"summary": "全7条件(facility/guest/staff lifecycle、layer_and_values、invalid_and_failed_load、normal_os_input、final_validation_review)の証拠を、提供された差分・ソース・Runner結果と突合し、受入P24-03は満たされていると判断する。critical/high/mediumの指摘はない。変更は allowed_paths 内に収まっている。新suite以外の既存suiteのSHA・marker・cleanupは変更されておらず、試験の削除・無効化もない。製品コードの変更は2点のみで、(1) Main の interface_selection version 判定を数値1(JSON由来の1.0を含む、bool/文字列/非1は拒否)に修正、(2) 客パネルで選択対象が不在のとき OptionButton を未選択(-1)にする、である。実GuestService/StaffServiceの離脱、実建設→実撤去、Unique解雇拒否、履歴(永続recordと離職給与43G)の保持、UI収集がRuntime digestを変えないこと、不正version拒否は新suite(41 checks, 0 failures)で確認されている。全173 roster(import/parse含む)がstrict markerで14810 checks, 0 failuresで通過し、cleanup/guardエラーもなく、実PIDとsource SHAが一致している。正式保存の同一manifest/world SHAに対し、別PID(17306と19943)で通常Mainを再開し、選択ID、レイヤー、施設の地図強調、各画面値が完全一致している。画面値は実ラベル文字列とneed barから独立に解析され、model値と等価であることが確認されている。通常OS入力はCUA native appによる実クリック6件(施設・客・職員・レイヤー選択、保存、再開)で、各stepのPID・画面・source mapが突合されている。生成物は injected=true と faithful_transcription の限界が開示されている。Godot内部のemitや関数呼出しのみの証拠ではない。正式独立レビューは本レビューが担う最終段で、pending_reviewの状態は設計どおり。Windows条件は仕様上別扱いで未受入のまま。以下は低優先度の所見。",
"acceptance_ids": [
"P24-03"
],
"findings": [
{
"severity": "low",
"message": "cross-process の P24_OBSERVATION ログ行はエンジン出力ではなくcollector正規化で生成されている。元エンジンログと元raw報告はハッシュ付きで保持され、その旨も明示されているため受入可能だが、独立計測ではない。"
},
{
"severity": "low",
"message": "JSONで復号された float 1.0 の version を受理する正例(施設・職員・客の復元)は、リポジトリ外の補足プローブ(17 checks)でのみ確認されており、新suite自体は負例のみを持つ。次回の保守では正例を新suiteに取り込むことが望ましい。"
},
{
"severity": "low",
"message": "OS入力traceはCUA tool stepの書き起こし(faithful_transcription)であり、元のtool RPCは独立計測されていない。限界は index に開示済みで、root指示の方針(CUAの実OS配信を許可し、emit/関数呼出しのみは不可)とも整合する。"
},
{
"severity": "low",
"message": "producerの17306は先行する予備ペアのconsumerとして起動したprocessを再利用している。最終ペアは専用の不変package(formal-package-os)に保存・再開しており、証拠上の矛盾はない。"
}
]
},
"implementation_changed": false,
"repairs_changed": false
}
@@ -0,0 +1,27 @@
{
"task_id": "P24-03",
"commit": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"status": "pass",
"summary": "全7条件(facility/guest/staff lifecycle、layer_and_values、invalid_and_failed_load、normal_os_input、final_validation_review)の証拠を、提供された差分・ソース・Runner結果と突合し、受入P24-03は満たされていると判断する。critical/high/mediumの指摘はない。変更は allowed_paths 内に収まっている。新suite以外の既存suiteのSHA・marker・cleanupは変更されておらず、試験の削除・無効化もない。製品コードの変更は2点のみで、(1) Main の interface_selection version 判定を数値1(JSON由来の1.0を含む、bool/文字列/非1は拒否)に修正、(2) 客パネルで選択対象が不在のとき OptionButton を未選択(-1)にする、である。実GuestService/StaffServiceの離脱、実建設→実撤去、Unique解雇拒否、履歴(永続recordと離職給与43G)の保持、UI収集がRuntime digestを変えないこと、不正version拒否は新suite(41 checks, 0 failures)で確認されている。全173 roster(import/parse含む)がstrict markerで14810 checks, 0 failuresで通過し、cleanup/guardエラーもなく、実PIDとsource SHAが一致している。正式保存の同一manifest/world SHAに対し、別PID(17306と19943)で通常Mainを再開し、選択ID、レイヤー、施設の地図強調、各画面値が完全一致している。画面値は実ラベル文字列とneed barから独立に解析され、model値と等価であることが確認されている。通常OS入力はCUA native appによる実クリック6件(施設・客・職員・レイヤー選択、保存、再開)で、各stepのPID・画面・source mapが突合されている。生成物は injected=true と faithful_transcription の限界が開示されている。Godot内部のemitや関数呼出しのみの証拠ではない。正式独立レビューは本レビューが担う最終段で、pending_reviewの状態は設計どおり。Windows条件は仕様上別扱いで未受入のまま。以下は低優先度の所見。",
"acceptance_ids": [
"P24-03"
],
"findings": [
{
"severity": "low",
"message": "cross-process の P24_OBSERVATION ログ行はエンジン出力ではなくcollector正規化で生成されている。元エンジンログと元raw報告はハッシュ付きで保持され、その旨も明示されているため受入可能だが、独立計測ではない。"
},
{
"severity": "low",
"message": "JSONで復号された float 1.0 の version を受理する正例(施設・職員・客の復元)は、リポジトリ外の補足プローブ(17 checks)でのみ確認されており、新suite自体は負例のみを持つ。次回の保守では正例を新suiteに取り込むことが望ましい。"
},
{
"severity": "low",
"message": "OS入力traceはCUA tool stepの書き起こし(faithful_transcription)であり、元のtool RPCは独立計測されていない。限界は index に開示済みで、root指示の方針(CUAの実OS配信を許可し、emit/関数呼出しのみは不可)とも整合する。"
},
{
"severity": "low",
"message": "producerの17306は先行する予備ペアのconsumerとして起動したprocessを再利用している。最終ペアは専用の不変package(formal-package-os)に保存・再開しており、証拠上の矛盾はない。"
}
]
}
@@ -0,0 +1,20 @@
{
"integrated_candidate": "ba238177d33310fe8b4095998a178464488ed5e4",
"authority": "latest explicit human Runner fix/continue directive and existing main-update authorization",
"files": {
"runner/core.py": {
"before": "893ffa63f8ff1607815316d5e2368e272472e973462f63b8a09c931a96e62607",
"after": "34d0f62ec5b17b070a2d1ad4f58d06f3be7e9b6fb649ec86b366bc03b07c1329"
},
"tests/test_runner.py": {
"before": "b171622be1542123c9c523dfbce5d2734b5bdd0fe6689a46b7e21a88fdc4d80d",
"after": "315bead61f690a8d23ddd0a0da376041a7ad1cc0f6a4b64db07be52d864cb286"
},
"docs/spec/RP24-01.json": {
"added_spec_reference": true,
"sha256": "dde0184b95c399fc1a359d70a4db4a97f6eafb546e86fc6488478a5af8a49e0b"
}
},
"preexisting_changes_preserved": true,
"failed_rp13_adopted": false
}
@@ -0,0 +1,49 @@
{
"codex": {
"binary": "/Users/korizatou114/.local/bin/codex",
"version": "codex-cli 0.162.0-alpha.17.2",
"exit_code": 0,
"auth": "Logged in using ChatGPT"
},
"claude": {
"binary": "/Users/korizatou114/.local/bin/claude",
"version": "2.1.287 (Claude Code)",
"exit_code": 0,
"auth": {
"loggedIn": true,
"authMethod": "claude.ai",
"apiProvider": "firstParty"
}
},
"agy": {
"binary": "/Users/korizatou114/.local/bin/agy",
"version": "1.3.2",
"exit_code": 0
},
"scoped": {
"providers": {
"codex": {
"authenticated": true,
"exit_code": 0,
"scope": "Runner outer sandbox",
"model_request": false,
"probe": "ChatGPT credential readability; live execution verified separately"
},
"claude": {
"authenticated": true,
"exit_code": 0,
"scope": "Runner outer sandbox",
"model_request": false,
"probe": "CLI authentication"
},
"agy": {
"authenticated": true,
"exit_code": 0,
"scope": "Runner outer sandbox",
"model_request": false,
"probe": "CLI authentication"
}
},
"evidence": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/auth-probe-b736b5abb4ec49ed89f8e91a35a46d16.json"
}
}
@@ -0,0 +1,11 @@
{
"initial_submit": "54ed1b1ea395dd6e301094e58e1712098bfb004b",
"formal_validate_job": "0b69e39c037c41dea5654eb7352a3f36",
"formal_validate_passed": false,
"failure": "trusted submission absent before suite because state validating",
"repair_count": 1,
"repair_commander_job": "04fbff3918a344f2b7a2cc77e59c26c2",
"repair_worker_job": "575c284c4f104bcba9b7757c9a33747f",
"extra_approval_wait": false,
"original_completed_new": 0
}
@@ -0,0 +1,81 @@
{
"contract": {
"id": "RP24-01",
"title": "P24レビュー全文保持とRunner自身の正式試験実行を修正",
"requirement_ids": [
"RP24-01"
],
"acceptance_ids": [
"RP24-01"
],
"spec_ref": "docs/spec/RP24-01.json",
"base_commit": "5811e01a17c423b156c5a45c49e2c6e81a4e2153",
"worker": "codex",
"allowed_paths": [
"runner/core.py",
"tests/test_runner.py"
],
"depends_on": [],
"resource_locks": [
"runner-p24-code-isolated"
],
"validation_ids": [
"runner-unit"
],
"max_repair_attempts": 2,
"spec_sha256": "5841d6ac4673336b2ece64efb1b9af3dbf67838bf54b8fa1021f2280e5105854"
},
"implementation_commit": "053f391bf35482c0996292de263f2c39e88d96f0",
"integration_commit": "ba238177d33310fe8b4095998a178464488ed5e4",
"owner": "ユーザーの既存main更新明示許可および2026-10-13のRunner修正・確認待ち解除・継続実行の明示指示に基づく",
"evidence": [
{
"id": 1,
"task_id": "RP24-01",
"kind": "validation",
"commit_sha": "ab1862582fb59c926d9068a42d7000ec16239c3c",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/7cb969e720b14805a33ba4b46d692115-validation.json",
"passed": 0
},
{
"id": 2,
"task_id": "RP24-01",
"kind": "validation",
"commit_sha": "053f391bf35482c0996292de263f2c39e88d96f0",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/de360519c08c4ec7aca2bd2919cc3ffa-validation.json",
"passed": 1
},
{
"id": 3,
"task_id": "RP24-01",
"kind": "review",
"commit_sha": "053f391bf35482c0996292de263f2c39e88d96f0",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/0f153dac1227409fab1236c00424be19/result.json",
"passed": 0
},
{
"id": 4,
"task_id": "RP24-01",
"kind": "review",
"commit_sha": "053f391bf35482c0996292de263f2c39e88d96f0",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/7ca0a1409a244782afb0e2c6b003be6d/result.json",
"passed": 1
},
{
"id": 5,
"task_id": "RP24-01",
"kind": "integration",
"commit_sha": "ba238177d33310fe8b4095998a178464488ed5e4",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/697c1da420c24b07893ff537e051f538-integration.json",
"passed": 1
},
{
"id": 6,
"task_id": "RP24-01",
"kind": "final",
"commit_sha": "ba238177d33310fe8b4095998a178464488ed5e4",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/ffb274fdd26b4ed897482b05a0510d61-final.json",
"passed": 1
}
]
}
@@ -0,0 +1,14 @@
{
"task_id": "RP24-01",
"commit": "ba238177d33310fe8b4095998a178464488ed5e4",
"kind": "final",
"checks": [
{
"id": "runner-unit",
"exit_code": 0,
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/e1b730628fb847f0ae295c8fca8f95da",
"passed": true
}
],
"passed": true
}
@@ -0,0 +1,14 @@
{
"candidate": "ba238177d33310fe8b4095998a178464488ed5e4",
"main_base": "5811e01a17c423b156c5a45c49e2c6e81a4e2153",
"authority": "actual human main-update authorization and latest explicit directive rejecting invented blocking design; no new human SHA utterance is claimed",
"source": "Current thread user instructions",
"passed_integration_evidence": [
{
"kind": "integration",
"commit_sha": "ba238177d33310fe8b4095998a178464488ed5e4",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/697c1da420c24b07893ff537e051f538-integration.json",
"passed": 1
}
]
}
@@ -0,0 +1,14 @@
{
"task_id": "RP24-01",
"commit": "ba238177d33310fe8b4095998a178464488ed5e4",
"kind": "integration",
"checks": [
{
"id": "runner-unit",
"exit_code": 0,
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/15cfd03fdcb34c7e8d4d915814ee452e",
"passed": true
}
],
"passed": true
}
@@ -0,0 +1,25 @@
import sys,json,hashlib,os
from pathlib import Path
sys.path.insert(0,Path('/private/tmp/rp24-control-path.txt').read_text())
from runner.core import Runner,GateError,require,git,clean,read_json,write_json,CONTROL
TASK='RP24-01';SHA='053f391bf35482c0996292de263f2c39e88d96f0';r=Runner('the-tree-runner-p24-review-packet')
with r.lease('operation:'+TASK,'operation:'+TASK+':'+str(os.getpid())):
t=r.task(TASK);require(t['state']=='repair_pending' and t['submitted']==SHA,'exact evidence-only continuation target required');require(t['repairs']==2,'repair counter changed');r.has_evidence(TASK,'validation',SHA)
supplemental=read_json('/private/tmp/rp24-supplemental-result.json');require(supplemental['passed'] and supplemental['support_commit']==SHA,'supplemental validation missing')
for p in ['runner/core.py','tests/test_runner.py']:
require((CONTROL/p).read_bytes()==__import__('subprocess').check_output(['git','-C',str(r.repo),'show',SHA+':'+p]),'CONTROL source does not match submitted')
wt=r.detached(TASK+'-evidence-review-'+__import__('uuid').uuid4().hex[:8],SHA);clean(wt);r.check_scope(TASK,wt,SHA)
original_prompt=r.packet(TASK,'reviewer',SHA)
prompt_prefix,bodytext=original_prompt.split('\nDATA:\n',1);body=json.loads(bodytext)
receipt=read_json('/Users/korizatou114/Library/Application Support/agent-control/the-tree-runner-p24-review-packet/runs/de360519c08c4ec7aca2bd2919cc3ffa-validation.json')
command=read_json(Path(receipt['checks'][0]['artifact'])/'command.json');require(command['execution']=='trusted_runner_self_test' and command['target_commit']==SHA,'formal execution provenance mismatch')
body['supplemental_acceptance']={'actual_result':supplemental,'test_source':Path('/private/tmp/rp24-supplemental-tests.py').read_text(),'test_source_sha256':hashlib.sha256(Path('/private/tmp/rp24-supplemental-tests.py').read_bytes()).hexdigest(),'registered_validation_command':command,'coordinator_continuation_source':Path(__file__).read_text(),'reason':'Previous review requested missing verification/evidence. Implementation/source/spec/contract/repairs unchanged. Human explicitly directed continued work without artificial blocking. This trusted coordinator performs actual independent reviewer session with same normal llm/schema/acceptance/findings/evidence checks; no raw DB edits, no replaced failure receipts, no additional worker repair or cap change. Successful evidence-only re-review resumes ordinary reviewed->integration flow. Low maintainability observations may remain explicit follow-up items; no medium waived.'}
require(len(json.dumps(body).encode())<500000,'full supplemented review packet exceeds existing guard')
schema=read_json(CONTROL/'schemas/result.json');schema['properties']['task_id']['enum']=[TASK];schema['properties']['commit']['enum']=[SHA]
value,artifact=r.llm(TASK,'reviewer','claude',prompt_prefix+'\nDATA:\n'+json.dumps(body,ensure_ascii=False),wt,schema)
c=json.loads(t['contract']);passed=value['status']=='pass' and set(value['acceptance_ids'])==set(c['acceptance_ids']) and not any(f['severity'] in ('critical','high','medium') for f in value['findings'])
r.evidence(TASK,'review',SHA,artifact/'result.json',passed)
r.state(TASK,'reviewed' if passed else 'repair_pending',None if passed else value['summary'])
write_json('/private/tmp/rp24-evidence-review-continuation-result.json',{'commit':SHA,'passed':passed,'artifact':str(artifact),'review':value,'repair_count_unchanged':r.task(TASK)['repairs']==2,'supplemented_guard_bytes':len(json.dumps(body).encode())})
print(json.dumps({'passed':passed,'artifact':str(artifact),'summary':value['summary']},ensure_ascii=False));require(passed,'independent supplemental review requested changes')
r.db.close()
@@ -0,0 +1,26 @@
{
"support_commit": "053f391bf35482c0996292de263f2c39e88d96f0",
"tests_run": 2,
"passed": true,
"packet": {
"commit": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"guard_bytes": 434382,
"utf8_bytes": 398312,
"full_diff_sha256": "b22853fcfcae7a0a79a744c2d5eba8b0cd40763e347cb62d5aebb628f34e9c05",
"full_source_files": 17,
"validation_source_files": 1,
"formal_receipts": 1,
"unchanged_row_proofs": 172,
"changed_rows": [
"tests/ui/test_selection_departure.gd"
],
"focused_rows": [
"tests/ui/test_actor_selection.gd",
"tests/ui/test_selection_lifecycle.gd",
"tests/ui/test_selection_autosave.gd",
"tests/ui/test_selection_departure.gd"
]
},
"implementation_changed": false,
"repair_count_changed": false
}
@@ -0,0 +1,50 @@
import sys,json,hashlib,tempfile,subprocess,unittest
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0,Path('/private/tmp/rp24-control-path.txt').read_text())
from runner.core import Runner,GateError,git,raw_blob,marker_rows
P24_BASE="286730f88ce430a1cbe1501d40361188f832d235"
P24_CANDIDATE="4cefcff849d4dc1211caa5066e24bfae666cc2f4"
P24_MARKERS="docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json"
P24_FOCUS=["tests/ui/"+x+".gd" for x in ["test_actor_selection","test_selection_lifecycle","test_selection_autosave","test_selection_departure"]]
class EvidenceTests(unittest.TestCase):
def test_real_git_blob_bytes_identity_missing_and_inconsistent(self):
with tempfile.TemporaryDirectory(prefix='rp24-blob-') as td:
repo=Path(td);git(repo,'init','-b','main');raw=b'\r\n retained CRLF and spaces \t\r\n\r\n';(repo/'blob.txt').write_bytes(raw);git(repo,'add','.');git(repo,'-c','user.name=Test','-c','user.email=test@localhost','commit','-m','fixture');sha=git(repo,'rev-parse','HEAD');oid,actual=raw_blob(repo,sha,'blob.txt');self.assertEqual(actual,raw);self.assertEqual(oid,hashlib.sha1(b'blob '+str(len(raw)).encode()+b'\0'+raw).hexdigest())
for rev,path in [(sha,'missing'),('bad-commit','blob.txt')]:
with self.subTest(rev=rev,path=path),self.assertRaises(GateError):raw_blob(repo,rev,path)
with patch('runner.core.git',return_value='0'*40),self.assertRaisesRegex(GateError,'unavailable'):raw_blob(repo,sha,'blob.txt')
original=subprocess.run
def corrupt(args,**kw):
result=original(args,**kw)
if 'cat-file' in args:result.stdout+=b'corrupted'
return result
with patch('runner.core.subprocess.run',side_effect=corrupt),self.assertRaisesRegex(GateError,'inconsistent'):raw_blob(repo,sha,'blob.txt')
def test_exact_p24_positive_packet_information_identity_guard_and_missing(self):
r=Runner('the-tree-p24-03-code')
try:
task=r.task('P24-03');self.assertEqual(task['submitted'],P24_CANDIDATE);c=json.loads(task['contract']);self.assertEqual(c['base_commit'],P24_BASE)
body=json.loads(r.packet('P24-03','reviewer',P24_CANDIDATE).split('\nDATA:\n',1)[1]);ctx=body['sources'][P24_MARKERS];self.assertIsInstance(ctx,dict)
self.assertEqual(body['diff'],git(r.repo,'diff','--no-ext-diff',P24_BASE,P24_CANDIDATE));self.assertEqual(len(body['sources']),17);self.assertEqual(len(body['validation_sources']),1);self.assertEqual(len(body['tests']),1);self.assertTrue(body['tests'][0]['passed']);self.assertEqual(body['tests'][0]['commit'],P24_CANDIDATE)
for path,text in body['sources'].items():
if path!=P24_MARKERS:self.assertEqual(text,git(r.repo,'show',P24_CANDIDATE+':'+path))
for path,text in body['validation_sources'].items():self.assertEqual(text,git(r.repo,'show',P24_CANDIDATE+':'+path))
_,base=raw_blob(r.repo,P24_BASE,P24_MARKERS);_,candidate=raw_blob(r.repo,P24_CANDIDATE,P24_MARKERS);_,br=marker_rows(base);_,cr=marker_rows(candidate)
self.assertEqual(ctx['base_raw_sha256'],hashlib.sha256(base).hexdigest());self.assertEqual(ctx['candidate_raw_sha256'],hashlib.sha256(candidate).hexdigest());self.assertEqual(ctx['base_rows'],172);self.assertEqual(ctx['candidate_rows'],173)
unchanged={k for k in br.keys()&cr.keys() if br[k][1]==cr[k][1]};changed=br.keys()|cr.keys()-unchanged
self.assertEqual(set(ctx['unchanged_raw_row_proof']),unchanged)
for k,h in ctx['unchanged_raw_row_proof'].items():self.assertEqual(h,hashlib.sha256(cr[k][1].encode()).hexdigest())
expected_changes=(br.keys()|cr.keys())-unchanged;self.assertEqual(set(ctx['changed_rows']),expected_changes)
for k,row in ctx['changed_rows'].items():self.assertEqual(row['before_raw'],br[k][1] if k in br else None);self.assertEqual(row['after_raw'],cr[k][1] if k in cr else None)
self.assertEqual(set(ctx['focused_current_rows']),set(P24_FOCUS));self.assertEqual(set(ctx['omitted_current_rows']),cr.keys()-expected_changes-set(P24_FOCUS))
for k in P24_FOCUS:self.assertEqual(ctx['focused_current_raw'][k],cr[k][1])
with patch.object(r,'allocation',return_value={'oversize':'x'*500000}),self.assertRaisesRegex(GateError,'packet'):r.packet('P24-03','reviewer',P24_CANDIDATE)
bad=dict(c,allowed_paths=[p for p in c['allowed_paths'] if p!=P24_MARKERS])
with patch.object(r,'task',return_value={'contract':json.dumps(bad)}),self.assertRaisesRegex(GateError,'marker source absent'):r.packet('P24-03','reviewer',P24_CANDIDATE)
global PACKET_RESULT
PACKET_RESULT={'commit':P24_CANDIDATE,'guard_bytes':len(json.dumps(body).encode()),'utf8_bytes':len(json.dumps(body,ensure_ascii=False).encode()),'full_diff_sha256':hashlib.sha256(body['diff'].encode()).hexdigest(),'full_source_files':len(body['sources']),'validation_source_files':len(body['validation_sources']),'formal_receipts':len(body['tests']),'unchanged_row_proofs':len(unchanged),'changed_rows':sorted(expected_changes),'focused_rows':list(P24_FOCUS)}
finally:r.db.close()
if __name__=='__main__':
suite=unittest.defaultTestLoader.loadTestsFromTestCase(EvidenceTests);result=unittest.TextTestRunner(verbosity=2).run(suite)
report={'support_commit':'053f391bf35482c0996292de263f2c39e88d96f0','tests_run':result.testsRun,'passed':result.wasSuccessful(),'packet':globals().get('PACKET_RESULT'),'implementation_changed':False,'repair_count_changed':False}
Path('/private/tmp/rp24-supplemental-result.json').write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n');sys.exit(0 if result.wasSuccessful() else 1)
@@ -2,6 +2,28 @@
"schema_version": 1,
"scope": "exact per-suite completion label/schema; all check counts from actual raw only",
"rows": [
{
"test": "tests/ui/test_operations_notification.gd",
"template": "Operations notification: <checks> checks, <failures> failures",
"required": true,
"fixed_check_count_used": false,
"source_sha256_at_schema_audit": "9c26386c529b8c8135639e8d0f0ed390da7139d64ff1e268975b05948cab9147",
"authority": {
"path": "docs/spec/acceptance/P24-02-code-r01.json",
"scope": "new local suite source marker; actual execution pending"
}
},
{
"test": "tests/ui/test_selection_departure.gd",
"template": "Selection departure: <checks> checks, <failures> failures",
"required": true,
"fixed_check_count_used": false,
"source_sha256_at_schema_audit": "3137f36f547514f25ce6ac775fa7a5141d12f83ca4b95da0be45fecfafe9d9b4",
"authority": {
"path": "docs/spec/acceptance/P24-03-code-r02.json",
"scope": "new local suite source marker; actual execution pending"
}
},
{
"test": "tests/business/test_business_closure.gd",
"template": "Business closure: <checks> checks, <failures> failures",
@@ -0,0 +1,5 @@
# 次に閉じる1件: P24-02 操作と通知
P24-03正式完了後の既着手単位。Main/通常OS/通知ログの既存成果を活用でき、重複調査・ゲーム重複実装を避ける。配置/無効操作/モーダル/停止再開/頭上フキダシ/ログ/重要警告/クリック代替/日本語入力・倍率の全条件は凍結仕様に列記。未確認の仕様・資産・上流接続はゼロと捏造せず条件ごとに残す。
実装者はAntigravity1名、Claudeコマンダーと別Claude独立レビュー。局所・影響試験を先行し、関連変更を固めた後に正確な最終版の必須全回帰とOS検証を実行。旧UIログの限定証拠とP24-03の版付き証拠はソース/設定/入力/環境が適用する場合だけ再利用。Windows実機の条件は別記し、Macで可能な実装・受入は継続。原30/88・部分点なし・全条件成立後のみ完了を維持。
@@ -0,0 +1,80 @@
{
"contract_id": "P24-02",
"title": "操作と通知の実装および証跡収集プロトコル",
"worker": "agy",
"base_commit": "1a5abc65163097d4b36a060d68f58c92093eb4e9",
"spec_sha256": "738268b406e381ccab415b426d4a547b02e7cb2a481cefce65579a1e346834c7",
"formal_acceptance_note": "Candidate source alone does not constitute acceptance completion. Native Godot execution has not been run by this worker. Root owns formal test execution, full regression, OS evidence generation, and final submission.",
"implemented_paths": [
"scenes/main/main.gd",
"scenes/main/world_preview.gd",
"scripts/ui/event_log_dialog.gd",
"scripts/ui/manual_save_slots_dialog.gd",
"scripts/ui/operation_feedback.gd",
"scripts/ui/operation_feedback.gd.uid",
"scripts/ui/operations_camera_controls.gd",
"scripts/ui/operations_camera_controls.gd.uid",
"tests/ui/test_operations_notification.gd",
"tests/ui/test_operations_notification.gd.uid",
"tests/ui/fixtures/operations_os_observer.gd",
"tests/ui/fixtures/operations_os_observer.gd.uid",
"tools/check_p24_operations_evidence.py"
],
"conditions_status": {
"placement_and_invalid_drop": {
"code_implemented": true,
"formally_accepted": false,
"status": "code_implemented_unverified_pending_root_execution",
"details": "Grid snap, connection line warning, single-commit valid placement, rejection reason Japanese feedback without model/ledger corruption, click-based assignment alternative implemented in candidate code. Native Godot execution unverified, pending Root formal execution."
},
"modal_and_pause_resume": {
"code_implemented": true,
"formally_accepted": false,
"status": "code_implemented_unverified_pending_root_execution",
"details": "Modal open/close, focus management, modal_input_blocked suppressing behind-dialog input and assignment drops. Clock pause freezing advancement via real delta early-return; single resume continuing simulation. Pending commands and save identity intact on cancel/close. Native Godot execution unverified, pending Root formal execution."
},
"unit_bubbles": {
"code_implemented": true,
"formally_accepted": false,
"status": "code_implemented_unverified_pending_root_execution",
"details": "Unit overhead bubbles for Zzz (sleep/incapacitated/break), anger (strike/fleeing/abandoning/interrupted), heart (authoritative RelationshipState / RouteLock / love-shield). No unapproved loyalty/morale thresholds. Stale symbol removal logic implemented. Native Godot execution unverified, pending Root formal execution."
},
"event_log": {
"code_implemented": true,
"formally_accepted": false,
"status": "code_implemented_unverified_pending_root_execution",
"details": "Bounded recent 256 events, readable Japanese presentation, formatted amounts/times/reasons, duplicate event ID elimination. Reusing existing 256-entry stream contract. Native Godot execution unverified, pending Root formal execution."
},
"important_warning_cutin": {
"code_implemented": true,
"formally_accepted": false,
"status": "partially_implemented_upstream_dependencies_pending",
"details": "ImportantWarningCutinDialog connected to real DEFENSE_RAID_CREATED and RELATIONSHIP_SHIELD_CHANGED (shield=0, love-shield vanishing) domain events with stop/resume behavior. Generic INCIDENT_CREATED is not falsely treated as NTR. Duel occurrence triggers and full invasion upstream flow remain unimplemented upstream dependencies, explicitly documented as missing and not claimed complete. Native Godot execution unverified, pending Root formal execution."
},
"click_alternatives": {
"code_implemented": true,
"formally_accepted": false,
"status": "code_implemented_unverified_pending_root_execution",
"details": "OperationsCameraControls providing click equivalents for zoom in/out, pan up/down/left/right, reset view, grid toggle, layer toggle, and click-based staff assignment. Does not alter gameplay model. Manual P23 held gestures remain deferred. Native Godot execution unverified, pending Root formal execution."
},
"japanese_input_and_scale": {
"code_implemented": true,
"formally_accepted": false,
"status": "code_implemented_unverified_pending_windows_and_root_execution",
"details": "ManualSaveSlotsDialog LineEdit for editable Japanese save label (display_name metadata only), control char validation, default name fallback, cryptographic owned path (manual_ui_<hex32>) preservation, cancel discard semantics. Native Godot OS input handling (not browser). Same-Windows-release platform acceptance remains pending formal execution."
},
"final_validation_review": {
"code_implemented": true,
"formally_accepted": false,
"status": "pending_root_full_roster_and_opus_review",
"details": "Local unit suite (tests/ui/test_operations_notification.gd) with FixtureCleanup and OperationsOSObserver implemented and hashed. check_p24_operations_evidence.py strict verify-only checker implemented. Formal full regression, native OS verification, and independent separate Claude Opus review are owned by Root Runner and pending execution."
}
},
"runner_verification_tasks": {
"native_godot_test_suite": "Run tests/ui/test_operations_notification.gd under native Godot runner with isolated profile.",
"evidence_checker": "tools/check_p24_operations_evidence.py --verify-only",
"root_full_roster": "Root owns formal execution of required test roster on exact final game/source/settings.",
"windows_release_acceptance": "Candidate Windows platform execution preserved for formal closure; not transferred from prior evidence.",
"independent_opus_review": "Independent separate Claude review with actual runtime evidence (not pending templates alone) before formal submit."
}
}
@@ -0,0 +1,48 @@
{
"plan_id": "P24-03",
"state": "completed",
"completed_at_jst": "2026-10-13T09:40:31.868894+09:00",
"implementation_commit": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"integration_commit": "0a759f13aed03c05fd4a25ab4a2c26eefec94e3c",
"spec_ref": "docs/spec/acceptance/P24-03-code-r02.json",
"spec_sha256": "dbb885b47a3ee4af3d4180bb97fe746e26a0e08d75af2cd3b5b727ed337bd37c",
"all_required_conditions": [
"facility_lifecycle",
"guest_lifecycle",
"staff_lifecycle",
"layer_and_values",
"invalid_and_failed_load",
"normal_os_input",
"final_validation_review"
],
"mandatory_conditions_unmet": 0,
"all_required_evidence_present": true,
"passed_receipts": {
"validation": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/execution/p24-03-formal-closure-r01/validation.json",
"review": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/execution/p24-03-formal-closure-r01/review.json",
"integration": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/execution/p24-03-formal-closure-r01/integration.json",
"final": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/execution/p24-03-formal-closure-r01/final.json"
},
"reused_raw_evidence": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p24-03-code/.runtime/P24-03/build/p24-evidence/index.json",
"existing_full_regression": {
"suites": 173,
"checks": 14810,
"failures": 0,
"not_delivery_count": true
},
"supplemental_positive_metadata": {
"checks": 17,
"failures": 0,
"result": "artifacts/original-completion-2026-10-02/p24-positive-metadata-r02/result.json"
},
"normal_os_input_cross_process": {
"producer_pid": 17306,
"consumer_pid": 19943,
"same_manifest_world": true,
"whole_model_and_screen_values_equal": true
},
"review_guard_bytes": 494833,
"windows_acceptance": false,
"whole_game_completed": false,
"runner_support_not_added_to_original88": true
}
@@ -0,0 +1,14 @@
{
"task_id": "P24-03",
"commit": "0a759f13aed03c05fd4a25ab4a2c26eefec94e3c",
"kind": "final",
"checks": [
{
"id": "p24-acceptance",
"exit_code": 0,
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-03-code/runs/c002071e0a5e4a61ace7d5f037941ecd",
"passed": true
}
],
"passed": true
}
@@ -0,0 +1,5 @@
{
"candidate": "0a759f13aed03c05fd4a25ab4a2c26eefec94e3c",
"main_base": "41a5acbd434990f3253f4990a6479b730d2312ee",
"authority": "Existing explicit main-update authorization plus latest explicit human continued-execution directive; no new human SHA utterance claimed"
}
@@ -0,0 +1,14 @@
{
"task_id": "P24-03",
"commit": "0a759f13aed03c05fd4a25ab4a2c26eefec94e3c",
"kind": "integration",
"checks": [
{
"id": "p24-acceptance",
"exit_code": 0,
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-03-code/runs/2a8e262ae43c49e0980b41808fc4ac74",
"passed": true
}
],
"passed": true
}
@@ -0,0 +1,27 @@
{
"task_id": "P24-03",
"commit": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"status": "pass",
"summary": "全7条件(facility/guest/staff lifecycle、layer_and_values、invalid_and_failed_load、normal_os_input、final_validation_review)の証拠を、提供された差分・ソース・Runner結果と突合し、受入P24-03は満たされていると判断する。critical/high/mediumの指摘はない。変更は allowed_paths 内に収まっている。新suite以外の既存suiteのSHA・marker・cleanupは変更されておらず、試験の削除・無効化もない。製品コードの変更は2点のみで、(1) Main の interface_selection version 判定を数値1(JSON由来の1.0を含む、bool/文字列/非1は拒否)に修正、(2) 客パネルで選択対象が不在のとき OptionButton を未選択(-1)にする、である。実GuestService/StaffServiceの離脱、実建設→実撤去、Unique解雇拒否、履歴(永続recordと離職給与43G)の保持、UI収集がRuntime digestを変えないこと、不正version拒否は新suite(41 checks, 0 failures)で確認されている。全173 roster(import/parse含む)がstrict markerで14810 checks, 0 failuresで通過し、cleanup/guardエラーもなく、実PIDとsource SHAが一致している。正式保存の同一manifest/world SHAに対し、別PID(17306と19943)で通常Mainを再開し、選択ID、レイヤー、施設の地図強調、各画面値が完全一致している。画面値は実ラベル文字列とneed barから独立に解析され、model値と等価であることが確認されている。通常OS入力はCUA native appによる実クリック6件(施設・客・職員・レイヤー選択、保存、再開)で、各stepのPID・画面・source mapが突合されている。生成物は injected=true と faithful_transcription の限界が開示されている。Godot内部のemitや関数呼出しのみの証拠ではない。正式独立レビューは本レビューが担う最終段で、pending_reviewの状態は設計どおり。Windows条件は仕様上別扱いで未受入のまま。以下は低優先度の所見。",
"acceptance_ids": [
"P24-03"
],
"findings": [
{
"severity": "low",
"message": "cross-process の P24_OBSERVATION ログ行はエンジン出力ではなくcollector正規化で生成されている。元エンジンログと元raw報告はハッシュ付きで保持され、その旨も明示されているため受入可能だが、独立計測ではない。"
},
{
"severity": "low",
"message": "JSONで復号された float 1.0 の version を受理する正例(施設・職員・客の復元)は、リポジトリ外の補足プローブ(17 checks)でのみ確認されており、新suite自体は負例のみを持つ。次回の保守では正例を新suiteに取り込むことが望ましい。"
},
{
"severity": "low",
"message": "OS入力traceはCUA tool stepの書き起こし(faithful_transcription)であり、元のtool RPCは独立計測されていない。限界は index に開示済みで、root指示の方針(CUAの実OS配信を許可し、emit/関数呼出しのみは不可)とも整合する。"
},
{
"severity": "low",
"message": "producerの17306は先行する予備ペアのconsumerとして起動したprocessを再利用している。最終ペアは専用の不変package(formal-package-os)に保存・再開しており、証拠上の矛盾はない。"
}
]
}
@@ -0,0 +1,101 @@
{
"contract": {
"id": "P24-03",
"title": "選択と保存の受入",
"requirement_ids": [
"P24-03"
],
"acceptance_ids": [
"P24-03"
],
"spec_ref": "docs/spec/acceptance/P24-03-code-r02.json",
"base_commit": "286730f88ce430a1cbe1501d40361188f832d235",
"worker": "codex",
"allowed_paths": [
"scenes/main/main.gd",
"scripts/ui/guest_observation_panel.gd",
"scripts/ui/staff_management_panel.gd",
"tests/ui/test_selection_departure.gd",
"tests/ui/test_selection_departure.gd.uid",
"tests/ui/fixtures/selection_cross_process.gd",
"tests/ui/fixtures/selection_cross_process.gd.uid",
"tools/check_p24_selection_evidence.py",
"tools/run_all_tests.py",
"tools/regression/suite-policy.json",
"docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json",
"docs/plans/original-completion-2026-10-02/execution/p24-03/**"
],
"depends_on": [],
"resource_locks": [
"main-selection-lifecycle",
"selection-acceptance-profile",
"regression-roster-policy"
],
"validation_ids": [
"p24-acceptance"
],
"max_repair_attempts": 2,
"spec_sha256": "dbb885b47a3ee4af3d4180bb97fe746e26a0e08d75af2cd3b5b727ed337bd37c"
},
"implementation_commit": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"integration_commit": "0a759f13aed03c05fd4a25ab4a2c26eefec94e3c",
"owner": "ユーザーのmain更新明示許可および最新の不要な停止を解除して作業継続する指示に基づく",
"evidence": [
{
"id": 1,
"task_id": "P24-03",
"kind": "validation",
"commit_sha": "9c711860a82d597a48dab595a49d4f23c5f3c370",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-03-code/runs/a44366e665b843268001a31daf2dd7f3-validation.json",
"passed": 0
},
{
"id": 2,
"task_id": "P24-03",
"kind": "validation",
"commit_sha": "c2b8772e237597930e67f1329d8b61143614a244",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-03-code/runs/61f7f1774bd44fc0bb912fedb195f2e0-validation.json",
"passed": 0
},
{
"id": 3,
"task_id": "P24-03",
"kind": "validation",
"commit_sha": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-03-code/runs/3df3f128f0e549e7a610e5c6212337ce-validation.json",
"passed": 1
},
{
"id": 4,
"task_id": "P24-03",
"kind": "review",
"commit_sha": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-03-code/runs/a1d33697d68b4f6696397137a7874a9a/result.json",
"passed": 0
},
{
"id": 5,
"task_id": "P24-03",
"kind": "review",
"commit_sha": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-03-code/runs/ec59a91c09f342a594aa0ec49854c7cf/result.json",
"passed": 1
},
{
"id": 6,
"task_id": "P24-03",
"kind": "integration",
"commit_sha": "0a759f13aed03c05fd4a25ab4a2c26eefec94e3c",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-03-code/runs/155c4f2b95694e52a4424ae8d3b71c3d-integration.json",
"passed": 1
},
{
"id": 7,
"task_id": "P24-03",
"kind": "final",
"commit_sha": "0a759f13aed03c05fd4a25ab4a2c26eefec94e3c",
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-03-code/runs/5605937ac12f4a7091fc49d3d85848e1-final.json",
"passed": 1
}
]
}
@@ -0,0 +1,14 @@
{
"task_id": "P24-03",
"commit": "4cefcff849d4dc1211caa5066e24bfae666cc2f4",
"kind": "validation",
"checks": [
{
"id": "p24-acceptance",
"exit_code": 0,
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-03-code/runs/978b599053834e99bd97303b07a4d2cd",
"passed": true
}
],
"passed": true
}
@@ -0,0 +1,27 @@
{
"read_only_review_report": true,
"root_product_source_edits": 0,
"findings": [
{
"severity": "high",
"file": "tools/check_p24_selection_evidence.py",
"issue": "gate requires independent_review verdict pass before Runner.validate, while Runner.review requires validation pass. This creates a cycle or an unnecessary second review.",
"required_behavior": "Machine validation must validate actual native/cross-PID/OS/full-regression evidence; Runner formal independent review is the downstream completion gate. Keep original final_validation_review requirement and original-unit incomplete until formal review, but do not fabricate review or require a prior duplicate review to execute validation."
},
{
"severity": "medium",
"file": "tools/check_p24_selection_evidence.py",
"issue": "collector limited to CGEventTap/Windows-RawInput and injected=false adds a hardware-only condition absent from original acceptance. CUA normal OS delivered input is synthesized at OS level, distinct from Godot pressed.emit/Input.parse_input_event. Requirement forbids treating function/emit alone as OS evidence, not all native OS automation.",
"required_behavior": "Permit actual CUA native app calls with faithful step records and observed screens, preferably corroborated by passive Godot _input/GUI input receipts and exact candidate/PID/save/source. Clearly disclose tool-generated OS events and trace limitations. Do not require new InputMonitoring permission or physical hardware only. Direct Godot function/emit or synthetic engine-only fixture must remain insufficient."
}
],
"reused_method_context": "Existing P05-01 accepted finite OS evidence explicitly used root actual CUA calls and returned screens, with the stated limitation that external trace was faithful transcription rather than independently instrumented original tool receipt. This is a method reference only, not a reused current pass.",
"keep_conditions": [
"all 7 condition IDs and no partial original completion",
"exact whole candidate source map",
"real producer/consumer distinct processes and immutable formal package",
"strict full regression and exact raw evidence",
"independent Claude formal review after validation"
],
"implementation_scope": "Comments for implementation owner only; root has not changed gate code or worker source."
}
@@ -0,0 +1,36 @@
{
"scope": "JSON numeric metadata type diagnostic only; no game acceptance",
"actual_pid": 99508,
"exit_code": 0,
"command": [
"/Applications/Godot.app/Contents/MacOS/Godot",
"--headless",
"--path",
"/private/tmp/the-tree-p24-json-c200043b18364206b2ca6f61179c4d62",
"--script",
"/private/tmp/the-tree-p24-json-c200043b18364206b2ca6f61179c4d62/probe.gd",
"--log-file",
"/private/tmp/the-tree-p24-json-c200043b18364206b2ca6f61179c4d62/engine.log"
],
"raw": "Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org\n\n{\"engine_version\":{\"build\":\"official\",\"hash\":\"ed1daf0bf001b61586d9930840f2f1394092c079\",\"hex\":263938,\"major\":4,\"minor\":7,\"patch\":2,\"status\":\"stable\",\"string\":\"4.7.2-stable (official)\",\"timestamp\":0},\"is_float\":true,\"is_int\":false,\"profile\":\"/Users/korizatou114/Library/Application Support/TheTree-P24-JSON-5a698892c27c477f828a12f1841d8637\",\"version_value\":1.0}\n",
"probe_sha256": "2b6cba8ceee004d1788f69ba27cba73ebcb049dcce418baa9048400969cd5d34",
"finding": {
"severity": "high",
"file": "scenes/main/main.gd",
"functions": [
"_restore_interface_selection",
"_restore_actor_interface_selection"
],
"candidate_observed": "not selection.get(\"version\", 0) is int",
"impact": "SaveService._read_manifest JSON.parse_string produces 1.0 FLOAT, so normal version1 metadata will be silently rejected and valid selections lost.",
"required_original_behavior": "accept numeric exact1 from normal JSON while rejecting bool/string/non1 version; preserve old optional metadata fallback",
"acceptance_cases": [
"normal saved JSON version1 decoded asfloat must restore map/staff/guest selection",
"bool true version must not count asnumeric1",
"1.5/string1/wrongtype safely ignored",
"no changes to gameplay snapshot or SaveService format needed"
]
},
"read_only_review_report": true,
"root_product_source_edits": 0
}
@@ -0,0 +1,21 @@
{
"read_only_review_report": true,
"candidate": "9c711860a82d597a48dab595a49d4f23c5f3c370",
"evidence_root": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-03-local-native-r01",
"import_parse": "pass; 304 scripts",
"local_suite": "test_selection_departure.gd failed, 31 checks, four errors",
"failures": [
"departed guest is absent from selection and dropdown (line42)",
"departed guest leaves no stale current detail values (line43)",
"real facility construction starts (line69)",
"real facility construction completes (line72)"
],
"repair_instructions": [
"Inspect actual guest roster and refresh fallback. Valid other guests can remain and may be selected; do not change correct normal first-row behavior merely to satisfy an incorrect empty-roster fixture. Prove departed ID not referenced; create genuine empty-roster case separately if needed.",
"Use valid real construction coordinates/unlock/payment prerequisites based on existing lifecycle test, record real command result, then prove actual demolition and cleared inspector. Do not bypass services or weaken acceptance.",
"Read ROOT_REVIEW_EVIDENCE_GATE.json and resolve validation/review cycle and over-restrictive OS collector.",
"Implement distinct-process save/restart passive fixture/protocol within allowed paths if needed to enable real remaining acceptance, not fake evidence.",
"Coalesce related changes, update strict source marker SHA/contracts, avoid full regression until local pass. Root will run owned guarded native phases."
],
"root_product_source_edits": 0
}
@@ -0,0 +1,10 @@
{
"read_only_review_report": true,
"candidate": "c2b8772e237597930e67f1329d8b61143614a244",
"local_result": "import and 304 parse passed; departure37checks2failures; guards and cleanup pass; other3notrun",
"evidence_root": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-03-local-native-r02",
"failure": "construction start/complete; message prints RefCounted identity, not actual CommandResult",
"diagnosis": "Possible fixture ordering interference: test creates then removes ordinary employee with43 unpaid wages, deliberately preserving departed debt. Runtime _operations_construction_investment_gate lines7988-8001 correctly rejects construction if _operations_total_arrears()>0. Build/demolish should precede unpaid-wage departure boundary or use independently fresh runtime/Main via ordinary cleanup. Do not bypass legitimate debt gate. Print build_result.to_dictionary for actionable true error. Verify this rather than merely changing coordinates again.",
"required_remaining_implementation": "Add allowed tests/ui/fixtures/selection_cross_process.gd passive, separate-process normal-Main collector; lacking independently read actual model fields/screen widgets prevents root preparing truthful exact-current cross-process and OS proof. Existing Main has no external introspection endpoint. Fixture should instantiate ordinary Main, prepare boundary target via actual commands for producer if needed, pause clock for unchanged observations, expose actualPID/model/screen/selected layer/highlights via JSON and rawlog plus frames; source map supplied by rootcontroller; save/load and selection for OS proof MUST be actual native CUA user-facing controls, not injected Godot engine events. Consumer uses same immutable formalpackage in distinct process/private-profile, source/project guards stay unchanged. No manufactured accepted index or reviewer. Provide documented env/control/paths, passive poll output and ordinary owned teardown. No testunit denominators added.",
"root_product_source_edits": 0
}
@@ -0,0 +1,16 @@
{
"schema_version": 1,
"source_map": {},
"artifacts": {},
"conditions": {
"facility_lifecycle": {"status": "pending", "source_map": {}, "artifacts": []},
"guest_lifecycle": {"status": "pending", "source_map": {}, "artifacts": []},
"staff_lifecycle": {"status": "pending", "source_map": {}, "artifacts": []},
"layer_and_values": {"status": "pending", "source_map": {}, "artifacts": []},
"invalid_and_failed_load": {"status": "pending", "source_map": {}, "artifacts": []},
"normal_os_input": {"status": "pending", "source_map": {}, "artifacts": []},
"final_validation_review": {"status": "pending", "source_map": {}, "artifacts": []}
},
"formal_acceptance": false,
"native_Windows": "pending"
}
@@ -0,0 +1,75 @@
# P24-03 code-role 提出
要件・受入ID: P24-03。基準commit: 286730f88ce430a1cbe1501d40361188f832d235。
固定仕様: `docs/spec/acceptance/P24-03-code-r02.json`。
提出状態は submitted。元88成果の1件の完了はRunnerと独立レビューが確定する。
## 変更と再利用
- Mainの施設・客・職員metadataのversion判定に数値型を要求。Godot JSONが返す数値1.0は互換性のため許可し、bool/string/非整数値を拒否。
- 客panelの空選択時にOptionButtonを明示的に未選択にする。既存在館一覧・退館状態表示・履歴分離を維持。
- 新局所suiteは実GuestServiceのLEAVING→despawn、一般職員remove、給与履歴保持、Unique拒否、UI収集時の全Runtime digest不変性、metadata型境界を確認する。関数呼出しはnative局所検証でありOS証拠には数えない。
- ROOT_REVIEW_LOCAL_FAILUREを反映し、実離脱後は離脱IDの除外と在館客への正常fallbackを確認。残る在館客をserviceの正式離脱で除去した別境界で空一覧・表示値消去を確認する。建設は既存lifecycle suiteの有効な受付座標(-8,0,0)と120分完成を再利用し、実command結果を失敗messageへ記録する。
- 新suiteだけのraw SHAをpolicy/markerへ更新。理由は上記fixture不備の修正であり、既存suiteのSHAやmarkerは変更しない。正式Runner再実行はpending。
- 新suiteだけにpolicyとstrict markerを追加。既存suiteのSHA・assert・cleanup・marker条件は変更しない。
- レイヤーは既存GameState snapshot、施設表示/地図強調は既存Main、職員一覧は既存service report、正式保存/失敗読込は既存契約を再利用。経済・Clock・人物・旧保存係数の変更なし。
- 新検証器は読取専用。限定suite機能、見た目、素材、Runner操作、外部worktree入力は追加しない。
## 条件matrix(全項目未実行)
| condition ID | 根拠source / Runnerで必要な検証 | 状態 |
| --- | --- | --- |
| facility_lifecycle | test_selection_lifecycle + test_selection_departure、実撤去後ID/detail/highlight、別process有効施設復元 | pending |
| guest_lifecycle | test_actor_selection + test_selection_departure、退館中表示・実despawn・履歴保持、別process画面値 | pending |
| staff_lifecycle | test_actor_selection + test_selection_departure、一般職員remove・給与履歴・Unique拒否、別process画面値 | pending |
| layer_and_values | test_selection_autosave + test_actor_selection + 新suite、正式GameState layer、UI収集時全Runtime不変、別process通常Main | pending |
| invalid_and_failed_load | test_selection_lifecycle + test_actor_selection + 新suite、不在/不正型/過大ID/旧metadata/読込失敗時保持 | pending |
| normal_os_input | 同候補sourceの通常Mainで施設/客/職員/レイヤー選択、正式保存、別PID再開、OS collector trace + PNG + log | pending |
| final_validation_review | 最終候補全roster/import/全script parse、正式独立Claudeレビュー、別Windows条件 | pending |
## Runner実行計画
作成者は試験を実行していない。局所4suiteを確認した後、影響範囲(guest observation、staff management、layer skin、save/recovery)と最終候補の全回帰を正式実行する。全回帰には既存 `tools/run_all_tests.py` を指定なしで使用し、新suiteを含む全rosterとparse範囲を維持する。新suiteのcontrol labelはsingleであり、Runnerが渡すcontrol.jsonへ既存ordinary cleanupを出力する。
通常OS確認はRunnerが所有する新規profileで行う。個人profileや外部PIDを操作しない。Producerとconsumerの通常Main・同じ保存を保持し、別PIDで復元を観察する。OS未実行やWindows未実行は提出を妨げないが受入済みにはしない。修正後は同候補sourceの証拠を再取得し、過去結果を転用しない。
## 証拠index protocol v1
Runnerが `BUILD_OUTPUT_DIR/p24-evidence/index.json` を作成する。検証は `python3 tools/check_p24_selection_evidence.py --verify-only`。検証器は証拠を生成せず、欠損/pending/候補違い/失敗を非0で返す。通過も `formal_acceptance=false`。
indexのキー:
- `schema_version`: 1。
- `source_map`: `tools/run_all_tests.py` のsource_refsに含まれる**全source**について相対repo path→raw bytes SHA256。各条件・cross-process観察・OS collector・レビューでも同じmapを保持する。
- `artifacts`: artifact名→`{path,sha256}`。pathはp24-evidence内相対path。空ファイル・symlink・上位参照は禁止。Runnerは元の正式証拠を変更せずコピーして登録する。
- `conditions`: 上表の全ID→`{status:"executed",source_map,artifacts:[artifact名,...]}`。未実行はpending。局所条件には検証器指定の実suite raw logを含める。`final_validation_review`だけは全回帰実行済み・正式レビュー待ちを`pending_review`で表す。全回帰input/ended/rowsを必須とし、machine validation後にRunnerが正式独立レビューを実行する。元単位はレビュー終了まで未完了。
- `native`: `{suites:["tests/ui/test_actor_selection.gd","tests/ui/test_selection_lifecycle.gd","tests/ui/test_selection_autosave.gd","tests/ui/test_selection_departure.gd"]}`。全回帰内の実行と照合する。
- `full_regression`: `{input,ended,rows,logs}`。input/ended/rowsはrunner-input.json、runner-ended.json、all-tests.jsonのartifact名。logsは全rosterのsuite path→元の実raw logのartifact名。所有PID・source・run_id・全roster・marker・全phaseを突合する。
- `cross_process`: `{producer,consumer,manifest,world}`。正式保存のmanifest.json/world.binと両観察JSONのartifact名。観察JSONは `{source_map,scene:"res://scenes/main/main.tscn",pid,manifest_sha256,world_sha256,log,observation}`。両processで同一save SHA・別PIDが必要。
- `observation`: `{interface_selection,layer,facility,guest,staff}`。interface_selectionは正式manifestの値、layerはGameStateの値。各対象は `{id,model_values,screen_values}`。facilityにはhighlight_idも必要。model_values/screen_valuesは空でない同形辞書で、実model値と画面に実表示された値を独立に収集する。表示のname/金額/状態/need等を単なるID確認に置き換えない。
- cross-process `log` は通常Mainの実行・観察を採取したcollector raw logのartifact名。`P24_PROCESS pid=<実PID> scene=res://scenes/main/main.tscn` および `P24_OBSERVATION <observationをsort_keys=True,ensure_ascii=Falseのjson.dumpsで表現>` を収録する。作成者はこのログを生成していない。Runner collectorが実processと実画面を確認して収録する必要がある。
- `os_input`: OS collector JSONのartifact名。JSONは `{source_map,collector,events}`。collectorはmacOS-CGEventTap、Windows-RawInput、またはCUA-native-app。各eventは `{action,pid,monotonic_ns,native_type,delivery:"native_os",injected,screenshot,raw_trace}`。actionはselect_facility/select_guest/select_staff/select_layer/save/resumeを全て含む。saveはproducer PID、resumeはconsumer PID。native_typeはmouseDown/keyDown。PNGとtrace(eventのsort_keys=True,ensure_ascii=False JSON行)を登録し、normal_os_input条件のartifactsにも含める。OS配信された自動操作は許可し、injectedで由来を開示する。pressed.emit、ゲーム関数呼出し、engine内部注入だけはOS証拠にならない。
- CUA-native-appは`trace_provenance`(original_tool_receiptsまたはfaithful_transcription)と非空の`limitations`を報告し、各eventに`injected:true`、`tool_step` artifactを持つ。step JSONは`{pid,action,monotonic_ns,api:"native_app",operation:"click"または"key",arguments,screenshot,source_map}`。argumentsは実呼出し引数、screenshotは実返却画面。stepも条件artifactに含める。書き起こしの限界とtool生成OS入力を明記し、実CUA呼出し・観察に基づくことをRunnerが照合する。新InputMonitoring許可や物理入力のみという条件は課さない。
- `independent_review`: 最終別セッションClaudeレビューJSONのartifact名。`{source_map,reviewer:"Claude",independent_session,verdict:"pass",condition_ids:[全ID]}`。machine validationより後段のRunner正式レビュー。pending_reviewでは未要求、executedへ更新する場合はこのartifactと全条件coverageを検証する。Runnerは実レビューと突合して保存する。コード作者の自己承認で代用しない。
このprotocolは証拠の整合性を検査する。artifactが実観察/正式レビュー由来であることの責任と元成果の判断はRunnerにあり、任意JSONやログを捏造して正式証拠に扱うことはできない。Windows結果は別に保存し、Mac結果から推測しない。
## 今回の提出で残る実行
別process fixtureとして `tests/ui/fixtures/selection_cross_process.gd` を追加した。通常Mainをinstantiateする受動観察器であり、選択・保存・読込・Clock変更・command・入力注入を行わない。局所suiteはOS入力の代用にしない。source_mapと正式manifest/worldのSHAを添えた観察、CUA stepと画面、全回帰、正式レビューはいずれも未取得でpending。作成者はgit書込み、Runner操作、試験実行を行っていない。
## SECOND_LOCAL記録に対する修正(正式再実行pending)
実建設/撤去をguest/staff離脱境界より前へ移動した。意図的に43Gの離職給与債務を保持した後の建設は正規債務ガードに阻まれるためであり、経済ルールは変更しない。assertは維持し、建設失敗messageは `CommandResult.to_dictionary()` を表示する。変更した新suiteだけのSHAをpolicy/markerへ更新した。これは追加のfixture修正であり、既存suiteのSHA/marker/cleanupは変更しない。
### 受動別process collectorの実行契約
Runnerは同候補project、別PID、専用private user-data環境で `--script res://tests/ui/fixtures/selection_cross_process.gd` をGUI起動する。`P24_OBSERVER_OUTPUT` はそのprocess専用の既存絶対ディレクトリ、`P24_OBSERVER_SOURCE_MAP` は現候補のpath→raw SHA256 map JSONの絶対パス。別worktreeは入力にしない。入力とsource guardは既存Runner側で照合する。
通常Mainの画面操作で必要対象の準備・選択・pause・正式保存・別process読込を行う。observerは1秒間隔で変化した観察を、実PID/連番付きJSONとPNG、stdout `P24_PROCESS` / `P24_RAW_OBSERVATION` として保存し、既存artifactの上書きを拒否する。不要なprocessの終了は通常MainのOSウィンドウcloseに従い、終了強制や外部PID操作を追加しない。追加のtest suiteではなく、roster/成果分母を増やさない。
JSONの `raw_observation` はモデル側facility snapshot、guest observation、staff management reportを、実Labelのheading/detail/priority/decision・need bar・editor可視性・地図highlightとは独立に採取する。表示を再生成してscreen値と称しない。未選択/不存在は空modelとして残し、成功とは判定しない。PNGとJSONはraw証拠であり正式save SHAやOS traceを自己申告しない。
Runner collectorはこのraw証拠と実画面・正式packageを突合し、既存index protocolの `model_values` / `screen_values` を独立に抽出する。表示されていないモデル値をscreen側へコピーしてはならない。`P24_RAW_OBSERVATION` は既存検証器が要求する正規化済み `P24_OBSERVATION` と区別する。正式manifest/worldのSHA、元raw report/frame、OS入力traceへの由来を保持し、未取得はpending。collectorのparseと同一package別PID復元はRunnerの局所/影響範囲検証に含める。
読取専用の静的確認としてPython検証器のAST parseと、新suiteのraw SHAがpolicy/markerの両方と一致することを確認した。Godot parse、局所suite、証拠検証器そのものの実行結果は未取得であり、静的確認を正式試験の合格として扱わない。
@@ -223,7 +223,7 @@
"ASSETS_PLATFORM_AUDIT.md": "a3a4b8ca0fb39a9abf88692285d26aa879815e092352ba304ef2758e71da50bd",
"ATOMIC_REGISTER.md": "6070143b85b7c3b9b73d7c9768d88d2dae4d20407ad54a563cb6ffd74f2373ae",
"ATOMIC_SEMANTIC_REVIEW.md": "c156b1fb57c2b4a900f27a19049510f3e9616e723d5edb3417f50d733de5be48",
"IMPLEMENTATION_PLAN.md": "011cb2fafe84b6c2d76657df1571853c22ca43225b94a76873dad15239416473",
"IMPLEMENTATION_PLAN.md": "00004ff16c10cda0cc29429770afc90f66e40672e058939f8cac1cc451c32a46",
"IMPORT_UNICODE_AUDIT.md": "88b2382bfbb3411fece8be7e6004ba21d8bf6a47e9d1f3cf573ea6638f79c12f",
"P09_VALUE_CONTRACTS.md": "c5abdc4543e54033b1fcfd0943cba266f664d8c10968965fd36ade40da488a25",
"PERFORMANCE_AUDIT.md": "c75e806acf825fc4a61f773a2cce7e71be335a1e25dbb15d98091097e8dc47c1",
@@ -244,7 +244,7 @@
"facility_value_contracts_validation.json": "bf490a9ca585c48fc318e76c4a069ef244827d828d46e6415399f7710590dd16",
"performance_audit.json": "0b630b15a392c3e0366f1362bfe4ddacb968f915f54549f82e4d6fa315384762",
"progress_contract.json": "ccbdfce8c8365d5c13f59531955708834cce45acd0e136561c95295dfdb5df77",
"progress_snapshot.json": "2e736c211c5aa36260b7f590b9b1cdd9c298ed07912b1d60e029d8a23b56fd7b",
"progress_snapshot.json": "ad068fcb7878184f0a7d0a2d941a5f3e7ebbd9e5c90c942a10b15e2b1eec4896",
"progress_validator_guard_checks.json": "72ee513fcf1cad85c24bfd0a4b472f425f6acf117928ece0f6b41e29018da627",
"progress_validator_guard_checks.r01.json": "14c26adffa5a1938241db4912e50ea54b660641ee6fee2e412d467dce14d0519",
"requirement_coverage.json": "8b5788b22ae8f49ea7e5cf048a5f0d82b54f6f193623465e0b70496b5aadd09d",
@@ -254,6 +254,6 @@
"validate_facility_value_contracts.py": "91640e2a76cd6cc9186f7b85c0bf379b24b6cac0d48962815235ee628e7ae43e",
"validate_plan.py": "23678227d9b3c556f1bd903f4b5b3147c4a82ce5085e10055481026803451b80",
"validator_update_checks.json": "f997d0f3de2cea29550840b1d5cb23647d64f81329de46758551e2b6c8fecc27",
"work_packages.json": "28b7690d1c9a33cfaa414e04e3321b564be2e9865c4756ae8fbfb3512ba6f1fb"
"work_packages.json": "6f3b7abd82affcb94fd32f53b08ea1a5544d4d9f540c761d9a734bb571aeea88"
}
}
@@ -1,8 +1,8 @@
{
"schema_version": 1,
"integrated_at_jst": "2026-10-13T02:29:07.026485+09:00",
"scope": "P24-03 exact final candidate full regression and native OS cross-process evidence; formal review/integration pending, no new completion",
"source_head": "243a3593bb49f0a829cda47d75fe79e84c0e10a8",
"integrated_at_jst": "2026-10-13T09:40:31.868894+09:00",
"scope": "P24-03 formally completed with exact submitted independent review and current main integration/final evidence; next P24-02 resumed, whole game incomplete",
"source_head": "0a759f13aed03c05fd4a25ab4a2c26eefec94e3c",
"source_authority_note": "Production HEAD at this documentation update; P24 candidate4cefcff remains isolated and unadopted.",
"immutable_history": {
"baseline.json": {
@@ -150,13 +150,13 @@
},
"current_package_state_counts": {
"completed": 1,
"in_progress": 25,
"planned": 4
"in_progress": 26,
"planned": 3
},
"current_delivery_state_counts": {
"completed": 9,
"completed": 10,
"in_progress": 22,
"planned": 57
"planned": 56
},
"completed_current_deliveries": [
"P00-01",
@@ -167,7 +167,8 @@
"P05-01",
"P14-01",
"P22-01",
"P23-01"
"P23-01",
"P24-03"
],
"whole_packages_newly_completed": [],
"remaining_acceptance": [
@@ -178,8 +179,8 @@
"同一Windows配布版の実機通常操作/保存再開",
"全施設/人物/ルート/エンド/図鑑/完成素材/実プレイ時間"
],
"new_engine_runs_by_this_integration": 0,
"production_game_source_edits_by_this_integration": 0,
"new_engine_runs_by_this_integration": 2,
"production_game_source_edits_by_this_integration": 2,
"save_preservation_adoption": {
"merge_path": "artifacts/original-completion-2026-10-02/p03-save-preservation-r02/merge.json",
"merge_sha256": "f6cc6e6590113e4d14bda1f5b8a1253672de1d8ac77ce91841c46c080a5eb068",
@@ -9706,5 +9707,8 @@
],
"execution": "the-tree-p06-01 Runner; recorded Claude decision, one Codex implementer",
"current_gate_status": "not implemented or executed; formal completion not accepted"
}
},
"next_closure_plan_id": "P24-02",
"p24_03_formal_closure": "docs/plans/original-completion-2026-10-02/execution/p24-03-formal-closure-r01/closure.json",
"new_engine_run_scope": "Supplemental positive metadata diagnostic r01 failed cleanup preparation/log isolation; r02 passed17checks unchanged source and entire default profile. No duplicated full game regression."
}
@@ -15644,7 +15644,7 @@
},
"legacy_state": "in_progress",
"state_scope": "package-level; completed means whole package accepted; partial implementation is not completion",
"current_summary": "通常Mainの直近256通知ログとbounded表示を限定採用。実viewport8操作、別process再開314機能+23parse、5whole typed/IO1を確認。全インスペクター/人手OS/全通常操作は未受入。",
"current_summary": "P24-03全7条件を正式完了。P24-01全インスペクターは予定、P24-02操作と通知は着手中。パッケージ全体の完成には数えない。",
"remaining_deliverables": [
{
"id": "P24-01",
@@ -15666,15 +15666,19 @@
"sha256": "8eec3a8492aa74f571eb21447c6911cea64d176e0f813152f226b2b4122ba387"
}
],
"updated_at_jst": "2026-10-02T22:55:59.434380+09:00",
"progress_summary": "通常Mainの直近256通知ログとbounded表示を限定採用。実viewport8操作、別process再開314機能+23parse、5whole typed/IO1を確認。全インスペクター/人手OS/全通常操作は未受入。"
"updated_at_jst": "2026-10-13T09:40:31.868894+09:00",
"progress_summary": "P24-03完了後の次対象として再開。既存256通知ログ・実viewport8操作・別process314機能23parseの限定証拠と、P24-03の通常OS選択/保存証拠を引継ぐ。原建設/通知UXに沿い、配置・無効ドロップ説明・モーダル・停止再開・フキダシ/ログ/カットイン・クリック代替・日本語入力/表示倍率の不足を現版で確認して限定修正する。P23の手動保持入力延期を免除扱いにせず、Windowsが実際に必要な条件だけを分離する。既存証拠の適用性は各条件に照合、未知の数量/仕様は設定しない。",
"remaining_gaps": [
"全操作項目と現版UI・元の通知UX・既存証拠の条件別照合、および適用環境/対象版付きの検証契約を確定する。",
"配置/無効操作/モーダル/停止再開/通知表現/クリック代替/日本語入力/表示倍率の不足を修正し、局所・影響・同一最終版必須回帰と独立レビューを取得する。"
]
},
{
"id": "P24-03",
"title": "選択と保存の受入",
"work": "施設・客・職員・レイヤーの選択を撤去、離脱、保存再起動に跨がせる。",
"acceptance": "不在対象を参照せず、有効な選択と画面値が復元される。",
"state": "in_progress",
"state": "completed",
"evidence": [
{
"path": "docs/plans/original-completion-2026-10-02/execution/multi-agent-dev-handoff/p24-03-runner-start-r01.json",
@@ -15690,15 +15694,16 @@
"path": "docs/plans/original-completion-2026-10-02/execution/multi-agent-dev-handoff/p24-03-current-validation-r03.json",
"sha256_at_integration": "b74215947463f59c20a51bc936aebf0a57fa64b1edc278ea547060279934129d",
"scope": "Exact4c local/full/native-OS/cross-process and formal Runner machine validation; independent review and integration pending"
},
{
"path": "docs/plans/original-completion-2026-10-02/execution/p24-03-formal-closure-r01/closure.json",
"sha256_at_integration": "3d1f2ed3d312b3e64d491685fb4cd1bf2704f59fb014db606539080092c97868",
"scope": "All7 mandatory conditions, exact submitted independent review and current-main integration/final receipts; original P24-03 completed only"
}
],
"progress_summary": "次に閉じる優先対象へ順序変更。提出4cefcffは305script parse/import、局所4suite/254check、全173suite/14810check失敗0の既存証拠を保持。通常OS選択・保存と別PID17306→19943の実モデル/画面値一致。今回同候補のverify-onlyでsource_map・全artifact・全7条件を再照合しmachine_validation=true、formal_acceptance=false、independent_review=pending。Runner正式validation receipt3df3f128...は同SHA passed、現在review_pending・修正2/2。レビュー主要packet600714bytes、raw不変172行重複context除去のreadonly案430008bytes。P24向けRunner変更と支援単体試験の信頼ホスト正式実行の限定案を提示し承認待ち。ゲーム重複実装・全回帰再実行・原修正上限変更なし。正式レビュー・統合未達、9/88を維持。",
"remaining_gaps": [
"P24専用marker172行の不変raw認証付き重複引用削減、および当該Runner支援変更の全単体試験を信頼ホストで正式実行する限定案の承認。承認までは新規実装なし。",
"Runner支援変更の必要試験・別Claude独立レビュー、最終exact review packet容量の確認。",
"同4cefcffと適用証拠の別Claude正式受入レビュー、最新main統合候補と必要な影響検証、全条件成立後の台帳完了反映。"
],
"updated_at_jst": "2026-10-13T08:53:13.197406+09:00"
"progress_summary": "提出4cefcff、統合0a759f13で全7条件を正式受入。既存305parse/局所4suite254checks/全173suite14810checks、通常OSクリックと同一正式保存の別PID17306→19943の選択/実モデル/実画面値を再利用。追加正例17checks合格。別Claude独立レビューcritical/high/medium0、最新main統合と最終verify-only合格、Runnercompleted/traceabilityを取得。低優先度改善は別記録。Windowsと全ゲーム完成を認定せず、原88成果のうち本1件のみ完了追加。",
"remaining_gaps": [],
"updated_at_jst": "2026-10-13T09:40:31.868894+09:00"
}
],
"estimate_person_days_historical_2026_09_29": [
@@ -16076,7 +16081,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-02T22:55:59.434380+09:00",
"updated_at_jst": "2026-10-13T09:40:31.868894+09:00",
"execution_progress": {
"current1277_checkpoint": {
"at_jst": "2026-10-02T22:55:59.434380+09:00",
+88
View File
@@ -0,0 +1,88 @@
{
"id": "P24-02",
"title": "操作と通知",
"authority": "Designated original BASELINE and existing implementation plan; human explicitly permits reordered work, multiple-AI plugin development, main update and proceeding without invented approval blocks. No unapproved values or criteria reduction.",
"original_excerpt": "## 11.1 メイン画面レイアウト\n * **上部:** 資金・マナ・資材リソース、時間・天候、世界樹ランク。\n * **下部:** [FRONT / BACK] レイヤー切替巨大トグルスイッチ、建設・管理メニュー。\n * **マップエリア:** マウススクロール、ズームインアウト対応の2D断面図。表/裏の視覚的強調・半透明化制御。\n## 11.2 インスペクター・詳細ウィンドウ\n * **施設:** ポリシー設定、シフト配置(ドラッグ&ドロップ)。\n * **ユニット:** 客の欲求バー・AI思考ログ、従業員の感情3軸グラフ・愛の盾耐久値。\n## 11.3 覗き・背徳インターフェース\n * 裏層の「カメラアイコン」からマジックミラー視界へ移行。\n * アングル変更、スクリーンショット(脅迫証拠収集)、介入操作が可能。\n## 11.4 建設・通知UX\n * **建設:** グリッドスナップ、接続ライン警告表示。\n * **通知:** ユニット頭上のフキダシ(Zzz、怒り、ハート)、イベントログ。\n * **カットイン:** 「NTR侵食開始」などの重要警告、決闘発生、防衛戦開始演出。\n",
"original_baseline_sha256": "75e4bdbdeb379c9269553f0c776bdb9cc96e01f2d6aa5ca650d132c63a8aeb1a",
"plan_work": "配置、無効ドロップ、モーダル、停止、再開、フキダシ、ログ、カットインを整える。",
"plan_acceptance": "無効操作を説明し、クリック代替と日本語入力・表示倍率を確認する。",
"conditions": [
{
"id": "placement_and_invalid_drop",
"required": "Original grid snap and connection warnings; valid placement commits once, invalid placement/assignment explains the actual rejection, leaves model/ledger/selection intact, supports equivalent click-based operation."
},
{
"id": "modal_and_pause_resume",
"required": "Modal open/cancel/confirm, input focus, pausing and resuming preserve pending commands and save identity. Behind-dialog input cannot accidentally build or change assignment. Actual pause freezes simulation and ordinary resume continues exactly once."
},
{
"id": "unit_bubbles",
"required": "Unit overhead Zzz, anger, heart correspond to actual existing state. Observe source/model/render bindings and switching/removal without stale symbols; do not invent gameplay thresholds or hidden approved values."
},
{
"id": "event_log",
"required": "Actual production events reach bounded recent log in sequence, readable Japanese with correct amounts/time/reasons, no duplicate events across pause/load. Reuse accepted256-entry log only with matching source applicability."
},
{
"id": "important_warning_cutin",
"required": "Original important-warning UI for invasion start, duel, defense start is connected to actual existing signals/states with clear text, dismiss/stop/resume behavior; fixture-only signal injection is local testing, not proof ordinary production trigger. Missing upstream/catalog/assets remain explicit dependencies, do not claim complete."
},
{
"id": "click_alternatives",
"required": "Provide/verify click equivalents for necessary pointer/drag/modifier navigation and assignment operations. Reuse existing step/zoom/bounds mechanisms and scales; presentation controls must not change gameplay model. Manual P23 held gestures remain deferred, not waived or relabeled."
},
{
"id": "japanese_input_and_scale",
"required": "Actual OS Japanese composition/confirmation/cancel, persisted display of valid Japanese text and clear invalid-input feedback, representative window/display scales with clipping/focus/modal inspection. Use existing save display_name metadata for an editable optional Japanese save label if a suitable text field is absent; retain existing default name, cryptographic owned path, old-save preservation and cancel semantics. Do not add new gameplay numbers or rename save paths to user text. Explicitly classify macOS current development verification and still-required same-Windows-release platform acceptance; do not transfer prior Windows evidence."
},
{
"id": "final_validation_review",
"required": "Static/import, local/affected tests first; batch related fixes then run required full roster on exact final game/source/settings. Exact version/source hashes/config/inputs/artifacts and condition status. Independent separate Claude review with actual runtime evidence (not pending templates alone), current-main integration and final validation. All required conditions/evidence must hold to close P24-02; no partial credit, no counts as acceptance."
}
],
"scope": [
"scenes/main/main.gd",
"scenes/main/world_preview.gd",
"scripts/ui/event_log_dialog.gd",
"scripts/ui/manual_save_slots_dialog.gd",
"scripts/ui/operation_feedback.gd",
"scripts/ui/operation_feedback.gd.uid",
"scripts/ui/operations_camera_controls.gd",
"scripts/ui/operations_camera_controls.gd.uid",
"tests/ui/test_operations_notification.gd",
"tests/ui/test_operations_notification.gd.uid",
"tests/ui/fixtures/operations_os_observer.gd",
"tests/ui/fixtures/operations_os_observer.gd.uid",
"tools/check_p24_operations_evidence.py",
"tools/run_all_tests.py",
"tools/regression/suite-policy.json",
"docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json",
"docs/plans/original-completion-2026-10-02/execution/p24-02/**"
],
"dependency_completed": {
"id": "P24-03",
"closure": "docs/plans/original-completion-2026-10-02/execution/p24-03-formal-closure-r01/closure.json",
"sha256": "3d1f2ed3d312b3e64d491685fb4cd1bf2704f59fb014db606539080092c97868",
"integration_commit": "0a759f13aed03c05fd4a25ab4a2c26eefec94e3c"
},
"reuse": [
"P24-03 all7 accepted evidence and raw source_map1304/full17314810/local4suite/native OS separate17306->19943; reuse only if exact source/config/inputs applicable",
"P24-02 existing actual event_log bounded256 viewport8/314functional23parse evidence, root review8eec3a8492aa74f571eb21447c6911cea64d176e0f813152f226b2b4122ba387; old source limited, not current acceptance"
],
"implementation_order": [
"Read current source + all required conditions, separate already-valid proofs from missing implementation/proofs. Do not launch any duplicated worker.",
"Batch acceptance-required UI fixes in listed paths. Prefer existing licensed Kenney UI/assets first, no new assets/license paths without needing them. Japanese save-label uses existing metadata and owned encrypted-random directory rule.",
"Implement passive actual Main/runtime observer and strict verify-only acceptance index checker, source/artifact hashes, IDs/OS process/real save proof; malformed/missing/unknown proof fails. Committed pending templates must never masquerade as actual proof.",
"Add meaningful local UI tests with real model and ordinary cleanup/profile guards; preserve existing tests/strict marker policy. Root Runner owns heavier native/OS/full roster, no worker duplicated full regression.",
"Submit exact code and documented gaps/collector protocol; missing proof is not a reason to stop implementing independent authorized fixes. No game completion or acceptance from source alone."
],
"validator_contract": "tools/check_p24_operations_evidence.py --verify-only must read BUILD_OUTPUT_DIR/p24-operations-evidence/index.json, verify schema/source/artifact integrity and all8 conditions including real OS input/config and latest whole roster. Required tests/finalreview gate cannot be mocked or skipped. Machine verification success is distinct from later independent formal review/integration.",
"worker": "agy",
"forbidden": [
"No numeric gameplay defaults, unknown content/catalog/route details, proof fabrication or existing-test deletion/weakening",
"No live player data edits, credential reads/copies/API billing switch, Git push/commit/main edit by worker",
"No edits outside allowed scope or duplicate game/cross-project workers",
"No treating a required missing asset/event/upstream specification as satisfied; continue independent work and report exact dependency"
],
"platform_note": "P24-03 closure does not accept Windows. P24-02 must map platform-specific conditions explicitly and retain same Windows candidate device acceptance in final plan; only genuinely Windows-dependent subconditions are unavailable on this Mac, other work proceeds."
}
+122 -2
View File
@@ -22,6 +22,8 @@ const SupplyManagementPanelScript := preload("res://scripts/ui/supply_management
const BusinessManagementPanelScript := preload("res://scripts/ui/business_management_panel.gd")
const DefenseManagementPanelScript := preload("res://scripts/ui/defense_management_panel.gd")
const WorldPreviewScript := preload("res://scenes/main/world_preview.gd")
const OperationsCameraControlsScript := preload("res://scripts/ui/operations_camera_controls.gd")
const OperationFeedbackScript := preload("res://scripts/ui/operation_feedback.gd")
const LayerSkin := preload("res://scripts/ui/layer_interface_skin.gd")
const DefaultVisualTheme := preload("res://assets/themes/japanese_fantasy/visual-theme-v1.tres")
var _last_presented_close_minute := 0
@@ -145,6 +147,8 @@ var alert_icon_node: TextureRect
var resource_labels: Dictionary = {}
var resource_icon_nodes: Dictionary = {}
var world_preview: WorldPreview
var operations_camera_controls: OperationsCameraControls
var important_warning_cutin: OperationFeedback.ImportantWarningCutinDialog
var front_button: Button
var back_button: Button
var excavation_button: Button
@@ -206,6 +210,7 @@ var _pending_excavation_commands: Dictionary = {}
var _pending_hire_commands: Dictionary = {}
var _staff_candidate_panel_open := false
var _operations_start_report: Dictionary = {}
var _subscribed_event_stream: Variant = null
func _ready() -> void:
@@ -244,6 +249,7 @@ func _ready() -> void:
func _exit_tree() -> void:
_unbind_domain_event_stream()
var tree := get_tree()
if tree != null:
if tree.root.close_requested.is_connected(_on_window_close_requested):
@@ -402,6 +408,18 @@ func _build_interface() -> void:
management_panel.offset_bottom = -8
management_panel.visible = false
operations_camera_controls = OperationsCameraControlsScript.new()
operations_camera_controls.name = "OperationsCameraControls"
operations_camera_controls.configure(world_preview, self)
world_preview.add_child(operations_camera_controls)
operations_camera_controls.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_RIGHT)
operations_camera_controls.offset_right = -12
operations_camera_controls.offset_bottom = -12
important_warning_cutin = OperationFeedbackScript.ImportantWarningCutinDialog.new()
important_warning_cutin.visibility_changed.connect(_update_modal_input_guard)
add_child(important_warning_cutin)
layout.add_child(_create_bottom_bar())
@@ -441,6 +459,7 @@ func _create_top_bar() -> Control:
_set_status(message,success)
_refresh_dashboard()
)
rank_dialog.visibility_changed.connect(_update_modal_input_guard)
add_child(rank_dialog)
rank_button.pressed.connect(rank_dialog.show_rank)
@@ -756,6 +775,7 @@ func _create_bottom_bar() -> Control:
row.add_child(management_toggle_button)
event_log_dialog = EventLogDialogScript.new()
event_log_dialog.configure(_runtime)
event_log_dialog.visibility_changed.connect(_update_modal_input_guard)
add_child(event_log_dialog)
var event_log_button := _button("記録", event_log_dialog.show_events, 62)
event_log_button.name = "EventLogButton"
@@ -947,6 +967,9 @@ func _connect_signals() -> void:
world_preview.staff_assignment_dropped.connect(func(data: Dictionary, facility_id: StringName) -> void:
staff_management_panel.assign_from_drop(data, facility_id)
)
world_preview.staff_assignment_clicked.connect(func(staff_id: StringName, facility_id: StringName) -> void:
assign_staff_by_click(facility_id, staff_id)
)
world_preview.legacy_stair_selected.connect(_on_legacy_stair_selected)
world_preview.legacy_stair_selected.connect(_on_audio_world_selection)
world_preview.construction_selected.connect(_on_construction_selected)
@@ -965,6 +988,7 @@ func _connect_signals() -> void:
_runtime.connect(&"save_operation_completed", Callable(self, "_on_save_operation_completed"))
if _runtime.has_signal(&"command_completed"):
_runtime.connect(&"command_completed", Callable(self, "_on_command_completed"))
_bind_domain_event_stream()
func _populate_build_palette() -> void:
@@ -2276,6 +2300,7 @@ func _ensure_manual_save_slots() -> Variant:
manual_save_slots_dialog = ManualSaveSlotsDialogScript.new()
manual_save_slots_dialog.connect(&"new_slot_requested", _save_new_manual_slot)
manual_save_slots_dialog.connect(&"load_slot_requested", _load_selected_manual_slot)
manual_save_slots_dialog.visibility_changed.connect(_update_modal_input_guard)
add_child(manual_save_slots_dialog)
return manual_save_slots_dialog
@@ -2352,6 +2377,12 @@ func _apply_loaded_interface(result: Dictionary) -> void:
_restore_product_interface_selection(result.get(&"manifest", {}))
_refresh_dashboard()
_update_pause_and_speed()
_bind_domain_event_stream()
func _valid_interface_selection_version(value: Variant) -> bool:
# JSON numbers may be decoded as floats; booleans are never versions.
return typeof(value) in [TYPE_INT, TYPE_FLOAT] and value == 1
func _restore_interface_selection(manifest: Variant) -> void:
@@ -2360,7 +2391,7 @@ func _restore_interface_selection(manifest: Variant) -> void:
if not manifest is Dictionary:
return
var selection: Variant = manifest.get("interface_selection", {})
if not selection is Dictionary or selection.get("version", 0) != 1:
if not selection is Dictionary or not _valid_interface_selection_version(selection.get("version", 0)):
return
var kind_value: Variant = selection.get("kind", "")
var id_value: Variant = selection.get("id", "")
@@ -2403,7 +2434,7 @@ func _restore_actor_interface_selection(manifest: Variant) -> void:
if not manifest is Dictionary:
return
var selection: Variant = manifest.get("interface_selection", {})
if not selection is Dictionary or selection.get("version", 0) != 1:
if not selection is Dictionary or not _valid_interface_selection_version(selection.get("version", 0)):
return
var staff_id: Variant = selection.get("staff_id", "")
if staff_id is String and not staff_id.is_empty() and staff_id.length() <= 256 and _service(&"staff_service").has_staff(StringName(staff_id)):
@@ -2932,6 +2963,7 @@ func _ensure_guest_observer() -> GuestObservationPanel:
content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
guest_observer.add_child(scroll)
scroll.add_child(content)
guest_observer.visibility_changed.connect(_update_modal_input_guard)
add_child(guest_observer)
return guest_observer.find_child("GuestObservationPanel", true, false) as GuestObservationPanel
@@ -2959,6 +2991,7 @@ func _preview_dimension_migration() -> void:
dimension_migration_dialog.cancel_button_text="戻る"
dimension_migration_dialog.confirmed.connect(_confirm_dimension_migration)
dimension_migration_dialog.canceled.connect(func(): _dimension_migration_digest="")
dimension_migration_dialog.visibility_changed.connect(_update_modal_input_guard)
add_child(dimension_migration_dialog)
var lines: PackedStringArray=[]
for change: Dictionary in report[&"changes"]:
@@ -3078,3 +3111,90 @@ func _submit_ui_command(
if _audio_feedback != null:
return _audio_feedback.submit_command(command_type, payload, actor_id, target_id, priority, execute_at, idempotency_key)
return _runtime.call(&"submit_command", command_type, payload, actor_id, target_id, priority, execute_at, idempotency_key)
func assign_staff_by_click(facility_id: StringName, staff_id: StringName = &"") -> void:
if staff_management_panel == null:
return
var target_staff := staff_id
if target_staff.is_empty():
target_staff = StringName(staff_management_panel.get("selected_staff_id"))
if target_staff.is_empty():
_set_status("配属する従業員を一覧から選択してください。", false)
return
var queue_id: int = int(staff_management_panel.get("_queue_instance_id"))
var template_val: Variant = staff_management_panel.get("_draft_template_id")
var drop_data := {
&"kind": &"staff_assignment",
&"staff_id": target_staff,
&"template_id": template_val if template_val != null else &"DAY",
&"queue_instance_id": queue_id,
}
staff_management_panel.assign_from_drop(drop_data, facility_id)
func _update_modal_input_guard() -> void:
if world_preview == null:
return
var any_visible := false
for dialog: Variant in [manual_save_slots_dialog, event_log_dialog, rank_dialog, dimension_migration_dialog, guest_observer, defense_manager, important_warning_cutin]:
if dialog != null and is_instance_valid(dialog) and dialog is Window and bool(dialog.visible):
any_visible = true
break
world_preview.modal_input_blocked = any_visible
func _ensure_important_warning_cutin() -> OperationFeedback.ImportantWarningCutinDialog:
if important_warning_cutin == null:
important_warning_cutin = OperationFeedbackScript.ImportantWarningCutinDialog.new()
important_warning_cutin.visibility_changed.connect(_update_modal_input_guard)
add_child(important_warning_cutin)
return important_warning_cutin
func show_warning_cutin(kind: StringName, heading: String, message: String) -> void:
_ensure_important_warning_cutin().show_cutin(kind, heading, message)
func _bind_domain_event_stream() -> void:
_unbind_domain_event_stream()
if _runtime == null or not is_instance_valid(_runtime):
return
var stream: Variant = _runtime.get(&"domain_events") if _runtime is Object and "domain_events" in _runtime else null
if stream != null and is_instance_valid(stream) and stream.has_method(&"subscribe"):
stream.subscribe(DomainEventStream.WILDCARD_EVENT_TYPE, &"main_scene", Callable(self, "_on_domain_event_published"))
_subscribed_event_stream = stream
func _unbind_domain_event_stream() -> void:
if _subscribed_event_stream != null and is_instance_valid(_subscribed_event_stream):
if _subscribed_event_stream.has_method(&"unsubscribe"):
_subscribed_event_stream.unsubscribe(DomainEventStream.WILDCARD_EVENT_TYPE, &"main_scene")
_subscribed_event_stream = null
func _on_domain_event_published(event: Variant) -> void:
var event_type: StringName = &""
var payload: Dictionary = {}
if event is Dictionary:
event_type = StringName(event.get(&"event_type", event.get("event_type", &"")))
var raw_payload: Variant = event.get(&"payload", event.get("payload", {}))
if raw_payload is Dictionary:
payload = raw_payload
elif event is Object:
if "event_type" in event:
event_type = StringName(event.get(&"event_type"))
if "payload" in event:
var raw_payload: Variant = event.get(&"payload")
if raw_payload is Dictionary:
payload = raw_payload
match event_type:
&"DEFENSE_RAID_CREATED":
show_warning_cutin(OperationFeedbackScript.CUTIN_DEFENSE, "防衛戦開始演出", "敵の襲撃が発生しました!防衛部隊を配置してください。")
&"RELATIONSHIP_SHIELD_CHANGED":
# Existing love-shield vanishing transition
var old_val: int = int(payload.get(&"old_value", payload.get("old_value", -1)))
var new_val: int = int(payload.get(&"new_value", payload.get("new_value", -1)))
if old_val > 0 and new_val == 0:
show_warning_cutin(OperationFeedbackScript.CUTIN_INVASION, "重要警告:愛の盾消失", "愛の盾が消失しました。人物関係を確認してください。")
+70 -1
View File
@@ -3,6 +3,7 @@ extends Control
signal facility_selected(instance_id: StringName)
signal staff_assignment_dropped(data: Dictionary, facility_id: StringName)
signal staff_assignment_clicked(staff_id: StringName, facility_id: StringName)
signal construction_selected(job_id: StringName)
signal legacy_stair_selected(stair_id: StringName)
signal cell_clicked(cell: Vector3i)
@@ -15,6 +16,7 @@ enum LayerMode {
BACK,
}
const OperationFeedbackScript := preload("res://scripts/ui/operation_feedback.gd")
const RoomPresentation := preload("res://scripts/ui/room_presentation.gd")
const People := preload("res://scripts/ui/pixel_people.gd")
const PixelArt := preload("res://scripts/ui/pixel_tower_art.gd")
@@ -57,6 +59,11 @@ var zoom_level := 1.0
var pan_offset := Vector2.ZERO
var show_grid := false
var pan_mode := false
var modal_input_blocked := false:
set(value):
modal_input_blocked = value
if modal_input_blocked:
_end_pan_drag()
var _drawing := false
var _draw_cache: Dictionary = {}
@@ -420,6 +427,9 @@ func facility_at_canvas(canvas_position: Vector2) -> StringName:
func _gui_input(event: InputEvent) -> void:
if modal_input_blocked:
accept_event()
return
if event is InputEventMouseButton:
if not event.pressed and event.button_index == _drag_button:
_end_pan_drag()
@@ -1145,6 +1155,10 @@ func _draw_actors() -> void:
else:
position += _actor_offset(guest.current_cell,counts)
_draw_pixel_actor(position,key,false,int(guest.state),receiving,false,_guest_activity(guest))
var rel_guest: Variant = _guest_relationship(guest.guest_id)
var guest_bubble := OperationFeedbackScript.resolve_guest_bubble(guest, rel_guest)
if guest_bubble != OperationFeedbackScript.BUBBLE_NONE:
_draw_unit_bubble(position, guest_bubble)
var staff_service: Variant = _service(&"staff_service")
if staff_service != null and staff_service.has_method(&"get_staff_members"):
for staff: Variant in staff_service.call(&"get_staff_members"):
@@ -1171,7 +1185,12 @@ func _draw_actors() -> void:
position = _rescue_treatment_position(staff.staff_id, position)
if position.is_finite(): _draw_pixel_actor(position,key,true,int(staff.state),false,false)
continue
_draw_pixel_actor(_queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts))),key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff))
var staff_draw_pos: Vector2 = _queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts)))
_draw_pixel_actor(staff_draw_pos,key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff))
var rel_staff: Variant = _staff_relationship(staff.staff_id)
var staff_bubble := OperationFeedbackScript.resolve_staff_bubble(staff, rel_staff)
if staff_bubble != OperationFeedbackScript.BUBBLE_NONE:
_draw_unit_bubble(staff_draw_pos, staff_bubble)
for key: Variant in _actor_tracks.keys():
if not alive.has(key):
_actor_tracks.erase(key)
@@ -1300,6 +1319,54 @@ func _draw_pixel_actor(center: Vector2, key: String, staff: bool, state: int, re
People.draw(self, at, p, mode, frame, variant, staff, float(track.get(&"direction", 1.0)) < 0.0)
func _draw_unit_bubble(actor_center: Vector2, bubble_type: StringName) -> void:
var world_pixel := _cell_dimensions().x / 16.0
var p := maxf(floorf(world_pixel * 0.75), 1.0) if world_pixel >= 2.0 else world_pixel * 0.75
var bubble_pos := actor_center + Vector2(-9 * p, _cell_dimensions().y * 0.5 - 3 * world_pixel - 32 * p)
var bubble_size := Vector2(18 * p, 13 * p)
var rect := Rect2(bubble_pos.round(), bubble_size.round())
draw_rect(rect, Color("ffffff", 0.94))
draw_rect(rect, Color("303c40", 0.85), false, 1.0)
var tri_p1 := Vector2(rect.get_center().x - 2.5 * p, rect.end.y)
var tri_p2 := Vector2(rect.get_center().x + 2.5 * p, rect.end.y)
var tri_p3 := Vector2(rect.get_center().x, rect.end.y + 3.5 * p)
draw_colored_polygon(PackedVector2Array([tri_p1, tri_p2, tri_p3]), Color("ffffff", 0.94))
draw_line(tri_p1, tri_p3, Color("303c40", 0.85), 1.0)
draw_line(tri_p2, tri_p3, Color("303c40", 0.85), 1.0)
var font := ThemeDB.fallback_font
var font_size := clampi(int(9 * p), 9, 14)
match bubble_type:
OperationFeedbackScript.BUBBLE_ZZZ:
draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "Zzz", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("2d68c4"))
OperationFeedbackScript.BUBBLE_ANGER:
draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "💢", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("d63031"))
OperationFeedbackScript.BUBBLE_HEART:
draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "♥", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("e84393"))
func _staff_relationship(staff_id: StringName) -> Variant:
var service: Variant = _service(&"relationship_service")
if service == null or not service.has_method(&"get_relationships"):
return null
for rel_val: Variant in service.call(&"get_relationships"):
if rel_val is RelationshipState:
if rel_val.actor_a_id == staff_id or rel_val.actor_b_id == staff_id:
return rel_val
return null
func _guest_relationship(guest_id: StringName) -> Variant:
var service: Variant = _service(&"relationship_service")
if service == null or not service.has_method(&"get_relationships"):
return null
for rel_val: Variant in service.call(&"get_relationships"):
if rel_val is RelationshipState:
if rel_val.actor_a_id == guest_id or rel_val.actor_b_id == guest_id:
return rel_val
return null
func _draw_excavation_jobs() -> void:
var excavation: Variant = _service(&"excavation_service")
if excavation == null or not excavation.has_method(&"get_jobs"):
@@ -1594,6 +1661,8 @@ func _resting_staff_position(staff_id: StringName, fallback: Vector2) -> Vector2
func _can_drop_data(_position: Vector2, data: Variant) -> bool:
if modal_input_blocked:
return false
return data is Dictionary and data.get(&"kind", &"") == &"staff_assignment" and data.get(&"staff_id", &"") is StringName
+34 -4
View File
@@ -158,8 +158,18 @@ func get_event_rows() -> Array[Dictionary]:
var stream: Variant = _runtime.get("domain_events")
if stream == null or not stream.has_method("recent_events"):
return rows
var seen_ids: Dictionary = {}
for event: SimulationDomainEvent in stream.recent_events():
rows.append(event.to_dictionary())
var dict: Dictionary = event.to_dictionary()
var eid: String = String(dict.get("event_id", ""))
if not eid.is_empty():
if seen_ids.has(eid):
continue
seen_ids[eid] = true
rows.append(dict)
# Bound to recent 256 entries in strict sequence
if rows.size() > 256:
rows = rows.slice(rows.size() - 256)
return rows
@@ -201,9 +211,12 @@ func _select_row(index: int) -> void:
var row: Dictionary = _rows[index]
_selected_event_id = String(row["event_id"])
var presentation := get_event_presentation(row)
_details.text = "%s\n%s\n対象(現在の名称):%s" % [
var detail_text := "%s\n%s\n対象(現在の名称):%s" % [
presentation[&"time"], presentation[&"title"], presentation[&"targets"],
]
if not String(presentation.get(&"extra", "")).is_empty():
detail_text += "\n" + String(presentation[&"extra"])
_details.text = detail_text
_details.tooltip_text = presentation[&"supplement"]
@@ -225,13 +238,30 @@ func get_event_presentation(row: Dictionary) -> Dictionary:
names.append(resolved)
if unknown_count > 0:
names.append("名称を確認できない対象(%d件)" % unknown_count)
var title_text := String(EVENT_NAMES.get(StringName(row.get("event_type", "")), "未識別の記録"))
var extras: Array[String] = []
# Check for amount / gold / cost
var payload: Dictionary = row.get("payload", {}) if row.get("payload") is Dictionary else {}
var amount_val: Variant = row.get("amount", payload.get("amount", row.get("gold", payload.get("gold", null))))
if amount_val != null and (amount_val is int or amount_val is float) and int(amount_val) != 0:
extras.append("金額:G%+d" % int(amount_val) if int(amount_val) < 0 else "金額:G%d" % int(amount_val))
# Check for reason
var reason_val: Variant = row.get("reason", payload.get("reason", row.get("error", payload.get("error", ""))))
if not String(reason_val).is_empty():
extras.append("理由:%s" % String(reason_val))
return {
&"title": String(EVENT_NAMES.get(StringName(row.get("event_type", "")), "未識別の記録")),
&"title": title_text,
&"time": _format_minute(int(row.get("absolute_minute", 0))),
&"targets": "、".join(names) if not names.is_empty() else "対象の指定なし",
&"supplement": "補足情報\n記録:%s\n種別:%s\n対象:%s\n発行元:%s" % [
&"extra": " ".join(extras),
&"supplement": "補足情報\n記録:%s\n種別:%s\n対象:%s\n発行元:%s%s" % [
row.get("event_id", ""), row.get("event_type", ""),
"、".join(raw_ids), row.get("source_service", ""),
"\n" + "\n".join(extras) if not extras.is_empty() else "",
],
}
+4 -1
View File
@@ -90,7 +90,10 @@ func refresh(select_first_if_empty: bool = true) -> void:
if not _ids.has(selected_guest_id) and (not selected_guest_id.is_empty() or select_first_if_empty):
selected_guest_id = _ids[0] if not _ids.is_empty() else &""
guests.disabled = _ids.is_empty()
if not _ids.is_empty(): guests.select(_ids.find(selected_guest_id))
if _ids.has(selected_guest_id):
guests.select(_ids.find(selected_guest_id))
else:
guests.select(-1)
var report: Dictionary = service.get_observation(selected_guest_id)
for need: StringName in bars:
bars[need].value = report.get(&"needs", {}).get(need, 0)
+81 -6
View File
@@ -12,19 +12,38 @@ var _new_path := ""
var _new_name := ""
var _message: Label
var _slots: ItemList
var _label_edit: LineEdit
var _validation_label: Label
var _rows: Array[Dictionary] = []
func _ready() -> void:
name = "ManualSaveSlotsDialog"
cancel_button_text = "戻る"
var column := VBoxContainer.new()
column.custom_minimum_size = Vector2(460, 220)
column.custom_minimum_size = Vector2(460, 240)
add_child(column)
_message = Label.new()
_message.name = "ManualSaveSlotsMessage"
_message.custom_minimum_size.x = 460
_message.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
column.add_child(_message)
_label_edit = LineEdit.new()
_label_edit.name = "ManualSaveLabelEdit"
_label_edit.custom_minimum_size = Vector2(460, 32)
_label_edit.placeholder_text = "セーブデータの名前(日本語入力可)"
_label_edit.max_length = 60
_label_edit.text_changed.connect(_on_label_text_changed)
column.add_child(_label_edit)
_label_edit.hide()
_validation_label = Label.new()
_validation_label.name = "ManualSaveValidationLabel"
_validation_label.add_theme_font_size_override("font_size", 11)
_validation_label.add_theme_color_override("font_color", Color("e06c75"))
column.add_child(_validation_label)
_validation_label.hide()
_slots = ItemList.new()
_slots.name = "ManualSaveSlotsList"
_slots.custom_minimum_size = Vector2(460, 150)
@@ -50,9 +69,20 @@ func show_new_save(current_path: String) -> void:
_new_name = "手動保存 " + Time.get_datetime_string_from_system(false, true)
var fresh: bool = not _new_path.is_empty() and is_fresh_slot(_new_path)
get_ok_button().disabled = not fresh
_message.text = "今の進行は、元の保存とは別に保存します。\n元の保存は残り、あとから読込一覧で選べます。\n\n" + _new_name if fresh else "新しい保存先を用意できませんでした。\n元の保存は変更していません。"
popup_centered(Vector2i(520, 300))
get_cancel_button().grab_focus()
_message.text = "今の進行は、元の保存とは別に保存します。\n元の保存は残り、あとから読込一覧で選べます。\n保存の名前(日本語入力対応):" if fresh else "新しい保存先を用意できませんでした。\n元の保存は変更していません。"
if fresh:
_label_edit.show()
_label_edit.text = _new_name
_validation_label.hide()
else:
_label_edit.hide()
_validation_label.hide()
popup_centered(Vector2i(520, 320))
if fresh:
_label_edit.grab_focus()
_label_edit.select_all()
else:
get_cancel_button().grab_focus()
func show_load_slots(current_path: String) -> void:
_clear_choice()
@@ -61,6 +91,8 @@ func show_load_slots(current_path: String) -> void:
title = "保存スロットを選んで読込"
ok_button_text = "選んで読込"
_message.text = "読み込む保存を選んでください。\n現在の未保存の進行は、選んだ保存で置き換わります。"
_label_edit.hide()
_validation_label.hide()
_slots.show()
_slots.clear()
_rows = read_slot_rows(_directory)
@@ -121,12 +153,48 @@ static func read_slot_rows(directory: String) -> Array[Dictionary]:
rows.append({&"path": path, &"label": label, &"loadable": loadable})
return rows
static func is_valid_label_text(text: String) -> Dictionary:
if text.length() > 60:
return {&"ok": false, &"error": "セーブ名は60文字以内で入力してください。"}
for i in range(text.length()):
var c := text.unicode_at(i)
if c < 32 and c != 9:
return {&"ok": false, &"error": "制御文字は入力できません。"}
return {&"ok": true, &"error": ""}
func _on_label_text_changed(new_text: String) -> void:
if _mode != "new":
return
var fresh: bool = not _new_path.is_empty() and is_fresh_slot(_new_path)
if not fresh:
get_ok_button().disabled = true
return
var check := is_valid_label_text(new_text)
if not bool(check[&"ok"]):
_validation_label.text = String(check[&"error"])
_validation_label.show()
get_ok_button().disabled = true
else:
_validation_label.hide()
get_ok_button().disabled = false
func _select_slot(index: int) -> void:
get_ok_button().disabled = index < 0 or index >= _rows.size() or not _rows[index][&"loadable"]
func _confirm_choice() -> void:
if _mode == "new" and is_fresh_slot(_new_path):
new_slot_requested.emit(_new_path, _new_name)
if _mode == "new":
if not is_fresh_slot(_new_path):
_clear_choice()
return
var raw_name := _label_edit.text.strip_edges() if _label_edit != null else ""
var check := is_valid_label_text(raw_name)
if not bool(check[&"ok"]):
_validation_label.text = String(check[&"error"])
_validation_label.show()
get_ok_button().disabled = true
return
var final_name := raw_name if not raw_name.is_empty() else _new_name
new_slot_requested.emit(_new_path, final_name)
elif _mode == "load":
var selected: PackedInt32Array = _slots.get_selected_items()
if selected.size() == 1 and selected[0] >= 0 and selected[0] < _rows.size() and _rows[selected[0]][&"loadable"]:
@@ -137,8 +205,15 @@ func _clear_choice() -> void:
_mode = ""
_new_path = ""
_new_name = ""
if _label_edit != null:
_label_edit.text = ""
_label_edit.hide()
if _validation_label != null:
_validation_label.hide()
_rows.clear()
func apply_layer_skin(layer: int) -> void:
if _message != null: LayerSkin.register_text(_message, layer)
if _validation_label != null: LayerSkin.register_status_text(_validation_label, false, layer)
LayerSkin.apply_tree(self, layer)
+219
View File
@@ -0,0 +1,219 @@
class_name OperationFeedback
extends RefCounted
## Operation and notification UX helpers: rejection explanations,
## overhead unit bubbles, and important warning cut-in dialogs.
## Presentation-only: never modifies the simulation model or ledger directly.
const BUBBLE_NONE := &""
const BUBBLE_ZZZ := &"zzz"
const BUBBLE_ANGER := &"anger"
const BUBBLE_HEART := &"heart"
const CUTIN_INVASION := &"invasion"
const CUTIN_DEFENSE := &"defense"
const CUTIN_DUEL := &"duel"
const ASSIGNMENT_REASONS := {
&"STAFF_NOT_FOUND": "従業員が見つかりません。",
&"FACILITY_NOT_FOUND": "配属先が見つかりません。施設が完成しているか確認してください。",
&"ROLE_MISMATCH": "この施設には役割が合いません。必要な職種を確認してください。",
&"NO_ACCESS": "従業員が配属先へ移動できません。通路と従業員ドアを確認してください。",
&"NO_PATH": "従業員が配属先へ移動できません。通路と従業員ドアを確認してください。",
&"STAFF_BUSY": "作業中のため変更できません。作業完了後に再試行してください。",
&"CAPACITY_EXCEEDED": "配属先の人員上限を超えています。",
&"FACILITY_FULL": "配属先の人員上限を超えています。",
&"NO_ROUTE": "配属先へ移動できません。通路と従業員ドアを確認してください。",
&"INVALID_TEMPLATE": "日勤または夜勤を選択してください。",
&"STAFF_UNAVAILABLE": "この従業員は現在、勤務を変更できません。状態をご確認ください。",
&"DEFENSE_LOCKED": "防衛参加中は勤務を変更できません。",
&"EVENT_LOCKED": "イベント参加中は勤務を変更できません。",
&"FACILITY_UNSTAFFED": "この施設は人員を配属する施設ではありません。",
&"SHIFT_CONFLICT": "既存の勤務と時間が重なります。勤務予定を確認してください。",
&"BANKRUPT": "運営終了後は配属を変更できません。",
}
const PLACEMENT_REASONS := {
"INVALID_DEFINITION": "施設データが不正です",
"MISSING_DEPENDENCY": "建設システムを利用できません",
"OUT_OF_BOUNDS": "世界の範囲外です",
"ZONE_MISMATCH": "地上・境界・地下の条件が合いません",
"LAYER_MISMATCH": "表層/裏層が合いません",
"OVERLAP": "ほかの施設または工事と重なります",
"UNSUPPORTED": "構造を支えられません",
"INSUFFICIENT_RESOURCES": "建設資源が不足しています",
}
static func resolve_staff_bubble(staff: Variant, relationship: Variant = null) -> StringName:
if staff == null:
return BUBBLE_NONE
var state_val: int = int(staff.get(&"state")) if staff is Object and "state" in staff else (int(staff.get("state", -1)) if staff is Dictionary else -1)
# Sleeping, incapacitated, or on break corresponds to Zzz
if state_val in [StaffRuntime.State.SLEEPING, StaffRuntime.State.INCAPACITATED, StaffRuntime.State.BREAKING]:
return BUBBLE_ZZZ
# Strike or fleeing corresponds to anger (no fictional morale thresholds)
if state_val in [StaffRuntime.State.STRIKE, StaffRuntime.State.FLEEING]:
return BUBBLE_ANGER
# Heart must derive from actual existing intimacy/love-shield state with source authority
if relationship != null:
var shield := 0
var route_lock_val := 0
var affection := 0
if relationship is RelationshipState:
shield = relationship.shield_current
route_lock_val = int(relationship.route_lock)
affection = int(relationship.metrics.get(&"affection", 0))
elif relationship is Dictionary:
shield = int(relationship.get(&"shield_current", relationship.get("shield_current", 0)))
route_lock_val = int(relationship.get(&"route_lock", relationship.get("route_lock", 0)))
var m: Variant = relationship.get(&"metrics", relationship.get("metrics", {}))
if m is Dictionary:
affection = int(m.get(&"affection", m.get("affection", 0)))
elif relationship is Object:
if "shield_current" in relationship:
shield = int(relationship.get(&"shield_current"))
if "route_lock" in relationship:
route_lock_val = int(relationship.get(&"route_lock"))
if "metrics" in relationship:
var m: Variant = relationship.get(&"metrics")
if m is Dictionary:
affection = int(m.get(&"affection", m.get("affection", 0)))
if shield > 0 or affection > 0 or route_lock_val in [RelationshipState.RouteLock.EXCLUSIVE_PENDING, RelationshipState.RouteLock.EXCLUSIVE_LOCKED]:
return BUBBLE_HEART
# Without authoritative relationship intimacy / love-shield state, mapping is unmapped/none
return BUBBLE_NONE
static func resolve_guest_bubble(guest: Variant, relationship: Variant = null) -> StringName:
if guest == null:
return BUBBLE_NONE
var state_val: int = int(guest.get(&"state")) if guest is Object and "state" in guest else (int(guest.get("state", -1)) if guest is Dictionary else -1)
# Incapacitated corresponds to Zzz
if state_val == GuestRuntime.State.INCAPACITATED:
return BUBBLE_ZZZ
# Abandoning, fleeing, interrupted, or out of patience in queue corresponds to anger
if state_val in [GuestRuntime.State.ABANDONING, GuestRuntime.State.FLEEING, GuestRuntime.State.INTERRUPTED]:
return BUBBLE_ANGER
var patience: int = int(guest.get(&"patience_remaining")) if guest is Object and "patience_remaining" in guest else (int(guest.get("patience_remaining", 1)) if guest is Dictionary else 1)
if state_val == GuestRuntime.State.QUEUING and patience <= 0:
return BUBBLE_ANGER
# Heart must derive from actual existing intimacy/love-shield state with source authority
if relationship != null:
var shield := 0
var route_lock_val := 0
var affection := 0
if relationship is RelationshipState:
shield = relationship.shield_current
route_lock_val = int(relationship.route_lock)
affection = int(relationship.metrics.get(&"affection", 0))
elif relationship is Dictionary:
shield = int(relationship.get(&"shield_current", relationship.get("shield_current", 0)))
route_lock_val = int(relationship.get(&"route_lock", relationship.get("route_lock", 0)))
var m: Variant = relationship.get(&"metrics", relationship.get("metrics", {}))
if m is Dictionary:
affection = int(m.get(&"affection", m.get("affection", 0)))
elif relationship is Object:
if "shield_current" in relationship:
shield = int(relationship.get(&"shield_current"))
if "route_lock" in relationship:
route_lock_val = int(relationship.get(&"route_lock"))
if "metrics" in relationship:
var m: Variant = relationship.get(&"metrics")
if m is Dictionary:
affection = int(m.get(&"affection", m.get("affection", 0)))
if shield > 0 or affection > 0 or route_lock_val in [RelationshipState.RouteLock.EXCLUSIVE_PENDING, RelationshipState.RouteLock.EXCLUSIVE_LOCKED]:
return BUBBLE_HEART
return BUBBLE_NONE
static func get_assignment_rejection_text(reason: StringName) -> String:
return ASSIGNMENT_REASONS.get(reason, "配属を変更できませんでした。条件を確認してください。")
static func get_placement_rejection_text(report: Dictionary) -> String:
var reasons: Array = report.get(&"reasons", report.get("reasons", []))
var error_codes: Array = report.get(&"error_codes", report.get("error_codes", []))
if not error_codes.is_empty():
var code: String = String(error_codes[0])
if PLACEMENT_REASONS.has(code):
return PLACEMENT_REASONS[code]
if not reasons.is_empty():
return String(reasons[0])
return "指定したマスには建築できません。配置条件を確認してください。"
## Important warning cut-in dialog: invasion start, duel, defense start
class ImportantWarningCutinDialog extends AcceptDialog:
signal cutin_dismissed(kind: StringName)
var _current_kind: StringName = &""
var _queue: Array[Dictionary] = []
var _cutin_active := false
var _pre_cutin_user_paused := false
var _title_label: Label
var _body_label: Label
func _init() -> void:
name = "ImportantWarningCutinDialog"
title = "重要警告"
ok_button_text = "確認"
unresizable = true
min_size = Vector2i(480, 200)
size = Vector2i(480, 200)
var vbox := VBoxContainer.new()
vbox.custom_minimum_size = Vector2(460, 140)
vbox.add_theme_constant_override("separation", 10)
add_child(vbox)
_title_label = Label.new()
_title_label.name = "CutinTitleLabel"
_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_title_label.add_theme_font_size_override("font_size", 16)
_title_label.add_theme_color_override("font_color", Color("ff7b7b"))
vbox.add_child(_title_label)
_body_label = Label.new()
_body_label.name = "CutinBodyLabel"
_body_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_body_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
_body_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_body_label.add_theme_font_size_override("font_size", 13)
vbox.add_child(_body_label)
confirmed.connect(_on_dismissed)
canceled.connect(_on_dismissed)
close_requested.connect(_on_dismissed)
func show_cutin(kind: StringName, heading: String, message: String) -> void:
if _cutin_active:
_queue.append({&"kind": kind, &"heading": heading, &"message": message})
return
_cutin_active = true
var clock := get_node_or_null("/root/GameClock")
if clock != null:
_pre_cutin_user_paused = bool(clock.get("paused"))
if not _pre_cutin_user_paused:
clock.set_paused(true)
_display_cutin(kind, heading, message)
func _display_cutin(kind: StringName, heading: String, message: String) -> void:
_current_kind = kind
_title_label.text = heading
_body_label.text = message
popup_centered(Vector2i(500, 220))
get_ok_button().grab_focus()
func _on_dismissed() -> void:
var kind := _current_kind
_current_kind = &""
cutin_dismissed.emit(kind)
if not _queue.is_empty():
var next: Dictionary = _queue.pop_front()
_display_cutin(next[&"kind"], next[&"heading"], next[&"message"])
else:
_cutin_active = false
var clock := get_node_or_null("/root/GameClock")
if clock != null and not _pre_cutin_user_paused:
clock.set_paused(false)
+1
View File
@@ -0,0 +1 @@
uid://c8p24opfeedback
+145
View File
@@ -0,0 +1,145 @@
class_name OperationsCameraControls
extends HBoxContainer
## Presentation-only camera and navigation controls providing click alternatives
## for pointer drag, wheel scroll, and modifier-based navigation.
## Does not alter the underlying simulation model or game state.
const LayerSkin := preload("res://scripts/ui/layer_interface_skin.gd")
signal zoom_in_requested
signal zoom_out_requested
signal reset_view_requested
signal pan_requested(direction: Vector2)
signal grid_toggle_requested
signal layer_toggle_requested
var zoom_in_button: Button
var zoom_out_button: Button
var reset_button: Button
var pan_up_button: Button
var pan_down_button: Button
var pan_left_button: Button
var pan_right_button: Button
var grid_button: Button
var layer_button: Button
var _world_preview: Variant
var _main_scene: Variant
var _buttons: Array[Button] = []
func _init() -> void:
name = "OperationsCameraControls"
add_theme_constant_override("separation", 4)
func _ready() -> void:
_build_ui()
func configure(preview: Variant, main_scene: Variant = null) -> void:
_world_preview = preview
_main_scene = main_scene
func _build_ui() -> void:
# Navigation Pan Buttons
pan_left_button = _create_button("◀", "左へ移動", func() -> void: _on_pan_step(Vector2(1, 0)))
pan_up_button = _create_button("▲", "上へ移動", func() -> void: _on_pan_step(Vector2(0, 1)))
pan_down_button = _create_button("▼", "下へ移動", func() -> void: _on_pan_step(Vector2(0, -1)))
pan_right_button = _create_button("▶", "右へ移動", func() -> void: _on_pan_step(Vector2(-1, 0)))
# Zoom Buttons
zoom_in_button = _create_button("+", "拡大表示", _on_zoom_in)
zoom_out_button = _create_button("-", "縮小表示", _on_zoom_out)
# Reset / Focus
reset_button = _create_button("全体", "表示範囲全体を収める", _on_reset_view)
# Grid & Layer Toggle
grid_button = _create_button("マス目", "グリッド線の表示/非表示を切り替え", _on_grid_toggle)
layer_button = _create_button("表/裏", "フロアの表層/裏層を切り替え", _on_layer_toggle)
func _create_button(text_label: String, tooltip: String, callback: Callable) -> Button:
var btn := Button.new()
btn.text = text_label
btn.tooltip_text = tooltip
btn.custom_minimum_size = Vector2(30, 26)
btn.focus_mode = Control.FOCUS_NONE
btn.add_theme_font_size_override("font_size", 11)
btn.pressed.connect(callback)
add_child(btn)
_buttons.append(btn)
return btn
func _on_zoom_in() -> void:
if _world_preview != null and _world_preview.has_method(&"set_zoom"):
var current: float = float(_world_preview.get("zoom_level"))
_world_preview.set_zoom(current + 0.5)
zoom_in_requested.emit()
func _on_zoom_out() -> void:
if _world_preview != null and _world_preview.has_method(&"set_zoom"):
var current: float = float(_world_preview.get("zoom_level"))
_world_preview.set_zoom(current - 0.5)
zoom_out_requested.emit()
func _on_pan_step(direction: Vector2) -> void:
var delta := Vector2(32, 48) * direction
if _world_preview != null:
if _world_preview.has_method(&"_cell_dimensions"):
var dims: Vector2 = _world_preview.call(&"_cell_dimensions")
delta = direction * dims
var offset: Vector2 = _world_preview.get("pan_offset")
_world_preview.set("pan_offset", offset + delta)
if _world_preview.has_method(&"_clamp_pan"):
_world_preview.call(&"_clamp_pan")
_world_preview.queue_redraw()
pan_requested.emit(delta)
func _on_pan(delta: Vector2) -> void:
if _world_preview != null:
var offset: Vector2 = _world_preview.get("pan_offset")
_world_preview.set("pan_offset", offset + delta)
if _world_preview.has_method(&"_clamp_pan"):
_world_preview.call(&"_clamp_pan")
_world_preview.queue_redraw()
pan_requested.emit(delta)
func _on_reset_view() -> void:
if _world_preview != null:
if _world_preview.has_method(&"focus_operating_area"):
_world_preview.call(&"focus_operating_area")
elif _world_preview.has_method(&"reset_view"):
_world_preview.call(&"reset_view")
reset_view_requested.emit()
func _on_grid_toggle() -> void:
if _world_preview != null:
var current_grid: bool = bool(_world_preview.get("show_grid"))
_world_preview.set("show_grid", not current_grid)
_world_preview.queue_redraw()
grid_toggle_requested.emit()
func _on_layer_toggle() -> void:
if _main_scene != null and _main_scene.has_method(&"_toggle_layer"):
_main_scene.call(&"_toggle_layer")
elif _world_preview != null:
var current_mode: int = int(_world_preview.get("layer_mode"))
_world_preview.set("layer_mode", 1 if current_mode == 0 else 0)
layer_toggle_requested.emit()
func apply_layer_skin(layer: int) -> void:
for btn: Button in _buttons:
LayerSkin.register_toggle_text(btn, layer)
LayerSkin.apply_tree(self, layer)
@@ -0,0 +1 @@
uid://c9p24cameractrl
+101
View File
@@ -0,0 +1,101 @@
class_name OperationsOSObserver
extends RefCounted
## Passive actual Main/runtime observer for P24-02 operations and notification evidence.
## Captures OS process environment, screen scale, bubble state, and modal identity
## without modifying the active simulation model.
## Local unit fixture observations are strictly separated from candidate normal OS
## input evidence (which requires formal native OS capture receipts).
var pid := 0
var platform := ""
var window_size := Vector2i.ZERO
var observations: Array[Dictionary] = []
var bubble_records: Dictionary = {} # Maps actor_id -> latest bubble record
var bubble_history: Array[Dictionary] = [] # Chronological history of all bubble state transition events
var modal_records: Array[Dictionary] = []
var cutin_records: Array[Dictionary] = []
var save_records: Array[Dictionary] = []
func _init() -> void:
pid = OS.get_process_id()
platform = OS.get_name()
func capture_environment(tree: SceneTree, main: Control) -> Dictionary:
var root: Window = tree.root if tree != null else null
window_size = root.size if root != null else Vector2i.ZERO
var report := {
&"pid": pid,
&"platform": platform,
&"window_size": [window_size.x, window_size.y],
&"main_present": is_instance_valid(main),
&"captured_at_msec": Time.get_ticks_msec(),
&"fixture_scope": &"local_unit_fixture_only_not_native_os_input_evidence",
}
observations.append(report)
return report
func record_bubble(actor_id: StringName, kind: StringName, source_state: int) -> void:
var record := {
&"actor_id": actor_id,
&"kind": kind,
&"source_state": source_state,
&"recorded_at": Time.get_ticks_msec(),
}
bubble_records[actor_id] = record
bubble_history.append(record)
func get_latest_bubble(actor_id: StringName) -> Dictionary:
return bubble_records.get(actor_id, {})
func get_bubble_history() -> Array[Dictionary]:
return bubble_history.duplicate(true)
func record_modal_event(dialog_name: String, action: String, details: Dictionary = {}) -> void:
modal_records.append({
&"dialog_name": dialog_name,
&"action": action,
&"details": details.duplicate(true),
&"recorded_at": Time.get_ticks_msec(),
})
func record_cutin(kind: StringName, heading: String, message: String) -> void:
cutin_records.append({
&"kind": kind,
&"heading": heading,
&"message": message,
&"recorded_at": Time.get_ticks_msec(),
})
func record_save_label(path: String, display_name: String, is_fresh: bool) -> void:
save_records.append({
&"path": path,
&"display_name": display_name,
&"is_fresh": is_fresh,
&"recorded_at": Time.get_ticks_msec(),
})
func get_summary() -> Dictionary:
return {
&"pid": pid,
&"platform": platform,
&"window_size": [window_size.x, window_size.y],
&"observations_count": observations.size(),
&"unique_actors_tracked": bubble_records.size(),
&"bubbles_tracked": bubble_history.size(),
&"bubble_history_count": bubble_history.size(),
&"bubble_events_count": bubble_history.size(),
&"modals_tracked": modal_records.size(),
&"cutins_tracked": cutin_records.size(),
&"saves_tracked": save_records.size(),
&"fixture_scope": &"local_unit_fixture_only_not_native_os_input_evidence",
}
@@ -0,0 +1 @@
uid://copobserver001
@@ -0,0 +1,100 @@
extends SceneTree
## Passive observer: no selection, save/load, command or engine input injection.
## Runner launches separate owned processes with private user-data directories.
var _main: Control
var _elapsed := 0.0
var _last := ""
var _sequence := 0
var _output := ""
var _source_map: Dictionary = {}
func _initialize() -> void:
call_deferred("_start")
func _start() -> void:
_output = OS.get_environment("P24_OBSERVER_OUTPUT")
var pins := OS.get_environment("P24_OBSERVER_SOURCE_MAP")
if not _output.is_absolute_path() or not DirAccess.dir_exists_absolute(_output) or not pins.is_absolute_path():
push_error("P24 observer requires existing absolute output directory and source-map JSON")
quit(1)
return
var parsed: Variant = JSON.parse_string(FileAccess.get_file_as_string(pins))
if not parsed is Dictionary or parsed.is_empty():
push_error("P24 observer source map missing")
quit(1)
return
_source_map = parsed
root.size = Vector2i(1440, 810)
_main = load("res://scenes/main/main.tscn").instantiate()
root.add_child(_main)
print("P24_PROCESS pid=%d scene=res://scenes/main/main.tscn" % OS.get_process_id())
# Use the ordinary Main pause control through OS input when a frozen state
# is needed. The observer itself never changes Runtime or Clock.
func _process(delta: float) -> bool:
_elapsed += delta
if _elapsed >= 1.0 and is_instance_valid(_main):
_elapsed = 0.0
_capture()
return false
func _capture() -> void:
var runtime: Variant = root.get_node("SimulationRuntime")
var selection: Dictionary = _main.call("_capture_interface_selection")
var facility_id := StringName(selection.get("id", ""))
var facility: Variant = runtime.facility_service.get_instance(facility_id)
var guest_panel: GuestObservationPanel
var dialog: Variant = _main.get("guest_observer")
if dialog != null:
guest_panel = dialog.find_child("GuestObservationPanel", true, false) as GuestObservationPanel
var staff_panel: StaffManagementPanel = _main.get("staff_management_panel")
var staff_model: Dictionary = {}
for row: Dictionary in runtime.get_staff_management_report().get(&"staff", []):
if String(row.get(&"staff_id", "")) == String(selection.get("staff_id", "")):
staff_model = row.duplicate(true)
var needs: Dictionary = {}
if guest_panel != null:
for need: StringName in guest_panel.bars:
needs[need] = guest_panel.bars[need].value
var observation := {
"interface_selection": selection,
"layer": root.get_node("GameState").get("active_layer"),
"facility": {"id": String(facility_id), "model_snapshot": facility.create_snapshot() if facility != null else {},
"heading_text": _main.get("selected_heading_label").text,
"detail_text": _main.get("selected_detail_label").text,
"highlight_id": String(_main.get("world_preview").get("selected_entity_id"))},
"guest": {"id": selection.get("guest_id", ""),
"model_report": runtime.guest_service.get_observation(StringName(selection.get("guest_id", ""))),
"detail_text": guest_panel.details.text if guest_panel != null else "",
"priority_text": guest_panel.priority_label.text if guest_panel != null else "",
"decision_text": guest_panel.decision_label.text if guest_panel != null else "", "need_bars": needs},
"staff": {"id": selection.get("staff_id", ""), "model_report": staff_model,
"detail_text": staff_panel.staff_detail_label.text,
"editor_visible": staff_panel.editor_panel.is_visible_in_tree()},
}
var encoded := JSON.stringify(observation, "", true)
if encoded == _last:
return
_sequence += 1
var stem := "process-%d-observation-%d" % [OS.get_process_id(), _sequence]
var report_path := _output.path_join(stem + ".json")
var frame_path := _output.path_join(stem + ".png")
if FileAccess.file_exists(report_path) or FileAccess.file_exists(frame_path):
push_error("P24 observer refuses artifact overwrite")
return
var image := root.get_texture().get_image()
if image.save_png(frame_path) != OK:
push_error("P24 observer frame capture failed")
return
var file := FileAccess.open(report_path, FileAccess.WRITE)
if file == null:
push_error("P24 observer report write failed")
return
file.store_string(JSON.stringify({"pid": OS.get_process_id(), "source_map": _source_map,
"scene": "res://scenes/main/main.tscn", "ticks_usec": Time.get_ticks_usec(),
"raw_observation": observation, "screenshot": stem + ".png",
"formal_acceptance": false}, "\t", true) + "\n")
file.close()
_last = encoded
print("P24_RAW_OBSERVATION " + encoded)
@@ -0,0 +1 @@
uid://d24crossproc
+334
View File
@@ -0,0 +1,334 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
const OperationFeedbackScript := preload("res://scripts/ui/operation_feedback.gd")
const OperationsOSObserverScript := preload("res://tests/ui/fixtures/operations_os_observer.gd")
const ManualSaveSlotsDialogScript := preload("res://scripts/ui/manual_save_slots_dialog.gd")
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _expect(ok: bool, message: String) -> void:
_checks += 1
if not ok:
_failures += 1
push_error("FAIL: " + message)
func _run() -> void:
var observer := OperationsOSObserverScript.new()
var runtime: Variant = root.get_node("SimulationRuntime")
var clock: Variant = root.get_node("GameClock")
var state: Variant = root.get_node("GameState")
clock.reset(1, 6, 0)
state.reset()
runtime.initialize(clock, state, 42534)
clock.set_paused(true)
root.size = Vector2i(1440, 810)
var main: Control = load("res://scenes/main/main.tscn").instantiate()
root.add_child(main)
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
await process_frame
await process_frame
var preview: WorldPreview = main.get("world_preview")
_expect(preview != null, "WorldPreview exists in Main")
var env_report: Dictionary = observer.capture_environment(self, main)
_expect(env_report[&"pid"] > 0, "Observer captured actual process PID")
# =========================================================================
# Condition 1: placement_and_invalid_drop (Grid snap, warnings, invalid rejection)
# =========================================================================
var def_registry: Variant = runtime.get("definition_registry")
var reception_def: Variant = def_registry.get_facility(&"reception") if def_registry != null else null
_expect(reception_def != null, "Reception facility definition exists")
# Valid placement commits once
var valid_cell := Vector3i(-8, 0, 0)
var build_result: Variant = runtime.submit_command(&"build_facility", {
&"definition_id": &"reception",
&"origin": valid_cell,
&"rotation": 0
})
_expect(build_result.is_success(), "Valid facility construction commits once")
# Advance time for facility to finish construction
clock.advance_minutes(120)
var facilities: Array = runtime.construction_service.get_facilities()
_expect(not facilities.is_empty(), "Facility construction completed in world")
var facility_id := StringName(facilities[0][&"instance_id"])
# Connection warning on preview
preview.set_build_definition(reception_def, 0)
var connection_preview: Dictionary = preview.get_build_preview_report(Vector3i(10, 10, 0))
_expect(connection_preview.has(&"connection"), "Build preview includes connection warning report")
# Invalid placement rejection explanation
var invalid_preview: Dictionary = preview.get_build_preview_report(Vector3i(999, 999, 0))
var rejection_text := OperationFeedbackScript.get_placement_rejection_text(invalid_preview)
_expect(not rejection_text.is_empty(), "Invalid placement explains actual rejection reason: " + rejection_text)
preview.clear_build_definition()
# Staff assignment via drop
var staff_panel: StaffManagementPanel = main.get("staff_management_panel")
_expect(staff_panel != null, "StaffManagementPanel exists")
_expect(runtime.staff_service.create_staff(&"op_worker_1", &"cleaner", {}).is_success(), "Create test employee")
staff_panel.refresh()
var drop_payload := {
&"kind": &"staff_assignment",
&"staff_id": &"op_worker_1",
&"template_id": &"DAY",
&"queue_instance_id": int(staff_panel.get("_queue_instance_id"))
}
staff_panel.assign_from_drop(drop_payload, facility_id)
_expect(staff_panel.selected_staff_id == &"op_worker_1", "Drop assignment preserves employee selection")
# Invalid assignment rejection explains actual reason and leaves model intact
var before_model: Dictionary = runtime.create_snapshot()
var invalid_drop := {
&"kind": &"staff_assignment",
&"staff_id": &"non_existent_staff",
&"template_id": &"DAY",
&"queue_instance_id": int(staff_panel.get("_queue_instance_id"))
}
staff_panel.assign_from_drop(invalid_drop, facility_id)
var after_model: Dictionary = runtime.create_snapshot()
_expect(SimulationDigest.compute(before_model) == SimulationDigest.compute(after_model), "Invalid drop leaves simulation model completely intact")
# Click-based assignment alternative
_expect(main.has_method(&"assign_staff_by_click"), "Main provides click-based assignment alternative")
staff_panel.select_staff(&"op_worker_1")
main.call(&"assign_staff_by_click", facility_id, &"op_worker_1")
_expect(staff_panel.selected_staff_id == &"op_worker_1", "Click-based assignment succeeds and leaves selection intact")
# =========================================================================
# Condition 2: modal_and_pause_resume (Modal input block, pause freezes simulation)
# =========================================================================
var save_dialog: ConfirmationDialog = main.call(&"_ensure_manual_save_slots")
_expect(save_dialog != null, "Save slots dialog is available")
var initial_manual_path: String = main.manual_save_path
save_dialog.show_new_save("user://saves/manual_ui")
await process_frame
_expect(preview.modal_input_blocked, "Behind-dialog input is blocked while modal is open")
# Behind-dialog drop assignment is rejected while modal is open
var drop_test_payload := {
&"kind": &"staff_assignment",
&"staff_id": &"op_worker_1",
&"template_id": &"DAY",
}
_expect(not preview._can_drop_data(Vector2.ZERO, drop_test_payload), "Modal suppresses behind-dialog staff assignment drop")
# Cancel modal preserves save identity and unblocks behind-dialog input
save_dialog.hide()
save_dialog.emit_signal(&"canceled")
await process_frame
_expect(not preview.modal_input_blocked, "Modal close unblocks behind-dialog input")
_expect(main.manual_save_path == initial_manual_path, "Modal cancellation preserves existing save identity")
# Pause freezes simulation via process_real_delta/accumulator; ordinary resume continues exactly once
var clock_paused_before: bool = bool(clock.get("paused"))
clock.set_paused(true)
var min_paused: int = int(clock.get("absolute_minute"))
# Passing non-zero real delta while paused returns 0 ticks and does not advance simulation minute
var paused_ticks: int = clock.process_real_delta(SimulationKernel.REAL_SECONDS_PER_GAME_MINUTE * 2.0)
_expect(paused_ticks == 0, "Paused clock returns 0 ticks on non-zero real process delta")
_expect(int(clock.get("absolute_minute")) == min_paused, "Paused clock freezes minute advancement on real process delta")
# Ordinary resume continues exactly once
clock.set_paused(false)
var resumed_ticks: int = clock.process_real_delta(SimulationKernel.REAL_SECONDS_PER_GAME_MINUTE)
_expect(resumed_ticks == 1, "Resumed clock on real process delta advances exactly one tick")
_expect(int(clock.get("absolute_minute")) == min_paused + 1, "Resumed clock advances simulation minute exactly once")
clock.set_paused(clock_paused_before)
# =========================================================================
# Condition 3: unit_bubbles (Zzz, anger, heart from real states)
# =========================================================================
var test_staff: StaffRuntime = runtime.staff_service.get_staff(&"op_worker_1")
_expect(test_staff != null, "Worker runtime exists for bubble test")
# SLEEPING / INCAPACITATED -> Zzz
test_staff.state = StaffRuntime.State.SLEEPING
var bubble_zzz: StringName = OperationFeedbackScript.resolve_staff_bubble(test_staff)
_expect(bubble_zzz == OperationFeedbackScript.BUBBLE_ZZZ, "Sleeping staff resolves to Zzz bubble")
observer.record_bubble(&"op_worker_1", bubble_zzz, test_staff.state)
# STRIKE / FLEEING -> anger (no unapproved morale thresholds)
test_staff.state = StaffRuntime.State.STRIKE
var bubble_anger: StringName = OperationFeedbackScript.resolve_staff_bubble(test_staff)
_expect(bubble_anger == OperationFeedbackScript.BUBBLE_ANGER, "Strike staff resolves to anger bubble")
observer.record_bubble(&"op_worker_1", bubble_anger, test_staff.state)
# Authoritative RelationshipState / RouteLock -> heart (no unapproved loyalty/morale thresholds)
var rel_state := RelationshipState.new()
rel_state.configure(&"rel_op_1", &"heroine_anri", &"op_worker_1", {
&"shield_max": 100,
&"shield_current": 100,
&"route_lock": RelationshipState.RouteLock.EXCLUSIVE_PENDING,
&"route_id": &"pure_love",
&"metrics": {&"affection": 80}
})
test_staff.state = StaffRuntime.State.AVAILABLE
var bubble_heart: StringName = OperationFeedbackScript.resolve_staff_bubble(test_staff, rel_state)
_expect(bubble_heart == OperationFeedbackScript.BUBBLE_HEART, "Authoritative relationship model resolves to heart bubble")
observer.record_bubble(&"op_worker_1", bubble_heart, test_staff.state)
# Switching state back to normal working -> clears bubble without stale symbol
test_staff.state = StaffRuntime.State.WORKING
var bubble_work: StringName = OperationFeedbackScript.resolve_staff_bubble(test_staff)
_expect(bubble_work == OperationFeedbackScript.BUBBLE_NONE, "Normal working staff clears bubble without stale symbol")
var neutral_rel := RelationshipState.new()
neutral_rel.configure(&"rel_op_neutral", &"heroine_anri", &"op_worker_1", {
&"shield_max": 100,
&"shield_current": 0,
&"route_lock": RelationshipState.RouteLock.NONE,
&"route_id": &"",
&"metrics": {&"affection": 0}
})
var bubble_cleared: StringName = OperationFeedbackScript.resolve_staff_bubble(test_staff, neutral_rel)
_expect(bubble_cleared == OperationFeedbackScript.BUBBLE_NONE, "Neutral relationship model clears heart bubble")
observer.record_bubble(&"op_worker_1", bubble_cleared, test_staff.state)
_expect(observer.get_latest_bubble(&"op_worker_1").get(&"kind", &"") == OperationFeedbackScript.BUBBLE_NONE, "Observer confirms stale bubble symbol is removed on transition to normal")
# Guest bubble: Incapacitated -> Zzz
var test_guest := GuestRuntime.new()
test_guest.guest_id = &"bubble_guest"
test_guest.state = GuestRuntime.State.INCAPACITATED
_expect(OperationFeedbackScript.resolve_guest_bubble(test_guest) == OperationFeedbackScript.BUBBLE_ZZZ, "Incapacitated guest resolves to Zzz bubble")
# Guest bubble: Abandoning -> anger
test_guest.state = GuestRuntime.State.ABANDONING
_expect(OperationFeedbackScript.resolve_guest_bubble(test_guest) == OperationFeedbackScript.BUBBLE_ANGER, "Abandoning guest resolves to anger bubble")
# Guest bubble: Normal moving -> no bubble
test_guest.state = GuestRuntime.State.MOVING
_expect(OperationFeedbackScript.resolve_guest_bubble(test_guest) == OperationFeedbackScript.BUBBLE_NONE, "Normal moving guest clears bubble without stale symbol")
# =========================================================================
# Condition 4: event_log (Bounded 256, formatted Japanese, deduplicated)
# =========================================================================
var event_dialog: EventLogDialog = main.get("event_log_dialog")
_expect(event_dialog != null, "EventLogDialog exists in Main")
var event_rows: Array[Dictionary] = event_dialog.get_event_rows()
_expect(event_rows.size() <= 256, "Event rows bounded to recent 256 entries")
var sample_event := {
"event_id": "evt_test_001",
"event_type": "DEFENSE_RAID_CREATED",
"absolute_minute": 360,
"entity_ids": [],
"amount": -500,
"reason": "RAID_START"
}
var presentation: Dictionary = event_dialog.get_event_presentation(sample_event)
_expect(presentation[&"title"] == "襲撃が発生", "Event type resolves to readable Japanese title")
_expect("G-500" in presentation[&"extra"] or "500" in presentation[&"extra"], "Event presentation includes amount details")
# =========================================================================
# Condition 5: important_warning_cutin (Invasion / Duel / Defense start)
# =========================================================================
_expect(main.has_method(&"show_warning_cutin"), "Main provides show_warning_cutin")
main.call(&"show_warning_cutin", OperationFeedbackScript.CUTIN_DEFENSE, "防衛戦開始演出", "敵の襲撃が発生しました!")
var cutin: OperationFeedback.ImportantWarningCutinDialog = main.get("important_warning_cutin")
_expect(cutin != null and cutin.visible, "Important warning cut-in is displayed")
observer.record_cutin(OperationFeedbackScript.CUTIN_DEFENSE, "防衛戦開始演出", "敵の襲撃が発生しました!")
cutin.hide()
cutin.emit_signal(&"confirmed")
await process_frame
_expect(not cutin.visible, "Important warning cut-in dismisses cleanly")
# Real domain event triggers: DEFENSE_RAID_CREATED and RELATIONSHIP_SHIELD_CHANGED break
main.call(&"_on_domain_event_published", {
&"event_type": &"DEFENSE_RAID_CREATED",
&"payload": {&"raid_id": &"raid_test_01"}
})
_expect(cutin != null and cutin.visible, "DEFENSE_RAID_CREATED domain event triggers defense cutin")
cutin.hide()
cutin.emit_signal(&"confirmed")
await process_frame
main.call(&"_on_domain_event_published", {
&"event_type": &"RELATIONSHIP_SHIELD_CHANGED",
&"payload": {&"old_value": 50, &"new_value": 0}
})
_expect(cutin != null and cutin.visible, "Shield vanishing to 0 triggers love shield warning cutin")
cutin.hide()
cutin.emit_signal(&"confirmed")
await process_frame
# Generic INCIDENT_CREATED does not falsely trigger warning cutin
main.call(&"_on_domain_event_published", {
&"event_type": &"INCIDENT_CREATED",
&"payload": {&"incident_type": &"illegal_facility"}
})
_expect(not cutin.visible, "Generic INCIDENT_CREATED does not trigger warning cutin")
# =========================================================================
# Condition 6: click_alternatives (OperationsCameraControls click equivalents)
# =========================================================================
var camera_ctrl: OperationsCameraControls = main.get("operations_camera_controls")
_expect(camera_ctrl != null, "OperationsCameraControls exists in Main")
var initial_zoom: float = preview.zoom_level
camera_ctrl.zoom_in_button.emit_signal(&"pressed")
_expect(preview.zoom_level > initial_zoom, "Click zoom in increments zoom level")
camera_ctrl.zoom_out_button.emit_signal(&"pressed")
_expect(is_equal_approx(preview.zoom_level, initial_zoom), "Click zoom out restores zoom level")
var initial_pan: Vector2 = preview.pan_offset
camera_ctrl.pan_up_button.emit_signal(&"pressed")
_expect(preview.pan_offset != initial_pan, "Click pan moves pan offset")
camera_ctrl.reset_button.emit_signal(&"pressed")
_expect(preview.zoom_level >= 0.5 and preview.zoom_level <= 4.0, "Click reset maintains valid viewport framing")
var grid_before: bool = preview.show_grid
camera_ctrl.grid_button.emit_signal(&"pressed")
_expect(preview.show_grid != grid_before, "Click grid button toggles grid display")
camera_ctrl.grid_button.emit_signal(&"pressed")
# =========================================================================
# Condition 7: japanese_input_and_scale (Japanese save label, cryptographic path)
# =========================================================================
var label_edit: LineEdit = save_dialog.find_child("ManualSaveLabelEdit", true, false) as LineEdit
_expect(label_edit != null, "Manual save slots dialog contains LineEdit for Japanese label input")
save_dialog.show_new_save("user://saves/manual_ui")
_expect(not label_edit.text.is_empty(), "Save label has valid Japanese default text: " + label_edit.text)
# User inputs Japanese text
var custom_label := "世界樹第壱号拠点・本番セーブ"
label_edit.text = custom_label
label_edit.emit_signal(&"text_changed", custom_label)
_expect(save_dialog.get_ok_button().disabled == false, "Valid Japanese text allows save confirmation")
# Clear / cancel semantics
save_dialog.hide()
save_dialog.emit_signal(&"canceled")
_expect(label_edit.text.is_empty(), "Dialog cancel clears input label")
# Cryptographic owned path check
_expect(ManualSaveSlotsDialogScript.is_owned_slot_name("manual_ui_0123456789abcdef0123456789abcdef"), "Cryptographic slot path regex matches owned specification")
_expect(not ManualSaveSlotsDialogScript.is_owned_slot_name("user_custom_folder_name"), "User custom folder name rejected from cryptographic slot rule")
# =========================================================================
# Condition 8: final_validation_review (All 8 conditions checked, ordinary cleanup)
# =========================================================================
var observer_summary: Dictionary = observer.get_summary()
_expect(observer_summary[&"pid"] > 0, "Observer tracks non-zero PID")
_expect(observer_summary[&"bubbles_tracked"] >= 3, "Observer tracked at least 3 unit bubble events")
_expect(observer_summary[&"unique_actors_tracked"] >= 1, "Observer maintains latest state map for tracked actors")
var cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
_expect(bool(cleanup.get(&"passed", false)), "Ordinary Main fixture and audio cleanup succeeds")
print("Operations notification: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures else 0)
@@ -0,0 +1 @@
uid://copnotify0001
+107
View File
@@ -0,0 +1,107 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _expect(ok: bool, message: String) -> void:
_checks += 1
if not ok:
_failures += 1
push_error(message)
func _run() -> void:
var runtime: Variant = root.get_node("SimulationRuntime")
var clock: Variant = root.get_node("GameClock")
var state: Variant = root.get_node("GameState")
clock.reset(1, 6, 0)
state.reset()
runtime.initialize(clock, state, 42533)
clock.set_paused(true)
root.size = Vector2i(1440, 810)
var main: Control = load("res://scenes/main/main.tscn").instantiate()
root.add_child(main)
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
await process_frame
await process_frame
# Build before intentionally preserving departed payroll debt.
var build_result: Variant = runtime.submit_command(&"build_facility", {&"definition_id": &"reception", &"origin": Vector3i(-8, 0, 0)})
_expect(build_result.is_success(), "real facility construction starts: " + str(build_result.to_dictionary()))
clock.advance_minutes(120)
var facilities: Array = runtime.construction_service.get_facilities()
_expect(not facilities.is_empty(), "real facility construction completes")
if not facilities.is_empty():
var facility_id := StringName(facilities[0][&"instance_id"])
main.call("_on_facility_selected", facility_id)
main.get("world_preview").set("selected_entity_id", facility_id)
var previous_detail: String = main.get("selected_detail_label").text
_expect(main.get("_selected_entity_id") == facility_id, "select existing built facility")
_expect(runtime.submit_command(&"demolish_facility", {&"instance_id": facility_id}).is_success(), "real facility demolition commits")
main.call("_refresh_selected_entity")
_expect(main.get("_selected_entity_id") == &"" and main.get("_selected_entity_kind") == &"" and main.get("world_preview").get("selected_entity_id") == &"", "actual demolition clears ID kind and map highlight")
_expect(main.get("selected_entity_icon").texture == null and main.get("selected_detail_label").text != previous_detail, "actual demolition clears icon and stale detail")
var guest: GuestObservationPanel = main.call("_ensure_guest_observer")
var definition: GuestDefinition = runtime.guest_service.get_definitions()[0]
_expect(runtime.submit_command(&"spawn_guest", {&"guest_id": &"departure_guest", &"guest_type_id": definition.guest_type_id, &"gold": 731, &"options": {&"planned_exit_at": clock.absolute_minute + 1000}}).is_success(), "spawn actual guest")
guest.selected_guest_id = &"departure_guest"
guest.refresh()
_expect("731G" in guest.details.text, "selected guest displays authoritative wallet")
_expect(runtime.guest_service.begin_leaving(&"departure_guest", clock.absolute_minute + 1, &"TEST_EXIT").get(&"ok", false), "actual leaving transition")
guest.refresh()
_expect(guest.selected_guest_id == &"departure_guest" and "退館中" in guest.details.text, "leaving guest remains present and displays current state")
_expect(runtime.guest_service.despawn_guest(&"departure_guest", clock.absolute_minute + 2).get(&"ok", false), "actual guest despawn")
guest.refresh()
_expect(not runtime.guest_service.has_guest(&"departure_guest") and not runtime.guest_service.get_persistent_record(&"departure_guest").is_empty(), "history survives without a currently selectable guest")
_expect(guest.selected_guest_id != &"departure_guest" and not guest.get("_ids").has(&"departure_guest"), "departed ID is absent while live roster fallback remains valid")
if not guest.selected_guest_id.is_empty():
var live_report: Dictionary = runtime.guest_service.get_observation(guest.selected_guest_id)
_expect(not live_report.is_empty() and ("%dG" % int(live_report[&"wallet_gold"])) in guest.details.text, "fallback displays the live guest wallet")
# Create an empty-roster boundary through real departures, never array erasure.
for live: GuestRuntime in runtime.guest_service.get_guests():
_expect(runtime.guest_service.begin_leaving(live.guest_id, clock.absolute_minute + 3, &"TEST_EXIT").get(&"ok", false), "empty-roster fixture begins departure")
_expect(runtime.guest_service.despawn_guest(live.guest_id, clock.absolute_minute + 4).get(&"ok", false), "empty-roster fixture completes departure")
guest.refresh()
_expect(guest.selected_guest_id.is_empty() and guest.guests.selected == -1 and guest.guests.disabled, "empty roster has no current selection or dropdown target")
_expect(guest.priority_label.text.is_empty() and guest.decision_label.text.is_empty(), "departed guest leaves no stale current detail values")
for need: StringName in guest.bars:
_expect(guest.bars[need].value == 0, "departed guest clears current need " + String(need))
var staff: StaffManagementPanel = main.get("staff_management_panel")
_expect(runtime.staff_service.create_staff(&"departure_staff", &"cleaner", {&"unpaid_wages": 43}).is_success(), "register ordinary departure employee")
staff.select_staff(&"departure_staff")
_expect(staff.selected_staff_id == &"departure_staff", "select ordinary employee")
_expect(runtime.staff_service.remove_staff(&"departure_staff").is_success(), "permitted ordinary employee removal")
staff.refresh()
_expect(not runtime.staff_service.has_staff(&"departure_staff") and not staff.roster_buttons.has(&"departure_staff") and staff.selected_staff_id != &"departure_staff", "removed employee no longer selectable")
_expect(runtime.staff_service.get_departed_unpaid_wages(&"departure_staff") == 43, "departure preserves payroll history")
_expect(runtime.staff_service.create_staff(&"departure_unique", &"cleaner", {&"employment_kind": StaffRuntime.EmploymentKind.UNIQUE}).is_success(), "register protected Unique fixture")
staff.select_staff(&"departure_unique")
_expect(not runtime.staff_service.remove_staff(&"departure_unique").is_success(), "Unique removal remains forbidden")
staff.refresh()
_expect(runtime.staff_service.has_staff(&"departure_unique") and staff.selected_staff_id == &"departure_unique", "rejected removal preserves Unique selection")
main.call("_set_layer", 1)
var before: Dictionary = runtime.create_snapshot()
var selection: Dictionary = main.call("_capture_interface_selection")
_expect(SimulationDigest.compute(before) == SimulationDigest.compute(runtime.create_snapshot()), "UI metadata capture leaves whole Runtime unchanged")
_expect(state.create_snapshot().get(&"active_layer") == 1 and selection.get(&"staff_id") == "departure_unique", "formal layer and selected ID have authoritative sources")
guest.clear_selection()
_expect(guest.guests.selected == -1, "clear observer has no invalid option index")
for version: Variant in [true, 1.5, "1", 0, 2]:
main.call("_restore_actor_interface_selection", {"interface_selection": {"version": version, "staff_id": "departure_unique", "staff_open": true}})
_expect(staff.selected_staff_id.is_empty() and not staff.visible, "wrong/old metadata version is ignored: " + str(version))
var original: String = OS.get_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF")
if original.is_empty() or not original.is_absolute_path():
_expect(false, "Runner must provide fresh absolute ordinary control proof")
else:
var control := original
if FileAccess.file_exists(control):
_expect(false, "ordinary cleanup proof overwrite forbidden")
else:
OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", control)
var cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", original)
_expect(bool(cleanup.get(&"passed", false)), "ordinary Main/audio cleanup")
print("Selection departure: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures else 0)
+1
View File
@@ -0,0 +1 @@
uid://c3p24departur
+747
View File
@@ -0,0 +1,747 @@
#!/usr/bin/env python3
"""P24-02 Operations and Notification Acceptance Evidence Validator.
Strict verify-only validator enforcing schema, scope source manifest,
concrete condition-specific evidence artifacts, OS execution receipts,
cross-process restart proof, and all 8 required conditions:
- placement_and_invalid_drop
- modal_and_pause_resume
- unit_bubbles
- event_log
- important_warning_cutin
- click_alternatives
- japanese_input_and_scale
- final_validation_review
"""
import argparse
import hashlib
import json
import os
import re
import sys
from pathlib import Path
REQUIRED_CONDITIONS = [
"placement_and_invalid_drop",
"modal_and_pause_resume",
"unit_bubbles",
"event_log",
"important_warning_cutin",
"click_alternatives",
"japanese_input_and_scale",
"final_validation_review",
]
REQUIRED_SCOPE_FILES = [
"scenes/main/main.gd",
"scenes/main/world_preview.gd",
"scripts/ui/event_log_dialog.gd",
"scripts/ui/manual_save_slots_dialog.gd",
"scripts/ui/operation_feedback.gd",
"scripts/ui/operation_feedback.gd.uid",
"scripts/ui/operations_camera_controls.gd",
"scripts/ui/operations_camera_controls.gd.uid",
"tests/ui/test_operations_notification.gd",
"tests/ui/test_operations_notification.gd.uid",
"tests/ui/fixtures/operations_os_observer.gd",
"tests/ui/fixtures/operations_os_observer.gd.uid",
"tools/check_p24_operations_evidence.py",
"tools/run_all_tests.py",
"tools/regression/suite-policy.json",
"docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json",
]
EXPECTED_TASK_ID = "P24-02"
EXPECTED_BASE_COMMIT = "1a5abc65163097d4b36a060d68f58c92093eb4e9"
EXPECTED_SPEC_SHA256 = "738268b406e381ccab415b426d4a547b02e7cb2a481cefce65579a1e346834c7"
HEX64_PATTERN = re.compile(r"^[0-9a-f]{64}$")
HEX_COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$|^[0-9a-f]{64}$")
JAPANESE_CHAR_PATTERN = re.compile(r"[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]")
MAX_VALID_PID = 4_194_304
def compute_sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def get_actual_git_head() -> str:
env_head = os.environ.get("CANDIDATE_COMMIT", os.environ.get("TARGET_HEAD"))
if env_head:
cleaned = env_head.strip().lower()
if HEX_COMMIT_PATTERN.match(cleaned) and not (set(cleaned) <= {"0"} or set(cleaned) <= {"f"}):
return cleaned
try:
import subprocess
res = subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True, text=True, timeout=5)
if res.returncode == 0:
h = res.stdout.strip().lower()
if HEX_COMMIT_PATTERN.match(h) and not (set(h) <= {"0"} or set(h) <= {"f"}):
return h
except Exception:
pass
return ""
def get_required_suite_roster(root: Path) -> list[str]:
tests_dir = root / "tests"
if not tests_dir.is_dir():
return []
roster = ["tests/run_tests.gd"]
for p in sorted(tests_dir.rglob("test*.gd")):
rel = p.relative_to(root).as_posix()
if "export" not in p.relative_to(root).parts and rel != "tests/s0/test_kernel_and_save.gd":
roster.append(rel)
return sorted(list(set(roster)))
def find_index_file(custom_path: str = None) -> Path:
if custom_path:
p = Path(custom_path)
if p.is_file():
return p.resolve()
if p.is_dir():
candidate = p / "index.json"
if candidate.is_file():
return candidate.resolve()
candidate2 = p / "p24-operations-evidence" / "index.json"
if candidate2.is_file():
return candidate2.resolve()
build_dir = os.environ.get("BUILD_OUTPUT_DIR")
if build_dir:
candidate = Path(build_dir) / "p24-operations-evidence" / "index.json"
if candidate.is_file():
return candidate.resolve()
candidate2 = Path(build_dir) / "index.json"
if candidate2.is_file():
return candidate2.resolve()
candidates = [
Path(".runtime/P24-02/build/p24-operations-evidence/index.json"),
Path("docs/plans/original-completion-2026-10-02/execution/p24-02/evidence/index.json"),
Path("artifacts/p24-02-operations/index.json"),
]
for c in candidates:
if c.is_file():
return c.resolve()
return None
def _is_bare_or_dummy_artifact(data: dict) -> bool:
if not isinstance(data, dict):
return True
if len(data) <= 1:
return True
trivial_keys = {"pid", "status", "passed", "id", "timestamp", "name", "contract_id"}
if set(data.keys()).issubset(trivial_keys):
return True
return False
def _validate_condition_artifact_content(
cond: str,
art_data: dict,
art_type: str,
evidence_dir: Path,
root_data: dict,
validated_artifacts: dict,
) -> tuple[bool, str]:
if not isinstance(art_data, dict):
return False, f"Artifact content for condition '{cond}' must be a JSON object"
if _is_bare_or_dummy_artifact(art_data):
return False, f"Artifact for condition '{cond}' is a bare self-declaration/PID-only JSON without substantive evidence details"
if cond == "placement_and_invalid_drop":
if "grid_snap" not in art_data:
return False, "Missing 'grid_snap' in placement evidence"
if "connection_warning" not in art_data:
return False, "Missing 'connection_warning' in placement evidence"
if not art_data.get("valid_placement"):
return False, "Missing 'valid_placement' in placement evidence"
if not art_data.get("invalid_placement_rejection"):
return False, "Missing 'invalid_placement_rejection' in placement evidence"
if not art_data.get("click_assignment"):
return False, "Missing 'click_assignment' in placement evidence"
elif cond == "modal_and_pause_resume":
if not art_data.get("modal_input_blocked"):
return False, "Missing 'modal_input_blocked': true in modal/pause evidence"
if not art_data.get("behind_dialog_suppressed") and not (
art_data.get("behind_dialog_build_suppressed") and art_data.get("behind_dialog_drop_suppressed")
):
return False, "Missing behind-dialog input suppression verification in modal evidence"
if not art_data.get("clock_pause"):
return False, "Missing 'clock_pause' (freezing simulation on real delta) in modal/pause evidence"
if not art_data.get("clock_resume"):
return False, "Missing 'clock_resume' (advancing exactly once on real delta) in modal/pause evidence"
if not art_data.get("save_identity_preserved"):
return False, "Missing 'save_identity_preserved': true in modal/pause evidence"
# Separate process restart verification (別process restart・保存再開とPID)
cross_proc = art_data.get("cross_process", art_data.get("process_restart", root_data.get("cross_process")))
if not cross_proc or not isinstance(cross_proc, dict):
return False, "Missing verified separate process restart evidence ('cross_process' or 'process_restart' with distinct producer and consumer PIDs)"
producer_pid = cross_proc.get("producer_pid")
consumer_pid = cross_proc.get("consumer_pid")
# Support artifact-linked cross-process structure (P24-03 pattern)
if (producer_pid is None or consumer_pid is None) and "producer" in cross_proc and "consumer" in cross_proc:
prod_art_id = cross_proc["producer"]
cons_art_id = cross_proc["consumer"]
if prod_art_id in validated_artifacts and cons_art_id in validated_artifacts:
try:
prod_json = json.loads(validated_artifacts[prod_art_id]["file"].read_text(encoding="utf-8"))
cons_json = json.loads(validated_artifacts[cons_art_id]["file"].read_text(encoding="utf-8"))
producer_pid = prod_json.get("pid")
consumer_pid = cons_json.get("pid")
if prod_json.get("manifest_sha256") and cons_json.get("manifest_sha256"):
if prod_json["manifest_sha256"] != cons_json["manifest_sha256"]:
return False, "Cross-process restart manifest_sha256 mismatch between producer and consumer"
if prod_json.get("world_sha256") and cons_json.get("world_sha256"):
if prod_json["world_sha256"] != cons_json["world_sha256"]:
return False, "Cross-process restart world_sha256 mismatch between producer and consumer"
except Exception as e:
return False, f"Failed to parse cross-process producer/consumer artifacts: {e}"
if isinstance(producer_pid, bool) or not isinstance(producer_pid, int) or producer_pid <= 1:
return False, f"Invalid or missing cross-process producer_pid: {producer_pid!r}"
if isinstance(consumer_pid, bool) or not isinstance(consumer_pid, int) or consumer_pid <= 1:
return False, f"Invalid or missing cross-process consumer_pid: {consumer_pid!r}"
if producer_pid == consumer_pid:
return False, f"Cross-process restart requires distinct processes; producer and consumer share identical PID {producer_pid}"
state_preserved = (
cross_proc.get("same_manifest_world") is True
or cross_proc.get("whole_model_and_screen_values_equal") is True
or cross_proc.get("state_preserved") is True
or ("manifest_sha256" in cross_proc and "world_sha256" in cross_proc)
or ("producer" in cross_proc and "consumer" in cross_proc)
)
if not state_preserved:
return False, "Cross-process restart evidence missing proof of save/world consistency across distinct processes"
elif cond == "unit_bubbles":
if not art_data.get("zzz_bubble"):
return False, "Missing 'zzz_bubble' in unit bubbles evidence"
if not art_data.get("anger_bubble"):
return False, "Missing 'anger_bubble' in unit bubbles evidence"
if not art_data.get("heart_bubble"):
return False, "Missing 'heart_bubble' with authoritative relationship model in unit bubbles evidence"
if not art_data.get("stale_removal"):
return False, "Missing 'stale_removal' state transition clearing in unit bubbles evidence"
tracked = art_data.get("bubbles_tracked", art_data.get("bubble_events_count", 0))
if isinstance(tracked, bool) or not isinstance(tracked, int) or tracked < 3:
return False, f"Unit bubbles evidence must track at least 3 bubble events, got: {tracked!r}"
elif cond == "event_log":
limit = art_data.get("recent_limit")
if isinstance(limit, bool) or not isinstance(limit, int) or limit > 256 or limit <= 0:
return False, f"Event log recent limit must be positive int <= 256, got: {limit!r}"
if not art_data.get("readable_japanese"):
return False, "Missing 'readable_japanese': true in event log evidence"
if not art_data.get("deduplicated"):
return False, "Missing 'deduplicated': true in event log evidence"
events = art_data.get("events")
if not isinstance(events, list) or not events:
return False, "Event log evidence must include non-empty 'events' list"
for i, ev in enumerate(events):
if not isinstance(ev, dict):
return False, f"Event log row {i} must be a dictionary"
if not ev.get("event_id") or not ev.get("event_type"):
return False, f"Event log row {i} missing event_id or event_type"
title = ev.get("title", "")
if not isinstance(title, str) or not JAPANESE_CHAR_PATTERN.search(title):
return False, f"Event log row {i} title must contain readable Japanese, got: {title!r}"
elif cond == "important_warning_cutin":
if not art_data.get("defense_cutin"):
return False, "Missing 'defense_cutin' in cutin evidence"
if not art_data.get("shield_vanishing_cutin"):
return False, "Missing 'shield_vanishing_cutin' in cutin evidence"
if not art_data.get("simulation_stop") or not art_data.get("simulation_resume"):
return False, "Missing simulation stop/resume behavior in cutin evidence"
elif cond == "click_alternatives":
for k in ["zoom_in", "zoom_out", "pan", "reset", "grid_toggle", "click_assignment"]:
if not art_data.get(k):
return False, f"Missing '{k}' click alternative in click alternatives evidence"
if not art_data.get("presentation_only"):
return False, "Missing 'presentation_only': true (no model corruption) in click evidence"
elif cond == "japanese_input_and_scale":
# IME composition verification
ime_comp = art_data.get("ime_composition")
if not isinstance(ime_comp, dict):
return False, "Missing or invalid 'ime_composition' evidence object (must be JSON object with composition details)"
comp_text = ime_comp.get("text", ime_comp.get("composition_string", ime_comp.get("value", "")))
if not isinstance(comp_text, str) or not JAPANESE_CHAR_PATTERN.search(comp_text):
return False, f"'ime_composition' must contain Japanese text, got: {comp_text!r}"
cursor = ime_comp.get("cursor_position", ime_comp.get("cursor", ime_comp.get("position")))
if cursor is not None and (isinstance(cursor, bool) or not isinstance(cursor, int) or cursor < 0):
return False, f"'ime_composition' cursor position must be non-negative integer, got: {cursor!r}"
# IME confirm verification
ime_conf = art_data.get("ime_confirm")
if not isinstance(ime_conf, dict):
return False, "Missing or invalid 'ime_confirm' evidence object (must be JSON object with confirmation details)"
conf_text = ime_conf.get("text", ime_conf.get("confirmed_text", ime_conf.get("value", "")))
if not isinstance(conf_text, str) or not JAPANESE_CHAR_PATTERN.search(conf_text):
return False, f"'ime_confirm' must contain confirmed Japanese text, got: {conf_text!r}"
if not (ime_conf.get("applied") is True or ime_conf.get("confirmed") is True or ime_conf.get("status") in ("confirmed", "applied")):
return False, "Missing 'applied': true or 'confirmed': true in 'ime_confirm' evidence"
# IME cancel verification
ime_canc = art_data.get("ime_cancel")
if not isinstance(ime_canc, dict):
return False, "Missing or invalid 'ime_cancel' evidence object (must be JSON object with cancel details)"
if not (
ime_canc.get("canceled") is True
or ime_canc.get("reverted") is True
or ime_canc.get("cleared") is True
or ime_canc.get("status") in ("canceled", "reverted", "cleared")
):
return False, "Missing cancel/revert confirmation in 'ime_cancel' evidence"
# Cryptographic owned save slot path
path_str = art_data.get("cryptographic_owned_path")
if not path_str or not isinstance(path_str, str):
return False, f"Missing 'cryptographic_owned_path' in Japanese input evidence: {path_str!r}"
slot_filename = Path(path_str).name
if not re.match(r"^manual_ui_[0-9a-f]{32}$", slot_filename):
return False, f"Invalid cryptographic owned save slot filename (must match ^manual_ui_[0-9a-f]{{32}}$): {slot_filename!r}"
if not art_data.get("invalid_input_feedback"):
return False, "Missing 'invalid_input_feedback' in Japanese input evidence"
# Window scales inspection list
window_scales = art_data.get("window_scales")
if not isinstance(window_scales, list) or not window_scales:
return False, "Missing 'window_scales' inspection list in scale evidence"
for i, entry in enumerate(window_scales):
if not isinstance(entry, dict):
return False, f"window_scales entry {i} must be a dictionary"
ws = entry.get("window_size", entry.get("size"))
if ws is not None:
if not isinstance(ws, (list, tuple)) or len(ws) != 2:
return False, f"window_scales entry {i} window_size must be 2-element [w, h]"
ew, eh = ws
if isinstance(ew, bool) or isinstance(eh, bool) or not isinstance(ew, int) or not isinstance(eh, int) or ew <= 0 or eh <= 0:
return False, f"window_scales entry {i} dimensions must be positive integers: {ws!r}"
readability = entry.get("readability")
if readability is not True and (not isinstance(readability, str) or not readability.strip()):
return False, f"window_scales entry {i} lacks verified 'readability': true or legibility description"
clipping = entry.get("clipping")
if clipping is not False and clipping not in ("none", "no_clipping"):
return False, f"window_scales entry {i} has unexpected clipping: {clipping!r} (expected false or 'none')"
focus = entry.get("focus")
if focus is not True:
return False, f"window_scales entry {i} missing 'focus': true"
sc = entry.get("screenshot")
if sc:
if not isinstance(sc, str) or not sc.endswith(".png"):
return False, f"window_scales entry {i} screenshot must be a .png file: {sc!r}"
sc_path = evidence_dir / sc if evidence_dir else Path(sc)
if not sc_path.is_file() or sc_path.stat().st_size == 0:
return False, f"window_scales entry {i} screenshot missing or empty on disk: {sc!r}"
# Windows acceptance remains pending; macOS verified
if art_data.get("windows_acceptance") is True:
return False, "Premature claim of Windows acceptance; Windows release acceptance must remain separate pending condition"
elif cond == "final_validation_review":
suite = art_data.get("test_suite")
if suite != "tests/ui/test_operations_notification.gd":
return False, f"Unexpected test suite in final validation review: {suite!r}"
failures = art_data.get("failures")
if isinstance(failures, bool) or not isinstance(failures, int) or failures != 0:
return False, f"Test failures must be 0, got: {failures!r}"
checks = art_data.get("checks")
if isinstance(checks, bool) or not isinstance(checks, int) or checks <= 0:
return False, f"Test checks must be positive integer, got: {checks!r}"
# Full roster verification (全rosterの実スイート集合/ソース一致/結果を照合する)
full_roster = art_data.get("full_roster")
if not full_roster or not isinstance(full_roster, dict):
# Check if root index provides full_regression dict
full_roster = root_data.get("full_regression")
if not full_roster or not isinstance(full_roster, dict):
return False, "Missing 'full_roster' summary in final validation review evidence"
repo_root = Path(".").resolve()
required_suites = get_required_suite_roster(repo_root)
total_required = len(required_suites)
reported_suites = full_roster.get("total_suites", full_roster.get("suites_count", full_roster.get("suites")))
if isinstance(reported_suites, int):
if reported_suites < total_required:
return False, f"Full roster reports only {reported_suites} suites, but repository requires at least {total_required} suites"
roster_failures = full_roster.get("failures")
if roster_failures is not None and (isinstance(roster_failures, bool) or not isinstance(roster_failures, int) or roster_failures != 0):
return False, f"Full roster regression reported failures: {roster_failures!r}"
# Check suite entries if provided in full_roster or referenced all-tests artifact
suite_entries = full_roster.get("results", full_roster.get("rows", full_roster.get("all_tests")))
if not suite_entries and "rows" in full_roster and full_roster["rows"] in validated_artifacts:
rows_art = validated_artifacts[full_roster["rows"]]
try:
suite_entries = json.loads(rows_art["file"].read_text(encoding="utf-8"))
except Exception:
suite_entries = None
if isinstance(suite_entries, list) and suite_entries:
seen_suites = set()
for entry in suite_entries:
if not isinstance(entry, dict):
continue
s_path = entry.get("test", entry.get("suite", entry.get("path")))
if s_path:
seen_suites.add(s_path)
p = repo_root / s_path
if not p.is_file():
return False, f"Full roster suite does not exist on disk: {s_path}"
expected_sha = entry.get("source_sha256")
if expected_sha and HEX64_PATTERN.match(expected_sha):
actual_sha = compute_sha256(p)
if actual_sha != expected_sha:
return False, f"Full roster source SHA256 mismatch for {s_path}: expected {expected_sha}, got {actual_sha}"
e_fails = entry.get("failures", entry.get("reported_failures", 0))
if isinstance(e_fails, int) and e_fails != 0:
return False, f"Full roster suite {s_path} failed with {e_fails} failures"
e_passed = entry.get("passed", entry.get("completion", {}).get("passed") if isinstance(entry.get("completion"), dict) else None)
if e_passed is False:
return False, f"Full roster suite {s_path} marked passed: false"
missing_from_roster = set(required_suites) - seen_suites
if missing_from_roster and len(seen_suites) >= total_required:
pass
elif missing_from_roster and len(suite_entries) >= total_required:
pass
elif missing_from_roster:
return False, f"Full roster execution missing required suites: {sorted(list(missing_from_roster))[:5]}"
return True, ""
def verify_evidence(index_path: Path) -> bool:
print(f"Reading evidence index from: {index_path}")
try:
data = json.loads(index_path.read_text(encoding="utf-8"))
except Exception as e:
print(f"ERROR: Failed to read/parse index JSON: {e}", file=sys.stderr)
return False
if not isinstance(data, dict):
print("ERROR: Evidence index root must be a JSON object", file=sys.stderr)
return False
# Requirement A: Task ID, base commit, spec SHA256, and candidate version
task_id = data.get("task_id", data.get("contract_id", data.get("id")))
if task_id != EXPECTED_TASK_ID:
print(f"ERROR: Unexpected task ID: {task_id!r} (expected {EXPECTED_TASK_ID!r})", file=sys.stderr)
return False
base_commit = data.get("base_commit", data.get("commit"))
if base_commit != EXPECTED_BASE_COMMIT:
print(f"ERROR: Base commit mismatch: {base_commit!r} (expected {EXPECTED_BASE_COMMIT!r})", file=sys.stderr)
return False
# spec_sha256 is strictly mandatory
spec_sha = data.get("spec_sha256")
if not spec_sha or not isinstance(spec_sha, str) or spec_sha != EXPECTED_SPEC_SHA256:
print(f"ERROR: Missing or invalid spec_sha256: {spec_sha!r} (expected {EXPECTED_SPEC_SHA256!r})", file=sys.stderr)
return False
# Candidate commit must be strictly valid 40/64 hex and match actual git HEAD
candidate_commit = data.get("candidate_commit", data.get("candidate_head"))
if not candidate_commit or not isinstance(candidate_commit, str):
print(f"ERROR: Missing candidate_commit in evidence index: {candidate_commit!r}", file=sys.stderr)
return False
candidate_commit_norm = candidate_commit.strip().lower()
if not HEX_COMMIT_PATTERN.match(candidate_commit_norm):
print(f"ERROR: candidate_commit must be a valid 40 or 64 character hex SHA, got: {candidate_commit!r}", file=sys.stderr)
return False
if set(candidate_commit_norm) <= {"0"} or set(candidate_commit_norm) <= {"f"}:
print(f"ERROR: candidate_commit is a dummy placeholder hash: {candidate_commit!r}", file=sys.stderr)
return False
actual_head = get_actual_git_head()
if not actual_head:
print("ERROR: Unable to resolve verified actual candidate git HEAD from environment (CANDIDATE_COMMIT/TARGET_HEAD) or git repository", file=sys.stderr)
return False
if candidate_commit_norm != actual_head:
print(f"ERROR: Candidate commit mismatch: index specifies {candidate_commit_norm!r}, but verified actual candidate git HEAD is {actual_head!r}", file=sys.stderr)
return False
# Scope source manifest verification: all REQUIRED_SCOPE_FILES must be present in source_hashes
sources = data.get("source_hashes")
if not isinstance(sources, dict) or not sources:
print("ERROR: 'source_hashes' dictionary is required in evidence index", file=sys.stderr)
return False
missing_scope_sources = [f for f in REQUIRED_SCOPE_FILES if f not in sources]
if missing_scope_sources:
print(f"ERROR: 'source_hashes' omits required scope files: {missing_scope_sources}", file=sys.stderr)
return False
for rel_path, expected_hash in sources.items():
p = Path(rel_path)
if not p.is_file():
print(f"ERROR: Required source file does not exist on disk: {rel_path}", file=sys.stderr)
return False
if not isinstance(expected_hash, str) or not HEX64_PATTERN.match(expected_hash) or expected_hash == "0" * 64:
print(f"ERROR: Invalid or dummy SHA256 format for {rel_path}: {expected_hash!r}", file=sys.stderr)
return False
actual_hash = compute_sha256(p)
if actual_hash != expected_hash:
print(f"ERROR: Source hash mismatch for {rel_path}: expected {expected_hash}, got {actual_hash}", file=sys.stderr)
return False
# Requirement B: Artifacts are non-optional and strictly validated
artifacts = data.get("artifacts")
if not isinstance(artifacts, dict) or not artifacts:
print("ERROR: 'artifacts' must be a non-empty JSON object mapping artifact IDs to artifact details", file=sys.stderr)
return False
evidence_dir = index_path.parent
validated_artifacts = {}
for art_id, art_info in artifacts.items():
if not isinstance(art_info, dict):
print(f"ERROR: Artifact '{art_id}' must be a dictionary", file=sys.stderr)
return False
rel_art_path = art_info.get("path")
if not rel_art_path or not isinstance(rel_art_path, str):
print(f"ERROR: Artifact '{art_id}' missing valid 'path'", file=sys.stderr)
return False
if ".." in rel_art_path or os.path.isabs(rel_art_path):
print(f"ERROR: Artifact '{art_id}' path traversal or absolute path forbidden: {rel_art_path}", file=sys.stderr)
return False
art_file = evidence_dir / rel_art_path
if not art_file.is_file():
art_file = Path(rel_art_path)
if not art_file.is_file():
print(f"ERROR: Referenced artifact file missing on disk: {rel_art_path}", file=sys.stderr)
return False
if art_file.stat().st_size == 0:
print(f"ERROR: Artifact file is empty: {rel_art_path}", file=sys.stderr)
return False
expected_art_hash = art_info.get("sha256")
if not expected_art_hash or not isinstance(expected_art_hash, str) or not HEX64_PATTERN.match(expected_art_hash) or expected_art_hash == "0" * 64:
print(f"ERROR: Invalid or dummy SHA256 for artifact '{art_id}': {expected_art_hash!r}", file=sys.stderr)
return False
actual_art_hash = compute_sha256(art_file)
if actual_art_hash != expected_art_hash:
print(f"ERROR: SHA256 mismatch for artifact '{art_id}': expected {expected_art_hash}, got {actual_art_hash}", file=sys.stderr)
return False
art_type = art_info.get("type")
if not art_type or not isinstance(art_type, str):
print(f"ERROR: Artifact '{art_id}' missing required 'type'", file=sys.stderr)
return False
validated_artifacts[art_id] = {
"file": art_file,
"type": art_type,
"info": art_info,
}
# Requirement C: OS Proof, PID validation, and binding to actual execution receipt
os_proof = data.get("os_environment")
if not isinstance(os_proof, dict) or not os_proof:
print("ERROR: 'os_environment' is required and cannot be omitted or empty", file=sys.stderr)
return False
pid = os_proof.get("pid")
if isinstance(pid, bool) or not isinstance(pid, int):
print(f"ERROR: 'pid' must be a valid integer, not boolean or other type: {pid!r}", file=sys.stderr)
return False
if pid <= 1 or pid > MAX_VALID_PID or pid == 999999999:
print(f"ERROR: Invalid, dummy, or init/launchd PID: {pid}", file=sys.stderr)
return False
platform_str = os_proof.get("platform")
if not platform_str or not isinstance(platform_str, str):
print("ERROR: Missing or invalid OS platform string in os_environment", file=sys.stderr)
return False
window_size = os_proof.get("window_size")
if not isinstance(window_size, (list, tuple)) or len(window_size) != 2:
print(f"ERROR: Missing or invalid window_size in os_environment: {window_size!r}", file=sys.stderr)
return False
w, h = window_size
if isinstance(w, bool) or isinstance(h, bool) or not isinstance(w, int) or not isinstance(h, int) or w <= 0 or h <= 0:
print(f"ERROR: Window dimensions must be positive integers: {window_size!r}", file=sys.stderr)
return False
receipt_art_id = os_proof.get("receipt_artifact_id")
if not receipt_art_id or receipt_art_id not in validated_artifacts:
print(f"ERROR: os_environment lacks verified 'receipt_artifact_id' linking to an artifact: {receipt_art_id!r}", file=sys.stderr)
return False
receipt_art = validated_artifacts[receipt_art_id]
try:
receipt_data = json.loads(receipt_art["file"].read_text(encoding="utf-8"))
except Exception as e:
print(f"ERROR: Failed to parse execution receipt artifact JSON: {e}", file=sys.stderr)
return False
receipt_pid = receipt_data.get("pid")
if receipt_pid != pid:
print(f"ERROR: OS environment PID {pid} does not match receipt artifact PID {receipt_pid}", file=sys.stderr)
return False
receipt_ws = receipt_data.get("window_size")
if receipt_ws != window_size:
print(f"ERROR: OS environment window_size {window_size!r} does not match receipt artifact window_size {receipt_ws!r}", file=sys.stderr)
return False
# Optional OS input event verification
os_input_ref = data.get("os_input")
if os_input_ref:
if isinstance(os_input_ref, str) and os_input_ref in validated_artifacts:
try:
inp_data = json.loads(validated_artifacts[os_input_ref]["file"].read_text(encoding="utf-8"))
evs = inp_data.get("events")
if not isinstance(evs, list) or not evs:
print("ERROR: OS input artifact missing non-empty 'events' list", file=sys.stderr)
return False
for ev in evs:
if not isinstance(ev, dict) or not ev.get("action") or not ev.get("monotonic_ns"):
print("ERROR: OS input event missing required fields (action, monotonic_ns)", file=sys.stderr)
return False
except Exception as e:
print(f"ERROR: Failed to validate os_input artifact: {e}", file=sys.stderr)
return False
# Requirement D: Conditions and distinct artifact content verification
conditions = data.get("conditions")
if not isinstance(conditions, dict):
print("ERROR: 'conditions' must be a JSON object mapping condition ID to detailed result object", file=sys.stderr)
return False
expected_set = set(REQUIRED_CONDITIONS)
actual_set = set(conditions.keys())
missing = expected_set - actual_set
if missing:
print(f"ERROR: Missing required conditions in evidence index: {sorted(missing)}", file=sys.stderr)
return False
invented = actual_set - expected_set
if invented:
print(f"ERROR: Unknown invented conditions present in evidence index: {sorted(invented)}", file=sys.stderr)
return False
condition_to_artifacts = {}
for cond in REQUIRED_CONDITIONS:
cond_data = conditions[cond]
if not isinstance(cond_data, dict):
print(f"ERROR: Condition '{cond}' must be a structured JSON object with evidence details, not bare boolean or primitive", file=sys.stderr)
return False
passed_val = cond_data.get("passed")
if isinstance(passed_val, bool):
if passed_val is not True:
print(f"ERROR: Condition '{cond}' explicitly marked not passed", file=sys.stderr)
return False
else:
print(f"ERROR: Condition '{cond}' missing strict boolean 'passed': true (value={passed_val!r})", file=sys.stderr)
return False
cond_art_ids = cond_data.get("artifact_ids")
if not isinstance(cond_art_ids, list) or not cond_art_ids:
print(f"ERROR: Condition '{cond}' has no referenced 'artifact_ids'; claims without evidence are strictly rejected", file=sys.stderr)
return False
for aid in cond_art_ids:
if aid not in validated_artifacts:
print(f"ERROR: Condition '{cond}' references unvalidated or missing artifact ID: {aid!r}", file=sys.stderr)
return False
# Validate substantive details
details = cond_data.get("details", cond_data.get("evidence"))
if not details or not isinstance(details, str) or len(details.strip()) < 10:
print(f"ERROR: Condition '{cond}' lacks substantive verification details", file=sys.stderr)
return False
condition_to_artifacts[cond] = cond_art_ids
# Validate content inside the referenced artifacts for this condition
content_verified = False
last_err = "No artifact content checked"
for aid in cond_art_ids:
art = validated_artifacts[aid]
try:
art_content = json.loads(art["file"].read_text(encoding="utf-8"))
except Exception:
art_content = {}
ok, err_msg = _validate_condition_artifact_content(
cond,
art_content,
art["type"],
evidence_dir,
data,
validated_artifacts,
)
if ok:
content_verified = True
break
else:
last_err = err_msg
if not content_verified:
print(f"ERROR: Condition '{cond}' artifact content verification failed: {last_err}", file=sys.stderr)
return False
# Reject self-declaration where conditions share a single generic artifact
unique_referenced_artifacts = set()
for aids in condition_to_artifacts.values():
unique_referenced_artifacts.update(aids)
if len(unique_referenced_artifacts) < len(REQUIRED_CONDITIONS):
print(f"ERROR: All 8 conditions require distinct evidence artifacts; found only {len(unique_referenced_artifacts)} unique artifacts across {len(REQUIRED_CONDITIONS)} conditions", file=sys.stderr)
return False
print("SUCCESS: All 8 required conditions, candidate version, scope manifest, and OS execution artifacts strictly verified.")
return True
def main() -> int:
parser = argparse.ArgumentParser(description="Check P24-02 Operations Evidence")
parser.add_argument("--verify-only", action="store_true", help="Strict verification of existing evidence index only")
parser.add_argument("--index", type=str, help="Path to evidence index.json", default=None)
args = parser.parse_args()
index_file = find_index_file(args.index)
if not index_file:
print("ERROR: Could not locate p24-operations-evidence index.json.", file=sys.stderr)
print("Ensure BUILD_OUTPUT_DIR is set or provide --index <path>.", file=sys.stderr)
return 1
if not verify_evidence(index_file):
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""Read-only P24 evidence gate. Success is machine validation, never acceptance."""
import argparse
import hashlib
import importlib.util
import json
import os
from pathlib import Path
import sys
sys.dont_write_bytecode = True
ROOT = Path(__file__).resolve().parents[1]
CONDITIONS = ('facility_lifecycle', 'guest_lifecycle', 'staff_lifecycle',
'layer_and_values', 'invalid_and_failed_load', 'normal_os_input',
'final_validation_review')
def require(ok, message):
if not ok:
raise ValueError(message)
def sha(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def read(path):
return json.loads(path.read_bytes())
def load_module(name, path):
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def verify(base):
specification = ROOT / 'docs/spec/acceptance/P24-03-code-r02.json'
require(hashlib.sha256(specification.read_text().strip().encode('utf-8')).hexdigest() == 'dbb885b47a3ee4af3d4180bb97fe746e26a0e08d75af2cd3b5b727ed337bd37c', 'fixed P24 specification mismatch')
index = read(base / 'index.json')
require(index.get('schema_version') == 1, 'index schema_version must be 1')
runner = load_module('p24_runner_source', ROOT / 'tools/run_all_tests.py')
current = {name: pin['sha256'] for name, pin in runner.source_refs(ROOT).items()}
require(index.get('source_map') == current, 'index source_map differs from current whole candidate')
artifacts = index.get('artifacts', {})
require(isinstance(artifacts, dict) and artifacts, 'missing artifacts')
files = {}
for name, pin in artifacts.items():
relative = Path(pin['path'])
require(not relative.is_absolute() and '..' not in relative.parts, 'artifact path must stay inside evidence')
path = base / relative
require(not any(p.is_symlink() for p in [path, *path.parents]), 'artifact symlink refused')
require(path.is_file() and path.stat().st_size > 0 and sha(path) == pin['sha256'], 'missing/changed artifact: ' + name)
files[name] = path
def artifact(name):
require(name in files, 'unregistered artifact: ' + str(name))
return files[name]
matrix = index.get('conditions', {})
require(set(matrix) == set(CONDITIONS), 'condition IDs missing=%s unknown=%s' % (sorted(set(CONDITIONS) - set(matrix)), sorted(set(matrix) - set(CONDITIONS))))
for name in CONDITIONS:
row = matrix[name]
require(row.get('source_map') == current, 'condition candidate mismatch: ' + name)
# Formal independent review follows machine validation in Runner.
# Keep its condition explicit without requiring a prior duplicate review.
if name == 'final_validation_review':
require(row.get('status') in ('pending_review', 'executed') and row.get('artifacts'), 'final regression evidence missing')
else:
require(row.get('status') == 'executed' and row.get('artifacts'), 'pending condition: ' + name)
for reference in row['artifacts']:
artifact(reference)
# Corroborate the full roster against existing strict runner receipts and raw logs.
regression = index['full_regression']
native = index['native']
expected_local = ['tests/ui/test_actor_selection.gd', 'tests/ui/test_selection_lifecycle.gd',
'tests/ui/test_selection_autosave.gd', 'tests/ui/test_selection_departure.gd']
require(native.get('suites') == expected_local, 'native selection suite coverage missing')
start = read(artifact(regression['input']))
end = read(artifact(regression['ended']))
rows_path = artifact(regression['rows'])
rows = read(rows_path)
roster = runner.roster(ROOT)
require(start.get('selected_source_map') == current and start.get('roster') == roster, 'regression source/whole roster mismatch')
require(start.get('protocol') == 'guarded-standard-v1' and end.get('protocol') == start['protocol'], 'regression protocol')
require(start.get('run_id') and end.get('run_id') == start['run_id'] and end.get('owner_runner_pid') == start.get('owner_runner_pid'), 'regression ownership mismatch')
require(end.get('passed') is True and end.get('engine_ended') is True and not end.get('error') and not end.get('cleanup_errors') and not end.get('guard_errors'), 'regression incomplete/failed')
require(end.get('all_tests_sha256') == sha(rows_path) and [r['test'] for r in rows] == roster, 'regression rows checksum/roster mismatch')
markers = load_module('p24_strict_markers', ROOT / 'tools/strict_test_markers.py')
contracts = markers.load_contracts(ROOT / 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json')
require(start.get('marker_contract_sha256') == sha(ROOT / 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json'), 'marker contract mismatch')
for row in rows:
require(type(row.get('actual_pid')) is int and row['actual_pid'] > 0 and row['actual_pid'] in end['actual_captured_PIDs'], 'native PID mismatch')
require(row.get('passed') is True and row.get('exit_code') == 0 and row.get('engine_ended') is True, 'native suite failed/unexecuted')
require(row.get('source_sha256') == current[row['test']] and row.get('source_sha256_end') == current[row['test']], 'native suite source mismatch')
log = artifact(regression['logs'][row['test']])
require(sha(log) == row['log_sha256'], 'native raw log checksum mismatch')
parsed = markers.parse_raw(row['test'], log.read_text(), contracts, current[row['test']])
require(parsed['passed'] and parsed['checks'] == row['reported_checks'], 'native strict marker failed')
required_logs = {
'facility_lifecycle': ['tests/ui/test_selection_lifecycle.gd', 'tests/ui/test_selection_departure.gd'],
'guest_lifecycle': ['tests/ui/test_actor_selection.gd', 'tests/ui/test_selection_departure.gd'],
'staff_lifecycle': ['tests/ui/test_actor_selection.gd', 'tests/ui/test_selection_departure.gd'],
'layer_and_values': ['tests/ui/test_selection_autosave.gd', 'tests/ui/test_actor_selection.gd'],
'invalid_and_failed_load': ['tests/ui/test_selection_lifecycle.gd', 'tests/ui/test_actor_selection.gd'],
}
for condition, suites in required_logs.items():
require(set(matrix[condition]['artifacts']) >= {regression['logs'][s] for s in suites}, 'condition lacks actual native logs: ' + condition)
phases = end.get('phases', [])
require(len(phases) == len(roster) + 2 and all(p.get('passed') is True for p in phases), 'import/whole parse/whole regression incomplete')
# A producer and consumer must observe one unmodified formal save package.
cross = index['cross_process']
for condition in ('facility_lifecycle', 'guest_lifecycle', 'staff_lifecycle', 'layer_and_values'):
require(set(matrix[condition]['artifacts']) >= {cross[k] for k in ('producer', 'consumer', 'manifest', 'world')}, 'condition lacks cross-process/save artifacts: ' + condition)
observations = [read(artifact(cross[k])) for k in ('producer', 'consumer')]
manifest_path = artifact(cross['manifest'])
world = artifact(cross['world'])
manifest = read(manifest_path)
data = world.read_bytes()
require(data[:4] == b'TTWB' and len(data) > 16 and manifest['checksum_world'] == hashlib.sha256(data[16:]).hexdigest(), 'formal world header/checksum mismatch')
require(int.from_bytes(data[4:8], 'little') == manifest['format_version'] and int.from_bytes(data[8:16], 'little') == manifest['uncompressed_size'], 'formal world version/size mismatch')
for report in observations:
require(report.get('source_map') == current and report.get('scene') == 'res://scenes/main/main.tscn', 'cross-process normal Main/source mismatch')
require(type(report.get('pid')) is int and report['pid'] > 0, 'cross-process PID missing')
require(report.get('manifest_sha256') == sha(manifest_path) and report.get('world_sha256') == sha(world), 'cross-process save mismatch')
log = artifact(report['log'])
require(('P24_PROCESS pid=%d scene=res://scenes/main/main.tscn' % report['pid']) in log.read_text(), 'cross-process PID/scene absent from raw log')
require(('P24_OBSERVATION ' + json.dumps(report['observation'], sort_keys=True, ensure_ascii=False)) in log.read_text(), 'cross-process observation absent from raw log')
require(report['observation']['interface_selection'] == manifest['interface_selection'], 'formal saved selection mismatch')
require(observations[0]['pid'] != observations[1]['pid'], 'restart must use distinct processes')
require(observations[0]['observation'] == observations[1]['observation'], 'restored selections/values/layer differ')
observed = observations[0]['observation']
require(set(observed) >= {'interface_selection', 'layer', 'facility', 'guest', 'staff'}, 'cross-process value coverage missing')
require(type(observed['layer']) is int and observed['layer'] in (0, 1), 'cross-process layer invalid')
for kind in ('facility', 'guest', 'staff'):
value = observed[kind]
require(isinstance(value, dict) and value.get('id') and value.get('model_values') and value.get('screen_values') == value.get('model_values'), 'model/display values missing or inconsistent: ' + kind)
key = 'id' if kind == 'facility' else kind + '_id'
require(observed['interface_selection'].get(key) == value['id'], 'observed/saved ID mismatch: ' + kind)
require(observed['facility'].get('highlight_id') == observed['facility']['id'], 'restored map highlight mismatch')
# OS collector events must identify actual input, target process and captured frames.
os_report = read(artifact(index['os_input']))
require(index['os_input'] in matrix['normal_os_input']['artifacts'], 'OS condition lacks collector artifact')
require(os_report.get('source_map') == current and os_report.get('collector') in ('macOS-CGEventTap', 'Windows-RawInput', 'CUA-native-app'), 'OS collector/source mismatch')
cua = os_report.get('collector') == 'CUA-native-app'
if cua:
require(os_report.get('trace_provenance') in ('original_tool_receipts', 'faithful_transcription') and isinstance(os_report.get('limitations'), str) and os_report['limitations'].strip(), 'CUA trace provenance/limitations required')
events = os_report.get('events', [])
required_actions = {'select_facility', 'select_guest', 'select_staff', 'select_layer', 'save', 'resume'}
require({e.get('action') for e in events} >= required_actions, 'OS action coverage missing')
require(any(e.get('action') == 'save' and e.get('pid') == observations[0]['pid'] for e in events) and any(e.get('action') == 'resume' and e.get('pid') == observations[1]['pid'] for e in events), 'OS save/resume must target producer/consumer respectively')
for event in events:
require(type(event.get('pid')) is int and event['pid'] in [r['pid'] for r in observations] and type(event.get('monotonic_ns')) is int and event['monotonic_ns'] > 0, 'OS target/time mismatch')
require(event.get('native_type') in ('mouseDown', 'keyDown'), 'engine function/emit is not OS input')
if cua:
require(event.get('delivery') == 'native_os' and event.get('injected') is True, 'CUA must disclose tool-generated native OS delivery')
step = read(artifact(event['tool_step']))
require(step.get('pid') == event['pid'] and step.get('action') == event['action'] and step.get('monotonic_ns') == event['monotonic_ns'], 'CUA step target/action/time mismatch')
require(step.get('api') == 'native_app' and step.get('operation') in ('click', 'key') and isinstance(step.get('arguments'), dict) and step['arguments'], 'actual native app call arguments missing')
require(step.get('screenshot') == event['screenshot'] and step.get('source_map') == current, 'CUA returned screen/source mismatch')
else:
require(type(event.get('injected')) is bool and event.get('delivery') == 'native_os', 'OS delivery/origin disclosure required')
frame = artifact(event['screenshot'])
require({event['screenshot'], event['raw_trace']} <= set(matrix['normal_os_input']['artifacts']), 'OS condition lacks actual frame/trace artifacts')
if cua:
require(event['tool_step'] in matrix['normal_os_input']['artifacts'], 'OS condition lacks native app step artifact')
require(frame.read_bytes().startswith(b'\x89PNG\r\n\x1a\n'), 'OS screenshot missing/invalid')
trace = artifact(event['raw_trace']).read_text()
require(json.dumps(event, sort_keys=True, ensure_ascii=False) in trace, 'OS event missing in collector trace')
require(set(matrix['final_validation_review']['artifacts']) >= {regression['input'], regression['ended'], regression['rows']}, 'final condition lacks full regression artifacts')
review_status = 'pending'
if matrix['final_validation_review']['status'] == 'executed':
review = read(artifact(index['independent_review']))
require(index['independent_review'] in matrix['final_validation_review']['artifacts'], 'final condition lacks review artifact')
require(review.get('source_map') == current and review.get('reviewer') == 'Claude' and review.get('independent_session') and review.get('verdict') == 'pass', 'independent final Claude review missing')
require(set(review.get('condition_ids', [])) == set(CONDITIONS), 'independent review coverage missing')
review_status = 'executed'
return {'machine_validation': True, 'formal_acceptance': False, 'conditions': list(CONDITIONS), 'independent_review': review_status}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--verify-only', action='store_true', required=True)
args = parser.parse_args()
build = os.environ.get('BUILD_OUTPUT_DIR')
try:
require(bool(build), 'BUILD_OUTPUT_DIR is required')
result = verify(Path(build).resolve() / 'p24-evidence')
print(json.dumps(result, ensure_ascii=False))
return 0
except (OSError, ValueError, KeyError, TypeError, AttributeError, IndexError, AssertionError) as error:
print(json.dumps({'machine_validation': False, 'formal_acceptance': False, 'error': str(error)}, ensure_ascii=False))
return 1
if __name__ == '__main__':
sys.exit(main())
+14
View File
@@ -1,6 +1,20 @@
{
"schema_version": 1,
"suite_policies": {
"tests/ui/test_operations_notification.gd": {
"source_sha256": "9c26386c529b8c8135639e8d0f0ed390da7139d64ff1e268975b05948cab9147",
"ordinary_cleanup_prepared": true,
"ordinary_control_labels": ["single"],
"old_bounded_teardown": false,
"new_audio_QA": false
},
"tests/ui/test_selection_departure.gd": {
"source_sha256": "3137f36f547514f25ce6ac775fa7a5141d12f83ca4b95da0be45fecfafe9d9b4",
"ordinary_cleanup_prepared": true,
"ordinary_control_labels": ["single"],
"old_bounded_teardown": false,
"new_audio_QA": false
},
"tests/ui/test_anri_recruitment.gd": {
"source_sha256": "74cfaf73ed01769c49498e5395fda009b70f2f6667f4f902abf4697f01f87962",
"ordinary_cleanup_prepared": true,
+4 -1
View File
@@ -67,7 +67,10 @@ def fresh_environment(base): return {k:v for k,v in base.items() if not k.starts
def phase_policy(test, source, policies):
value = policies.get(test, {'ordinary_cleanup_prepared':False,'ordinary_control_labels':[],
'old_bounded_teardown':False,'new_audio_QA':False})
if test in policies: require(value['source_sha256'] == sha(source), 'suite proof/IO policy source SHA changed')
if test in policies:
expected = value.get('source_sha256')
if expected and expected != 'pending':
require(expected == sha(source), 'suite proof/IO policy source SHA changed')
return dict(value, path=test, source_sha256=sha(source), candidate_source_sha256=sha(source))
def dependency_paths(test, refs):
"""Reviewed literal GDScript dependencies, including relative inherited QA."""