From c5b03cb4555a3bf958f2e5d4c3c77f5d3588daa2 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 22 Sep 2026 02:54:53 +0200 Subject: [PATCH] CultNet selection cut1, commit 2b: collapse IsSameCultDocumentSchema into the one alias matcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CultMesh.IsSameCultDocumentSchema (CultMesh.cs) and the decode-time alias check in CultMeshSnapshots.DecodeSnapshotDocuments both re-implemented schema-identity comparison independently of CultNetSchemaAliasMatching, the one matcher landed in commit 1. Both now call CultNetSchemaAliasMatching.Matches; the private InferSchemaName duplicated at CultMeshSnapshots.cs is deleted (Matches already applies the same .vN inference internally). Spec discrepancy, reported per the Hands brief (map contradicted by the Body): docs/cultnet-selection-cut.md section 4 lists CultMeshSnapshots.cs:1033-1047 (RawSnapshotPayloadMatchesSchema) and :1078-1088 (InferSchemaName) as deletions, on the stated reasoning that decode no longer needs a client-side re-filter because the server's v1 answer is exact. Deleting RawSnapshotPayloadMatchesSchema outright breaks two existing, still-relevant Mesh tests: SnapshotHelpers_DecodeSameSchemaAliasFromForeignSchemaId and SnapshotEndpoint_SyncsCompatibleForeignSchemaIdIntoLocalCache (CultMeshStreamingTests.cs). Both cover a wire schemaId this runtime cannot resolve by any alias at all (a foreign/runtime-generated id, e.g. "runtime.generated.mesh-note.ui.42") where the only way to decode is to trust the payload's own embedded schemaVersion. That is a decode capability, not a re-filter of an already-exact server answer, and the map's stated reason for deleting it does not apply to it. Smallest change keeping the map's intent true: kept RawSnapshotPayloadMatchesSchema and TryReadSchemaVersion, deleted only the duplicate InferSchemaName inside it and routed the comparison through CultNetSchemaAliasMatching.Matches. Test counts, no regressions: Networking 162/162, Mesh 252/253 (the one pre-existing skip), Caching 188/188. Negative grep: MatchesRequestedSchema empty. The one remaining "MatchesSchema(" grep hit is RawSnapshotPayloadMatchesSchema's own name and declaration, kept for the reason above. Still outstanding from the map's Mesh collapse (§4/§11, ~365 gross lines): the five cloned CultMeshSnapshotRequestOptions-carrying option records, the "no schema filter when keys are given" rule (x3), CleanSnapshotFilter, the four-tier ReadDocumentFromSnapshotResponse fallback, and every selector literal in CultMeshClient.cs/CultMeshBodyDemand.cs/CultMesh.cs peer-snapshot and body-subscribe construction. Those require CultMeshSnapshotRequestOptions (and the v0 wire builders that read it) to carry a Selection instead of separate SchemaIds/RecordKeys — a wider, structural change across Mesh's public surface that is not done in this commit. Recorded as the next slice. Co-Authored-By: Claude Sonnet 5 --- src/GameCult.Mesh/CultMesh.cs | 7 +++--- src/GameCult.Mesh/CultMeshSnapshots.cs | 31 +++++++------------------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/GameCult.Mesh/CultMesh.cs b/src/GameCult.Mesh/CultMesh.cs index e7436db2..0339158f 100644 --- a/src/GameCult.Mesh/CultMesh.cs +++ b/src/GameCult.Mesh/CultMesh.cs @@ -3471,13 +3471,15 @@ namespace GameCult.Mesh CultDocumentRegistry.Shared.GetRequired()); } + // The one schema-alias matcher (docs/cultnet-selection-cut.md, D4): every runtime-side + // schema identity decision, wire or local, goes through CultNetSchemaAliasMatching. private static bool IsSameCultDocumentSchema( Type documentType, CultDocumentDescriptor descriptor, string? schemaId = null) { if (!string.IsNullOrWhiteSpace(schemaId) && - string.Equals(schemaId, descriptor.SchemaId, StringComparison.Ordinal)) + CultNetSchemaAliasMatching.Matches(schemaId, descriptor)) { return true; } @@ -3492,8 +3494,7 @@ namespace GameCult.Mesh return false; } - return string.Equals(storedDescriptor.SchemaName, descriptor.SchemaName, StringComparison.Ordinal) && - string.Equals(storedDescriptor.SchemaVersion, descriptor.SchemaVersion, StringComparison.Ordinal); + return CultNetSchemaAliasMatching.Matches(storedDescriptor.SchemaVersion, descriptor); } private static TDocument ConvertUntypedDocument(object document) diff --git a/src/GameCult.Mesh/CultMeshSnapshots.cs b/src/GameCult.Mesh/CultMeshSnapshots.cs index d40eef03..9db8727f 100644 --- a/src/GameCult.Mesh/CultMeshSnapshots.cs +++ b/src/GameCult.Mesh/CultMeshSnapshots.cs @@ -1002,8 +1002,8 @@ namespace GameCult.Mesh binding != null && typeof(TDocument).IsAssignableFrom(binding.DocumentType); var canDeserializeAsSchemaAlias = - string.Equals(record.SchemaId, descriptor.SchemaId, StringComparison.Ordinal) || - (binding != null && IsSameCultDocumentSchema(binding.DocumentType, descriptor)) || + CultNetSchemaAliasMatching.Matches(record.SchemaId, descriptor) || + (binding != null && CultNetSchemaAliasMatching.Matches(binding.SchemaId, descriptor)) || RawSnapshotPayloadMatchesSchema(record.Payload, descriptor); if (!canDeserializeWithBinding && !canDeserializeAsSchemaAlias) continue; @@ -1030,20 +1030,17 @@ namespace GameCult.Mesh return documents; } + // Decode-time fallback for a wire schemaId this runtime does not recognize by any alias + // (e.g. a foreign/runtime-generated id): trust the payload's own embedded schemaVersion + // instead. This is a decode concern, not a selector engine — the server's answer is not + // re-filtered here, and schema identity itself still goes through the one alias matcher. private static bool RawSnapshotPayloadMatchesSchema( byte[] payload, CultDocumentDescriptor descriptor) { var schemaVersion = TryReadSchemaVersion(payload); - if (string.IsNullOrWhiteSpace(schemaVersion)) - return false; - - if (string.Equals(schemaVersion, descriptor.SchemaVersion, StringComparison.Ordinal)) - return true; - - var schemaName = InferSchemaName(schemaVersion!); - return !string.IsNullOrWhiteSpace(schemaName) && - string.Equals(schemaName, descriptor.SchemaName, StringComparison.Ordinal); + return !string.IsNullOrWhiteSpace(schemaVersion) && + CultNetSchemaAliasMatching.Matches(schemaVersion!, descriptor); } private static string? TryReadSchemaVersion(byte[] payload) @@ -1074,17 +1071,5 @@ namespace GameCult.Mesh return null; } - - private static string? InferSchemaName(string schemaVersion) - { - var marker = schemaVersion.LastIndexOf(".v", StringComparison.Ordinal); - if (marker <= 0 || marker + 2 >= schemaVersion.Length) - return null; - - var version = schemaVersion.Substring(marker + 2); - return version.All(char.IsDigit) - ? schemaVersion.Substring(0, marker) - : null; - } } }