Compare commits

...
7 changed files with 875 additions and 57 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
"base_commit": "e196578c1a4ad2fdf84642565bd102e524be386f", "base_commit": "e196578c1a4ad2fdf84642565bd102e524be386f",
"spec_sha256_stripped_utf8": "19f02bb7912a4d7354e565186f02366fc1d4dd698468d26142e8204fd14ebfb0", "spec_sha256_stripped_utf8": "19f02bb7912a4d7354e565186f02366fc1d4dd698468d26142e8204fd14ebfb0",
"spec_sha256_raw": "01b3600e8815b514ffd5c4d341d77a6a3e8341064796c1dae706062b746ba6aa", "spec_sha256_raw": "01b3600e8815b514ffd5c4d341d77a6a3e8341064796c1dae706062b746ba6aa",
"scope": "Repair r04: definition-derived diagnostic deficit, three construction stages in one batch, minimal checker mutations and compact verified history references; no production/native/Runner/git writes.", "scope": "Metadata-only synchronization against Git 1545bd42; no code, fixture, runtime, marker or acceptance changes.",
"outputs": [ "outputs": [
{ {
"path": "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json", "path": "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json",
@@ -27,13 +27,13 @@
}, },
{ {
"path": "tests/fixtures/p08-02/README.md", "path": "tests/fixtures/p08-02/README.md",
"sha256": "8a6b8dcde00be86e1ff96b29b255b586366e759a561b03e060b28e8713e210e0", "sha256": "e88ca33fdb4e96602d1fb108d71a9c35b6fa054619ecf6f1396e9a6c5e0ebd98",
"hash_scope": "raw bytes", "hash_scope": "raw bytes",
"disposition": "r04 current source; retained paths and UID/marker entries" "disposition": "r04 current source; retained paths and UID/marker entries"
}, },
{ {
"path": "tests/fixtures/p08-02/mixed_world.gd", "path": "tests/fixtures/p08-02/mixed_world.gd",
"sha256": "e084d7aa6baf122089bd6f717af7e1e62de3c9930288439e6f51cc524bb55dde", "sha256": "a23f8a686be1d77e4874b9956eef89be4756f6e5eb01cc62454c3845acc62c25",
"hash_scope": "raw bytes", "hash_scope": "raw bytes",
"disposition": "r04 current source; retained paths and UID/marker entries" "disposition": "r04 current source; retained paths and UID/marker entries"
}, },
@@ -57,13 +57,13 @@
}, },
{ {
"path": "tools/check_p08_migration_evidence.py", "path": "tools/check_p08_migration_evidence.py",
"sha256": "c6edc43b114a64f5dedaafbfde47516baa265978c7aa836296c6bf5fe7a26857", "sha256": "928353233bf85436a5aaf8c4f77f3f82472d94b8ad2646cb1b030baf92b3b5dc",
"hash_scope": "raw bytes", "hash_scope": "raw bytes",
"disposition": "r04 current source; retained paths and UID/marker entries" "disposition": "r04 current source; retained paths and UID/marker entries"
}, },
{ {
"path": "tools/test_p08_migration_evidence.py", "path": "tools/test_p08_migration_evidence.py",
"sha256": "22e75b2d4e79e7b6f8d7990c1eb14917565ca0e60197435716588ebaef3e09ea", "sha256": "fd9ef7ec63f53dfd5e04e261606c9303d01a2e6590e22a35fef2e35df1429280",
"hash_scope": "raw bytes", "hash_scope": "raw bytes",
"disposition": "r04 current source; retained paths and UID/marker entries" "disposition": "r04 current source; retained paths and UID/marker entries"
}, },
@@ -75,7 +75,7 @@
}, },
{ {
"path": "docs/plans/original-completion-2026-10-02/execution/p08-02/implementation.json", "path": "docs/plans/original-completion-2026-10-02/execution/p08-02/implementation.json",
"sha256": "7783f882e49c86432ea286293fd691d7d0abbec3847a7759c814e67436645e62", "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", "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" "disposition": "r04 current source; retained paths and UID/marker entries"
}, },
@@ -101,25 +101,44 @@
"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.", "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": [ "commands_actually_run": [
{ {
"command": "python3 -B AST parse of tools/run_p08_mixed_migration.py, tools/check_p08_migration_evidence.py, tools/test_p08_migration_evidence.py", "command": "python3 -B AST parse of three P08 Python tools",
"exit_code": 0, "exit_code": 0,
"result": "3 AST parses passed" "result": "3 parses passed"
}, },
{ {
"command": "python3 -B tools/test_p08_migration_evidence.py", "command": "python3 -B tools/test_p08_migration_evidence.py (first run)",
"exit_code": 0, "exit_code": 1,
"result": "2 tests in 34.514s, OK, synthetic only" "result": "2 tests; positive stale source map because worker changed README during test"
}, },
{ {
"command": "git show fixed historical references (stdout SHA only; no Git writes)", "command": "python3 -B tools/test_p08_migration_evidence.py (stable-source rerun)",
"exit_code": 0, "exit_code": 0,
"result": "both references matched original raw SHA and 1082602 bytes" "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": { "local_checks": {
"ast_parse": "passed, three Python sources", "ast_parse": "passed, three Python sources",
"checker_tests": "2 tests passed in 34.514s; synthetic only, includes missing_start_stage and resource_delta_mismatch", "checker_tests": {
"native_or_full_regression_run": false "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": [ "pending_root_conditions": [
"mixed_construction", "mixed_construction",
@@ -181,7 +200,7 @@
"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.", "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": { "complete_example_generator": {
"source": "tools/test_p08_migration_evidence.py", "source": "tools/test_p08_migration_evidence.py",
"source_raw_sha256": "22e75b2d4e79e7b6f8d7990c1eb14917565ca0e60197435716588ebaef3e09ea", "source_raw_sha256": "fd9ef7ec63f53dfd5e04e261606c9303d01a2e6590e22a35fef2e35df1429280",
"class": "Proof", "class": "Proof",
"constructor": "Proof(Path(fresh_temporary_directory))", "constructor": "Proof(Path(fresh_temporary_directory))",
"project_root_binding": "module ROOT resolved from unchanged generator inside this exact candidate source tree", "project_root_binding": "module ROOT resolved from unchanged generator inside this exact candidate source tree",
@@ -224,13 +243,13 @@
"fixture_sources": { "fixture_sources": {
"tests/s1/test_p08_combined_migration.gd": "9284a8cfb478b4c8d69f6d8166eec6c41107e3902029289b3c0e9ba48c370855", "tests/s1/test_p08_combined_migration.gd": "9284a8cfb478b4c8d69f6d8166eec6c41107e3902029289b3c0e9ba48c370855",
"tests/s1/test_p08_combined_migration.gd.uid": "8c3a70d0ce619b8cb98dc003eb6c53a31f167a686179a3ffc105f7cd4d856da5", "tests/s1/test_p08_combined_migration.gd.uid": "8c3a70d0ce619b8cb98dc003eb6c53a31f167a686179a3ffc105f7cd4d856da5",
"tests/fixtures/p08-02/README.md": "8a6b8dcde00be86e1ff96b29b255b586366e759a561b03e060b28e8713e210e0", "tests/fixtures/p08-02/README.md": "e88ca33fdb4e96602d1fb108d71a9c35b6fa054619ecf6f1396e9a6c5e0ebd98",
"tests/fixtures/p08-02/mixed_world.gd": "e084d7aa6baf122089bd6f717af7e1e62de3c9930288439e6f51cc524bb55dde", "tests/fixtures/p08-02/mixed_world.gd": "a23f8a686be1d77e4874b9956eef89be4756f6e5eb01cc62454c3845acc62c25",
"tests/fixtures/p08-02/reader.gd": "a760d0e45979d747dbd1f8710033bdce13287b00eb879d947d8378ff920ed1be", "tests/fixtures/p08-02/reader.gd": "a760d0e45979d747dbd1f8710033bdce13287b00eb879d947d8378ff920ed1be",
"tests/fixtures/p08-02/writer.gd": "1a918b5509773b4b07a27d47ee44583b0eb5288138f7bf896088a7913773c260", "tests/fixtures/p08-02/writer.gd": "1a918b5509773b4b07a27d47ee44583b0eb5288138f7bf896088a7913773c260",
"tools/run_p08_mixed_migration.py": "7e86737e6354a4734726ba8d3171ad5f58d6c0380aba5a0fa3c57879d580e579", "tools/run_p08_mixed_migration.py": "7e86737e6354a4734726ba8d3171ad5f58d6c0380aba5a0fa3c57879d580e579",
"tools/check_p08_migration_evidence.py": "c6edc43b114a64f5dedaafbfde47516baa265978c7aa836296c6bf5fe7a26857", "tools/check_p08_migration_evidence.py": "928353233bf85436a5aaf8c4f77f3f82472d94b8ad2646cb1b030baf92b3b5dc",
"tools/test_p08_migration_evidence.py": "22e75b2d4e79e7b6f8d7990c1eb14917565ca0e60197435716588ebaef3e09ea" "tools/test_p08_migration_evidence.py": "fd9ef7ec63f53dfd5e04e261606c9303d01a2e6590e22a35fef2e35df1429280"
}, },
"native_runner_input_shape": { "native_runner_input_shape": {
"protocol": "p08-mixed-v1", "protocol": "p08-mixed-v1",
@@ -250,7 +269,15 @@
"timeouts": "unchanged: collection 900s, phase maximum 180s", "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" "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." "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": { "historical_record": {
"path": "docs/plans/original-completion-2026-10-02/execution/p08-02/implementation-historical-6c7c724.json", "path": "docs/plans/original-completion-2026-10-02/execution/p08-02/implementation-historical-6c7c724.json",
@@ -279,5 +306,25 @@
"mismatch": "Previous zero added resources insufficient; no acceptance narrowed.", "mismatch": "Previous zero added resources insufficient; no acceptance narrowed.",
"git_history_verification": "Both fixed Git references raw SHA matched ab1fcf46ef73f2157f16f69394b4039a562c031fae8d407adca013a452226e2f, 1082602 bytes", "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." "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."
} }
} }
+6
View File
@@ -11,6 +11,12 @@ The fixture derives and records this deficit from the actual registry/catalogs
in identity.diagnostic_budget before using the existing change_resources command. in identity.diagnostic_budget before using the existing change_resources command.
No price or production coefficient is changed. 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 The first legacy premium job progresses while the six actor/transport facets
are prepared. The second is paid and reserved immediately before saving, with are prepared. The second is paid and reserved immediately before saving, with
zero progress; the dormitory is already complete. All three stages share the zero progress; the dormitory is already complete. All three stages share the
+8 -2
View File
@@ -21,7 +21,9 @@ func expect(ok: bool, label: String) -> bool:
func command(kind: StringName, payload: Dictionary) -> bool: func command(kind: StringName, payload: Dictionary) -> bool:
serial += 1 serial += 1
var result: CommandResult = runtime.submit_command(kind, payload, &"player", &"", 0, -1, StringName("p08.%d" % serial)) var result: CommandResult = runtime.submit_command(kind, payload, &"player", &"", 0, -1, StringName("p08.%d" % serial))
return expect(result.is_success(), String(kind) + ": " + result.message) 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: func registry(old: bool) -> void:
var paths: Array[String] = FacilityCatalog.FACILITY_PATHS + FacilityCatalog.INFRASTRUCTURE_FACILITY_PATHS + FacilityCatalog.RESCUE_FACILITY_PATHS 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"]) 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"])
@@ -102,9 +104,13 @@ func setup(operations: bool) -> bool:
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]]: 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 if not build(row[0],row[1],false): return false
clock.advance_minutes(180) 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]: 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 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(runtime.definition_registry.get_facility(&"guest_stairs").build_minutes) clock.advance_minutes(stair_minutes)
if not build(&"staff_dormitory",Vector3i(-8,2,1)): return false if not build(&"staff_dormitory",Vector3i(-8,2,1)): return false
identity[&"dorm"] = instance(&"staff_dormitory").instance_id identity[&"dorm"] = instance(&"staff_dormitory").instance_id
if not build(&"premium_room",Vector3i(-6,2,0),false): return false if not build(&"premium_room",Vector3i(-6,2,0),false): return false
+74 -9
View File
@@ -4,6 +4,7 @@ import argparse
import hashlib import hashlib
import importlib.util import importlib.util
import json import json
import math
import os import os
from pathlib import Path from pathlib import Path
import re import re
@@ -43,7 +44,16 @@ def decode(node):
return text == 'true' return text == 'true'
if t == 'Nil': return None if t == 'Nil': return None
if t in ('Vector2i','Vector3i','Vector2','Vector3'): if t in ('Vector2i','Vector3i','Vector2','Vector3'):
return [float(x.strip()) if '.' in x else int(x.strip()) for x in text.strip('()').split(',')] 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. # Preserve unfamiliar scalar types verbatim; never silently drop them.
return (t,text) return (t,text)
@@ -101,13 +111,44 @@ def semantics(snapshot, identity, initial=False):
if not identity['logistics_requests']: raise ValueError('missing original food request') if not identity['logistics_requests']: raise ValueError('missing original food request')
for rid in identity['logistics_requests']: for rid in identity['logistics_requests']:
req = requests[rid] req = requests[rid]
if req['source_inventory_id'] not in inventories or req['destination_inventory_id'] not in inventories: for endpoint in ('source_inventory_id','destination_inventory_id'):
raise ValueError('orphan cargo endpoint') value = req[endpoint]
if initial: if not value.startswith('facility.inventory:') or value.removeprefix('facility.inventory:') not in operational:
carrier = staff[req['assigned_carrier_id']] raise ValueError('orphan cargo endpoint')
cargo = inventories[carrier['carried_inventory_id']] state = req['state']
if not cargo['stacks'] or not req['reservation_ids'] or any(x not in reservations for x in req['reservation_ids']): carrier_id = req['assigned_carrier_id']
raise ValueError('lost cargo/reservation') 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') trips = indexed(r['elevators']['trips'],'request_id')
actors = set(guests) | set(staff) actors = set(guests) | set(staff)
for trip in trips.values(): for trip in trips.values():
@@ -136,6 +177,27 @@ def typed_differences(a, b, 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 [p for i,(x,y) in enumerate(zip(a['items'],b['items'])) for p in typed_differences(x,y,path+(str(i),))]
return [path] 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. # 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. # This fixture deliberately places actors away from removed cells: NO route changes approved.
GEOMETRY = [ GEOMETRY = [
@@ -235,10 +297,13 @@ def check(project_root, evidence_dir):
proc = decode(typed(role+'-process')) 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) 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') checkpoint = typed('writer-checkpoint')
migrated = decode(checkpoint)['systems']['runtime']
migration_revisions(source,migrated,refusal)
if refusal: need(original == checkpoint,'partial earlier candidate '+name) if refusal: need(original == checkpoint,'partial earlier candidate '+name)
else: else:
for path in typed_differences(original,checkpoint): for path in typed_differences(original,checkpoint):
need(any(re.fullmatch(pattern,'/'.join(path)) for pattern in GEOMETRY),'unapproved typed migration delta '+name+'/'+ '/'.join(path)) 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'] migrated = decode(checkpoint)['systems']['runtime']
jobs = indexed(migrated['construction']['jobs'],'job_id') 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) need(all(len(jobs[j]['reserved_cells']) == 4 for j in identity['jobs']),'positive batch not applied '+name)
+77 -7
View File
@@ -14,8 +14,13 @@ import check_p08_migration_evidence as c
ROOT = Path(__file__).resolve().parent.parent ROOT = Path(__file__).resolve().parent.parent
BANNER = 'Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org\n' 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): 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,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]} 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' kind = 'Nil' if value is None else 'bool' if type(value) is bool else 'int' if type(value) is int else 'String'
@@ -46,19 +51,24 @@ class Proof:
self.put(prefix+name+'.var',json.dumps(node).encode()) self.put(prefix+name+'.var',json.dumps(node).encode())
def save(self): (self.path/'index.json').write_text(json.dumps(self.index)) def save(self): (self.path/'index.json').write_text(json.dumps(self.index))
def state(self,operations): def state(self,operations):
jobs = [{'job_id':j,'state':'PLANNED' if j=='j2' else 'BUILDING','progress':0 if j=='j2' else 466,'reserved_cells':[[x,2,0] for x in range(offset,offset+6)],'construction_cost':{'gold':1}} 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)]] 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':'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)]] 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 = [{'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':[ 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'}]}, {'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)}, 'construction':{'jobs':jobs,'facilities':facilities},'facilities':{'instances':copy.deepcopy(facilities)},
'world':{'occupancies':[{'entity_id':j['job_id'],'cells':copy.deepcopy(j['reserved_cells'])} for j in jobs]+ '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]}, [{'entity_id':f['instance_id'],'cells':copy.deepcopy(f['occupied_cells'])} for f in facilities]},
'inventory':{'inventories':[{'owner_inventory_id':x,'stacks':[{'item_id':'ingredient/raw_food','quantity':4}]} for x in ('source','dest','cargo')], '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
'reservations':[{'reservation_id':'reserve'}]}, [('facility.inventory:facility_00000005','stack_00000001',4),('cargo:staff_gina','stack_00000002',0)]],
'logistics_requests':[{'request_id':'food','source_inventory_id':'source','destination_inventory_id':'dest','assigned_carrier_id':'staff_gina','reservation_ids':['reserve'],'state':'IN_TRANSIT'}], '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}], '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']}]}} '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}} return {'systems':{'runtime':r}}
@@ -82,6 +92,7 @@ class Proof:
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['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 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 = {'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) original = self.state(operations)
self.variant(prefix,'identity',identity); self.variant(prefix,'original',original); self.variant(prefix,'after-live',original) self.variant(prefix,'identity',identity); self.variant(prefix,'original',original); self.variant(prefix,'after-live',original)
before = {}; after = {} before = {}; after = {}
@@ -94,6 +105,7 @@ class Proof:
checkpoint = copy.deepcopy(original) checkpoint = copy.deepcopy(original)
if not refusal: if not refusal:
r = checkpoint['systems']['runtime'] 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 job in r['construction']['jobs']: job['reserved_cells'] = job['reserved_cells'][:4]
for row in r['world']['occupancies'][:2]: row['cells'] = row['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']] events = [{'event_type':'ELEVATOR_ARRIVED','payload':{'request_id':rid}} for rid in identity['ev_requests']]
@@ -103,7 +115,7 @@ class Proof:
state = copy.deepcopy(checkpoint) state = copy.deepcopy(checkpoint)
if label == 'minute-160': if label == 'minute-160':
state['systems']['runtime']['staff']['staff'][0]['health'] = 100 state['systems']['runtime']['staff']['staff'][0]['health'] = 100
state['systems']['runtime']['logistics_requests'][0]['state'] = 'COMPLETED' for req in state['systems']['runtime']['logistics_requests']: req['state'] = 'COMPLETED'
self.variant(prefix,role+'-'+label,events if label=='events' else state) self.variant(prefix,role+'-'+label,events if label=='events' else state)
def os(self): def os(self):
proof = {'commit':self.index['commit'],'source_map':self.index['source_map'],'input_method':'native_os','setup':'controlled_old_save','actions':[]} proof = {'commit':self.index['commit'],'source_map':self.index['source_map'],'input_method':'native_os','setup':'controlled_old_save','actions':[]}
@@ -143,6 +155,64 @@ class EvidenceTests(unittest.TestCase):
def tearDownClass(cls): cls.temp.cleanup() def tearDownClass(cls): cls.temp.cleanup()
def test_positive_reaches_whole_check(self): def test_positive_reaches_whole_check(self):
self.assertEqual(c.check(ROOT,self.proof.path),[]) 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): def test_corruptions_cannot_be_accepted(self):
p = self.proof p = self.proof
mutations = { mutations = {