Add the two pending cites-refusal parity vectors, now that C# refuses them

Item 1 of the fix-batch-3 brief: check whether CultNetSelection.Validate
refuses a cites.target.schemaId that CultNetSchemaAliasMatching cannot
resolve. It already does (CultNetSelection.cs:389-394), landed by an earlier
Hands pass on this branch (the "C# matrix Hands", after the parity table in
the spec was captured) - so no production code changed here.

What was still missing: SelectionParityVectorTests.RefusalCases() had a
comment explaining why refuses_unmatched_cites_target_schema stayed
"Rust-only", dated before that door check landed. The comment was stale and
the vector was never added. Added it, plus
refuses_cites_target_by_hash_shaped_alias (Self's ruling 2026-09-22: "the C#
reference's alias rule is the rule" - C# only resolves a name-form alias, so
a hash-shaped cites.target.schemaId is exactly the same unresolved-target
case and must refuse at the same field). RefusalCases() now takes the
fixture's schemas dictionary to reach schemas["leaf_a"].HashAlias.

Verified on Yggdrasil: dotnet test tests/GameCult.Networking.Tests -c Release
--filter FullyQualifiedName~SelectionParityVectorTests (the existing
cross-runtime reader against the committed rs-written.json still passes, so
this change does not depend on anything the Rust Hands owns). Regenerated
contracts/cultnet/interop/selection-vectors.cs-written.json with
CULTNET_WRITE_VECTORS=1: both new refusal vectors are present and both
refuse at "cites.target.schemaId" as expected before being committed as
vectors (WriteSelectionVectors's own Assert.Throws gate).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-22 19:22:18 +02:00
commit b1932d95ed

View file

@ -231,17 +231,26 @@ namespace GameCult.Networking.Tests
// either side - only decoded and handed to Validate/validate, which must refuse at exactly
// the named field. Mirrors packages/cultnet-rs/tests/selection.rs's refusal_cases.
//
// Not mirrored here: cultnet-rs's refuses_unmatched_cites_target_schema. R-E says an
// unresolved cites.target.schemaId "is refused at the door, never answered with an empty
// page", but CultNetSelection.Validate (CultNetSelection.cs:361-391) never checks that
// cites.target.schemaId resolves to any known schema at all - only that it is present. Adding
// that vector here would make this file's own Assert.Throws fail before a vector could ever be
// written, so it stays Rust-only; the report below covers the resulting cross-runtime failure
// when this file's reader judges it.
private static IEnumerable<(string Name, CultNetSelection Selection, string ExpectedField)> RefusalCases()
// refuses_unmatched_cites_target_schema and refuses_cites_target_by_hash_shaped_alias now
// land here too: the C# matrix Hands pass added the door check
// (CultNetSelection.cs:389-394) that CultNetSelection.Validate refuses a cites.target.schemaId
// that CultNetSchemaAliasMatching cannot resolve to any declared schema. Under Self's ruling
// 2026-09-22 ("the C# reference's alias rule is the rule"), C# only ever resolves a name-form
// alias, never a hash-shaped one, so a hash-shaped cites.target.schemaId is exactly such an
// unresolved target and is refused at the same field.
private static IEnumerable<(string Name, CultNetSelection Selection, string ExpectedField)> RefusalCases(
IReadOnlyDictionary<string, FixtureSchema> schemas)
{
yield return ("refuses_empty_schemas_list", new CultNetSelection { Schemas = Array.Empty<string>() }, "schemas");
yield return ("refuses_blank_key_entry", new CultNetSelection { Keys = new[] { "a-eq", " " } }, "keys");
yield return ("refuses_unmatched_cites_target_schema", new CultNetSelection
{
Cites = new CultNetCitation { Target = new CultNetRecordRef { SchemaId = "sha256:not-a-declared-schema", RecordKey = "a-eq" } }
}, "cites.target.schemaId");
yield return ("refuses_cites_target_by_hash_shaped_alias", new CultNetSelection
{
Cites = new CultNetCitation { Target = new CultNetRecordRef { SchemaId = schemas["leaf_a"].HashAlias, RecordKey = "a-eq" } }
}, "cites.target.schemaId");
}
// R-E: id, not name - a row's cross-runtime identity is its real schema id plus its key, the
@ -387,7 +396,7 @@ namespace GameCult.Networking.Tests
// Self's ruling, 2026-09-22 (commit 2 fix batch): door-refusal vectors. Never
// evaluated on either side - only decoded and handed to Validate, which must refuse
// at exactly the named field.
foreach (var (name, selection, expectedField) in RefusalCases())
foreach (var (name, selection, expectedField) in RefusalCases(schemas))
{
var error = Assert.Throws<CultNetSelectionInvalidException>(
() => selection.Validate(registry.AllDescriptors.ToArray()),