From b543c26c95a1d634b8d7efdf127cc55eba55611a Mon Sep 17 00:00:00 2001 From: Agent Control Runner Date: Fri, 16 Oct 2026 06:12:33 +0900 Subject: [PATCH] =?UTF-8?q?P04-01:=20=E5=90=8C=E6=99=82=E7=99=BA=E7=94=9F?= =?UTF-8?q?=E3=81=AE=E6=9B=B4=E6=96=B0=E8=A6=8F=E7=B4=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../execution/p04-01/REPAIR17_STATUS.md | 92 ++ .../p04-01/REPAIR18_COLLECTOR_OUTPUT.jsonl | 2 + .../p04-01/REPAIR18_COMBINED_OUTPUT.json | 1 + .../p04-01/REPAIR18_REAL_SERIES_OUTPUT.json | 153 +++ .../REPAIR18_SERIES_FIXTURE_OUTPUT.json | 367 +++++++ .../execution/p04-01/REPAIR18_STATUS.md | 211 ++++ .../execution/p04-01/REPAIR19_CORRIGENDUM.md | 58 ++ .../execution/p04-01/REPAIR19_STATUS.md | 78 ++ .../execution/p04-01/REPAIR20_CORRIGENDUM.md | 40 + .../execution/p04-01/REPAIR20_STATUS.md | 183 ++++ .../execution/p04-01/build_p04_index.py | 164 +++ .../p04-01/checker_encoder_fixtures.py | 89 ++ .../execution/p04-01/checker_join_fixtures.py | 70 ++ .../p04-01/checker_negative_fixtures.py | 20 +- .../execution/p04-01/checker_role_fixtures.py | 124 +++ .../p04-01/checker_series_fixtures.py | 123 +++ .../execution/p04-01/collect_p04_native.py | 61 ++ .../execution/p04-01/p04_native_series.py | 930 ++++++++++++++++++ tools/check_p04_order_evidence.py | 334 ++++++- 19 files changed, 3076 insertions(+), 24 deletions(-) create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR17_STATUS.md create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_COLLECTOR_OUTPUT.jsonl create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_COMBINED_OUTPUT.json create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_REAL_SERIES_OUTPUT.json create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_SERIES_FIXTURE_OUTPUT.json create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_STATUS.md create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR19_CORRIGENDUM.md create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR19_STATUS.md create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR20_CORRIGENDUM.md create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR20_STATUS.md create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/build_p04_index.py create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/checker_encoder_fixtures.py create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/checker_join_fixtures.py create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/checker_role_fixtures.py create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/checker_series_fixtures.py create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/collect_p04_native.py create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/p04_native_series.py diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR17_STATUS.md b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR17_STATUS.md new file mode 100644 index 0000000..c39e09c --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR17_STATUS.md @@ -0,0 +1,92 @@ +# REPAIR17 — blocked on required raw access + +This is not accepted or completed. Stage R is not implemented. No new condition +is claimed as proven. This document records an observed input-access blocker, +not a replacement implementation or a proposal to trust Root summaries. + +## Checkout and scope + +- Observed HEAD: `accd07c91d4e34d9b7c7fc20e389df2424b167e1` (the requested reused checkout). +- Registered contract base: `deb1bcb2374eb378b840ca345db952bd8572cb6e`. +- Only this status file was created. The checker, `verify_native`, C checks, + existing fixtures, probes, originals, and logs were not modified. +- No git writes, Runner operations, engine runs, or other agents were used. + +## Reproduced blocker + +Let `B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`. +The four `B//raw/{writer,reader}.json` files are readable. Their literal +`slots` mappings locate the formal packages. Attempting `Path.read_bytes()` on +every `journal.log`, `manifest.json`, `thumbnail.png`, and `world.bin` in those +slots returned `PermissionError: [Errno 1] Operation not permitted`: + +| Series | Slots per role | Unreadable original + copy members | +| --- | ---: | ---: | +| `p04-s1-native-r02` | 14 | 112 | +| `p04-s2a-native-r03` | 1 | 8 | +| `p04-s2b-native-r02` | 6 | 48 | +| `p04-s3-native-r01` | 22 | 176 | + +For example, this exact required S3 original cannot be read: + +```text +/Users/korizatou114/Library/Application Support/TheTree-PCK-tests-078167a92c6e4888a6b4ee4a005fd858-standard-runtime-writer/saves/p04_s3/B1-after/world.bin +``` + +Its Reader counterpart is also inaccessible: + +```text +/Users/korizatou114/Library/Application Support/TheTree-PCK-tests-b1fd1956bd28493cbac1e00f3a775232-standard-runtime-reader/saves/p04_s3/B1-after/world.bin +``` + +Searching each series directory for `world.bin` found only the unrelated +`stage/tests/fixtures/required_kitchen_legacy14/formal-package/world.bin`. +There is no `.review-material` directory in this checkout. Root result SHA +fields cannot replace re-hashing the actual original and Reader member bytes. + +Current-game source access also fails: reading the first source_before entry, +`/Users/korizatou114/Projects/とりにく工房ゲーム制作部/AGENTS.md`, returned the same error. +Each run's fixed_input_pins contains three inaccessible entries. The remaining +entries re-hashed successfully (S1: 13, S2a: 14, S2b: 10, S3: 15). +Each staged source tree has 1337 hashes matching source_before and one differing +test script (`tests/s3/test_runtime_staff_assignment.gd`); this is not a proof +that the current game's full 1338-file source set still matches. + +Required unblock: make these declared original inputs readable to the worker, +or supply an authorized, provenance-bound relocation of their full bytes, +including both formal roles and the current-game source/fixed inputs. No new +engine run is requested. Permission denial must not be interpreted as missing +files, matching hashes, or failed product behavior. + +## Evidence and remaining conditions + +Readable raw locations were inspected under all four series, including +runner-input/source_before, lifecycle receipts, raw inventory, and raw Writer +and Reader records. D05 supplement bytes were read. ROOT result files were +used to inspect locators, never as proof of passing conditions. + +- S1: EV remains unverified in this run; no paid-service power claim. +- S2a: midnight_settlement, midnight_replay, save_restore remain unverified. +- S2b: night_staff_absent, overtime_task and same-minute rejection remain unverified. +- S3: closure_release, exit_release, patience_release and B4 release-once remain unverified. +- power_stop, destruction_stop, shift_completion, same_minute_replay remain unverified. +- Existing C verification was neither changed nor rerun; no fresh C result is claimed. +- Required native-Windows conditions remain pending individually; this run supplies no Windows proof. + +Stage A remains pending: actual HEAD + filesystem candidate binding, the known +`MAIN_ANCHOR` `9694...`, candidate_tree trust, and `3139` metadata holes are +untouched. No final index or composition was generated. + +## Footprint and verification + +Read-only `df -h` reported about 3.1 GiB available. No evidence payload was +copied. `B/audit-support/p04-raw-collect-r17/` did not exist at the access audit +and was not created. A trustworthy collection footprint cannot be finalized +without access to the required member bytes; no collector was run. + +Only Python read/hash/access diagnostics were executed. Fixture suites were +not run and are not reported as passing. `p04_native_series.py`, +`collect_p04_native.py`, and `checker_series_fixtures.py` were not created, so +there are no valid Root collector/new-fixture commands to advertise yet. +Implementation and its required positive/adversarial checks remain outstanding +after the required inputs become readable. diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_COLLECTOR_OUTPUT.jsonl b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_COLLECTOR_OUTPUT.jsonl new file mode 100644 index 0000000..c9cd4fb --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_COLLECTOR_OUTPUT.jsonl @@ -0,0 +1,2 @@ +{"estimated_payload_bytes": 2970454, "files": 5, "available_disk_bytes": 4596576256, "large_payloads_copied": 0} +{"collection": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/audit-support/p04-raw-collect-r17", "manifest_sha256": "953a798831043abdf7d54909e8e041cb3fbe6d901ca8affa661ea28e62ca5975", "actual_payload_bytes": 2970454, "formal_acceptance": false} diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_COMBINED_OUTPUT.json b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_COMBINED_OUTPUT.json new file mode 100644 index 0000000..9377a04 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_COMBINED_OUTPUT.json @@ -0,0 +1 @@ +{"C_provenance_sha256": "51d746879b4197445a09cd44a88dd918c365fa98025d9cd652a740cee09a9040", "C_source_manifest_sha256": "dbaab3c293c191eb5f3aff34854945e2d564f824df50a92802b3a7eda55321a8", "error": "candidate binding stage A pending; uncollected original boundaries: power_stop, save_restore, shift_completion", "formal_acceptance": false, "machine_verified": false, "missing_conditions": ["power_stop", "save_restore", "shift_completion"], "nativeWindows": {"EV": "pending", "closure_release": "pending", "dependency_stop": "pending", "destruction_stop": "pending", "exit_release": "pending", "midnight_replay": "pending", "midnight_settlement": "pending", "night_staff_absent": "pending", "overtime_task": "pending", "patience_release": "pending", "paused_midnight": "pending", "power_stop": "pending", "recovery_completion": "pending", "same_minute_replay": "pending", "save_restore": "pending", "shift_completion": "pending"}, "relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8", "series_bindings": {"s1": "9792569af9c8ecef177ffdeb880b814fb2878231f23e77c3749e18c228ff0ceb", "s2a": "746b58cf1aff95b082c35f6cc2c29556f146a7c56cce0475f4f2c091168605a7", "s2b": "e6d5412c14809825cf368af1d93972bc9c4e41f4c09afa414732b007c9cc7589", "s3": "a9b4b80d54c18ae2a1dc82514052e22d6174ff79999c76fa622282709759b433"}, "series_manifest_sha256": "953a798831043abdf7d54909e8e041cb3fbe6d901ca8affa661ea28e62ca5975", "stage_A": "candidate binding stage A pending", "task_id": "P04-01", "verified_conditions": ["EV", "closure_release", "dependency_stop", "destruction_stop", "exit_release", "midnight_replay", "midnight_settlement", "night_staff_absent", "overtime_task", "patience_release", "paused_midnight", "recovery_completion", "same_minute_replay"]} diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_REAL_SERIES_OUTPUT.json b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_REAL_SERIES_OUTPUT.json new file mode 100644 index 0000000..2170302 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_REAL_SERIES_OUTPUT.json @@ -0,0 +1,153 @@ +{ + "s1": { + "facts": { + "paid_service_power_claim": false + }, + "formal_acceptance": false, + "owned_pids": [ + 64745, + 64788, + 64818, + 64850 + ], + "phase_pairs": 0, + "product_content": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1", + "raw_reference_count": 97, + "relocated_reference_count": 1464, + "relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8", + "series": "s1", + "source_files": 1338, + "verified_conditions": [ + "EV" + ], + "verifier_sha256": "61f6647be4d2ec95db0599b94a3c6acb5f91ca43501256548f5ae161ab2f0e4f" + }, + "s2a": { + "facts": { + "paid_originals": { + "completed_service_id": "service_00000027", + "same_minute_replay": "request resend, not advance_to settlement replay", + "shift_completion": "missing: zero SERVICE_SALE rows at 1080", + "transaction_id": "ledger_00000085" + }, + "payroll_gold": 255, + "raw_getter_bytes_equal": false, + "sales_gold": 25, + "save_restore_gap": "Raw getter also changes typed arrays to untyped arrays; authorized normalization permits only StringName/String and ordering", + "settlement_replay_is_request_resend": false, + "upkeep_gold": 5 + }, + "formal_acceptance": false, + "owned_pids": [ + 85229, + 85263, + 85303, + 85425 + ], + "phase_pairs": 0, + "product_content": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1", + "raw_reference_count": 486, + "relocated_reference_count": 1362, + "relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8", + "series": "s2a", + "source_files": 1338, + "verified_conditions": [ + "midnight_replay", + "midnight_settlement", + "same_minute_replay" + ], + "verifier_sha256": "61f6647be4d2ec95db0599b94a3c6acb5f91ca43501256548f5ae161ab2f0e4f" + }, + "s2b": { + "facts": { + "same_minute_rejection": "MINUTE_ALREADY_APPLIED" + }, + "formal_acceptance": false, + "owned_pids": [ + 98696, + 98744, + 98770, + 98810 + ], + "phase_pairs": 12, + "product_content": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1", + "raw_reference_count": 163, + "relocated_reference_count": 1397, + "relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8", + "series": "s2b", + "source_files": 1338, + "verified_conditions": [ + "night_staff_absent", + "overtime_task" + ], + "verifier_sha256": "61f6647be4d2ec95db0599b94a3c6acb5f91ca43501256548f5ae161ab2f0e4f" + }, + "s3": { + "facts": { + "B1": { + "amount": 0, + "reservation_states": [ + "ACTIVE", + "RELEASED" + ], + "sales": 0 + }, + "B2a": { + "amount": 25, + "reservation_states": [ + "CONSUMED" + ], + "sales": 1 + }, + "B2b": { + "amount": 0, + "reservation_states": [ + "RELEASED" + ], + "sales": 0 + }, + "B3": { + "amount": 0, + "reservation_states": [ + "RELEASED" + ], + "sales": 0 + }, + "B4a": { + "amount": 25, + "reservation_states": [ + "CONSUMED" + ], + "sales": 1 + }, + "B4b": { + "amount": 0, + "reservation_states": [ + "RELEASED" + ], + "sales": 0 + } + }, + "formal_acceptance": false, + "owned_pids": [ + 10448, + 10493, + 10523, + 10578 + ], + "phase_pairs": 352, + "product_content": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1", + "raw_reference_count": 891, + "relocated_reference_count": 1530, + "relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8", + "series": "s3", + "source_files": 1338, + "verified_conditions": [ + "closure_release", + "destruction_stop", + "exit_release", + "patience_release" + ], + "verifier_sha256": "61f6647be4d2ec95db0599b94a3c6acb5f91ca43501256548f5ae161ab2f0e4f" + } +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_SERIES_FIXTURE_OUTPUT.json b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_SERIES_FIXTURE_OUTPUT.json new file mode 100644 index 0000000..41d68bb --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_SERIES_FIXTURE_OUTPUT.json @@ -0,0 +1,367 @@ +{ + "formal_acceptance": false, + "results": [ + { + "class": "small_synthetic_positive", + "result": "pass", + "series": "shared" + }, + { + "class": "full_body_positive", + "result": "pass", + "series": "s1" + }, + { + "class": "wrong_source_pin", + "error": "SHA drift: native /native/body.gd", + "result": "rejected", + "series": "s1" + }, + { + "class": "signature_only_pin", + "error": "full-function-body pin mismatch", + "result": "rejected", + "series": "s1" + }, + { + "class": "symlink", + "error": "symlink: /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/.series-fixtures-ici55vnl/symlink.gd", + "result": "rejected", + "series": "s1" + }, + { + "class": "traversal", + "error": "raw traversal", + "result": "rejected", + "series": "s1" + }, + { + "class": "unmapped_original_path", + "error": "unmapped original path: /native/unknown.gd", + "result": "rejected", + "series": "s1" + }, + { + "class": "relocated_manifest_native_mismatch", + "error": "byte length drift: native /native/body.gd", + "result": "rejected", + "series": "s1" + }, + { + "class": "relocated_byte_manifest_mismatch", + "error": "SHA drift: relocation /native/body.gd", + "result": "rejected", + "series": "s1" + }, + { + "class": "JSON_only_getter_substitute", + "error": "unsupported Variant flags 8827:25959 at 0", + "result": "rejected", + "series": "s1" + }, + { + "class": "forged_scalar", + "error": "SHA drift: p04-s1-native-r02/raw/writer.json", + "result": "rejected", + "series": "s1" + }, + { + "class": "changed_raw_byte", + "error": "SHA drift: p04-s1-native-r02/raw/writer.json", + "result": "rejected", + "series": "s1" + }, + { + "class": "formal_member_SHA_drift", + "error": "SHA drift: p04-s1-native-r02/execution.json", + "result": "rejected", + "series": "s1" + }, + { + "class": "unowned_missing_PID_end", + "error": "missing PID end", + "result": "rejected", + "series": "s1" + }, + { + "class": "altered_phase_pair", + "error": "SHA drift: p04-s1-native-r02/raw/reader-landing-held.var", + "result": "rejected", + "series": "s1" + }, + { + "class": "relabeled_interruption_reason", + "error": "SHA drift: p04-s1-native-r02/raw/writer-landing-depleted.json", + "result": "rejected", + "series": "s1" + }, + { + "class": "full_body_positive", + "result": "pass", + "series": "s2a" + }, + { + "class": "wrong_source_pin", + "error": "SHA drift: native /native/body.gd", + "result": "rejected", + "series": "s2a" + }, + { + "class": "signature_only_pin", + "error": "full-function-body pin mismatch", + "result": "rejected", + "series": "s2a" + }, + { + "class": "symlink", + "error": "symlink: /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/.series-fixtures-ici55vnl/symlink.gd", + "result": "rejected", + "series": "s2a" + }, + { + "class": "traversal", + "error": "raw traversal", + "result": "rejected", + "series": "s2a" + }, + { + "class": "unmapped_original_path", + "error": "unmapped original path: /native/unknown.gd", + "result": "rejected", + "series": "s2a" + }, + { + "class": "relocated_manifest_native_mismatch", + "error": "byte length drift: native /native/body.gd", + "result": "rejected", + "series": "s2a" + }, + { + "class": "relocated_byte_manifest_mismatch", + "error": "SHA drift: relocation /native/body.gd", + "result": "rejected", + "series": "s2a" + }, + { + "class": "JSON_only_getter_substitute", + "error": "unsupported Variant flags 8827:25959 at 0", + "result": "rejected", + "series": "s2a" + }, + { + "class": "forged_scalar", + "error": "SHA drift: p04-s2a-native-r03/raw/writer.json", + "result": "rejected", + "series": "s2a" + }, + { + "class": "changed_raw_byte", + "error": "SHA drift: p04-s2a-native-r03/raw/writer.json", + "result": "rejected", + "series": "s2a" + }, + { + "class": "formal_member_SHA_drift", + "error": "SHA drift: p04-s2a-native-r03/execution.json", + "result": "rejected", + "series": "s2a" + }, + { + "class": "unowned_missing_PID_end", + "error": "missing PID end", + "result": "rejected", + "series": "s2a" + }, + { + "class": "altered_phase_pair", + "error": "SHA drift: p04-s2a-native-r03/raw/reader-C3-recall.var", + "result": "rejected", + "series": "s2a" + }, + { + "class": "relabeled_interruption_reason", + "error": "SHA drift: p04-s2a-native-r03/raw/writer-C1-midnight.json", + "result": "rejected", + "series": "s2a" + }, + { + "class": "full_body_positive", + "result": "pass", + "series": "s2b" + }, + { + "class": "wrong_source_pin", + "error": "SHA drift: native /native/body.gd", + "result": "rejected", + "series": "s2b" + }, + { + "class": "signature_only_pin", + "error": "full-function-body pin mismatch", + "result": "rejected", + "series": "s2b" + }, + { + "class": "symlink", + "error": "symlink: /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/.series-fixtures-ici55vnl/symlink.gd", + "result": "rejected", + "series": "s2b" + }, + { + "class": "traversal", + "error": "raw traversal", + "result": "rejected", + "series": "s2b" + }, + { + "class": "unmapped_original_path", + "error": "unmapped original path: /native/unknown.gd", + "result": "rejected", + "series": "s2b" + }, + { + "class": "relocated_manifest_native_mismatch", + "error": "byte length drift: native /native/body.gd", + "result": "rejected", + "series": "s2b" + }, + { + "class": "relocated_byte_manifest_mismatch", + "error": "SHA drift: relocation /native/body.gd", + "result": "rejected", + "series": "s2b" + }, + { + "class": "JSON_only_getter_substitute", + "error": "unsupported Variant flags 8827:25959 at 0", + "result": "rejected", + "series": "s2b" + }, + { + "class": "forged_scalar", + "error": "SHA drift: p04-s2b-native-r02/raw/writer.json", + "result": "rejected", + "series": "s2b" + }, + { + "class": "changed_raw_byte", + "error": "SHA drift: p04-s2b-native-r02/raw/writer.json", + "result": "rejected", + "series": "s2b" + }, + { + "class": "formal_member_SHA_drift", + "error": "SHA drift: p04-s2b-native-r02/execution.json", + "result": "rejected", + "series": "s2b" + }, + { + "class": "unowned_missing_PID_end", + "error": "missing PID end", + "result": "rejected", + "series": "s2b" + }, + { + "class": "altered_phase_pair", + "error": "SHA drift: p04-s2b-native-r02/raw/reader-night_absent-1080-before-facility.var", + "result": "rejected", + "series": "s2b" + }, + { + "class": "relabeled_interruption_reason", + "error": "SHA drift: p04-s2b-native-r02/raw/writer-night_absent-1081.json", + "result": "rejected", + "series": "s2b" + }, + { + "class": "full_body_positive", + "result": "pass", + "series": "s3" + }, + { + "class": "wrong_source_pin", + "error": "SHA drift: native /native/body.gd", + "result": "rejected", + "series": "s3" + }, + { + "class": "signature_only_pin", + "error": "full-function-body pin mismatch", + "result": "rejected", + "series": "s3" + }, + { + "class": "symlink", + "error": "symlink: /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/.series-fixtures-ici55vnl/symlink.gd", + "result": "rejected", + "series": "s3" + }, + { + "class": "traversal", + "error": "raw traversal", + "result": "rejected", + "series": "s3" + }, + { + "class": "unmapped_original_path", + "error": "unmapped original path: /native/unknown.gd", + "result": "rejected", + "series": "s3" + }, + { + "class": "relocated_manifest_native_mismatch", + "error": "byte length drift: native /native/body.gd", + "result": "rejected", + "series": "s3" + }, + { + "class": "relocated_byte_manifest_mismatch", + "error": "SHA drift: relocation /native/body.gd", + "result": "rejected", + "series": "s3" + }, + { + "class": "JSON_only_getter_substitute", + "error": "unsupported Variant flags 8827:25959 at 0", + "result": "rejected", + "series": "s3" + }, + { + "class": "forged_scalar", + "error": "SHA drift: p04-s3-native-r01/raw/writer.json", + "result": "rejected", + "series": "s3" + }, + { + "class": "changed_raw_byte", + "error": "SHA drift: p04-s3-native-r01/raw/writer.json", + "result": "rejected", + "series": "s3" + }, + { + "class": "formal_member_SHA_drift", + "error": "SHA drift: p04-s3-native-r01/execution.json", + "result": "rejected", + "series": "s3" + }, + { + "class": "unowned_missing_PID_end", + "error": "missing PID end", + "result": "rejected", + "series": "s3" + }, + { + "class": "altered_phase_pair", + "error": "SHA drift: p04-s3-native-r01/raw/reader-B1-phase-960-TIME_BOUNDARIES.var", + "result": "rejected", + "series": "s3" + }, + { + "class": "relabeled_interruption_reason", + "error": "SHA drift: p04-s3-native-r01/raw/writer-B1-after.json", + "result": "rejected", + "series": "s3" + } + ], + "scope": "file-only Stage R controls; immutable descriptor and primitive negatives" +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_STATUS.md b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_STATUS.md new file mode 100644 index 0000000..fea445f --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR18_STATUS.md @@ -0,0 +1,211 @@ +# REPAIR18 — executable Stage R, blocked on remaining conditions + +This is **not accepted or completed**. Executable R artifacts and a reference-only +collection were produced and the file-only checks were run. The whole gate +fails. Original completion credit remains **13/88**, with no additional credit. +The repair counter and `max_repair_attempts: null` were not changed. + +## Scope and preservation + +- Checkout: `accd07c91d4e34d9b7c7fc20e389df2424b167e1`; registered base: + `deb1bcb2374eb378b840ca345db952bd8572cb6e`. +- Changed `tools/check_p04_order_evidence.py` only to add R composition and its + separate read-only CLI, call composition from the whole missing-check, and + retain an unconditional Stage A failure. +- Created `p04_native_series.py`, `collect_p04_native.py`, + `checker_series_fixtures.py`, this status, and the four output files listed below. +- All existing checker functions except `verify` and `main` were compared with + HEAD and are byte-unchanged, including `verify_native` and all C helpers. + All three existing fixture files are byte-unchanged. Their failures were not + hidden by editing assertions or Stage A code. +- `REPAIR17_STATUS.md` was never written. Its SHA256 is + `6497c335739f266c9026aa6499cf5c49ae80d590d6aa2d0f90d32adc08018061`. +- No git writes, new worktree/branch, agents, Runner operations, engine runs, + full regression, permission requests, production edits, input edits, or final + `B/p04-evidence/index.json` writes occurred. This did not revalidate accd. + +## Inputs and actual collection + +Here `B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`, +`R=B/audit-support/p04-r17-full-inputs-r01`, and +`E=docs/plans/original-completion-2026-10-02/execution/p04-01`. + +The first input identity check verified `R/MANIFEST.json`: **1,244,135 bytes**, +SHA256 `a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8`. +All **1,721** relocated members were used and rehashed across the series, +covering 71,488,772 logical bytes. All four complete native `source_before` +sets equal the supplied 1,338-file product set at content anchor +`5f6bc11d6bb6e92d22d787989b219fef3a435bc1`. The one native QA override is checked +separately against its supplied probe bytes; it is not represented as product +content. Full-function-body pins include trailing comments and declarations. + +External original paths remain provenance strings. The resolver uses exact +`original_to_relocated` keys, and checks each used file against both native +descriptors and relocation descriptors. There is no prefix substitution or +external-path fallback. Formal slot membership, directory membership, separate +Writer/Reader paths and inodes, symlinks, traversal and duplicates are checked. +All **344** formal members in **86** original/Reader slots were rehashed. + +The collector created only `B/audit-support/p04-raw-collect-r17/`. It did not +exist before creation. It contains four series reference manifests and one +collection manifest; no large evidence snapshots were copied. + +Actual collector output (exit 0): + +```json +{"estimated_payload_bytes":2970454,"files":5,"available_disk_bytes":4596576256,"large_payloads_copied":0} +{"actual_payload_bytes":2970454,"manifest_sha256":"953a798831043abdf7d54909e8e041cb3fbe6d901ca8affa661ea28e62ca5975","formal_acceptance":false} +``` + +The estimate was printed before directory creation or output writes. The +collection is deterministic and refuses an existing destination on rerun. +Per-series manifests bind native raw references, relocation references, and +verifier bytes by SHA. Composition reruns C and all series verifiers and +compares the recomputed results and references with those manifests; editing a +`verified_conditions` list cannot add credit. + +## Conditions derived from literal raw + +| Verifier | Native raw | Relocated formal members | Conditions proved | +| --- | --- | --- | --- | +| S1 | `B/p04-s1-native-r02/raw/` | `R/formal/p04-s1-native-r02/{writer,reader}/` | `EV` | +| S2a | `B/p04-s2a-native-r03/raw/` | `R/formal/p04-s2a-native-r03/{writer,reader}/C3/` | `midnight_settlement`, `midnight_replay` | +| S2b | `B/p04-s2b-native-r02/raw/` | `R/formal/p04-s2b-native-r02/{writer,reader}/` | `night_staff_absent`, `overtime_task` | +| S3 | `B/p04-s3-native-r01/raw/` | `R/formal/p04-s3-native-r01/{writer,reader}/` | `closure_release`, `exit_release`, `patience_release`, `destruction_stop` (B4b) | +| Paid/resend supplement, collected with S2a | `B/audit-support/p04-current-native-series-r01/p04-idempotency-paid-midnight-native-r01/order-evidence/` | Manifest pin resolved through `R/MANIFEST.json`, full raw already inside B | `same_minute_replay` (request resend) | +| Unchanged C | `B/audit-support/native-first-current/` | Existing C bundle, unchanged | `dependency_stop`, `recovery_completion`, `paused_midnight` | + +All product reads resolve to `R/product/` through manifest keys. Fixed-input +relocations and every original formal provenance string are retained in the +four generated series manifests; they are not replaced with guessed paths. + +Owned import/parse/Writer/Reader PIDs checked against starts, ends, owners, +commands, raw stream hashes, exit gates and cleanup/guard records: + +| Series | PIDs | Formal slots per role | Exact raw phase pairs | +| --- | --- | ---: | ---: | +| S1 | 64745, 64788, 64818, 64850 | 14 | 0 (14 exact typed snapshot restore points instead) | +| S2a | 85229, 85263, 85303, 85425 | 1 | 0 (typed settlement/replay and formal Runtime restore comparisons) | +| S2b | 98696, 98744, 98770, 98810 | 6 | 12 | +| S3 | 10448, 10493, 10523, 10578 | 22 | 352 JSON/typed phase files | + +- S1 checks task/trip/resource identity, safe landing, no-safe-landing hold, + preserved reservation identity and release. It makes no paid-service power claim. +- S2a derives a 25G sale/one transaction, 255G payroll/one transaction + (Akane 120, Bardo 135 joined to actual staff and shifts), and 5G upkeep in + three rows/one transaction. `advance_to(1440)` has processed minutes 0 and + daily closes 0, with entire typed Runtime bytes unchanged. Formal Reader + Runtime restore and recall bytes match exactly. +- The supplemental request-resend raw preserves typed economy, facilities, + inventory, guests and staff at the same minute, and joins the one completed + service `service_00000027` to sale transaction `ledger_00000085`. This is not + settlement-replay proof; S2a supplies that distinct proof. +- S2b derives `STAFF_UNAVAILABLE`/`WAITING_FOR_STAFF`, the absent night workers, + overtime task/staff/shift joins and one duty minute. Its same-minute call is + rejected as `MINUTE_ALREADY_APPLIED`, with entire typed Runtime unchanged. +- S3 retains `RELATED_FACILITY_INACTIVE` in B1, never relabeling it `NO_POWER`. + The directly closed facility retains its started service while the dependent + facility interruption pauses it; the queued reservation is released. + B2a completes/consumes once; B2b and B3 release without a sale. + B4b's queued event completes, durability becomes 0/`BROKEN`, then the service + fails with `BROKEN` at 961, releases its reservation and has zero sale rows. + +## Unresolved raw/specification gaps + +1. **`power_stop` missing.** EV evidence is not a paid-service power proof; + B1 is a dependency interruption. No relabeling or invented D05 rule was used. +2. **`shift_completion` missing.** The actual supplied paid-shift originals at + `B/audit-support/p04-current-native-series-r01/p04-paid-shift-midnight-native-r01/order-evidence/` + have zero `SERVICE_SALE` rows in all three 17:59/completion-phase/18:00 + snapshots. The `paid` filenames do not establish a charged completion at 1080. +3. **`save_restore` withheld under the fixed normalization restriction.** The + entire formal Runtime does restore exactly. However, the getter comparison + changes not only StringName/String and dictionary order but typed arrays to + untyped arrays. Examples are `/anomalies`, `/anomalies/0/allocations`, + `/anomalies/0/unallocated_by_staff`, `/anomalies/0/facility_ids`, + `/settlement/input_payments` and `/settlement/rows`. Native array headers + include `1c000100...` versus `1c000000...`. The raw getter byte inequality is + preserved and parsed from `.var`; JSON is not substituted. Normalization was + not broadened beyond the instruction, so this condition remains missing. +4. **B4a zero-charge assertion is unsupported.** + `B/p04-s3-native-r01/raw/writer-B4a-next.{json,var}` contains one 25G sale at + 960 and a completed service with consumed reservation. B4b has zero sales. + The implementation records both facts, rather than declaring B4a charge 0. + Destruction-stop credit above is specifically the B4b failed-service branch. + +D05 approval and supplement full bytes in `p04-whole-closure-r01` were read. +They approve dependency closure and the 18:00 ordering choice, not an invented +blanket policy for all D05 domains. The supplied raw discrepancies require +owner-level resolution or appropriate evidence; this worker did not amend scope. + +Each of these native-Windows conditions remains individually **pending**: +`EV`, `power_stop`, `destruction_stop`, `dependency_stop`, `shift_completion`, +`recovery_completion`, `same_minute_replay`, `closure_release`, `exit_release`, +`patience_release`, `paused_midnight`, `midnight_settlement`, `midnight_replay`, +`save_restore`, `night_staff_absent`, `overtime_task`. The machine output also +contains an explicit per-condition pending map. + +## Actual validation outputs + +- Static Python compilation: **4 files passed** (in-memory compile; no engine). +- Existing C fixture: **exit 0**, 5 positive controls passed and 25 negatives + rejected. `verify_native` reports 10 owned processes and its original three + conditions. No C condition names were edited. +- Existing negative fixture: **exit 1**, at `synthetic_scalar_only_rejected`: + `AssertionError: ('synthetic_scalar_only_rejected', {'task_id': 'P04-01', 'machine_verified': False, 'formal_acceptance': False, 'error': "'product_binding'"})`. +- Existing applicability fixture: **exit 1**: + `AssertionError: negative fixture accepted: wrong_tested_revision`. + These two suites therefore could not be confirmed to pass/reject as requested. + Their files and the functions involved were unchanged; they were not repaired + by touching the deferred A holes, and no baseline rerun is claimed. +- New series fixtures: **exit 0**, **5 small positive controls and 56 rejected + negatives**, 14 negative classes per series. Controls cover immutable raw and + descriptor overlays, source/body pins, PID endings, paths, relocation byte + drift and typed-vs-JSON rejection. Immutable descriptor hashes are deliberately + not refreshed; the tests do not claim to be new engine evidence. +- Real series verifiers ran successfully during collection and were rerun by + composition. Outputs are recorded in `REPAIR18_REAL_SERIES_OUTPUT.json`. +- Separate composition CLI: **exit 1**, with 13 raw-derived conditions and: + +```text +candidate binding stage A pending; uncollected original boundaries: power_stop, save_restore, shift_completion +``` + +Full new outputs: `REPAIR18_COLLECTOR_OUTPUT.jsonl`, +`REPAIR18_SERIES_FIXTURE_OUTPUT.json`, `REPAIR18_REAL_SERIES_OUTPUT.json`, +`REPAIR18_COMBINED_OUTPUT.json`. The 13 verified boundary conditions are not +13 newly accepted units and do not change the original 13/88 credit. + +## Exact Root commands + +Run from this existing checkout. These commands perform file-only work: + +```sh +B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build +E=docs/plans/original-completion-2026-10-02/execution/p04-01 +python3 -B "$E/collect_p04_native.py" --build "$B" +python3 -B "$E/checker_series_fixtures.py" --build "$B" +python3 -B "$E/p04_native_series.py" --build "$B" +python3 -B "$E/checker_negative_fixtures.py" +python3 -B "$E/checker_applicability_fixtures.py" --native-bundle "$B/audit-support/native-first-current" +python3 -B "$E/checker_native_fixtures.py" "$B/audit-support/native-first-current" +BUILD_OUTPUT_DIR="$B" python3 -B tools/check_p04_order_evidence.py --verify-series-only +``` + +The collector command above was already executed successfully; another execution +must refuse the now-existing destination. Review the existing collection rather +than overwriting it. The final command is intentionally failing; it does not +need or generate a final index and never performs candidate composition. + +## Stage A and subsequent dependencies + +Stage A remains pending: actual HEAD/filesystem candidate binding, +`MAIN_ANCHOR` `9694...`, `candidate_tree` trust and the `3139` metadata holes +are untouched. `--verify-only` still requires the actual final evidence index; +its missing-check unions recomputed C/R results and still fails for any missing +condition, and it cannot succeed while Stage A is pending. + +Required sequence remains **R → A → final index → mandatory whole validation → +independent review → ordinary integration**. Outstanding raw/specification gaps +and the two existing fixture failures prevent representing this dispatch as a +completed or accepted implementation. diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR19_CORRIGENDUM.md b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR19_CORRIGENDUM.md new file mode 100644 index 0000000..6c5b724 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR19_CORRIGENDUM.md @@ -0,0 +1,58 @@ +# Repair19 persistence explanation corrigendum + +This corrects the old Root explanation that the live day-summary comparison +differed only in StringName/String and dictionary order. That explanation missed +generic Array headers. It also corrects the superseded proposal to explain this +path using `_to_snapshot_variant`. + +The exact relocated source is +`B/audit-support/p04-r17-full-inputs-r01/product/scripts/operations/operations_coordinator.gd`. +Its 130891 bytes have SHA256 +`871a248ee101b6df543fd5cf0440b5d4b0f02c21fa145a99f0687888c1bc6f90`. +Full bodies, including bytes through the next named function, were read directly +and compared with `T/ROOT_ACTUAL_PERSISTENCE_CALL_CHAIN.json`, where +`T=B/audit-support/p04-repair19-close-r01`. All five bodies, lengths and hashes +match that supplied file: + +| Function | Line | Bytes | SHA256 | +| --- | ---: | ---: | --- | +| `get_day_summary` | 577 | 153 | `449eef926054cf76c39d6b4c016da3d775d41f2b343b4f8ad57b62c594cb32bb` | +| `create_snapshot` | 620 | 3988 | `8c6e4f000e6df6f0f996445a57c4c17223cae4e1df57a9c2e3a605cf4d9f5309` | +| `_snapshot_root_report` | 2943 | 2062 | `96084d76beeac11d165753a82160f789cd47b4dec4b6bb8cd6599a4f5ed08efe` | +| `_visit_snapshot_tree` | 2992 | 3045 | `70792369c05882b813632a3c96f934928e9a089d420700cfc0d4c8fa971972a0` | +| `_to_snapshot_variant` | 3083 | 550 | `f7db3cf2d495bb117fc7f6795633daa272c03d8eb7f99977e462e0a9df20cd8a` | + +`get_day_summary` returns `_day_summaries[day].duplicate(true)` directly. It +exposes an internal API representation; the product getter need not change. +`create_snapshot` first validates with capture=true. In that pass, +`day_summaries` is not among the reusable encoded fields. It then assembles +`saved_days` and calls `_snapshot_root_report(raw_snapshot, field_cache, false)`. +That report invokes `_visit_snapshot_tree` with encoding enabled. The visitor +creates untyped arrays, converts StringName to String, stringifies permitted +dictionary keys and sorts the resulting String keys with native sorting. + +The separate `_to_snapshot_variant` helper is not on this call chain. Its +`sort_custom(_variant_string_less)`, lack of finite-float/budget checks and +pass-through of unsupported types prevent using it as a general equivalent. + +The supplied `T/ROOT_S2A_TYPED_ARRAY_HEADER_AUDIT.json` lists the following six +changes. These are explicitly attributed to that diagnostic; they have not been +independently rerun by a repair19 fixture in this dispatch. + +| Path below `day_summary` | Writer header | Writer element kind | Reader header | Length | +| --- | --- | --- | --- | ---: | +| `/anomalies` | `1c000100` | Dictionary (27) | `1c000000` | 1 | +| `/anomalies/0/allocations` | `1c000100` | Dictionary (27) | `1c000000` | 1 | +| `/anomalies/0/facility_ids` | `1c000100` | String (4) | `1c000000` | 3 | +| `/anomalies/0/unallocated_by_staff` | `1c000100` | Dictionary (27) | `1c000000` | 0 | +| `/settlement/input_payments` | `1c000100` | Dictionary (27) | `1c000000` | 5 | +| `/settlement/rows` | `1c000100` | Dictionary (27) | `1c000000` | 5 | + +Every listed Reader array is untyped. Raw getter byte equality remains **false**. +This diagnostic is separate from the source-defined persisted snapshot. +Neither this explanation nor an optional projection may weaken exact native +expected/restored typed-byte comparison, entire Runtime equality, formal 4+4 +member equality, settlement counts, duplicate-payment checks or replay checks. +No projection implementation or new save_restore verification is claimed here. +The executable R18 normalization gate is still unchanged, so save_restore is +still open pending the exact encoder port and its required validation. diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR19_STATUS.md b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR19_STATUS.md new file mode 100644 index 0000000..7835924 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR19_STATUS.md @@ -0,0 +1,78 @@ +# Repair19 — incomplete; execution constraint clarification pending + +This is not a ready submission and not formal acceptance. Original credit stays +13/88. All 15 conditions plus EV retain their existing scope; nativeWindows +remains pending for each. No product, acceptance, domain or outcome was changed. + +## Blocking instruction conflict + +The recorded repair19 direction requires local fixture reruns, isolated temp-Git +negative and positive controls, and removing the Stage-A refusal only after +those tests pass. The final worker instructions prohibit Git writes and say +tests are formally executed by Runner. This dispatch has requested clarification +on whether that prohibition exempts the specifically requested file-only and +isolated temp-Git fixtures. There is no answer at the time of this report. +No exemption has been assumed. This is an execution-constraint blocker, not a +claim that pending formal acceptance itself blocks implementation. + +## Changes actually made in this dispatch + +- `p04_native_series.py`: retain both JSON and Variant Writer/Reader phase-byte + comparisons, but increment the S3 pair metric only for `.var`. The requested + interpretation is 352 checked file pairs representing 176 observations. A + new execution output confirming that count has not been generated. +- `p04_native_series.py`: add the B1 `power_stop` join for the five observations + at 959–963, positive finite original reserve, actual public mana and power + flags, direct closure, same service identity/progress/reservations, queued B + release/stock/patience, recovery payment and unchanged next ledger. Bind the + saved 961 recovery command and the APPLY_COMMANDS phase. Preserve the actual + `RELATED_FACILITY_INACTIVE` reason. This code is untested; its addition to the + verifier's returned conditions is conditional on every assertion succeeding + when Runner executes it. No successful power_stop result is claimed here. +- `REPAIR19_CORRIGENDUM.md`: correct the StringName/order-only explanation, + distinguish raw getter from public persistence, list all six supplied typed + Array header changes, and record the actual source call-chain full-body pins. + +## Static observations and preservation + +- In-memory Python `compile` of the edited series module succeeded. No fixture, + native, Godot, full-regression or Runner run occurred. +- The exact relocated operations source was read. All five complete function + bodies, lengths and hashes equal + `B/audit-support/p04-repair19-close-r01/ROOT_ACTUAL_PERSISTENCE_CALL_CHAIN.json`. + The hash table and exact call-chain explanation are in the corrigendum. +- The checker region from `NATIVE_RUNS` through `verify_native`, inclusive of + the C constants and helpers, equals its HEAD bytes. Region SHA256: + `5d778b3cfab7c139ca03812489508ce9aae90c1d17f8c2249c340f30d1496b3b`. +- No R17/R18 output or status file was edited. The existing uncommitted R18 + checker changes remain. No Git write, agent, integration or Runner operation + occurred. No native evidence was created or modified. +- No collector was run. Both the existing r17 collection and the required fresh + r19 destination remain untouched by this dispatch. Since the series verifier + bytes changed, the old r17 collection cannot validate these new verifier bytes; + it must never be overwritten or represented as a repair19 collection. + +## Remaining implementation and validation + +Repair19 is incomplete. R2's C-backed paid shift join, R3's exact encoder port +and fixtures, Stage A's actual Git-role verifier, new index generator, fixture +protocol repairs and all requested fixture executions remain to be completed. +The old Stage-A unconditional refusal remains in place. The old +`verify_product_target` has not been edited or replaced in this dispatch. +The 9b1a18c anchor resolves to +`9b1a18c1eb25ca1300dcd59da78423c0dda2f498`; the old checker constant has not yet +been changed as part of a complete Stage-A repair. + +No new output JSON is reported, and no old fixture success is reused as success +for the edited module. B4a/B4b semantics, frozen save_restore measurements and +its current open result remain unchanged. The 25 C negatives and 56 series +negatives have not been rerun. + +There are no valid repair19 index-generation or submitted/merged-candidate Root +commands yet: the required generator and role verifier are not implemented. +Providing plausible commands for nonexistent functionality would be misleading. +The completed follow-up must supply those exact commands after implementing and +validating the protocol, retain all existing outputs, and collect exclusively +into the fresh `B/audit-support/p04-raw-collect-r19` destination. + +Here `B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`. diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR20_CORRIGENDUM.md b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR20_CORRIGENDUM.md new file mode 100644 index 0000000..0d222cc --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR20_CORRIGENDUM.md @@ -0,0 +1,40 @@ +# Repair20 persistence corrigendum + +The old Root explanation, “StringName/order only”, omitted six generic Array +header changes. `get_day_summary` returns the internal day's `duplicate(true)`; +it does not return the canonical persisted representation. Its raw Writer and +Reader bytes remain unequal. All original raw files and values are retained. +No product getter change is proposed. + +| Path under day_summary | Writer header | Element kind | Reader header | Length | +| --- | --- | --- | --- | ---: | +| anomalies | 1c000100 | Dictionary (27) | 1c000000 | 1 | +| anomalies/0/allocations | 1c000100 | Dictionary (27) | 1c000000 | 1 | +| anomalies/0/facility_ids | 1c000100 | String (4) | 1c000000 | 3 | +| anomalies/0/unallocated_by_staff | 1c000100 | Dictionary (27) | 1c000000 | 0 | +| settlement/input_payments | 1c000100 | Dictionary (27) | 1c000000 | 5 | +| settlement/rows | 1c000100 | Dictionary (27) | 1c000000 | 5 | + +These are the six entries in `T/ROOT_S2A_TYPED_ARRAY_HEADER_AUDIT.json`. +The new code derives them again from typed nodes and compares the list to that +diagnostic. This execution is **NOT RUN** in repair20. + +The persistence chain is `create_snapshot` (620) → +`_snapshot_root_report(raw_snapshot, field_cache, false)` (2943) → +`_visit_snapshot_tree(..., encode=true)` (2992). The earlier capture=true pass +does not cache encoded day_summaries. `_to_snapshot_variant` is not on this path. + +The projection is confined to raw getter → persisted day1. It retains all +scalar Variant bytes, including numeric type, rejects nonfinite floats and +unsupported values, stringifies permitted keys, rejects canonical collisions, +sorts String keys, rebuilds untyped Arrays, and enforces collection, depth, node, +single-string and aggregate-string limits. Unsupported dictionary-key types +are rejected, never silently normalized. The optional admitted subset does not +include composite-key Godot stringification. No projection is allowed on the +native persisted expected/restored comparison. + +Full Runtime Writer/Reader/restored equality, formal 4+4 member equality, +settlement/duplicate-payment checks and the actual advance_to replay checks +remain independent strict requirements. If getter projection still differs +from the native canonical_day1 bytes, `save_restore` remains open and reports +that exact mismatch. No new successful equality is claimed without Root's run. diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR20_STATUS.md b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR20_STATUS.md new file mode 100644 index 0000000..f6fc819 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR20_STATUS.md @@ -0,0 +1,183 @@ +# Repair20 — source submission; tests NOT RUN + +Source-only repair. No Git command, fixture, collector, series, Godot/native, +Runner, agent, commit, branch or integration operation was executed. The only +code validation performed was Python `compile(..., 'exec')` in memory, without +imports/execution of the edited modules. No acceptance is claimed. Original +credit remains 13/88; all 15 conditions + EV, seven domains and three safety +outcomes retain their scope. nativeWindows remains pending for every condition. +The next submission SHA is assigned by Runner and is not predeclared here. + +Changed files: + +- `tools/check_p04_order_evidence.py`: real checkout/role binding, isolated + object stores under B, first-1080 R2 join, schema2 recomputation and r20 collection. +- `p04_native_series.py`: B1 departure/despawn/full-state joins, explicit 352/176 + S3 checks, confined getter projection, persistence pins and six-header diagnostic. +- `collect_p04_native.py`: exclusive new collection at + `B/audit-support/p04-raw-collect-r20`. +- `checker_negative_fixtures.py`: retain existing content-case IDs and meanings; + isolate their content layer explicitly in the test harness, while the separate + real-Git fixture exercises production role binding without a bypass. +- New `build_p04_index.py`, `checker_role_fixtures.py`, + `checker_encoder_fixtures.py`, `checker_join_fixtures.py`. +- New `REPAIR20_STATUS.md` and `REPAIR20_CORRIGENDUM.md`. + +In-memory compilation: all eleven Python files (checker plus ten modules in E) +compile. This is syntax validation only; every test below is **NOT RUN**. +The retained C region, `NATIVE_RUNS` through `verify_native`, is byte-for-byte +equal to T's retained source, SHA256 +`5d778b3cfab7c139ca03812489508ce9aae90c1d17f8c2249c340f30d1496b3b`. +`verify_product_target` itself is also byte-for-byte retained and no longer called. +Existing repair17/18/19 status, corrigendum, raw outputs and T's retained archives +were not modified. Existing C 25 negatives and series 56 negatives remain required. + +## R3 source pins + +Product source: `scripts/operations/operations_coordinator.gd`, 130891 bytes, +SHA256 `871a248ee101b6df543fd5cf0440b5d4b0f02c21fa145a99f0687888c1bc6f90`. +Runtime verification checks the complete bodies and cross-checks the actual +body bytes, lengths and hashes against `T/ROOT_ACTUAL_PERSISTENCE_CALL_CHAIN.json`. + +| Function | SHA256 | +| --- | --- | +| get_day_summary | 449eef926054cf76c39d6b4c016da3d775d41f2b343b4f8ad57b62c594cb32bb | +| create_snapshot | 8c6e4f000e6df6f0f996445a57c4c17223cae4e1df57a9c2e3a605cf4d9f5309 | +| _snapshot_root_report | 96084d76beeac11d165753a82160f789cd47b4dec4b6bb8cd6599a4f5ed08efe | +| _visit_snapshot_tree | 70792369c05882b813632a3c96f934928e9a089d420700cfc0d4c8fa971972a0 | + +The corrigendum lists all six Array header changes. Native persisted comparisons +are direct typed-byte comparisons. Raw getter inequality remains diagnostic. +The optional projection admits only the supported persistence subset; unsupported +composite dictionary keys fail closed. Its negative controls include field loss, +extra fields, both numeric-type directions, value changes, nonfinite floats, +invalid keys, collisions, unsaveable values, wrong call chain and use on native +persisted comparisons. The positive changes only Array headers and StringName. + +## Condition disposition on this untested source + +No condition is newly proven by this dispatch. All tests are **NOT RUN**. + +| Condition | State and reason pending Root execution | +| --- | --- | +| power_stop | Open: new B1 959–963 join; real B at 959/960, normal recorded departure then absence, unchanged one-time release, stock 1, wallet 50, no B charge; recovery 25 at 962 and no extra 963 charge. | +| destruction_stop | Pending: retained S3 B4b BROKEN failure and zero-sale/release predicates. | +| dependency_stop | Pending: unchanged C paused service identity/progress/reservation proof. | +| shift_completion | Open: new C first-1080 join; PIDs 77462/77504/77536 ended, 1338-file content 5f, 44→45, ledger_00000006/25G, DAY2 present before handover and zero after, no extra at 1081. | +| recovery_completion | Pending: unchanged C recovery transaction/idempotency join. | +| same_minute_replay | Pending: retained real paid request-resend join; never substituted for settlement replay. | +| closure_release | Pending: retained S3 closure/release plus despawn-aware B1 checks. | +| exit_release | Pending: retained S3 B2 release/completion ordering. | +| patience_release | Pending: retained S3 B3 patience expiration and release. | +| paused_midnight | Pending: unchanged C paused-midnight series. | +| midnight_settlement | Pending: retained S2a nonzero sales/payroll/upkeep/duty joins. | +| midnight_replay | Pending: actual advance_to replay, zero processed minutes and zero daily closes, exact Runtime bytes. | +| save_restore | Open: direct full typed Runtime and formal 4+4 equality remain mandatory; optional getter projection must equal actual persisted day1. A remaining mismatch is explicitly reported, never projected out of native restore. | +| night_staff_absent | Pending: retained S2b WAITING_FOR_STAFF at 1081. | +| overtime_task | Pending: retained S2b interrupt-level-zero task/staff/shift and once-only duty joins. | +| EV | Pending: retained S1 reachable reserved staff power-stop proof and pinned static chain. | + +Stage A is implemented but **NOT RUN**. Submission is inferred from actual Git +ancestry and scoped diff; the composed product is labelled prospective, not a +tested worker game. Integration/final requires an actual two-parent merge and +exact recomputed merge tree, native product map/modes and submitted checker/doc +patch. Every tracked file and executing module must match HEAD bytes/mode; +untracked/ignored files and symlinks fail. Unknown/deleted sources and candidate, +code-only or phase declarations cannot authorize a role. Metadata membership +comes from the real 9b1a18c tree, without the old count assumptions. + +Schema2 retains native source/config/input/environment, real lifecycle PIDs, +formal proofs, complete raw references, authority and original-audit uncertainty, +domain and reachability mappings. Its entire payload is recomputed from raw by +the same checker. It contains no candidate==HEAD pin or Root-passed flag. +`generation-invocation.json` separately records the actual invocation/HEAD. + +## Root execution order (all commands below NOT RUN) + +Run from the clean, actual auto-submitted checkout. Root supplies actual clean +merged/final checkout paths later; do not substitute invented SHAs. Preserve all +prior output directories and use exclusive fresh output destinations. + +```sh +export B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build +export E=docs/plans/original-completion-2026-10-02/execution/p04-01 +export BUILD_OUTPUT_DIR="$B" +export PYTHONDONTWRITEBYTECODE=1 +``` + +1. Local file-only and fixture controls on the actual new SHA: + +```sh +python3 "$E/checker_negative_fixtures.py" +python3 "$E/checker_applicability_fixtures.py" --native-bundle "$B/audit-support/native-first-current" +python3 "$E/checker_native_fixtures.py" "$B/audit-support/native-first-current" +python3 "$E/checker_series_fixtures.py" --build "$B" +python3 "$E/checker_encoder_fixtures.py" +python3 "$E/checker_join_fixtures.py" --build "$B" +``` + +2. Real series against B, then the exclusive r20 collection: + +```sh +python3 "$E/p04_native_series.py" --build "$B" +python3 "$E/collect_p04_native.py" --build "$B" +``` + +3. After auto-submit, generate the fresh normal Runner index. B/p04-evidence was +absent at static inspection. If it is now occupied, retain it and choose an +explicit fresh directory with P04_EVIDENCE_DIR for manual checks; do not overwrite. +The normal Runner uses B/p04-evidence and must receive that actual generated index. + +```sh +python3 "$E/build_p04_index.py" --build "$B" --output "$B/p04-evidence" \ + --invocation validate \ + --registered-ref deb1bcb2374eb378b840ca345db952bd8572cb6e \ + --tested-ref 5f6bc11d6bb6e92d22d787989b219fef3a435bc1 +``` + +4. Checker on submitted, merged and final actual checkouts. Real temp-Git fixtures +need the generated index, so their execution follows step 3, not a fabricated +pre-submission index. They create throwaway repositories/objects only under B. + +```sh +python3 "$E/checker_role_fixtures.py" --build "$B" --evidence "$B/p04-evidence" +python3 tools/check_p04_order_evidence.py --verify-only +python3 "$P04_MERGED_CHECKOUT/tools/check_p04_order_evidence.py" --verify-only +python3 "$P04_FINAL_CHECKOUT/tools/check_p04_order_evidence.py" --verify-only +``` + +The same index works in all three roles. For separate invocation records, the +generator also accepts `--invocation prepare-integration` and `--invocation final` +with explicit fresh output paths while executing from those actual checkouts. +These labels are receipt metadata and grant no role. Root must respect the normal +Runner state machine: its prepare_integration requires prior formal review. +An isolated local prospective-merge control does not replace that review or move +Runner state. `runner.validate("P04-01")`, `runner.prepare_integration("P04-01")` +and `runner.integrate("P04-01")` remain Root operations only. + +5. Mandatory whole validation and full 178-game regression once on exact final +sources. The supplied Runner core exposes +`runner.verify("P04-01", final_checkout, actual_final_sha, "final")`; retain every +configured mandatory validation, then run the existing guarded full-game runner: + +```sh +python3 "$P04_FINAL_CHECKOUT/tools/run_all_tests.py" \ + --project-root "$P04_FINAL_CHECKOUT" --output "$B/p04-final-product-full-r01" +``` + +Do not change timeout, marker contracts or suite policy, splice phases, or reuse +historical 177-suite proof. Confirm the final selected source map and 178-suite +roster, and preserve source/default-profile invariance. This command is the one +full run, not permission for an additional `make test` native run. + +6. Independent Opus review using Root's existing reviewer route, with exact +source/validation materials mirrored into the frozen checkout's .review-material. +The supplied core's formal method is `runner.review("P04-01")`; that method reviews +the actual submitted revision and requires its validation evidence. Any additional +final-composition Opus review must explicitly receive the actual final revision. +The external commander/Opus CLI entry point and installed Runner configuration +were not supplied in this checkout, so no guessed shell command is asserted for +them. Root retains its existing formal validation/review/integration workflow. + +The absent external entry-point/configuration details are handoff context gaps, +not evidence of a pass, and not a blocker solely because Root testing is pending. diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/build_p04_index.py b/docs/plans/original-completion-2026-10-02/execution/p04-01/build_p04_index.py new file mode 100644 index 0000000..feebb2d --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/build_p04_index.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +"""Build a fresh evidence index from supplied native bytes after auto-submit. + +The index has no candidate identity or Root-passed flag. Invocations share the +same payload; checkout roles are established afresh by the checker. +""" +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[5] + + +def checker(): + spec = importlib.util.spec_from_file_location('p04_index_checker', ROOT / 'tools/check_p04_order_evidence.py') + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +def descriptor(path): + data = path.read_bytes() + return {'path': str(path.absolute()), 'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()} + + +def payload(build, gate=None): + gate = gate or checker() + build = Path(build).resolve(strict=True) + native = build / 'audit-support/native-first-current' + c = gate.verify_native(native) + raw = gate.combine_native_series(build, c) + source = gate.product_manifest(gate.tree_entries(gate.TESTED_PRODUCT)) + gate.require(len(source) == 1338, 'complete native source required') + source_map = {p: v['sha256'] for p, v in source.items()} + # Resolve content from supplied roots only; never open original provenance + # paths, never omit required source/config/input/environment material. + supplied = {} + roots = [build / 'audit-support' / name for name in + ('native-first-current', 'p04-whole-closure-r01', 'p04-current-native-series-r01', + 'p04-r17-full-inputs-r01')] + for root in roots: + if not root.exists(): + continue + for path in sorted(root.rglob('*')): + if path.is_file(): + gate.safe(root, path.relative_to(root).as_posix()) + pin = descriptor(path) + supplied.setdefault(pin['sha256'], pin) + runners = [] + for run in gate.NATIVE_RUNS: + path = native / run / 'runner-input.json' + inputs = gate.load(path) + row = descriptor(path) + row['materials'] = {} + for pin in inputs.get('fixed_input_pins', []) + [inputs['QA']]: + gate.require(pin['sha256'] in supplied, 'missing supplied runner material: ' + pin['path']) + actual = supplied[pin['sha256']] + gate.require(actual['bytes'] == pin['bytes'], 'runner material length') + row['materials'][pin['path']] = actual + gate.require(inputs['wrapper_sha256'] in supplied, 'missing supplied native wrapper') + row['wrapper'] = supplied[inputs['wrapper_sha256']] + runners.append(row) + # C references retain every lifecycle, raw phase, source, environment and + # formal member mapping; Stage R retains its complete original->relocated map. + references = {} + for row in gate.load(native / 'SOURCE_MANIFEST.json')['files']: + references['C/' + row['copy']] = descriptor(gate.safe(native, row['copy'])) + for row in gate.load(native / 'OWNERSHIP_RAW_PROVENANCE.json')['copied']: + references['C/' + row['target']] = descriptor(gate.safe(native, row['target'])) + series = {} + collection = build / 'audit-support/p04-raw-collect-r20' + for name in ('s1', 's2a', 's2b', 's3'): + series[name] = gate.load(collection / (name + '.json')) + relocation = gate.load(build / 'audit-support/p04-r17-full-inputs-r01/MANIFEST.json') + authorities = {} + for ref in sorted({r for _, refs, _ in gate.REQUIRED.values() for r in refs}): + relative = (gate.DOC_SCOPE + 'D05_OWNER_APPROVAL.json' if ref.startswith('D05-P04-') else + 'docs/traceability/original/BASELINE.txt' if ref.startswith('BASELINE:') else + 'docs/DESIGN_DECISIONS.md' if ref == 'D-034' else + 'docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/ORDER_CONTRACT_DRAFT.md') + path = gate.safe(gate.ROOT, relative) + if ref.startswith('D05-P04-'): + gate.require(gate.sha(path) == gate.OWNER_HASH, 'owner approval hash') + authorities[ref] = {'source': relative, 'sha256': gate.sha(path), + 'status': 'approved' if not ref.startswith('BASELINE:') else 'original'} + conditions = {cid: {'case_id': cid, 'authority_refs': list(refs), 'nativeWindows': 'pending', + 'raw_verified': cid in raw['verified_conditions'], + 'required_measurements': metrics} + for cid, (_, refs, metrics) in gate.REQUIRED.items()} + domains = {domain: {'conditions': [cid for cid, (d, _, _) in gate.REQUIRED.items() if d == domain], + 'source_refs': [{'path': p, 'sha256': source_map[p], 'functions': list(fs)} + for p, fs in gate.ORDER_FUNCTIONS.items()], + 'authority_refs': sorted({r for _, (d, refs, _) in gate.REQUIRED.items() + if d == domain for r in refs})} + for domain in sorted(gate.DOMAINS)} + reachability = {} + for name, rows in gate.REACH_REFS.items(): + refs = [] + for path, function, digest, lines in rows: + gate.resolve_anchor(gate.git_bytes('show', gate.TESTED_PRODUCT + ':' + path), function, digest, lines) + refs.append({'path': path, 'sha256': source_map[path], 'function': function, + 'anchor_sha256': digest, 'line_count': lines}) + reachability[name] = {'reach': name == 'EV', 'case_id': 'EV' if name == 'EV' else None, 'source_refs': refs, + 'raw_verified': 'EV' in raw['verified_conditions'] if name == 'EV' else None} + delta = gate.source_delta(gate.REGISTERED_COMMIT, gate.TESTED_PRODUCT) + index = {'schema_version': 2, 'task_id': 'P04-01', 'formal_acceptance': False, + 'versions': {'registered': gate.REGISTERED_COMMIT, 'execution_origin': gate.EXECUTION_ORIGIN, + 'tested': gate.TESTED_PRODUCT}, + 'product_binding': {'tested_product': gate.TESTED_PRODUCT, 'runner_inputs': runners}, + 'source_map': source_map, 'registered_pins': gate.load(gate.SPEC)['current_source_pins'], + 'applicability_delta': delta, + 'delta_cases': {r['path'] + ':' + r['function']: {cid: cid for cid in + gate.boundary_conditions(r['path'], r['function'])} for r in delta}, + 'domains': domains, 'authority': authorities, 'conditions': conditions, + 'reachability': reachability, 'raw_conditions': raw, 'raw_references': references, + 'series': series, 'source_config_input_environment_and_formal': relocation, + 'native_bundle': descriptor(native / 'OWNERSHIP_RAW_PROVENANCE.json'), + 'original_audit': {'sha256': gate.sha(gate.ROOT / gate.DOC_SCOPE / 'order-audit.json'), + 'content': gate.load(gate.ROOT / gate.DOC_SCOPE / 'order-audit.json')}, + 'review_status': 'pending_review', 'original_credit': '13/88', + 'safety_outcomes': ['no charging while legitimately stopped', + 'no double midnight settlement', 'no orphan reservations']} + gate.applicability(index, gate.load(gate.SPEC), source_map, gate.TESTED_PRODUCT) + return index + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--build', required=True, type=Path) + parser.add_argument('--output', required=True, type=Path, help='fresh directory; exclusive creation') + parser.add_argument('--invocation', required=True, choices=('validate', 'prepare-integration', 'final')) + parser.add_argument('--registered-ref', required=True) + parser.add_argument('--tested-ref', required=True) + args = parser.parse_args() + gate = checker() + os.environ['BUILD_OUTPUT_DIR'] = str(args.build.resolve(strict=True)) + gate.require(args.registered_ref == gate.REGISTERED_COMMIT and args.tested_ref == gate.TESTED_PRODUCT, + 'source refs differ from immutable evidence roles') + output = args.output.absolute() + gate.require(not output.exists() and not output.is_symlink(), 'output must be fresh') + for path in output.parents: + gate.require(not path.is_symlink(), 'output parent symlink') + index = payload(args.build, gate) + with gate.isolated_objects(): + _, role = gate.verify_roles(index, args.build.resolve()) + # The historical invocation is separate from the reusable evidence index. + receipt = {'invocation': args.invocation, 'actual_head': role['actual_head'], + 'actual_tree': role['actual_tree'], 'role': role['role'], 'formal_acceptance': False} + output.mkdir(parents=False) + for name, value in (('index.json', index), ('generation-invocation.json', receipt)): + with (output / name).open('x', encoding='utf-8') as stream: + json.dump(value, stream, ensure_ascii=False, sort_keys=True, indent=2) + stream.write('\n') + print(json.dumps({'evidence_directory': str(output), **receipt})) + + +if __name__ == '__main__': + main() diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/checker_encoder_fixtures.py b/docs/plans/original-completion-2026-10-02/execution/p04-01/checker_encoder_fixtures.py new file mode 100644 index 0000000..191a6d1 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/checker_encoder_fixtures.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +"""Optional getter projection controls; these are never native proof.""" +import json +import struct +import sys + +sys.dont_write_bytecode = True +import p04_native_series as s + + +def string(value, kind=4): + data = value.encode() + return struct.pack(' estimate + 1048576, 'insufficient disk for pointers') + destination.mkdir() # exclusive creation: a previous collection is never overwritten + for name, data in sorted(payloads.items()): + with (destination / name).open('xb') as output: + output.write(data) + print(json.dumps({'collection': str(destination), 'manifest_sha256': series.digest(payloads['MANIFEST.json']), + 'actual_payload_bytes': estimate, 'formal_acceptance': False})) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--build', type=Path, required=True) + try: + collect(parser.parse_args().build) + except (ValueError, OSError, KeyError, TypeError) as exc: + print(json.dumps({'collected': False, 'error': str(exc)})) + sys.exit(1) diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/p04_native_series.py b/docs/plans/original-completion-2026-10-02/execution/p04-01/p04_native_series.py new file mode 100644 index 0000000..d1117da --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/p04_native_series.py @@ -0,0 +1,930 @@ +"""Read-only P04 Stage R. Paths in native records are provenance, not authority. + +No engine invocation, source adoption, candidate binding or acceptance occurs here. +""" +import argparse +import hashlib +import json +import math +from pathlib import Path +import re +import struct +import sys + +sys.dont_write_bytecode = True +HERE = Path(__file__).resolve().parent +MANIFEST_SHA256 = 'a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8' +MANIFEST_BYTES = 1244135 +PRODUCT = '5f6bc11d6bb6e92d22d787989b219fef3a435bc1' +RUNS = {'s1': 'p04-s1-native-r02', 's2a': 'p04-s2a-native-r03', + 's2b': 'p04-s2b-native-r02', 's3': 'p04-s3-native-r01'} +# Native descriptors pinned from literal files, never ROOT result passed fields. +NATIVE_PINS = { + 's1': ('1e26d9585396da9415747d87d77b70f23425a3ee0714e4fca114748f9479fd4e', '01372beb0ffec181c892d3b4e88bce32001e08fb2007938d7bf19135511a7acc', '0b6d11eb34d8af64b22cb806caeb06eed4ec521fa9ee24d1204ea185345e655c'), + 's2a': ('2598cc4979b5e58cf71632187e4fc3c1832a37321344a0da0be8b27ce68edfc2', '346d504af64cb1f86b74c668e27305273d3d526e0f56b4fd24bd6942d98b2a32', 'db61ee7ba066aebfb3f4f20a636c0e3de3c8126abd726f312ab68bef313df5fb'), + 's2b': ('608df09daa604ca41706b94cf480943e9a5eccd98a1754bc668dbe38d931fb2d', 'd7f114143b99b1e640da88337ea33918d783e46e5b7c6a1dd4444fbe5c65d105', 'c19c16fe193ff0c8caf9b1bdef71d59b7bb6caa0cc6d59c4f0e8f1356795d6eb'), + 's3': ('f1e97dcb0d518f980bdf12b46ab78a0b7026e53a0b73b06d64132ea8d60d91cc', '9795df0a049d9888d60a4dd32a77450e81459b0dad0d42fe325ca067322d557a', '6aa632cdc6d7bb9cc79f46e8fcce396a80a8d04fee01c32aba836795d2e52b71'), +} + + +def require(ok, message): + if not ok: + raise ValueError(message) + + +def digest(data): + return hashlib.sha256(data).hexdigest() + + +def unique(rows): + result = {} + for key, value in rows: + require(key not in result, 'duplicate key: ' + str(key)) + result[key] = value + return result + + +def function_bodies(data): + starts = list(re.finditer(rb'(?m)^(?:static )?func ([A-Za-z_][A-Za-z_0-9]*)\(', data)) + return {m[1].decode(): data[m.start():starts[i + 1].start() if i + 1 < len(starts) else len(data)] + for i, m in enumerate(starts)} + + +def source_body_pins(resolver, rows): + for row in rows: + pin = resolver.manifest['product_source'][row['path']] + body = function_bodies(resolver.read(pin['path'], pin))[row['function']] + require(digest(body) == row['production_body_sha256'] == row['registered_body_sha256'], + 'full-function-body pin mismatch') + + +class Series: + def __init__(self, build, name): + self.name, self.build = name, Path(build) + self.run = RUNS[name] + self.base = self.build / self.run + self.resolver = Resolver(self.build / 'audit-support/p04-r17-full-inputs-r01/MANIFEST.json') + self.references = {} + self.nodes = {} + inputs, inventory, execution = [loads(self.read(n, {'sha256': h})) for n, h in zip( + ('runner-input.json', 'raw-inventory.json', 'execution.json'), NATIVE_PINS[name])] + self.inputs, self.inventory, self.execution = inputs, inventory, execution + source = loads(self.read('source_before.json', {'sha256': self.resolver.manifest['source_before_sha'][self.run]})) + require(source == inputs['source_before'] == self.resolver.manifest['product_source'] and len(source) == 1338, + 'source_before product set mismatch') + selected = {k: v['sha256'] for k, v in source.items()} + require(inputs['QA_overrides'] == ['tests/s3/test_runtime_staff_assignment.gd'], 'QA override membership') + selected[inputs['QA_overrides'][0]] = inputs['QA']['sha256'] + require(inputs['selected_source_map'] == selected, 'selected source pin') + self.resolver.read(inputs['QA']['path'], inputs['QA']) + require(loads(self.read('source_after.json')) == source, 'source changed during native run') + for pin in source.values(): + self.resolver.read(pin['path'], pin) + for pin in inputs['fixed_input_pins']: + self.resolver.read(pin['path'], pin) + app = loads(self.read_build('audit-support/native-first-current/POST_P18_CURRENT_SOURCE_APPLICABILITY.json', + {'sha256': '46e0cc35cb95496d5f26d3cab7d3a31cb29faacaa16714daab3aefdf65b420a3'})) + source_body_pins(self.resolver, app['ordering_functions']) + require(execution['error'] is None and execution['guard_errors'] == [] and + execution['cleanup_errors'] == [], 'native guards/cleanup failed') + self.pids = [] + require([p['phase'] for p in execution['phases']] == ['import', 'parse', 'writer', 'reader'], 'phase membership') + for phase in execution['phases']: + self.ownership(phase) + require(len(set(self.pids)) == 4 and self.pids == execution['actual_captured_PIDs'], 'owned PID membership') + pidset = loads(self.read('PID-set.json')) + require(pidset['engine_PIDs'] == self.pids and pidset['owner_runner_pid'] == inputs['owner_runner_pid'], 'PID set binding') + for path, pin in inventory.items(): + self.read('raw/' + path, pin) + self.formal() + self.phase_pairs = 0 + phase_files = 0 + for path in inventory: + if path.startswith('writer-') and '-phase-' in path: + other = 'reader-' + path[len('writer-'):] + require(other in inventory and self.read('raw/' + path) == self.read('raw/' + other), + 'altered phase pair: ' + path) + phase_files += 1 + # Both representations are checked above; count each native + # Writer/Reader observation once, not once per file format. + if path.endswith('.var'): + self.phase_pairs += 1 + if name == 's3': + require(phase_files == 352 and self.phase_pairs == 176, 'S3 phase files/unique pair counts') + + def read_build(self, relative, pin=None): + require(not Path(relative).is_absolute() and all(p not in ('', '.', '..') for p in relative.split('/')), + 'raw traversal') + p = ordinary(self.build / relative) + data = p.read_bytes() + if pin is not None: + checked(data, pin, relative) + self.references[relative] = {'sha256': digest(data), 'bytes': len(data)} + return data + + def read(self, relative, pin=None): + return self.read_build(self.run + '/' + relative, pin) + + def typed(self, label): + if label not in self.nodes: + name = label + '.var' + require(name in self.inventory, 'typed raw missing (JSON-only substitute): ' + label) + self.nodes[label] = variant(self.read('raw/' + name, self.inventory[name])) + return self.nodes[label] + + def value(self, label): + return self.typed(label).plain() + + def ownership(self, phase): + role = phase['phase'] + start = loads(self.read(role + '/started.json')) + end = loads(self.read(role + '/ended.json')) + process = phase['native_process'] + require(start['event'] == 'ENGINE_STARTED' and end['event'] == 'ENGINE_ENDED', 'missing PID end') + require(start['actual_pid'] == end['actual_pid'] == phase['actual_pid'] == process['pid'], 'unowned PID') + require(start['owner_pid'] == end['owner_pid'] == self.inputs['owner_runner_pid'], 'wrong PID owner') + require(start['current_process_identity']['ppid'] == start['owner_pid'] and + start['current_process_identity']['pid'] == start['actual_pid'], 'live PID ownership') + require(start['command'] == end['command'] == phase['command'] == process['argv'], 'PID command binding') + require(end['monotonic_ns'] > start['monotonic_ns'] and end['engine_ended'] is True and + end['exit_code'] == end['actual_process_exit_code'] == phase['exit_code'] == process['exit_code'] == 0 + and end['timed_out'] is False and end['lifecycle_error'] is None, 'PID end/gate failure') + for stream in ('stdout', 'stderr'): + self.read(role + '/' + stream + '.log', {'sha256': process[stream + '_sha256']}) + self.read(role + '/engine-native.log', phase['raw_native_log']) + require(process['strict_native_diagnostics']['errors'] == [] and process['error_lines'] == [], 'native diagnostics') + require(phase.get('collector_errors', []) == [] and phase['validation_error'] is None, 'native phase gate') + env = phase['controlled_environment'] + if role in ('writer', 'reader'): + raw = loads(self.read('raw/' + role + '.json')) + require(raw['pid'] == end['actual_pid'] and raw['profile'] == env['THE_TREE_PACK_PROFILE_EXPECTED'], 'raw PID/environment') + self.pids.append(end['actual_pid']) + + def formal(self): + before, after = self.execution['formal_before'], self.execution['formal_after'] + require(before == after and len(before) == {'s1': 14, 's2a': 1, 's2b': 6, 's3': 22}[self.name], 'formal original drift/count') + roles = {} + originals = [] + for role in ('writer', 'reader'): + record = loads(self.read('raw/' + role + '.json')) + require(set(record['slots']) == set(before), 'formal slot membership') + roles[role] = {} + for slot, original in record['slots'].items(): + originals.append(original) + roles[role][slot] = self.resolver.slot(original, self.run, role, slot, before[slot]) + require(len(set(originals)) == len(originals), 'duplicate original/Reader slot') + require(roles['writer'] == roles['reader'], 'Reader member drift') + inodes = [] + for original in originals: + for member in before[next(k for k in before if original.endswith('/' + k))]: + st = ordinary(self.resolver.mapping[original + '/' + member]['relocated']).stat() + inodes.append((st.st_dev, st.st_ino)) + require(len(set(inodes)) == len(inodes), 'original/Reader members share inode') + + def result(self, conditions, facts=None): + return {'series': self.name, 'verified_conditions': sorted(conditions), 'facts': facts or {}, + 'product_content': PRODUCT, 'relocation_manifest_sha256': MANIFEST_SHA256, + 'source_files': 1338, 'owned_pids': self.pids, 'phase_pairs': self.phase_pairs, + 'references': self.references, + 'relocated_references': self.resolver.used, 'formal_acceptance': False} + + +def loads(data): + return json.loads(data, object_pairs_hook=unique, + parse_constant=lambda v: require(False, 'nonfinite JSON')) + + +def ordinary(path): + path = Path(path) + require(path.is_absolute() and '..' not in path.parts, 'traversal/relative path') + for parent in (path, *path.parents): + require(not parent.is_symlink(), 'symlink: ' + str(parent)) + require(path.is_file(), 'missing file: ' + str(path)) + return path + + +def checked(data, descriptor, label): + require(digest(data) == descriptor['sha256'], 'SHA drift: ' + label) + if 'bytes' in descriptor: + require(type(descriptor['bytes']) is int and len(data) == descriptor['bytes'], + 'byte length drift: ' + label) + return data + + +class Resolver: + def __init__(self, manifest): + self.path = ordinary(manifest) + data = self.path.read_bytes() + require(len(data) == MANIFEST_BYTES and digest(data) == MANIFEST_SHA256, + 'relocation MANIFEST identity mismatch') + self.manifest = loads(data) + self.base = self.path.parent + self.mapping = self.manifest['original_to_relocated'] + require(len(self.mapping) == 1721 and self.manifest['logical_bytes'] == 71488772, + 'relocation membership/count') + require(self.manifest['product_content_anchor'] == PRODUCT, 'product content anchor') + targets = [] + for original, row in self.mapping.items(): + require(Path(original).is_absolute() and '..' not in Path(original).parts, + 'invalid original provenance') + p = Path(row['relocated']) + require(p.is_relative_to(self.base) and '..' not in p.parts, 'relocation traversal') + targets.append(str(p)) + require(len(set(targets)) == len(targets), 'duplicate relocated member') + self.used = {} + + def read(self, original, native): + require(original in self.mapping, 'unmapped original path: ' + original) + row = self.mapping[original] + data = ordinary(row['relocated']).read_bytes() + checked(data, row, 'relocation ' + original) + checked(data, native, 'native ' + original) + self.used[original] = dict(row) + return data + + def slot(self, original, run, role, slot, members): + mapping = self.manifest['formal_slot_original_to_relocated'] + require(original in mapping, 'unmapped original slot') + row = mapping[original] + require((row['run'], row['role'], row['slot']) == (run, role, slot), + 'wrong parent-slot membership') + require(set(members) == {'journal.log', 'manifest.json', 'thumbnail.png', 'world.bin'}, + 'formal member membership') + directory = Path(row['relocated']) + require(not directory.is_symlink() and {p.name for p in directory.iterdir()} == set(members), + 'formal directory membership') + result = {} + for member, pin in members.items(): + key = original + '/' + member + require(key in self.mapping and + self.mapping[key]['relocated'] == row['relocated'] + '/' + member, + 'formal parent membership') + result[member] = self.read(key, pin) + return result + + +class Node: + def __init__(self, kind, value, raw): + self.kind, self.value, self.raw = kind, value, raw + + def at(self, key): + require(self.kind == 27, 'typed dictionary required') + rows = [v for k, v in self.value if k.value == key and k.kind in (4, 21)] + require(len(rows) == 1, 'missing/ambiguous typed key: ' + str(key)) + return rows[0] + + def plain(self): + if self.kind == 27: + return unique((k.value, v.plain()) for k, v in self.value) + if self.kind == 28: + return [v.plain() for v in self.value] + return self.value + + +def variant(data): + """Strict non-object Godot 4 var_to_bytes subset; retain every typed byte. + + Unknown types/flags and trailing bytes fail, never fall back to JSON. + """ + offset = 0 + + def take(size): + nonlocal offset + require(0 <= size <= len(data) - offset, 'truncated Variant') + result = data[offset:offset + size] + offset += size + return result + + def number(fmt): + return struct.unpack('<' + fmt, take(struct.calcsize('<' + fmt)))[0] + + def string(): + size = number('I') + value = take(size).decode('utf-8') + require(take((-size) % 4) == b'\0' * ((-size) % 4), 'Variant string padding') + return value + + def parse(depth=0): + require(depth < 100, 'Variant nesting') + start = offset + header = number('I') + kind, flags = header & 0xffff, header >> 16 + require(flags == 0 or (flags == 1 and kind in (2, 3, 28)), f'unsupported Variant flags {kind}:{flags} at {start}') + if kind == 0: + value = None + elif kind == 1: + value = number('I') + require(value in (0, 1), 'Variant bool') + value = bool(value) + elif kind == 2: + value = number('q' if flags else 'i') + elif kind == 3: + value = number('d' if flags else 'f') + elif kind in (4, 21): + value = string() + elif kind in (5, 6, 9, 10, 12, 13, 20): + count = {5: 2, 6: 2, 9: 3, 10: 3, 12: 4, 13: 4, 20: 4}[kind] + value = tuple(number('i' if kind in (6, 10, 13) else 'f') for _ in range(count)) + elif kind in (27, 28): + element_type = number('I') if kind == 28 and flags == 1 else None + count = number('I') + require(count < 1000000, 'shared/oversized Variant collection') + if kind == 27: + value = [(parse(depth + 1), parse(depth + 1)) for _ in range(count)] + keys = [(k.kind, k.raw) for k, _ in value] + require(len(set(keys)) == len(keys), 'duplicate typed key') + else: + value = [parse(depth + 1) for _ in range(count)] + require(element_type is None or all(v.kind == element_type for v in value), + 'typed array member mismatch') + elif kind == 29: + count = number('I') + value = take(count) + take((-count) % 4) + elif kind in (30, 31, 32, 33, 34): + count = number('I') + require(count < 1000000, 'oversized packed Variant') + value = tuple(string() if kind == 34 else number({30: 'i', 31: 'q', 32: 'f', 33: 'd'}[kind]) + for _ in range(count)) + else: + raise ValueError('unsupported Variant type: ' + str(kind)) + return Node(kind, value, data[start:offset]) + + result = parse() + require(offset == len(data), 'trailing Variant bytes') + return result + + +def normalized(node): + """Only String/StringName and dictionary insertion order may normalize.""" + if node.kind in (4, 21): + return ('string', node.value) + if node.kind == 27: + rows = [(normalized(k), normalized(v)) for k, v in node.value] + require(len({repr(k) for k, _ in rows}) == len(rows), 'getter key collision') + return (27, tuple(sorted(rows, key=lambda row: repr(row[0])))) + if node.kind == 28: + return (node.raw[:4], tuple(normalized(v) for v in node.value)) + return (node.kind, node.raw) + + +PERSISTENCE_BODIES = { + 'get_day_summary': '449eef926054cf76c39d6b4c016da3d775d41f2b343b4f8ad57b62c594cb32bb', + 'create_snapshot': '8c6e4f000e6df6f0f996445a57c4c17223cae4e1df57a9c2e3a605cf4d9f5309', + '_snapshot_root_report': '96084d76beeac11d165753a82160f789cd47b4dec4b6bb8cd6599a4f5ed08efe', + '_visit_snapshot_tree': '70792369c05882b813632a3c96f934928e9a089d420700cfc0d4c8fa971972a0', +} + + +def persistence_pins(s): + path = 'scripts/operations/operations_coordinator.gd' + pin = s.resolver.manifest['product_source'][path] + source = s.resolver.read(pin['path'], pin) + bodies = function_bodies(source) + report = loads(s.read_build('audit-support/p04-repair19-close-r01/ROOT_ACTUAL_PERSISTENCE_CALL_CHAIN.json')) + require(digest(source) == report['full_file_sha256'] == + '871a248ee101b6df543fd5cf0440b5d4b0f02c21fa145a99f0687888c1bc6f90', 'persistence whole-source pin') + for function, expected in PERSISTENCE_BODIES.items(): + rows = [r for r in report['full_functions'] if r['function'] == function] + require(len(rows) == 1 and digest(bodies[function]) == rows[0]['sha256'] == expected and + len(bodies[function]) == rows[0]['bytes'] and + bodies[function] == rows[0]['full_body'].encode(), 'persistence full-body/call-chain pin') + + +def getter_array_headers(writer, reader, path='day_summary'): + changes = [] + if writer.kind == reader.kind == 27: + for key, value in writer.value: + require(key.kind in (4, 21), 'diagnostic dictionary key') + changes.extend(getter_array_headers(value, reader.at(key.value), path + '/' + key.value)) + elif writer.kind == reader.kind == 28: + require(len(writer.value) == len(reader.value), 'diagnostic array length') + if writer.raw[:4] != reader.raw[:4]: + changes.append({'path': path, 'writer_header': writer.raw[:4].hex(), + 'reader_header': reader.raw[:4].hex(), + 'writer_typed_builtin': struct.unpack('> 16 else None, + 'reader_typed_builtin': struct.unpack('> 16 else None, + 'length': len(writer.value)}) + for i, (a, b) in enumerate(zip(writer.value, reader.value)): + changes.extend(getter_array_headers(a, b, path + '/' + str(i))) + return changes + + +def persisted_day_projection(node, *, chain='_snapshot_root_report/_visit_snapshot_tree', + comparison='getter_to_persisted_day1'): + """Optional, fail-closed encoder port for the permitted day-summary subset. + + Output retains scalar Variant bytes. Only StringName, key coercion/order and + generic Array headers can change. Native persisted comparisons never call it. + The root uses capture=false and an empty cache, exactly the uncached source + path. The original raw getters remain independently unequal and preserved. + """ + require(chain == '_snapshot_root_report/_visit_snapshot_tree', 'projection call chain') + require(comparison == 'getter_to_persisted_day1', 'projection forbidden for native persisted comparison') + budget = [1, 0] + + def text_bytes(value): + encoded = value.encode('utf-8') + return struct.pack('' + if key.kind == 1: + return 'true' if key.value else 'false' + return None # Refuse at this key's source-order validation step below. + + def dictionary(value, depth, root=False): + require(len(value.value) <= 20000, 'COLLECTION_TOO_LARGE') + keys = [key_text(k) for k, _ in value.value] + known_keys = [key for key in keys if key is not None] + require(len(set(known_keys)) == len(known_keys), 'DUPLICATE_CANONICAL_KEY') + converted = [] + for (key, child), text in zip(value.value, keys): + require(key.kind in (4, 21, 2), 'INVALID_DICTIONARY_KEY') + require(root or depth + 1 <= 32, 'MAX_VARIANT_DEPTH') + count_node() + count_text(text) + converted.append((text, visit(child, 1 if root else depth + 1))) + converted.sort(key=lambda row: row[0]) + return struct.pack(' 0} + require(paid == {'staff_akane': 120, 'staff_bardo': 135}, 'actual paid staff membership') + for staff_id, amount in paid.items(): + staff = [p for p in after['staff']['staff'] if p['staff_id'] == staff_id] + require(len(staff) == 1 and staff[0]['daily_wage'] == amount, 'staff wage source') + shifts = [r for r in after['shifts'] if r['staff_id'] == staff_id and r['start_day'] == 0 and r['worked_minutes'] + r['overtime_minutes'] > 0] + require(shifts, 'actual duty missing') + worked = 0 + for shift in shifts: + prior = [r for r in before['shifts'] if r['shift_instance_id'] == shift['shift_instance_id']] + require(len(prior) == 1 and shift['overtime_minutes'] == prior[0]['overtime_minutes'], 'duty shift join') + worked += shift['worked_minutes'] - prior[0]['worked_minutes'] + require(worked == (1 if staff_id == 'staff_bardo' else 0), 'actual midnight duty delta') + require(len(upkeep) == 3 and sum(r['amount'] for r in upkeep) == 5 and + len({r['transaction_id'] for r in upkeep}) == 1, 'upkeep 5G/three rows/one transaction') + for payment in upkeep: + accrual = [r for r in delta if r['category'] == 'MAINTENANCE_ACCRUAL' and + r['source_entity_id'] == payment['source_entity_id'] and r['amount'] == payment['amount'] and + r['transaction_id'] == payment['transaction_id'] and r['absolute_minute'] == 1439] + require(len(accrual) == 1 and payment['absolute_minute'] == 1440, 'upkeep accrual join') + require(after['staff'] == runtime(after)['staff'] and after['operations'] == runtime(after)['operations']['coordinator'], 'settlement Runtime join') + days = [d for d in after['operations']['day_summaries'] if d['day'] == 1] + require(len(days) == 1 and days[0]['closed_at'] == 1440, 'one daily close') + + +def recall(node): + command = node.at('command').plain() + require(command['result']['is_replay'] is True and command['payload']['processed_minutes'] == 0 + and command['payload']['boundary_counts']['daily_closes'] == 0, 'advance_to replay result') + require(node.at('before').at('snapshot').raw == node.at('after').at('snapshot').raw, + 'entire Runtime replay bytes changed') + + +def s2a_semantics(s): + persistence_pins(s) + for branch in ('C1', 'C2', 'C3'): + before = s.value('writer-' + branch + '-before') + after = s.value('writer-' + branch + '-midnight') + settlement(before, after) + recall(s.typed('writer-C2-recall')) + reader = s.typed('reader-C3-branch') + writer = s.typed('writer-C3-midnight') + require(writer.at('snapshot').raw == reader.at('expected').at('snapshot').raw == + reader.at('restored').at('snapshot').raw, 'formal Reader exact Runtime restore') + recall(reader.at('recall')) + getter1, getter2 = writer.at('day_summary'), reader.at('restored').at('day_summary') + require(getter1.raw != getter2.raw, 'raw getter byte inequality lost/JSON-only getter substitute') + conditions = ['midnight_settlement', 'midnight_replay'] + # Canonical persisted values are compared directly; projection is confined + # to the diagnostic raw getter, never Reader expected/restored Runtime. + canonical = writer.at('canonical_day1') + if persisted_day_projection(getter1) == canonical.raw == persisted_day_projection(getter2): + conditions.append('save_restore') + return conditions + + +def s2b_semantics(s): + for branch in ('night_absent', 'overtime'): + for minute in (1079, 1080, 1081): + label = branch + '-' + str(minute) + expected = s.typed('writer-' + label).at('snapshot').raw + require(expected == s.typed('reader-' + label + '-restored').at('actual').at('snapshot').raw, 'S2b formal Reader state') + if minute != 1079: + require(expected == s.typed('reader-' + label + '-replay').at('snapshot').raw, 'S2b exact replay') + for phase in ('before-facility', 'after-facility', 'after-staff'): + require(s.typed('writer-' + label + '-' + phase).raw == + s.typed('reader-' + label + '-' + phase).raw, 'S2b altered phase pair') + s.phase_pairs += 1 + rows = [s.value('writer-night_absent-' + str(m)) for m in (1079, 1080, 1081)] + for row in rows: + service_reservations(row, 'ACTIVE') + require(not sales(row), 'night-absent charge') + require(rows[2]['service']['state'] == 'WAITING_FOR_STAFF' and + rows[2]['service']['interruption_reason'] == 'STAFF_UNAVAILABLE' and + rows[2]['service']['elapsed_minutes'] == rows[1]['service']['elapsed_minutes'], 'night staff stop reason/progress') + require(rows[2]['facility']['active_staff_ids'] == [] and all(w['state'] == 'OFF_DUTY' for w in rows[2]['workers']), 'night staff actual absence') + previous = s.value('writer-overtime-1079') + for minute in (1080, 1081): + obs = s.value('writer-overtime-' + str(minute)) + require(obs['task']['interrupt_level'] == 0 and obs['worker']['is_overtime'] is True and + obs['worker']['current_task_id'] == obs['task']['task_id'] and + obs['task']['assigned_staff_id'] == obs['worker']['staff_id'] == obs['shift']['staff_id'], 'overtime task/staff/shift join') + require(obs['shift']['shift_instance_id'] == previous['shift']['shift_instance_id'] and + obs['shift']['overtime_minutes'] == previous['shift']['overtime_minutes'] + 1 and + obs['shift']['worked_minutes'] == previous['shift']['worked_minutes'], 'overtime duty once') + guard = s.typed('writer-overtime-' + str(minute) + '-guard') + require(guard.at('result').plain() == {'ok': False, 'reason': 'MINUTE_ALREADY_APPLIED'} and + guard.at('before').raw == guard.at('after').raw, 'same-minute rejection entire Runtime invariant') + previous = obs + return ['night_staff_absent', 'overtime_task'] + + +def s3_semantics(s): + observed = {} + for slot in s.execution['formal_before']: + require(s.typed('writer-' + slot).at('snapshot').raw == + s.typed('reader-' + slot + '-restored').at('snapshot').raw, + 'S3 formal Reader exact Runtime') + for case in ('B1', 'B2a', 'B2b', 'B3', 'B4a', 'B4b'): + before, after, next_ = [s.value('writer-' + case + '-' + p) for p in ('before', 'after', 'next')] + terminal = next_ if case == 'B4b' else after + require(next_['reservations'] == terminal['reservations'], 'release-once reservation drift') + old = {r['reservation_id']: r for r in before['reservations']} + require(len(old) == len(before['reservations']) and set(old) == {r['reservation_id'] for r in terminal['reservations']}, 'release reservation membership') + for reservation in terminal['reservations']: + prior = old[reservation['reservation_id']] + require(prior['state'] == 'ACTIVE' and all(prior[k] == reservation[k] for k in + ('requester_id', 'item_id', 'stack_id', 'quantity')), 'release reservation identity') + if reservation['state'] == 'ACTIVE': + request = terminal['requests'][reservation['requester_id']] + require(request and reservation['reservation_id'] in request['reserved_inventory_ids'], 'orphan active reservation') + else: + require(reservation['state'] in ('RELEASED', 'CONSUMED') and + not terminal['requests'][reservation['requester_id']], 'terminal reservation/request join') + if case == 'B1': + require(after['facility']['operator_open'] is False and after['service']['state'] == 'PAUSED' + and after['service']['interruption_reason'] == 'RELATED_FACILITY_INACTIVE' + and after['service']['service_id'] == before['service']['service_id'], 'B1 direct closure/started service/reason') + service_reservations(after, 'ACTIVE') + require(not sales(after), 'B1 stopped charge') + elif case in ('B2b', 'B3', 'B4b'): + require(not sales(terminal) and all(r['state'] == 'RELEASED' for r in terminal['reservations']), 'release zero charge') + if case == 'B4b': + require(terminal['service']['state'] == 'FAILED' and terminal['service']['failure_reason'] == 'BROKEN', 'B4b actual failed reason') + require(before['event']['state'] == 'QUEUED' and after['event']['state'] == 'COMPLETED' + and after['facility']['state'] == 'BROKEN' and after['facility']['durability'] == 0, + 'B4b destruction event/facility join') + else: + require(terminal['service']['state'] == 'COMPLETED' and len(sales(terminal)) == 1 and + sales(terminal)[0]['amount'] == 25, 'completion-before-exit/event actual charge') + observed[case] = {'sales': len(sales(terminal)), 'amount': sum(r['amount'] for r in sales(terminal)), + 'reservation_states': [r['state'] for r in terminal['reservations']]} + observed['B1_power'] = power_stop(s) + return ['closure_release', 'exit_release', 'patience_release', 'destruction_stop', 'power_stop'], observed + + +def power_stop(s): + """B1's observed power boundary, retaining its actual dependency reason.""" + labels = ('before', 'after', 'next', 'recovered', 'duplicate_guard') + points = [s.value('writer-B1-' + label) for label in labels] + before, after, next_, recovered, guard = points + require([p['minute'] for p in points] == [959, 960, 961, 962, 963], 'B1 power minutes') + reserve = before['inputs']['reserve'] + require(type(reserve) is int and reserve > 0, 'B1 original finite reserve') + identity = ('service_id', 'request_id', 'facility_id', 'guest_ids', + 'inventory_reservations', 'price_snapshot', 'duration_minutes', 'started_at') + original = before['service'] + released = None + for i, point in enumerate(points): + service, facility = point['service'], point['facility'] + state = runtime(point) + guest_b = point['guests']['p04.s3.guest.b'] + live_b = [g for g in state['guests']['guests'] if g['guest_id'] == 'p04.s3.guest.b'] + if i < 2: + require(live_b == [guest_b] and guest_b['patience_remaining'] == 45, + 'B1 real B before/at closure') + if i == 1: + require(guest_b['state'] == 'LEAVING' and guest_b['state_entered_at'] == 960 and + guest_b['exit_reason'] == 'NO_SUITABLE_FACILITY' and + guest_b['outstanding_reservation_ids'] == [], 'B1 normal departure history') + else: + require(guest_b == {} and live_b == [], 'B1 normal despawn after departure') + records = [g for g in state['guests']['persistent_records'] if g['guest_id'] == 'p04.s3.guest.b'] + departed = after['guests']['p04.s3.guest.b'] + require(len(records) == 1 and set(records[0]) == {'debt_gold', 'guest_id', 'guest_type_id', + 'outstanding_reservation_ids', 'unresolved_incident_ids'} and + records[0] == {k: departed[k] for k in records[0]} and + records[0]['outstanding_reservation_ids'] == [], 'B1 persisted departure join') + account_b = 'GUEST_WALLET:p04.s3.guest.b' + wallet_rows = [r for r in point['ledger'] if account_b in (r['credit_account'], r['debit_account'])] + initial_rows = [r for r in before['ledger'] if account_b in (r['credit_account'], r['debit_account'])] + require(point['wallets']['p04.s3.guest.b'] == 50 and wallet_rows == initial_rows and + len(wallet_rows) == 1 and wallet_rows[0]['category'] == 'INITIALIZATION' and + wallet_rows[0]['debit_account'] == account_b and wallet_rows[0]['amount'] == 50, + 'B1 B wallet/no economy charge') + require(point['reservations'] == state['inventory']['reservations'], 'B1 immutable reservation join') + require(all(service[k] == original[k] for k in identity), 'B1 power service identity') + require(facility in state['facilities']['instances'] and + (service in facility['active_services'] or service in state['facilities']['service_history']), + 'B1 power Runtime facility/service join') + require(point['ledger'] == state['economy']['entries'], 'B1 power Runtime ledger join') + require(point['snapshot']['game_state']['resources']['mana'] == + (0 if i in (1, 2) else reserve), 'B1 exact public mana reserve') + require(facility['power_available'] is (i not in (1, 2)), 'B1 actual power flag') + require(facility['operator_open'] is (i == 0), 'B1 direct closure retained') + require(service['elapsed_minutes'] == (44 if i < 3 else 45) and + service['duration_minutes'] == 45, 'B1 power progress') + service_reservations(point, 'ACTIVE' if i < 3 else 'CONSUMED') + if i in (1, 2): + require(service['state'] == 'PAUSED' and + service['interruption_reason'] == 'RELATED_FACILITY_INACTIVE' and not sales(point), + 'B1 stopped reason/zero charge') + if i: + require(not any(r['request_id'] == 'p04.s3.request.b' for r in state['facilities']['requests']), + 'B1 B request absent from full state') + available = sum(stack['quantity'] - stack['reserved_quantity'] + for inventory in state['inventory']['inventories'] + if inventory['owner_inventory_id'] == 'facility.inventory:' + original['facility_id'] + for stack in inventory['stacks'] if stack['item_id'] == 'ingredient/basic_meal') + require(available == point['available_meals'] == 1, 'B1 full-state available meal stock') + rows = [r for r in point['reservations'] if r['requester_id'] == 'p04.s3.request.b'] + require(len(rows) == 1 and rows[0]['state'] == 'RELEASED' and + rows[0]['terminal_at'] == 960 and point['available_meals'] == 1 and + point['requests']['p04.s3.request.b'] == {}, 'B1 queued release/stock/patience') + if released is not None: + require(rows[0] == released, 'B1 repeated release') + released = rows[0] + require(not sales(before) and recovered['service']['state'] == guard['service']['state'] == 'COMPLETED' + and recovered['service']['completed_at'] == 962, 'B1 one recovery completion') + paid = sales(recovered) + require(len(paid) == 1 and paid[0]['amount'] == original['price_snapshot'] == 25 and + paid[0]['absolute_minute'] == 962 and paid[0]['source_entity_id'] == original['facility_id'] and + paid[0]['credit_account'] == 'GUEST_WALLET:' + original['guest_ids'][0] and + guard['ledger'] == recovered['ledger'], 'B1 recovery payment/no extra charge') + links = [r for r in runtime(recovered)['economy']['idempotency'] + if r['key'] == 'service.payment.' + original['service_id']] + require(len(links) == 1 and links[0]['transaction_id'] == paid[0]['transaction_id'], + 'B1 recovery transaction identity') + commands = s.value('writer-B1-commands') + saved = before['inputs']['recovery'] + restored = [c for c in commands['submitted'] if c['serial'] == saved['serial']] + require(len(restored) == 1, 'B1 saved public command membership') + command = restored[0] + require(all(command[k] == saved[k] for k in ('at', 'kind', 'payload', 'idempotency_key')) and + command['issued_at'] == saved['issue_minute'] == 961 and + command['result']['command_id'] == saved['command_id'] and + command['payload'] == {'deltas': {'mana': reserve}}, 'B1 same saved reserve command') + for kind, payload in ( + ('change_resources', {'deltas': {'mana': -reserve}}), + ('set_facility_policy', {'facility_id': original['facility_id'], 'patch': {'business_open': False}})): + matches = [c for c in commands['submitted'] if c['at'] == 960 and c['kind'] == kind and c['payload'] == payload] + require(len(matches) == 1, 'B1 public stop command') + completed = [r for r in commands['completed_including_fixture'] + if r['result']['command_id'] == matches[0]['result']['command_id']] + require(len(completed) == 1 and completed[0]['result']['code'] == 'OK' and + completed[0]['result']['is_replay'] is False, 'B1 stop command completion') + phase = s.value('writer-B1-phase-961-APPLY_COMMANDS') + require(phase['minute'] == 961 and phase['phase'] == 'APPLY_COMMANDS' and + phase['snapshot']['game_state']['resources']['mana'] == reserve and + phase['facility']['power_available'] is False and phase['facility']['operator_open'] is False and + phase['service'] == next_['service'] and phase['ledger'] == next_['ledger'], + 'B1 restored reserve precedes next power/service evaluation') + return {'reserve': reserve, 'stopped_minutes': [960, 961], 'completion_minute': 962, + 'payment': 25, 'interruption_reason': 'RELATED_FACILITY_INACTIVE'} + + +def verify_s1(build): + s = Series(build, 's1') + return s.result(s1_semantics(s), {'paid_service_power_claim': False}) + + +def verify_s2a(build): + s = Series(build, 's2a') + conditions = s2a_semantics(s) + extra, paid_facts = paid_series(s) + headers = getter_array_headers(s.typed('writer-C3-midnight').at('day_summary'), + s.typed('reader-C3-branch').at('restored').at('day_summary')) + audit = loads(s.read_build('audit-support/p04-repair19-close-r01/ROOT_S2A_TYPED_ARRAY_HEADER_AUDIT.json')) + require(len(headers) == 6 and sorted(headers, key=lambda r: r['path']) == + sorted(audit['generic_array_header_changes'], key=lambda r: r['path']), + 'six raw generic array header changes') + return s.result(conditions + extra, {'paid_originals': paid_facts, 'raw_getter_bytes_equal': False, 'sales_gold': 25, 'payroll_gold': 255, + 'generic_array_header_changes': headers, 'persistence_body_pins': PERSISTENCE_BODIES, + 'upkeep_gold': 5, 'settlement_replay_is_request_resend': False, + 'save_restore_gap': None if 'save_restore' in conditions else + 'Exact encoder projection of raw day_summary differs from native canonical_day1'}) + + +def paid_series(s): + prefix = 'audit-support/p04-current-native-series-r01/' + original = str(s.build / prefix / 'SOURCE_MANIFEST.json') + s1_input = loads(s.read_build(RUNS['s1'] + '/runner-input.json', {'sha256': NATIVE_PINS['s1'][0]})) + pins = [p for p in s1_input['fixed_input_pins'] if p['path'] == original] + require(len(pins) == 1, 'paid native manifest pin missing') + manifest = loads(s.resolver.read(original, pins[0])) + rows = manifest['files'] + require(len({r['copy'] for r in rows}) == len(rows), 'duplicate paid native member') + mapping = {r['copy']: r for r in rows} + for row in rows: + s.read_build(prefix + row['copy'], row) + + def read(name): + require(name in mapping, 'paid raw out of manifest membership') + return s.read_build(prefix + name, mapping[name]) + + paid = 'p04-paid-shift-midnight-native-r01' + resend = 'p04-idempotency-paid-midnight-native-r01' + for run in (paid, resend): + inputs = loads(read(run + '/runner-input.json')) + require(inputs['source_before'] == s.resolver.manifest['product_source'], 'paid current source mismatch') + for pin in inputs['source_before'].values(): + s.resolver.read(pin['path'], pin) + execution = loads(read(run + '/execution.json')) + require(execution['error'] is None and execution['cleanup_errors'] == [], 'paid execution cleanup') + # Use the same PID/log/exit checks with this native layout, without + # treating any ROOT summary as a process receipt. + context = Series.__new__(Series) + context.build, context.run = s.build, prefix + run + context.references, context.inputs, context.pids = s.references, inputs, [] + for phase in execution['phases']: + context.ownership(phase) + require(context.pids == execution['actual_captured_PIDs'], 'paid owned PID membership') + + def snapshot(run, label): + return variant(read(run + '/order-evidence/' + label + '.var')) + + shift_nodes = [snapshot(paid, 'midnight-first-paid-' + suffix) for suffix in + ('before-handover-17-59', 'at-completion-before-handover-18-00', 'after-handover-18-00')] + shift_states = [n.at('systems').at('runtime').plain() for n in shift_nodes] + shift_sales = [[e for e in state['economy']['entries'] if e['category'] == 'SERVICE_SALE'] for state in shift_states] + # This supplied run has no sale at 1080. Recording that fact is not a + # shift_completion proof, even though the filenames say "paid". + require(all(not rows for rows in shift_sales), 'paid shift raw changed; review exact boundary semantics') + observations = loads(read(resend + '/order-evidence/observations.json')) + results = observations['duplicate_results'] + require([r['label'] for r in results] == ['queued-same-minute', 'paused-00-01', 'completed-00-02'], 'resend case membership') + final = None + for row in results: + label = 'midnight-duplicate-' + row['label'] + before, after = [snapshot(resend, label + '-' + side) for side in ('before', 'after')] + require(before.at('clock').raw == after.at('clock').raw, 'resend same-minute clock') + a, b = [n.at('systems').at('runtime') for n in (before, after)] + for key in ('economy', 'facilities', 'inventory', 'guests', 'staff'): + require(a.at(key).raw == b.at(key).raw, 'resend duplicate generation/payment: ' + key) + command = row['command_result'] + require(command['code'] == 'DUPLICATE_IDEMPOTENCY_KEY' and + command['details']['original_result']['details']['request_id'] == row['request_id'], 'resend actual request join') + final = b.plain() + history = [v for v in final['facilities']['service_history'] if v['request_id'] == results[0]['request_id']] + require(len(history) == 1 and history[0]['state'] == 'COMPLETED', 'resend one completed service') + service = history[0] + sales_ = [e for e in final['economy']['entries'] if e['category'] == 'SERVICE_SALE'] + require(len(sales_) == 1 and sales_[0]['amount'] == service['price_snapshot'] == 25 and + sales_[0]['absolute_minute'] == service['completed_at'], 'resend one payment') + require([e['transaction_id'] for e in final['economy']['idempotency'] if e['key'] == 'service.payment.' + service['service_id']] + == [sales_[0]['transaction_id']], 'resend payment/service idempotency join') + return ['same_minute_replay'], {'shift_completion': 'missing: zero SERVICE_SALE rows at 1080', + 'same_minute_replay': 'request resend, not advance_to settlement replay', + 'completed_service_id': service['service_id'], 'transaction_id': sales_[0]['transaction_id']} + + +def verify_s2b(build): + s = Series(build, 's2b') + return s.result(s2b_semantics(s), {'same_minute_rejection': 'MINUTE_ALREADY_APPLIED'}) + + +def verify_s3(build): + s = Series(build, 's3') + conditions, facts = s3_semantics(s) + return s.result(conditions, facts) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--build', type=Path, required=True) + args = parser.parse_args() + failed = False + for name in RUNS: + try: + result = globals()['verify_' + name](args.build) + print(json.dumps({k: v for k, v in result.items() if k not in ('references', 'relocated_references')}, sort_keys=True)) + except (ValueError, OSError, KeyError, TypeError) as exc: + print(json.dumps({'series': name, 'verified_conditions': [], 'error': str(exc)})) + failed = True + return int(failed) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/check_p04_order_evidence.py b/tools/check_p04_order_evidence.py index b0d06b4..1c676bd 100644 --- a/tools/check_p04_order_evidence.py +++ b/tools/check_p04_order_evidence.py @@ -15,6 +15,7 @@ import subprocess import sys import tempfile +sys.dont_write_bytecode = True ROOT = Path(__file__).resolve().parents[1] SPEC = ROOT / 'docs/spec/acceptance/P04-01.json' SPEC_HASH = '9ec722e7dfb0bee178d291624a026420c08412574e238bba1fde012c7a6f7881' @@ -24,7 +25,8 @@ REGISTERED_COMMIT = 'deb1bcb2374eb378b840ca345db952bd8572cb6e' EXECUTION_ORIGIN = '9767b0a28ca8d252520307dbbac60a1d8cfb87ab' CHECKER_PATH = 'tools/check_p04_order_evidence.py' DOC_SCOPE = 'docs/plans/original-completion-2026-10-02/execution/p04-01/' -MAIN_ANCHOR = '9694f50ee63750192376ca69bc7dda3f884784c5' +MAIN_ANCHOR = '9b1a18c1eb25ca1300dcd59da78423c0dda2f498' +TESTED_PRODUCT = '5f6bc11d6bb6e92d22d787989b219fef3a435bc1' SELECTION_SHA = '320ca668732b4ff72fc4449998af2327d4e5c527b06896740806aaf0487282b4' _GIT_ENV = None # Fixed membership, never supplied by the evidence index. @@ -135,10 +137,17 @@ def literal(a, b): def git_bytes(*args): - env = _GIT_ENV if _GIT_ENV is not None else dict(os.environ, GIT_OPTIONAL_LOCKS='0') + env = _GIT_ENV if _GIT_ENV is not None else git_environment() return subprocess.check_output(['git', '--no-replace-objects', *args], cwd=ROOT, env=env) +def git_environment(): + env = {k: v for k, v in os.environ.items() if not k.startswith('GIT_')} + env.update(GIT_OPTIONAL_LOCKS='0', GIT_NO_REPLACE_OBJECTS='1', GIT_CONFIG_NOSYSTEM='1', + GIT_CONFIG_GLOBAL=os.devnull) + return env + + @contextmanager def isolated_objects(): """merge-tree writes only disposable objects; canonical refs/index stay untouched. @@ -149,11 +158,9 @@ def isolated_objects(): global _GIT_ENV require(_GIT_ENV is None, 'nested object store') objects = git_bytes('rev-parse', '--path-format=absolute', '--git-path', 'objects').decode().strip() - with tempfile.TemporaryDirectory(prefix='p04-object-audit-') as temporary: - env = dict(os.environ) - for key in ('GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES', - 'GIT_INDEX_FILE', 'GIT_QUARANTINE_PATH'): - env.pop(key, None) + build = Path(os.environ['BUILD_OUTPUT_DIR']).resolve(strict=True) + with tempfile.TemporaryDirectory(prefix='p04-object-audit-', dir=build) as temporary: + env = git_environment() env.update(GIT_OBJECT_DIRECTORY=temporary, GIT_ALTERNATE_OBJECT_DIRECTORIES=objects, GIT_OPTIONAL_LOCKS='0', GIT_NO_REPLACE_OBJECTS='1') _GIT_ENV = env @@ -208,7 +215,7 @@ def supplied_file(directory, descriptor): if path.is_absolute(): support = directory / 'audit-support' roots = [support / n for n in ('p04-whole-closure-r01', 'native-first-current', - 'p04-current-native-series-r01')] + 'p04-current-native-series-r01', 'p04-r17-full-inputs-r01')] matches = [root for root in roots if path.is_relative_to(root)] require(len(matches) == 1, 'unsupplied external material: ' + name) path = safe(matches[0], path.relative_to(matches[0]).as_posix()) @@ -304,6 +311,128 @@ def verify_product_target(index, directory, checker_commit): 'tested_product': tested, 'product_target': target} +def ancestor(older, newer): + # merge-base's nonzero status is data only for the ordinary non-ancestor case. + try: + git_bytes('merge-base', '--is-ancestor', older, newer) + return True + except subprocess.CalledProcessError as exc: + require(exc.returncode == 1, 'cannot determine ancestry') + return False + + +def submission_role(commit): + require(ancestor(REGISTERED_COMMIT, commit) and not ancestor(MAIN_ANCHOR, commit), + 'invalid submission ancestry') + changed = git_bytes('diff', '--no-ext-diff', '--no-renames', '--name-only', '-z', + REGISTERED_COMMIT, commit).split(b'\0') + require(all(allowed_path(p.decode()) for p in changed if p), 'submission outside allowed_paths') + + +def actual_checkout(): + """Bind the executing modules and every worktree file to the actual HEAD.""" + commit = git_bytes('rev-parse', '--verify', 'HEAD^{commit}').decode().strip() + tree = git_bytes('rev-parse', 'HEAD^{tree}').decode().strip() + entries = tree_entries(tree) + require(not git_bytes('status', '--porcelain=v1', '--untracked-files=all', + '--ignore-submodules=none'), 'checkout is not clean') + require(not git_bytes('ls-files', '--others', '-z'), 'untracked/ignored substitution') + for path, (mode, oid) in entries.items(): + actual = safe(ROOT, path) + require(actual.read_bytes() == git_bytes('cat-file', 'blob', oid), 'HEAD/tree/FS bytes: ' + path) + require(('100755' if actual.stat().st_mode & 0o111 else '100644') == mode, + 'HEAD/tree/FS mode: ' + path) + # Also reject untracked directory symlinks, including ones ignored by Git. + for parent, directories, files in os.walk(ROOT, followlinks=False): + if Path(parent) == ROOT: + directories[:] = [p for p in directories if p != '.git'] + files = [p for p in files if p != '.git'] + for name in directories + files: + require(not (Path(parent) / name).is_symlink(), 'worktree symlink') + require(Path(__file__).resolve() == safe(ROOT, CHECKER_PATH), 'checker module location') + return commit, tree, entries + + +def verify_roles(index, directory): + """Infer roles from Git, never from an index or a caller's stage label.""" + require(not ({'candidate_commit', 'candidate_tree', 'code_only', 'candidate', 'phase'} & index.keys()), + 'candidate/code-only/phase declarations are not evidence') + commit, actual_tree, actual = actual_checkout() + require(_GIT_ENV is not None, 'isolated object store required') + if not ancestor(MAIN_ANCHOR, commit): + submission_role(commit) + submitted, main, role = commit, MAIN_ANCHOR, 'prospective composition' + else: + parents = git_bytes('show', '-s', '--format=%P', commit).decode().split() + require(len(parents) == 2, 'integration/final requires a two-parent no-ff merge') + main, submitted = parents + require(ancestor(MAIN_ANCHOR, main), 'merge main ancestry') + submission_role(submitted) + role = 'integration/final' + composed = git_bytes('-c', 'rerere.enabled=false', 'merge-tree', '--write-tree', + main, submitted).decode().strip() + require(re.fullmatch('[0-9a-f]{40}', composed) is not None, 'conflicted composition') + main_entries, patch, target = [tree_entries(t) for t in (main, submitted, composed)] + anchor_entries = tree_entries(MAIN_ANCHOR) + for path in main_entries.keys() | target.keys(): + require(main_entries.get(path) == target.get(path) or allowed_path(path), + 'composition changed game/unknown source: ' + path) + # Metadata membership comes from the real main anchor, never a declared exclusion. + metadata = sorted(p for p in anchor_entries if not selected_path(p)) + for path in target: + require(selected_path(path) or allowed_path(path) or path in metadata, + 'unknown source hidden as metadata: ' + path) + if role == 'integration/final': + require(actual_tree == composed and actual == target, 'actual merge differs from composition') + require(target[CHECKER_PATH] == patch[CHECKER_PATH] == actual[CHECKER_PATH], + 'checker blob differs from submission') + for path in patch: + if allowed_path(path): + require(target.get(path) == patch[path], 'reviewed patch differs in merge: ' + path) + binding = index['product_binding'] + require(set(binding) == {'tested_product', 'runner_inputs'}, 'unknown product role/metadata declaration') + require(binding['tested_product'] == TESTED_PRODUCT, 'wrong tested revision') + product = product_manifest(target) + tested_entries = tree_entries(TESTED_PRODUCT) + expected = product_manifest(tested_entries) + require(len(product) == 1338 and product == expected and + product_manifest(main_entries) == expected and product_manifest(anchor_entries) == expected, + 'complete 1338-file native product mismatch') + require(all(target[p][0] == main_entries[p][0] == tested_entries[p][0] for p in product), + 'native product mode mismatch') + require(index['source_map'] == {p: v['sha256'] for p, v in product.items()}, 'stale pin: source map') + runner_paths = set() + for descriptor in binding['runner_inputs']: + path = supplied_file(directory, descriptor) + require(path not in runner_paths, 'duplicate runner input') + runner_paths.add(path) + native = load(path) + before = native['source_before'] + require(set(before) == set(product) and all( + {'bytes': v['bytes'], 'sha256': v['sha256']} == product[p] for p, v in before.items()), + 'runner complete source map mismatch') + require(native['runner_sha256'] == product['tools/run_all_tests.py']['sha256'], 'runner source pin') + overrides = native['QA_overrides'] + require(len(overrides) == 1 and overrides[0].startswith('tests/') and overrides[0] in product, + 'QA override membership') + selected = {p: v['sha256'] for p, v in product.items()} + selected[overrides[0]] = native['QA']['sha256'] + require(native['selected_source_map'] == selected, 'selected QA source map') + needed = native.get('fixed_input_pins', []) + [native['QA']] + require(set(descriptor['materials']) == {p['path'] for p in needed}, 'input material membership') + for pin in needed: + material = supplied_file(directory, descriptor['materials'][pin['path']]) + require(material.stat().st_size == pin['bytes'] and sha(material) == pin['sha256'], 'input bytes') + require(sha(supplied_file(directory, descriptor['wrapper'])) == native['wrapper_sha256'], 'wrapper bytes') + require(runner_paths, 'runner inputs missing') + require(index['versions'] == {'registered': REGISTERED_COMMIT, 'execution_origin': EXECUTION_ORIGIN, + 'tested': TESTED_PRODUCT}, 'evidence version roles') + return composed, {'role': role, 'actual_head': commit, 'actual_tree': actual_tree, + 'submission': submitted, 'main': main, 'existing_metadata': metadata, + 'manifest_sha256': manifest_digest(product), 'runner_paths': runner_paths, + 'tested_product': TESTED_PRODUCT} + + def function_bytes(data): """Top-level named func through next named func, preserving every raw byte. @@ -366,6 +495,10 @@ def boundary_conditions(path, function): def applicability(index, spec, source, head): + tested = index.get('versions', {}).get('tested') + require(isinstance(tested, str) and tree_entries(tested) and + product_manifest(tree_entries(tested)) == product_manifest(tree_entries(head)), + 'wrong tested revision') registered = spec['current_source_pins'] require(index.get('registered_pins') == registered, 'registered pin declaration') for path, digest in registered.items(): @@ -805,6 +938,148 @@ def verify_native(base): verified_conditions=['dependency_stop', 'recovery_completion', 'paused_midnight']) +def verify_shift_completion(base): + """C's first 18:00 transaction, never the paid-midnight 1442 sale.""" + for row in load(safe(base, 'SOURCE_MANIFEST.json'))['files']: + native_file(base, row['copy'], row) + require(native_applicability(base) == TESTED_PRODUCT, 'shift tested revision') + pids = native_ownership(base) + require([pids['actual-native/' + phase] for phase in NATIVE_RUNS['actual-native']] + == [77462, 77504, 77536], 'shift ended PID identity') + inputs = load(safe(base, 'actual-native/runner-input.json')) + expected = product_manifest(tree_entries(TESTED_PRODUCT)) + require(len(inputs['source_before']) == 1338 and + {p: {'bytes': v['bytes'], 'sha256': v['sha256']} for p, v in inputs['source_before'].items()} + == expected, 'shift complete native source') + prefix = 'actual-native/order-evidence/' + observations = load(safe(base, prefix + 'observations.json')) + labels = ['first-before-17-59', 'first-18-00-before-facility', 'first-18-00-after-facility', + 'first-18-00-after-staff', 'first-18-00-tick-complete', 'first-18-01-tick-complete'] + points = [] + previous_ledger = None + service_id = None + paid_rows = None + original_service = None + completed_service = None + for i, label in enumerate(labels): + rows = [r for r in observations['observations'] if r['label'] == label] + require(len(rows) == 1, 'shift phase membership') + observation = rows[0] + snapshot = load(safe(base, prefix + label + '.json')) + minute = [1079, 1080, 1080, 1080, 1080, 1081][i] + require(snapshot['clock']['absolute_minute'] == observation['minute'] == minute, 'shift phase clock') + state = snapshot['systems']['runtime'] + service = observation['service'] + if i == 0: + service_id = service['service_id'] + original_service = service + identity = ('service_id', 'request_id', 'facility_id', 'guest_ids', 'inventory_reservations', + 'price_snapshot', 'duration_minutes', 'started_at', 'staff_ids') + require(all(service[k] == original_service[k] for k in identity), 'shift original service identity') + require(service['service_id'] == service_id and service['elapsed_minutes'] == (44 if i < 2 else 45) + and service['duration_minutes'] == 45 and service['price_snapshot'] == 25, + 'shift service identity/progress') + candidates = state['facilities']['service_history'] + [s for f in state['facilities']['instances'] + for s in f['active_services']] + require([s for s in candidates if s['service_id'] == service_id] == [service], 'shift raw service join') + require(observation['reservations'] == state['inventory'], 'shift inventory join') + reserved = [r for r in state['inventory']['reservations'] if r['reservation_id'] in service['inventory_reservations']] + require(reserved and len(reserved) == len(service['inventory_reservations']) and all( + r['requester_id'] == service['request_id'] and r['state'] == ('ACTIVE' if i < 2 else 'CONSUMED') + and r['terminal_at'] == (-1 if i < 2 else 1080) for r in reserved), 'shift reservation join') + day2 = ['p04.order.server.a', 'p04.order.server.b'] + require(observation['active_staff'] == (day2 if i < 3 else []), 'shift DAY2 before/after handover') + facility = [f for f in state['facilities']['instances'] if f['instance_id'] == service['facility_id']] + require(len(facility) == 1 and facility[0]['active_staff_ids'] == observation['active_staff'], + 'shift full-state active staff join') + require([w['staff']['staff_id'] for w in observation['workers']] == day2, 'shift worker identity set') + for worker in observation['workers']: + require(worker['staff'] in state['staff']['staff'], + 'shift worker raw join') + if i < 3: + require(worker['shift'] in state['staff']['shifts'] and worker['is_present'] is True and worker['shift']['kind'] == 'DAY' and + worker['shift']['end_at'] == 1080 and worker['shift']['actual_ended_at'] == -1, + 'shift original DAY worker present') + else: + ended = [r for r in state['staff']['shifts'] if r['staff_id'] == worker['staff']['staff_id']] + require(worker['shift'] == {} and worker['is_present'] is False and + worker['staff']['state'] == 'OFF_DUTY' and len(ended) == 1 and + ended[0]['actual_ended_at'] == ended[0]['end_at'] == 1080, + 'shift normal handover history') + ledger = state['economy']['entries'] + sale = [r for r in ledger if r['category'] == 'SERVICE_SALE'] + if i < 2: + require(sale == [] and observation['receipts'] == [], 'shift precompletion charge') + else: + require(service['state'] == 'COMPLETED' and service['completed_at'] == 1080 and len(sale) == 1, + 'shift single completion') + transaction = sale[0] + require(transaction['transaction_id'] == 'ledger_00000006' and transaction['amount'] == 25 and + transaction['absolute_minute'] == 1080 and transaction['source_entity_id'] == service['facility_id'] + and transaction['credit_account'] == 'GUEST_WALLET:' + service['guest_ids'][0], 'shift sale identity') + receipts = receipt_rows(observation['receipts']) + require(receipts == [r for r in ledger if r['transaction_id'] == 'ledger_00000006'], 'shift receipt raw join') + require([r for r in state['economy']['idempotency'] if r['key'] == 'service.payment.' + service_id] + == [{'key': 'service.payment.' + service_id, 'transaction_id': 'ledger_00000006'}], + 'shift idempotency join') + if i == 2: + completed_service = service + paid_rows = observation['receipts'] + require(paid_rows == observations['first_receipts'] and paid_rows[0]['staff_at_receipt'] == day2, + 'shift receipt precedes handover') + else: + require(service == completed_service and ledger == previous_ledger and + observation['receipts'] == paid_rows, 'shift extra charge/completion') + previous_ledger = ledger + points.append(label) + return {'verified_conditions': ['shift_completion'], 'phases': points, 'transaction_id': 'ledger_00000006'} + + +def combine_native_series(directory, native): + """Independent R composition; rerun raw checks, never trust condition scalars. + + The C verifier and its fixtures remain unchanged. This function does not + establish candidate applicability, and cannot grant a successful whole gate. + """ + import importlib.util + module_path = ROOT / DOC_SCOPE / 'p04_native_series.py' + module_spec = importlib.util.spec_from_file_location('p04_series', module_path) + series = importlib.util.module_from_spec(module_spec) + module_spec.loader.exec_module(series) + c_base = directory / 'audit-support/native-first-current' + require(native == verify_native(c_base), 'C result must be recomputed from raw') + base = directory / 'audit-support/p04-raw-collect-r20' + manifest_path = safe(base, 'MANIFEST.json') + manifest = load(manifest_path) + require(manifest['protocol'] == 'p04-native-series-collection-v1' and + manifest['relocation_manifest_sha256'] == series.MANIFEST_SHA256, + 'series relocation binding') + require(set(manifest['series']) == set(series.RUNS), 'series manifest membership') + shift = verify_shift_completion(c_base) + verified = set(native['verified_conditions']) | set(shift['verified_conditions']) + bindings = {} + for name in series.RUNS: + binding = manifest['series'][name] + path = safe(base, binding['path']) + require(path.name == name + '.json' and sha(path) == binding['sha256'] and + path.stat().st_size == binding['bytes'] and + binding['relocation_manifest_sha256'] == series.MANIFEST_SHA256, 'series manifest SHA binding') + recorded = load(path) + require(recorded['verifier_sha256'] == sha(module_path), 'series verifier changed since collection') + actual = getattr(series, 'verify_' + name)(directory) + require({k: v for k, v in recorded.items() if k != 'verifier_sha256'} == actual, + 'series raw result/reference drift') + verified.update(actual['verified_conditions']) + bindings[name] = binding['sha256'] + return {'verified_conditions': sorted(verified), 'series_manifest_sha256': sha(manifest_path), + 'C_source_manifest_sha256': sha(safe(c_base, 'SOURCE_MANIFEST.json')), + 'C_provenance_sha256': sha(safe(c_base, 'OWNERSHIP_RAW_PROVENANCE.json')), + 'series_bindings': bindings, 'relocation_manifest_sha256': series.MANIFEST_SHA256, + 'missing_conditions': sorted((set(REQUIRED) | {'EV'}) - verified), + 'nativeWindows': {name: 'pending' for name in sorted(set(REQUIRED) | {'EV'})}, + 'stage_A': 'candidate binding stage A pending', 'formal_acceptance': False} + + def verify(directory): # Contract hashes the supplied spec string, while the checked-in JSON has # one final LF. Accept only that exact representation difference. @@ -812,14 +1087,23 @@ def verify(directory): require(sha(SPEC) == SPEC_HASH or (spec_bytes.endswith(b'\n') and hashlib.sha256(spec_bytes[:-1]).hexdigest() == SPEC_HASH), 'dispatch spec pin mismatch') spec = load(SPEC) - evidence = directory / 'p04-evidence' + evidence = Path(os.environ.get('P04_EVIDENCE_DIR', str(directory / 'p04-evidence'))).absolute() require(not evidence.is_symlink(), 'symlink evidence directory') index = load(safe(evidence, 'index.json')) - require(type(index.get('schema_version')) is int and index['schema_version'] == 1, 'schema_version') + require(type(index.get('schema_version')) is int and index['schema_version'] in (1, 2), 'schema_version') require(index.get('task_id') == 'P04-01' and index.get('formal_acceptance') is False, 'task/acceptance') - checker_commit = git_bytes('rev-parse', 'HEAD').decode().strip() - require(index.get('candidate_commit') == checker_commit, 'checker invocation commit mismatch') - head, target_result = verify_product_target(index, directory, checker_commit) + head, target_result = verify_roles(index, directory) + if index['schema_version'] == 2: + import importlib.util + module_path = ROOT / DOC_SCOPE / 'build_p04_index.py' + module_spec = importlib.util.spec_from_file_location('p04_index', module_path) + builder = importlib.util.module_from_spec(module_spec) + module_spec.loader.exec_module(builder) + expected = builder.payload(directory, sys.modules[__name__]) + require(index == expected, 'forged index/raw mapping differs from recomputation') + require(not index['raw_conditions']['missing_conditions'], 'uncollected original boundaries: ' + + ', '.join(index['raw_conditions']['missing_conditions'])) + return target_result source = index.get('source_map') applicability(index, spec, source, head) oldmap = load(ROOT / 'docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/historical-source-map.json') @@ -903,23 +1187,37 @@ def verify(directory): require(native['production_commit'] == target_result['tested_product'], 'native production role mismatch') # This adapter covers only the supplied dependency/formal series. Never # promote it to the missing original scenarios through scalar booleans. - missing = (set(REQUIRED) | {'EV'}) - set(native['verified_conditions']) + combined = combine_native_series(directory, native) + missing = set(combined['missing_conditions']) require(not missing, 'uncollected original boundaries: ' + ', '.join(sorted(missing)) + '; formal_original_credit=0') + return target_result def main(): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--verify-only', action='store_true', required=True) - parser.parse_args() + mode = parser.add_mutually_exclusive_group(required=True) + mode.add_argument('--verify-only', action='store_true') + mode.add_argument('--verify-series-only', action='store_true', help='Read-only Stage R composition; always leaves Stage A pending') + args = parser.parse_args() try: require(bool(os.environ.get('BUILD_OUTPUT_DIR')), 'BUILD_OUTPUT_DIR required') + if args.verify_series_only: + directory = Path(os.environ['BUILD_OUTPUT_DIR']).absolute() + native = verify_native(directory / 'audit-support/native-first-current') + result = combine_native_series(directory, native) + result.update(task_id='P04-01', machine_verified=False, + error='candidate binding stage A pending; uncollected original boundaries: ' + + ', '.join(result['missing_conditions'])) + print(json.dumps(result, ensure_ascii=False, sort_keys=True)) + return 1 with isolated_objects(): - verify(Path(os.environ['BUILD_OUTPUT_DIR']).resolve()) + result = verify(Path(os.environ['BUILD_OUTPUT_DIR']).resolve()) except (ValueError, OSError, KeyError, TypeError, IndexError, AttributeError, subprocess.SubprocessError) as exc: print(json.dumps({'task_id': 'P04-01', 'machine_verified': False, 'formal_acceptance': False, 'error': str(exc)}, ensure_ascii=False)) return 1 - print(json.dumps({'task_id': 'P04-01', 'machine_verified': True, 'formal_acceptance': False})) + print(json.dumps({'task_id': 'P04-01', 'machine_verified': True, 'formal_acceptance': False, + 'role': result['role'], 'actual_head': result['actual_head']})) return 0