From 972e7b2ed43aad4f842e7f6d1dde62f136f98e3e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 22 Oct 2026 22:16:14 +0200 Subject: [PATCH] Caching: a reference gained on an otherwise identical slot is compatible drift (1.0.x ref-list stores open); 2.0.1 Co-Authored-By: Claude Sonnet 5.5 --- src/GameCult.Caching/CultCache.cs | 19 +++++++++++++++++-- unity/org.gamecult.cultlib/CHANGELOG.md | 10 ++++++++++ unity/org.gamecult.cultlib/package.json | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/GameCult.Caching/CultCache.cs b/src/GameCult.Caching/CultCache.cs index 53a7d18e..4a4f7d37 100644 --- a/src/GameCult.Caching/CultCache.cs +++ b/src/GameCult.Caching/CultCache.cs @@ -936,7 +936,22 @@ namespace GameCult.Caching errors.Add($"slot {localMember.Slot} changed type '{persistedMember.TypeName}' -> '{localMember.TypeName}'"); } - if (localMember.IsReference != persistedMember.IsReference) + // 1.0.x persisted a CultRecordRef collection as a plain value slot (not a reference, no target); the + // payload is the same array of keys. Gaining the reference classification on an otherwise identical slot + // is compatible drift. Losing it, or any other change of reference or target, is not. + var gainedReference = !persistedMember.IsReference + && persistedMember.TargetSchemaName == null + && localMember.IsReference + && localMember.TargetSchemaName != null; + if (gainedReference) + { + warnings.Add(new CultSchemaMigrationWarning + { + Code = "gained_reference_semantics", + Message = $"Local slot {localMember.Slot} ('{localMember.Name}') is a reference to '{localMember.TargetSchemaName}'; the persisted schema stored it as a plain value with the same payload." + }); + } + else if (localMember.IsReference != persistedMember.IsReference) { errors.Add($"slot {localMember.Slot} changed reference semantics"); } @@ -946,7 +961,7 @@ namespace GameCult.Caching errors.Add($"slot {localMember.Slot} changed cardinality"); } - if (!string.Equals(localMember.TargetSchemaName, persistedMember.TargetSchemaName, StringComparison.Ordinal)) + if (!gainedReference && !string.Equals(localMember.TargetSchemaName, persistedMember.TargetSchemaName, StringComparison.Ordinal)) { errors.Add($"slot {localMember.Slot} changed target schema '{persistedMember.TargetSchemaName ?? ""}' -> '{localMember.TargetSchemaName ?? ""}'"); } diff --git a/unity/org.gamecult.cultlib/CHANGELOG.md b/unity/org.gamecult.cultlib/CHANGELOG.md index f6b8c838..4c89550c 100644 --- a/unity/org.gamecult.cultlib/CHANGELOG.md +++ b/unity/org.gamecult.cultlib/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this package are documented in this file. +## [2.0.1] - 2026-10-22 + +### Fixed + +- A store written by 1.0.x with a list of record references (`CultRecordRef[]` or any `IEnumerable>` slot) + opens again. 2.0.0 classified such a slot as a reference to `T`, 1.0.x had persisted it as a plain value, and the schema + comparison refused the store with "changed reference semantics" and "changed target schema". The payload is the same array + of keys, so gaining the reference classification on an otherwise identical slot is now compatible drift (migration warning + `gained_reference_semantics`). A reference that is lost or retargeted, and any change of type or cardinality, still refuses. + ## [2.0.0] - 2026-10-22 ### Breaking diff --git a/unity/org.gamecult.cultlib/package.json b/unity/org.gamecult.cultlib/package.json index 4bd776bc..19e42b58 100644 --- a/unity/org.gamecult.cultlib/package.json +++ b/unity/org.gamecult.cultlib/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.cultlib", "displayName": "CultLib", - "version": "2.0.0", + "version": "2.0.1", "unity": "2021.3", "dependencies": { "org.gamecult.cultmath": "0.5.0"