P18-01-CODE: 原本関係モデル・旧値保全・Runtime保存と読取経路

This commit is contained in:
Agent Control Runner
2026-10-15 20:58:05 +09:00
parent b7a7cc01d4
commit 265ce30f81
6 changed files with 138 additions and 3 deletions
@@ -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}
+9 -1
View File
@@ -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
+1 -1
View File
@@ -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,