Soul found commit 2 didn't pass (docs/cultnet-selection-cut.md, 2026-09-22):
empty/blank schemas-keys still reached the evaluator, the shard snapshot ran
its own loop instead of the evaluator, Mesh's v0 wire silently dropped v1-only
selection terms, the Mesh one-pass read could return the wrong schema's
record at a shared key, two decode rules for a foreign schema id disagreed,
dead identity-comparison code from the _projectRecord removal was still live,
and a disposed-socket race could abort a mutation run instead of recording
NO VERDICT. This is Self's fix batch for all of it.
- CultNetSelection.Validate now refuses a blank/whitespace entry inside
schemas or keys, not just an empty array. CultNetSelectionEvaluator no
longer treats [] as "no filter" (the door already refuses it before
evaluation). CultNetV0SelectionLowering is the one place that lowers an
empty or all-blank v0 list to null; CreateRawSnapshotResponse and
CreateShardSnapshotResponse both route through it, and CultMesh's own
Clean() now delegates to it instead of duplicating the rule.
- CreateShardSnapshotResponse no longer carries a private loop or its own
schema/key matching: it builds a CultNetSelection and answers through
CultNetDocumentRegistry.SelectPage (the same evaluator path as the
non-shard snapshot), with the shard's own membership check as a row
filter. Binding-alias expansion now runs on both paths identically.
- ResolveDefaultSelection decides "no schema filter" from the cleaned
recordKeys, not the raw array length. OverlaySelection stops deciding what
a missing override means: any explicit schemaIds/recordKeys override
replaces the default selection wholesale; no override keeps the default
untouched.
- CultMeshSnapshotRequestOptions.Selection is now checked by
EnsureV0Compatible before every send: fields, cites, cited, limit, cursor,
descending, and any non-default projection are refused typed
(CultNetSelectionInvalidException), never silently dropped from the v0
wire message. Mesh sending v1 is FU-Mesh-v1, not this cut.
- CultMeshHotBodySubscription.Selection returns a defensive copy on every
read so its arrays can't be mutated out from under the stored selection.
- ReadDocumentFromSnapshotResponse now searches candidates at one recordKey
in one order - exact schema, then alias, then a foreign schema id decoded
from the payload - instead of one OR'd condition where a case-insensitive
or structurally-lucky candidate could win first. The foreign-id decode
reads the payload's embedded schema version with the registry's shared
CultNetDocumentRegistry.TryReadSchemaVersion and alias-matches it against
the caller's own descriptor directly, rather than resolving to "whichever
registered type owns this schema string" - that resolution is ambiguous
when two document types alias the same schema id (MeshNoteDocument and
MeshNoteAliasDocument in the existing fixtures), which is what broke on
the first pass; TryResolveDescriptorByPayloadSchema stays for its existing
caller (ResolveDescriptorForRawDocument) with a note on when not to use it.
DecodeSnapshotDocuments and the Mesh copy of the payload-schema decode
(RawSnapshotPayloadMatchesSchema/TryReadSchemaVersion) are deleted in
favour of the shared one.
- CultNetDatabaseSubscriptionServer: the dead RecordKey/SchemaId identity
comparisons in Reconcile and ApplyProjectedChange, and the duplicate-key
throw in CreateProjectedSnapshot, are deleted - they can no longer fire
now that a projection's dictionary key is always the record's own key.
A new test flips authorizeRecord per key while the whole request stays
authorized (so DemandActive never toggles) and proves both halves of the
remaining diff: the removal of a now-unauthorized key and the add loop
picking up a newly-authorized one.
- Every NetworkingTests.cs test that starts an IsBackground poll thread now
joins it before its server disposes (about 20 tests; one already did).
- scripts/mutate-dotnet.ps1: a dotnet-test-host crash writing to stderr no
longer throws the whole run away under PowerShell 5.1's
$ErrorActionPreference = 'Stop' (2>&1 on a native command wraps stderr in
a terminating NativeCommandError); Run-Tests catches it and the entry loop
records NO VERDICT instead of aborting every remaining entry.
- Two probe tests: CultNetDatabase_ShardAndNonShardSnapshot_Agree{On
UnfilteredRequest,OnBindingSchemaIdOverride} prove the shard and non-shard
snapshot paths agree, including shard-membership exclusion of a document
outside the shard's key prefix and a binding schema-id override matching
on both paths.
- Mutation entries updated/added to match: the stale NET-D4-FourthMatcher
entry (its anchor was the private loop this batch deleted, not refactored)
is replaced by NET-D4-ShardMembership-Revert; the Mesh entries file's
header miscount ("two of three" loosening entries when there are only
two) is corrected and both previously-surviving loosening mutants
(MESH-DEFAULT-Loosening, MESH-KEY-Loosening) now have killing fixtures;
scripts/mutate-cultnet-selection-fixbatch2.entries.ps1 is new, covering
Reconcile's remaining two mutants.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
33 lines
2.2 KiB
PowerShell
33 lines
2.2 KiB
PowerShell
# Entries for scripts/mutate-dotnet.ps1: CultNet typed selection, Cut 1, commit 2 fix batch
|
|
# (docs/cultnet-selection-cut.md, Self's rulings 2026-09-22). Targets the dead-identity-code deletion
|
|
# in src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs (S2-2): Reconcile must still walk
|
|
# its diff when the request stays authorized (not gated on DemandActive changing), and its add loop
|
|
# must still fire. Test project: tests/GameCult.Networking.Tests.
|
|
|
|
@(
|
|
@{
|
|
Id = 'NET-RECONCILE-EarlyReturn-Loosening'
|
|
Rule = 'Reconcile walks the delivered/added diff whenever it runs; it must not early-return merely because request-level authorization (and so DemandActive) has not changed - per-record authorization can still have moved.'
|
|
Mutant = 'loosening'
|
|
File = 'src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs'
|
|
Killer = 'FullyQualifiedName=GameCult.Networking.Tests.NetworkingTests.DatabaseSubscriptionServer_Reconcile_FlipsPerRecordAuthorizationWithoutChangingRequestAuthority'
|
|
Old = 'if (!_projections.TryGetValue(key, out var projection)) return;
|
|
var authorized = _authorizeRequest?.Invoke(request, key.Peer) != false;'
|
|
New = 'if (!_projections.TryGetValue(key, out var projection)) return;
|
|
var authorized = _authorizeRequest?.Invoke(request, key.Peer) != false;
|
|
if (projection.DemandActive == authorized) return;'
|
|
},
|
|
@{
|
|
Id = 'NET-RECONCILE-AddLoop-Revert'
|
|
Rule = 'Reconcile''s add loop sends an upsert (added: true) for every key in the new snapshot that was not already delivered.'
|
|
Mutant = 'revert'
|
|
File = 'src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs'
|
|
Killer = 'FullyQualifiedName=GameCult.Networking.Tests.NetworkingTests.DatabaseSubscriptionServer_Reconcile_FlipsPerRecordAuthorizationWithoutChangingRequestAuthority'
|
|
Old = 'foreach (var current in next.BySourceRecordKey)
|
|
{
|
|
if (!projection.DeliveredBySourceRecordKey.ContainsKey(current.Key))
|
|
SendUpsert(key.Peer, key.Id, current.Value, added: true);
|
|
}'
|
|
New = ''
|
|
}
|
|
)
|