CultNet selection cut1, commit 2 fix batch: R-2, Python refuses v1 selection messages
R-2 was owed by Cut 1 and not yet done (docs/cultnet-selection-cut.md, section
9, Self's rulings 2026-09-22): a v0-only Python peer must refuse a
cultnet.selection.v1 carrier message loudly instead of silently answering
with an empty list.
- packages/cultnet-py/src/cultnet_py/interop_peer.py:
handle_server_message's fallback now recognises any cultnet.*.v1 schema
version and replies cultnet.error.v0 { code: "unsupported_schema_version",
details: { schemaVersion } } via the new unsupported_schema_version_error
helper, matching cultmesh_py's existing error shape. One test
(test_python_interop_peer_refuses_v1_selection_messages) proves a
snapshot_request.v1 and a database_subscribe.v1 message both get the
refusal, not an empty list.
- packages/cultmesh-py/src/cultmesh_py/server.py's delegating path
(_handle_connection_message -> handle_message) already refused every
unsupported schema version generically, v1 included - no code change
needed there. test_cultmesh_local_server_returns_errors_for_bad_requests
gains an explicit assertion for a snapshot_request.v1 message through that
same path, so the coverage is named rather than only implied by the
generic case.
This is the one exception to "do not touch packages/" in this fix batch: only
these two Python packages' interop/server files and their tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
568e8e356f
commit
021e3f1bba
3 changed files with 50 additions and 1 deletions
|
|
@ -4317,6 +4317,14 @@ class CultMeshTests(unittest.TestCase):
|
|||
{"schemaVersion": "cultnet.document_delete.v0", "messageId": "bad-delete", "schemaId": "mesh.error_note.v1"},
|
||||
expected_schema_version="cultnet.error.v0",
|
||||
)
|
||||
# R-2, owed by Cut 1 (docs/cultnet-selection-cut.md, section 9): this v0-only Mesh server
|
||||
# refuses a cultnet.selection.v1 carrier message through the same delegating path
|
||||
# (_handle_connection_message -> handle_message) as any other unsupported schema, not a
|
||||
# silent empty response.
|
||||
unsupported_v1 = raw_client.request(
|
||||
{"schemaVersion": "cultnet.snapshot_request.v1", "messageId": "bad-v1-selection"},
|
||||
expected_schema_version="cultnet.error.v0",
|
||||
)
|
||||
finally:
|
||||
server.stop()
|
||||
|
||||
|
|
@ -4328,6 +4336,9 @@ class CultMeshTests(unittest.TestCase):
|
|||
self.assertEqual(malformed_put["code"], "malformed_document_put")
|
||||
self.assertEqual(malformed_delete["messageId"], "bad-delete")
|
||||
self.assertEqual(malformed_delete["code"], "malformed_document_delete")
|
||||
self.assertEqual(unsupported_v1["messageId"], "bad-v1-selection")
|
||||
self.assertEqual(unsupported_v1["code"], "unsupported_schema_version")
|
||||
self.assertEqual(unsupported_v1["details"]["schemaVersion"], "cultnet.snapshot_request.v1")
|
||||
|
||||
def test_cultmesh_authority_lease_requires_live_matching_lease(self) -> None:
|
||||
peer = CultMeshPeerCard(
|
||||
|
|
|
|||
|
|
@ -384,9 +384,25 @@ def handle_server_message(state: PeerState, message: dict[str, Any], subscriptio
|
|||
return [state.verse_catalog.create_response(message)]
|
||||
if schema_version == PEER_EXCHANGE_REQUEST:
|
||||
return [state.peer_catalog.create_response(message)]
|
||||
if isinstance(schema_version, str) and schema_version.startswith("cultnet.") and schema_version.endswith(".v1"):
|
||||
# R-2, owed by Cut 1 (docs/cultnet-selection-cut.md, section 9): this peer speaks
|
||||
# cultnet.selection.v1's carrier messages (snapshot_request.v1, database_subscribe.v1) not at
|
||||
# all - it is v0-only. A v1 message is refused loudly, not answered with a silent empty list.
|
||||
return [unsupported_schema_version_error(message)]
|
||||
return []
|
||||
|
||||
|
||||
def unsupported_schema_version_error(message: dict[str, Any]) -> dict[str, Any]:
|
||||
schema_version = message.get("schemaVersion")
|
||||
return {
|
||||
"schemaVersion": "cultnet.error.v0",
|
||||
"messageId": str(message.get("messageId") or ""),
|
||||
"error": f"Unsupported CultNet message schema: {schema_version!r}.",
|
||||
"code": "unsupported_schema_version",
|
||||
"details": {"schemaVersion": schema_version},
|
||||
}
|
||||
|
||||
|
||||
def handle_database_subscribe(state: PeerState, message: dict[str, Any], subscriptions: dict[str, DatabaseSubscription]) -> list[dict[str, Any]]:
|
||||
subscription_id = str(message.get("subscriptionId") or message.get("messageId") or "")
|
||||
if not subscription_id:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,12 @@ from cultnet_py.compare_csharp import (
|
|||
_median_result,
|
||||
_parity_status,
|
||||
)
|
||||
from cultnet_py.interop_peer import append_shard_log_put, build_state, raw_snapshot_response
|
||||
from cultnet_py.interop_peer import (
|
||||
append_shard_log_put,
|
||||
build_state,
|
||||
handle_server_message,
|
||||
raw_snapshot_response,
|
||||
)
|
||||
from cultnet_py import (
|
||||
compute_simulation_claim_hash,
|
||||
CultNetDatabaseChange,
|
||||
|
|
@ -1948,6 +1953,23 @@ class CultNetTests(unittest.TestCase):
|
|||
self.assertEqual(response["shardLogSequence"], 1)
|
||||
self.assertEqual([record["recordKey"] for record in response["documents"]], ["note:logged"])
|
||||
|
||||
def test_python_interop_peer_refuses_v1_selection_messages(self) -> None:
|
||||
# R-2, owed by Cut 1 (docs/cultnet-selection-cut.md, section 9): a v0-only peer refuses a v1
|
||||
# message loudly - cultnet.error.v0 { code: "unsupported_schema_version" } - rather than
|
||||
# silently answering with an empty list. handle_server_message never touches `state` before
|
||||
# this refusal fires, so no PeerState fixture is needed here.
|
||||
for schema_version in ("cultnet.snapshot_request.v1", "cultnet.database_subscribe.v1"):
|
||||
with self.subTest(schema_version=schema_version):
|
||||
responses = handle_server_message(
|
||||
None, # type: ignore[arg-type]
|
||||
{"schemaVersion": schema_version, "messageId": "v1-refused"},
|
||||
{},
|
||||
)
|
||||
self.assertEqual(len(responses), 1)
|
||||
self.assertEqual(responses[0]["schemaVersion"], "cultnet.error.v0")
|
||||
self.assertEqual(responses[0]["code"], "unsupported_schema_version")
|
||||
self.assertEqual(responses[0]["messageId"], "v1-refused")
|
||||
|
||||
def test_cultnet_simulation_consensus_dedupes_witnesses_and_requires_quorum(self) -> None:
|
||||
from cultnet_py import CultNetSimulationConsensus
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue