Fix batch 3: R-N wire refusal codes, R-P one authorizer id, R-T removal/cites gaps
R-N: cultnet.error.v0 gains additive code/details fields (contracts/cultnet). CultNetErrorMessage grows ForSelectionInvalid/ForCursor/ForReferenceOutsideTarget factories; both C# servers (CultNetDatabaseServer for snapshot+live, CultNetDatabaseSubscriptionServer) use them at every existing catch site, plus two sites that previously let the exception escape uncaught instead of reaching the peer (CultNetDatabaseServer's v1 subscribe snapshot branch, CultNetDatabaseSubscriptionServer's Subscribe around CreateProjectedSnapshot). CultNetSelectionCursorException now carries AsOf/Current for cursor_stale. R-P: CultNetDatabaseSubscriptionServer.CreateMatchedRecord (the live fast path) authorized against descriptor.SchemaId; the snapshot path (CreateProjectedSnapshot) already authorized against the wire id. Both now call the same CultNetDocumentRegistry.WireSchemaId (made internal, was private) so one id reaches the authorizer on the snapshot, live and reconcile paths alike. R-T(a): CultNetSelectionEvaluator.MatchesSchemaKeysFields no longer reads a Removed change's (always-null) document against Fields - it matches on keys/schemas alone and treats a field predicate as unknown instead of throwing TryGetIndexValue(null). R-T(b): EnsureWithinDeclaredTarget no longer skips the out-of-target check when TargetType is null; it checks against every registered leaf (unconstrained is a real, checked target set, not a bypass). CultCache's ResolveReferenceTarget also now infers the target from a many reference's CultRecordRef<T> element when no explicit [CultReference(typeof(...))] is declared, which was the one remaining source of a null TargetType reaching that check on a real, testable path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
c606eb38f2
commit
66b5e60b43
7 changed files with 191 additions and 18 deletions
|
|
@ -155,7 +155,7 @@ namespace GameCult.Networking
|
|||
}
|
||||
catch (CultNetSelectionInvalidException ex)
|
||||
{
|
||||
peer.SendCultNet(new CultNetErrorMessage { Error = $"selection_invalid: {ex.Message}" });
|
||||
peer.SendCultNet(CultNetErrorMessage.ForSelectionInvalid(ex));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
@ -190,6 +190,20 @@ namespace GameCult.Networking
|
|||
PublishDemand(request, key, active: true);
|
||||
projection.DemandActive = true;
|
||||
}
|
||||
catch (CultNetSelectionInvalidException ex)
|
||||
{
|
||||
// R-N: a hop-bearing selection re-validates inside CreateProjectedSnapshot's
|
||||
// EvaluateAll, so a selection that passed the door check above can still refuse here.
|
||||
Withdraw(key, sendRemovals: false, forgetRequest: true);
|
||||
peer.SendCultNet(CultNetErrorMessage.ForSelectionInvalid(ex));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (CultNetSelectionReferenceOutsideTargetException ex)
|
||||
{
|
||||
Withdraw(key, sendRemovals: false, forgetRequest: true);
|
||||
peer.SendCultNet(CultNetErrorMessage.ForReferenceOutsideTarget(ex));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Withdraw(key, sendRemovals: false, forgetRequest: true);
|
||||
|
|
@ -515,7 +529,13 @@ namespace GameCult.Networking
|
|||
if (!CultNetSelectionEvaluator.Matches(descriptor, key, document, effectiveSelection))
|
||||
return null;
|
||||
var legacyRequest = request.ToLegacyShape(request.SubscriptionId);
|
||||
if (_authorizeRecord?.Invoke(legacyRequest, peer, key.Value, descriptor.SchemaId) == false)
|
||||
// R-P: one id reaches the authorizer, and it is the wire id, on every path. The snapshot
|
||||
// path (CreateProjectedSnapshot, below) authorizes against the raw record's own SchemaId,
|
||||
// which is the wire id ToRawRecord/binding emit; this live fast path used to pass
|
||||
// descriptor.SchemaId instead - the CLR type's own registered id, not the id a binding
|
||||
// overrides to - so a row delivered by the snapshot could be refused on its first live
|
||||
// change, or leak through a denylist keyed on the wire id.
|
||||
if (_authorizeRecord?.Invoke(legacyRequest, peer, key.Value, _database.Documents.WireSchemaId(descriptor)) == false)
|
||||
return null;
|
||||
|
||||
if (kind == CultNetDatabaseChangeKind.Removed || document == null)
|
||||
|
|
|
|||
Loading…
Reference in a new issue