P18-REG-KITCHEN-LEGACY14: P18承認済み保存移行と厨房旧14回帰の整合(原88件に加算しない補助)
This commit is contained in:
@@ -188,7 +188,93 @@ func _io_valid(event: Dictionary, kind: String, operation: StringName, path: Str
|
||||
var payload: Dictionary = event.get("payload", {})
|
||||
return event.get("event_type") == kind and event.get("source_service") == "save_service" and event.get("causation_command_id") == 0 and event.get("entity_ids") == [] and not String(event.get("event_id", "")).is_empty() and payload.get(&"operation") == operation and typeof(payload.get(&"ok")) == TYPE_BOOL and payload[&"ok"] and payload.get(&"code") == &"OK" and payload.get(&"save_path") == path and payload.get(&"checkpoint_absolute_minute") == checkpoint_minute and payload.get(&"saved_absolute_minute") == checkpoint_minute and event.get("absolute_minute") == checkpoint_minute and not String(payload.get(&"save_id", "")).is_empty() and payload.get(&"request_id") == 0 and payload.get(&"reason") == (&"manual" if operation == &"save" else &"") and (operation != &"load" or typeof(payload.get(&"duration_us")) == TYPE_INT and payload[&"duration_us"] >= 0 and typeof(payload.get(&"runtime_duration_us")) == TYPE_INT and payload[&"runtime_duration_us"] >= 0)
|
||||
|
||||
func _compare_loaded(writer: Dictionary, write_path: String, read_path: String, label: String) -> void:
|
||||
func _typed_sha(value: Variant) -> String:
|
||||
var context := HashingContext.new()
|
||||
context.start(HashingContext.HASH_SHA256)
|
||||
context.update(var_to_bytes(value))
|
||||
return context.finish().hex_encode()
|
||||
|
||||
func _legacy_relationship_v1_to_v2(expected_subtree: Variant, actual_subtree: Variant) -> Dictionary:
|
||||
var rejected := {"ok": false, "expected": null, "proof": {}}
|
||||
if not expected_subtree is Dictionary or not actual_subtree is Dictionary: return rejected
|
||||
var old: Dictionary = expected_subtree
|
||||
var actual: Dictionary = actual_subtree
|
||||
# The genuine fixture uses StringName keys, in this exact serialization order.
|
||||
if var_to_bytes(old.keys()) != var_to_bytes([&"version", &"relationships", &"threshold_candidates", &"next_candidate_sequence"]): return rejected
|
||||
if typeof(old[&"version"]) != TYPE_INT or old[&"version"] != 1: return rejected
|
||||
if not old[&"relationships"] is Array or not old[&"threshold_candidates"] is Array or typeof(old[&"next_candidate_sequence"]) != TYPE_INT: return rejected
|
||||
if var_to_bytes(actual.keys()) != var_to_bytes([&"version", &"legacy", &"relationships", &"original_relationships"]): return rejected
|
||||
if typeof(actual[&"version"]) != TYPE_INT or actual[&"version"] != 2: return rejected
|
||||
if not actual[&"legacy"] is Dictionary or var_to_bytes(actual[&"legacy"]) != var_to_bytes(old): return rejected
|
||||
if var_to_bytes(actual[&"legacy"].get(&"next_candidate_sequence")) != var_to_bytes(old[&"next_candidate_sequence"]): return rejected
|
||||
if not actual[&"original_relationships"] is Array or not actual[&"original_relationships"].is_empty(): return rejected
|
||||
# create_snapshot uses a typed compatibility collection even for empty v1 input.
|
||||
var projection: Array[Dictionary] = []
|
||||
for entry: Variant in old[&"relationships"]:
|
||||
if not entry is Dictionary: return rejected
|
||||
projection.append(entry.duplicate(true))
|
||||
if var_to_bytes(actual[&"relationships"]) != var_to_bytes(projection): return rejected
|
||||
var originals: Array[Dictionary] = []
|
||||
var migrated := {&"version": 2, &"legacy": old.duplicate(true), &"relationships": projection.duplicate(true), &"original_relationships": originals}
|
||||
if var_to_bytes(migrated) != var_to_bytes(actual): return rejected
|
||||
return {"ok": true, "expected": migrated, "proof": {"old_v1_bytes": var_to_bytes(old).size(), "old_v1_sha256": _typed_sha(old), "replaced_subtree_sha256": _typed_sha(migrated)}}
|
||||
|
||||
func _legacy_checkpoint(expected: Dictionary, reader: Dictionary) -> Dictionary:
|
||||
var migration := _legacy_relationship_v1_to_v2(expected[&"systems"][&"runtime"][&"relationships"], reader[&"systems"][&"runtime"][&"relationships"])
|
||||
if not migration["ok"]: return migration
|
||||
# Reverse just this subtree in a copy of the reader: every other byte must
|
||||
# already match the writer after the separately validated IO1 replacement.
|
||||
var outside := reader.duplicate(true)
|
||||
outside[&"systems"][&"runtime"][&"relationships"] = expected[&"systems"][&"runtime"][&"relationships"].duplicate(true)
|
||||
if var_to_bytes(outside) != var_to_bytes(expected): return {"ok": false}
|
||||
var result := expected.duplicate(true)
|
||||
result[&"systems"][&"runtime"][&"relationships"] = migration["expected"].duplicate(true)
|
||||
if var_to_bytes(result) != var_to_bytes(reader) or result != reader or SimulationDigest.compute(result) != SimulationDigest.compute(reader): return {"ok": false}
|
||||
return {"ok": true, "expected": result, "proof": migration["proof"]}
|
||||
|
||||
func _legacy_control(writer: Dictionary, reader: Dictionary, label: String, kind: String, field: StringName = &"", value: Variant = null) -> void:
|
||||
var writer_bytes := var_to_bytes(writer); var reader_bytes := var_to_bytes(reader)
|
||||
var candidate := writer.duplicate(true); var observed := reader.duplicate(true)
|
||||
var old: Dictionary = candidate[&"systems"][&"runtime"][&"relationships"]
|
||||
var actual: Dictionary = observed[&"systems"][&"runtime"][&"relationships"]
|
||||
match kind:
|
||||
"outside": candidate[&"legacy_control_outside"] = true
|
||||
"delete": old.erase(field)
|
||||
"change": old[field] = value
|
||||
"sequence": actual[&"legacy"][&"next_candidate_sequence"] += 1
|
||||
"originals": actual[&"original_relationships"].append({&"unapproved": true})
|
||||
"version": actual[&"version"] = value
|
||||
"reorder":
|
||||
var reordered := {}
|
||||
var keys := old.keys(); keys.reverse()
|
||||
for key: Variant in keys: reordered[key] = old[key]
|
||||
candidate[&"systems"][&"runtime"][&"relationships"] = reordered
|
||||
"reencode":
|
||||
var reencoded := {}
|
||||
for key: Variant in old: reencoded[String(key)] = old[key]
|
||||
candidate[&"systems"][&"runtime"][&"relationships"] = reencoded
|
||||
var before_candidate := var_to_bytes(candidate); var before_observed := var_to_bytes(observed)
|
||||
var encoding_control := true
|
||||
if kind in ["reorder", "reencode"]:
|
||||
encoding_control = candidate == writer and before_candidate != writer_bytes
|
||||
var result := _legacy_checkpoint(candidate, observed)
|
||||
expect(encoding_control and result["ok"] == (kind == "copy") and var_to_bytes(writer) == writer_bytes and var_to_bytes(reader) == reader_bytes and var_to_bytes(candidate) == before_candidate and var_to_bytes(observed) == before_observed, "legacy migration control immutable " + label)
|
||||
|
||||
func _legacy_controls(writer: Dictionary, reader: Dictionary) -> void:
|
||||
_legacy_control(writer, reader, "outside", "outside")
|
||||
for field: StringName in [&"version", &"relationships", &"threshold_candidates", &"next_candidate_sequence"]:
|
||||
_legacy_control(writer, reader, "delete " + String(field), "delete", field)
|
||||
_legacy_control(writer, reader, "change " + String(field), "change", field, null)
|
||||
_legacy_control(writer, reader, "sequence +1", "sequence")
|
||||
_legacy_control(writer, reader, "nonempty originals", "originals")
|
||||
for version: Variant in [0, 3, "2"]:
|
||||
_legacy_control(writer, reader, "version " + str(version), "version", &"", version)
|
||||
_legacy_control(writer, reader, "equal reordered keys", "reorder")
|
||||
_legacy_control(writer, reader, "equal String reencoding", "reencode")
|
||||
_legacy_control(writer, reader, "byte-identical deep copy", "copy")
|
||||
_proof("legacy14-migration-controls", {"attempts": 17, "writer_sha256": _typed_sha(writer), "reader_sha256": _typed_sha(reader), "failures": failures})
|
||||
|
||||
func _compare_loaded(writer: Dictionary, write_path: String, read_path: String, label: String, migrate_relationships_v1: bool = false) -> void:
|
||||
var reader: Dictionary = runtime.create_snapshot()
|
||||
var old: Dictionary = writer[&"systems"][&"runtime"][&"domain_events"]["recent_events"].back().duplicate(true)
|
||||
var actual: Dictionary = reader[&"systems"][&"runtime"][&"domain_events"]["recent_events"].back().duplicate(true)
|
||||
@@ -199,9 +285,22 @@ func _compare_loaded(writer: Dictionary, write_path: String, read_path: String,
|
||||
if event.get("event_id") == old.get("event_id"):
|
||||
valid = valid and var_to_bytes(event) == var_to_bytes(old)
|
||||
event["event_type"] = actual["event_type"]; event["payload"] = actual["payload"].duplicate(true); replaced += 1
|
||||
var migration_proof := {}
|
||||
if migrate_relationships_v1:
|
||||
_legacy_controls(expected, reader)
|
||||
var migration := _legacy_checkpoint(expected, reader)
|
||||
valid = valid and migration["ok"]
|
||||
if migration["ok"]:
|
||||
expected = migration["expected"]
|
||||
migration_proof = migration["proof"]
|
||||
migration_proof["write_checkpoint_sha256"] = FileAccess.get_sha256(LEGACY.path_join("write-checkpoint.var"))
|
||||
valid = valid and replaced == 1 and expected[&"systems"][&"runtime"][&"domain_events"]["published_ids"].has(actual.get("event_id")) and var_to_bytes(expected) == var_to_bytes(reader) and expected == reader and SimulationDigest.compute(expected) == SimulationDigest.compute(reader)
|
||||
_proof(label + "-expected-io1", expected); _proof(label + "-actual", reader)
|
||||
_proof(label + "-io1-binding", {"whole_typed_bytes_equal": valid, "writer_io": old, "reader_io": actual, "mapped_fields": ["event_type", "payload"], "event_deletion_count": 0, "write_path": write_path, "read_path": read_path, "copied_legacy_package": write_path != read_path})
|
||||
var binding := {"whole_typed_bytes_equal": valid, "writer_io": old, "reader_io": actual, "mapped_fields": ["event_type", "payload"], "event_deletion_count": 0, "write_path": write_path, "read_path": read_path, "copied_legacy_package": write_path != read_path}
|
||||
if migrate_relationships_v1:
|
||||
binding["mapped_fields"].append("relationships_v1_to_v2")
|
||||
binding["relationships_v1_to_v2"] = migration_proof
|
||||
_proof(label + "-io1-binding", binding)
|
||||
expect(valid, "whole typed loaded state unchanged except complete identity/saveID-bound IO1: " + label)
|
||||
|
||||
func _new_saved_boundary() -> void:
|
||||
@@ -340,7 +439,7 @@ func _legacy_saved_boundary() -> void:
|
||||
expect(persisted is Dictionary and inspected.get(&"ok", false) and inspected.get(&"read_only", false) and var_to_bytes(inspected.get(&"snapshot", {})) == var_to_bytes(persisted) and var_to_bytes(live) == var_to_bytes(runtime.create_snapshot()) and _package(path) == hashes, "old14 inspection exact complete typed persisted state and read-only for every live byte/package")
|
||||
expect(runtime.load_from_path(path).get(&"ok", false), "ordinary strict old14 formal Load, schema stays14")
|
||||
_listen(true)
|
||||
_compare_loaded(expected, contract["slot"], path, "legacy14-load")
|
||||
_compare_loaded(expected, contract["slot"], path, "legacy14-load", true)
|
||||
var cafe: FacilityInstance = runtime.facility_service.get_instance(ids["cafe"])
|
||||
var service: ServiceInstance = runtime.facility_service.get_service(ids["service"])
|
||||
expect(cafe.related_facility_id.is_empty() and service.state == ServiceInstance.State.ACTIVE and service.elapsed_minutes == 5 and cafe.state == FacilityInstance.State.OPEN and not cafe.related_facility_operating and cafe.operation_state == FacilityInstance.OperationState.INACTIVE, "old raw OPEN/ACTIVE5 remains serialized immediately; derived missing kitchen false")
|
||||
|
||||
Reference in New Issue
Block a user