diff --git a/packages/cultnet-rs/README.md b/packages/cultnet-rs/README.md index 3c7488ba..48ac2dd3 100644 --- a/packages/cultnet-rs/README.md +++ b/packages/cultnet-rs/README.md @@ -11,8 +11,9 @@ The contract is intentionally boring: - `gamecult.networking.v0` maps the legacy C# union shape explicitly. - schema discovery uses explicit catalog request/response messages; no inbound auto-detect sludge. -- put/delete/snapshot travel only as raw messages that carry the canonical - MessagePack payload bytes; the receiver decodes them through registered types. +- put and snapshot travel only as raw messages that carry the canonical + MessagePack payload bytes; the receiver decodes them through registered + types. Delete carries ids. - hello messages advertise `supportedMutationContracts`, so callers discover which document types are read-only, which accept typed intents, which authority owns the mutation, and which receipt documents prove the outcome. diff --git a/packages/cultnet-rs/src/contracts.rs b/packages/cultnet-rs/src/contracts.rs index bb1b1e57..e4f67164 100644 --- a/packages/cultnet-rs/src/contracts.rs +++ b/packages/cultnet-rs/src/contracts.rs @@ -1658,7 +1658,7 @@ fn require_legacy_optional_schema_kind_array( "wire_message" => Ok(CultNetSchemaKind::WireMessage), "document_payload" => Ok(CultNetSchemaKind::DocumentPayload), "shared_contract" => Ok(CultNetSchemaKind::SharedContract), - _ => Err(anyhow!("{field_name} has unsupported schema kind {value}")), + _ => Err(anyhow!("{field_name} has an unsupported schema kind")), }) .collect::>>() .map(Some) diff --git a/packages/cultnet-rs/tests/decode_door.rs b/packages/cultnet-rs/tests/decode_door.rs index 8a25ef02..fd5bab6a 100644 --- a/packages/cultnet-rs/tests/decode_door.rs +++ b/packages/cultnet-rs/tests/decode_door.rs @@ -675,6 +675,26 @@ fn a_networking_schema_descriptor_with_a_repeated_key_is_refused_and_a_bad_kind_ assert!(text.contains("Kind") && !text.contains("CANARY"), "{text}"); } +#[test] +fn a_networking_catalog_request_with_a_bad_kind_element_is_refused_without_quoting_it() { + let union = rmpv::Value::Array(vec![ + 7.into(), + rmpv::Value::Array(vec![ + "m".into(), + false.into(), + rmpv::Value::Nil, + rmpv::Value::Array(vec!["CANARY-s2x".into()]), + ]), + ]); + let bytes = rmp_serde::to_vec(&union).unwrap(); + let text = on_stack(BIG, move || { + decode_cultnet_message_from_slice(&bytes, CultNetWireContract::GameCultNetworkingV0) + .map_err(|error| format!("{error:#}")) + }) + .unwrap_err(); + assert!(text.contains("Kinds") && !text.contains("CANARY"), "{text}"); +} + #[test] fn a_schema_version_that_is_not_shaped_like_a_schema_name_is_described_by_length() { let long = format!("cultnet.{}.v0", "a".repeat(100));