hecate_pubsub_server (macula v10.10.1)
View SourcePubSub gen_server wrapping hecate_pubsub state for one realm namespace.
Activates the dormant hecate_pubsub pure-state module by giving it a process identity. One server instance owns the topic-to-subscriber index for a single realm tag; the realm is an opaque 32-byte namespace key, not validated against any authority. Multi-tenancy comes from running multiple servers under different realm tags — the station does not arbitrate which realm tags are "real" (Sprint A: realm identity lives outside infrastructure).
Phase 1 scope (this commit)
State mutations + frame processing only. The publish path builds the signed EVENT frame and returns the matched LOCAL subscribers, but does NOT fan out across the cluster — that requires the Plumtree wire layer (hecate_plumtree) and the DHT topic-discovery integration which land in subsequent commits.
Sequencing
- This commit: server in isolation, no integration with station listener or DHT.
- Next: per-realm-namespace registry under
hecate_overlay_supso the listener can route inbound SUBSCRIBE / UNSUBSCRIBE / EVENT frames to the right server. - Then: Plumtree fan-out for cross-station delivery.
- Then: DHT integration for topic-mesh discovery.
Summary
Functions
Process an inbound EVENT frame received from the wire. Returns the matched local subscribers; the caller delivers.
Generic frame dispatch — handles subscribe / unsubscribe / event uniformly. Returns the matched subscribers for event frames, empty list for subscribe / unsubscribe.
Build a signed EVENT frame for Topic/Payload and return it together with the set of LOCAL subscribers that match. The caller is responsible for handing the frame to the cross-station delivery layer (Plumtree, future commit) and for delivering to the matched local subscribers via the application channel.
Relay an inbound PUBLISH frame from a remote daemon. The server builds an EVENT frame signed by THIS server's identity (intermediate hop re-signing — Phase 1 simplification; Phase 2 tightens to publisher-end-to-end auth via UCAN), preserves the original publisher pubkey + seq inside the EVENT, and returns the matched local subscribers. The caller (typically the peer observer) is responsible for sending EventFrame on each subscriber's peering connection.
Types
-type opts() :: #{realm := <<_:256>>, identity := macula_identity:key_pair()}.
Functions
-spec deliver_event(pid(), macula_frame:frame()) -> [<<_:256>>].
Process an inbound EVENT frame received from the wire. Returns the matched local subscribers; the caller delivers.
-spec process_frame(pid(), <<_:256>>, macula_frame:frame()) -> [<<_:256>>].
Generic frame dispatch — handles subscribe / unsubscribe / event uniformly. Returns the matched subscribers for event frames, empty list for subscribe / unsubscribe.
-spec publish(pid(), binary(), binary()) -> {macula_frame:frame(), [<<_:256>>]}.
Build a signed EVENT frame for Topic/Payload and return it together with the set of LOCAL subscribers that match. The caller is responsible for handing the frame to the cross-station delivery layer (Plumtree, future commit) and for delivering to the matched local subscribers via the application channel.
-spec realm(pid()) -> <<_:256>>.
-spec relay_publish(pid(), macula_frame:frame()) -> {macula_frame:frame(), [<<_:256>>]} | {error, realm_mismatch}.
Relay an inbound PUBLISH frame from a remote daemon. The server builds an EVENT frame signed by THIS server's identity (intermediate hop re-signing — Phase 1 simplification; Phase 2 tightens to publisher-end-to-end auth via UCAN), preserves the original publisher pubkey + seq inside the EVENT, and returns the matched local subscribers. The caller (typically the peer observer) is responsible for sending EventFrame on each subscriber's peering connection.
Returns {error, realm_mismatch} when the publish frame's realm does not match this server's realm. The registry routes by realm so this should never fire in practice — defensive check.
-spec stop(pid()) -> ok.
-spec subscriber_count(pid()) -> non_neg_integer().
-spec topic_count(pid()) -> non_neg_integer().