Commit graph CultLib/tests/GameCult.Caching.Tests/DocumentShapeTests.cs
Author SHA1 Message Date
Meta Vens
5bd4e4ed6e Persist get-only and readonly members the way MessagePack does
The reflective registry now decides persistence from the same two facts
MessagePack 3.1.7's DynamicObjectResolver uses: whether it can assign a keyed
member after construction, and which constructor it picks.

- Every keyed public member is discovered, get-only properties included.
  MessagePack writes them all; the registry used to leave a get-only property
  out of the catalog and schema id even when a constructor filled it.
- MessagePack picks the [SerializationConstructor] if marked, otherwise the
  longest visible constructor whose parameters take the keyed members by
  position (probed: a longer non-matching constructor falls back to a shorter
  one). A member it cannot assign is read back only when its slot is below
  that constructor's parameter count.
- A readonly field is assignable under AllowPrivate (probed: round-trips with
  only a parameterless constructor), and otherwise only through the picked
  constructor. Keyed readonly fields that constructor fills are accepted.
- A keyed member MessagePack writes but never reads back is refused with a
  message naming the constructor position: a get-only property, a readonly
  field without AllowPrivate, or a non-public setter without AllowPrivate,
  when the picked constructor does not fill it (probed: loaded as default).
- An unkeyed public get-only property is refused; MessagePack throws "all
  public members must mark KeyAttribute or IgnoreMemberAttribute". The unkeyed
  message now names both fixes.

Schema ids change for five GameCult.Eve.Surface documents, whose keyed members
are all get-only properties filled by a [SerializationConstructor]:
gamecult.eve.surface, gamecult.eve.asset_catalog,
gamecult.eve.command_invocation, gamecult.eve.command_receipt and
gamecult.eve.provider_advertisement (EveProviderAdvertisementDocument). Their
catalogs gain those members. No .cc store under F:\Projects holds records of
these schemas: the gamecult.eve.provider_advertisement catalog in Gjallar's
scratch smoke stores belongs to GjallarProviderAdvertisementRecord, which has
no get-only members, and every other hit is prose or a git tree listing.

Consumer registration scan (CultLib src/tests/tools/samples, Ymir.Core,
Gjallar, AquaSynth Core/Faust, Mimir): 148 types register; the two rejections
(CounterState string key, BenchItem without [MessagePackObject]) are rejected
on 9139bf3 too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 00:21:21 +02:00
Meta Vens
9139bf3195 Make registry member visibility follow MessagePack's
The reflective registry now accepts what MessagePack 3.1.7 round-trips for a
[CultDocument] and refuses what it would silently lose or throw on:

- A property is writable when its setter is public, or any setter (private,
  internal, init-only) under AllowPrivate. A public getter with a non-public
  setter on a type without AllowPrivate is refused: MessagePack wrote it and
  loaded null. Private and init-only setters on AllowPrivate types, previously
  over-rejected, are accepted and round-trip.
- A [Key] on a non-public member of a type without AllowPrivate is refused;
  MessagePack skipped it silently.
- A class with no constructor MessagePack can pick is refused (the primary
  constructor case). The check mirrors DynamicObjectResolver as probed: the
  [SerializationConstructor] if marked, else any public (any, under
  AllowPrivate) constructor whose parameters take the keyed members by
  position with an assignable type.

Tests: InternalSetterWithoutAllowPrivate, KeyedNonPublicMemberWithoutAllowPrivate,
AllowPrivatePrimaryConstructor, AllowPrivateSettersRoundTrip (byte-matched
against MessagePackSerializer); InheritedPrivateSetter carries the new message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 00:03:23 +02:00
Meta Vens
96ee5fdce3 Delete the CultCache MessagePack source generator
The reflective CultDocumentRegistry is now the only descriptor authority,
and payloads always go through MessagePackSerializer with
OptionsFor(document assembly). No consumer builds AOT, IL2CPP or trimmed,
Unity always ran the reflective path, and no benchmark showed a gain,
while the generator duplicated the registry's shape rules.

Removed: GameCult.Caching.MessagePack.Generator and .Analyzers (solution,
project and analyzer references, NuGet pack list), CultGeneratedDocumentMetadata
(provider attribute, interface, definitions, loader), the generated descriptor
and codec branches in CultCache and CultDocumentMessagePackSerialization,
DocumentShapeSweepTests and the generator half of the unkeyed test, the
Microsoft.CodeAnalysis.CSharp test reference and InternalsVisibleTo.

The registry rejects, with a message naming the fix, what MessagePackSerializer
cannot serialize and what the generator used to paper over: a document without
[MessagePackObject], a non-public document without AllowPrivate = true, and a
non-public member of an AllowPrivate document that is not [IgnoreMember].
DocumentShapeTests pins every rejection message and the accepted shapes'
round trips against MessagePackSerializer bytes (keeps the 93A16E0305 pin).
Rejected shapes are emitted into a dynamic assembly, which registry
discovery skips.

Nested member type names use reflection's Outer+Inner form, not the
generator's dot form, so ReferenceHolderEntry's fixture id is re-pinned
(bd8506... -> 4ec0c5...). A scan of the schema catalogs in all 2786 .cc
stores under F:\Projects (AetheriaEve and CultLib-* worktrees, bin and obj
excluded) found no member type name naming a nested type in either form.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 23:38:08 +02:00