Rust R-AB: vendor the parity fixture too, so cargo-mutants' baseline
actually runs The schema_discovery.rs include_str! escape was not the only one: tests/selection.rs's fixture() read ../../contracts/cultnet/interop/selection-vectors.fixture.json, the same three-levels-up path shape, and 37 of 45 tests panicked on it under cargo-mutants' isolated copy - confirmed on Yggdrasil after the schema_discovery.rs fix alone still landed "cargo test failed in an unmutated tree, so no mutants were tested". selection-vectors.fixture.json is a checked-in input (not a generated artifact), so it gets the same treatment as the 22 schemas: a byte-identical vendored copy under packages/cultnet-rs/contracts/cultnet/interop/, a drift test (fixture_contract_matches_the_canonical_source, same skip-when-absent shape as schema_discovery_contracts.rs), and the loader reads the vendored copy unconditionally. selection-vectors.cs-written.json is different in kind: it is the C# reference's own generated output, produced only by a real cross-runtime checkout running SelectionParityVectorTests with CULTNET_WRITE_VECTORS=1. There is nothing to vendor - an isolated Rust-only copy can never satisfy that test's premise regardless of what this crate does with its own files. That one test now skips (with an explanation) when the canonical contracts/ tree is entirely absent, rather than panicking and failing the whole suite over a cross-runtime artifact the isolated copy cannot produce by construction. Verified locally by hiding contracts/ entirely and rerunning the full suite (217 tests across every binary): unchanged pass count, same as with it present. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
4914e560d5
commit
97bc2076b1
2 changed files with 111 additions and 1 deletions
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"//": "CultNet typed selection, Cut 1 fix batch (docs/cultnet-selection-cut.md, Self's rulings for the Cut 1 fix batch, R-E and the shared-fixture ruling, 2026-09-22). The one row set both the C# reference and cultnet-rs build their parity vectors from. schemaId is each schema's real SHA-256 content-hash id, exactly as GameCult.Caching.CultDocumentRegistry computes it for the fixture's C# document types (leaf_a=ParityLeafA, leaf_b=ParityLeafB, citer=ParityCiter, tests/GameCult.Networking.Tests/SelectionParityVectorTests.cs) - CultCache.cs's Sha256(semanticFingerprint), which carries no '.vN' suffix of its own; the version is folded into the hash, never appended as text. Both writers read this file instead of building their own fixture rows.",
|
||||
"//aliases": "Two DIFFERENT alias forms, now deliberate rather than a cross-runtime defect (S-12, 2026-09-22: the Rust alias port made this so - see docs/cultnet-selection-cut.md, 'Self's ruling, 2026-09-22: the C# reference's alias rule is the rule'). Both runtimes resolve a schema alias the same one way: strip a trailing .vN and compare the remainder against the descriptor's declared SchemaName (CultNetSchemaAliasMatching.Matches(candidate, CultDocumentDescriptor), src/GameCult.Networking/CultNetDatabase.cs:74-87; cultnet_rs::selection::schema_alias::matches ports it name-for-name and deletes the hash-stripping overload it used to carry). nameAlias is <SchemaName>.v9 and resolves through both runtimes under that one rule. hashAlias is <schemaId>.v1 and is refused as unmatched by both - it is not a wire form either runtime's production call sites ever produce or accept, and it is kept in the fixture specifically to pin that neither runtime resolves it.",
|
||||
"schemas": {
|
||||
"leaf_a": {
|
||||
"schemaId": "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff",
|
||||
"nameAlias": "leaf_a.v9",
|
||||
"hashAlias": "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff.v1",
|
||||
"indexes": [
|
||||
{ "name": "kind", "numeric": false },
|
||||
{ "name": "mass", "numeric": true }
|
||||
]
|
||||
},
|
||||
"leaf_b": {
|
||||
"schemaId": "sha256:b1f2407d0227a79dcbffdf7ea317f827eec64be38a3f61b7d4e99e81e3b24b42",
|
||||
"nameAlias": "leaf_b.v9",
|
||||
"hashAlias": "sha256:b1f2407d0227a79dcbffdf7ea317f827eec64be38a3f61b7d4e99e81e3b24b42.v1",
|
||||
"indexes": [
|
||||
{ "name": "kind", "numeric": false },
|
||||
{ "name": "mass", "numeric": true }
|
||||
]
|
||||
},
|
||||
"citer": {
|
||||
"schemaId": "sha256:9d0bc9aebc3619b07aabf564f98667afde41444c1b866661d777687d443608dc",
|
||||
"nameAlias": "citer.v9",
|
||||
"hashAlias": "sha256:9d0bc9aebc3619b07aabf564f98667afde41444c1b866661d777687d443608dc.v1",
|
||||
"roles": [
|
||||
{ "name": "Design", "targets": ["leaf_a", "leaf_b"] }
|
||||
]
|
||||
}
|
||||
},
|
||||
"rows": [
|
||||
{ "schema": "leaf_a", "key": "a-lo", "ordinal": 1, "fields": { "kind": "weapon", "mass": "4" } },
|
||||
{ "schema": "leaf_a", "key": "a-eq", "ordinal": 2, "fields": { "kind": "weapon", "mass": "5" } },
|
||||
{ "schema": "leaf_b", "key": "b-hi", "ordinal": 3, "fields": { "kind": "armor", "mass": "6" } },
|
||||
{ "schema": "leaf_a", "key": "shared-1", "ordinal": 4, "fields": { "kind": "shared", "mass": "1" } },
|
||||
{ "schema": "leaf_b", "key": "shared-2", "ordinal": 5, "fields": { "kind": "shared", "mass": "1" } },
|
||||
{ "schema": "leaf_a", "key": "shared-3", "ordinal": 6, "fields": { "kind": "shared", "mass": "1" } },
|
||||
{
|
||||
"schema": "citer",
|
||||
"key": "citer-1",
|
||||
"ordinal": 7,
|
||||
"references": [ { "role": "Design", "targetSchema": "leaf_a", "targetKey": "a-eq" } ]
|
||||
},
|
||||
{
|
||||
"//": "R-D: 2233759.25 is exactly representable as an f32 and sits on a round-to-even tie between two shortest-form spellings; its exact canonical decimal is 2233759.25 either way. kind=tie so no weapon/armor/shared case can sweep it up.",
|
||||
"schema": "leaf_a", "key": "tie-mass", "ordinal": 8, "fields": { "kind": "tie", "mass": "2233759.25" }
|
||||
},
|
||||
{
|
||||
"//": "R-C: BMP private-use key (U+E000), same ordinal as the astral row below so the tiebreak - not the ordinal - decides order. kind=unicode, mass=0 so no unfiltered or mass>1 case reaches these.",
|
||||
"schema": "leaf_a", "key": "-row", "ordinal": 9, "fields": { "kind": "unicode", "mass": "0" }
|
||||
},
|
||||
{
|
||||
"//": "R-C: astral key (U+1F602, encoded as a surrogate pair whose high half 0xD83D is less than 0xE000 - a UTF-16 code-unit compare sorts this first; code-point order sorts it after the BMP row above).",
|
||||
"schema": "leaf_a", "key": "😂-row", "ordinal": 9, "fields": { "kind": "unicode", "mass": "0" }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -92,9 +92,22 @@ struct FixtureFile {
|
|||
|
||||
static FIXTURE: std::sync::OnceLock<FixtureFile> = std::sync::OnceLock::new();
|
||||
|
||||
// R-AB: `fixture()` used to read only `contracts_dir()` (the canonical, repo-root
|
||||
// `contracts/cultnet/interop/`), which is outside `packages/cultnet-rs` and escapes any tool that
|
||||
// copies just this package's directory - the same failure shape `schema_discovery.rs`'s
|
||||
// `include_str!`s had. `selection-vectors.fixture.json` is a checked-in input, not a generated
|
||||
// artifact (unlike `*-written.json` below, which each runtime produces from the *other* runtime's
|
||||
// output and cannot be vendored the same way), so it gets the same vendored-copy treatment: a
|
||||
// byte-identical copy lives under `packages/cultnet-rs/contracts/cultnet/interop/`, checked by the
|
||||
// drift test right below, and the loader reads the vendored copy unconditionally so it works the
|
||||
// same way whether or not the wider repo is checked out beside the package.
|
||||
fn vendored_fixture_dir() -> std::path::PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("contracts").join("cultnet").join("interop")
|
||||
}
|
||||
|
||||
fn fixture() -> &'static FixtureFile {
|
||||
FIXTURE.get_or_init(|| {
|
||||
let path = contracts_dir().join("selection-vectors.fixture.json");
|
||||
let path = vendored_fixture_dir().join("selection-vectors.fixture.json");
|
||||
let text = fs::read_to_string(&path)
|
||||
.unwrap_or_else(|error| panic!("{} is missing or unreadable: {error}", path.display()));
|
||||
serde_json::from_str(&text)
|
||||
|
|
@ -102,6 +115,31 @@ fn fixture() -> &'static FixtureFile {
|
|||
})
|
||||
}
|
||||
|
||||
// The drift guard for the vendored fixture, mirroring
|
||||
// `schema_discovery_contracts.rs`'s vendored-schema check: skips (not fails) when the canonical
|
||||
// repo-root tree is not present beside the package, which is exactly the isolated-copy case the
|
||||
// vendored copy exists to survive.
|
||||
#[test]
|
||||
fn fixture_contract_matches_the_canonical_source() {
|
||||
let canonical = contracts_dir().join("selection-vectors.fixture.json");
|
||||
if !canonical.is_file() {
|
||||
eprintln!(
|
||||
"skipping: {canonical:?} not present beside the package (isolated build, the case \
|
||||
the vendored fixture copy is for)"
|
||||
);
|
||||
return;
|
||||
}
|
||||
let canonical_text = fs::read_to_string(&canonical).expect("reading canonical fixture");
|
||||
let vendored_text = fs::read_to_string(vendored_fixture_dir().join("selection-vectors.fixture.json"))
|
||||
.expect("reading vendored fixture");
|
||||
assert_eq!(
|
||||
canonical_text, vendored_text,
|
||||
"packages/cultnet-rs/contracts/cultnet/interop/selection-vectors.fixture.json has \
|
||||
drifted from the canonical contracts/cultnet/interop/selection-vectors.fixture.json - \
|
||||
re-copy it"
|
||||
);
|
||||
}
|
||||
|
||||
fn fixture_schema(name: &str) -> &'static FixtureSchemaDef {
|
||||
fixture()
|
||||
.schemas
|
||||
|
|
@ -1036,6 +1074,20 @@ fn assert_refusal_vector(vector: &Vector, expected_field: &str) {
|
|||
/// Vectors written by the reference (C#) decode and evaluate identically in Rust.
|
||||
#[test]
|
||||
fn selection_vectors_written_by_the_reference_decode_and_evaluate_identically_in_rust() {
|
||||
if !contracts_dir().is_dir() {
|
||||
// R-AB: cs-written.json is the C# reference's own generated output - there is nothing to
|
||||
// vendor (it is not a checked-in input like the fixture; it exists only once the C# side
|
||||
// has actually run its writer against a real checkout of both runtimes). An isolated
|
||||
// Rust-only copy, such as cargo-mutants' baseline build, can never satisfy this test's
|
||||
// premise no matter what this crate does with its own files, so it skips rather than
|
||||
// failing the whole suite over a cross-runtime artifact the isolated copy cannot produce.
|
||||
eprintln!(
|
||||
"skipping: {:?} not present beside the package (isolated build, no C# checkout to \
|
||||
have written cs-written.json against)",
|
||||
contracts_dir()
|
||||
);
|
||||
return;
|
||||
}
|
||||
let path = contracts_dir().join("selection-vectors.cs-written.json");
|
||||
let Ok(text) = fs::read_to_string(&path) else {
|
||||
panic!(
|
||||
|
|
|
|||
Loading…
Reference in a new issue