documentation to use mycelium_network as message layer #44
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
there are features which allow us to use mycelium without tun adaptor
we need following features
so basically mycelium is then a swiss army knive to send openrpc messages around
document what we have and what still needs to be implemented and how we should do it best
how to test:
make e2e test where we launch 3 daemons in hero_proc
and we call them mycelium_network_test1 ...
and we boot them message bus layer allone (no tun)
then we test how we can send messages around between them
and how they can talk to each other
also we test how we can remotely manage them if we are the manager (the admin)
implementation notes for admin functionality
it might be that there is no admin functionality in there yet
if not implementit, so only whitelisted administrators (based on pub key of other mycelium node) can talk to us
for mgmt tasks
now also in _api implement socket to messages over the message bus
and termination
administrators can manage all
usecase is socket to socket
e.g.
2 nodes
on node A, there is socket ~/hero/var/sockets/hero_proc
on node B, we ask mycelium_server (engine?) to create ~/hero/var/sockets/hero_proc_remote_A
every openrpc message we now send to ~/hero/var/sockets/hero_proc_remote_A gets picked up by our mycelium node and forwarded to A where it gets terminated on hero_proc
so we basically implemented a proxy between a local socket & remote, fully seamless
the full configuration needs to be possible (B socket to openrpc message, openrpc message goes to A, there there is topic defining it where to terminate, and security mgmt)
admin UI
make sure our _ui is userfriendly so we can configure this seamless, I should be able to configure my remote endpoints from my local daemon if I am administrator of a remote node
Implementation Spec for Issue #44
Scope of this run: documentation + gap analysis only. No Rust changes.
Objective
Document mycelium_network's existing message-layer features and produce an unambiguous gap analysis against the asks in this issue. After this run, readers must be able to tell exactly which capabilities exist today (with code citations), which are partial, and which are missing — and what the proposed design for the missing pieces (persisted state model, admin layer, outbound socket proxy) looks like.
Requirements
/hero_sockets).mycelium_network_test1..3, no TUN, message-bus only)..tomlfiles under$MYCELIUM_STATE_DIR/, with a clean model layer.Files to Modify/Create
docs/messagebus.md— new, top-level "use mycelium_network as a message layer" guide. EXISTS/PARTIAL/MISSING matrix with file:line citations. Indexes the other docs.docs/message.md— update. Queue retention semantics (in-memory only, lost on restart), reply-correlation timeouts, limits/gaps section.docs/state_persistence.md— new. The model layer for everything we remember. Documents the existingDaemonState(priv_key.bin, config.toml, peers.toml) and the proposed additions:topics.tomlandadmin.toml. Atomic tmp+rename convention, file modes, RPC-write-through behavior.docs/topic_configuration.md— update. Add "Limits today" (subnet-only ACL), make clearforward_socketis inbound-only, add a "Persistence" section pointing atstate_persistence.md.docs/admin.md— new. Section 1: existing UI-tierADMIN_SECRETSIP whitelist. Section 2: what the issue asks for. Section 3: proposed daemon-level admin model withadmin.toml, RPC surface, authorize step.docs/socket_proxy.md— new. Section 1: inboundforward_socket(exists). Section 2: outbound socket → topic (proposed, nested under[topics.<name>]intopics.toml). Section 3: contrast with the existing SOCKS5proxy.rsso they aren't conflated.docs/e2e_messagebus_test.md— new. 3-node test plan with phases A (today), B (admin model), C (outbound proxy), D (restart persistence).CLAUDE.md— update. Add a "Message-bus surface" section linking to the new docs.README.md— update. Add the new doc paths under existing docs links.Current State (audit summary)
Topic messaging — EXISTS. Wire types in
crates/mycelium_api/src/message.rs, engine APIpush_message/reply_message/get_messageincrates/mycelium_engine/src/message.rs:541-700. Topic byte-string max 255 bytes. Inbox is an in-memoryVecDeque<ReceivedMessage>— no on-disk persistence, lost on restart.popMessagepeek/pop/timeout/topic filter exposed via OpenRPC (crates/mycelium_api/openrpc.json, dispatchercrates/mycelium_api/src/rpc.rs:277).Per-topic source whitelists — PARTIAL. Subnet-based whitelist fully implemented in
crates/mycelium_engine/src/message/topic.rs:51-118(TopicConfig,TopicWhitelistConfig); enforcement incrates/mycelium_engine/src/message.rs:703-728(topic_allowedcheckssrc: IpAddr). RPC:addTopic,removeTopic,addTopicSource,removeTopicSource,getTopicSources,getDefaultTopicAction,setDefaultTopicAction. Gap: no pubkey-based whitelist. The pubkey is available later in receive (mycelium_engine/src/message.rs:543-558) but not consulted bytopic_allowed. Adding it means extendingTopicWhitelistConfigwithpubkeys: Vec<PublicKey>and adding RPC methodsaddTopicPubkeySource/removeTopicPubkeySource.Topic → local-socket termination (inbound) — EXISTS.
TopicWhitelistConfig::forward_socket: Option<PathBuf>(crates/mycelium_engine/src/message/topic.rs:13-49); dispatcher incrates/mycelium_engine/src/message.rs:580-650writes message data to the UDS, reads a reply with 5s timeout, sends it back viareply_message. RPC:getTopicForwardSocket,setTopicForwardSocket,removeTopicForwardSocket.Local-socket → remote-topic outbound proxy — MISSING. No code listens on a local UDS and forwards writes onto the message bus.
grep -rn UnixListenerfinds only themycelium_uiHTTP socket and the RPC UDS. Proposed: new daemon component (sibling offorward_socketin topic config) that takes(topic, dst_pubkey_or_ip, local_socket_path)and runs an accept loop; each inbound write becomes apushMessage; replies route back to the originating UDS connection (correlate via myceliumMessageId). RPC mirror:addTopicOutboundSocket(topic, dst, path),removeTopicOutboundSocket(topic),getTopicOutboundSocket(topic). Stored on disk as a nestedoutbound_socket = { path, dst }field under[topics.<name>]in the proposedtopics.toml.Admin / pubkey-based authorization — MISSING (at the daemon).
MyceliumRpctrait (crates/mycelium_api/src/rpc.rs:248-377) has no auth context. UDS server (crates/mycelium_api/src/rpc/unix.rs) accepts every caller. The only existing admin gate is the UI-tierADMIN_SECRETSIP whitelist incrates/mycelium_ui/src/admin_secrets.rs— gates which TCP clients can reach the UI, not which mycelium peers can RPC the daemon. Proposed: daemon-sideadmin_pubkeys: Vec<PublicKey>config (with RPCadmin.list/add/remove); admin-only RPC methods routed through an authorize step that compares the caller's mycelium source pubkey to the admin set. Persisted asadmin.tomlin$MYCELIUM_STATE_DIR/. Pattern reference:herolib_openrpc_authorize.OpenRPC method surface for messages — EXISTS.
crates/mycelium_api/openrpc.jsonand traitcrates/mycelium_api/src/rpc.rs:248-330:pushMessage,popMessage,pushMessageReply,getMessageInfo,getDefaultTopicAction,setDefaultTopicAction,getTopics,addTopic,removeTopic,getTopicSources,addTopicSource,removeTopicSource,getTopicForwardSocket,setTopicForwardSocket,removeTopicForwardSocket. All gated behind themessageCargo feature. No RPC for: per-pubkey topic ACL, outbound socket forwarding, admin pubkey management.Persisted state — PARTIAL. Today
DaemonState(crates/mycelium_daemon/src/state.rs) ownspriv_key.bin(mode 0o640),config.tomlandpeers.toml(mode 0o644), with atomic tmp+rename writes; state dir mode 0o700. Topic config (TopicConfigin the engine) lives only in memory — everyaddTopic/addTopicSource/setTopicForwardSocketmutation is lost on restart. The legacy path (crates/mycelium_daemon/src/legacy_runner.rs:35-40) reads a stand-alone topic toml read-only at startup, which is not the model we want going forward.UI coverage — PARTIAL. Templates exist for messages and topics:
crates/mycelium_ui/templates/messages.html(push/pop) andcrates/mycelium_ui/templates/topics.html(default action, add topic, configure subnets, configure forward socket). Thecrates/mycelium_ui/templates/admin.htmlpage configures the UI's ownADMIN_SECRETSIP list, not a daemon-level admin set. Missing UI: pubkey-based topic ACLs, outbound socket forwarding, remote-node management.E2E test coverage — PARTIAL.
crates/mycelium_e2e/tests/two_node_linux.rs(325 lines) spins up twomycelium_serverchildren with isolatedMYCELIUM_STATE_DIR/HERO_SOCKET_DIR/ TCP ports, peers them, exercises peers/routes/topics/messaging throughmycelium_sdk(add_topicat line 260,push_messageat line 290,pop_messageat line 300). Missing: a third node, the cross-node admin-management flow, restart-persistence assertions, and any messagebus-only no-TUN three-way scenario as named in the issue.Proposed persisted-state model layer (design only — not implemented this run)
User decision: one consolidated
topics.toml+ oneadmin.toml; reuseTopicConfigdirectly (no DTO); admin lives in the daemon.Rust shape (design only, no code in this run):
mycelium_daemon::DaemonStategrows two fields —topics: TopicConfigandadmin: AdminConfig— and two save methods —save_topics(),save_admin()— mirroring the existingsave_config()/save_peers()(atomic tmp+rename, mode 0o644).load_or_initreads each file with the existing "missing → write defaults → continue" pattern. Engine boot usesstate.topicsto seed its in-memoryArc<RwLock<TopicConfig>>.TopicConfigstays inmycelium_engineand is reused as the on-disk shape (it already derives serde) — no DTO drift.AdminConfigis a new type inmycelium_daemon(not the engine; admin is an RPC-authorization concern, not a routing concern).addTopic,setTopicForwardSocket,addTopicPubkeySource,admin.add, etc.) updates the in-memory copy then callsstate.save_*()before returning. Persist failure → return RPC error, do not leave drift.Implementation Plan
Step 1: Author
docs/messagebus.md— single entry point and gap matrixFiles:
docs/messagebus.mdtopics.toml(proposed) andadmin.toml(proposed) alongside existingconfig.toml/peers.toml.admin.md,socket_proxy.md,state_persistence.md).message.md→state_persistence.md→topic_configuration.md→admin.md→socket_proxy.md→e2e_messagebus_test.md.crates/mycelum_messagebus/message_bus_docs.md(note: the crate name is misspelled — out of scope to rename, flag it in Notes).Dependencies: none.
Step 2: Refresh
docs/message.mdFiles:
docs/message.mdVecDeque, no on-disk persistence (citecrates/mycelium_engine/src/message.rs:541-700).DEFAULT_MESSAGE_TRY_DURATION = 300s,RETRANSMISSION_DELAY = 100ms,REPLY_SUBSCRIBER_CLEAR_DELAY = 60s,SOCKET_REPLY_TIMEOUT = 5swith citations.messagebus.md.state_persistence.md,topic_configuration.md,admin.md,socket_proxy.md.Dependencies: Step 1.
Step 2a: Author
docs/state_persistence.md— the model layerFiles:
docs/state_persistence.md$MYCELIUM_STATE_DIR/, files and modes (priv_key.bin 0o640, config/peers.toml 0o644, dir 0o700), atomic tmp+rename pattern (citecrates/mycelium_daemon/src/state.rs:140-213). Make explicit: topic config is NOT persisted today — every RPC mutation is lost on restart.DaemonStateextension shape,AdminConfiglocation, "RPC writes through to disk" rule, atomic-write reuse, error contract on persist failure. Design only — no code in this run.topics.tomland not many": topic name is the natural primary key; subnet/pubkey ACL, inboundforward_socket, and outbound socket all hang off the same topic.TopicConfigand not a DTO": already serde-derived; eliminates drift; engine stays the source of truth for the wire shape.herolib_openrpc_authorize).admin.md,socket_proxy.md,topic_configuration.md.Dependencies: Step 1.
Step 3: Update
docs/topic_configuration.mdFiles:
docs/topic_configuration.mdcrates/mycelium_engine/src/message/topic.rs:13-49andcrates/mycelium_engine/src/message.rs:703-728).forward_socketis inbound-only (topic → local UDS); point atsocket_proxy.mdfor the proposed outbound mirror.state_persistence.mdand stating that topic config is not persisted today; all mutations are lost on restart.messagebus.md,socket_proxy.md,state_persistence.md.Dependencies: Step 1, Step 2a.
Step 4: Author
docs/admin.mdFiles:
docs/admin.mdADMIN_SECRETSIP whitelist (citecrates/mycelium_ui/src/admin_secrets.rs:1-60andcrates/mycelium_ui/src/main.rs:41,98-107). Make explicit: UI-only, has nothing to do with mycelium pubkeys, daemon RPC is unauthenticated.$MYCELIUM_STATE_DIR/admin.toml;mycelium_daemon::AdminConfig; RPCadmin.list / admin.add / admin.remove; authorize step in dispatcher; identity surfaced for message-bus-arrived requests via source pubkey onReceivedMessage; "implicit local admin" for the local UDS. Referenceherolib_openrpc_authorize.Dependencies: Step 1, Step 2a.
Step 5: Author
docs/socket_proxy.mdFiles:
docs/socket_proxy.mdforward_socket— RPC names, engine flow, timeouts. Citecrates/mycelium_engine/src/message.rs:580-650,crates/mycelium_engine/src/message/topic.rs:13-49,crates/mycelium_api/src/rpc.rs:318-330.outbound_socket = { path = "...", dst = { pk = "..." | ip = "..." } }nested under[topics.<name>]in the proposedtopics.toml(cross-linkstate_persistence.md), (b) RPC surfaceaddTopicOutboundSocket/removeTopicOutboundSocket/getTopicOutboundSocket, (c) lifecycle (one accept loop per configured outbound socket; per-connection mapping of myceliumMessageId→ local stream for reply routing), (d) error behavior. No Rust.proxy.rs": one-paragraph contrast —crates/mycelium_engine/src/proxy.rsis a SOCKS5 TCP forwarder, not the requested per-topic UDS feature; citedocs/proxy.md.Dependencies: Step 1, Step 2a, Step 3.
Step 6: Author
docs/e2e_messagebus_test.mdFiles:
docs/e2e_messagebus_test.mdmycelium_network_test1..3,no_tun = true, isolated state dirs/socket dirs/TCP ports (citecrates/mycelium_e2e/tests/two_node_linux.rs:62-77).pushMessage/popMessageround-trip across all three pairs; topic add + subnet whitelist over RPC.setTopicForwardSocketagainst node 3 over the message bus, node 3 enforces.forward_socketfor the matching topic, node 3 echoes, node 1 reads the reply on its UDS.topics.toml/admin.toml).mycelium_sdkcalls and assertion shape, mirroring the structure of the two-node test. No Rust written.Dependencies: Step 2a, Step 4, Step 5.
Step 7: Cross-link from
CLAUDE.mdandREADME.mdFiles:
CLAUDE.md,README.mdCLAUDE.md: short "Message-bus surface" section after "Hero Socket Compliance", linking todocs/messagebus.md,docs/state_persistence.md,docs/admin.md,docs/socket_proxy.md,docs/e2e_messagebus_test.md.README.md: add the four new doc paths under existing docs links.Dependencies: all prior steps.
Acceptance Criteria
docs/messagebus.mdexists with an EXISTS/PARTIAL/MISSING table for every issue capability and at least onecrates/...:linecitation per "EXISTS"/"PARTIAL" row.docs/state_persistence.mdlists every remembered item, distinguishes existing files (config.toml,peers.toml,priv_key.bin) from proposed (topics.toml,admin.toml), shows the proposedDaemonStateshape, and explains why onetopics.tomland why admin lives in the daemon.docs/topic_configuration.mdmakes clear ACL is subnet-only andforward_socketis inbound-only, and points atstate_persistence.mdfor the persistence story.docs/admin.mddistinguishes UI-tierADMIN_SECRETSfrom the proposed daemon-level pubkey admin model persisted inadmin.toml.docs/socket_proxy.mddocuments inbound (existing) and outbound (proposed nested undertopics.toml), and disambiguates from the SOCKS5 proxy.docs/e2e_messagebus_test.mdlists phases A–D with explicit dependencies, including a restart-persistence phase.CLAUDE.mdandREADME.mdlink to the new docs.Notes
crates/mycelum_messagebus/message_bus_docs.mdis internal design notes and remains authoritative for the current engine surface; the new top-leveldocs/messagebus.mdis broader (covers admin, persistence, outbound proxy too).crates/mycelum_messagebus/is misspelled (should bemycelium_*). Out of scope for this run; flagged for a follow-up.docs/message.md:74already states that messages received with no listener are queued — the doc should be sharper that this is in-memory only, lost on restart.forward_socketin the topic config so inbound and outbound share one mental model; persistence is one consolidatedtopics.toml, not split.crates/<crate>/<path>:<line>citations so a future implementer can grep straight to the spot.Implementation Spec for Issue #44
Scope of this run: documentation + gap analysis only. No Rust changes.
Objective
Document mycelium_network's existing message-layer features and produce an unambiguous gap analysis against the asks in this issue. After this run, readers must be able to tell exactly which capabilities exist today (with code citations), which are partial, and which are missing — and what the proposed design for the missing pieces (persisted state model, admin layer, outbound socket proxy) looks like.
Requirements
/hero_sockets).mycelium_network_test1..3, no TUN, message-bus only)..tomlfiles under$MYCELIUM_STATE_DIR/, with a clean model layer.Files to Modify/Create
docs/messagebus.md— new, top-level "use mycelium_network as a message layer" guide. EXISTS/PARTIAL/MISSING matrix with file:line citations. Indexes the other docs.docs/message.md— update. Queue retention semantics (in-memory only, lost on restart), reply-correlation timeouts, limits/gaps section.docs/state_persistence.md— new. The model layer for everything we remember. Documents the existingDaemonState(priv_key.bin, config.toml, peers.toml) and the proposed additions:topics.tomlandadmin.toml. Atomic tmp+rename convention, file modes, RPC-write-through behavior.docs/topic_configuration.md— update. Add "Limits today" (subnet-only ACL), make clearforward_socketis inbound-only, add a "Persistence" section pointing atstate_persistence.md.docs/admin.md— new. Section 1: existing UI-tierADMIN_SECRETSIP whitelist. Section 2: what the issue asks for. Section 3: proposed daemon-level admin model withadmin.toml, RPC surface, authorize step.docs/socket_proxy.md— new. Section 1: inboundforward_socket(exists). Section 2: outbound socket → topic (proposed, nested under[topics.<name>]intopics.toml). Section 3: contrast with the existing SOCKS5proxy.rsso they aren't conflated.docs/e2e_messagebus_test.md— new. 3-node test plan with phases A (today), B (admin model), C (outbound proxy), D (restart persistence).CLAUDE.md— update. Add a "Message-bus surface" section linking to the new docs.README.md— update. Add the new doc paths under existing docs links.Current State (audit summary)
Topic messaging — EXISTS. Wire types in
crates/mycelium_api/src/message.rs, engine APIpush_message/reply_message/get_messageincrates/mycelium_engine/src/message.rs:541-700. Topic byte-string max 255 bytes. Inbox is an in-memoryVecDeque<ReceivedMessage>— no on-disk persistence, lost on restart.popMessagepeek/pop/timeout/topic filter exposed via OpenRPC (crates/mycelium_api/openrpc.json, dispatchercrates/mycelium_api/src/rpc.rs:277).Per-topic source whitelists — PARTIAL. Subnet-based whitelist fully implemented in
crates/mycelium_engine/src/message/topic.rs:51-118(TopicConfig,TopicWhitelistConfig); enforcement incrates/mycelium_engine/src/message.rs:703-728(topic_allowedcheckssrc: IpAddr). RPC:addTopic,removeTopic,addTopicSource,removeTopicSource,getTopicSources,getDefaultTopicAction,setDefaultTopicAction. Gap: no pubkey-based whitelist. The pubkey is available later in receive (mycelium_engine/src/message.rs:543-558) but not consulted bytopic_allowed. Adding it means extendingTopicWhitelistConfigwithpubkeys: Vec<PublicKey>and adding RPC methodsaddTopicPubkeySource/removeTopicPubkeySource.Topic → local-socket termination (inbound) — EXISTS.
TopicWhitelistConfig::forward_socket: Option<PathBuf>(crates/mycelium_engine/src/message/topic.rs:13-49); dispatcher incrates/mycelium_engine/src/message.rs:580-650writes message data to the UDS, reads a reply with 5s timeout, sends it back viareply_message. RPC:getTopicForwardSocket,setTopicForwardSocket,removeTopicForwardSocket.Local-socket → remote-topic outbound proxy — MISSING. No code listens on a local UDS and forwards writes onto the message bus.
grep -rn UnixListenerfinds only themycelium_uiHTTP socket and the RPC UDS. Proposed: new daemon component (sibling offorward_socketin topic config) that takes(topic, dst_pubkey_or_ip, local_socket_path)and runs an accept loop; each inbound write becomes apushMessage; replies route back to the originating UDS connection (correlate via myceliumMessageId). RPC mirror:addTopicOutboundSocket(topic, dst, path),removeTopicOutboundSocket(topic),getTopicOutboundSocket(topic). Stored on disk as a nestedoutbound_socket = { path, dst }field under[topics.<name>]in the proposedtopics.toml.Admin / pubkey-based authorization — MISSING (at the daemon).
MyceliumRpctrait (crates/mycelium_api/src/rpc.rs:248-377) has no auth context. UDS server (crates/mycelium_api/src/rpc/unix.rs) accepts every caller. The only existing admin gate is the UI-tierADMIN_SECRETSIP whitelist incrates/mycelium_ui/src/admin_secrets.rs— gates which TCP clients can reach the UI, not which mycelium peers can RPC the daemon. Proposed: daemon-sideadmin_pubkeys: Vec<PublicKey>config (with RPCadmin.list/add/remove); admin-only RPC methods routed through an authorize step that compares the caller's mycelium source pubkey to the admin set. Persisted asadmin.tomlin$MYCELIUM_STATE_DIR/. Pattern reference:herolib_openrpc_authorize.OpenRPC method surface for messages — EXISTS.
crates/mycelium_api/openrpc.jsonand traitcrates/mycelium_api/src/rpc.rs:248-330:pushMessage,popMessage,pushMessageReply,getMessageInfo,getDefaultTopicAction,setDefaultTopicAction,getTopics,addTopic,removeTopic,getTopicSources,addTopicSource,removeTopicSource,getTopicForwardSocket,setTopicForwardSocket,removeTopicForwardSocket. All gated behind themessageCargo feature. No RPC for: per-pubkey topic ACL, outbound socket forwarding, admin pubkey management.Persisted state — PARTIAL. Today
DaemonState(crates/mycelium_daemon/src/state.rs) ownspriv_key.bin(mode 0o640),config.tomlandpeers.toml(mode 0o644), with atomic tmp+rename writes; state dir mode 0o700. Topic config (TopicConfigin the engine) lives only in memory — everyaddTopic/addTopicSource/setTopicForwardSocketmutation is lost on restart. The legacy path (crates/mycelium_daemon/src/legacy_runner.rs:35-40) reads a stand-alone topic toml read-only at startup, which is not the model we want going forward.UI coverage — PARTIAL. Templates exist for messages and topics:
crates/mycelium_ui/templates/messages.html(push/pop) andcrates/mycelium_ui/templates/topics.html(default action, add topic, configure subnets, configure forward socket). Thecrates/mycelium_ui/templates/admin.htmlpage configures the UI's ownADMIN_SECRETSIP list, not a daemon-level admin set. Missing UI: pubkey-based topic ACLs, outbound socket forwarding, remote-node management.E2E test coverage — PARTIAL.
crates/mycelium_e2e/tests/two_node_linux.rs(325 lines) spins up twomycelium_serverchildren with isolatedMYCELIUM_STATE_DIR/HERO_SOCKET_DIR/ TCP ports, peers them, exercises peers/routes/topics/messaging throughmycelium_sdk(add_topicat line 260,push_messageat line 290,pop_messageat line 300). Missing: a third node, the cross-node admin-management flow, restart-persistence assertions, and any messagebus-only no-TUN three-way scenario as named in the issue.Proposed persisted-state model layer (design only — not implemented this run)
User decision: one consolidated
topics.toml+ oneadmin.toml; reuseTopicConfigdirectly (no DTO); admin lives in the daemon.Rust shape (design only, no code in this run):
mycelium_daemon::DaemonStategrows two fields —topics: TopicConfigandadmin: AdminConfig— and two save methods —save_topics(),save_admin()— mirroring the existingsave_config()/save_peers()(atomic tmp+rename, mode 0o644).load_or_initreads each file with the existing "missing → write defaults → continue" pattern. Engine boot usesstate.topicsto seed its in-memoryArc<RwLock<TopicConfig>>.TopicConfigstays inmycelium_engineand is reused as the on-disk shape (it already derives serde) — no DTO drift.AdminConfigis a new type inmycelium_daemon(not the engine; admin is an RPC-authorization concern, not a routing concern).addTopic,setTopicForwardSocket,addTopicPubkeySource,admin.add, etc.) updates the in-memory copy then callsstate.save_*()before returning. Persist failure → return RPC error, do not leave drift.Implementation Plan
Step 1: Author
docs/messagebus.md— single entry point and gap matrixFiles:
docs/messagebus.mdtopics.toml(proposed) andadmin.toml(proposed) alongside existingconfig.toml/peers.toml.admin.md,socket_proxy.md,state_persistence.md).message.md→state_persistence.md→topic_configuration.md→admin.md→socket_proxy.md→e2e_messagebus_test.md.crates/mycelum_messagebus/message_bus_docs.md(note: the crate name is misspelled — out of scope to rename, flag it in Notes).Dependencies: none.
Step 2: Refresh
docs/message.mdFiles:
docs/message.mdVecDeque, no on-disk persistence (citecrates/mycelium_engine/src/message.rs:541-700).DEFAULT_MESSAGE_TRY_DURATION = 300s,RETRANSMISSION_DELAY = 100ms,REPLY_SUBSCRIBER_CLEAR_DELAY = 60s,SOCKET_REPLY_TIMEOUT = 5swith citations.messagebus.md.state_persistence.md,topic_configuration.md,admin.md,socket_proxy.md.Dependencies: Step 1.
Step 2a: Author
docs/state_persistence.md— the model layerFiles:
docs/state_persistence.md$MYCELIUM_STATE_DIR/, files and modes (priv_key.bin 0o640, config/peers.toml 0o644, dir 0o700), atomic tmp+rename pattern (citecrates/mycelium_daemon/src/state.rs:140-213). Make explicit: topic config is NOT persisted today — every RPC mutation is lost on restart.DaemonStateextension shape,AdminConfiglocation, "RPC writes through to disk" rule, atomic-write reuse, error contract on persist failure. Design only — no code in this run.topics.tomland not many": topic name is the natural primary key; subnet/pubkey ACL, inboundforward_socket, and outbound socket all hang off the same topic.TopicConfigand not a DTO": already serde-derived; eliminates drift; engine stays the source of truth for the wire shape.herolib_openrpc_authorize).admin.md,socket_proxy.md,topic_configuration.md.Dependencies: Step 1.
Step 3: Update
docs/topic_configuration.mdFiles:
docs/topic_configuration.mdcrates/mycelium_engine/src/message/topic.rs:13-49andcrates/mycelium_engine/src/message.rs:703-728).forward_socketis inbound-only (topic → local UDS); point atsocket_proxy.mdfor the proposed outbound mirror.state_persistence.mdand stating that topic config is not persisted today; all mutations are lost on restart.messagebus.md,socket_proxy.md,state_persistence.md.Dependencies: Step 1, Step 2a.
Step 4: Author
docs/admin.mdFiles:
docs/admin.mdADMIN_SECRETSIP whitelist (citecrates/mycelium_ui/src/admin_secrets.rs:1-60andcrates/mycelium_ui/src/main.rs:41,98-107). Make explicit: UI-only, has nothing to do with mycelium pubkeys, daemon RPC is unauthenticated.$MYCELIUM_STATE_DIR/admin.toml;mycelium_daemon::AdminConfig; RPCadmin.list / admin.add / admin.remove; authorize step in dispatcher; identity surfaced for message-bus-arrived requests via source pubkey onReceivedMessage; "implicit local admin" for the local UDS. Referenceherolib_openrpc_authorize.Dependencies: Step 1, Step 2a.
Step 5: Author
docs/socket_proxy.mdFiles:
docs/socket_proxy.mdforward_socket— RPC names, engine flow, timeouts. Citecrates/mycelium_engine/src/message.rs:580-650,crates/mycelium_engine/src/message/topic.rs:13-49,crates/mycelium_api/src/rpc.rs:318-330.outbound_socket = { path = "...", dst = { pk = "..." | ip = "..." } }nested under[topics.<name>]in the proposedtopics.toml(cross-linkstate_persistence.md), (b) RPC surfaceaddTopicOutboundSocket/removeTopicOutboundSocket/getTopicOutboundSocket, (c) lifecycle (one accept loop per configured outbound socket; per-connection mapping of myceliumMessageId→ local stream for reply routing), (d) error behavior. No Rust.proxy.rs": one-paragraph contrast —crates/mycelium_engine/src/proxy.rsis a SOCKS5 TCP forwarder, not the requested per-topic UDS feature; citedocs/proxy.md.Dependencies: Step 1, Step 2a, Step 3.
Step 6: Author
docs/e2e_messagebus_test.mdFiles:
docs/e2e_messagebus_test.mdmycelium_network_test1..3,no_tun = true, isolated state dirs/socket dirs/TCP ports (citecrates/mycelium_e2e/tests/two_node_linux.rs:62-77).pushMessage/popMessageround-trip across all three pairs; topic add + subnet whitelist over RPC.setTopicForwardSocketagainst node 3 over the message bus, node 3 enforces.forward_socketfor the matching topic, node 3 echoes, node 1 reads the reply on its UDS.topics.toml/admin.toml).mycelium_sdkcalls and assertion shape, mirroring the structure of the two-node test. No Rust written.Dependencies: Step 2a, Step 4, Step 5.
Step 7: Cross-link from
CLAUDE.mdandREADME.mdFiles:
CLAUDE.md,README.mdCLAUDE.md: short "Message-bus surface" section after "Hero Socket Compliance", linking todocs/messagebus.md,docs/state_persistence.md,docs/admin.md,docs/socket_proxy.md,docs/e2e_messagebus_test.md.README.md: add the four new doc paths under existing docs links.Dependencies: all prior steps.
Acceptance Criteria
docs/messagebus.mdexists with an EXISTS/PARTIAL/MISSING table for every issue capability and at least onecrates/...:linecitation per "EXISTS"/"PARTIAL" row.docs/state_persistence.mdlists every remembered item, distinguishes existing files (config.toml,peers.toml,priv_key.bin) from proposed (topics.toml,admin.toml), shows the proposedDaemonStateshape, and explains why onetopics.tomland why admin lives in the daemon.docs/topic_configuration.mdmakes clear ACL is subnet-only andforward_socketis inbound-only, and points atstate_persistence.mdfor the persistence story.docs/admin.mddistinguishes UI-tierADMIN_SECRETSfrom the proposed daemon-level pubkey admin model persisted inadmin.toml.docs/socket_proxy.mddocuments inbound (existing) and outbound (proposed nested undertopics.toml), and disambiguates from the SOCKS5 proxy.docs/e2e_messagebus_test.mdlists phases A–D with explicit dependencies, including a restart-persistence phase.CLAUDE.mdandREADME.mdlink to the new docs.Notes
crates/mycelum_messagebus/message_bus_docs.mdis internal design notes and remains authoritative for the current engine surface; the new top-leveldocs/messagebus.mdis broader (covers admin, persistence, outbound proxy too).crates/mycelum_messagebus/is misspelled (should bemycelium_*). Out of scope for this run; flagged for a follow-up.docs/message.md:74already states that messages received with no listener are queued — the doc should be sharper that this is in-memory only, lost on restart.forward_socketin the topic config so inbound and outbound share one mental model; persistence is one consolidatedtopics.toml, not split.crates/<crate>/<path>:<line>citations so a future implementer can grep straight to the spot.Verification report
This run was scoped to documentation + gap analysis only — no Rust source files were touched. There is no
cargo testoutput to share. Instead, the verification below confirms the docs were written, cross-link correctly, and every code citation points at a real file:line.Files written (5 new + 4 modified)
docs/messagebus.mddocs/state_persistence.mddocs/admin.mddocs/socket_proxy.mddocs/e2e_messagebus_test.mddocs/message.mddocs/topic_configuration.mdCLAUDE.mdREADME.mdCode-citation spot checks
The plan agent and each writing agent verified citations by reading the cited files. Examples (all confirmed against the working tree):
crates/mycelium_engine/src/message/topic.rs:13-49—TopicWhitelistConfig(subnet whitelist +forward_socket: Option<PathBuf>).crates/mycelium_engine/src/message.rs:541-700—push_message/reply_message/get_messageAPI and inboxVecDeque.crates/mycelium_engine/src/message.rs:703-728—topic_allowed(subnet check, no pubkey path).crates/mycelium_engine/src/message.rs:59—SOCKET_REPLY_TIMEOUT = 5 s.crates/mycelium_api/src/rpc.rs:248-330—MyceliumRpctrait method surface (no auth context).crates/mycelium_api/src/rpc/unix.rs:49-82, 172-241— UDS server accepts every caller, no identity check.crates/mycelium_ui/src/admin_secrets.rsandcrates/mycelium_ui/src/main.rs:36-41, 86-92, 96-110— UI-tierADMIN_SECRETSIP whitelist (UI-only).crates/mycelium_daemon/src/state.rs:22-24, 50-58, 136-148, 151-164, 180-203— existingDaemonState(file constants,MYCELIUM_STATE_DIRresolution, save methods, dir mode, atomic writers).crates/mycelium_daemon/src/legacy_runner.rs:35-40— legacy stand-alone topic toml read at startup, no write-back.crates/mycelium_e2e/tests/two_node_linux.rs:30-322— structural template for the proposed 3-node test.Cross-links
Each new doc has a "See also" footer linking to the others, and each updated doc gained the same. The
messagebus.mdcapability matrix points each PARTIAL/MISSING row at the relevant deeper doc.CLAUDE.mdandREADME.mdlink to all five new docs.Out of scope (deliberate)
topics.toml/admin.tomlfiles, theTopicWhitelistConfig::pubkeysextension, theoutbound_socketfield, theaddTopicPubkeySource/addTopicOutboundSocket/admin.*RPC methods, and the dispatcher authorize step are all documented as proposals only.crates/mycelium_e2e/tests/three_node_linux.rsdoes not exist; the plan indocs/e2e_messagebus_test.mdis a written specification of what such a test would do across phases A (basic messaging — achievable today) and B/C/D (blocked on the proposed admin / outbound-proxy / persistence work).crates/mycelum_messagebus/is flagged indocs/messagebus.mdNotes as a follow-up; not renamed.Pre-existing changes in the working tree
The working tree contains many other modified files (Rust sources under
crates/,Cargo.toml, scripts, etc.) — these were already present at the start of this session and are unrelated to this run. The single commit at the end of this skill will only stage the 9 doc files listed above.Verification report
This run was scoped to documentation + gap analysis only — no Rust source files were touched. There is no
cargo testoutput to share. Instead, the verification below confirms the docs were written, cross-link correctly, and every code citation points at a real file:line.Files written (5 new + 4 modified)
docs/messagebus.mddocs/state_persistence.mddocs/admin.mddocs/socket_proxy.mddocs/e2e_messagebus_test.mddocs/message.mddocs/topic_configuration.mdCLAUDE.mdREADME.mdCode-citation spot checks
The plan agent and each writing agent verified citations by reading the cited files. Examples (all confirmed against the working tree):
crates/mycelium_engine/src/message/topic.rs:13-49—TopicWhitelistConfig(subnet whitelist +forward_socket: Option<PathBuf>).crates/mycelium_engine/src/message.rs:541-700—push_message/reply_message/get_messageAPI and inboxVecDeque.crates/mycelium_engine/src/message.rs:703-728—topic_allowed(subnet check, no pubkey path).crates/mycelium_engine/src/message.rs:59—SOCKET_REPLY_TIMEOUT = 5 s.crates/mycelium_api/src/rpc.rs:248-330—MyceliumRpctrait method surface (no auth context).crates/mycelium_api/src/rpc/unix.rs:49-82, 172-241— UDS server accepts every caller, no identity check.crates/mycelium_ui/src/admin_secrets.rsandcrates/mycelium_ui/src/main.rs:36-41, 86-92, 96-110— UI-tierADMIN_SECRETSIP whitelist (UI-only).crates/mycelium_daemon/src/state.rs:22-24, 50-58, 136-148, 151-164, 180-203— existingDaemonState(file constants,MYCELIUM_STATE_DIRresolution, save methods, dir mode, atomic writers).crates/mycelium_daemon/src/legacy_runner.rs:35-40— legacy stand-alone topic toml read at startup, no write-back.crates/mycelium_e2e/tests/two_node_linux.rs:30-322— structural template for the proposed 3-node test.Cross-links
Each new doc has a "See also" footer linking to the others, and each updated doc gained the same. The
messagebus.mdcapability matrix points each PARTIAL/MISSING row at the relevant deeper doc.CLAUDE.mdandREADME.mdlink to all five new docs.Out of scope (deliberate)
topics.toml/admin.tomlfiles, theTopicWhitelistConfig::pubkeysextension, theoutbound_socketfield, theaddTopicPubkeySource/addTopicOutboundSocket/admin.*RPC methods, and the dispatcher authorize step are all documented as proposals only.crates/mycelium_e2e/tests/three_node_linux.rsdoes not exist; the plan indocs/e2e_messagebus_test.mdis a written specification of what such a test would do across phases A (basic messaging — achievable today) and B/C/D (blocked on the proposed admin / outbound-proxy / persistence work).crates/mycelum_messagebus/is flagged indocs/messagebus.mdNotes as a follow-up; not renamed.Pre-existing changes in the working tree
The working tree contains many other modified files (Rust sources under
crates/,Cargo.toml, scripts, etc.) — these were already present at the start of this session and are unrelated to this run. The single commit at the end of this skill will only stage the 9 doc files listed above.Verification report
This run was scoped to documentation + gap analysis only — no Rust source files were touched. There is no
cargo testoutput to share. Instead, the verification below confirms the docs were written, cross-link correctly, and every code citation points at a real file:line.Files written (5 new + 4 modified)
docs/messagebus.mddocs/state_persistence.mddocs/admin.mddocs/socket_proxy.mddocs/e2e_messagebus_test.mddocs/message.mddocs/topic_configuration.mdCLAUDE.mdREADME.mdCode-citation spot checks
The plan agent and each writing agent verified citations by reading the cited files. Examples (all confirmed against the working tree):
crates/mycelium_engine/src/message/topic.rs:13-49—TopicWhitelistConfig(subnet whitelist +forward_socket: Option<PathBuf>).crates/mycelium_engine/src/message.rs:541-700—push_message/reply_message/get_messageAPI and inboxVecDeque.crates/mycelium_engine/src/message.rs:703-728—topic_allowed(subnet check, no pubkey path).crates/mycelium_engine/src/message.rs:59—SOCKET_REPLY_TIMEOUT = 5 s.crates/mycelium_api/src/rpc.rs:248-330—MyceliumRpctrait method surface (no auth context).crates/mycelium_api/src/rpc/unix.rs:49-82, 172-241— UDS server accepts every caller, no identity check.crates/mycelium_ui/src/admin_secrets.rsandcrates/mycelium_ui/src/main.rs:36-41, 86-92, 96-110— UI-tierADMIN_SECRETSIP whitelist (UI-only).crates/mycelium_daemon/src/state.rs:22-24, 50-58, 136-148, 151-164, 180-203— existingDaemonState(file constants,MYCELIUM_STATE_DIRresolution, save methods, dir mode, atomic writers).crates/mycelium_daemon/src/legacy_runner.rs:35-40— legacy stand-alone topic toml read at startup, no write-back.crates/mycelium_e2e/tests/two_node_linux.rs:30-322— structural template for the proposed 3-node test.Cross-links
Each new doc has a "See also" footer linking to the others, and each updated doc gained the same. The
messagebus.mdcapability matrix points each PARTIAL/MISSING row at the relevant deeper doc.CLAUDE.mdandREADME.mdlink to all five new docs.Out of scope (deliberate)
topics.toml/admin.tomlfiles, theTopicWhitelistConfig::pubkeysextension, theoutbound_socketfield, theaddTopicPubkeySource/addTopicOutboundSocket/admin.*RPC methods, and the dispatcher authorize step are all documented as proposals only.crates/mycelium_e2e/tests/three_node_linux.rsdoes not exist; the plan indocs/e2e_messagebus_test.mdis a written specification of what such a test would do across phases A (basic messaging — achievable today) and B/C/D (blocked on the proposed admin / outbound-proxy / persistence work).crates/mycelum_messagebus/is flagged indocs/messagebus.mdNotes as a follow-up; not renamed.Pre-existing changes in the working tree
The working tree contains many other modified files (Rust sources under
crates/,Cargo.toml, scripts, etc.) — these were already present at the start of this session and are unrelated to this run. The single commit at the end of this skill will only stage the 9 doc files listed above.Final summary
Scope delivered: documentation + gap analysis. No Rust code changes.
What landed in this run
5 new docs:
docs/messagebus.md— top-level entry point with a capability matrix (EXISTS / PARTIAL / MISSING) for every ask in this issue, each row backed bycrates/<crate>/<file>:<line>citations.docs/state_persistence.md— the model layer for everything we remember on disk. Documents the existingDaemonState(priv_key.bin,config.toml,peers.toml) and the proposed additions (topics.toml,admin.toml). Reuses engineTopicConfigdirectly as the on-disk shape; daemon-onlyAdminConfig. Atomic tmp+rename writes; RPC write-through rule.docs/admin.md— distinguishes the existing UI-tierADMIN_SECRETSIP whitelist (UI-only, IP-keyed) from the proposed daemon-level pubkey admin model. Authorize step in the dispatcher;admin.list/add/removeRPC; "implicit local admin" for the local UDS.docs/socket_proxy.md— inboundforward_socket(existing) vs proposed outbound local-UDS → remote-topic proxy nested under[topics.<name>]intopics.toml. Disambiguates from the existing SOCKS5 proxy (docs/proxy.md).docs/e2e_messagebus_test.md— 3-node test plan (mycelium_network_test1..3,no_tun = true). Phase A (basic messaging — achievable today), Phase B (admin management — blocked on admin model), Phase C (outbound socket — blocked on socket_proxy proposal), Phase D (restart persistence — blocked on persistence proposal).4 docs updated:
docs/message.md— added "Queue retention", "Reply correlation and timeouts" (with verified constants), "Limits and gaps", "See also".docs/topic_configuration.md— added "Limits today" (subnet-only ACL), madeforward_socketdirection explicit (inbound only), added "Persistence" section, tidied stray triple-backticks.CLAUDE.md— new "Message-bus surface" section linking to the five new docs.README.md— new "Documentation" section linking to the five new docs.Key design decisions captured in the docs
topics.toml+ oneadmin.tomlunder$MYCELIUM_STATE_DIR/. Topic name is the natural primary key; per-topic subnet ACL, proposed pubkey ACL, inboundforward_socket, and proposed outbound socket all hang off the same topic.mycelium_engine::message::topic::TopicConfigas the on-disk shape — it already derives serde, no DTO drift.AdminConfiglives inmycelium_daemon, not the engine. Admin is an RPC-authorization concern, not a routing concern (matchesherolib_openrpc_authorize).state.save_*()before returning. Persist failure → return RPC error.Gap summary (capabilities missing today)
ADMIN_SECRETSis UI-only).forward_socketexists today).priv_key.bin/config.toml/peers.tomlare persisted today).Each is documented with proposed RPC surface, on-disk shape, and architecture sketch — implementation is a separate run.
Out of scope
crates/mycelum_messagebus/is flagged inmessagebus.mdNotes; not renamed.admin.mdas a separate decision.Verification
See the verification report earlier in the thread — files, sizes, citation spot checks, and cross-link confirmation.
Implementation complete — six phases shipped
All deliverables from the spec earlier in this thread are now on
development_crate_layout. Six commits, each green (cargo check --workspaceclean, lib tests pass) before the next began.Commits
81e2bf4f7521f7eb112758fd1902ef24b0070b777a(Phases were shipped in order 1, 2, 4, 3, 5, 6 — Phase 4 went before 3 because it is independent of the admin model and let us defer the harder design questions.)
What landed
Persistence (Phase 1) — Two new files alongside
config.toml/peers.toml:DaemonStategainstopics: TopicConfig+admin: AdminConfigfields with atomicsave_topics()/save_admin(). Engine boot seedsArc<RwLock<TopicConfig>>from disk. Every existing mutating topic RPC writes through to disk before returning; persist failure surfaces asRpcError::Domain { code: -32099 }. The engine'sTopicConfigis reused directly as the on-disk shape — no DTO drift.Pubkey ACL (Phase 2) —
TopicWhitelistConfiggainspubkeys: Vec<PublicKey>next tosubnets. A topic is allowed when EITHER the source IP matches a subnet entry OR the source pubkey matches a pubkey entry. New RPC:addTopicPubkeySource/removeTopicPubkeySource/getTopicPubkeySources. Wired through the SDK, CLI (topic pubkey-source), and the topics UI.Outbound socket proxy (Phase 4) — Per-topic accept loop bound to a local UDS. Each accepted connection reads a request body to EOF, calls
node.push_message(... subscribe_reply=true), awaits the reply watcher with a 5-minute timeout, writes the reply back, half-shuts. NewOutboundSocketManagerowns lifecycle (start / stop / shutdown / replay-on-boot fromtopics.toml). RPC:setTopicOutboundSocket/removeTopicOutboundSocket/getTopicOutboundSocket. UDS bind failure rolls back the engine state and returns-32030 outbound_socket_bind_failed. Stale socket files are cleaned on bind and on listener exit.Admin model + bus RPC (Phase 3) — Reserved topic
mycelium.admincarries raw JSON-RPC 2.0 requests; the receiver dispatches them through the sameMyceliumRpcimpl with a bus-aware authorizer. Caller identity = sender pubkey fromReceivedMessage.src_pk. NewAuthorizeMethodtrait withauthorize+check_lockouthooks;dispatch_with_authwraps the existingdispatch. Local UDS / TCP keep using the no-op authorizer (implicit-local-admin); the bus path usesBusAuthorizerSnapshotwhich checksAdminConfig.allow(...)againstmethod_patterns(default-deny — empty patterns reject every bus call). Lockout protection refusesadmin.removeover the bus when the caller is the only admin; UDS keeps the freedom to wipe locally. RPC surface:admin.list / admin.add / admin.remove / admin.getMethodPatterns / admin.setMethodPatterns. CLI:mycelium_cli admin {list,add,remove,patterns,call}.Three-node e2e (Phase 5) —
crates/mycelium_e2e/tests/three_node_linux.rswith four cases:basic_three_way_messaging— triangle peering, push/pop across all three pairs.pubkey_acl_three_nodes— node 1 with a pubkey-only whitelist for node 3; node 3 messages succeed, node 2 messages drop.admin_over_bus_three_nodes— node 1 admins node 3; node 3's bus addTopic succeeds, node 2's returns -32001.restart_persistence_three_nodes— configures default action + topic + subnet + pubkey + forward_socket + outbound_socket + admin entry, kills node, re-spawns against the sameMYCELIUM_STATE_DIR, asserts every setting survived.Helpers were factored into
tests/common/mod.rs(332 lines) shared by both two-node and three-node tests. Linux-gated; macOS compiles to empty modules.Admin UI (Phase 6) — Two new tabs in
admin.html:admin_pubkeys; editmethod_patternsas one-pattern-per-line textarea.admin.busCallvia the existing/rpcproxy and pretty-prints the JSON-RPC reply.The new daemon RPC
admin.busCall(dst_pubkey, dst_ip, method, params, id?, timeout_secs?)is the primitive that powers the Remote management tab; it's also useful for scripting (the CLI'sadmin callfrom Phase 3 uses the same shape).Tests
cargo test --workspace --lib: 102 tests pass across mycelium_engine (83), mycelium_daemon (14), mycelium_api (4), mycelium_sdk (1).cargo check --workspace --tests: clean. Two- and three-node Linux e2e binaries compile on macOS as empty modules; run on Linux withcargo test -p mycelium_e2e.Open follow-ups (not gating the issue)
docs/admin.md.crates/mycelum_messagebus/is still flagged for a rename indocs/messagebus.mdNotes.admin.busCallitself is implicit-local-admin (anyone with UDS access can use it to drive remote nodes). Not gated further; documented as such on the trait method.Documentation
The doc commits (
02a0876) earlier in this thread are still accurate; the proposal language indocs/admin.md,docs/socket_proxy.md,docs/state_persistence.mdanddocs/e2e_messagebus_test.mddescribes what these commits implement. A doc refresh that flips "proposed" → "implemented" wherever it applies is a small follow-up — easy to do in a single pass once you confirm the implementation matches the design.