From ee227bb7824cb06a32f5a49867a6b8479a475928 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 10 Oct 2026 21:34:57 +0200 Subject: [PATCH] Contract and changelog: unions refuse an unknown arm and an unnamed subtype Co-Authored-By: Claude Opus 5.5 --- .../cultcache-schema-compatibility.md | 21 +++++++++++++++++++ unity/org.gamecult.cultlib/CHANGELOG.md | 11 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md b/src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md index 9ae6f553..f44ff667 100644 --- a/src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md +++ b/src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md @@ -73,6 +73,27 @@ Typed lookups and watches (`Get`, `GetAll`, `GetByName`, type is assignable to `T`. A single-result lookup with several candidates throws. +## Unions + +A union is an abstract class or interface carrying `[Union(key, typeof(Arm))]` +attributes on itself (a union whose arms are also a union has its own key set; +attributes are not inherited). A union value is `nil` for a null reference, and +otherwise an arm: the two-element array `[key, armSlots]`, where `armSlots` is the +arm's own slot array (a unit arm is `[key, []]`). Keys start anywhere and may +have gaps. + +- A key is the identity of an arm. It is never reused, and a retired arm's key + stays a gap. +- An arm key the reader's union does not declare is refused on read, in a field, + a list or an arm, with an error naming the union type and the key. The + payload is never echoed, and the value is never read back as `null`: a runtime + that cannot name an arm cannot re-encode it. +- A value whose runtime type is not exactly a declared arm is refused on write, + naming the union and the type. A subtype of an arm is not the arm. +- A store that holds a refused arm fails to load, and the error names the record + key and its schema id. +- Arm slots follow the slot rules below; this rule does not change them. + ## Soft-migratable drift CultCache accepts compatible drift only when the local reader can still map the diff --git a/unity/org.gamecult.cultlib/CHANGELOG.md b/unity/org.gamecult.cultlib/CHANGELOG.md index a5217360..8a7083de 100644 --- a/unity/org.gamecult.cultlib/CHANGELOG.md +++ b/unity/org.gamecult.cultlib/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to this package are documented in this file. +## [Unreleased] + +### Breaking + +- CultCache refuses an unknown union arm on read and an unnamed union subtype on + write. A payload whose arm key the union does not declare used to decode to + `null` (and the next save wrote the loss back); a value whose type the union + does not name used to be written as `nil`. Both now throw, naming the union and + the key or type, and a store pull names the record key and schema id. Known arms + encode to the same bytes as before. + ## [1.0.60] ### Changed