diff --git a/contracts/cultnet/interop/selection-vectors.cs-written.json b/contracts/cultnet/interop/selection-vectors.cs-written.json index 037f1fe8..8c58a174 100644 --- a/contracts/cultnet/interop/selection-vectors.cs-written.json +++ b/contracts/cultnet/interop/selection-vectors.cs-written.json @@ -20,11 +20,11 @@ "expectedRefusalField": null, "expectedEvaluationRefusal": null, "expectedIds": [ + "sha256:9d0bc9aebc3619b07aabf564f98667afde41444c1b866661d777687d443608dc/citer-both-in-target", "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/aa-tie-schema", - "sha256:b1f2407d0227a79dcbffdf7ea317f827eec64be38a3f61b7d4e99e81e3b24b42/zz-tie-schema", - "sha256:ee192f6526a8f8cdec7044551aa2c8e28c612426766bb270605023bc94e2614d/narrow-citer-accepted" + "sha256:b1f2407d0227a79dcbffdf7ea317f827eec64be38a3f61b7d4e99e81e3b24b42/zz-tie-schema" ], - "matched": 17, + "matched": 18, "hasNext": true, "expectedEdges": [] }, @@ -51,9 +51,10 @@ "expectedRefusalField": null, "expectedEvaluationRefusal": null, "expectedIds": [ - "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/a-eq" + "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/a-eq", + "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/dup-key" ], - "matched": 1, + "matched": 2, "hasNext": false, "expectedEdges": [ { @@ -61,6 +62,12 @@ "role": "Design", "toId": "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/a-eq", "payloadBase64": null + }, + { + "fromId": "sha256:9d0bc9aebc3619b07aabf564f98667afde41444c1b866661d777687d443608dc/citer-both-in-target", + "role": "Design", + "toId": "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/dup-key", + "payloadBase64": null } ] }, @@ -77,10 +84,9 @@ "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/tie-mass", "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/-row", "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/\uD83D\uDE02-row", - "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/dup-key", "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/aa-tie-schema" ], - "matched": 8, + "matched": 7, "hasNext": false, "expectedEdges": [] }, @@ -114,7 +120,7 @@ }, { "name": "hop_by_role_cites_exact_id", - "selectionMessagePackBase64": "iadzY2hlbWFzwKRrZXlzwKZmaWVsZHPApWNpdGVzgqZ0YXJnZXSCqHNjaGVtYUlk2UdzaGEyNTY6ZmYzNWYzZDlkNmRlM2YxMDY2YjkzNjllNzNmZTZjNzY0OGU4ODM2OGQxOGVmYzIyNzVjOTk0NzMwY2Q4NGNmZqlyZWNvcmRLZXmkYS1lcaRyb2xlpkRlc2lnbqVjaXRlZMCqcHJvamVjdGlvbqZoZWFkZXKqZGVzY2VuZGluZ8KlbGltaXTApmN1cnNvcsA=", + "selectionMessagePackBase64": "iadzY2hlbWFzwKRrZXlzkadjaXRlci0xpmZpZWxkc8ClY2l0ZXOCpnRhcmdldIKoc2NoZW1hSWTZR3NoYTI1NjpmZjM1ZjNkOWQ2ZGUzZjEwNjZiOTM2OWU3M2ZlNmM3NjQ4ZTg4MzY4ZDE4ZWZjMjI3NWM5OTQ3MzBjZDg0Y2ZmqXJlY29yZEtleaRhLWVxpHJvbGWmRGVzaWdupWNpdGVkwKpwcm9qZWN0aW9upmhlYWRlcqpkZXNjZW5kaW5nwqVsaW1pdMCmY3Vyc29ywA==", "asOf": 1, "expectedRefusalField": null, "expectedEvaluationRefusal": null, @@ -134,7 +140,7 @@ }, { "name": "cites_target_by_name_alias", - "selectionMessagePackBase64": "iadzY2hlbWFzwKRrZXlzwKZmaWVsZHPApWNpdGVzgqZ0YXJnZXSCqHNjaGVtYUlkqWxlYWZfYS52OalyZWNvcmRLZXmkYS1lcaRyb2xlpkRlc2lnbqVjaXRlZMCqcHJvamVjdGlvbqZoZWFkZXKqZGVzY2VuZGluZ8KlbGltaXTApmN1cnNvcsA=", + "selectionMessagePackBase64": "iadzY2hlbWFzwKRrZXlzkadjaXRlci0xpmZpZWxkc8ClY2l0ZXOCpnRhcmdldIKoc2NoZW1hSWSpbGVhZl9hLnY5qXJlY29yZEtleaRhLWVxpHJvbGWmRGVzaWdupWNpdGVkwKpwcm9qZWN0aW9upmhlYWRlcqpkZXNjZW5kaW5nwqVsaW1pdMCmY3Vyc29ywA==", "asOf": 1, "expectedRefusalField": null, "expectedEvaluationRefusal": null, @@ -249,6 +255,26 @@ } ] }, + { + "name": "shared_key_design_resolves_the_first_in_target_candidate_in_row_order", + "selectionMessagePackBase64": "iadzY2hlbWFzwKRrZXlzkbRjaXRlci1ib3RoLWluLXRhcmdldKZmaWVsZHPApWNpdGVzgqZ0YXJnZXSCqHNjaGVtYUlk2UdzaGEyNTY6ZmYzNWYzZDlkNmRlM2YxMDY2YjkzNjllNzNmZTZjNzY0OGU4ODM2OGQxOGVmYzIyNzVjOTk0NzMwY2Q4NGNmZqlyZWNvcmRLZXmnZHVwLWtleaRyb2xlpkRlc2lnbqVjaXRlZMCqcHJvamVjdGlvbqZoZWFkZXKqZGVzY2VuZGluZ8KlbGltaXTApmN1cnNvcsA=", + "asOf": 1, + "expectedRefusalField": null, + "expectedEvaluationRefusal": null, + "expectedIds": [ + "sha256:9d0bc9aebc3619b07aabf564f98667afde41444c1b866661d777687d443608dc/citer-both-in-target" + ], + "matched": 1, + "hasNext": false, + "expectedEdges": [ + { + "fromId": "sha256:9d0bc9aebc3619b07aabf564f98667afde41444c1b866661d777687d443608dc/citer-both-in-target", + "role": "Design", + "toId": "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/dup-key", + "payloadBase64": null + } + ] + }, { "name": "refuses_empty_schemas_list", "selectionMessagePackBase64": "iadzY2hlbWFzkKRrZXlzwKZmaWVsZHPApWNpdGVzwKVjaXRlZMCqcHJvamVjdGlvbqZoZWFkZXKqZGVzY2VuZGluZ8KlbGltaXTApmN1cnNvcsA=", diff --git a/contracts/cultnet/interop/selection-vectors.fixture.json b/contracts/cultnet/interop/selection-vectors.fixture.json index 1ba62a9e..7e94fd7b 100644 --- a/contracts/cultnet/interop/selection-vectors.fixture.json +++ b/contracts/cultnet/interop/selection-vectors.fixture.json @@ -93,6 +93,13 @@ }, { "schema": "leaf_a", "key": "aa-tie-schema", "ordinal": 15, "fields": { "kind": "tie-schema", "mass": "1" } + }, + { + "//": "R-AO, 2026-09-23: 'dup-key' already carries two rows (leaf_a ordinal 11, leaf_b ordinal 12) - both are in citer's Design role's declared target ([leaf_a, leaf_b], unlike citer_narrow's narrow_ref above, which only ever had one in-target candidate at a shared key). This citer is the first row in this file (hence in both runtimes' row order) to name 'dup-key' through Design, so resolving it must deterministically pick the leaf_a row - the first in-target candidate in the caller's order - never the leaf_b row, in both runtimes.", + "schema": "citer", + "key": "citer-both-in-target", + "ordinal": 16, + "references": [ { "role": "Design", "targetKey": "dup-key" } ] } ] } diff --git a/contracts/cultnet/interop/selection-vectors.rs-written.json b/contracts/cultnet/interop/selection-vectors.rs-written.json index 36fbab66..a9b635c4 100644 --- a/contracts/cultnet/interop/selection-vectors.rs-written.json +++ b/contracts/cultnet/interop/selection-vectors.rs-written.json @@ -16,11 +16,11 @@ "selectionMessagePackBase64": "g6pwcm9qZWN0aW9upmhlYWRlcqpkZXNjZW5kaW5nw6VsaW1pdAM=", "asOf": 1, "expectedIds": [ + "sha256:9d0bc9aebc3619b07aabf564f98667afde41444c1b866661d777687d443608dc/citer-both-in-target", "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/aa-tie-schema", - "sha256:b1f2407d0227a79dcbffdf7ea317f827eec64be38a3f61b7d4e99e81e3b24b42/zz-tie-schema", - "sha256:ee192f6526a8f8cdec7044551aa2c8e28c612426766bb270605023bc94e2614d/narrow-citer-accepted" + "sha256:b1f2407d0227a79dcbffdf7ea317f827eec64be38a3f61b7d4e99e81e3b24b42/zz-tie-schema" ], - "matched": 17, + "matched": 18, "hasNext": true, "expectedEdges": [] }, @@ -43,9 +43,10 @@ "selectionMessagePackBase64": "hKdzY2hlbWFzkdlHc2hhMjU2OmZmMzVmM2Q5ZDZkZTNmMTA2NmI5MzY5ZTczZmU2Yzc2NDhlODgzNjhkMThlZmMyMjc1Yzk5NDczMGNkODRjZmalY2l0ZWSCpHJvbGWmRGVzaWdupmV4aXN0c8OqcHJvamVjdGlvbqZoZWFkZXKqZGVzY2VuZGluZ8I=", "asOf": 1, "expectedIds": [ - "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/a-eq" + "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/a-eq", + "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/dup-key" ], - "matched": 1, + "matched": 2, "hasNext": false, "expectedEdges": [ { @@ -53,6 +54,12 @@ "role": "Design", "toId": "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/a-eq", "payloadBase64": null + }, + { + "fromId": "sha256:9d0bc9aebc3619b07aabf564f98667afde41444c1b866661d777687d443608dc/citer-both-in-target", + "role": "Design", + "toId": "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/dup-key", + "payloadBase64": null } ] }, @@ -67,10 +74,9 @@ "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/tie-mass", "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/-row", "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/😂-row", - "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/dup-key", "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/aa-tie-schema" ], - "matched": 8, + "matched": 7, "hasNext": false, "expectedEdges": [] }, @@ -204,6 +210,24 @@ } ] }, + { + "name": "shared_key_design_resolves_the_first_in_target_candidate_in_row_order", + "selectionMessagePackBase64": "hKRrZXlzkbRjaXRlci1ib3RoLWluLXRhcmdldKVjaXRlc4KmdGFyZ2V0gqhzY2hlbWFJZNlHc2hhMjU2OmZmMzVmM2Q5ZDZkZTNmMTA2NmI5MzY5ZTczZmU2Yzc2NDhlODgzNjhkMThlZmMyMjc1Yzk5NDczMGNkODRjZmapcmVjb3JkS2V5p2R1cC1rZXmkcm9sZaZEZXNpZ26qcHJvamVjdGlvbqZoZWFkZXKqZGVzY2VuZGluZ8I=", + "asOf": 1, + "expectedIds": [ + "sha256:9d0bc9aebc3619b07aabf564f98667afde41444c1b866661d777687d443608dc/citer-both-in-target" + ], + "matched": 1, + "hasNext": false, + "expectedEdges": [ + { + "fromId": "sha256:9d0bc9aebc3619b07aabf564f98667afde41444c1b866661d777687d443608dc/citer-both-in-target", + "role": "Design", + "toId": "sha256:ff35f3d9d6de3f1066b9369e73fe6c7648e88368d18efc2275c994730cd84cff/dup-key", + "payloadBase64": null + } + ] + }, { "name": "refuses_empty_schemas_list", "selectionMessagePackBase64": "g6dzY2hlbWFzkKpwcm9qZWN0aW9upmhlYWRlcqpkZXNjZW5kaW5nwg==", diff --git a/packages/cultnet-rs/contracts/cultnet/interop/selection-vectors.fixture.json b/packages/cultnet-rs/contracts/cultnet/interop/selection-vectors.fixture.json index 1ba62a9e..7e94fd7b 100644 --- a/packages/cultnet-rs/contracts/cultnet/interop/selection-vectors.fixture.json +++ b/packages/cultnet-rs/contracts/cultnet/interop/selection-vectors.fixture.json @@ -93,6 +93,13 @@ }, { "schema": "leaf_a", "key": "aa-tie-schema", "ordinal": 15, "fields": { "kind": "tie-schema", "mass": "1" } + }, + { + "//": "R-AO, 2026-09-23: 'dup-key' already carries two rows (leaf_a ordinal 11, leaf_b ordinal 12) - both are in citer's Design role's declared target ([leaf_a, leaf_b], unlike citer_narrow's narrow_ref above, which only ever had one in-target candidate at a shared key). This citer is the first row in this file (hence in both runtimes' row order) to name 'dup-key' through Design, so resolving it must deterministically pick the leaf_a row - the first in-target candidate in the caller's order - never the leaf_b row, in both runtimes.", + "schema": "citer", + "key": "citer-both-in-target", + "ordinal": 16, + "references": [ { "role": "Design", "targetKey": "dup-key" } ] } ] } diff --git a/tests/GameCult.Networking.Tests/SelectionParityVectorTests.cs b/tests/GameCult.Networking.Tests/SelectionParityVectorTests.cs index b8d1c233..e9a070fc 100644 --- a/tests/GameCult.Networking.Tests/SelectionParityVectorTests.cs +++ b/tests/GameCult.Networking.Tests/SelectionParityVectorTests.cs @@ -277,6 +277,16 @@ namespace GameCult.Networking.Tests Keys = new[] { "narrow-citer-accepted" }, Cites = new CultNetCitation { Target = new CultNetRecordRef { SchemaId = schemas["leaf_a"].SchemaId, RecordKey = "dup-key" }, Role = "narrow_ref" } }); + // R-AO: "dup-key" has two in-target candidates for citer's Design role (unlike + // citer_narrow's narrow_ref above, which only ever had one). citer-both-in-target is the + // first row in the fixture file (and so in both runtimes' row order) to name it through + // Design - resolution must deterministically pick the first in-target candidate in that + // order, the leaf_a row (ordinal 11), never the leaf_b row (ordinal 12) sharing the key. + yield return ("shared_key_design_resolves_the_first_in_target_candidate_in_row_order", new CultNetSelection + { + Keys = new[] { "citer-both-in-target" }, + Cites = new CultNetCitation { Target = new CultNetRecordRef { SchemaId = schemas["leaf_a"].SchemaId, RecordKey = "dup-key" }, Role = "Design" } + }); } // R-AF: the shared-key shape's other direction - the only row at the declared edge's key is