docs: retain exact P18 preservation patch in verified gzip

This commit is contained in:
Takeshi
2026-10-15 21:09:54 +09:00
parent 1a6cc43f2e
commit cb0a58bc28
3 changed files with 11 additions and 506 deletions
@@ -1,506 +0,0 @@
diff --git a/autoload/simulation_runtime.gd b/autoload/simulation_runtime.gd
index 27bb139..82fa152 100644
--- a/autoload/simulation_runtime.gd
+++ b/autoload/simulation_runtime.gd
@@ -1056,14 +1056,10 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
return false
var restored_relationships := RelationshipService.new()
restored_relationships.configure_original_identity_validator(
- func(payload: Dictionary) -> bool: return _original_relationship_identities_valid(payload, restored_staff)
+ func(payload: Dictionary) -> bool: return _original_relationship_identities_valid(payload, restored_staff, restored_catalog)
)
if not restored_relationships.restore_snapshot(runtime_data[&"relationships"]):
return false
- for relationship: RelationshipState in restored_relationships.get_relationships():
- for actor_id: StringName in [relationship.actor_a_id, relationship.actor_b_id]:
- if actor_id != &"player" and not restored_staff.has_staff(actor_id) and not restored_guests.has_guest(actor_id):
- return false
var restored_events := EventService.new()
for raw_definition: Variant in runtime_data[&"event_definitions"]:
var definition := EventDefinition.from_variant(raw_definition)
@@ -5387,7 +5383,7 @@ func _configure_relationship_event_boundaries() -> void:
if relationship_service == null or event_service == null:
return
relationship_service.configure_original_identity_validator(
- func(payload: Dictionary) -> bool: return _original_relationship_identities_valid(payload, staff_service)
+ func(payload: Dictionary) -> bool: return _original_relationship_identities_valid(payload, staff_service, mvp_content_catalog)
)
relationship_service.configure_candidate_callback(
Callable(self, "_on_relationship_threshold_candidate")
@@ -6770,15 +6766,15 @@ func _execute_progress_staff_task(command: SimulationCommand) -> CommandResult:
result.command_id = command.command_id
return result
-func _original_relationship_identities_valid(payload: Dictionary, staff_registry: StaffService) -> bool:
+func _original_relationship_identities_valid(payload: Dictionary, staff_registry: StaffService, catalog: MvpContentCatalog) -> bool:
if payload.get("protagonist_id") != "player" or staff_registry == null:
return false
var subject := StringName(payload.get("subject_id", ""))
var rival := StringName(payload.get("rival_id", ""))
if subject == &"player" or rival == &"player" or subject == rival or not staff_registry.has_staff(subject):
return false
- var heroine_definition := mvp_content_catalog.get_entry(&"unique_characters", subject)
- var rival_definition := mvp_content_catalog.get_entry(&"unique_characters", rival)
+ var heroine_definition := catalog.get_entry(&"unique_characters", subject)
+ var rival_definition := catalog.get_entry(&"unique_characters", rival)
var subject_staff := staff_registry.get_staff(subject)
return subject_staff.employment_kind == StaffRuntime.EmploymentKind.UNIQUE \
and heroine_definition.get(&"role_id") == String(subject_staff.role_id) \
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 5474439..6d5e0ae 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
@@ -7,7 +7,7 @@
"template": "Original relationship model: <checks> checks, <failures> failures",
"required": true,
"fixed_check_count_used": false,
- "source_sha256_at_schema_audit": "ad509f5fb67ab91e1d1d99a08af51e4695e411b9a2541ea63ee22b1555cb0924",
+ "source_sha256_at_schema_audit": "c74b243dbbb54dba9bbc165e29508cca910f0beb63e32d25ebbb8ea606dccce9",
"authority": {
"path": "docs/spec/acceptance/P18-01-code-r01.json",
"scope": "new backend suite marker; native execution pending"
@@ -18,7 +18,7 @@
"template": "Original relationship runtime: <checks> checks, <failures> failures",
"required": true,
"fixed_check_count_used": false,
- "source_sha256_at_schema_audit": "374f3d31a65d2f2b62dfef29721c8c838575b856f4fa5a0bca31c6ba4c7e09dd",
+ "source_sha256_at_schema_audit": "349271cc8b8a4750ef339ecac6905852bd7a5d006cca68ee10d5d0c737f426de",
"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 453eeb2..efc5cea 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
@@ -1,6 +1,53 @@
# P18-01-CODE implementation handoff
-Status: submitted for Runner validation; no acceptance or unit completion claimed.
+Status: repair 4 implementation prepared for Runner; no acceptance or unit completion claimed. P02-01 representative inputs remain an unavailable context dependency, detailed below.
+
+## Repair 3 historical failure (2026-10-15)
+
+Historical record only: stopping independent repairs over decoder permissiveness was wrong. The commander producer-evidence interpretation supersedes this old policy concern; the following describes the failed repair-3 attempt, not the current policy.
+
+Inspected baseline d2a1f80a613e7c77b02b941026bc49c2d85db863 using read-only git show.
+RelationshipState.from_snapshot delegates to configure; _value preserves canonical
+key precedence. Numeric fields use int(), with clampi/maxi where applicable.
+Thus finite fractional numbers are truncated as well as integral JSON floats;
+numeric strings such as metrics.affection = "42" are also converted. The baseline
+RelationshipService._parse_snapshot similarly converts version and candidate
+sequence with int(). These are broader than a finite-integral-float-only policy.
+No acceptance policy for these conflicting formats has been selected here.
+
+Baseline normalization also replaces non-Dictionary metrics, action_counts,
+daily_metric_deltas, flags and crossed_thresholds with defaults, coerces flags
+with bool(), and ignores non-Array threshold collections. The immutable spec
+requires wrong types to reject, while the repair direction requires baseline
+legitimate acceptance without a new restriction or loosening. In particular,
+accepting or rejecting numeric strings cannot satisfy both without an explicit
+policy decision. Non-numeric/non-finite input, wrong containers, future versions,
+duplicates, invalid references, corrupt original records and projection mismatches
+remain required rejections under the spec; this report does not waive any.
+
+Baseline Runtime restore delegates legacy relationships to RelationshipService
+without the added actor-existence loop. Baseline GuestService.despawn_guest retains
+the departed guest in persistent_records and removes it from active guests.
+The held actor loop is therefore still an unresolved repair item. Candidate
+catalog selection, lifecycle tests, mutation-through-alias tests, atomic rejection
+coverage and source-pin updates are also still outstanding. The numeric Reader
+helpers in SimulationSnapshot and SaveService accept bounded finite integral JSON
+floats for their own envelope fields; they do not establish a policy overriding
+RelationshipState's coercions. P02-01 representative-save applicability has not
+been established; no fixture acceptance claim is made.
+
+Only this handoff and prepare-only evidence were changed. Production code, test
+sources and marker/policy pins were left untouched pending policy resolution.
+Prepare-only exited 0: 1,311 source files, 67,310,742 bytes; 3,384,279,040 free
+bytes; 381,883,542 conservative required bytes; capacity available. Seven suites:
+original_relationship_model, original_relationship_runtime,
+relationship_event_service, runtime_relationship_events,
+runtime_snapshot_serialization, runtime_save_lifecycle, staff_management.
+308 future parse paths and nine future native phases; engines/stages = 0,
+actual_checks = null, formal_acceptance = false. The first invocation omitted
+required --output and exited 2; the corrected invocation used the allowed
+p18-01-code/repair-3-prepare output path. Native checks were not run.
+P18-01 remains 30/88, in progress, with no partial credit.
Scope: dedicated original relationship state, lossless legacy snapshot preservation,
Runtime registration and read projection, and the contract-listed tests and collector.
@@ -34,8 +81,8 @@ The acceptance specification without its final newline matches the contract SHA2
catalog membership does not invent recruitment. No new original update policy,
contact, route, attack, threshold or duel behavior is defined.
- Snapshot restore validates candidates before replacing live services, then
- rebinds the identity validator to live staff. Existing legacy actor references
- must resolve against player, restored staff or restored guests.
+ rebinds the identity validator to live staff. Legacy relationships retain the original acceptance of departed guests; original
+ records separately validate staff/person/catalog references.
- Every staff-management row exposes `original_relationship`. Missing records
have `configured: false` and null values, distinct from explicit zero. Configured
projections are copies, so reads and caller edits cannot change service state.
@@ -77,3 +124,91 @@ in_progress: ordinary Japanese UI rendering/equality, approved production values
all-seven recruitment, final full required roster and applicable Windows final
acceptance remain outstanding. No git writes, other agents or Runner controls
were used by worker.
+
+## Repair 4 producer interpretation and independent repairs
+
+The unchanged wrong-types/valid-legacy contract follows legitimate producers,
+not corrupt-input coercions. RelationshipState.create_snapshot
+(scripts/relationship/relationship_state.gd:132-152) emits String ids/route,
+int numeric fields, named route_lock, Dictionary numeric/bool collections and
+Array thresholds. Its validity check (:99-101) requires int metrics.
+Read-only git log identifies 423fd6d as the only history entry for that file;
+git show of that producer (:132-152) also emits the enum NAME String, not int.
+
+Actual writer scripts/core/save_service.gd:874-878 serializes the snapshot with
+var_to_bytes and ZSTD; Reader :1021-1028 uses bytes_to_var. Thus world numeric
+ints remain ints. JSON decoding (:970-978) applies to the manifest, not relationship
+payloads. Integral JSON floats are not an established relationship save format
+on this path. Accept int legacy values, named route_lock and producer collections.
+Reject numeric strings, null, bool-as-number, fractional/nonfinite numbers, float
+relationship numbers, int route_lock, wrong containers and invalid enum names.
+Unknown extensions remain opaque; canonical values precede aliases, and aliases,
+unknown facts and collection order remain raw retained facts. Neither old axis
+configures original Affection/Lust/Netorare.
+
+relationship_service.gd _legacy_types_valid now also checks collection key types
+and restricts route_lock to producer enum names. The pre-existing int/container
+checks implement the producer policy. No conversion or rewrite of raw input occurs.
+Runtime candidate validation now takes restored_catalog; command/service binding
+uses the live catalog. Removed only the added active legacy actor loop.
+GuestService.despawn_guest (scripts/guest/guest_service.gd:954-978) retains the
+persistent record and removes the active guest, so that loop rejected valid saves.
+Original identity/person validation remains in place.
+Existing StaffService.remove_staff (:897-898) and retire_staff (:942-943) already
+refuse UNIQUE departures before changes; tests assert refusal and saved original
+record survival. No new departure rule or record deletion was added.
+
+The two new suites now cover real Service and Runtime affection/dependence
+mutations from v1 affinity/obligation, canonical precedence, raw extensions and
+order, null original values, v2 restore/rebind, departed persistent guest acceptance,
+and atomic malformed candidates. Rejection checks retain service identity/state,
+whole Runtime bytes and caller input. The five existing suites remain unchanged.
+Fixtures are validation inputs, not production values or newly hired characters.
+
+Collector derives every sorted .gd path from the exact shared.source_refs map.
+Verification binds parse-paths.json, the receipt count, the unique zero-failure
+FULL_DEPENDENCY_PARSE marker, actual_parse_checks and the whole retained stage
+to that same source map. No count-specific skips or other provenance changes.
+This frozen map has 308 paths; expanded maps must verify their own full count.
+
+### Precise remaining context gap
+
+The P02-01 input manifest referenced by
+docs/plans/original-completion-2026-10-02/execution/multi-agent-dev-handoff/delivery_contract_proposal.json:333
+is absent at artifacts/original-completion-2026-10-02/p02-01-fixed-baseline-r01/input-manifest.json
+in this worktree. Reading the exact production-root counterpart returns
+Operation not permitted. Production-root listing/search is also denied.
+No representative save bytes or decoded fixture are available in this checkout;
+.review-material is absent. Accordingly their exact types/applicability have NOT
+been verified, no older conflicting producer shape is asserted, and no replacement
+representative fixture has been invented. Runner/owner must supply readable pinned
+manifest and applicable save members to finish that mandatory evidence and test.
+This gap did not stop the independent repairs. No native checks were executed.
+UI, values, routes, contacts and duel remain untouched. P18-01 remains 30/88,
+in progress. Native seven-suite frozen evidence, p18-model validation, separate
+Opus review, latest-main integration and fresh merged-source evidence remain
+Root/Runner work; no completion claim is made.
+
+### Worker static/prepare evidence (repair 4)
+
+Python AST parsed successfully. git diff --check passed. Both new suite SHA-256
+pins match their final source in marker contracts and suite-policy; all other
+rows are unchanged. The five existing suite files are byte-identical to HEAD.
+No engine, native collector, default profile or full regression was run.
+
+Prepare-only roster (seven): original_relationship_model,
+original_relationship_runtime, relationship_event_service,
+runtime_relationship_events, runtime_snapshot_serialization,
+runtime_save_lifecycle, staff_management. Full paths are the unchanged ROSTER in
+tools/run_p18_relationship_tests.py:17-25.
+Initial repair-4 preview: 1,311 files, 67,318,770 source bytes,
+3,388,039,168 free bytes, conservative single-set admission 381,891,570 bytes,
+308 parse paths, nine future phases, engines/stages zero, actual_checks null,
+formal_acceptance false. A final preview after final edits is retained separately
+as repair-4-prepare.json. No native pass is implied by this preview.
+
+Using the supplied old output estimate conservatively as 474 MiB per full set
+(including 351 MiB engine), two fresh sets require 948 MiB = 994,050,048 bytes.
+They fit the observed ~3.39 GB free space, with ~2.39 GB left before other new
+consumers. This is capacity arithmetic, not a reservation or a measured new run;
+actual output growth remains Root's admission responsibility.
diff --git a/scripts/relationship/relationship_service.gd b/scripts/relationship/relationship_service.gd
index 35d4670..d4a49ef 100644
--- a/scripts/relationship/relationship_service.gd
+++ b/scripts/relationship/relationship_service.gd
@@ -543,7 +543,7 @@ func _parse_snapshot(snapshot: Dictionary) -> Dictionary:
static func _legacy_types_valid(raw: Dictionary) -> bool:
for key: String in ["relationship_id", "actor_a_id", "actor_b_id"]:
- if not (raw.get(key) is String or raw.get(key) is StringName):
+ if not raw.get(key) is String:
return false
for key: String in ["shield_max", "shield_current", "last_event_at", "daily_attack_count", "attack_day", "metric_delta_day"]:
if raw.has(key) and not raw[key] is int:
@@ -559,9 +559,9 @@ static func _legacy_types_valid(raw: Dictionary) -> bool:
for value: Variant in raw.get(key, {}).values():
if not value is int:
return false
- for key: String in ["action_counts", "flags"]:
+ for key: String in ["metrics", "action_counts", "daily_metric_deltas", "flags", "crossed_thresholds"]:
for identity: Variant in raw.get(key, {}):
- if not (identity is String or identity is StringName):
+ if not identity is String:
return false
for value: Variant in raw.get("flags", {}).values():
if not value is bool:
@@ -572,10 +572,11 @@ static func _legacy_types_valid(raw: Dictionary) -> bool:
for threshold: Variant in value:
if not threshold is int:
return false
- if raw.has("route_id") and not (raw["route_id"] is String or raw["route_id"] is StringName):
- return false
- if raw.has("route_lock") and not (raw["route_lock"] is int or raw["route_lock"] is String or raw["route_lock"] is StringName):
+ if raw.has("route_id") and not raw["route_id"] is String:
return false
+ if raw.has("route_lock"):
+ if not raw["route_lock"] is String or not RelationshipStateType.RouteLock.keys().has(raw["route_lock"]):
+ return false
return true
diff --git a/tests/s4/test_original_relationship_model.gd b/tests/s4/test_original_relationship_model.gd
index d2a2c81..1f24b4f 100644
--- a/tests/s4/test_original_relationship_model.gd
+++ b/tests/s4/test_original_relationship_model.gd
@@ -30,7 +30,50 @@ func _service() -> RelationshipService:
)
+static func legacy_fixture() -> Dictionary:
+ return {"version": 1, "relationships": [
+ {"relationship_id": "aliases", "actor_a_id": "heroine_anri", "actor_b_id": "player",
+ "metrics": {"affinity": 41, "obligation": 19, "extension": {"opaque": [3, "keep"]}},
+ "extra": ["raw", 2.5]},
+ {"relationship_id": "second", "actor_a_id": "departed", "actor_b_id": "player",
+ "metrics": {"trust": 7}}],
+ "threshold_candidates": [], "next_candidate_sequence": 1, "extension": ["ordered", 7]}
+
+
+func _alias_mutations() -> void:
+ var service := _service()
+ var legacy := legacy_fixture()
+ var input := var_to_bytes(legacy)
+ _expect(service.restore_snapshot(legacy), "int v1 aliases accepted")
+ _expect(service.apply_value_change(&"aliases", &"affection", 2, 0).is_success(), "affection mutation through real service")
+ _expect(service.apply_value_change(&"aliases", &"dependence", 3, 0).is_success(), "dependence mutation through real service")
+ var saved := service.create_snapshot()
+ var raw: Dictionary = saved[&"legacy"]["relationships"][0]
+ _expect(raw["metrics"]["affection"] == 43 and raw["metrics"]["dependence"] == 22, "canonical changes merged")
+ _expect(raw["metrics"]["affinity"] == 41 and raw["metrics"]["obligation"] == 19, "aliases remain old facts")
+ _expect(raw["metrics"]["extension"] == legacy["relationships"][0]["metrics"]["extension"] and raw["extra"] == ["raw", 2.5], "unknown facts survive mutation")
+ _expect(saved[&"legacy"]["relationships"][1] == legacy["relationships"][1], "record order preserved")
+ var restored := _service()
+ _expect(restored.restore_snapshot(saved), "mutated v2 restores")
+ _expect(var_to_bytes(restored.create_snapshot()) == var_to_bytes(saved), "mutated v2 raw order and bytes preserved")
+ _expect(restored.get_relationship(&"aliases").get_metric(&"affection") == 43 and restored.get_relationship(&"aliases").get_metric(&"dependence") == 22, "canonical values take precedence over raw aliases")
+ for key: StringName in [&"affection", &"lust", &"netorare"]:
+ _expect(restored.get_original_relationship(&"heroine_anri")[key] == null, "old axes never configure original " + String(key))
+ _expect(var_to_bytes(legacy) == input, "mutation never changes input bytes")
+ for value: Variant in ["50", null, true, 50.0, 1.5, INF, NAN]:
+ var bad := legacy_fixture()
+ bad["relationships"][0]["metrics"]["affinity"] = value
+ _rejected(restored, bad)
+ var bad := legacy_fixture()
+ bad["relationships"][0]["metrics"] = []
+ _rejected(restored, bad)
+ bad = saved.duplicate(true)
+ bad[&"relationships"] = []
+ _rejected(restored, bad)
+
+
func _run() -> void:
+ _alias_mutations()
var service := _service()
var before := var_to_bytes(service.create_snapshot())
var unset := service.get_original_relationship(&"heroine_anri")
diff --git a/tests/s4/test_original_relationship_runtime.gd b/tests/s4/test_original_relationship_runtime.gd
index f2aa656..7029ce1 100644
--- a/tests/s4/test_original_relationship_runtime.gd
+++ b/tests/s4/test_original_relationship_runtime.gd
@@ -71,8 +71,90 @@ func _run() -> void:
invalid["rival_id"] = "missing"
_expect(not runtime.relationship_service.register_original_relationship(invalid).is_success(), "restore rebind keeps identity validator")
_expect(runtime.relationship_service.register_original_relationship(fixture).is_success(), "restored service accepts correct identities")
+ _expect(not runtime.staff_service.remove_staff(&"heroine_anri").is_success(), "registered UNIQUE heroine removal refused")
+ _expect(not runtime.staff_service.retire_staff(&"heroine_anri").is_success(), "registered UNIQUE heroine retirement refused")
+ var retained: Dictionary = runtime.create_snapshot()
+ _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)
runtime.free()
clock.free()
state.free()
print("Original relationship runtime: %d checks, %d failures" % [_checks, _failures])
quit(_failures)
+
+
+func _reject_runtime(runtime: Variant, candidate: Dictionary) -> void:
+ var before := var_to_bytes(runtime.create_snapshot())
+ var service: Variant = runtime.relationship_service
+ var service_bytes := var_to_bytes(service.create_snapshot())
+ var input := var_to_bytes(candidate)
+ _expect(not runtime.restore_snapshot(candidate), "malformed candidate rejected atomically")
+ _expect(runtime.relationship_service == service and var_to_bytes(service.create_snapshot()) == service_bytes, "rejection preserves prior service identity and bytes")
+ _expect(var_to_bytes(runtime.create_snapshot()) == before, "rejection preserves whole Runtime bytes")
+ _expect(var_to_bytes(candidate) == input, "rejection preserves all input bytes")
+
+
+func _extra_coverage(runtime: Variant) -> void:
+ var candidate: Dictionary = runtime.create_snapshot()
+ var legacy := Fixtures.legacy_fixture()
+ candidate[&"systems"][&"runtime"][&"relationships"] = legacy
+ # GuestService's legitimate departure representation, retained after active removal.
+ candidate[&"systems"][&"runtime"][&"guests"][&"persistent_records"].append({
+ &"guest_id": "departed", &"guest_type_id": "traveler", &"debt_gold": 0,
+ &"unresolved_incident_ids": [], &"outstanding_reservation_ids": []})
+ candidate[&"systems"][&"runtime"][&"progression"][&"metrics"][&"guest_lifetime"] += 1
+ var input := var_to_bytes(candidate)
+ _expect(runtime.restore_snapshot(candidate), "int v1 and departed persistent guest relationships accepted")
+ _expect(var_to_bytes(candidate) == input, "accepted v1 Runtime input unchanged")
+ for change: Dictionary in [{&"metric_id": &"affection", &"delta": 2}, {&"metric_id": &"dependence", &"delta": 3}]:
+ change[&"relationship_id"] = &"aliases"
+ _expect(runtime.submit_command(&"change_relationship", change).is_success(), "alias changes use public Runtime command")
+ var saved: Dictionary = runtime.create_snapshot()
+ var raw: Dictionary = saved[&"systems"][&"runtime"][&"relationships"][&"legacy"]["relationships"][0]
+ _expect(raw["metrics"]["affection"] == 43 and raw["metrics"]["dependence"] == 22, "Runtime merges canonical mutations")
+ _expect(raw["metrics"]["affinity"] == 41 and raw["metrics"]["obligation"] == 19, "Runtime keeps raw aliases")
+ _expect(raw["extra"] == ["raw", 2.5] and raw["metrics"]["extension"] == {"opaque": [3, "keep"]}, "Runtime keeps unknown facts")
+ var saved_bytes := var_to_bytes(saved)
+ _expect(runtime.restore_snapshot(saved), "mutated v2 Runtime restores and rebinds")
+ _expect(var_to_bytes(runtime.create_snapshot()) == saved_bytes, "v2 Runtime preserves raw bytes and order")
+ _expect(runtime.relationship_service.get_relationship(&"aliases").get_metric(&"affection") == 43 and runtime.relationship_service.get_relationship(&"aliases").get_metric(&"dependence") == 22, "Runtime canonical precedence survives rebind")
+ for key: StringName in [&"affection", &"lust", &"netorare"]:
+ _expect(runtime.relationship_service.get_original_relationship(&"heroine_anri")[key] == null, "Runtime old axes leave original unset")
+ _expect(runtime.relationship_service.register_original_relationship(Fixtures.fixture()).is_success(), "rebound service accepts original")
+ saved = runtime.create_snapshot()
+ for kind: String in ["numeric_string", "metrics_container", "duplicate", "version_type", "unknown_version", "projection", "non_heroine", "counterpart", "non_staff", "corrupt"]:
+ var bad := saved.duplicate(true)
+ var relationships: Dictionary = bad[&"systems"][&"runtime"][&"relationships"]
+ match kind:
+ "numeric_string", "metrics_container":
+ if kind == "numeric_string":
+ relationships[&"legacy"]["relationships"][0]["metrics"]["affection"] = "50"
+ else:
+ relationships[&"legacy"]["relationships"][0]["metrics"] = []
+ relationships[&"relationships"] = relationships[&"legacy"]["relationships"].duplicate(true)
+ "duplicate":
+ relationships[&"original_relationships"].append(Fixtures.fixture())
+ "version_type":
+ relationships[&"version"] = "2"
+ "unknown_version":
+ relationships[&"version"] = 3
+ "projection":
+ relationships[&"relationships"] = []
+ "non_heroine":
+ var rival_staff := StaffRuntime.new().configure(&"rival_rai", &"influencer", {&"employment_kind": &"UNIQUE"})
+ _expect(rival_staff.is_valid(), "non-heroine rejection fixture is valid UNIQUE staff")
+ bad[&"systems"][&"runtime"][&"staff"][&"staff"].append(rival_staff.create_snapshot())
+ relationships[&"original_relationships"][0]["subject_id"] = "rival_rai"
+ relationships[&"original_relationships"][0]["rival_id"] = "heroine_anri"
+ var registry := StaffService.new()
+ _expect(registry.register_staff(rival_staff).is_success(), "non-heroine UNIQUE exists in identity fixture registry")
+ _expect(Fixtures.Model.from_snapshot(relationships[&"original_relationships"][0]) != null, "non-heroine payload passes structural model validation")
+ _expect(not runtime._original_relationship_identities_valid(relationships[&"original_relationships"][0], registry, runtime.mvp_content_catalog), "catalog rejects non-heroine UNIQUE subject specifically")
+ "counterpart":
+ relationships[&"original_relationships"][0]["rival_id"] = "rival_other"
+ "non_staff":
+ relationships[&"original_relationships"][0]["subject_id"] = "departed"
+ "corrupt":
+ relationships[&"original_relationships"][0].erase("lust")
+ _reject_runtime(runtime, bad)
diff --git a/tools/regression/suite-policy.json b/tools/regression/suite-policy.json
index 63d34d8..6604a3d 100644
--- a/tools/regression/suite-policy.json
+++ b/tools/regression/suite-policy.json
@@ -2,14 +2,14 @@
"schema_version": 1,
"suite_policies": {
"tests/s4/test_original_relationship_model.gd": {
- "source_sha256": "ad509f5fb67ab91e1d1d99a08af51e4695e411b9a2541ea63ee22b1555cb0924",
+ "source_sha256": "c74b243dbbb54dba9bbc165e29508cca910f0beb63e32d25ebbb8ea606dccce9",
"ordinary_cleanup_prepared": false,
"ordinary_control_labels": [],
"old_bounded_teardown": false,
"new_audio_QA": false
},
"tests/s4/test_original_relationship_runtime.gd": {
- "source_sha256": "374f3d31a65d2f2b62dfef29721c8c838575b856f4fa5a0bca31c6ba4c7e09dd",
+ "source_sha256": "349271cc8b8a4750ef339ecac6905852bd7a5d006cca68ee10d5d0c737f426de",
"ordinary_cleanup_prepared": false,
"ordinary_control_labels": [],
"old_bounded_teardown": false,
diff --git a/tools/run_p18_relationship_tests.py b/tools/run_p18_relationship_tests.py
index c520311..925ccca 100644
--- a/tools/run_p18_relationship_tests.py
+++ b/tools/run_p18_relationship_tests.py
@@ -199,6 +199,7 @@ def seal(out, refs, engine, contract_sha):
source = content_map(refs)
shared.write(out / 'receipt.json', {
'schema': 'guarded-p18-v2', 'source_map': source,
+ 'parse_checks': len([name for name in refs if name.endswith('.gd')]),
'source_map_sha256': digest(source),
'project_settings_sha256': source['project.godot']['sha256'],
'marker_contract_sha256': contract_sha,
@@ -281,8 +282,10 @@ def verify(out, refs, markers, contracts, policy, contract_sha, parse_paths):
require(engine and shared.sha(engine) == start['engine_sha256']
== manifest['engine_sha256'] == files['engine.bin']['sha256'],
'current native engine and retained bytes pinned')
- require(read('parse-paths.json') == parse_paths and len(parse_paths) == 308,
- 'all 308 exact source parse paths')
+ derived_paths = ['res://' + name for name in sorted(refs) if name.endswith('.gd')]
+ require(read('parse-paths.json') == parse_paths == derived_paths
+ and manifest['parse_checks'] == len(derived_paths),
+ 'all exact source parse paths and receipt count')
require(end['passed'] is True and end['engine_ended'] is True and end['error'] is None
and end['cleanup_errors'] == [] and end['guard_errors'] == [],
'finished successful collection without guard/cleanup error')
@@ -463,8 +466,9 @@ def verify(out, refs, markers, contracts, policy, contract_sha, parse_paths):
else:
require(row['controlled_environment'] == {}, 'fresh import environment')
if phase == 'parse':
- require(re.findall(r'(?m)^FULL_DEPENDENCY_PARSE checks=([0-9]+) failures=([0-9]+)\n', raw)
- == [('308', '0')] and row['actual_parse_checks'] == 308, 'unique all-script parse marker')
+ require(len(re.findall(r'(?m)^FULL_DEPENDENCY_PARSE\b', raw)) == 1
+ and re.findall(r'(?m)^FULL_DEPENDENCY_PARSE checks=([0-9]+) failures=([0-9]+)\n', raw)
+ == [(str(len(parse_paths)), '0')] and row['actual_parse_checks'] == len(parse_paths), 'unique all-script parse marker')
if test:
parsed = markers.parse_raw(test, raw, contracts, source[test]['sha256'])
require(parsed['passed'] and row['completion'] == parsed
@@ -492,7 +496,7 @@ def verify(out, refs, markers, contracts, policy, contract_sha, parse_paths):
'whole raw evidence unchanged during verification')
require(content_map(shared.source_refs(shared.HERE.parent)) == source,
'whole current source unchanged during verification')
- print(json.dumps({'verified': True, 'actual_suites': 7, 'parse_checks': 308,
+ print(json.dumps({'verified': True, 'actual_suites': 7, 'parse_checks': len(parse_paths),
'actual_checks': end['actual_all_roster_checks'], 'engines_started': 0,
'formal_acceptance': False, 'original_unit_complete': False}))
return 0
@@ -536,7 +540,6 @@ def main(argv=None):
require(contracts[test]['source_sha256_at_schema_audit'] == refs[test]['sha256'],
'new strict source-hashed marker')
parse_paths = ['res://' + name for name in sorted(refs) if name.endswith('.gd')]
- require(len(parse_paths) == 308, 'exact expected parse count')
if args.prepare_only:
ancestor = out.parent
while not ancestor.exists():
@@ -545,7 +548,7 @@ def main(argv=None):
size = sum(p['bytes'] for p in refs.values())
print(json.dumps({'roster': ROSTER, 'source_files': len(refs), 'source_bytes': size,
'free_bytes': free, 'conservative_required_bytes': size + shared.MIN_SPACE,
- 'capacity_available': free >= size + shared.MIN_SPACE, 'parse_paths': 308,
+ 'capacity_available': free >= size + shared.MIN_SPACE, 'parse_paths': len(parse_paths),
'future_native_phases': 9, 'actual_checks': None, 'engines': 0, 'stages': 0,
'formal_acceptance': False}))
return 0
@@ -0,0 +1,11 @@
{
"original_path": "docs/plans/original-completion-2026-10-02/execution/p18-01-resume-20261015/PRESERVED_REPAIR4_DIFF.patch",
"original_bytes": 32892,
"original_sha256": "b4d570884c1d0eb90c6780c7346419cb7e3f43780ec590bb472822f087e3f72d",
"compressed_path": "docs/plans/original-completion-2026-10-02/execution/p18-01-resume-20261015/PRESERVED_REPAIR4_DIFF.patch.gz",
"compressed_bytes": 10186,
"compressed_sha256": "73f67bd136833919e20bbc6d3e0dc1c31524e8956eb71a75d3ceafc94e55a402",
"verified_content_preserved": true,
"raw_original_also_in_git_commit": "1a6cc43f2efe5c0128d7677c6ee5dd17dcb15ce2",
"reason": "Raw patch contains intentional diff context prefixes followed by tabs, which git diff --check misinterprets when patch is added as text. Preserve exact raw artifact in verified gzip, not whitespace-edit proof."
}