P18-01-CODE: 原本関係モデル・旧値保全・Runtime保存と読取経路
This commit is contained in:
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
"template": "Original relationship runtime: <checks> checks, <failures> failures",
|
"template": "Original relationship runtime: <checks> checks, <failures> failures",
|
||||||
"required": true,
|
"required": true,
|
||||||
"fixed_check_count_used": false,
|
"fixed_check_count_used": false,
|
||||||
"source_sha256_at_schema_audit": "845ae200575563400c3497e517137effb7d4d928dd7e6a4990ed8aabdbda5ae6",
|
"source_sha256_at_schema_audit": "c56bdb99dd43a4e7ceb3ca2861e8db39972c5c4e805126a77e2f7da32ca5bdc9",
|
||||||
"authority": {
|
"authority": {
|
||||||
"path": "docs/spec/acceptance/P18-01-code-r01.json",
|
"path": "docs/spec/acceptance/P18-01-code-r01.json",
|
||||||
"scope": "new backend suite marker; native execution pending"
|
"scope": "new backend suite marker; native execution pending"
|
||||||
|
|||||||
@@ -316,3 +316,41 @@ Free space at preview: 3,390,361,600 bytes (about 3.39 GB); single-set admission
|
|||||||
382,071,084 bytes. At the conservative prior estimate of 474 MiB per set,
|
382,071,084 bytes. At the conservative prior estimate of 474 MiB per set,
|
||||||
two fresh native sets require 994,050,048 bytes and fit, leaving about 2.39 GB.
|
two fresh native sets require 994,050,048 bytes and fit, leaving about 2.39 GB.
|
||||||
Capacity remains a point-in-time preview, not reserved disk or native evidence.
|
Capacity remains a point-in-time preview, not reserved disk or native evidence.
|
||||||
|
|
||||||
|
### Repair 6 — actual Opus high/medium feedback
|
||||||
|
|
||||||
|
Preserved the held implementation and all representative inputs. Added strict
|
||||||
|
candidate field validation matching `_register_threshold_candidate`: identifiers
|
||||||
|
and actor identities accept String/StringName, deduplication_key is String,
|
||||||
|
threshold/created_at are int, requires_unlock_event is bool, and actor_ids is
|
||||||
|
an Array of two identities. No numeric bounds or gameplay policy were added.
|
||||||
|
Unknown candidate fields remain raw facts.
|
||||||
|
|
||||||
|
The new Runtime suite now keeps nested legacy and compatibility projection
|
||||||
|
identical for the exclusive-route positive control and empty-route-only negative
|
||||||
|
control. Both exercise normal restore and real checksum-valid SaveService package
|
||||||
|
recovery. Negative checks retain whole Runtime bytes, service identity/bytes,
|
||||||
|
caller bytes, RESTORE_FAILED, restore_committed=false and no forbidden repair.
|
||||||
|
Producer-created typed candidates traverse v1, v2 and package recovery; String
|
||||||
|
identifier compatibility and unknown raw extensions have positive controls.
|
||||||
|
Each reviewed wrong field type exercises service, normal Runtime and recovery
|
||||||
|
rejection for both v1 and v2. Native execution remains pending Root ownership.
|
||||||
|
|
||||||
|
Ordering disposition: preserve restored raw collection order under the explicit
|
||||||
|
lossless round-trip contract. Fresh snapshots retain their existing sorted output.
|
||||||
|
The actual producer issues zero-padded monotonic ids; after restore, new candidates
|
||||||
|
append without disturbing raw facts. Added a real producer mutation check for
|
||||||
|
that boundary. No new sorting policy was introduced. The unrelated absolute-path
|
||||||
|
historical inspector limitation remains deferred.
|
||||||
|
|
||||||
|
Static diff whitespace checks passed. All five existing suites retain the exact
|
||||||
|
SHA-256 values forwarded by Root. Both new test pins remain exact (model unchanged;
|
||||||
|
Runtime c56bdb99dd43a4e7ceb3ca2861e8db39972c5c4e805126a77e2f7da32ca5bdc9).
|
||||||
|
Prepare-only: seven suites, 308 parse paths, 1,311 source files, zero engines/stages,
|
||||||
|
no executed checks and formal_acceptance=false. No native/default-profile/full
|
||||||
|
regression, git writes, Runner operations, integration or agents were used.
|
||||||
|
P18-01 remains 30/88, in progress; submitted for Root verification, not accepted.
|
||||||
|
The final repair-6-prepare.json receipt records 67,504,813 source bytes,
|
||||||
|
2,674,012,160 free bytes and 382,077,613 single-set admission bytes. Two sets
|
||||||
|
at the retained conservative 474 MiB estimate require 994,050,048 bytes and fit
|
||||||
|
at this preview. Capacity is not reserved and must be checked at collection time.
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"roster": ["tests/s4/test_original_relationship_model.gd", "tests/s4/test_original_relationship_runtime.gd", "tests/s4/test_relationship_event_service.gd", "tests/s4/test_runtime_relationship_events.gd", "tests/s6/test_runtime_snapshot_serialization.gd", "tests/s6/test_runtime_save_lifecycle.gd", "tests/ui/test_staff_management.gd"], "source_files": 1311, "source_bytes": 67504813, "free_bytes": 2674012160, "conservative_required_bytes": 382077613, "capacity_available": true, "parse_paths": 308, "future_native_phases": 9, "actual_checks": null, "engines": 0, "stages": 0, "formal_acceptance": false}
|
||||||
@@ -517,9 +517,17 @@ func _parse_snapshot(snapshot: Dictionary) -> Dictionary:
|
|||||||
for raw_candidate: Variant in raw_candidates:
|
for raw_candidate: Variant in raw_candidates:
|
||||||
if not raw_candidate is Dictionary:
|
if not raw_candidate is Dictionary:
|
||||||
return {&"ok": false, &"error": "Threshold candidate is malformed."}
|
return {&"ok": false, &"error": "Threshold candidate is malformed."}
|
||||||
for key: String in ["candidate_id", "relationship_id"]:
|
for key: String in ["candidate_id", "relationship_id", "metric_id"]:
|
||||||
if not (raw_candidate.get(key) is String or raw_candidate.get(key) is StringName):
|
if not (raw_candidate.get(key) is String or raw_candidate.get(key) is StringName):
|
||||||
return {&"ok": false, &"error": "Threshold candidate identity type is invalid."}
|
return {&"ok": false, &"error": "Threshold candidate identity type is invalid."}
|
||||||
|
if not raw_candidate.get("deduplication_key") is String or not raw_candidate.get("threshold") is int or not raw_candidate.get("created_at") is int or not raw_candidate.get("requires_unlock_event") is bool:
|
||||||
|
return {&"ok": false, &"error": "Threshold candidate field type is invalid."}
|
||||||
|
var actors: Variant = raw_candidate.get("actor_ids")
|
||||||
|
if not actors is Array or actors.size() != 2:
|
||||||
|
return {&"ok": false, &"error": "Threshold candidate actor collection is invalid."}
|
||||||
|
for actor: Variant in actors:
|
||||||
|
if not (actor is String or actor is StringName):
|
||||||
|
return {&"ok": false, &"error": "Threshold candidate actor type is invalid."}
|
||||||
var candidate: Dictionary = raw_candidate.duplicate(true)
|
var candidate: Dictionary = raw_candidate.duplicate(true)
|
||||||
var candidate_id := StringName(candidate.get(&"candidate_id", candidate.get("candidate_id", "")))
|
var candidate_id := StringName(candidate.get(&"candidate_id", candidate.get("candidate_id", "")))
|
||||||
var relationship_id := StringName(candidate.get(&"relationship_id", candidate.get("relationship_id", "")))
|
var relationship_id := StringName(candidate.get(&"relationship_id", candidate.get("relationship_id", "")))
|
||||||
|
|||||||
@@ -1457,6 +1457,7 @@ func _run() -> void:
|
|||||||
_expect(runtime.restore_snapshot(retained), "refused departures survive save and readback")
|
_expect(runtime.restore_snapshot(retained), "refused departures survive save and readback")
|
||||||
_expect(runtime.relationship_service.get_original_relationship(&"heroine_anri")[&"configured"], "departure never deletes original record")
|
_expect(runtime.relationship_service.get_original_relationship(&"heroine_anri")[&"configured"], "departure never deletes original record")
|
||||||
_extra_coverage(runtime)
|
_extra_coverage(runtime)
|
||||||
|
_review6_coverage(runtime)
|
||||||
_representative_saves(runtime)
|
_representative_saves(runtime)
|
||||||
runtime.free()
|
runtime.free()
|
||||||
clock.free()
|
clock.free()
|
||||||
@@ -1465,6 +1466,93 @@ func _run() -> void:
|
|||||||
quit(_failures)
|
quit(_failures)
|
||||||
|
|
||||||
|
|
||||||
|
func _review6_recovery(runtime: Variant, snapshot: Dictionary, accepted: bool) -> void:
|
||||||
|
var path := "user://p18_review6"
|
||||||
|
SaveService.delete_save(path)
|
||||||
|
var input := var_to_bytes(snapshot)
|
||||||
|
_expect(SaveService.save_snapshot(path, snapshot)[&"ok"], "review6 checksum-valid package written")
|
||||||
|
var before := var_to_bytes(runtime.create_snapshot())
|
||||||
|
var service: Variant = runtime.relationship_service
|
||||||
|
var service_bytes := var_to_bytes(service.create_snapshot())
|
||||||
|
var result: Dictionary = runtime.load_with_recovery(path)
|
||||||
|
_expect(result[&"ok"] == accepted, "review6 actual recovery outcome")
|
||||||
|
if not accepted:
|
||||||
|
_expect(result[&"code"] == &"RESTORE_FAILED" and not result[&"recovery_report"][&"restore_committed"] and not result[&"recovery_report"][&"forbidden_repairs_applied"], "review6 no forbidden repair")
|
||||||
|
_expect(runtime.relationship_service == service and var_to_bytes(service.create_snapshot()) == service_bytes, "recovery retains service identity and bytes")
|
||||||
|
_expect(var_to_bytes(runtime.create_snapshot()) == before, "recovery retains complete authority and clock")
|
||||||
|
_expect(var_to_bytes(snapshot) == input, "recovery preserves caller bytes")
|
||||||
|
SaveService.delete_save(path)
|
||||||
|
|
||||||
|
|
||||||
|
func _review6_coverage(runtime: Variant) -> void:
|
||||||
|
var snapshot: Dictionary = runtime.create_snapshot()
|
||||||
|
var container: Dictionary = snapshot[&"systems"][&"runtime"][&"relationships"]
|
||||||
|
container[&"legacy"]["relationships"][0]["route_lock"] = "EXCLUSIVE_LOCKED"
|
||||||
|
container[&"legacy"]["relationships"][0]["route_id"] = "review6_explicit_route"
|
||||||
|
container[&"relationships"] = container[&"legacy"]["relationships"].duplicate(true)
|
||||||
|
_expect(runtime.restore_snapshot(snapshot), "valid exclusive route normal positive control")
|
||||||
|
_review6_recovery(runtime, snapshot, true)
|
||||||
|
var bad := snapshot.duplicate(true)
|
||||||
|
var payload: Dictionary = bad[&"systems"][&"runtime"][&"relationships"]
|
||||||
|
payload[&"legacy"]["relationships"][0]["route_id"] = ""
|
||||||
|
payload[&"relationships"] = payload[&"legacy"]["relationships"].duplicate(true)
|
||||||
|
_reject_runtime(runtime, bad)
|
||||||
|
_review6_recovery(runtime, bad, false)
|
||||||
|
var service: Variant = runtime.relationship_service
|
||||||
|
service._register_threshold_candidate(service.get_relationship(&"aliases"), &"affection", 60, 360, false)
|
||||||
|
snapshot = runtime.create_snapshot()
|
||||||
|
var legacy: Dictionary = snapshot[&"systems"][&"runtime"][&"relationships"][&"legacy"]
|
||||||
|
legacy["threshold_candidates"][-1]["review6_extension"] = {"raw": [7, "keep"]}
|
||||||
|
var v1 := snapshot.duplicate(true)
|
||||||
|
v1[&"systems"][&"runtime"][&"relationships"] = legacy.duplicate(true)
|
||||||
|
_expect(runtime.restore_snapshot(v1), "real producer candidate v1 positive")
|
||||||
|
var roundtrip: Dictionary = runtime.create_snapshot()
|
||||||
|
_expect(roundtrip[&"systems"][&"runtime"][&"relationships"][&"legacy"] == legacy, "producer raw types and unknown facts preserved")
|
||||||
|
_expect(runtime.restore_snapshot(roundtrip), "producer v2 readback positive")
|
||||||
|
_review6_recovery(runtime, roundtrip, true)
|
||||||
|
var strings := roundtrip.duplicate(true)
|
||||||
|
var string_candidate: Dictionary = strings[&"systems"][&"runtime"][&"relationships"][&"legacy"]["threshold_candidates"][-1]
|
||||||
|
for identity: String in ["candidate_id", "relationship_id", "metric_id"]:
|
||||||
|
string_candidate[identity] = String(string_candidate[identity])
|
||||||
|
string_candidate["actor_ids"] = ["heroine_anri", "player"]
|
||||||
|
_expect(runtime.restore_snapshot(strings), "legacy String identifiers remain valid")
|
||||||
|
_expect(runtime.create_snapshot() == strings, "String identifiers retained without normalization")
|
||||||
|
_review6_recovery(runtime, strings, true)
|
||||||
|
_expect(runtime.restore_snapshot(roundtrip), "return to typed producer positive control")
|
||||||
|
# Restored raw order is intentional; new monotonic producer ids append in order.
|
||||||
|
service = runtime.relationship_service
|
||||||
|
var prior_candidates: Array = service.create_snapshot()[&"legacy"]["threshold_candidates"]
|
||||||
|
service._register_threshold_candidate(service.get_relationship(&"aliases"), &"affection", 61, 361, false)
|
||||||
|
var appended: Array = service.create_snapshot()[&"legacy"]["threshold_candidates"]
|
||||||
|
_expect(appended.slice(0, prior_candidates.size()) == prior_candidates and appended.size() == prior_candidates.size() + 1, "mutation preserves restored raw candidate order and appends producer id")
|
||||||
|
_expect(runtime.restore_snapshot(roundtrip), "candidate mutation control reset")
|
||||||
|
var wrong := {
|
||||||
|
"candidate_id": [null, 9, []], "relationship_id": [null, 9, []],
|
||||||
|
"metric_id": [null, 9, {}], "deduplication_key": [null, 9, {}],
|
||||||
|
"threshold": ["60", true, 60.0, null], "created_at": ["360", true, 360.0, null],
|
||||||
|
"requires_unlock_event": [1, "false", null],
|
||||||
|
"actor_ids": ["actors", {}, [9, &"player"], []]}
|
||||||
|
for key: String in wrong:
|
||||||
|
for value: Variant in wrong[key]:
|
||||||
|
bad = roundtrip.duplicate(true)
|
||||||
|
payload = bad[&"systems"][&"runtime"][&"relationships"]
|
||||||
|
payload[&"legacy"]["threshold_candidates"][-1][key] = value
|
||||||
|
service = runtime.relationship_service
|
||||||
|
var before := var_to_bytes(service.create_snapshot())
|
||||||
|
var input := var_to_bytes(payload)
|
||||||
|
_expect(not service.restore_snapshot(payload), "candidate wrong type rejected by service")
|
||||||
|
_expect(var_to_bytes(service.create_snapshot()) == before and var_to_bytes(payload) == input, "candidate service rejection atomic")
|
||||||
|
_reject_runtime(runtime, bad)
|
||||||
|
_review6_recovery(runtime, bad, false)
|
||||||
|
var bad_v1 := bad.duplicate(true)
|
||||||
|
bad_v1[&"systems"][&"runtime"][&"relationships"] = payload[&"legacy"].duplicate(true)
|
||||||
|
var legacy_input := var_to_bytes(payload[&"legacy"])
|
||||||
|
_expect(not service.restore_snapshot(payload[&"legacy"]), "v1 candidate wrong type rejected by service")
|
||||||
|
_expect(var_to_bytes(service.create_snapshot()) == before and var_to_bytes(payload[&"legacy"]) == legacy_input, "v1 candidate rejection preserves service and input")
|
||||||
|
_reject_runtime(runtime, bad_v1)
|
||||||
|
_review6_recovery(runtime, bad_v1, false)
|
||||||
|
|
||||||
|
|
||||||
func _reject_runtime(runtime: Variant, candidate: Dictionary) -> void:
|
func _reject_runtime(runtime: Variant, candidate: Dictionary) -> void:
|
||||||
var before := var_to_bytes(runtime.create_snapshot())
|
var before := var_to_bytes(runtime.create_snapshot())
|
||||||
var service: Variant = runtime.relationship_service
|
var service: Variant = runtime.relationship_service
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"new_audio_QA": false
|
"new_audio_QA": false
|
||||||
},
|
},
|
||||||
"tests/s4/test_original_relationship_runtime.gd": {
|
"tests/s4/test_original_relationship_runtime.gd": {
|
||||||
"source_sha256": "845ae200575563400c3497e517137effb7d4d928dd7e6a4990ed8aabdbda5ae6",
|
"source_sha256": "c56bdb99dd43a4e7ceb3ca2861e8db39972c5c4e805126a77e2f7da32ca5bdc9",
|
||||||
"ordinary_cleanup_prepared": false,
|
"ordinary_cleanup_prepared": false,
|
||||||
"ordinary_control_labels": [],
|
"ordinary_control_labels": [],
|
||||||
"old_bounded_teardown": false,
|
"old_bounded_teardown": false,
|
||||||
|
|||||||
Reference in New Issue
Block a user