From 221f59b754e9f4c1357a37e05ec88bc77c23d28b Mon Sep 17 00:00:00 2001 From: Libby Date: Sat, 7 Nov 2026 19:08:33 +0000 Subject: [PATCH 1/2] Blender add-on: grant peer writes explicitly to its served node cultmesh_py (CultLib GameCult/CultLib#33) refuses peer puts and deletes unless a write authority grants them. The add-on's clients put command intents, and the session token guard already authenticates them, so the target passes CultMeshGrantAllWrites() when cultmesh_py has it. Older cultmesh_py already accepts every write and is left alone. Co-Authored-By: Claude Sonnet 5.5 --- .../blender/brokkr_bridge/blender_target.py | 7 +++++++ surfaces/blender/tests/test_server_guard.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/surfaces/blender/brokkr_bridge/blender_target.py b/surfaces/blender/brokkr_bridge/blender_target.py index fdd3a9a..f482de9 100644 --- a/surfaces/blender/brokkr_bridge/blender_target.py +++ b/surfaces/blender/brokkr_bridge/blender_target.py @@ -345,6 +345,12 @@ class BrokkrBlenderTarget: guard = SessionGuard() if require_token else None if guard is not None: server_class = guarded_server_class(server_class, guard) + # The add-on's own clients put command intents to this server, so it grants peer writes + # explicitly: cultmesh_py refuses them by default, and the session token above is what + # authenticates a peer when it is required. A cultmesh_py older than the gate has no + # CultMeshGrantAllWrites and already accepts every write. + grant_all = getattr(cultmesh, "CultMeshGrantAllWrites", None) + authority_args = {"write_authority": grant_all()} if grant_all is not None else {} server = server_class( node=node, verse_catalog=verse_catalog, @@ -354,6 +360,7 @@ class BrokkrBlenderTarget: display_name="Brokkr Blender Editor", max_snapshot_documents=int(max_snapshot_documents), max_snapshot_bytes=int(max_snapshot_bytes), + **authority_args, ).start() endpoint = f"cultnet://{resolved_host}:{server.port}" diff --git a/surfaces/blender/tests/test_server_guard.py b/surfaces/blender/tests/test_server_guard.py index c7fc23a..10f964b 100644 --- a/surfaces/blender/tests/test_server_guard.py +++ b/surfaces/blender/tests/test_server_guard.py @@ -261,6 +261,23 @@ class RealServerTests(unittest.TestCase): response = server._handle_connection_message(message, {}) self.assertIn("malformed", response[0].get("code", "")) # past the guard, refused by the store as an empty put + def test_a_peer_put_is_accepted_over_tcp_because_the_target_grants_peer_writes(self): + status = self.start() + cultmesh, _ = bt._load_cultmesh(PACKAGES) + import cultnet_py + document = self.target._documents["command_intent"] + client = cultmesh.CultMesh.create_client("127.0.0.1", status["port"], timeout_seconds=3.0) + with client.subscribe_database(subscription_id="peer", schema_ids=[bt.COMMAND_INTENT_SCHEMA], + include_snapshot=False) as subscription: + subscription.send(cultnet_py.document_put_raw( + message_id="peer-put", key="blender/commands/peer", schema_id=document.catalog_entry().schema_id, + stored_at="2026-11-07T00:00:00Z", + payload=document.encode_payload({ + "schema": bt.COMMAND_INTENT_SCHEMA, "commandId": "peer", "action": "createObject"}), + )) + change = subscription.read_next_change() # a refusal would raise CultNetPeerError instead + self.assertEqual(change.record_key, "blender/commands/peer") + def queue_intent(self, key="blender/commands/q1"): node, documents = self.target._open_node(self.cache, PACKAGES) node.database.put(documents["command_intent"], key, { -- 2.47.3 From a98ccacb37f4568f049163702377178af7cf7ad9 Mon Sep 17 00:00:00 2001 From: Libby Date: Sat, 7 Nov 2026 19:14:21 +0000 Subject: [PATCH 2/2] Blender add-on docs: grant-all lets a peer write any registered document Co-Authored-By: Claude Sonnet 5.5 --- surfaces/blender/docs/install.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/surfaces/blender/docs/install.md b/surfaces/blender/docs/install.md index a61be34..6548af4 100644 --- a/surfaces/blender/docs/install.md +++ b/surfaces/blender/docs/install.md @@ -51,6 +51,12 @@ write, not only at connect. Reads stay open. `Stop Server`, quitting, or disabling the add-on revokes it; it is held in memory only and never saved to preferences or the cache. +Peer write scope: CultLib's `cultmesh_py` refuses peer writes unless an authority grants them, and the add-on +passes `CultMeshGrantAllWrites()`. A peer that gets past the session token (or any peer, when the token is off) can +therefore put or delete **any registered document** on the served node, not only command intents. That is acceptable +on loopback, the default; a grant scoped to command intents is future work. Do not combine `Allow Non-Loopback Bind` +with the token off. + Run the tests (no Blender needed; `CULTLIB_PACKAGES` adds the real-server tests): ```text -- 2.47.3