From 265ce30f81b1302ca374330f7129f02d6fde4697 Mon Sep 17 00:00:00 2001 From: Agent Control Runner Date: Thu, 15 Oct 2026 20:58:05 +0900 Subject: [PATCH] =?UTF-8?q?P18-01-CODE:=20=E5=8E=9F=E6=9C=AC=E9=96=A2?= =?UTF-8?q?=E4=BF=82=E3=83=A2=E3=83=87=E3=83=AB=E3=83=BB=E6=97=A7=E5=80=A4?= =?UTF-8?q?=E4=BF=9D=E5=85=A8=E3=83=BBRuntime=E4=BF=9D=E5=AD=98=E3=81=A8?= =?UTF-8?q?=E8=AA=AD=E5=8F=96=E7=B5=8C=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../completion_marker_contracts.json | 2 +- .../execution/p18-01-code/IMPLEMENTATION.md | 38 ++++++++ .../p18-01-code/repair-6-prepare.json | 1 + scripts/relationship/relationship_service.gd | 10 ++- .../s4/test_original_relationship_runtime.gd | 88 +++++++++++++++++++ tools/regression/suite-policy.json | 2 +- 6 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 docs/plans/original-completion-2026-10-02/execution/p18-01-code/repair-6-prepare.json diff --git a/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json b/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json index 8960459..b015278 100644 --- a/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json +++ b/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json @@ -18,7 +18,7 @@ "template": "Original relationship runtime: checks, failures", "required": true, "fixed_check_count_used": false, - "source_sha256_at_schema_audit": "845ae200575563400c3497e517137effb7d4d928dd7e6a4990ed8aabdbda5ae6", + "source_sha256_at_schema_audit": "c56bdb99dd43a4e7ceb3ca2861e8db39972c5c4e805126a77e2f7da32ca5bdc9", "authority": { "path": "docs/spec/acceptance/P18-01-code-r01.json", "scope": "new backend suite marker; native execution pending" diff --git a/docs/plans/original-completion-2026-10-02/execution/p18-01-code/IMPLEMENTATION.md b/docs/plans/original-completion-2026-10-02/execution/p18-01-code/IMPLEMENTATION.md index 2a9bb8e..6d25af7 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p18-01-code/IMPLEMENTATION.md +++ b/docs/plans/original-completion-2026-10-02/execution/p18-01-code/IMPLEMENTATION.md @@ -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, 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. + +### 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. diff --git a/docs/plans/original-completion-2026-10-02/execution/p18-01-code/repair-6-prepare.json b/docs/plans/original-completion-2026-10-02/execution/p18-01-code/repair-6-prepare.json new file mode 100644 index 0000000..828e2ee --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p18-01-code/repair-6-prepare.json @@ -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} diff --git a/scripts/relationship/relationship_service.gd b/scripts/relationship/relationship_service.gd index d4a49ef..ba632c3 100644 --- a/scripts/relationship/relationship_service.gd +++ b/scripts/relationship/relationship_service.gd @@ -517,9 +517,17 @@ func _parse_snapshot(snapshot: Dictionary) -> Dictionary: for raw_candidate: Variant in raw_candidates: if not raw_candidate is Dictionary: 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): 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_id := StringName(candidate.get(&"candidate_id", candidate.get("candidate_id", ""))) var relationship_id := StringName(candidate.get(&"relationship_id", candidate.get("relationship_id", ""))) diff --git a/tests/s4/test_original_relationship_runtime.gd b/tests/s4/test_original_relationship_runtime.gd index a06284c..eec45f9 100644 --- a/tests/s4/test_original_relationship_runtime.gd +++ b/tests/s4/test_original_relationship_runtime.gd @@ -1457,6 +1457,7 @@ func _run() -> void: _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") _extra_coverage(runtime) + _review6_coverage(runtime) _representative_saves(runtime) runtime.free() clock.free() @@ -1465,6 +1466,93 @@ func _run() -> void: 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: var before := var_to_bytes(runtime.create_snapshot()) var service: Variant = runtime.relationship_service diff --git a/tools/regression/suite-policy.json b/tools/regression/suite-policy.json index fdaefc3..48e1ae2 100644 --- a/tools/regression/suite-policy.json +++ b/tools/regression/suite-policy.json @@ -9,7 +9,7 @@ "new_audio_QA": false }, "tests/s4/test_original_relationship_runtime.gd": { - "source_sha256": "845ae200575563400c3497e517137effb7d4d928dd7e6a4990ed8aabdbda5ae6", + "source_sha256": "c56bdb99dd43a4e7ceb3ca2861e8db39972c5c4e805126a77e2f7da32ca5bdc9", "ordinary_cleanup_prepared": false, "ordinary_control_labels": [], "old_bounded_teardown": false,