P08-02: 旧配置の安全な変更
This commit is contained in:
+302
-35
@@ -1,39 +1,306 @@
|
|||||||
{
|
{
|
||||||
"preserved_previous_handoff": {
|
"purpose": "Same retained candidate; diagnose checker against real native outputs. Original native bytes and candidate preserved; not game acceptance.",
|
||||||
"record_type": "preserved_coordinator_input_reference",
|
"actual_candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
|
||||||
"original_git_reference": {
|
"all8_native_phases_pass": true,
|
||||||
"commit": "bb34f443c4645a56d7798a142f1e2d8ddf615cae",
|
"defects": [
|
||||||
"path": "docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r04.json"
|
"actual Vector3i constructor scalar format rejected by decoder",
|
||||||
},
|
"logistics lazy destination inventory treated as orphan although canonical live facility exists",
|
||||||
"original_raw_sha256": "d6977dd3e189bbb19d08d79733d70f7db8b25c082b64483ae2781177c91da519",
|
"RESERVED queued request has no assigned carrier by domain contract",
|
||||||
"original_raw_bytes": 27521,
|
"support revision is global world revision epoch, not independent +1 counter; actual changed142→150 alongside world144→150 after occupancy rewrites"
|
||||||
"purpose": "Coordinator handoff input, not game acceptance proof. Full original retained in Git candidate already pushed.",
|
|
||||||
"do_not_omit_acceptance_sources": true
|
|
||||||
},
|
|
||||||
"current_native_failure": {
|
|
||||||
"candidate": "bb34f443c4645a56d7798a142f1e2d8ddf615cae",
|
|
||||||
"actual_writer_pid": 70289,
|
|
||||||
"log_message": "build_facility: 先に階段の一つ下の階に通路を作ってください。",
|
|
||||||
"checks": 30,
|
|
||||||
"failures": 1,
|
|
||||||
"initial_resource_failure_resolved_to_next_phase": true,
|
|
||||||
"import_pid": 70134,
|
|
||||||
"parse_pid": 70255,
|
|
||||||
"all_remaining_cases_not_run": true,
|
|
||||||
"no_original_unit_acceptance": true
|
|
||||||
},
|
|
||||||
"root_packet_actual_bytes": 501277,
|
|
||||||
"scope": [
|
|
||||||
"fixture diagnostic setup sequencing only",
|
|
||||||
"Root-owned handoff compact provenance",
|
|
||||||
"metadata of these changes"
|
|
||||||
],
|
],
|
||||||
"opportunity": "Keep all acceptance source18, no reviewer source omission. Compact this coordinator JSON instead of repeating full27KB after it has been preserved in Git.",
|
"actual_readonly_diagnostics": {
|
||||||
"worker_guidance": [
|
"ACTUAL_NATIVE_CHECKER.json": {
|
||||||
"Fix actual fixture prerequisite using existing completed lower landings, registered build_minutes; do not invent floor rules or skip stairs.",
|
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
|
||||||
"Read only needed definition/rule snippets; preflight remaining setup command preconditions and resolve closely related fixture preparation defects together. No production value or source edits.",
|
"elapsed_seconds": 16.212463750038296,
|
||||||
"Improve failed-command diagnostic label to include kind/payload/details only upon failure so Root can diagnose future native failures without separate replays. No valid behaviour changes.",
|
"actual_readonly_checker_deficits": [
|
||||||
"No wholesale three-stage/collector/checker reimplementation. Preserve current successful resource derivation and full legacy history reference.",
|
"OS proof: unsafe artifact path",
|
||||||
"AST and checker relevant local verification. No duplicate full regression or native worker execution. Root runs final exactcandidate."
|
"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."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'):
|
||||||
|
value = req[endpoint]
|
||||||
|
if not value.startswith('facility.inventory:') or value.removeprefix('facility.inventory:') not in operational:
|
||||||
raise ValueError('orphan cargo endpoint')
|
raise ValueError('orphan cargo endpoint')
|
||||||
if initial:
|
state = req['state']
|
||||||
carrier = staff[req['assigned_carrier_id']]
|
carrier_id = req['assigned_carrier_id']
|
||||||
cargo = inventories[carrier['carried_inventory_id']]
|
if state == 'RESERVED' and carrier_id:
|
||||||
if not cargo['stacks'] or not req['reservation_ids'] or any(x not in reservations for x in req['reservation_ids']):
|
raise ValueError('RESERVED carrier contradiction')
|
||||||
raise ValueError('lost cargo/reservation')
|
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)
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user