From 392a54bc80588403bee4013947199b5cfd17c006 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 14 Jun 2026 14:16:41 +0100 Subject: [PATCH] Advertise Python CultNet error contract --- packages/cultcache-py/PARITY.md | 5 ++++- packages/cultcache-py/src/cultcache_py/verify.py | 1 + packages/cultcache-py/src/cultmesh_py/server.py | 1 + packages/cultcache-py/src/cultnet_py/schema_catalog.py | 8 ++++++++ packages/cultcache-py/tests/test_cultcache.py | 9 +++++++++ 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/cultcache-py/PARITY.md b/packages/cultcache-py/PARITY.md index 2ec6c76a..9a5cb329 100644 --- a/packages/cultcache-py/PARITY.md +++ b/packages/cultcache-py/PARITY.md @@ -35,7 +35,10 @@ CultCache, CultNet, and CultMesh. It is evidence, not confetti. messages they speak through the same catalog authority. Python callers can also ingest and filter remote schema catalog responses through typed `CultNetSchemaCatalog` and `CultNetSchemaDescriptor` helpers, including - `CultNetRawClient.sync_schema_catalog(...)`. + `CultNetRawClient.sync_schema_catalog(...)`. The catalog includes + `cultnet.error.v0`, and the local server advertises that version in hello + responses because oversized snapshots and other peer failures use that + contract on the wire. - Python CultNet shard catalog: Provides typed `CultNetShardCatalog` and `CultNetShardDescriptor` helpers for ingesting, filtering, and emitting shard topology responses, plus diff --git a/packages/cultcache-py/src/cultcache_py/verify.py b/packages/cultcache-py/src/cultcache_py/verify.py index 4c0f7d95..b0bb4af6 100644 --- a/packages/cultcache-py/src/cultcache_py/verify.py +++ b/packages/cultcache-py/src/cultcache_py/verify.py @@ -227,6 +227,7 @@ def _check_local_cultmesh_wire_smoke() -> dict[str, Any]: failures.append("hello_runtime") supported_versions = set(hello_response.get("supportedMessageVersions") or ()) for schema_version in ( + "cultnet.error.v0", "cultnet.document_put_raw.v0", "cultnet.document_delete.v0", "cultnet.shard_log_request.v0", diff --git a/packages/cultcache-py/src/cultmesh_py/server.py b/packages/cultcache-py/src/cultmesh_py/server.py index ed79bb02..086eeb71 100644 --- a/packages/cultcache-py/src/cultmesh_py/server.py +++ b/packages/cultcache-py/src/cultmesh_py/server.py @@ -268,6 +268,7 @@ class CultMeshLocalServer: ], "supportedMessageVersions": [ "cultnet.hello.v0", + "cultnet.error.v0", "cultnet.schema_catalog_request.v0", "cultnet.snapshot_request.v0", "cultnet.database_subscribe.v0", diff --git a/packages/cultcache-py/src/cultnet_py/schema_catalog.py b/packages/cultcache-py/src/cultnet_py/schema_catalog.py index 53631bad..e91c573e 100644 --- a/packages/cultcache-py/src/cultnet_py/schema_catalog.py +++ b/packages/cultcache-py/src/cultnet_py/schema_catalog.py @@ -12,6 +12,7 @@ PEER_EXCHANGE_REQUEST = "cultmesh.peer_exchange_request.v0" WIRE_MESSAGE_SCHEMA_VERSIONS = ( ("cultnet.hello.v0", "CultNet Hello Message", f"{CULTNET_SCHEMA_BASE}/cultnet.hello.schema.json"), + ("cultnet.error.v0", "CultNet Error Message", f"{CULTNET_SCHEMA_BASE}/cultnet.error.schema.json"), ("cultnet.document_delete.v0", "CultNet Document Delete Message", f"{CULTNET_SCHEMA_BASE}/cultnet.document-delete.schema.json"), ("cultnet.document_put_raw.v0", "CultNet Raw Document Put Message", f"{CULTNET_SCHEMA_BASE}/cultnet.document-put-raw.schema.json"), ("cultnet.snapshot_request.v0", "CultNet Snapshot Request Message", f"{CULTNET_SCHEMA_BASE}/cultnet.snapshot-request.schema.json"), @@ -182,6 +183,7 @@ def wire_message_schema_json(schema_id: str, title: str, schema_version: str) -> def wire_message_required_fields(schema_version: str) -> list[str]: required = { "cultnet.hello.v0": ["schemaVersion", "runtimeId"], + "cultnet.error.v0": ["schemaVersion", "error"], "cultnet.document_delete.v0": ["schemaVersion", "messageId", "schemaId", "recordKey"], "cultnet.document_put_raw.v0": ["schemaVersion", "messageId", "document"], "cultnet.snapshot_request.v0": ["schemaVersion", "messageId"], @@ -256,6 +258,12 @@ def wire_message_schema_properties(schema_version: str) -> dict[str, Any]: "supportedMessageVersions": string_array, "supportsSchemaCatalog": {"type": "boolean"}, }, + "cultnet.error.v0": { + "messageId": common["messageId"], + "error": {"type": "string"}, + "code": {"type": "string"}, + "details": {"type": "object", "additionalProperties": True}, + }, "cultnet.document_delete.v0": { **common, }, diff --git a/packages/cultcache-py/tests/test_cultcache.py b/packages/cultcache-py/tests/test_cultcache.py index 46221345..cf1340cd 100644 --- a/packages/cultcache-py/tests/test_cultcache.py +++ b/packages/cultcache-py/tests/test_cultcache.py @@ -1053,6 +1053,7 @@ class CultCacheTests(unittest.TestCase): descriptors = wire_message_schema_descriptors(include_schema_json=True) by_version = {descriptor["schemaVersion"]: descriptor for descriptor in descriptors} for schema_version in [ + "cultnet.error.v0", "cultnet.document_delete.v0", "cultnet.database_change_raw.v0", "cultnet.shard_log_response.v0", @@ -1072,6 +1073,11 @@ class CultCacheTests(unittest.TestCase): ) self.assertEqual(delete_schema["properties"]["recordKey"]["type"], "string") + error_schema = json.loads(by_version["cultnet.error.v0"]["schemaJson"]) + self.assertEqual(error_schema["required"], ["schemaVersion", "error"]) + self.assertEqual(error_schema["properties"]["error"]["type"], "string") + self.assertIn("details", error_schema["properties"]) + change_schema = json.loads(by_version["cultnet.database_change_raw.v0"]["schemaJson"]) self.assertEqual(change_schema["properties"]["changeKind"]["enum"], ["added", "updated", "removed"]) self.assertIn("document", change_schema["properties"]) @@ -3077,6 +3083,7 @@ class CultCacheTests(unittest.TestCase): self.assertEqual(hello_response["runtimeId"], "mesh-server") self.assertEqual(hello_response["displayName"], "Mesh Server") self.assertIn("cultnet.database_subscribe.v0", hello_response["supportedMessageVersions"]) + self.assertIn("cultnet.error.v0", hello_response["supportedMessageVersions"]) self.assertIn("cultnet.document_put_raw.v0", hello_response["supportedMessageVersions"]) self.assertIn("cultnet.simulation_observation.v0", hello_response["supportedMessageVersions"]) self.assertEqual(hello_response["supportedMutationContracts"][0]["documentType"], "mesh.server_note") @@ -3087,9 +3094,11 @@ class CultCacheTests(unittest.TestCase): self.assertIn("cultnet.document_delete.v0", schema_response["schemas"][0]["wireContracts"]) self.assertIn("cultnet.shard_log_response.v0", schema_response["schemas"][0]["wireContracts"]) wire_descriptors = {schema["schemaVersion"]: schema for schema in wire_schema_response["schemas"]} + self.assertEqual(wire_descriptors["cultnet.error.v0"]["kind"], "wire_message") self.assertEqual(wire_descriptors["cultnet.document_put_raw.v0"]["kind"], "wire_message") self.assertIn("schemaJson", wire_descriptors["cultnet.document_put_raw.v0"]) self.assertIn("cultmesh.peer_exchange_response.v0", wire_descriptors) + self.assertIn("cultnet.error.v0", {descriptor.schema_version for descriptor in synced_wire_descriptors}) self.assertIn("cultnet.document_put_raw.v0", {descriptor.schema_version for descriptor in synced_wire_descriptors}) self.assertIsNotNone(synced_schema_catalog.get("https://github.com/GameCult/cultnet-ts/contracts/cultnet.document-put-raw.schema.json")) self.assertEqual(snapshot_response["documents"][0]["recordKey"], "note:1")