Advertise Python daemon readiness capabilities

This commit is contained in:
Meta Vens 2026-06-14 16:44:21 +01:00
commit e181259db2
3 changed files with 37 additions and 13 deletions

View file

@ -104,19 +104,22 @@ CultCache, CultNet, and CultMesh. It is evidence, not confetti.
store beside the cache file by default. The `cultmesh-py-daemon` /
`python -m cultmesh_py.daemon` entrypoint launches that same local TCP server
body and emits a bounded readiness document for process supervisors while the
served CultNet endpoint remains the capability owner. The daemon can register
and seed the package interop-note document so launched processes advertise a
real payload schema and serve both a real snapshot and a real shard-log entry
over the same CultNet lane. When launched with a Verse id, it publishes its
actual bound endpoint in the Verse and peer catalogs so discovery clients can
find the running Python peer without embedding code-owned catalogs. With a
cache file and durable shard-log path, a restarted daemon can rehydrate the
persisted interop-note snapshot and shard-log entry without reseeding. The
launched daemon also accepts raw puts over a live database subscription stream,
emits the matching change notification, and records the mutation in the served
shard log. If its durable shard log has been compacted, stale shard-log
requests return the same explicit `compacted_history` resync boundary over
the daemon wire endpoint instead of silently looking empty.
served CultNet endpoint remains the capability owner. Readiness now mirrors
the served hello capability metadata, shard ids, and configured snapshot
limits so supervisors can inspect startup state without inventing a second
protocol truth. The daemon can register and seed the package interop-note
document so launched processes advertise a real payload schema and serve both
a real snapshot and a real shard-log entry over the same CultNet lane. When
launched with a Verse id, it publishes its actual bound endpoint in the Verse
and peer catalogs so discovery clients can find the running Python peer
without embedding code-owned catalogs. With a cache file and durable shard-log
path, a restarted daemon can rehydrate the persisted interop-note snapshot and
shard-log entry without reseeding. The launched daemon also accepts raw puts
over a live database subscription stream, emits the matching change
notification, and records the mutation in the served shard log. If its durable
shard log has been compacted, stale shard-log requests return the same
explicit `compacted_history` resync boundary over the daemon wire endpoint
instead of silently looking empty.
The daemon CLI snapshot document/byte limits feed the same public server edge;
oversized daemon snapshot responses are returned as structured
`cultnet.error.v0` peer errors rather than partial payloads.

View file

@ -9,6 +9,7 @@ from pathlib import Path
from typing import Any
from cultcache_py.interop import INTEROP_SCHEMA_VERSION, interop_note_document
from cultnet_py import hello
from .facade import CultMesh
from .server import CultMeshLocalServer
@ -133,14 +134,23 @@ def advertise_self(
def daemon_ready_document(server: CultMeshLocalServer) -> dict[str, Any]:
hello_response = server.handle_message(hello(runtime_id="daemon-ready-probe").to_wire()) or {}
return {
"schemaVersion": READY_SCHEMA_VERSION,
"runtimeId": server.node.runtime_id,
"runtimeKind": server.runtime_kind,
"displayName": hello_response.get("displayName") or server.node.runtime_id,
"host": server.host,
"port": server.port,
"endpoint": f"cultnet://{server.host}:{server.port}",
"shardIds": server.node.database.shard_ids(),
"supportedDocumentTypes": [document.type for document in server.node.documents],
"supportedMessageVersions": list(hello_response.get("supportedMessageVersions") or ()),
"supportedMutationContracts": list(hello_response.get("supportedMutationContracts") or ()),
"snapshotLimits": {
"maxSnapshotDocuments": server.max_snapshot_documents,
"maxSnapshotBytes": server.max_snapshot_bytes,
},
}

View file

@ -3082,8 +3082,16 @@ class CultCacheTests(unittest.TestCase):
ready = json.loads(ready_path.read_text(encoding="utf-8"))
self.assertEqual(ready["schemaVersion"], READY_SCHEMA_VERSION)
self.assertEqual(ready["runtimeId"], "daemon-test-peer")
self.assertEqual(ready["runtimeKind"], "python")
self.assertEqual(ready["displayName"], "Daemon Test Peer")
self.assertEqual(ready["endpoint"], f"cultnet://127.0.0.1:{ready['port']}")
self.assertEqual(ready["shardIds"], ["daemon-interop"])
self.assertEqual(ready["supportedDocumentTypes"], ["cultcache.interop-note"])
self.assertIn("cultnet.hello.v0", ready["supportedMessageVersions"])
self.assertIn("cultnet.document_put_raw.v0", ready["supportedMessageVersions"])
self.assertEqual(ready["supportedMutationContracts"][0]["documentType"], "cultcache.interop-note")
self.assertIn("shardLog", ready["supportedMutationContracts"][0]["operations"])
self.assertEqual(ready["snapshotLimits"], {"maxSnapshotBytes": None, "maxSnapshotDocuments": None})
client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0)
hello_response = client.request(
@ -3096,6 +3104,8 @@ class CultCacheTests(unittest.TestCase):
self.assertIn("cultnet.hello.v0", hello_response["supportedMessageVersions"])
self.assertIn("cultnet.schema_catalog_request.v0", hello_response["supportedMessageVersions"])
self.assertEqual(hello_response["supportedDocumentTypes"], ["cultcache.interop-note"])
self.assertEqual(ready["supportedMessageVersions"], hello_response["supportedMessageVersions"])
self.assertEqual(ready["supportedMutationContracts"], hello_response["supportedMutationContracts"])
schema_response = client.fetch_schema_catalog(schema_ids=["cultcache.interop-note"], include_schema_json=True)
self.assertEqual(schema_response["schemas"][0]["schemaVersion"], INTEROP_SCHEMA_VERSION)
@ -3556,6 +3566,7 @@ class CultCacheTests(unittest.TestCase):
)
try:
ready = self._wait_for_ready_file(process, ready_path)
self.assertEqual(ready["snapshotLimits"], {"maxSnapshotBytes": None, "maxSnapshotDocuments": 0})
client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0)
with self.assertRaisesRegex(CultNetPeerError, "Snapshot document limit exceeded") as raised: