hecate_pubsub_registry (macula v10.11.0)

View Source

Per-identity registry for hecate_pubsub_server processes.

Holds a RealmTag => pid() map and acts as the dispatch hub for inbound SUBSCRIBE / UNSUBSCRIBE / EVENT frames. New realms are materialised via register/2: the registry spawn-links a hecate_pubsub_server worker for the realm and stores its pid. A linked worker that crashes delivers an `EXIT'' message which the registry traps + uses to clear the entry; a later register/2 yields a fresh server.

Sprint A invariant

Realm tags are opaque 32-byte namespace keys. The registry does NOT validate authenticity — multi-tenancy is structural (one server per tag, no cross-realm leakage). Realm authority lives outside the station per PLAN_DEFERRED_WORK §6.

Multi-identity (PLAN_MULTI_IDENTITY_RELAY §Phase 2)

N identities run inside one BEAM. Each identity has its OWN pubsub_registry, owning its OWN per-realm pubsub_server pool. No cross-identity leakage — a realm tag X under identity A is a different overlay than the same realm tag X under identity B.

Phase 2 also folded the previous hecate_pubsub_server_sup (simple_one_for_one pool) into the registry: the registry spawn-links pubsub_servers itself. Equivalent semantics — they are temporary, the registry's monitor was already doing the bookkeeping that the supervisor would have — minus a module + the pid-passing coordination that splitting them required under per-identity supervision.

Summary

Functions

Route a SUBSCRIBE / UNSUBSCRIBE / EVENT frame for Realm to the matching pubsub_server. Returns the matched local subscribers (empty list for SUBSCRIBE / UNSUBSCRIBE) or {error, not_found} if no server is registered for Realm AND no default_identity was configured at start-up.

Snapshot the realm tags currently materialised under RegistryPid. Used by status pages + tests.

Find the pubsub_server pid for Realm under RegistryPid, or report not_found.

Remove Sub (a subscriber pubkey) from every topic under every realm currently materialised on this registry — i.e. from every live hecate_pubsub_server it owns. A dead server pid (raced against its own EXIT cleanup, see handle_info/2) is skipped rather than treated as an error; the registry's own by_realm/by_pid bookkeeping self-heals on the pending EXIT.

Idempotently start a pubsub_server for Realm under RegistryPid, signing with Identity. If a live server already exists, returns its pid; otherwise spawns a new one and records the mapping. A stale entry pointing at a dead pid is replaced transparently.

Relay an inbound PUBLISH frame for Realm to the matching pubsub_server. The server builds an EVENT frame and returns it together with the local subscribers that should receive it. The caller is responsible for sending EventFrame on each subscriber's peering connection.

Stop the registry. Uses reason shutdown (not the default normal) so the registry's spawn-linked pubsub_servers receive the exit signal and terminate alongside it. Without this, normal exit does not propagate to non-trapping linked workers.

Types

identity/0

-type identity() :: macula_identity:key_pair().

opts/0

-type opts() :: #{identity => identity(), identity_key => term()}.

realm/0

-type realm() :: <<_:256>>.

Functions

dispatch_frame(RegistryPid, Realm, From, Frame)

-spec dispatch_frame(pid(), realm(), <<_:256>>, macula_frame:frame()) ->
                        {ok, [<<_:256>>]} | {error, not_found}.

Route a SUBSCRIBE / UNSUBSCRIBE / EVENT frame for Realm to the matching pubsub_server. Returns the matched local subscribers (empty list for SUBSCRIBE / UNSUBSCRIBE) or {error, not_found} if no server is registered for Realm AND no default_identity was configured at start-up.

**Auto-registration**: when default_identity is set on the registry (the production path under macula_station_identity_sup), an unknown realm is materialised on demand using that identity and the frame is dispatched against the freshly-spawned server. Tests that omit default_identity retain the strict {error, not_found} semantics.

handle_call(Other, From, S)

handle_cast(Msg, S)

handle_info(Info, S)

init(Opts)

list_realms(RegistryPid)

-spec list_realms(pid()) -> [realm()].

Snapshot the realm tags currently materialised under RegistryPid. Used by status pages + tests.

lookup(RegistryPid, Realm)

-spec lookup(pid(), realm()) -> {ok, pid()} | {error, not_found}.

Find the pubsub_server pid for Realm under RegistryPid, or report not_found.

purge_subscriber(RegistryPid, Sub)

-spec purge_subscriber(pid(), <<_:256>>) -> ok.

Remove Sub (a subscriber pubkey) from every topic under every realm currently materialised on this registry — i.e. from every live hecate_pubsub_server it owns. A dead server pid (raced against its own EXIT cleanup, see handle_info/2) is skipped rather than treated as an error; the registry's own by_realm/by_pid bookkeeping self-heals on the pending EXIT.

Intended caller: the station's peer/daemon connection-lifecycle path, once a NodeId is confirmed to have no remaining connection. Sub has no notion of "which realm" it was subscribed under, so this fans out to all of them rather than requiring the caller to know.

register(RegistryPid, Realm, Identity)

-spec register(pid(), realm(), identity()) -> {ok, pid()} | {error, term()}.

Idempotently start a pubsub_server for Realm under RegistryPid, signing with Identity. If a live server already exists, returns its pid; otherwise spawns a new one and records the mapping. A stale entry pointing at a dead pid is replaced transparently.

relay_publish(RegistryPid, Realm, Frame)

-spec relay_publish(pid(), realm(), macula_frame:frame()) ->
                       {ok, macula_frame:frame(), [<<_:256>>]} | {error, not_found | realm_mismatch}.

Relay an inbound PUBLISH frame for Realm to the matching pubsub_server. The server builds an EVENT frame and returns it together with the local subscribers that should receive it. The caller is responsible for sending EventFrame on each subscriber's peering connection.

Returns {ok, EventFrame, [Subs]} on success, {error, not_found} when no server is registered for the realm AND no default_identity was configured at start-up.

**Auto-registration**: parallel to dispatch_frame/4. With default_identity set (the production path under macula_station_identity_sup), an unknown realm is materialised on demand and the EVENT frame is built against a freshly-spawned server with empty subscribers. This makes the EVENT available for publisher-side bloom-fan forwarding to peer stations that have the topic in their Bloom filter but no subscribe-on-peer chain terminating at us. Tests that omit default_identity retain the strict {error, not_found} semantics.

start_link(Opts)

-spec start_link(opts()) -> {ok, pid()} | {error, term()}.

stop(RegistryPid)

-spec stop(pid()) -> ok.

Stop the registry. Uses reason shutdown (not the default normal) so the registry's spawn-linked pubsub_servers receive the exit signal and terminate alongside it. Without this, normal exit does not propagate to non-trapping linked workers.

terminate(Reason, State)