diff --git a/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json b/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json index 9438e65..6572835 100644 --- a/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json +++ b/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json @@ -3668,6 +3668,17 @@ "text": "\tprint(\"TOMARI_FLOOR_TROUBLE checks=%d failures=%d\" % [checks,failures])" } ] + }, + { + "test": "tests/s1/test_p08_combined_migration.gd", + "template": "P08 combined migration: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "9284a8cfb478b4c8d69f6d8166eec6c41107e3902029289b3c0e9ba48c370855", + "authority": { + "path": "docs/spec/acceptance/P08-02.json", + "scope": "new standalone mixed suite; native execution pending" + } } ], "unknown_roster_suite_policy": "refuse, explicitly review its source/schema before accepting", diff --git a/docs/plans/original-completion-2026-10-02/execution/p08-02/CONDITION_MATRIX.md b/docs/plans/original-completion-2026-10-02/execution/p08-02/CONDITION_MATRIX.md new file mode 100644 index 0000000..c53338d --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p08-02/CONDITION_MATRIX.md @@ -0,0 +1,44 @@ +# P08-02 retained implementation condition matrix + +Status: submitted for Root verification; original P08-02 acceptance remains pending. +Frozen spec: `docs/spec/acceptance/P08-02.json`, stripped UTF-8 SHA-256 +`19f02bb7912a4d7354e565186f02366fc1d4dd698468d26142e8204fd14ebfb0`. +The raw file has a trailing newline. The six rows are facets of one existing unit, not six units. + +All rows use `tests/fixtures/p08-02/mixed_world.gd` through `writer.gd` and +`reader.gd`, and the standalone `tests/s1/test_p08_combined_migration.gd`. +Driver phases are `off-positive`, `off-refusal`, `on-positive`, `on-refusal`, +each with `-writer` then `-reader`; import/parse precede them. + +| Required condition | Retained fixture/test coverage | Driver phase | Checker rule (`tools/check_p08_migration_evidence.py`) | Negative mutation (`tools/test_p08_migration_evidence.py`) | Pending evidence | +|---|---|---|---|---|---| +| mixed_construction | `setup`: paid zero-progress old-size premium job, progressing premium job, completed dorm in the same save; `run`: full progress/payment/reservation/completion fields | All four pairs; positive applies batch, refusal continues old shape | `semantics` checks starting/progressing paid jobs, completed dorm and diagnostic budget; `case_gate` permits only explicit typed geometry deltas and requires four-cell positive jobs | `missing_start_stage`, `resource_delta_mismatch`, `partial_batch`; unapproved typed changes are compared without dropping fields | Native runtime/save and distinct-PID continuation; normal OS flow; Root final verification pending | +| occupancy_change | `invariant`: restore validation of ownership/support; migrated occupied/reserved cells captured in whole snapshots | All four pairs | `semantics`: disjoint cells, exact construction/world ownership and operational facility cells; `GEOMETRY` finite allowlist | `orphan_occupancy` | Native added/released cells and support validation; OS model/formal records; Root review pending | +| actual_actor_positions | Mid-route walker, fractional credit, real staff/rescue/cargo and EV positions; no route changes approved | All four pairs, checkpoint and minutes 001–160 | `typed_differences` forbids actor/credit/route/money deltas; full typed and raw Variant writer/reader equality | `duplicate_actor`, `unequal_cross_pid` | Native actual positions/credit/routes and cross-PID equality; OS state captures; Root review pending | +| combined_rescue_logistics_ev | `setup`: actual in-flight rescue, purchased carried food with original requests, four EV boarded plus one waiting; `run`: patient treated, original cargo completed, each EV arrival once | All four pairs and uncapped events | `semantics`: IDs, endpoints, reservations and EV actors; `case_gate`: final patient health, original cargo completion and exactly one arrival per original EV request | `lost_patient`, `lost_cargo`, `lost_ev` | Native coherent same-save mixed state and continuation; OS save/model attribution; Root review pending | +| no_overlap_orphan_loss | Whole typed snapshots/raw Variant files and uncapped events; runtime/cargo invariants | ON and OFF positive/refusal pairs | Ordered four-case roster, actual mode, distinct lifecycle PIDs, strict markers, all 162 labels compared, semantic ownership/reference checks | `duplicate_actor`, `orphan_occupancy`, `unequal_cross_pid`, `missing_on`, `missing_phase`, `duplicate_pid`, `wrong_source`, `stale_source` | Native ON/OFF and separate writer/reader PIDs; normal-menu OS proof; final regression and Root verification pending | +| unsolvable_preservation | Real pre-existing migration backup, late maintenance obstruction at dorm, independently migratable first job; refusal rollback and unchanged live/formal/backup | `off-refusal`, `on-refusal` writer/reader | Live equality; original equals refusal checkpoint; before/after member hash maps equal; raw four-member original/result/backup hashes bound | `partial_batch`, `overwritten_original`, `overwritten_backup` | Native late refusal and separate-PID original continuation; OS cancel/changed-source preservation; Root review pending | + +The standalone suite executes both roles in one process and does not establish distinct-PID proof. +Only the Root collector launches separate phases through existing `execute_phase`/owned-outer +and fresh guarded profiles. Exactly one public suite marker exists: +`P08 combined migration: checks, failures`. +The driver privately owns the writer/reader marker templates; no second public entry is added. + +Local Python tests are synthetic checker fixtures, including synthetic lifecycle/OS/regression +records; their success establishes checker reachability and rejection tests only. +Native Godot/OS execution, normal production menu preview/cancel/confirm/changed-source rejection, +final full regression, final submitted reviewer-packet measurement, applicable Windows coverage, +independent Opus review, integration/current verification and ledger closure remain Root conditions. +Controlled old-save setup must be labelled controlled, never natural production. +Checker integrity success cannot close the original unit. No production defect or product fix +was demonstrated by the authorized local checks; retained code and marker contract are reused. + +Repair r04: seed 42588; existing definition-derived construction total gold 7110 / +materials 88, signing 150, food eight units 24. Initial gold 5000 / materials 50 +and previous added resources zero leave a deficit of gold 2284 / materials 38. +The controlled fixture records and supplies only this derived deficit. +Historical full metadata is now referenced by +`implementation-historical-6c7c724.json`: both fixed Git versions were SHA-verified +before replacement. Root's verified gzip roundtrip SHA is provenance only; +no external archive is a checker/runtime dependency. Historical failure logs remain preserved. diff --git a/docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r03.json b/docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r03.json new file mode 100644 index 0000000..478cd5f --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r03.json @@ -0,0 +1,101 @@ +{ + "classification": "Root coordinator supplied repair input, not worker implementation or acceptance proof", + "measured_UIDs": { + "tests/fixtures/p08-02/mixed_world.gd.uid": { + "text": "uid://dg5n4yme6rk4p\n", + "raw_sha256": "d7549f933b74f5e807cd2d3b3adcdd083af4a3bd331614314ff7bfd7b0903f31" + }, + "tests/fixtures/p08-02/reader.gd.uid": { + "text": "uid://741t47oxl3ct\n", + "raw_sha256": "3e68ce192d5770e3823df9699c5c99ebb618fe0a480b4d56913624941900b08a" + }, + "tests/fixtures/p08-02/writer.gd.uid": { + "text": "uid://csw2850g6gjct\n", + "raw_sha256": "8f150654767de0c66329d7d9e84d306c4fa5a65c918e04fb520b626b39f68334" + } + }, + "replacement_example": { + "classification": "Synthetic machine-readable complete index from retained Proof; diagnostic ONLY, never native/OS/regression acceptance evidence.", + "construction": "tools/test_p08_migration_evidence.py Proof creates all linked bytes and reachable whole-check positive. Embedded documents illustrate receipts/lifecycle/OS/regression/typed records. Digests belong to the disposable synthetic fixture, not delivered acceptance artifacts; reproduce with Proof in a worktree-local disposable directory.", + "complete_example_generator": { + "source": "tools/test_p08_migration_evidence.py", + "source_raw_sha256": "9924f4cb4f55b15caf9f9733d91f5095553abcf2861e4b335755531480b534a9", + "class": "Proof", + "constructor": "Proof(Path(fresh_temporary_directory))", + "project_root_binding": "module ROOT resolved from unchanged generator inside this exact candidate source tree", + "generated_full_index": "proof.index", + "generated_representative_documents": "{path: json.loads((proof.path / path).read_text()) for path in representative_artifact_document_paths}", + "native_or_os_runs": 0 + }, + "index_top_level_fields": [ + "schema", + "commit", + "source_map", + "spec_sha256", + "marker_contract_sha256", + "inputs", + "cases", + "conditions", + "artifacts", + "acceptance", + "collection_error", + "phases", + "os_proof", + "regression" + ], + "representative_artifact_document_paths": [ + "runs/off-positive-writer/off-positive-writer-receipt.json", + "runs/off-positive-reader/off-positive-reader-receipt.json", + "runs/off-positive-writer/off-positive-writer/started.json", + "runs/off-positive-writer/off-positive-writer/ended.json", + "runs/off-positive-writer/off-positive-writer/outer-owned-finally.json", + "os.json", + "os/preview/input.json", + "regression.json", + "regression/input.json", + "regression/ended.json", + "pairs/off-positive/identity.json", + "pairs/off-positive/writer-process.json", + "pairs/off-positive/before-members.json" + ], + "fixture_config_inputs": { + "fixture_sources": { + "tests/s1/test_p08_combined_migration.gd": "9284a8cfb478b4c8d69f6d8166eec6c41107e3902029289b3c0e9ba48c370855", + "tests/s1/test_p08_combined_migration.gd.uid": "8c3a70d0ce619b8cb98dc003eb6c53a31f167a686179a3ffc105f7cd4d856da5", + "tests/fixtures/p08-02/README.md": "bc76975e49e7761c2a3540a1e907ab2215ec315653a1d87df6919d1cebd779e0", + "tests/fixtures/p08-02/mixed_world.gd": "889bc895888b8c6ad50659a3b23a3ee45e9edf8dde9168f5560792c292070d26", + "tests/fixtures/p08-02/reader.gd": "a760d0e45979d747dbd1f8710033bdce13287b00eb879d947d8378ff920ed1be", + "tests/fixtures/p08-02/writer.gd": "1a918b5509773b4b07a27d47ee44583b0eb5288138f7bf896088a7913773c260", + "tools/run_p08_mixed_migration.py": "7e86737e6354a4734726ba8d3171ad5f58d6c0380aba5a0fa3c57879d580e579", + "tools/check_p08_migration_evidence.py": "8d697c8e9a606bcff6a0a8c6ec55e0e9c434ca9682b7ce6915b5bcb1ea4ff3fd", + "tools/test_p08_migration_evidence.py": "9924f4cb4f55b15caf9f9733d91f5095553abcf2861e4b335755531480b534a9" + }, + "native_runner_input_shape": { + "protocol": "p08-mixed-v1", + "commit": "", + "selected_source_map": "", + "engine": "", + "engine_sha256": "", + "owner_pid": "", + "default_player": "", + "diagnostic_seed": 42588 + }, + "case_environment": { + "P08_PAIR_ROOT": "", + "P08_OPERATIONS": "off|on", + "P08_CASE": "positive|refusal" + }, + "timeouts": "unchanged: collection 900s, phase maximum 180s", + "formal_validation": "Root copies actual evidence into allocated build directory; --verify-only reads BUILD_OUTPUT_DIR/p08-evidence/index.json" + }, + "exact_reconstruction_verification": { + "entire_index_equal": true, + "entire_representative_documents_equal": true, + "index_canonical_sha256": "456c67833dc6d6d905a1c0543f7a3e6b1ead715b8e88a5fe816dbcb77072efc0", + "representative_canonical_sha256": "5727797a0ec11363d1463406be4f1979e279a9c77fff02c284ff7fff60bc32e6", + "canonical_encoding": "json.dumps(value,sort_keys=True,ensure_ascii=False).encode()" + }, + "purpose": "Lossless complete synthetic example regeneration from retained code, not a real native/OS acceptance index. Explicit index_schema above remains authoritative. Root proved equality of all original fields before this proposal. Full prior implementation preserved in commit6c7c7244 and byte-verified gzip evidence." + }, + "instructions": "Read this compact input first. Do not cat the entire 1MB old example into model context. Parse implementation.json with stdlib, replace example field, keep all other fields/schema/source logic. Add exact supplied UIDs; update outputs; after source map changes recompute current synthetic example digests with unchanged Proof class. Historical exact old reconstruction remains tied to6c. This input is within allowed scope, not an external path." +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r04.json b/docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r04.json new file mode 100644 index 0000000..3a6e324 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r04.json @@ -0,0 +1,306 @@ +{ + "purpose": "Same retained candidate; diagnose checker against real native outputs. Original native bytes and candidate preserved; not game acceptance.", + "actual_candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9", + "all8_native_phases_pass": true, + "defects": [ + "actual Vector3i constructor scalar format rejected by decoder", + "logistics lazy destination inventory treated as orphan although canonical live facility exists", + "RESERVED queued request has no assigned carrier by domain contract", + "support revision is global world revision epoch, not independent +1 counter; actual changed142→150 alongside world144→150 after occupancy rewrites" + ], + "actual_readonly_diagnostics": { + "ACTUAL_NATIVE_CHECKER.json": { + "candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9", + "elapsed_seconds": 16.212463750038296, + "actual_readonly_checker_deficits": [ + "OS proof: unsafe artifact path", + "case off-positive: invalid literal for int() with base 10: 'Vector3i(0'", + "case off-refusal: invalid literal for int() with base 10: 'Vector3i(0'", + "case on-positive: invalid literal for int() with base 10: 'Vector3i(6'", + "case on-refusal: invalid literal for int() with base 10: 'Vector3i(-12'", + "final regression: unsafe artifact path" + ], + "full_game_acceptance": false, + "original_unit_completed": false, + "index": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p08-02-native-mixed-r05/index.json" + }, + "VECTOR_DECODER_READONLY_DIAGNOSTIC.json": { + "candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9", + "scope": "Root readonly decoder prototype diagnostic; no candidate code or actual evidence altered; no acceptance", + "observations": [ + { + "case": "off-positive", + "jobs": [ + { + "id": "construction_00000015", + "state": "BUILDING", + "progress": 1266 + }, + { + "id": "construction_00000016", + "state": "PLANNED", + "progress": 0 + } + ], + "starting_job": "construction_00000016" + }, + { + "case": "off-refusal", + "jobs": [ + { + "id": "construction_00000015", + "state": "BUILDING", + "progress": 1266 + }, + { + "id": "construction_00000016", + "state": "PLANNED", + "progress": 0 + } + ], + "starting_job": "construction_00000016" + }, + { + "case": "on-positive", + "jobs": [ + { + "id": "construction_00000015", + "state": "BUILDING", + "progress": 1266 + }, + { + "id": "construction_00000016", + "state": "PLANNED", + "progress": 0 + } + ], + "starting_job": "construction_00000016" + }, + { + "case": "on-refusal", + "jobs": [ + { + "id": "construction_00000015", + "state": "BUILDING", + "progress": 1266 + }, + { + "id": "construction_00000016", + "state": "PLANNED", + "progress": 0 + } + ], + "starting_job": "construction_00000016" + } + ], + "remaining_deficits": [ + "OS proof: unsafe artifact path", + "case off-positive: orphan cargo endpoint", + "case off-refusal: orphan cargo endpoint", + "case on-positive: orphan cargo endpoint", + "case on-refusal: orphan cargo endpoint", + "final regression: unsafe artifact path" + ], + "elapsed_seconds": 16.21374141704291 + }, + "CARGO_ENDPOINT_READONLY_DIAGNOSTIC.json": [ + { + "first_label": "original", + "original_requests": [ + "supply.logistics_request.000000000001", + "supply.logistics_request.000000000002" + ], + "active_inventory_ids": [ + "cargo:staff_gina", + "facility.inventory:facility_00000005" + ], + "requests": [ + { + "request_id": "supply.logistics_request.000000000001", + "source_inventory_id": "facility.inventory:facility_00000005", + "destination_inventory_id": "facility.inventory:facility_00000006", + "item_requirements": { + "ingredient/raw_food": 4 + }, + "priority": 50, + "created_at": 728, + "deadline": -1, + "total_weight": 4, + "reserved_stack_ids": [ + "stack_00000001" + ], + "reservation_ids": [ + "reservation_00000001" + ], + "assigned_carrier_id": "staff_gina", + "state": "DELIVERING", + "retry_count": 0, + "state_reason": "", + "state_changed_at": 735 + }, + { + "request_id": "supply.logistics_request.000000000002", + "source_inventory_id": "facility.inventory:facility_00000005", + "destination_inventory_id": "facility.inventory:facility_00000006", + "item_requirements": { + "ingredient/raw_food": 4 + }, + "priority": 50, + "created_at": 729, + "deadline": -1, + "total_weight": 4, + "reserved_stack_ids": [ + "stack_00000001" + ], + "reservation_ids": [ + "reservation_00000002" + ], + "assigned_carrier_id": "", + "state": "RESERVED", + "retry_count": 0, + "state_reason": "NO_AVAILABLE_PORTER_OR_ROUTE", + "state_changed_at": 729 + } + ] + } + ], + "CARGO_RULE_READONLY_DIAGNOSTIC.json": { + "candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9", + "scope": "Root readonly prototype only. Candidate/checker/evidence files unchanged, no formal acceptance.", + "basis": "Authoritative SupplyLogisticsBridge.validate_crosslinks lines508-554 resolves facility inventory endpoints by facility ownership and allows unassigned RESERVED request with valid reservation; destination inventory materializes upon delivery.", + "remaining_deficits": [ + "OS proof: unsafe artifact path", + "final regression: unsafe artifact path", + "unapproved typed migration delta off-positive/systems/runtime/world/revisions/support", + "unapproved typed migration delta on-positive/systems/runtime/world/revisions/support" + ], + "elapsed_seconds": 78.40071395831183 + }, + "ACTUAL_WORLD_REVISION_DELTAS.json": [ + { + "case": "off-positive", + "original_world_revisions": { + "world": 144, + "occupancy": 144, + "walkability": 99, + "excavation": 0, + "support": 142 + }, + "checkpoint_world_revisions": { + "world": 150, + "occupancy": 150, + "walkability": 99, + "excavation": 0, + "support": 150 + } + }, + { + "case": "on-positive", + "original_world_revisions": { + "world": 144, + "occupancy": 144, + "walkability": 99, + "excavation": 0, + "support": 142 + }, + "checkpoint_world_revisions": { + "world": 150, + "occupancy": 150, + "walkability": 99, + "excavation": 0, + "support": 150 + } + }, + { + "case": "off-refusal", + "original_world_revisions": { + "world": 144, + "occupancy": 144, + "walkability": 99, + "excavation": 0, + "support": 142 + }, + "checkpoint_world_revisions": { + "world": 144, + "occupancy": 144, + "walkability": 99, + "excavation": 0, + "support": 142 + } + }, + { + "case": "on-refusal", + "original_world_revisions": { + "world": 144, + "occupancy": 144, + "walkability": 99, + "excavation": 0, + "support": 142 + }, + "checkpoint_world_revisions": { + "world": 144, + "occupancy": 144, + "walkability": 99, + "excavation": 0, + "support": 142 + } + } + ] + }, + "source_basis": { + "scripts/supply/supply_logistics_bridge.gd:508": [ + "static func validate_crosslinks(requests: Dictionary, staff: StaffService, inventory: InventoryService, facilities: FacilityService) -> bool:", + "\tvar carrier_owners: Dictionary = {}", + "\tvar active_task_ids: Dictionary = {}", + "\tfor id: StringName in _request_ids(requests):", + "\t\tvar request: LogisticsRequest = requests[id]", + "\t\tvar source := _facility_for_inventory(facilities, request.source_inventory_id)", + "\t\tvar destination := _facility_for_inventory(facilities, request.destination_inventory_id)", + "\t\tif not String(request.source_inventory_id).begins_with(\"facility.inventory:\") or not String(request.destination_inventory_id).begins_with(\"facility.inventory:\"):", + "\t\t\treturn false", + "\t\tif not request.is_terminal() and (source == null or destination == null):", + "\t\t\treturn false", + "\t\tvar task := _active_task(staff, id)", + "\t\tif request.state in [LogisticsRequest.State.RESERVED, LogisticsRequest.State.BLOCKED] or request.is_terminal():", + "\t\t\tif task != null:", + "\t\t\t\treturn false", + "\t\t\tif request.state == LogisticsRequest.State.RESERVED and not _reservation_quantities_valid(request, inventory, true):", + "\t\t\t\treturn false", + "\t\t\tcontinue", + "\t\tif request.state not in [LogisticsRequest.State.ASSIGNED, LogisticsRequest.State.PICKING_UP, LogisticsRequest.State.DELIVERING]:", + "\t\t\treturn false", + "\t\tvar worker := staff.get_staff(request.assigned_carrier_id)", + "\t\tif worker == null or worker.role_id != &\"porter\" or carrier_owners.has(worker.staff_id) \\", + "\t\t\tor task == null or task.assigned_staff_id != worker.staff_id or worker.current_task_id != task.task_id \\" + ], + "scripts/world/world_service.gd:652": [ + "func _mark_dirty(", + "\tcells: Array,", + "\toccupancy_changed: bool,", + "\twalkability_changed: bool,", + "\texcavation_changed: bool,", + "\tsupport_changed: bool", + ") -> void:", + "\trevision += 1", + "\tif occupancy_changed:", + "\t\toccupancy_revision = revision", + "\tif walkability_changed:", + "\t\twalkability_revision = revision", + "\tif excavation_changed:", + "\t\texcavation_revision = revision", + "\tif support_changed:", + "\t\tsupport_revision = revision", + "\tfor cell: Variant in cells:", + "\t\tif cell is Vector3i and is_in_bounds(cell):", + "\t\t\t_dirty_cells[cell] = true" + ] + }, + "instructions": [ + "Read only this compact file first; no broad history or full evidence dump.", + "Do not modify fixture/runtime/collector/native semantics already passed. Fix checker decoding/crosslink/counter checks, realistic targeted tests and metadata together. Original6conditions and30/88 unchanged.", + "Native bytes must stay immutable. Decide whether tested runtime inputs exactly match final after tooling-only changes; do not silently re-label old tested SHA. If final validator requires exact full source-map, execute final native once after all tooling fixes consolidated.", + "Freeze all source edits before running checker tests; prior README mutation during tests caused preventable stale proof retry.", + "Root external native collector should check first-writer actual scalar/crosslink semantics before running all other phases to avoid repeated whole collection.", + "Independent review and OS/final regression remain mandatory." + ] +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r07.json b/docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r07.json new file mode 100644 index 0000000..e0ba917 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r07.json @@ -0,0 +1,341 @@ +{ + "purpose": "Owner coordinator preserved test-data input; not worker implementation or acceptance waiver", + "submitted_failed": "d1c7ce27d21a77be87081e12d20e7c8dd6ffa353", + "actual_review": { + "task_id": "P08-02", + "commit": "d1c7ce27d21a77be87081e12d20e7c8dd6ffa353", + "status": "changes_requested", + "summary": "The evidence Root collected for the six facets is substantial, and I checked it independently. The p08-evidence validator ran check_p08_migration_evidence.py --verify-only and reported integrity \"verified\" with no deficits. The index is bound to commit d1c7ce2 with collection_error=null. There are four native ON/OFF positive and refusal pairs, each with separate writer and reader receipts. The normal-menu OS proof uses real native mouse input through the production PopupMenu id_pressed handler and covers preview, cancel, confirm and changed-source rejection. A full final regression is recorded (174 suites per Root's notes, all PASS).\n\nAt the submitted commit, though, a required implementation output no longer runs. tools/test_p08_migration_evidence.py reads R04['actual_readonly_diagnostics'] when the module loads. This commit cut ROOT_REPAIR_INPUT-r04.json down to a short summary, and that key is gone, so the checker test suite fails with KeyError before any test executes. The spec requires meaningful whole-checker positive and negative tests (duplicate actor, orphan occupancy, patient/cargo/EV loss, partial batch, overwritten original/backup, unequal cross-PID state, missing ON, wrong/stale source, fake OS signal). Those can no longer be run, which in effect disables the tests.\n\nOn top of that, implementation.json reports \"4 tests in 61.547s, PASS\" for a run at an earlier commit and presents it as the current local check. That result does not hold for the submitted sources. Because of this, I can't call the P08-02 acceptance satisfied, and the status is changes_requested.", + "acceptance_ids": [ + "P08-02" + ], + "findings": [ + { + "severity": "high", + "message": "tools/test_p08_migration_evidence.py:17-20 loads docs/plans/.../p08-02/ROOT_REPAIR_INPUT-r04.json and indexes R04['actual_readonly_diagnostics'] (plus CARGO_ENDPOINT_READONLY_DIAGNOSTIC.json and ACTUAL_WORLD_REVISION_DELTAS.json) when the module loads. This commit shortened that file to a summary with no 'actual_readonly_diagnostics' key; a Grep of the checkout finds the key only in the test file. As a result the module fails with KeyError on import, and none of the required checker positives or negatives can run at the submitted SHA. That breaks the required output and in effect disables the tests (rule: do not delete or disable tests). Fix: put the fixture data the test needs back into an allowed path, ideally a dedicated file under tests/fixtures/p08-02/, and point the test there instead of the Root handoff file. Then re-run python3 -B tools/test_p08_migration_evidence.py at the final SHA." + }, + { + "severity": "medium", + "message": "implementation.json local_checks.checker_tests reports 'Recorded Astra run 7cd74f00…: 4 tests in 61.547s, PASS' and lists tools/test_p08_migration_evidence.py with its current SHA. That run happened against an earlier version of ROOT_REPAIR_INPUT-r04.json, and the test cannot run against the submitted one. The current-state test claim is therefore stale. It should either be re-recorded at the final SHA or explicitly marked as not applicable to the current sources." + }, + { + "severity": "low", + "message": "tests/s1/test_p08_combined_migration.gd: when a role fails, `break` leaves only the writer/reader loop, so the remaining operations/refusal combinations still run on top of a failed state. The final marker still reports failures correctly, so this is a minor diagnostic issue rather than a false pass." + } + ] + }, + "root_reproduction": "python3 -B tools/test_p08_migration_evidence.py => KeyError actual_readonly_diagnostics before tests execute", + "preserved_original_path": "docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r04.json", + "preserved_at_git_revision": "1545bd42", + "preserved_raw_sha256": "23ef44a0775ca53f1f67d6d64fcc96cd33eae935478768e9ce9431796eaa12ef", + "preserved_raw_utf8": "{\n \"purpose\": \"Same retained candidate; diagnose checker against real native outputs. Original native bytes and candidate preserved; not game acceptance.\",\n \"actual_candidate\": \"592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9\",\n \"all8_native_phases_pass\": true,\n \"defects\": [\n \"actual Vector3i constructor scalar format rejected by decoder\",\n \"logistics lazy destination inventory treated as orphan although canonical live facility exists\",\n \"RESERVED queued request has no assigned carrier by domain contract\",\n \"support revision is global world revision epoch, not independent +1 counter; actual changed142→150 alongside world144→150 after occupancy rewrites\"\n ],\n \"actual_readonly_diagnostics\": {\n \"ACTUAL_NATIVE_CHECKER.json\": {\n \"candidate\": \"592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9\",\n \"elapsed_seconds\": 16.212463750038296,\n \"actual_readonly_checker_deficits\": [\n \"OS proof: unsafe artifact path\",\n \"case off-positive: invalid literal for int() with base 10: 'Vector3i(0'\",\n \"case off-refusal: invalid literal for int() with base 10: 'Vector3i(0'\",\n \"case on-positive: invalid literal for int() with base 10: 'Vector3i(6'\",\n \"case on-refusal: invalid literal for int() with base 10: 'Vector3i(-12'\",\n \"final regression: unsafe artifact path\"\n ],\n \"full_game_acceptance\": false,\n \"original_unit_completed\": false,\n \"index\": \"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p08-02-native-mixed-r05/index.json\"\n },\n \"VECTOR_DECODER_READONLY_DIAGNOSTIC.json\": {\n \"candidate\": \"592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9\",\n \"scope\": \"Root readonly decoder prototype diagnostic; no candidate code or actual evidence altered; no acceptance\",\n \"observations\": [\n {\n \"case\": \"off-positive\",\n \"jobs\": [\n {\n \"id\": \"construction_00000015\",\n \"state\": \"BUILDING\",\n \"progress\": 1266\n },\n {\n \"id\": \"construction_00000016\",\n \"state\": \"PLANNED\",\n \"progress\": 0\n }\n ],\n \"starting_job\": \"construction_00000016\"\n },\n {\n \"case\": \"off-refusal\",\n \"jobs\": [\n {\n \"id\": \"construction_00000015\",\n \"state\": \"BUILDING\",\n \"progress\": 1266\n },\n {\n \"id\": \"construction_00000016\",\n \"state\": \"PLANNED\",\n \"progress\": 0\n }\n ],\n \"starting_job\": \"construction_00000016\"\n },\n {\n \"case\": \"on-positive\",\n \"jobs\": [\n {\n \"id\": \"construction_00000015\",\n \"state\": \"BUILDING\",\n \"progress\": 1266\n },\n {\n \"id\": \"construction_00000016\",\n \"state\": \"PLANNED\",\n \"progress\": 0\n }\n ],\n \"starting_job\": \"construction_00000016\"\n },\n {\n \"case\": \"on-refusal\",\n \"jobs\": [\n {\n \"id\": \"construction_00000015\",\n \"state\": \"BUILDING\",\n \"progress\": 1266\n },\n {\n \"id\": \"construction_00000016\",\n \"state\": \"PLANNED\",\n \"progress\": 0\n }\n ],\n \"starting_job\": \"construction_00000016\"\n }\n ],\n \"remaining_deficits\": [\n \"OS proof: unsafe artifact path\",\n \"case off-positive: orphan cargo endpoint\",\n \"case off-refusal: orphan cargo endpoint\",\n \"case on-positive: orphan cargo endpoint\",\n \"case on-refusal: orphan cargo endpoint\",\n \"final regression: unsafe artifact path\"\n ],\n \"elapsed_seconds\": 16.21374141704291\n },\n \"CARGO_ENDPOINT_READONLY_DIAGNOSTIC.json\": [\n {\n \"first_label\": \"original\",\n \"original_requests\": [\n \"supply.logistics_request.000000000001\",\n \"supply.logistics_request.000000000002\"\n ],\n \"active_inventory_ids\": [\n \"cargo:staff_gina\",\n \"facility.inventory:facility_00000005\"\n ],\n \"requests\": [\n {\n \"request_id\": \"supply.logistics_request.000000000001\",\n \"source_inventory_id\": \"facility.inventory:facility_00000005\",\n \"destination_inventory_id\": \"facility.inventory:facility_00000006\",\n \"item_requirements\": {\n \"ingredient/raw_food\": 4\n },\n \"priority\": 50,\n \"created_at\": 728,\n \"deadline\": -1,\n \"total_weight\": 4,\n \"reserved_stack_ids\": [\n \"stack_00000001\"\n ],\n \"reservation_ids\": [\n \"reservation_00000001\"\n ],\n \"assigned_carrier_id\": \"staff_gina\",\n \"state\": \"DELIVERING\",\n \"retry_count\": 0,\n \"state_reason\": \"\",\n \"state_changed_at\": 735\n },\n {\n \"request_id\": \"supply.logistics_request.000000000002\",\n \"source_inventory_id\": \"facility.inventory:facility_00000005\",\n \"destination_inventory_id\": \"facility.inventory:facility_00000006\",\n \"item_requirements\": {\n \"ingredient/raw_food\": 4\n },\n \"priority\": 50,\n \"created_at\": 729,\n \"deadline\": -1,\n \"total_weight\": 4,\n \"reserved_stack_ids\": [\n \"stack_00000001\"\n ],\n \"reservation_ids\": [\n \"reservation_00000002\"\n ],\n \"assigned_carrier_id\": \"\",\n \"state\": \"RESERVED\",\n \"retry_count\": 0,\n \"state_reason\": \"NO_AVAILABLE_PORTER_OR_ROUTE\",\n \"state_changed_at\": 729\n }\n ]\n }\n ],\n \"CARGO_RULE_READONLY_DIAGNOSTIC.json\": {\n \"candidate\": \"592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9\",\n \"scope\": \"Root readonly prototype only. Candidate/checker/evidence files unchanged, no formal acceptance.\",\n \"basis\": \"Authoritative SupplyLogisticsBridge.validate_crosslinks lines508-554 resolves facility inventory endpoints by facility ownership and allows unassigned RESERVED request with valid reservation; destination inventory materializes upon delivery.\",\n \"remaining_deficits\": [\n \"OS proof: unsafe artifact path\",\n \"final regression: unsafe artifact path\",\n \"unapproved typed migration delta off-positive/systems/runtime/world/revisions/support\",\n \"unapproved typed migration delta on-positive/systems/runtime/world/revisions/support\"\n ],\n \"elapsed_seconds\": 78.40071395831183\n },\n \"ACTUAL_WORLD_REVISION_DELTAS.json\": [\n {\n \"case\": \"off-positive\",\n \"original_world_revisions\": {\n \"world\": 144,\n \"occupancy\": 144,\n \"walkability\": 99,\n \"excavation\": 0,\n \"support\": 142\n },\n \"checkpoint_world_revisions\": {\n \"world\": 150,\n \"occupancy\": 150,\n \"walkability\": 99,\n \"excavation\": 0,\n \"support\": 150\n }\n },\n {\n \"case\": \"on-positive\",\n \"original_world_revisions\": {\n \"world\": 144,\n \"occupancy\": 144,\n \"walkability\": 99,\n \"excavation\": 0,\n \"support\": 142\n },\n \"checkpoint_world_revisions\": {\n \"world\": 150,\n \"occupancy\": 150,\n \"walkability\": 99,\n \"excavation\": 0,\n \"support\": 150\n }\n },\n {\n \"case\": \"off-refusal\",\n \"original_world_revisions\": {\n \"world\": 144,\n \"occupancy\": 144,\n \"walkability\": 99,\n \"excavation\": 0,\n \"support\": 142\n },\n \"checkpoint_world_revisions\": {\n \"world\": 144,\n \"occupancy\": 144,\n \"walkability\": 99,\n \"excavation\": 0,\n \"support\": 142\n }\n },\n {\n \"case\": \"on-refusal\",\n \"original_world_revisions\": {\n \"world\": 144,\n \"occupancy\": 144,\n \"walkability\": 99,\n \"excavation\": 0,\n \"support\": 142\n },\n \"checkpoint_world_revisions\": {\n \"world\": 144,\n \"occupancy\": 144,\n \"walkability\": 99,\n \"excavation\": 0,\n \"support\": 142\n }\n }\n ]\n },\n \"source_basis\": {\n \"scripts/supply/supply_logistics_bridge.gd:508\": [\n \"static func validate_crosslinks(requests: Dictionary, staff: StaffService, inventory: InventoryService, facilities: FacilityService) -> bool:\",\n \"\\tvar carrier_owners: Dictionary = {}\",\n \"\\tvar active_task_ids: Dictionary = {}\",\n \"\\tfor id: StringName in _request_ids(requests):\",\n \"\\t\\tvar request: LogisticsRequest = requests[id]\",\n \"\\t\\tvar source := _facility_for_inventory(facilities, request.source_inventory_id)\",\n \"\\t\\tvar destination := _facility_for_inventory(facilities, request.destination_inventory_id)\",\n \"\\t\\tif not String(request.source_inventory_id).begins_with(\\\"facility.inventory:\\\") or not String(request.destination_inventory_id).begins_with(\\\"facility.inventory:\\\"):\",\n \"\\t\\t\\treturn false\",\n \"\\t\\tif not request.is_terminal() and (source == null or destination == null):\",\n \"\\t\\t\\treturn false\",\n \"\\t\\tvar task := _active_task(staff, id)\",\n \"\\t\\tif request.state in [LogisticsRequest.State.RESERVED, LogisticsRequest.State.BLOCKED] or request.is_terminal():\",\n \"\\t\\t\\tif task != null:\",\n \"\\t\\t\\t\\treturn false\",\n \"\\t\\t\\tif request.state == LogisticsRequest.State.RESERVED and not _reservation_quantities_valid(request, inventory, true):\",\n \"\\t\\t\\t\\treturn false\",\n \"\\t\\t\\tcontinue\",\n \"\\t\\tif request.state not in [LogisticsRequest.State.ASSIGNED, LogisticsRequest.State.PICKING_UP, LogisticsRequest.State.DELIVERING]:\",\n \"\\t\\t\\treturn false\",\n \"\\t\\tvar worker := staff.get_staff(request.assigned_carrier_id)\",\n \"\\t\\tif worker == null or worker.role_id != &\\\"porter\\\" or carrier_owners.has(worker.staff_id) \\\\\",\n \"\\t\\t\\tor task == null or task.assigned_staff_id != worker.staff_id or worker.current_task_id != task.task_id \\\\\"\n ],\n \"scripts/world/world_service.gd:652\": [\n \"func _mark_dirty(\",\n \"\\tcells: Array,\",\n \"\\toccupancy_changed: bool,\",\n \"\\twalkability_changed: bool,\",\n \"\\texcavation_changed: bool,\",\n \"\\tsupport_changed: bool\",\n \") -> void:\",\n \"\\trevision += 1\",\n \"\\tif occupancy_changed:\",\n \"\\t\\toccupancy_revision = revision\",\n \"\\tif walkability_changed:\",\n \"\\t\\twalkability_revision = revision\",\n \"\\tif excavation_changed:\",\n \"\\t\\texcavation_revision = revision\",\n \"\\tif support_changed:\",\n \"\\t\\tsupport_revision = revision\",\n \"\\tfor cell: Variant in cells:\",\n \"\\t\\tif cell is Vector3i and is_in_bounds(cell):\",\n \"\\t\\t\\t_dirty_cells[cell] = true\"\n ]\n },\n \"instructions\": [\n \"Read only this compact file first; no broad history or full evidence dump.\",\n \"Do not modify fixture/runtime/collector/native semantics already passed. Fix checker decoding/crosslink/counter checks, realistic targeted tests and metadata together. Original6conditions and30/88 unchanged.\",\n \"Native bytes must stay immutable. Decide whether tested runtime inputs exactly match final after tooling-only changes; do not silently re-label old tested SHA. If final validator requires exact full source-map, execute final native once after all tooling fixes consolidated.\",\n \"Freeze all source edits before running checker tests; prior README mutation during tests caused preventable stale proof retry.\",\n \"Root external native collector should check first-writer actual scalar/crosslink semantics before running all other phases to avoid repeated whole collection.\",\n \"Independent review and OS/final regression remain mandatory.\"\n ]\n}\n", + "preserved_original_document": { + "purpose": "Same retained candidate; diagnose checker against real native outputs. Original native bytes and candidate preserved; not game acceptance.", + "actual_candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9", + "all8_native_phases_pass": true, + "defects": [ + "actual Vector3i constructor scalar format rejected by decoder", + "logistics lazy destination inventory treated as orphan although canonical live facility exists", + "RESERVED queued request has no assigned carrier by domain contract", + "support revision is global world revision epoch, not independent +1 counter; actual changed142→150 alongside world144→150 after occupancy rewrites" + ], + "actual_readonly_diagnostics": { + "ACTUAL_NATIVE_CHECKER.json": { + "candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9", + "elapsed_seconds": 16.212463750038296, + "actual_readonly_checker_deficits": [ + "OS proof: unsafe artifact path", + "case off-positive: invalid literal for int() with base 10: 'Vector3i(0'", + "case off-refusal: invalid literal for int() with base 10: 'Vector3i(0'", + "case on-positive: invalid literal for int() with base 10: 'Vector3i(6'", + "case on-refusal: invalid literal for int() with base 10: 'Vector3i(-12'", + "final regression: unsafe artifact path" + ], + "full_game_acceptance": false, + "original_unit_completed": false, + "index": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p08-02-native-mixed-r05/index.json" + }, + "VECTOR_DECODER_READONLY_DIAGNOSTIC.json": { + "candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9", + "scope": "Root readonly decoder prototype diagnostic; no candidate code or actual evidence altered; no acceptance", + "observations": [ + { + "case": "off-positive", + "jobs": [ + { + "id": "construction_00000015", + "state": "BUILDING", + "progress": 1266 + }, + { + "id": "construction_00000016", + "state": "PLANNED", + "progress": 0 + } + ], + "starting_job": "construction_00000016" + }, + { + "case": "off-refusal", + "jobs": [ + { + "id": "construction_00000015", + "state": "BUILDING", + "progress": 1266 + }, + { + "id": "construction_00000016", + "state": "PLANNED", + "progress": 0 + } + ], + "starting_job": "construction_00000016" + }, + { + "case": "on-positive", + "jobs": [ + { + "id": "construction_00000015", + "state": "BUILDING", + "progress": 1266 + }, + { + "id": "construction_00000016", + "state": "PLANNED", + "progress": 0 + } + ], + "starting_job": "construction_00000016" + }, + { + "case": "on-refusal", + "jobs": [ + { + "id": "construction_00000015", + "state": "BUILDING", + "progress": 1266 + }, + { + "id": "construction_00000016", + "state": "PLANNED", + "progress": 0 + } + ], + "starting_job": "construction_00000016" + } + ], + "remaining_deficits": [ + "OS proof: unsafe artifact path", + "case off-positive: orphan cargo endpoint", + "case off-refusal: orphan cargo endpoint", + "case on-positive: orphan cargo endpoint", + "case on-refusal: orphan cargo endpoint", + "final regression: unsafe artifact path" + ], + "elapsed_seconds": 16.21374141704291 + }, + "CARGO_ENDPOINT_READONLY_DIAGNOSTIC.json": [ + { + "first_label": "original", + "original_requests": [ + "supply.logistics_request.000000000001", + "supply.logistics_request.000000000002" + ], + "active_inventory_ids": [ + "cargo:staff_gina", + "facility.inventory:facility_00000005" + ], + "requests": [ + { + "request_id": "supply.logistics_request.000000000001", + "source_inventory_id": "facility.inventory:facility_00000005", + "destination_inventory_id": "facility.inventory:facility_00000006", + "item_requirements": { + "ingredient/raw_food": 4 + }, + "priority": 50, + "created_at": 728, + "deadline": -1, + "total_weight": 4, + "reserved_stack_ids": [ + "stack_00000001" + ], + "reservation_ids": [ + "reservation_00000001" + ], + "assigned_carrier_id": "staff_gina", + "state": "DELIVERING", + "retry_count": 0, + "state_reason": "", + "state_changed_at": 735 + }, + { + "request_id": "supply.logistics_request.000000000002", + "source_inventory_id": "facility.inventory:facility_00000005", + "destination_inventory_id": "facility.inventory:facility_00000006", + "item_requirements": { + "ingredient/raw_food": 4 + }, + "priority": 50, + "created_at": 729, + "deadline": -1, + "total_weight": 4, + "reserved_stack_ids": [ + "stack_00000001" + ], + "reservation_ids": [ + "reservation_00000002" + ], + "assigned_carrier_id": "", + "state": "RESERVED", + "retry_count": 0, + "state_reason": "NO_AVAILABLE_PORTER_OR_ROUTE", + "state_changed_at": 729 + } + ] + } + ], + "CARGO_RULE_READONLY_DIAGNOSTIC.json": { + "candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9", + "scope": "Root readonly prototype only. Candidate/checker/evidence files unchanged, no formal acceptance.", + "basis": "Authoritative SupplyLogisticsBridge.validate_crosslinks lines508-554 resolves facility inventory endpoints by facility ownership and allows unassigned RESERVED request with valid reservation; destination inventory materializes upon delivery.", + "remaining_deficits": [ + "OS proof: unsafe artifact path", + "final regression: unsafe artifact path", + "unapproved typed migration delta off-positive/systems/runtime/world/revisions/support", + "unapproved typed migration delta on-positive/systems/runtime/world/revisions/support" + ], + "elapsed_seconds": 78.40071395831183 + }, + "ACTUAL_WORLD_REVISION_DELTAS.json": [ + { + "case": "off-positive", + "original_world_revisions": { + "world": 144, + "occupancy": 144, + "walkability": 99, + "excavation": 0, + "support": 142 + }, + "checkpoint_world_revisions": { + "world": 150, + "occupancy": 150, + "walkability": 99, + "excavation": 0, + "support": 150 + } + }, + { + "case": "on-positive", + "original_world_revisions": { + "world": 144, + "occupancy": 144, + "walkability": 99, + "excavation": 0, + "support": 142 + }, + "checkpoint_world_revisions": { + "world": 150, + "occupancy": 150, + "walkability": 99, + "excavation": 0, + "support": 150 + } + }, + { + "case": "off-refusal", + "original_world_revisions": { + "world": 144, + "occupancy": 144, + "walkability": 99, + "excavation": 0, + "support": 142 + }, + "checkpoint_world_revisions": { + "world": 144, + "occupancy": 144, + "walkability": 99, + "excavation": 0, + "support": 142 + } + }, + { + "case": "on-refusal", + "original_world_revisions": { + "world": 144, + "occupancy": 144, + "walkability": 99, + "excavation": 0, + "support": 142 + }, + "checkpoint_world_revisions": { + "world": 144, + "occupancy": 144, + "walkability": 99, + "excavation": 0, + "support": 142 + } + } + ] + }, + "source_basis": { + "scripts/supply/supply_logistics_bridge.gd:508": [ + "static func validate_crosslinks(requests: Dictionary, staff: StaffService, inventory: InventoryService, facilities: FacilityService) -> bool:", + "\tvar carrier_owners: Dictionary = {}", + "\tvar active_task_ids: Dictionary = {}", + "\tfor id: StringName in _request_ids(requests):", + "\t\tvar request: LogisticsRequest = requests[id]", + "\t\tvar source := _facility_for_inventory(facilities, request.source_inventory_id)", + "\t\tvar destination := _facility_for_inventory(facilities, request.destination_inventory_id)", + "\t\tif not String(request.source_inventory_id).begins_with(\"facility.inventory:\") or not String(request.destination_inventory_id).begins_with(\"facility.inventory:\"):", + "\t\t\treturn false", + "\t\tif not request.is_terminal() and (source == null or destination == null):", + "\t\t\treturn false", + "\t\tvar task := _active_task(staff, id)", + "\t\tif request.state in [LogisticsRequest.State.RESERVED, LogisticsRequest.State.BLOCKED] or request.is_terminal():", + "\t\t\tif task != null:", + "\t\t\t\treturn false", + "\t\t\tif request.state == LogisticsRequest.State.RESERVED and not _reservation_quantities_valid(request, inventory, true):", + "\t\t\t\treturn false", + "\t\t\tcontinue", + "\t\tif request.state not in [LogisticsRequest.State.ASSIGNED, LogisticsRequest.State.PICKING_UP, LogisticsRequest.State.DELIVERING]:", + "\t\t\treturn false", + "\t\tvar worker := staff.get_staff(request.assigned_carrier_id)", + "\t\tif worker == null or worker.role_id != &\"porter\" or carrier_owners.has(worker.staff_id) \\", + "\t\t\tor task == null or task.assigned_staff_id != worker.staff_id or worker.current_task_id != task.task_id \\" + ], + "scripts/world/world_service.gd:652": [ + "func _mark_dirty(", + "\tcells: Array,", + "\toccupancy_changed: bool,", + "\twalkability_changed: bool,", + "\texcavation_changed: bool,", + "\tsupport_changed: bool", + ") -> void:", + "\trevision += 1", + "\tif occupancy_changed:", + "\t\toccupancy_revision = revision", + "\tif walkability_changed:", + "\t\twalkability_revision = revision", + "\tif excavation_changed:", + "\t\texcavation_revision = revision", + "\tif support_changed:", + "\t\tsupport_revision = revision", + "\tfor cell: Variant in cells:", + "\t\tif cell is Vector3i and is_in_bounds(cell):", + "\t\t\t_dirty_cells[cell] = true" + ] + }, + "instructions": [ + "Read only this compact file first; no broad history or full evidence dump.", + "Do not modify fixture/runtime/collector/native semantics already passed. Fix checker decoding/crosslink/counter checks, realistic targeted tests and metadata together. Original6conditions and30/88 unchanged.", + "Native bytes must stay immutable. Decide whether tested runtime inputs exactly match final after tooling-only changes; do not silently re-label old tested SHA. If final validator requires exact full source-map, execute final native once after all tooling fixes consolidated.", + "Freeze all source edits before running checker tests; prior README mutation during tests caused preventable stale proof retry.", + "Root external native collector should check first-writer actual scalar/crosslink semantics before running all other phases to avoid repeated whole collection.", + "Independent review and OS/final regression remain mandatory." + ] + }, + "recommended_scope": "Restore the exact preserved r04 input fields required by the unchanged checker tests and correct implementation.json test provenance. Dedicated test fixture relocation is optional, not required by reviewer; do not change source/fixture merely for tidiness. Preserve existing native and full174 evidence if the final selected source map remains exactly identical. Run all4 meaningful checker tests against the repaired final files. Do not repair low diagnostic fixture note in this task unless it prevents acceptance.", + "unlimited_individual_repair_and_packet_caps": true, + "full_game_completion": false +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p08-02/implementation-historical-6c7c724.json b/docs/plans/original-completion-2026-10-02/execution/p08-02/implementation-historical-6c7c724.json new file mode 100644 index 0000000..faa5315 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p08-02/implementation-historical-6c7c724.json @@ -0,0 +1,16 @@ +{ + "classification": "Historical full record preserved; compact provenance reference", + "original_raw_sha256": "ab1fcf46ef73f2157f16f69394b4039a562c031fae8d407adca013a452226e2f", + "original_raw_bytes": 1082602, + "git_reference": { + "commit": "6c7c724410c350afe3d49fa8ec52defe20e78041", + "path": "docs/plans/original-completion-2026-10-02/execution/p08-02/implementation.json" + }, + "second_full_copy_git_reference": { + "commit": "e59206b9b2578ddb4a1ec023f836d413a7a8fbfb", + "path": "docs/plans/original-completion-2026-10-02/execution/p08-02/implementation-historical-6c7c724.json" + }, + "root_lossless_archive": "artifacts/original-completion-2026-10-02/p08-02-preserved-r01/full-submitted-implementation.json.gz", + "raw_archive_roundtrip_verified": true, + "current_acceptance_claimed": false +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p08-02/implementation.json b/docs/plans/original-completion-2026-10-02/execution/p08-02/implementation.json new file mode 100644 index 0000000..ddd13e5 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p08-02/implementation.json @@ -0,0 +1,330 @@ +{ + "task_id": "P08-02", + "status": "submitted", + "acceptance": "pending", + "base_commit": "e196578c1a4ad2fdf84642565bd102e524be386f", + "spec_sha256_stripped_utf8": "19f02bb7912a4d7354e565186f02366fc1d4dd698468d26142e8204fd14ebfb0", + "spec_sha256_raw": "01b3600e8815b514ffd5c4d341d77a6a3e8341064796c1dae706062b746ba6aa", + "scope": "Metadata-only synchronization against Git 1545bd42; no code, fixture, runtime, marker or acceptance changes.", + "outputs": [ + { + "path": "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json", + "sha256": "c4c6dbea08a92682c692a280dbc90702ccbe63702a4f2624519b18d42930e18f", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "tests/s1/test_p08_combined_migration.gd", + "sha256": "9284a8cfb478b4c8d69f6d8166eec6c41107e3902029289b3c0e9ba48c370855", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "tests/s1/test_p08_combined_migration.gd.uid", + "sha256": "8c3a70d0ce619b8cb98dc003eb6c53a31f167a686179a3ffc105f7cd4d856da5", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "tests/fixtures/p08-02/README.md", + "sha256": "e88ca33fdb4e96602d1fb108d71a9c35b6fa054619ecf6f1396e9a6c5e0ebd98", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "tests/fixtures/p08-02/mixed_world.gd", + "sha256": "a23f8a686be1d77e4874b9956eef89be4756f6e5eb01cc62454c3845acc62c25", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "tests/fixtures/p08-02/reader.gd", + "sha256": "a760d0e45979d747dbd1f8710033bdce13287b00eb879d947d8378ff920ed1be", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "tests/fixtures/p08-02/writer.gd", + "sha256": "1a918b5509773b4b07a27d47ee44583b0eb5288138f7bf896088a7913773c260", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "tools/run_p08_mixed_migration.py", + "sha256": "7e86737e6354a4734726ba8d3171ad5f58d6c0380aba5a0fa3c57879d580e579", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "tools/check_p08_migration_evidence.py", + "sha256": "928353233bf85436a5aaf8c4f77f3f82472d94b8ad2646cb1b030baf92b3b5dc", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "tools/test_p08_migration_evidence.py", + "sha256": "fd9ef7ec63f53dfd5e04e261606c9303d01a2e6590e22a35fef2e35df1429280", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "docs/plans/original-completion-2026-10-02/execution/p08-02/CONDITION_MATRIX.md", + "sha256": "6673051df9ae10f2df9b1812828e71ad9f5f24c433572defeef4e0978962a4e8", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "docs/plans/original-completion-2026-10-02/execution/p08-02/implementation.json", + "sha256": "fee85e6f4bb2824fc64bdfa275302f503a3234d83ab6c473c5e3ae6fdf91a3a6", + "hash_scope": "canonical normalized self record: replace this sha256 with null, UTF-8 json.dumps(ensure_ascii=False,sort_keys=True,separators=(comma,colon)), no newline", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "tests/fixtures/p08-02/mixed_world.gd.uid", + "sha256": "d7549f933b74f5e807cd2d3b3adcdd083af4a3bd331614314ff7bfd7b0903f31", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "tests/fixtures/p08-02/reader.gd.uid", + "sha256": "3e68ce192d5770e3823df9699c5c99ebb618fe0a480b4d56913624941900b08a", + "hash_scope": "raw bytes", + "disposition": "r04 current source; retained paths and UID/marker entries" + }, + { + "path": "tests/fixtures/p08-02/writer.gd.uid", + "sha256": "8f150654767de0c66329d7d9e84d306c4fa5a65c918e04fb520b626b39f68334", + "hash_scope": "raw bytes", + "disposition": "retained unchanged UID" + } + ], + "self_hash_note": "A raw self-containing SHA cannot be embedded in itself. This explicit normalized hash covers implementation.json; Root must separately measure its raw SHA and the final full packet on actual submitted SHA. No final packet measurement claimed.", + "commands_actually_run": [ + { + "command": "python3 -B AST parse of three P08 Python tools", + "exit_code": 0, + "result": "3 parses passed" + }, + { + "command": "python3 -B tools/test_p08_migration_evidence.py (first run)", + "exit_code": 1, + "result": "2 tests; positive stale source map because worker changed README during test" + }, + { + "command": "python3 -B tools/test_p08_migration_evidence.py (stable-source rerun)", + "exit_code": 0, + "result": "2 tests in 33.523s, OK; synthetic only", + "classification": "Historical worker run; superseded as current local-check metadata, preserved without relabeling." + }, + { + "command": "python3 -B tools/test_p08_migration_evidence.py (Repair 7, final frozen sources)", + "exit_code": 0, + "result": "4 tests in 55.197s, OK; synthetic only" + } + ], + "local_checks": { + "ast_parse": "passed, three Python sources", + "checker_tests": { + "command": "python3 -B tools/test_p08_migration_evidence.py", + "tests_run": 4, + "elapsed_seconds": 55.197, + "result": "OK", + "exit_code": 0, + "scope": "Synthetic local checker tests only; source edits frozen before this single run. Not native, OS, full regression or acceptance evidence.", + "final_raw_sha256": { + "tools/test_p08_migration_evidence.py": "fd9ef7ec63f53dfd5e04e261606c9303d01a2e6590e22a35fef2e35df1429280", + "tools/check_p08_migration_evidence.py": "928353233bf85436a5aaf8c4f77f3f82472d94b8ad2646cb1b030baf92b3b5dc", + "docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r04.json": "23ef44a0775ca53f1f67d6d64fcc96cd33eae935478768e9ce9431796eaa12ef" + } + }, + "native_or_full_regression_run": false, + "evidence_reuse_pending": "Reuse of earlier native 8, OS and final 174 regression evidence is pending Root confirmation of exact source_map, input, config and environment matches. If restored r04 is in the selected source map, Root must decide whether to re-collect. Independent review and integration remain pending; no unconfirmed evidence counts asserted." + }, + "pending_root_conditions": [ + "mixed_construction", + "occupancy_change", + "actual_actor_positions", + "combined_rescue_logistics_ev", + "no_overlap_orphan_loss", + "unsolvable_preservation", + "native_Godot_and_OS_execution", + "separate_PID_writer_reader", + "normal_menu_OS_proof", + "final_full_regression", + "final_submitted_packet_measurement", + "applicable_Windows_coverage", + "independent_Opus_review", + "main_integration_current_final_verification", + "ledger_reflection_and_original_unit_closure" + ], + "product_fix_proposal": null, + "index_schema": { + "name": "p08-evidence-v1", + "location": "BUILD_OUTPUT_DIR/p08-evidence/index.json", + "authority": "tools/check_p08_migration_evidence.py check() and tools/run_p08_mixed_migration.py collect()", + "top_level": { + "schema": "literal p08-evidence-v1", + "commit": "40 lowercase hex actual submitted commit; collector checks HEAD", + "source_map": "exact runner.source_refs(root) path -> raw SHA-256 including fixture/suite/tool sources", + "inputs": "exact frozen docs/spec/inputs/p08-02 immediate file path -> raw SHA-256", + "spec_sha256": "stripped UTF-8 frozen spec SHA", + "marker_contract_sha256": "raw marker contract SHA", + "cases": "ordered off-positive, off-refusal, on-positive, on-refusal", + "phases": "collector import/parse and case receipt paths", + "conditions": "all six IDs -> all four cases, except unsolvable_preservation -> both refusal cases", + "artifacts": "relative path -> {bytes: integer, sha256: raw digest}; no absolute/traversal/symlink; index itself excluded", + "os_proof": "relative OS proof JSON path; initially null in collector", + "regression": "relative final regression proof JSON path; initially null in collector", + "acceptance": "literal pending, including after integrity verification", + "collection_error": "null for integrity gate" + }, + "case": { + "id": "operations-kind", + "operations": "off|on", + "kind": "positive|refusal", + "directory": "relative pair directory", + "phases": "writer then reader receipt paths (two)", + "files": "collector inventory path -> bytes/sha256; checker artifacts is content authority" + }, + "phase_receipt": "actual_pid unique positive integer; phase, command argv, test, source_sha256, log relative to receipt parent; exit_code=0, engine_ended=true; native_process exit_code/helper_exit_code=0,timed_out=false,lifecycle_error=null; actual_private_profile.report fresh_before_parent_initialize=true,profile_writes_before_guard=0,actual_user_data_dir=expected_user_data_dir", + "raw_native_lifecycle": "receipt parent/phase/{started.json,ended.json,outer-owned-finally.json}. Start/end actual_pid and command match receipt. Start live_identity_exact or tracked_child_ended_before_initial_probe. End exit_code=0,engine_ended=true. Outer actual_captured_pids=[actual_pid],all_captured_ended=true,no cleanup errors or foreign signals. Raw version/argv/lifecycle must be actual Root capture.", + "strict_logs": "receipt parent/log: native version banner and exact role completion template, current source_sha256; full regression checks existing strict contracts for every current roster suite", + "typed_state": "Every .var raw Variant has .json typed tree: Dictionary entries=[typed key,typed value], Array items plus element_type/element_class, scalar type and exact var_to_str text. Required identity,original,after-live,before-members,after-members,writer/reader-process,writer/reader-checkpoint,writer/reader-minute-001 through 160,writer/reader-events. Preserve all fields and uncapped events. Driver also retains first-candidate,preview,migration diagnostics.", + "formal_members": "original/result/backup formal/{world.bin,manifest.json,thumbnail.png,journal.log}; raw artifact hashes bound to before/after typed member maps. Refusal preserves full original and pre-existing backup.", + "normal_OS_proof": "commit and exact source_map; input_method=native_os,setup=controlled_old_save; ordered preview,cancel,confirm,changed_source_rejection. Each action links input_receipt (method=native_os,event_type mouse|keyboard,target_pid positive int,timestamp_ns positive),screen_before/after,model_before/after,formal_before/after. Non-confirm formal JSON maps equal. Root independently assesses actual OS input/screens/state; signal-emitted UI is handler evidence only.", + "final_regression": "commit,directory,input,ended artifact paths. Input selected_source_map exact and roster runner.roster(root). Ended error null,cleanup_errors/guard_errors empty,ordered phases full roster each strict raw log,exit_code=0,engine_ended=true.", + "limits": "This documents implemented gates, not an acceptance reducer. Import/parse and config/environment runner-input are retained and hashed by driver; checker explicitly gates case phases. Root independently checks native version/config/environment, normal OS behavior and original conditions. Synthetic metadata cannot prove actual execution." + }, + "example": { + "classification": "Synthetic machine-readable complete index from retained Proof; diagnostic ONLY, never native/OS/regression acceptance evidence.", + "construction": "tools/test_p08_migration_evidence.py Proof creates all linked bytes and reachable whole-check positive. Embedded documents illustrate receipts/lifecycle/OS/regression/typed records. Digests belong to the disposable synthetic fixture, not delivered acceptance artifacts; reproduce with Proof in a worktree-local disposable directory.", + "complete_example_generator": { + "source": "tools/test_p08_migration_evidence.py", + "source_raw_sha256": "fd9ef7ec63f53dfd5e04e261606c9303d01a2e6590e22a35fef2e35df1429280", + "class": "Proof", + "constructor": "Proof(Path(fresh_temporary_directory))", + "project_root_binding": "module ROOT resolved from unchanged generator inside this exact candidate source tree", + "generated_full_index": "proof.index", + "generated_representative_documents": "{path: json.loads((proof.path / path).read_text()) for path in representative_artifact_document_paths}", + "native_or_os_runs": 0 + }, + "index_top_level_fields": [ + "schema", + "commit", + "source_map", + "spec_sha256", + "marker_contract_sha256", + "inputs", + "cases", + "conditions", + "artifacts", + "acceptance", + "collection_error", + "phases", + "os_proof", + "regression" + ], + "representative_artifact_document_paths": [ + "runs/off-positive-writer/off-positive-writer-receipt.json", + "runs/off-positive-reader/off-positive-reader-receipt.json", + "runs/off-positive-writer/off-positive-writer/started.json", + "runs/off-positive-writer/off-positive-writer/ended.json", + "runs/off-positive-writer/off-positive-writer/outer-owned-finally.json", + "os.json", + "os/preview/input.json", + "regression.json", + "regression/input.json", + "regression/ended.json", + "pairs/off-positive/identity.json", + "pairs/off-positive/writer-process.json", + "pairs/off-positive/before-members.json" + ], + "fixture_config_inputs": { + "fixture_sources": { + "tests/s1/test_p08_combined_migration.gd": "9284a8cfb478b4c8d69f6d8166eec6c41107e3902029289b3c0e9ba48c370855", + "tests/s1/test_p08_combined_migration.gd.uid": "8c3a70d0ce619b8cb98dc003eb6c53a31f167a686179a3ffc105f7cd4d856da5", + "tests/fixtures/p08-02/README.md": "e88ca33fdb4e96602d1fb108d71a9c35b6fa054619ecf6f1396e9a6c5e0ebd98", + "tests/fixtures/p08-02/mixed_world.gd": "a23f8a686be1d77e4874b9956eef89be4756f6e5eb01cc62454c3845acc62c25", + "tests/fixtures/p08-02/reader.gd": "a760d0e45979d747dbd1f8710033bdce13287b00eb879d947d8378ff920ed1be", + "tests/fixtures/p08-02/writer.gd": "1a918b5509773b4b07a27d47ee44583b0eb5288138f7bf896088a7913773c260", + "tools/run_p08_mixed_migration.py": "7e86737e6354a4734726ba8d3171ad5f58d6c0380aba5a0fa3c57879d580e579", + "tools/check_p08_migration_evidence.py": "928353233bf85436a5aaf8c4f77f3f82472d94b8ad2646cb1b030baf92b3b5dc", + "tools/test_p08_migration_evidence.py": "fd9ef7ec63f53dfd5e04e261606c9303d01a2e6590e22a35fef2e35df1429280" + }, + "native_runner_input_shape": { + "protocol": "p08-mixed-v1", + "commit": "", + "selected_source_map": "", + "engine": "", + "engine_sha256": "", + "owner_pid": "", + "default_player": "", + "diagnostic_seed": 42588 + }, + "case_environment": { + "P08_PAIR_ROOT": "", + "P08_OPERATIONS": "off|on", + "P08_CASE": "positive|refusal" + }, + "timeouts": "unchanged: collection 900s, phase maximum 180s", + "formal_validation": "Root copies actual evidence into allocated build directory; --verify-only reads BUILD_OUTPUT_DIR/p08-evidence/index.json" + }, + "purpose": "Synthetic Proof generator documents current schema. Historical reconstruction measurements are preserved in Git, not asserted for changed fixture.", + "previous_full_inline_record": { + "commit": "1545bd42d689a9a6382e2b003ee0550fa197f6e4", + "path": "docs/plans/original-completion-2026-10-02/execution/p08-02/implementation.json", + "sha256": "ae4a9f11c4e10a2be31d8fc6429577ff5f14f6097fae4f2d5a6a321937fefdac", + "bytes": 16946, + "hash_scope": "Git raw bytes", + "preservation": "Full previous metadata/example remains byte-preserved in Git; historical links and SHA values unchanged." + } + }, + "historical_record": { + "path": "docs/plans/original-completion-2026-10-02/execution/p08-02/implementation-historical-6c7c724.json", + "sha256": "94321c2e924ef509738c8c4302c9e68854b9ce305830352d2dbb51d43f9ff34c", + "original_full_raw_sha256": "ab1fcf46ef73f2157f16f69394b4039a562c031fae8d407adca013a452226e2f", + "classification": "Reference to byte-preserved original full historical record in fixed Git commits and Root archive; not current canonical evidence" + }, + "repair_r04": { + "seed": 42588, + "construction_cost": { + "gold": 7110, + "materials": 88 + }, + "signing_gold": 150, + "food_quantity": 8, + "food_gold": 24, + "initial": { + "gold": 5000, + "materials": 50 + }, + "previous_change_resources": {}, + "derived_delta": { + "gold": 2284, + "materials": 38 + }, + "mismatch": "Previous zero added resources insufficient; no acceptance narrowed.", + "git_history_verification": "Both fixed Git references raw SHA matched ab1fcf46ef73f2157f16f69394b4039a562c031fae8d407adca013a452226e2f, 1082602 bytes", + "archive_verification": "Reuse Root supplied roundtrip evidence; archive not opened or executed." + }, + "landing_repair": { + "input": "ROOT_REPAIR_INPUT-r04.json", + "ground": "initialized world paths within rank 1 bounds", + "upper": "complete each stair pair before ordering next floor, using maximum registered build_minutes", + "nearby_preflight": "rank bounds, registered stair definitions and existing completed logistics endpoints checked locally", + "acceptance_sources": "all 18 retained; no omissions", + "packet_limit_bytes": null, + "packet_measurement": "Final actual submitted Runner packet measurement remains Root responsibility; obsolete byte cap removed; no source omission or compression.", + "collection_timeout_seconds": 900 + }, + "metadata_sync": { + "source_commit": "1545bd42d689a9a6382e2b003ee0550fa197f6e4", + "current_tool_raw_sha256": { + "tools/check_p08_migration_evidence.py": "928353233bf85436a5aaf8c4f77f3f82472d94b8ad2646cb1b030baf92b3b5dc", + "tools/test_p08_migration_evidence.py": "fd9ef7ec63f53dfd5e04e261606c9303d01a2e6590e22a35fef2e35df1429280" + }, + "source_map": "run_all_tests.py source_refs enumerates autoload/scripts/scenes/data/tests/assets/tools plus project.godot/export_presets.cfg/Makefile/AGENTS.md, excluding docs. Only docs changed, so source refs unchanged.", + "worker_validation": "JSON parse, Git raw SHA equality, generator Proof reference existence; no checker/native/Godot/full regression execution.", + "review_material_context_gap": "No .review-material or explicit complete 18-source Runner packet manifest provided in this checkout. Exact packet bytes cannot be claimed without Runner operation (prohibited); Root must measure complete actual submitted packet. No required source was removed." + } +} diff --git a/tests/fixtures/p08-02/README.md b/tests/fixtures/p08-02/README.md new file mode 100644 index 0000000..b602db4 --- /dev/null +++ b/tests/fixtures/p08-02/README.md @@ -0,0 +1,23 @@ +# P08-02 diagnostic fixture + +Controlled fixture inputs are diagnostic, not production balance values. +Native collection and acceptance remain pending. + +Seed: 42588. Registered construction costs for the 16 planned builds total +gold 7110 / materials 88; Gina signing costs 150 and eight raw food units cost +24. Initial resources are gold 5000 / materials 50. The previous fixture had +no change_resources delta (zero), short by gold 2284 / materials 38. +The fixture derives and records this deficit from the actual registry/catalogs +in identity.diagnostic_budget before using the existing change_resources command. +No price or production coefficient is changed. + +Stair preparation proceeds from floor 1 through floor 3. Each pair finishes +using the maximum registered stair build_minutes before the next floor is +ordered. Ground landings use initialized world paths; upper landings are +created by completed stairs through build_facility, with a local path check +before each pair. Failed commands include kind, payload and result details. + +The first legacy premium job progresses while the six actor/transport facets +are prepared. The second is paid and reserved immediately before saving, with +zero progress; the dormitory is already complete. All three stages share the +same migration batch and whole-state writer/reader comparisons. diff --git a/tests/fixtures/p08-02/mixed_world.gd b/tests/fixtures/p08-02/mixed_world.gd new file mode 100644 index 0000000..bfd68b1 --- /dev/null +++ b/tests/fixtures/p08-02/mixed_world.gd @@ -0,0 +1,241 @@ +extends RefCounted +## All seeds, actor IDs, positions, food quantities and schedules are diagnostic inputs. +var checks := 0 +var failures := 0 +var runtime: Variant +var clock: Variant +var state: Variant +var directory: String +var mode: String +var slot: String +var serial := 0 +var events: Array = [] +var identity: Dictionary = {} +const MEMBERS := [SaveService.WORLD_FILE, SaveService.MANIFEST_FILE, SaveService.THUMBNAIL_FILE, SaveService.JOURNAL_FILE] +func expect(ok: bool, label: String) -> bool: + checks += 1 + if not ok: + failures += 1 + push_error("P08 mixed: " + label) + return ok +func command(kind: StringName, payload: Dictionary) -> bool: + serial += 1 + var result: CommandResult = runtime.submit_command(kind, payload, &"player", &"", 0, -1, StringName("p08.%d" % serial)) + var label := String(kind) + ": " + result.message + if not result.is_success(): label += " payload=" + var_to_str(payload) + " details=" + var_to_str(result.details) + return expect(result.is_success(), label) +func registry(old: bool) -> void: + var paths: Array[String] = FacilityCatalog.FACILITY_PATHS + FacilityCatalog.INFRASTRUCTURE_FACILITY_PATHS + FacilityCatalog.RESCUE_FACILITY_PATHS + paths.append_array(["res://tests/fixtures/legacy_premium_room.tres", "res://tests/fixtures/legacy_staff_dormitory.tres"] if old else ["res://data/facilities/expansion/premium_room.tres", "res://data/facilities/expansion/staff_dormitory.tres"]) + expect(runtime.definition_registry.rebuild_from_paths(paths)[&"valid"], "definition registry") +func build(id: StringName, cell: Vector3i, complete := true) -> bool: + if not command(&"build_facility", {&"definition_id":id, &"origin":cell}): return false + if complete: clock.advance_minutes(runtime.definition_registry.get_facility(id).build_minutes) + return true +func instance(id: StringName) -> FacilityInstance: + for room: FacilityInstance in runtime.facility_service.get_instances(): + if room.definition_id == id: return room + return null +## Scalar text avoids loss of integer precision; raw Variant bytes accompany JSON. +func typed(value: Variant) -> Dictionary: + var result := {"type":type_string(typeof(value))} + if value is Dictionary: + result["entries"] = [] + for key: Variant in value: result["entries"].append([typed(key),typed(value[key])]) + elif value is Array: + result["element_type"] = value.get_typed_builtin() + result["element_class"] = String(value.get_typed_class_name()) + result["items"] = [] + for item: Variant in value: result["items"].append(typed(item)) + else: result["text"] = var_to_str(value) + return result +func put(name: String, value: Variant) -> void: + var path := directory.path_join(name) + if not expect(not FileAccess.file_exists(path+".var") and not FileAccess.file_exists(path+".json"), "fresh proof "+name): return + var file := FileAccess.open(path+".var",FileAccess.WRITE) + if not expect(file != null,"Variant output"): return + file.store_buffer(var_to_bytes(value)); file.close() + file = FileAccess.open(path+".json",FileAccess.WRITE) + if not expect(file != null,"typed output"): return + file.store_string(JSON.stringify(typed(value))); file.close() +func get_proof(name: String) -> Variant: + return bytes_to_var(FileAccess.get_file_as_bytes(directory.path_join(name+".var"))) +func capture(name: String, value: Variant) -> void: + put(mode+"-"+name,value) + if mode == "reader": expect(var_to_bytes(value) == var_to_bytes(get_proof("writer-"+name)),"whole cross-PID "+name) +func copy_package(source: String, destination: String) -> void: + expect(DirAccess.make_dir_recursive_absolute(destination) == OK,"package directory") + for member: String in MEMBERS: + var data := FileAccess.get_file_as_bytes(source.path_join(member)) + if not expect(not data.is_empty() and not FileAccess.file_exists(destination.path_join(member)),"fresh complete member "+member): continue + var file := FileAccess.open(destination.path_join(member),FileAccess.WRITE) + if not expect(file != null,"member writable"): continue + file.store_buffer(data); file.close() + expect(data == FileAccess.get_file_as_bytes(destination.path_join(member)),"whole member copy verified") +func packages() -> Dictionary: + var hashes := {} + for suffix: String in ["",SaveService.MIGRATION_BACKUP_SUFFIX]: + for member: String in MEMBERS: hashes[suffix+"/"+member] = FileAccess.get_sha256(ProjectSettings.globalize_path(slot+suffix).path_join(member)) + return hashes +func observe(event: SimulationDomainEvent) -> void: events.append(event.to_dictionary()) +## Controlled diagnostic input; costs come only from registered domain definitions. +func prepare_diagnostic_budget() -> bool: + var planned: Array[StringName] = [&"warehouse",&"kitchen",&"infirmary",&"staff_door",&"reception",&"reception",&"guest_elevator",&"staff_dormitory",&"premium_room",&"premium_room",&"service_stairs",&"service_stairs",&"service_stairs",&"guest_stairs",&"guest_stairs",&"guest_stairs"] + var needed := {&"gold":0,&"materials":0} + var costs: Array = [] + for id: StringName in planned: + var definition: FacilityDefinition = runtime.definition_registry.get_facility(id) + if not expect(definition != null,"budget definition "+String(id)): return false + costs.append({&"definition_id":id,&"cost":definition.construction_cost.duplicate(true)}) + for resource: StringName in needed: needed[resource] += int(definition.construction_cost.get(resource,0)) + var signing := int(runtime.mvp_content_catalog.staff_hire_payload(&"staff_gina").get(&"signing_cost",0)) + var food := 8 * int(SupplyCatalog.get_item(SupplyCatalog.RAW_FOOD)[&"unit_price"]) + needed[&"gold"] += signing + food + var deltas := {} + for resource: StringName in needed: + var deficit: int = maxi(0,int(needed[resource])-state.get_resource(resource)) + if deficit > 0: deltas[resource] = deficit + identity[&"diagnostic_budget"] = {&"seed":42588,&"costs":costs,&"signing":signing,&"food_quantity":8,&"food_cost":food,&"needed":needed,&"initial":state.resources.duplicate(true),&"previous_change_resources":{},&"deltas":deltas} + return deltas.is_empty() or command(&"change_resources",{&"deltas":deltas}) +func setup(operations: bool) -> bool: + registry(true) + if not prepare_diagnostic_budget(): return false + if operations and not expect(runtime.enable_production_operations().get(&"ok",false),"Operations enabled at Day 1 06:00"): return false + for row: Array in [[&"warehouse",Vector3i(-8,0,1)],[&"kitchen",Vector3i(2,0,1)],[&"infirmary",Vector3i(6,0,1)],[&"staff_door",Vector3i(0,0,1)],[&"reception",Vector3i(-6,0,0)],[&"reception",Vector3i(5,0,0)],[&"guest_elevator",Vector3i.ZERO]]: + if not build(row[0],row[1],false): return false + clock.advance_minutes(180) + # Completed stairs supply the next floor's landing through the domain projector. + # Ground-floor landings come from the initialized world; never inject path cells. + var stair_minutes: int = maxi(runtime.definition_registry.get_facility(&"service_stairs").build_minutes,runtime.definition_registry.get_facility(&"guest_stairs").build_minutes) + for y: int in [1,2,3]: + if not expect(runtime.path_service.has_cell(Vector3i(-9,y-1,1)) and runtime.path_service.has_cell(Vector3i(4,y-1,0)),"completed lower stair landings for floor %d" % y): return false + if not build(&"service_stairs",Vector3i(-9,y,1),false) or not build(&"guest_stairs",Vector3i(4,y,0),false): return false + clock.advance_minutes(stair_minutes) + if not build(&"staff_dormitory",Vector3i(-8,2,1)): return false + identity[&"dorm"] = instance(&"staff_dormitory").instance_id + if not build(&"premium_room",Vector3i(-6,2,0),false): return false + clock.advance_minutes(7) + identity[&"jobs"] = [] + for job: ConstructionJob in runtime.construction_service.get_jobs(): + if not job.is_terminal(): identity[&"jobs"].append(job.job_id) + if not expect(identity[&"jobs"].size() == 1,"one paid progressing job"): return false + var warehouse := instance(&"warehouse") + var kitchen := instance(&"kitchen") + if not expect(warehouse != null and kitchen != null,"completed logistics endpoints"): return false + if not command(&"hire_staff_candidate",{&"candidate_id":&"staff_gina"}): return false + if not command(&"set_staff_work_assignment",{&"staff_id":&"staff_gina",&"facility_id":warehouse.instance_id,&"template_id":&"DAY"}): return false + if not command(&"purchase_supplies",{&"warehouse_id":warehouse.instance_id,&"item_id":SupplyCatalog.RAW_FOOD,&"quantity":8}): return false + if not command(&"set_kitchen_production",{&"facility_id":kitchen.instance_id,&"enabled":true,&"target_stock":4}): return false + var cargo_ready := false + for minute: int in range(60): + clock.advance_minutes(1) + if not runtime.staff_service.get_staff(&"staff_gina").carried_inventory_id.is_empty(): cargo_ready = true; break + if not expect(cargo_ready,"real purchased cargo picked up"): return false + for id: StringName in [&"patient",&"carrier"]: + if not command(&"register_staff",{&"staff_id":id,&"role_id":&"porter",&"options":{&"current_cell":Vector3i(-3,0,1) if id == &"patient" else Vector3i(-1,0,1),&"health":0 if id == &"patient" else 100,&"state":StaffRuntime.State.AVAILABLE,&"role_tags":[&"medic"],&"permitted_task_tags":[&"rescue"]}}): return false + var carrying := false + for minute: int in range(6): + clock.advance_minutes(1) + for task: StaffTask in runtime.staff_service.get_tasks(): + if task.task_type == &"rescue_transport" and task.state == StaffTask.State.MOVING: + carrying = true; identity[&"rescue_task"] = task.task_id + if carrying: break + if not expect(carrying,"actual rescue transport"): return false + for index: int in range(5): + if not command(&"spawn_guest",{&"guest_id":StringName("p08.passenger.%d" % index),&"guest_type_id":&"traveler",&"gold":100,&"options":{&"current_cell":Vector3i.ZERO,&"state":GuestRuntime.State.MOVING,&"state_entered_at":clock.absolute_minute-1,&"planned_exit_at":clock.absolute_minute+2000,&"path_handle":{&"target_cell":Vector3i(0,1,0)}}}): return false + clock.advance_minutes(1) + if not command(&"spawn_guest",{&"guest_id":&"p08.walker",&"guest_type_id":&"traveler",&"gold":100,&"options":{&"current_cell":Vector3i(4,0,0),&"state":GuestRuntime.State.MOVING,&"state_entered_at":clock.absolute_minute-1,&"planned_exit_at":clock.absolute_minute+2000,&"path_handle":{&"target_cell":Vector3i(4,3,0)}}}): return false + clock.advance_minutes(1) + identity[&"ev_requests"] = [] + for trip: Dictionary in runtime.elevators.get_report()[&"trips"]: identity[&"ev_requests"].append(trip[&"request_id"]) + identity[&"logistics_requests"] = [] + for request: LogisticsRequest in runtime.logistics_requests.values(): + if not request.is_terminal(): identity[&"logistics_requests"].append(request.request_id) + expect(not runtime.staff_service.get_staff(&"staff_gina").carried_inventory_id.is_empty(),"cargo in SAME save as patient and EV") + expect(runtime.staff_service.get_staff(&"patient").health == 0,"patient remains untreated") + var car: Dictionary = runtime.elevators.get_report()[&"shafts"][0] + expect(car[&"passenger_count"] == 4 and car[&"waiting_count"] == 1,"passengers and queue coexist") + expect(runtime.create_snapshot()[&"systems"][&"runtime"][&"movement_credits"].size() > 0,"fractional movement credit") + if not build(&"premium_room",Vector3i(5,2,0),false): return false + for candidate: ConstructionJob in runtime.construction_service.get_jobs(): + if not candidate.is_terminal() and not identity[&"jobs"].has(candidate.job_id): identity[&"jobs"].append(candidate.job_id) + if not expect(identity[&"jobs"].size() == 2,"two paid active jobs"): return false + identity[&"starting_job"] = identity[&"jobs"][1] + for id: StringName in identity[&"jobs"]: + var job: ConstructionJob = runtime.construction_service.get_job(id) + expect(not job.is_terminal() and (job.progress == 0 if id == identity[&"starting_job"] else job.progress > 0) and not job.construction_cost.is_empty() and job.reserved_cells.size() == 6,"old-size paid starting/progressing jobs remain active") + return failures == 0 +func invariant() -> void: + expect(runtime.staff_service.validate_invariants()[&"ok"],"staff ownership") + expect(SupplyLogisticsBridge.validate_crosslinks(runtime.logistics_requests,runtime.staff_service,runtime.inventory_service,runtime.facility_service),"cargo crosslinks") + var snap: Dictionary = runtime.create_snapshot() + expect(runtime.restore_snapshot(snap),"whole Runtime ownership/support/transport validation") + expect(var_to_bytes(snap) == var_to_bytes(runtime.create_snapshot()),"whole canonical typed state") +func run(tree: SceneTree, output: String, role: String, operations: bool, refusal: bool) -> Dictionary: + directory = output; mode = role + slot = "user://p08-%d/formal" % Time.get_ticks_usec() + tree.root.get_node("GameClock").set_paused(true); tree.root.get_node("GameClock").set_process(false) + tree.root.get_node("SimulationRuntime").configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false}) + runtime = load("res://autoload/simulation_runtime.gd").new() + clock = load("res://autoload/game_clock.gd").new(); state = load("res://autoload/game_state.gd").new() + clock.reset(1,6,0); state.reset(); expect(runtime.initialize(clock,state,42588)[&"valid"],"current initialization") + clock.set_paused(true); runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false,&"auto_migrate":false}) + if mode == "writer": + expect(DirAccess.make_dir_recursive_absolute(directory) == OK,"proof directory") + if not setup(operations): return finish() + registry(false) + var baseline: Dictionary = runtime.create_snapshot() + expect(SaveService.save_snapshot(slot,baseline)[&"ok"],"formal original saved") + if refusal: + var initial: Dictionary = runtime.migrate_facility_dimensions_at_path(slot) + expect(initial.get(&"ok",false) and initial.get(&"backup_created",false),"real prior migration backup") + expect(runtime.staff_service.create_task(&"maintenance",10,clock.absolute_minute,{&"target_entity_id":identity[&"dorm"],&"target_cell":Vector3i(-9,3,1),&"estimated_minutes":30},&"p08.late").is_success(),"later old entrance task") + if not command(&"register_staff",{&"staff_id":&"maintainer",&"role_id":&"porter",&"options":{&"current_cell":Vector3i(-9,1,1),&"state":StaffRuntime.State.AVAILABLE}}): return finish() + expect(runtime.staff_service.assign_task_to_staff(&"p08.late",&"maintainer",clock.absolute_minute).is_success(),"real task ownership") + expect(SaveService.save_snapshot(slot,runtime.create_snapshot())[&"ok"],"obstructed source saved") + put("identity",identity); put("original",runtime.create_snapshot()) + copy_package(ProjectSettings.globalize_path(slot),directory.path_join("original/formal")) + var live: Dictionary = runtime.create_snapshot() + var before := packages(); put("before-members",before) + var first: Dictionary = runtime.prepare_facility_dimension_snapshot(identity[&"jobs"][0]) + expect(first.get(&"ok",false),"earlier candidate independently migratable") + put("first-candidate",first) + var preview: Dictionary = runtime.preview_facility_dimensions_at_path(slot); put("preview",preview) + var result: Dictionary = runtime.migrate_facility_dimensions_at_path(slot,&"",String(preview.get(&"source_digest",""))); put("migration",result) + expect(var_to_bytes(live) == var_to_bytes(runtime.create_snapshot()),"live unchanged") + put("after-live",runtime.create_snapshot()) + if refusal: + expect(not result.get(&"ok",true) and result.get(&"details",{}).get(&"rolled_back",false) and result.get(&"details",{}).get(&"facility_id",&"") == identity[&"dorm"],"later refusal rolls back earlier candidates") + expect(before == packages(),"original and existing backup unchanged") + else: expect(preview.get(&"ok",false) and result.get(&"ok",false) and result.get(&"backup_created",false),"positive whole batch") + put("after-members",packages()) + copy_package(ProjectSettings.globalize_path(slot),directory.path_join("result/formal")) + copy_package(ProjectSettings.globalize_path(slot+SaveService.MIGRATION_BACKUP_SUFFIX),directory.path_join("backup/formal")) + else: + registry(false); identity = get_proof("identity") + copy_package(directory.path_join("result/formal"),ProjectSettings.globalize_path(slot)) + copy_package(directory.path_join("backup/formal"),ProjectSettings.globalize_path(slot+SaveService.MIGRATION_BACKUP_SUFFIX)) + var loaded := SaveService.load_snapshot(slot) + expect(loaded.get(&"ok",false) and runtime.restore_snapshot(loaded.get(&"snapshot",{})),"formal continuation restore") + expect(runtime.get_operations_report().get(&"enabled",false) == operations,"actual Operations mode") + invariant(); capture("checkpoint",runtime.create_snapshot()) + expect(runtime.domain_events.subscribe(&"*",&"p08.uncapped",Callable(self,"observe")),"uncapped events") + for minute: int in range(160): + clock.advance_minutes(1); capture("minute-%03d" % (minute+1),runtime.create_snapshot()) + capture("events",events) + for id: StringName in identity[&"jobs"]: + var job: ConstructionJob = runtime.construction_service.get_job(id) + expect(job.state == ConstructionJob.State.COMPLETED and runtime.facility_service.get_instance(job.facility_instance_id).occupied_cells.size() == (6 if refusal else 4),"paid job completes once with expected shape") + expect(runtime.staff_service.get_staff(&"patient").health == 100,"patient treated") + for id: StringName in identity[&"logistics_requests"]: expect(runtime.logistics_requests[id].state == LogisticsRequest.State.COMPLETED,"original cargo delivered") + for id: StringName in identity[&"ev_requests"]: + var count := 0 + for event: Dictionary in events: + if event["event_type"] == "ELEVATOR_ARRIVED" and event["payload"].get(&"request_id",&"") == id: count += 1 + expect(count == 1,"original EV request completes exactly once") + invariant() + put(mode+"-process",{&"pid":OS.get_process_id(),&"operations":operations,&"refusal":refusal,&"checks":checks,&"failures":failures}) + return finish() +func finish() -> Dictionary: + runtime.free(); clock.free(); state.free() + return {"checks":checks,"failures":failures} diff --git a/tests/fixtures/p08-02/mixed_world.gd.uid b/tests/fixtures/p08-02/mixed_world.gd.uid new file mode 100644 index 0000000..206b8d0 --- /dev/null +++ b/tests/fixtures/p08-02/mixed_world.gd.uid @@ -0,0 +1 @@ +uid://dg5n4yme6rk4p diff --git a/tests/fixtures/p08-02/reader.gd b/tests/fixtures/p08-02/reader.gd new file mode 100644 index 0000000..3f0b4b1 --- /dev/null +++ b/tests/fixtures/p08-02/reader.gd @@ -0,0 +1,7 @@ +extends SceneTree +func _initialize() -> void: call_deferred("run") +func run() -> void: + var fixture := preload("res://tests/fixtures/p08-02/mixed_world.gd").new() + var result: Dictionary = fixture.run(self,OS.get_environment("P08_PAIR_ROOT"),"reader",OS.get_environment("P08_OPERATIONS") == "on",OS.get_environment("P08_CASE") == "refusal") + print("P08 mixed reader: %d checks, %d failures" % [result.checks,result.failures]) + quit(0 if result.failures == 0 else 1) diff --git a/tests/fixtures/p08-02/reader.gd.uid b/tests/fixtures/p08-02/reader.gd.uid new file mode 100644 index 0000000..bd22b14 --- /dev/null +++ b/tests/fixtures/p08-02/reader.gd.uid @@ -0,0 +1 @@ +uid://741t47oxl3ct diff --git a/tests/fixtures/p08-02/writer.gd b/tests/fixtures/p08-02/writer.gd new file mode 100644 index 0000000..782c8aa --- /dev/null +++ b/tests/fixtures/p08-02/writer.gd @@ -0,0 +1,7 @@ +extends SceneTree +func _initialize() -> void: call_deferred("run") +func run() -> void: + var fixture := preload("res://tests/fixtures/p08-02/mixed_world.gd").new() + var result: Dictionary = fixture.run(self,OS.get_environment("P08_PAIR_ROOT"),"writer",OS.get_environment("P08_OPERATIONS") == "on",OS.get_environment("P08_CASE") == "refusal") + print("P08 mixed writer: %d checks, %d failures" % [result.checks,result.failures]) + quit(0 if result.failures == 0 else 1) diff --git a/tests/fixtures/p08-02/writer.gd.uid b/tests/fixtures/p08-02/writer.gd.uid new file mode 100644 index 0000000..88d3fdf --- /dev/null +++ b/tests/fixtures/p08-02/writer.gd.uid @@ -0,0 +1 @@ +uid://csw2850g6gjct diff --git a/tests/s1/test_p08_combined_migration.gd b/tests/s1/test_p08_combined_migration.gd new file mode 100644 index 0000000..dbec37d --- /dev/null +++ b/tests/s1/test_p08_combined_migration.gd @@ -0,0 +1,15 @@ +extends SceneTree +func _initialize() -> void: call_deferred("run") +func run() -> void: + var checks := 0 + var failures := 0 + for operations: bool in [false,true]: + for refusal: bool in [false,true]: + var output := ProjectSettings.globalize_path("user://p08-suite-%d-%s-%s" % [Time.get_ticks_usec(),operations,refusal]) + for role: String in ["writer","reader"]: + var fixture := preload("res://tests/fixtures/p08-02/mixed_world.gd").new() + var result: Dictionary = fixture.run(self,output,role,operations,refusal) + checks += result.checks; failures += result.failures + if result.failures > 0: break + print("P08 combined migration: %d checks, %d failures" % [checks,failures]) + quit(0 if failures == 0 else 1) diff --git a/tests/s1/test_p08_combined_migration.gd.uid b/tests/s1/test_p08_combined_migration.gd.uid new file mode 100644 index 0000000..36d825f --- /dev/null +++ b/tests/s1/test_p08_combined_migration.gd.uid @@ -0,0 +1 @@ +uid://bq08mixed02wld diff --git a/tools/check_p08_migration_evidence.py b/tools/check_p08_migration_evidence.py new file mode 100644 index 0000000..bbfb1da --- /dev/null +++ b/tools/check_p08_migration_evidence.py @@ -0,0 +1,369 @@ +#!/usr/bin/env python3 +"""Read-only artifact integrity gate. Success NEVER accepts original P08-02.""" +import argparse +import hashlib +import importlib.util +import json +import math +import os +from pathlib import Path +import re +import sys + +sys.dont_write_bytecode = True +HERE = Path(__file__).resolve().parent +CONTRACT = 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json' +SPEC_SHA = '19f02bb7912a4d7354e565186f02366fc1d4dd698468d26142e8204fd14ebfb0' +CONDITIONS = ('mixed_construction','occupancy_change','actual_actor_positions', + 'combined_rescue_logistics_ev','no_overlap_orphan_loss','unsolvable_preservation') +MEMBERS = ('world.bin','manifest.json','thumbnail.png','journal.log') +CASES = ('off-positive','off-refusal','on-positive','on-refusal') +LABELS = ['checkpoint'] + ['minute-%03d' % n for n in range(1,161)] + ['events'] + +def sha(path): return hashlib.sha256(Path(path).read_bytes()).hexdigest() +def load(path): return json.loads(Path(path).read_bytes()) +def module(name, path): + spec = importlib.util.spec_from_file_location(name,path) + obj = importlib.util.module_from_spec(spec); spec.loader.exec_module(obj); return obj + +def decode(node): + """Only decode for semantic checks; whole typed nodes remain comparison authority.""" + t = node['type'] + if t == 'Dictionary': + pairs = [(decode(k),decode(v)) for k,v in node['entries']] + keys = [json.dumps(k,sort_keys=True) for k,v in pairs] + if len(keys) != len(set(keys)): raise ValueError('duplicate typed dictionary key') + return {k:v for k,v in pairs} + if t == 'Array': return [decode(n) for n in node['items']] + text = node['text'] + if t in ('String','StringName'): return json.loads(text.removeprefix('&')) + if t == 'int': return int(text) + if t == 'float': return float(text) + if t == 'bool': + if text not in ('true','false'): raise ValueError('invalid bool') + return text == 'true' + if t == 'Nil': return None + if t in ('Vector2i','Vector3i','Vector2','Vector3'): + match = re.fullmatch(re.escape(t)+r'\(([^()]*)\)',text) + parts = match.group(1).split(',') if match else [] + integer = t.endswith('i') + number = r'[+-]?[0-9]+' if integer else r'[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?' + if len(parts) != int(t[6]) or any(not re.fullmatch(number,x.strip()) for x in parts): + raise ValueError('invalid typed vector '+t) + values = [(int if integer else float)(x.strip()) for x in parts] + if not integer and not all(math.isfinite(x) for x in values): + raise ValueError('invalid typed vector '+t) + return values + # Preserve unfamiliar scalar types verbatim; never silently drop them. + return (t,text) + +def indexed(rows, key): + result = {row[key]:row for row in rows} + if len(result) != len(rows): raise ValueError('duplicate '+key) + return result + +def semantics(snapshot, identity, initial=False): + """Independent ID/cell/reference checks on actual serialized Runtime state.""" + r = snapshot['systems']['runtime'] + guests = indexed(r['guests']['guests'],'guest_id') + staff = indexed(r['staff']['staff'],'staff_id') + if set(guests) & set(staff): raise ValueError('duplicate actor across domains') + jobs = indexed(r['construction']['jobs'],'job_id') + facilities = indexed(r['construction']['facilities'],'instance_id') + operational = indexed(r['facilities']['instances'],'instance_id') + owners = {k:v['occupied_cells'] for k,v in facilities.items()} + owners.update({k:v['reserved_cells'] for k,v in jobs.items() if v['state'] not in ('COMPLETED','CANCELLED')}) + occupied = indexed(r['world']['occupancies'],'entity_id') + cells = set() + for owner,row in occupied.items(): + actual = {tuple(c) for c in row['cells']} + if len(actual) != len(row['cells']) or cells & actual: raise ValueError('overlap occupancy') + cells |= actual + if owner not in owners or actual != {tuple(c) for c in owners[owner]}: raise ValueError('orphan occupancy/ownership') + if set(owners) != set(occupied): raise ValueError('lost occupancy owner') + for fid,facility in operational.items(): + if fid not in facilities or facility['occupied_cells'] != facilities[fid]['occupied_cells']: + raise ValueError('operational/construction ownership mismatch') + for jid in identity['jobs']: + if jid not in jobs: raise ValueError('lost paid job') + starting = jid == identity['starting_job'] + stage_ok = (jobs[jid]['progress'] == 0 and jobs[jid]['state'] == 'PLANNED') if starting else (jobs[jid]['progress'] > 0 and jobs[jid]['state'] == 'BUILDING') + if initial and (not stage_ok or not jobs[jid]['construction_cost'] or len(jobs[jid]['reserved_cells']) != 6): + raise ValueError('missing paid old-size progress/reservation') + if identity['starting_job'] not in identity['jobs']: raise ValueError('missing starting stage') + budget = identity['diagnostic_budget'] + total = {key:sum(row['cost'].get(key,0) for row in budget['costs']) for key in ('gold','materials')} + total['gold'] += budget['signing'] + budget['food_cost'] + deficit = {key:max(0,value-budget['initial'][key]) for key,value in total.items() if value > budget['initial'][key]} + if budget['seed'] != 42588 or budget['food_quantity'] != 8 or total != budget['needed'] or deficit != budget['deltas']: + raise ValueError('diagnostic resource derivation mismatch') + if len(identity['jobs']) < 2 or identity['dorm'] not in facilities: raise ValueError('mixed jobs/completed facility missing') + tasks = indexed(r['staff']['tasks'],'task_id') + if 'patient' not in staff or 'carrier' not in staff: raise ValueError('lost patient/carrier') + rescue = tasks[identity['rescue_task']] + if rescue['target_entity_id'] != 'patient' or rescue['rescue_facility_id'] not in facilities: + raise ValueError('rescue patient/destination reference') + if initial and (rescue['state'] != 'MOVING' or rescue['assigned_staff_id'] != 'carrier' or staff['patient']['health'] != 0): + raise ValueError('rescue is not actually in flight') + inventories = indexed(r['inventory']['inventories'],'owner_inventory_id') + requests = indexed(r['logistics_requests'],'request_id') + reservations = indexed(r['inventory']['reservations'],'reservation_id') + if not identity['logistics_requests']: raise ValueError('missing original food request') + for rid in identity['logistics_requests']: + req = requests[rid] + for endpoint in ('source_inventory_id','destination_inventory_id'): + value = req[endpoint] + if not value.startswith('facility.inventory:') or value.removeprefix('facility.inventory:') not in operational: + raise ValueError('orphan cargo endpoint') + state = req['state'] + carrier_id = req['assigned_carrier_id'] + if state == 'RESERVED' and carrier_id: + raise ValueError('RESERVED carrier contradiction') + if state in ('RESERVED','DELIVERING'): + ids = req['reservation_ids'] + if not ids or len(ids) != len(set(ids)) or any(x not in reservations for x in ids): + raise ValueError(state+' missing/duplicate reservation') + totals = {} + for reservation_id in ids: + row = reservations[reservation_id] + if (row['requester_id'] != rid or row['owner_inventory_id'] != req['source_inventory_id'] + or row['stack_id'] not in req['reserved_stack_ids'] + or row['state'] != ('ACTIVE' if state == 'RESERVED' else 'CONSUMED') + or type(row['quantity']) is not int or row['quantity'] <= 0): + raise ValueError(state+' reservation crosslink mismatch') + totals[row['item_id']] = totals.get(row['item_id'],0)+row['quantity'] + if totals != req['item_requirements'] or set(req['reserved_stack_ids']) != {reservations[x]['stack_id'] for x in ids}: + raise ValueError(state+' reservation quantity mismatch') + if state == 'RESERVED': + source_stacks = indexed(inventories.get(req['source_inventory_id'],{}).get('stacks',[]),'stack_id') + for reservation_id in ids: + row = reservations[reservation_id]; stack = source_stacks.get(row['stack_id'],{}) + if (stack.get('item_id') != row['item_id'] or stack.get('quantity',0) < row['quantity'] + or stack.get('reserved_quantity',0) < row['quantity']): + raise ValueError('RESERVED cargo/reservation mismatch') + else: + carrier = staff.get(carrier_id,{}) + cargo = inventories.get(carrier.get('carried_inventory_id'),{}) + cargo_totals = {} + for stack in cargo.get('stacks',[]): + cargo_totals[stack['item_id']] = cargo_totals.get(stack['item_id'],0)+stack['quantity'] + if not carrier or cargo_totals != totals: + raise ValueError('DELIVERING carrier/cargo mismatch') + trips = indexed(r['elevators']['trips'],'request_id') + actors = set(guests) | set(staff) + for trip in trips.values(): + actor = trip['actor_id'] + if actor not in actors and actor.removeprefix('guest:').removeprefix('staff:') not in actors: + raise ValueError('orphan EV actor') + if initial: + if len(identity['ev_requests']) != 5 or not set(identity['ev_requests']) <= set(trips): raise ValueError('lost EV request') + if sum(len(c['boarded']) for c in r['elevators']['shafts']) != 4: raise ValueError('EV passengers/queue absent') + if 'p08.walker' not in guests or not r['movement_credits']: raise ValueError('mid-route guest/credit absent') + return r + +def typed_differences(a, b, path=()): + if a == b: return [] + if a.get('type') != b.get('type'): return [path] + if a['type'] == 'Dictionary': + aa = {str(decode(k)):(k,v) for k,v in a['entries']}; bb = {str(decode(k)):(k,v) for k,v in b['entries']} + if aa.keys() != bb.keys(): return [path] + differences = [] + for k in aa: + if aa[k][0] != bb[k][0]: differences.append(path+(k,'@key-type')) + differences += typed_differences(aa[k][1],bb[k][1],path+(k,)) + return differences + if a['type'] == 'Array' and a.get('element_type') == b.get('element_type') and a.get('element_class') == b.get('element_class'): + if len(a['items']) != len(b['items']): return [path] + return [p for i,(x,y) in enumerate(zip(a['items'],b['items'])) for p in typed_differences(x,y,path+(str(i),))] + return [path] + +def migration_revisions(before, after, refusal): + """WorldService._mark_dirty assigns domain counters the global epoch. + + r04: (world, occupancy, support) (144,144,142) -> (150,150,150). + Only an actual occupancy rewrite can justify the support epoch delta. + Whole typed comparison still rejects every other unapproved field. + """ + old, new = before['world']['revisions'], after['world']['revisions'] + keys = ('world','occupancy','support') + if any(type(row[k]) is not int or row[k] < 0 for row in (old,new) for k in keys): + raise ValueError('invalid revision epoch') + if refusal: + if any(old[k] != new[k] for k in keys): + raise ValueError('refusal revision changed') + return + changed = before['world']['occupancies'] != after['world']['occupancies'] + if (not changed or not old['support'] <= old['world'] or not old['occupancy'] <= old['world'] + or new['world'] <= old['world'] or new['occupancy'] != new['world'] + or new['support'] != new['world'] or new['support'] < old['support']): + raise ValueError('support epoch/occupancy mismatch') + +# Explicit geometry changes only. No actor, economy, request, event or route wildcard. +# This fixture deliberately places actors away from removed cells: NO route changes approved. +GEOMETRY = [ + r'systems/runtime/world/occupancies/\d+/cells(?:/.*)?', + r'systems/runtime/world/dirty_cells(?:/.*)?', + r'systems/runtime/world/revisions/(world|occupancy)', + r'systems/runtime/construction/jobs/\d+/reserved_cells(?:/.*)?', + r'systems/runtime/construction/facilities/\d+/occupied_cells(?:/.*)?', + r'systems/runtime/facilities/instances/\d+/occupied_cells(?:/.*)?', +] + +def check(project_root, evidence_dir): + root, evidence = Path(project_root).resolve(), Path(evidence_dir).resolve() + deficits = [] + def need(ok, reason): + if not ok: deficits.append(reason) + def guarded(label, fn): + try: return fn() + except (KeyError,ValueError,TypeError,OSError,IndexError,AttributeError) as exc: + deficits.append(label+': '+str(exc)); return None + index = guarded('index',lambda:load(evidence/'index.json')) + if not isinstance(index,dict): return deficits or ['index is not an object'] + def artifact(name): + if not isinstance(name,str) or Path(name).is_absolute() or '..' in Path(name).parts: raise ValueError('unsafe artifact path') + path = evidence/name + if not path.is_file() or any(p.is_symlink() for p in [path,*path.parents] if p != evidence.parent): raise ValueError('missing/aliased artifact '+name) + pin = index['artifacts'][name] + if pin != {'bytes':path.stat().st_size,'sha256':sha(path)}: raise ValueError('artifact hash '+name) + return path + def document(name): return load(artifact(name)) + need(index.get('schema') == 'p08-evidence-v1','schema') + need(index.get('acceptance') == 'pending','integrity is not unit acceptance') + need(index.get('collection_error') is None,'collection failure') + def source_gate(): + runner = module('p08_readonly_source',root/'tools/run_all_tests.py') + actual = {n:p['sha256'] for n,p in runner.source_refs(root).items()} + need(index['source_map'] == actual,'wrong/stale/incomplete final source map') + spec = load(root/'docs/spec/acceptance/P08-02.json') + need(index['spec_sha256'] == SPEC_SHA == hashlib.sha256((root/'docs/spec/acceptance/P08-02.json').read_text().strip().encode()).hexdigest(),'frozen spec SHA') + for name,pin in spec['current_source_pins'].items(): need(actual.get(name) == pin,'frozen production source '+name) + inputs = {str(p.relative_to(root)):sha(p) for p in (root/'docs/spec/inputs/p08-02').iterdir() if p.is_file()} + need(index['inputs'] == inputs,'frozen input map') + need(index['marker_contract_sha256'] == sha(root/CONTRACT),'marker contract source') + need(re.fullmatch('[0-9a-f]{40}',index['commit']) is not None,'submitted commit identity') + guarded('source',source_gate) + for name in index.get('artifacts',{}): guarded('artifact '+name,lambda name=name:artifact(name)) + markers = module('p08_evidence_markers',root/'tools/strict_test_markers.py') + contracts = markers.load_contracts(root/CONTRACT) + pids = set() + def phase(name, role): + receipt = document(name); pid = receipt['actual_pid'] + need(type(pid) is int and pid > 0 and pid not in pids,'distinct native PID '+name); pids.add(pid) + parent = str(Path(name).parent) + prefix = '' if parent == '.' else parent+'/' + folder = prefix+receipt['phase']+'/' + start,end,outer = [document(folder+n) for n in ('started.json','ended.json','outer-owned-finally.json')] + for row in (start,end): + need(row['actual_pid'] == pid and row['command'] == receipt['command'],'raw lifecycle PID/argv '+name) + need(end.get('exit_code') == 0 and end.get('engine_ended') is True,'ended exit '+name) + need(start.get('live_identity_exact') is True or start.get('tracked_child_ended_before_initial_probe') is True,'native Popen identity '+name) + need(outer['actual_captured_pids'] == [pid] and outer['all_captured_ended'] is True and not outer['outer_cleanup_errors'] and outer['foreign_process_or_group_signals'] == 0,'owned cleanup '+name) + native = receipt['native_process'] + need(native['exit_code'] == 0 and native['helper_exit_code'] == 0 and native['timed_out'] is False and native['lifecycle_error'] is None,'native exit/errors '+name) + need(receipt['exit_code'] == 0 and receipt['engine_ended'] is True,'phase ended '+name) + if role: + test = 'tests/fixtures/p08-02/'+role+'.gd' + need(receipt['test'] == test and receipt['source_sha256'] == index['source_map'][test],'phase source '+name) + raw = artifact(prefix+receipt['log']).read_text() + parsed = markers.parse_raw(test,raw,{test:{'template':'P08 mixed '+role+': checks, failures'}},receipt['source_sha256']) + need(not parsed['errors'],'strict completion '+name+': '+str(parsed['errors'])) + profile = receipt['actual_private_profile']['report'] + need(profile['fresh_before_parent_initialize'] is True and profile['profile_writes_before_guard'] == 0 and profile['actual_user_data_dir'] == profile['expected_user_data_dir'],'guarded fresh profile '+name) + return receipt + def case_gate(case): + name = case['id']; prefix = case['directory']+'/' + need(name == case['operations']+'-'+case['kind'],'case identity '+name) + need(len(case['phases']) == 2,'writer and reader phases '+name) + receipts = [phase(n,r) for n,r in zip(case['phases'],('writer','reader'))] + def typed(name): + artifact(prefix+name+'.var') + return document(prefix+name+'.json') + identity = decode(typed('identity')) + original = typed('original'); after_live = typed('after-live') + need(original == after_live,'live partial batch '+name) + before_members,after_members = [decode(typed(n)) for n in ('before-members','after-members')] + for package in ('original','result','backup'): + for member in MEMBERS: artifact(prefix+package+'/formal/'+member) + for member in MEMBERS: + need(before_members['/'+member] == sha(artifact(prefix+'original/formal/'+member)),'original overwritten '+name+'/'+member) + need(after_members['/'+member] == sha(artifact(prefix+'result/formal/'+member)),'result member '+name+'/'+member) + need(after_members['.pre_migration.bak/'+member] == sha(artifact(prefix+'backup/formal/'+member)),'backup overwritten '+name+'/'+member) + refusal = case['kind'] == 'refusal' + if refusal: need(before_members == after_members,'refusal original/backup overwritten '+name) + source = semantics(decode(original),identity,True) + need(source['operations']['enabled'] == (case['operations']=='on'),'missing actual Operations mode '+name) + for role,receipt in zip(('writer','reader'),receipts): + proc = decode(typed(role+'-process')) + need(proc['pid'] == receipt['actual_pid'] and proc['failures'] == 0 and proc['operations'] == (case['operations']=='on') and proc['refusal'] == refusal,'fixture/native binding '+name+'/'+role) + checkpoint = typed('writer-checkpoint') + migrated = decode(checkpoint)['systems']['runtime'] + migration_revisions(source,migrated,refusal) + if refusal: need(original == checkpoint,'partial earlier candidate '+name) + else: + for path in typed_differences(original,checkpoint): + support_epoch = path == ('systems','runtime','world','revisions','support') + need(support_epoch or any(re.fullmatch(pattern,'/'.join(path)) for pattern in GEOMETRY),'unapproved typed migration delta '+name+'/'+ '/'.join(path)) + migrated = decode(checkpoint)['systems']['runtime'] + jobs = indexed(migrated['construction']['jobs'],'job_id') + need(all(len(jobs[j]['reserved_cells']) == 4 for j in identity['jobs']),'positive batch not applied '+name) + for label in LABELS: + writer,reader = typed('writer-'+label),typed('reader-'+label) + need(writer == reader,'cross-PID full typed state/events '+name+'/'+label) + need(sha(artifact(prefix+'writer-'+label+'.var')) == sha(artifact(prefix+'reader-'+label+'.var')),'cross-PID raw Variant '+name+'/'+label) + if label != 'events': semantics(decode(writer),identity) + final = decode(typed('writer-minute-160'))['systems']['runtime'] + need(indexed(final['staff']['staff'],'staff_id')['patient']['health'] == 100,'patient lost/untreated '+name) + final_requests = indexed(final['logistics_requests'],'request_id') + need(all(final_requests[i]['state']=='COMPLETED' for i in identity['logistics_requests']),'original cargo completion '+name) + events = decode(typed('writer-events')) + for rid in identity['ev_requests']: + need(sum(e['event_type']=='ELEVATOR_ARRIVED' and e['payload'].get('request_id')==rid for e in events)==1,'EV original request exactly once '+name+'/'+rid) + cases = index.get('cases',[]) + need([c.get('id') for c in cases] == list(CASES),'missing/duplicate ON/OFF positive/refusal case') + for case in cases: guarded('case '+str(case.get('id')),lambda case=case:case_gate(case)) + for condition in CONDITIONS: + expected = [c for c in CASES if condition != 'unsolvable_preservation' or c.endswith('refusal')] + need(index.get('conditions',{}).get(condition) == expected,'condition mapping '+condition) + def os_gate(): + proof = document(index['os_proof']) + need(proof['commit'] == index['commit'] and proof['source_map'] == index['source_map'],'OS current candidate') + need(proof['input_method'] == 'native_os' and proof['setup'] == 'controlled_old_save','OS input provenance/setup') + actions = proof['actions'] + need([a['action'] for a in actions] == ['preview','cancel','confirm','changed_source_rejection'],'OS actions missing') + for action in actions: + raw = document(action['input_receipt']) + need(raw['method'] == 'native_os' and raw['event_type'] in ('mouse','keyboard') and type(raw['target_pid']) is int and raw['target_pid'] > 0 and raw['timestamp_ns'] > 0,'fake OS signal') + for key in ('screen_before','screen_after','model_before','model_after','formal_before','formal_after'): artifact(action[key]) + if action['action'] in ('preview','cancel','changed_source_rejection'): + need(document(action['formal_before']) == document(action['formal_after']),'OS formal preservation '+action['action']) + guarded('OS proof',os_gate) + def regression_gate(): + proof = document(index['regression']); run_input = document(proof['input']); ended = document(proof['ended']) + need(run_input['selected_source_map'] == index['source_map'],'final regression source map') + need(proof['commit'] == index['commit'],'final regression commit') + need(not ended['error'] and not ended['cleanup_errors'] and not ended['guard_errors'],'regression failure') + runner = module('p08_regression_roster',root/'tools/run_all_tests.py') + roster = runner.roster(root) + need(run_input['roster'] == roster,'full final roster') + rows = [x for x in ended['phases'] if x.get('test')] + need([x['test'] for x in rows] == roster,'full final regression receipt') + for row in rows: + raw = artifact(proof['directory']+'/'+row['log']).read_text() + need(not markers.parse_raw(row['test'],raw,contracts,index['source_map'][row['test']])['errors'],'regression strict '+row['test']) + need(row['exit_code'] == 0 and row['engine_ended'] is True,'regression native exit '+row['test']) + guarded('final regression',regression_gate) + return sorted(set(deficits)) + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--verify-only',action='store_true',required=True) + parser.add_argument('--project-root',type=Path,default=HERE.parent) + args = parser.parse_args(argv) + build = os.environ.get('BUILD_OUTPUT_DIR') + deficits = ['BUILD_OUTPUT_DIR missing'] if not build else check(args.project_root,Path(build)/'p08-evidence') + print(json.dumps({'integrity':'deficient' if deficits else 'verified','deficits':deficits, + 'acceptance':'pending','conditions':{c:'pending' for c in CONDITIONS}},ensure_ascii=False,indent=2)) + return 1 if deficits else 0 + +if __name__ == '__main__': raise SystemExit(main()) diff --git a/tools/run_p08_mixed_migration.py b/tools/run_p08_mixed_migration.py new file mode 100644 index 0000000..6ccde4c --- /dev/null +++ b/tools/run_p08_mixed_migration.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""Root-only native collection. Does not run regression or claim acceptance.""" +import argparse +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import shutil +import subprocess +import sys +import time + +sys.dont_write_bytecode = True +HERE = Path(__file__).resolve().parent +CONTRACT = 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json' +CONDITIONS = ('mixed_construction', 'occupancy_change', 'actual_actor_positions', + 'combined_rescue_logistics_ev', 'no_overlap_orphan_loss', 'unsolvable_preservation') + +def module(name, path): + spec = importlib.util.spec_from_file_location(name, path) + obj = importlib.util.module_from_spec(spec) + spec.loader.exec_module(obj) + return obj + +def absolute(value): + path = Path(value) + if not path.is_absolute(): + raise argparse.ArgumentTypeError('absolute path required') + return path.resolve() + +def collect(args): + root, out = args.project_root, args.output + r = module('p08_standard_runner', root/'tools/run_all_tests.py') + commit = subprocess.check_output(['git', '-C', str(root), 'rev-parse', 'HEAD'], text=True).strip() + r.require(commit == args.commit, 'actual final submitted commit required') + r.require(not out.exists(), 'fresh output; never overwrite earlier proof') + refs = r.source_refs(root) + outer = r.module('p08_owned_outer', r.SUPPORT/'owned_outer.py') + r.require(not outer.native.helper['live_engines'](), 'one native engine') + engine = shutil.which(args.godot) + r.require(engine is not None, 'native engine exists') + engine = str(Path(engine).resolve()) + out.mkdir(parents=True) + stage = out/'stage'; stage.mkdir() + (out/'starters').mkdir(); (out/'bootstrap').mkdir() + for name, pin in r.read(r.SUPPORT/'bootstrap-scripts.json').items(): + data = pin['text'].encode() + r.require(hashlib.sha256(data).hexdigest() == pin['sha256'], 'bootstrap pin') + (out/'bootstrap'/name).write_bytes(data) + for name, pin in refs.items(): + target = stage/name; target.parent.mkdir(parents=True, exist_ok=True) + r.copy_source(Path(pin['path']), target) + r.require(r.sha(target) == pin['sha256'], 'whole copied source '+name) + expected = {n: {'bytes':v['bytes'], 'sha256':v['sha256']} for n,v in refs.items()} + selected = {'root_before':refs, 'map':{n:v['sha256'] for n,v in refs.items()}, 'timeout':180} + r.require(r.inventory(stage, stage=True) == expected, 'complete private source') + protected = r.default_player(root); public = (stage/'project.godot').read_bytes() + paths = ['res://'+n for n in sorted(refs) if n.endswith('.gd')] + r.write(out/'parse-paths.json', paths) + markers = r.module('p08_strict_markers', root/'tools/strict_test_markers.py') + contracts = markers.load_contracts(root/CONTRACT) + for role in ('writer','reader'): + name = 'tests/fixtures/p08-02/'+role+'.gd' + contracts[name] = {'test':name, 'template':'P08 mixed '+role+': checks, failures', 'required':True} + policy = r.read(r.SUPPORT/'suite-policy.json') + diagnostics = r.module('p08_diagnostics', r.SUPPORT/'diagnostic_gate.py') + owners = r.module('p08_owners', r.SUPPORT/'ordinary_proof_gate.py') + audio = r.module('p08_audio', r.SUPPORT/'audio_evidence_gate.py') + r.write(out/'runner-input.json', {'protocol':'p08-mixed-v1', 'commit':commit, + 'selected_source_map':selected['map'], 'engine':engine, 'engine_sha256':r.sha(engine), + 'owner_pid':os.getpid(), 'default_player':protected, 'diagnostic_seed':42588}) + phases = []; previous = {}; cases = []; error = None + deadline = time.monotonic()+900 + saved_env = {k:os.environ.get(k) for k in ('P08_PAIR_ROOT','P08_OPERATIONS','P08_CASE')} + def phase(label, test): + remaining = int(deadline-time.monotonic()) + r.require(remaining >= (180 if label == 'import' else 1), '900s collection deadline') + selected['timeout'] = min(180,remaining) + phase_out = out/'runs'/label + phase_out.mkdir(parents=True) + (phase_out/'starters').mkdir(); (phase_out/'bootstrap').mkdir() + for original in (out/'bootstrap').iterdir(): + r.copy_source(original,phase_out/'bootstrap'/original.name) + r.require(r.sha(original)==r.sha(phase_out/'bootstrap'/original.name),'bootstrap full copy') + r.write(phase_out/'parse-paths.json',paths) + row = r.execute_phase(engine, stage, phase_out, label, test, selected, expected, previous, + policy, contracts, paths, public, protected, root, outer, markers, diagnostics, owners, audio) + phases.append('runs/'+label+'/'+label+'-receipt.json') + r.require(not row['failed'], 'strict phase failed: '+label) + try: + phase('import', None); phase('parse', None) + for operations in ('off','on'): + for case in ('positive','refusal'): + name = operations+'-'+case; pair = out/'pairs'/name + os.environ.update(P08_PAIR_ROOT=str(pair), P08_OPERATIONS=operations, P08_CASE=case) + case_phases = [] + preserved = {} + for role in ('writer','reader'): + label = name+'-'+role + test = 'tests/fixtures/p08-02/'+role+'.gd' + phase(label, test) + actual = r.inventory(pair) + r.require(all(actual.get(n) == pin for n,pin in preserved.items()), 'reader preserved every writer byte') + preserved = actual + case_phases.append(phases[-1]) + cases.append({'id':name, 'operations':operations, 'kind':case, + 'directory':'pairs/'+name, 'phases':case_phases, 'files':preserved}) + except BaseException as caught: + error = type(caught).__name__+': '+str(caught) + finally: + cleanup, cleanup_errors = outer.finish_captured(outer.OWNED) + for key,value in saved_env.items(): + if value is None: os.environ.pop(key, None) + else: os.environ[key] = value + r.require(r.source_refs(root) == refs and r.default_player(root) == protected, 'protected root/profile unchanged') + r.write(out/'runner-ended.json', {'error':error, 'cleanup':cleanup, 'cleanup_errors':cleanup_errors, + 'phases':phases, 'formal_acceptance':False, 'native_Windows':'pending'}) + inputs = {str(p.relative_to(root)):r.sha(p) for p in (root/'docs/spec/inputs/p08-02').iterdir() if p.is_file()} + # The collector is intentionally incomplete until Root supplies OS and regression artifacts. + r.write(out/'index.json', {'schema':'p08-evidence-v1', 'commit':commit, + 'source_map':selected['map'], 'inputs':inputs, 'marker_contract_sha256':r.sha(root/CONTRACT), + 'spec_sha256':hashlib.sha256((root/'docs/spec/acceptance/P08-02.json').read_text().strip().encode()).hexdigest(), + 'cases':cases, 'phases':phases, 'conditions':{c:[x['id'] for x in cases if c != 'unsolvable_preservation' or x['kind']=='refusal'] for c in CONDITIONS}, + 'artifacts':{str(p.relative_to(out)):{'bytes':p.stat().st_size,'sha256':r.sha(p)} + for p in out.rglob('*') if p.is_file() and 'stage' not in p.relative_to(out).parts}, + 'os_proof':None, 'regression':None, + 'acceptance':'pending', 'collection_error':error}) + return 1 if error or cleanup_errors else 0 + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--project-root', type=absolute, required=True) + parser.add_argument('--output', type=absolute, required=True) + parser.add_argument('--commit', required=True) + parser.add_argument('--godot', default='godot') + parser.add_argument('--collect', action='store_true', required=True) + return collect(parser.parse_args(argv)) + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/tools/test_p08_migration_evidence.py b/tools/test_p08_migration_evidence.py new file mode 100644 index 0000000..699b0f0 --- /dev/null +++ b/tools/test_p08_migration_evidence.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +"""Synthetic evidence ONLY: disposable end-to-end check() positives and corruptions. + +No Godot/OS execution, no actual acceptance proof. Mutations update hashes so the +semantic gate, not merely a stale digest, must reject invalid state. +""" +import copy +import hashlib +import json +from pathlib import Path +import tempfile +import unittest +import check_p08_migration_evidence as c + +ROOT = Path(__file__).resolve().parent.parent +BANNER = 'Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org\n' +R04 = json.loads((ROOT/'docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r04.json').read_text()) +ACTUAL = R04['actual_readonly_diagnostics'] +REQUESTS = ACTUAL['CARGO_ENDPOINT_READONLY_DIAGNOSTIC.json'][0]['requests'] +REVISIONS = ACTUAL['ACTUAL_WORLD_REVISION_DELTAS.json'][0] + +def typed(value): + if isinstance(value,tuple): return {'type':'Vector3i','text':'Vector3i('+', '.join(map(str,value))+')'} + if isinstance(value,dict): return {'type':'Dictionary','entries':[[typed(k),typed(v)] for k,v in value.items()]} + if isinstance(value,list): return {'type':'Array','element_type':0,'element_class':'','items':[typed(x) for x in value]} + kind = 'Nil' if value is None else 'bool' if type(value) is bool else 'int' if type(value) is int else 'String' + return {'type':kind,'text':json.dumps(value)} + +class Proof: + def __init__(self, path): + self.path = path + runner = c.module('p08_synthetic_source',ROOT/'tools/run_all_tests.py') + self.runner = runner + source = {n:p['sha256'] for n,p in runner.source_refs(ROOT).items()} + self.index = {'schema':'p08-evidence-v1','commit':'e196578c1a4ad2fdf84642565bd102e524be386f', + 'source_map':source,'spec_sha256':c.SPEC_SHA,'marker_contract_sha256':c.sha(ROOT/c.CONTRACT), + 'inputs':{str(p.relative_to(ROOT)):c.sha(p) for p in (ROOT/'docs/spec/inputs/p08-02').iterdir() if p.is_file()}, + 'cases':[],'conditions':{k:[x for x in c.CASES if k!='unsolvable_preservation' or x.endswith('refusal')] for k in c.CONDITIONS}, + 'artifacts':{},'acceptance':'pending','collection_error':None,'phases':[], + 'os_proof':'os.json','regression':'regression.json'} + for number,name in enumerate(c.CASES): self.case(name,number) + self.os(); self.regression(); self.save() + def put(self,name,value): + path = self.path/name; path.parent.mkdir(parents=True,exist_ok=True) + path.write_bytes(value if isinstance(value,bytes) else json.dumps(value).encode()) + self.index['artifacts'][name] = {'bytes':path.stat().st_size,'sha256':c.sha(path)} + def variant(self,prefix,name,value): + node = typed(value) + self.put(prefix+name+'.json',node) + # Deliberately not a native Variant: this is an isolated Python proof fixture. + self.put(prefix+name+'.var',json.dumps(node).encode()) + def save(self): (self.path/'index.json').write_text(json.dumps(self.index)) + def state(self,operations): + jobs = [{'job_id':j,'state':'PLANNED' if j=='j2' else 'BUILDING','progress':0 if j=='j2' else 1266,'reserved_cells':[(x,2,0) for x in range(offset,offset+6)],'construction_cost':{'gold':1}} + for j,offset in [('j1',0),('j2',10)]] + facilities = [{'instance_id':'dorm','occupied_cells':[[30,0,1]]},{'instance_id':'clinic','occupied_cells':[[31,0,1]]}] + facilities += [{'instance_id':'facility_00000005','occupied_cells':[(32,0,1)]}, + {'instance_id':'facility_00000006','occupied_cells':[(33,0,1)]}] + guests = [{'guest_id':g} for g in ['p08.walker']+['g'+str(n) for n in range(5)]] + staff = [{'staff_id':'patient','health':0},{'staff_id':'carrier'},{'staff_id':'staff_gina','carried_inventory_id':'cargo:staff_gina'}] + r = {'operations':{'enabled':operations},'guests':{'guests':guests},'staff':{'staff':staff,'tasks':[ + {'task_id':'rescue','target_entity_id':'patient','rescue_facility_id':'clinic','state':'MOVING','assigned_staff_id':'carrier'}]}, + 'construction':{'jobs':jobs,'facilities':facilities},'facilities':{'instances':copy.deepcopy(facilities)}, + 'world':{'revisions':copy.deepcopy(REVISIONS['original_world_revisions']),'occupancies':[{'entity_id':j['job_id'],'cells':copy.deepcopy(j['reserved_cells'])} for j in jobs]+ + [{'entity_id':f['instance_id'],'cells':copy.deepcopy(f['occupied_cells'])} for f in facilities]}, + 'inventory':{'inventories':[{'owner_inventory_id':owner,'stacks':[{'stack_id':stack,'item_id':'ingredient/raw_food','quantity':4,'reserved_quantity':reserved}]} for owner,stack,reserved in + [('facility.inventory:facility_00000005','stack_00000001',4),('cargo:staff_gina','stack_00000002',0)]], + 'reservations':[{'reservation_id':req['reservation_ids'][0],'requester_id':req['request_id'], + 'owner_inventory_id':req['source_inventory_id'],'stack_id':'stack_00000001','item_id':'ingredient/raw_food', + 'quantity':4,'state':'CONSUMED' if req['state']=='DELIVERING' else 'ACTIVE'} for req in REQUESTS]}, + 'logistics_requests':copy.deepcopy(REQUESTS), + 'movement_credits':[{'credit':1000}], + 'elevators':{'trips':[{'request_id':'ev'+str(n),'actor_id':'g'+str(n)} for n in range(5)],'shafts':[{'boarded':['g0','g1','g2','g3']}]}} + return {'systems':{'runtime':r}} + def phase(self,name,role,pid): + folder = 'runs/'+name+'/'; test = 'tests/fixtures/p08-02/'+role+'.gd' + command = ['synthetic-native','--script',test] + start = {'actual_pid':pid,'command':command,'live_identity_exact':True,'event':'ENGINE_STARTED','owner_pid':1,'monotonic_ns':1} + end = dict(start,event='ENGINE_ENDED',monotonic_ns=2,exit_code=0,engine_ended=True) + self.put(folder+name+'/started.json',start); self.put(folder+name+'/ended.json',end) + self.put(folder+name+'/outer-owned-finally.json',{'actual_captured_pids':[pid],'all_captured_ended':True,'outer_cleanup_errors':[],'foreign_process_or_group_signals':0}) + self.put(folder+'raw.log',(BANNER+'P08 mixed '+role+': 1 checks, 0 failures\n').encode()) + receipt = {'phase':name,'actual_pid':pid,'command':command,'exit_code':0,'engine_ended':True,'test':test, + 'source_sha256':self.index['source_map'][test],'log':'raw.log', + 'native_process':{'exit_code':0,'helper_exit_code':0,'timed_out':False,'lifecycle_error':None}, + 'actual_private_profile':{'report':{'fresh_before_parent_initialize':True,'profile_writes_before_guard':0,'actual_user_data_dir':'diagnostic-private-'+name,'expected_user_data_dir':'diagnostic-private-'+name}}} + self.put(folder+name+'-receipt.json',receipt) + return folder+name+'-receipt.json' + def case(self,name,number): + operations = name.startswith('on-'); refusal = name.endswith('refusal'); prefix = 'pairs/'+name+'/' + phases = [self.phase(name+'-'+role,role,100+2*number+i) for i,role in enumerate(('writer','reader'))] + self.index['cases'].append({'id':name,'operations':'on' if operations else 'off','kind':'refusal' if refusal else 'positive','directory':prefix[:-1],'phases':phases}) + self.index['phases'] += phases + identity = {'starting_job':'j2','diagnostic_budget':{'seed':42588,'food_quantity':8,'food_cost':24,'signing':1,'costs':[{'cost':{'gold':2,'materials':1}}],'initial':{'gold':0,'materials':0},'needed':{'gold':27,'materials':1},'deltas':{'gold':27,'materials':1}},'jobs':['j1','j2'],'dorm':'dorm','rescue_task':'rescue','logistics_requests':['food'],'ev_requests':['ev'+str(n) for n in range(5)]} + identity['logistics_requests'] = [req['request_id'] for req in REQUESTS] + original = self.state(operations) + self.variant(prefix,'identity',identity); self.variant(prefix,'original',original); self.variant(prefix,'after-live',original) + before = {}; after = {} + for member in c.MEMBERS: + raw = ('synthetic '+member).encode() + for package in ('original','result','backup'): self.put(prefix+package+'/formal/'+member,raw) + before['/'+member] = after['/'+member] = hashlib.sha256(raw).hexdigest() + before['.pre_migration.bak/'+member] = after['.pre_migration.bak/'+member] = hashlib.sha256(raw).hexdigest() + self.variant(prefix,'before-members',before); self.variant(prefix,'after-members',after) + checkpoint = copy.deepcopy(original) + if not refusal: + r = checkpoint['systems']['runtime'] + r['world']['revisions'] = copy.deepcopy(REVISIONS['checkpoint_world_revisions']) + for job in r['construction']['jobs']: job['reserved_cells'] = job['reserved_cells'][:4] + for row in r['world']['occupancies'][:2]: row['cells'] = row['cells'][:4] + events = [{'event_type':'ELEVATOR_ARRIVED','payload':{'request_id':rid}} for rid in identity['ev_requests']] + for i,role in enumerate(('writer','reader')): + self.variant(prefix,role+'-process',{'pid':100+2*number+i,'failures':0,'checks':1,'operations':operations,'refusal':refusal}) + for label in c.LABELS: + state = copy.deepcopy(checkpoint) + if label == 'minute-160': + state['systems']['runtime']['staff']['staff'][0]['health'] = 100 + for req in state['systems']['runtime']['logistics_requests']: req['state'] = 'COMPLETED' + self.variant(prefix,role+'-'+label,events if label=='events' else state) + def os(self): + proof = {'commit':self.index['commit'],'source_map':self.index['source_map'],'input_method':'native_os','setup':'controlled_old_save','actions':[]} + for name in ('preview','cancel','confirm','changed_source_rejection'): + row = {'action':name,'input_receipt':'os/'+name+'/input.json'} + self.put(row['input_receipt'],{'method':'native_os','event_type':'mouse','target_pid':500,'timestamp_ns':1}) + for field in ('screen_before','screen_after','model_before','model_after','formal_before','formal_after'): + row[field] = 'os/'+name+'/'+field+'.json'; self.put(row[field],{'synthetic':'diagnostic'}) + proof['actions'].append(row) + self.put('os.json',proof) + def regression(self): + contracts = c.module('p08_synthetic_markers',ROOT/'tools/strict_test_markers.py').load_contracts(ROOT/c.CONTRACT) + roster = self.runner.roster(ROOT); rows = [] + for i,test in enumerate(roster): + contract = contracts[test] + def marker(template): + for key in ('checks','passed'): template = template.replace('<'+key+'>','1') + for key in ('failures','failed','skipped'): template = template.replace('<'+key+'>','0') + return template+'\n' + raw = BANNER+marker(contract['template']) + for auxiliary in contract.get('auxiliary',[]): raw += marker(auxiliary['template']) + if contract.get('json_report'): + raw += contract['json_report']['prefix']+json.dumps({'check_summary':{'total':1,'passed':1,'failed':0,'skipped':0},'status':'PASS','runtime_errors':[],'unmet_fixture_fields':[]})+'\n' + log = str(i)+'.log'; self.put('regression/'+log,raw.encode()) + rows.append({'test':test,'log':log,'exit_code':0,'engine_ended':True}) + self.put('regression/input.json',{'selected_source_map':self.index['source_map'],'roster':roster}) + self.put('regression/ended.json',{'error':None,'cleanup_errors':[],'guard_errors':[],'phases':rows}) + self.put('regression.json',{'commit':self.index['commit'],'directory':'regression','input':'regression/input.json','ended':'regression/ended.json'}) + +class EvidenceTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.temp = tempfile.TemporaryDirectory(prefix='p08-checker-') + cls.proof = Proof(Path(cls.temp.name)) + cls.baseline = copy.deepcopy(cls.proof.index) + @classmethod + def tearDownClass(cls): cls.temp.cleanup() + def test_positive_reaches_whole_check(self): + self.assertEqual(c.check(ROOT,self.proof.path),[]) + def test_strict_native_vectors(self): + for kind,good,expected in ( + ('Vector3i','Vector3i(0, -12, 6)',[0,-12,6]), + ('Vector2i','Vector2i(-2, 3)',[-2,3]), + ('Vector3','Vector3(1.5, -2e1, .25)',[1.5,-20,.25]), + ('Vector2','Vector2(0, 1e-3)',[0,.001])): + with self.subTest(kind=kind): + self.assertEqual(c.decode({'type':kind,'text':good}),expected) + invalid = [good.replace(kind,'WrongVector'),good+'tail',good.replace(',',',,',1), + good[:good.rfind(',')]+')',good.replace(')',', 4)')] + if kind.endswith('i'): invalid += [good.replace('(', '(1.0,',1), kind+'(1.0, '+', '.join(['2']*(len(expected)-1))+')'] + for text in invalid: + with self.subTest(text=text), self.assertRaisesRegex(ValueError,'invalid typed vector'): + c.decode({'type':kind,'text':text}) + + def test_r04_local_deficits_through_whole_check(self): + p = self.proof + def mutate_state(node, change): + state = c.decode(node); change(state['systems']['runtime']); return typed(state) + def bad_vector(node): + if node['type'] == 'Vector3i': + node['text'] = 'Vector3i(0, , 1)'; return True + children = ([n for pair in node['entries'] for n in pair] if node['type']=='Dictionary' + else node.get('items',[])) + return any(bad_vector(child) for child in children) + corruptions = [ + ('original','invalid typed vector',None), + ('original','orphan cargo endpoint',lambda r:r['logistics_requests'][0].update(destination_inventory_id='facility.inventory:absent')), + ('original','RESERVED missing/duplicate reservation',lambda r:r['inventory']['reservations'].pop()), + ('original','DELIVERING carrier/cargo mismatch',lambda r:r['logistics_requests'][0].update(assigned_carrier_id='patient')), + ('original','RESERVED carrier contradiction',lambda r:r['logistics_requests'][1].update(assigned_carrier_id='staff_gina')), + ('original','DELIVERING reservation quantity mismatch',lambda r:r['inventory']['reservations'][0].update(quantity=3)), + ('writer-checkpoint','support epoch/occupancy mismatch',lambda r:r['world']['revisions'].update(support=143)), + ] + for label,message,change in corruptions: + path = 'pairs/off-positive/'+label+'.json' + original = (p.path/path).read_bytes() + try: + node = json.loads(original) + if change: node = mutate_state(node,change) + else: self.assertTrue(bad_vector(node)) + p.put(path,node); p.save() + with self.subTest(message=message): + self.assertTrue(any(message in deficit for deficit in c.check(ROOT,p.path)),message) + finally: + p.put(path,original); p.index = copy.deepcopy(self.baseline); p.save() + for key in ('world','occupancy','support'): + path = 'pairs/off-refusal/writer-checkpoint.json' + original = (p.path/path).read_bytes() + try: + node = c.decode(json.loads(original)); revisions = node['systems']['runtime']['world']['revisions'] + revisions[key] += 1 + p.put(path,typed(node)); p.save() + with self.subTest(refusal_revision=key): + self.assertTrue(any('refusal revision changed' in deficit for deficit in c.check(ROOT,p.path))) + finally: + p.put(path,original); p.index = copy.deepcopy(self.baseline); p.save() + + def test_corruptions_cannot_be_accepted(self): + p = self.proof + mutations = { + 'duplicate_actor':lambda r:r['guests']['guests'].append(copy.deepcopy(r['guests']['guests'][0])), + 'orphan_occupancy':lambda r:r['world']['occupancies'][0].update(entity_id='orphan'), + 'lost_patient':lambda r:r['staff']['staff'].pop(0), + 'lost_cargo':lambda r:r['inventory']['inventories'].pop(), + 'lost_ev':lambda r:r['elevators']['trips'].pop(), + 'missing_start_stage':lambda r:r['construction']['jobs'][1].update(state='BUILDING',progress=1), + } + path = 'pairs/off-positive/original.json' + original = (p.path/path).read_bytes() + for label,mutate in mutations.items(): + with self.subTest(label=label): + node = c.decode(json.loads(original)); mutate(node['systems']['runtime']) + p.put(path,typed(node)); p.save() + self.assertTrue(c.check(ROOT,p.path),label) + p.put(path,original); p.index = copy.deepcopy(self.baseline); p.save() + path = 'pairs/off-positive/identity.json' + original = (p.path/path).read_bytes() + node = c.decode(json.loads(original)); node['diagnostic_budget']['deltas']['gold'] += 1 + p.put(path,typed(node)); p.save() + self.assertTrue(c.check(ROOT,p.path),'resource_delta_mismatch') + p.put(path,original); p.index = copy.deepcopy(self.baseline); p.save() + paths = [('partial_batch','pairs/off-refusal/after-live.json'),('unequal_cross_pid','pairs/on-positive/reader-checkpoint.json'), + ('overwritten_original','pairs/off-refusal/original/formal/world.bin'),('overwritten_backup','pairs/off-refusal/backup/formal/world.bin')] + for label,path in paths: + with self.subTest(label=label): + original = (p.path/path).read_bytes() + p.put(path,b'{}'); p.save(); self.assertTrue(c.check(ROOT,p.path),label) + p.put(path,original); p.index = copy.deepcopy(self.baseline); p.save() + for label in ('missing_on','wrong_source','stale_source','fake_os_signal','missing_phase','duplicate_pid'): + with self.subTest(label=label): + changed = None + if label=='missing_on': p.index['cases'] = p.index['cases'][:2] + elif label=='wrong_source': p.index['source_map']['autoload/simulation_runtime.gd'] = '0'*64 + elif label=='stale_source': del p.index['source_map']['tools/check_p08_migration_evidence.py'] + elif label=='missing_phase': p.index['cases'][0]['phases'].pop() + elif label=='fake_os_signal': + changed = 'os/preview/input.json'; original = (p.path/changed).read_bytes() + row = json.loads(original); row['method'] = 'signal_emitted'; p.put(changed,row) + else: + changed = p.index['cases'][0]['phases'][1]; original = (p.path/changed).read_bytes() + row = json.loads(original); row['actual_pid'] = 100; p.put(changed,row) + p.save(); self.assertTrue(c.check(ROOT,p.path),label) + if changed: p.put(changed,original) + p.index = copy.deepcopy(self.baseline); p.save() + +if __name__ == '__main__': unittest.main()