From 0308167c3e9df663e2ca619c95f97b5d73dd6cc4 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 16 Jun 2026 16:26:04 +0100 Subject: [PATCH] Add Rust CultMesh catalog factories --- docs/cultnet-transport-parity.md | 11 ++--- docs/runtime-parity-scope.md | 2 +- packages/cultnet-rs/README.md | 22 ++++++---- packages/cultnet-rs/src/cultmesh.rs | 15 +++++++ packages/cultnet-rs/tests/cultnet.rs | 61 ++++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 13 deletions(-) diff --git a/docs/cultnet-transport-parity.md b/docs/cultnet-transport-parity.md index e069caac..ea567c99 100644 --- a/docs/cultnet-transport-parity.md +++ b/docs/cultnet-transport-parity.md @@ -362,11 +362,12 @@ Current progress: Android clients can react to lease changes without treating peer-card contact hints as trust. - Rust now has a small `CultMesh` facade for its substrate role: peer cards, - sorted peer catalogs, authority lease catalogs, `rudp://host:port` endpoint - parsing, and RUDP client/server construction from endpoint or peer-card - contact hints. The facade delegates to the existing Rust RUDP socket - transport; discovery contact remains separate from trust, which is still - gated by the authority lease catalog. + sorted peer catalogs, authority lease catalogs, schema registry factories, + shard catalog factories, `rudp://host:port` endpoint parsing, and RUDP + client/server construction from endpoint or peer-card contact hints. The + facade delegates to the existing Rust schema registry, shard catalog, and RUDP + socket transport owners; discovery contact remains separate from trust, which + is still gated by the authority lease catalog. - Rust now also has substrate-shaped shard catalog helpers: `CultNetShardDescriptor`, `CultNetShardCatalog`, request filtering by schema id and record key, response creation, response application, and built-in diff --git a/docs/runtime-parity-scope.md b/docs/runtime-parity-scope.md index 296809ab..221cfe09 100644 --- a/docs/runtime-parity-scope.md +++ b/docs/runtime-parity-scope.md @@ -33,7 +33,7 @@ CultMesh. | C# | Reference implementation and production game/server substrate | CultCache typed documents and SoA, CultNet schema-v0, LiteNetLib production path with adapter profile, shared transport profiles, shared RUDP socket transport, RUDP reconnect policy/controller and reconnect-loop helper, LiteNetLib client reconnect-controller adoption, schema-client transport port for service request/response clients, CultMesh node/session/catalog/stream/simulation surfaces | No need to abandon LiteNetLib before the CultNet adapter fully owns production service adoption | | Python | Package runtime, local daemon endpoint, raw interop peer, typed state participant | CultCache/CultNet/CultMesh package surface, schema catalogs, shard catalogs/logs, schema-transport factory for raw client request/subscription flows, local TCP CultMesh server, session/prediction/simulation fact helpers, authority leases, streams, RUDP codec/session/socket transport, RUDP reconnect policy/controller and reconnect-loop helper, schema-v0 MessagePack over RUDP evidence, benchmark evidence against C# cache paths | Universal C# throughput, full C# daemon/server stack, and RUDP as the only public service pipe | | TypeScript | Node/browser-adjacent projection and interop runtime | CultCache parity tests, CultNet schema-v0 peer, advertised schema/shard catalog wire contracts plus request/sync helpers, transport profiles, TCP-framed transport port and peer helper, shared RUDP socket transport, RUDP reconnect policy/controller, RUDP reconnect-loop helper, branded CultMesh node/schema/shard/Verse/peer/authority/stream ergonomics | Full C# game session/server stack | -| Rust | Low-level cache/transport/runtime substrate | CultCache/CultNet interop lane, schema and shard catalog helpers, transport profiles, TCP-framed port, shared RUDP codec/session/socket transport, RUDP reconnect policy/controller and reconnect-loop helper, schema-v0 MessagePack over RUDP evidence, small CultMesh facade for peer cards, authority leases, RUDP endpoint parsing, and peer-card RUDP client construction | Full CultMesh game-session/server facade, stream catalog, or production game-session server | +| Rust | Low-level cache/transport/runtime substrate | CultCache/CultNet interop lane, schema and shard catalog helpers, transport profiles, TCP-framed port, shared RUDP codec/session/socket transport, RUDP reconnect policy/controller and reconnect-loop helper, schema-v0 MessagePack over RUDP evidence, small CultMesh facade for schema registry, shard catalog, peer cards, authority leases, RUDP endpoint parsing, and peer-card RUDP client construction | Full CultMesh game-session/server facade, stream catalog, or production game-session server | | Kotlin | JVM/Android client substrate | Typed MessagePack cache, built-in schema catalog advertisement, schema/shard catalogs with shared schema-message transport fetch/sync helpers, authority leases, stream catalog, branded `CultMesh` facade, WebSocket transport profile/connection helper, RUDP client/server sugar, RUDP reconnect policy/controller and reconnect-loop helper, schema-v0 MessagePack over RUDP evidence | C# SoA store, full C# server daemon, or Unity hot-loop memory layout | ## Evidence Gates diff --git a/packages/cultnet-rs/README.md b/packages/cultnet-rs/README.md index ae86911e..c3ef8b43 100644 --- a/packages/cultnet-rs/README.md +++ b/packages/cultnet-rs/README.md @@ -56,10 +56,11 @@ surface: - shard catalog request/response messages - the canonical `ghostlight.agent-state` document payload schema -Use `builtin_schema_registry()` when you want the standard catalog, or register -your own closed-world schema set with `CultNetSchemaRegistry`. Discovery stays -explicit on purpose: peers advertise only the contracts they were compiled to -understand, the same way CultCache only consumes the document types you +Use `builtin_schema_registry()` or `CultMesh::create_builtin_schema_registry()` +when you want the standard catalog, or register your own closed-world schema set +with `CultNetSchemaRegistry` / `CultMesh::create_schema_registry()`. Discovery +stays explicit on purpose: peers advertise only the contracts they were compiled +to understand, the same way CultCache only consumes the document types you registered instead of pretending polymorphism is a public park. ## Shard Catalogs @@ -68,9 +69,9 @@ Rust now exposes the same shard topology vocabulary as the other runtimes without claiming to be the full C# service body: ```rust -use cultnet_rs::{CultNetMessage, CultNetShardCatalog, CultNetShardDescriptor}; +use cultnet_rs::{CultMesh, CultNetMessage, CultNetShardDescriptor}; -let mut catalog = CultNetShardCatalog::new(); +let mut catalog = CultMesh::create_shard_catalog(); catalog.upsert(CultNetShardDescriptor { shard_id: "notes-a".to_string(), owner_runtime_id: "rust-primary".to_string(), @@ -123,9 +124,16 @@ contact and authority ergonomics: ```rust use cultnet_rs::{ - CultMesh, CultMeshPeerCard, CultMeshRudpSocketOptions, + CultMesh, CultMeshPeerCard, CultMeshRudpSocketOptions, CultNetSchemaKind, }; +let schemas = CultMesh::create_builtin_schema_registry()?; +let shared_contracts = schemas.list(&cultnet_rs::CultNetSchemaCatalogOptions { + include_schema_json: false, + schema_ids: None, + kinds: Some(vec![CultNetSchemaKind::SharedContract]), +}); + let mut server = CultMesh::create_rudp_server( "rust-server", 0x1020_3040, diff --git a/packages/cultnet-rs/src/cultmesh.rs b/packages/cultnet-rs/src/cultmesh.rs index d186913c..41aeb994 100644 --- a/packages/cultnet-rs/src/cultmesh.rs +++ b/packages/cultnet-rs/src/cultmesh.rs @@ -11,6 +11,9 @@ use std::time::Duration; use crate::CultNetRudpSocketMode; use crate::CultNetRudpSocketTransportConnection; use crate::CultNetRudpSocketTransportOptions; +use crate::CultNetSchemaRegistry; +use crate::CultNetShardCatalog; +use crate::builtin_schema_registry; #[derive(Clone, Debug, PartialEq, Eq)] pub struct CultMeshPeerCard { @@ -236,6 +239,18 @@ impl Default for CultMeshRudpSocketOptions { pub struct CultMesh; impl CultMesh { + pub fn create_schema_registry() -> CultNetSchemaRegistry { + CultNetSchemaRegistry::new() + } + + pub fn create_builtin_schema_registry() -> Result { + builtin_schema_registry() + } + + pub fn create_shard_catalog() -> CultNetShardCatalog { + CultNetShardCatalog::new() + } + pub fn create_peer_catalog() -> CultMeshPeerCatalog { CultMeshPeerCatalog::new() } diff --git a/packages/cultnet-rs/tests/cultnet.rs b/packages/cultnet-rs/tests/cultnet.rs index c0692c88..1467aeb6 100644 --- a/packages/cultnet-rs/tests/cultnet.rs +++ b/packages/cultnet-rs/tests/cultnet.rs @@ -28,6 +28,7 @@ use cultnet_rs::CultNetRudpSocketMode; use cultnet_rs::CultNetRudpSocketTransportConnection; use cultnet_rs::CultNetRudpSocketTransportOptions; use cultnet_rs::CultNetSchemaKind; +use cultnet_rs::CultNetSchemaRegistration; use cultnet_rs::CultNetSchemaRegistry; use cultnet_rs::CultNetSecret; use cultnet_rs::CultNetServerSecurityOptions; @@ -1184,6 +1185,66 @@ fn cultmesh_facade_creates_rudp_client_from_peer_endpoint() -> Result<()> { Ok(()) } +#[test] +fn cultmesh_facade_exposes_schema_registry_and_shard_catalog_owners() -> Result<()> { + let mut schemas = CultMesh::create_schema_registry(); + schemas.register(CultNetSchemaRegistration { + schema_id: "rust.cultmesh.note.v1".to_string(), + kind: CultNetSchemaKind::DocumentPayload, + wire_contracts: vec![CultNetWireContract::CultNetSchemaV0], + schema_version: Some("rust.cultmesh.note.v1".to_string()), + document_type: Some("rust.cultmesh.note".to_string()), + title: Some("Rust CultMesh Note".to_string()), + schema_json: Some( + r#"{"$id":"rust.cultmesh.note.v1","type":"object","properties":{"body":{"type":"string"}}}"# + .to_string(), + ), + })?; + assert_eq!( + schemas + .get("rust.cultmesh.note.v1", false) + .expect("registered schema is discoverable") + .document_type + .as_deref(), + Some("rust.cultmesh.note") + ); + + let builtins = CultMesh::create_builtin_schema_registry()?; + let shared_contracts = builtins.list(&cultnet_rs::CultNetSchemaCatalogOptions { + include_schema_json: false, + schema_ids: None, + kinds: Some(vec![CultNetSchemaKind::SharedContract]), + }); + assert!(shared_contracts.iter().any(|schema| { + schema.schema_id + == "https://github.com/GameCult/cultnet-ts/contracts/cultnet.transport-profile.schema.json" + && schema.schema_version.as_deref() == Some("cultnet.transport_profile.v0") + })); + + let mut shards = CultMesh::create_shard_catalog(); + shards.upsert(CultNetShardDescriptor { + shard_id: "rust-notes-a".to_string(), + owner_runtime_id: "rust-cultmesh".to_string(), + epoch: 7, + is_primary: Some(true), + schema_ids: vec!["rust.cultmesh.note.v1".to_string()], + key_prefix: Some("note:".to_string()), + primary_endpoints: vec!["rudp://127.0.0.1:4100".to_string()], + replica_endpoints: Vec::new(), + read_replica_endpoints: Vec::new(), + region: None, + authority_lease_id: Some("lease:rust-notes-a".to_string()), + })?; + let matching = shards.list(&cultnet_rs::CultNetShardCatalogOptions { + schema_ids: Some(vec!["rust.cultmesh.note.v1".to_string()]), + record_keys: Some(vec!["note:1".to_string()]), + }); + assert_eq!(matching.len(), 1); + assert_eq!(matching[0].shard_id, "rust-notes-a"); + assert!(shards.get("rust-notes-a").is_some()); + Ok(()) +} + #[test] fn cultmesh_authority_leases_gate_peer_contact_hints() -> Result<()> { let now = Utc::now();