hive
v0.1.0-selfupdate.9
Portable Hive protocol values, registry exposure catalog, and client for Bee
Run
wippy run bee/hivebee.hive
The cross-node protocol of Bee, as defined by the runtime Hive owner. Six terms: Principal, Owner, Operation, Request, Grant, Session. Every call passes the caller's supervisor and, when remote, the destination supervisor; the owner decides; a grant may open a direct session.
Cluster telemetry lives in the optional bee/hive-telemetry package
(bee.hive.telemetry); this module keeps the protocol itself lean.
Slices
| Slice | Responsibility |
|---|---|
| bee.hive | bounds (identifiers, objects, lists, timestamps), types (envelopes and decoders), client, canonical, principals (the destination identity table), output (contract validation), invoke_check, the supervisor-host provenance resource, and the default host composition (replica_sender, supervisor_service, workspaces) |
| bee.hive.registry | catalog, the registry read model for operation exposure and interfaces |
| bee.hive.supervisor | The root-owned supervisor: hello, admission, forwarding, generic adapter routing, guarded dispatch, epochs |
| bee.hive.desktop | Root-owned desktop integration |
The Hive workspace listing API and desktop list operation share
bee.hive:workspace_query for label, cursor and page-size validation. The
workspace listing handler validates a dense, bounded catalog page and requires
the local node identity before it returns results.
Host composition
bee.hive.service:supervisor_service is the default process.service. It starts
bee.hive.supervisor:main on bee.hive.service:supervisor_host with an empty
configured_nodes list, so a fresh Bee can route local calls while remaining
portable and offline. That default puts no transport credentials or network
settings in the registry. Its lifecycle actor and policies are selected by the
host composition, not by an ordinary application.
An admitted host overlay may replace that service input with peer node IDs and an optional validated desktop configuration. TLS, seeds, ports and native membership settings remain outside the registry; they are not service input.
Exposure
An operation is a function.lua entry with meta.hive: open | approval | policy and a meta.hive_operation block (revision, title, bounded input and
output schemas, limits). The host ceiling is an ordinary security policy with
actions hive.expose.<mode> over entry ids; the catalog includes an operation
only when the ceiling admits its mode, and the supervisor resolves it again at
admission. A package exposes its operations through the hive.expose
capability instead: its request names the operations, a mode and audiences,
and the install grant writes a policy over exactly those operation ids into
the bee.security.hive:hive_exposure_scope group the supervisor loads. Open
dispatch additionally admits only the peers in the host's
bee.hive.supervisor:exposure_audiences table for a listed operation.
Interfaces are registry.entry entries with meta.type: hive.interface naming operation_ref, fixed arguments and allowed arguments;
they narrow and never widen.
Policy operations use the same generic route with an additional destination
authorization check. The host exposes an exact operation through
hive.expose.policy; the destination supervisor then resolves its configured
bee.hive.supervisor:principal_mappings entry, maps the authenticated issuer
and subject pair to its derived member actor and configured policy IDs, and
checks that mapped actor's scope grants hive.invoke for the operation. Only
after those checks does it call the owner function, rechecking the operation
revision, input digest and output contract. A request cannot choose its actor,
policies or destination, and metadata cannot grant them.
A feature-owned operation whose admission is domain-specific (a thread send, a
replica receipt) routes through the same generic supervisor. The host selects a
bee.hive.supervisor:operation_adapters row mapping the operation reference to
the admission worker in the package that owns the feature; the supervisor
authenticates the peer and dispatch, and the worker still runs under the
policies its package declares. The supervisor itself keeps only authenticated
routing and the host-selected grant table, not the per-feature adapter code.
Joining a hive
A node joins another node's hive with one invite; no address, port or key file
is typed. The operations, all implemented by the Bee root's supervisor
(src/hive/supervisor/invites.lua) and native launch (native/launch):
| Operation | Principal and route | Effect |
|---|---|---|
| bee.hive.join:invite (bee hive invite) | enrolled local client of the owner | mints a single-use invite that lives 15 minutes; the supervisor keeps only the secret's digest |
| bee.hive.join:invites (bee hive invites) | enrolled local client | lists recorded invites as pending, used, revoked or expired |
| bee.hive.join:revoke (bee hive revoke INVITE_ID) | enrolled local client | settles a pending invite so it is never redeemed |
| bee.hive.join:peers (bee hive peers) | enrolled local client | lists the Hive peers and whether each holds an established Session |
| bee.hive.join:redeem | the owner's native join listener, host bee.hive:join_host on the same node | consumes a pending invite for the joining node |
| bee hive join INVITE | the joining node's operator, while its owner is stopped | redeems the invite and starts the owner in the hive |
| bee hive leave NODE | the node's operator | retires the peer's pin; the Session ends |
Every supervisor operation also requires the host policy
bee.security.hive:hive_invite_policy (action hive.invite); without it every invite
operation is refused. Invites live in the supervisor process, so an owner
restart voids every outstanding invite and an unknown invite is refused.
The invite line is bee-hive://ID:SECRET@HOST:PORT/NODE?key=FINGERPRINT
with up to eight URL-escaped &c=KIND,SCOPE,ENDPOINT hints:
the owner's join listener (bound on the mesh advertise address with an
automatically selected port), its node and the sha256 of its internode identity
key. The joining node dials the listener over TLS 1.3, accepts it only when its
certificate key matches FINGERPRINT, proves its own identity key with its client
certificate, and only then sends the secret. The joiner races candidate TLS
handshakes and sends the single-use secret over the first verified path. If no
path verifies, the error names each candidate and failure. The hive node
redeems through its
supervisor, pins the joiner's identity key, certifies the joiner's mesh TLS key
under its own authority and returns its gossip address, the mesh secret and its
authority pool. The joiner pins the hive node and records the hive; its owner
then boots with the hive's secret, the verified path's IP and hive node's gossip
port as a seed, the certified leaf and the hive's authorities beside its own.
Both nodes admit each other through the host enrollment: the owner writes
bee.hive.host:enrollment as {nodes, peers} from its local client
keys and its pinned peers, and resolves the same keys for the runtime's
internode.peer_key_source. This entry belongs to the native owner's process-local
registry overlay, created after the supervisor publishes readiness; it is not
package content or durable registry history. A package update cannot replace
live enrollment with an empty default. Client departure and pin retirement still
update the same enrollment and revoke mounts through the existing supervisor.
Peers are configured and discovered, so their
supervisors complete the hello exchange and hold a Session; local clients reach
the desktop bridge and the invite operations only. Exposure levels are
unchanged: a peer reaches the operations the catalog exposes, as any configured
node does. Every owner runs its mesh over internode TLS, keeps its gossip port
across boots and seeds each pinned peer's last gossip address, so either node
can restart and rejoin without a new invite. make hive-join-check boots two
nodes from their own state directories and proves the join, both sessions,
restarts of both nodes, refused used and revoked invites and a refused retired
peer. A node that stops without leaving the mesh (a crash) can lose its
supervisor name to its own previous incarnation in the runtime's eventual name
registry, and its peers then address a process that no longer exists; that
recovery depends on a runtime fix. Retiring a pin refuses new
internode connections and ends the Session; the runtime does not fence an
established connection.
A node that already joined a hive, or that other nodes joined, refuses to
join, because one mesh has one secret. The owner picks the address it
advertises to the mesh itself: a Tailscale address when one is present,
otherwise the first non-virtual LAN interface address, otherwise loopback when
the node is alone. It persists the pick in hive/advertise and reads it back on
the next boot, repicking when the stored address is no longer assigned locally,
so a DHCP lease change or a Tailscale toggle never advertises a stale address.
The local descriptor still uses loopback aliases for same-machine clients. Both
sides of a join keep the path it proved: the joiner adopts the IP the inviter
observed on the authenticated join TCP when this host owns it, and otherwise
records itself in hive/nat and publishes internode_dial=out; the join
listener records the local address a remote peer's join arrived on in
hive/reached and advertises it in preference to the automatic pick, so a peer
that reached the LAN address keeps using it when the pick is a Tailscale
address. A member is dialed at its membership address, so the pick is also the
internode endpoint the runtime dials. A running owner republishes the dial
direction through the runtime's membership metadata when its address changes
and rewrites each pinned peer's .addr seed from cluster NodeJoined,
NodeLeft and NodeUpdated events. The runtime carries a copy of memberlist
gossip over connected internode links, so a peer reached in only one direction
keeps its link. The
reachability guide describes
the remaining runtime work.
Peer membership also grants desktop access: every pinned peer may reach this
node's desktop in Hive Manager, still subject to that peer's live host
enrollment. bee hive leave NODE revokes the grant by retiring the pin. A
statically configured node (desktop.allowed_nodes) remains separate from
these revocable peer grants.
Client
client.open() returns a client whose call(owner_ref, target, input, options) sends a Call to this node's supervisor, found by the LOCAL name
bee.hive.supervisor and trusted only when its PID runs on
bee.hive.service:supervisor_host on the client's own native node. A name that resolves
to a foreign supervisor is refused. Replies are accepted only from that PID with the
matching request id; a timeout returns DEADLINE_EXCEEDED and never cancels
owner execution.
Testing
make test runs tests/lua/hive: envelope decoders and digests, catalog
ceilings under narrowed scopes, malformed declarations, interface narrowing
from one snapshot, and the client against a real
fake-supervisor process on the supervisor host (absent supervisor, wrong
host, stale and impostor replies, malformed replies, deadlines). Support
entries carry meta.type: test_support.
Do not name a variable interface: it is a reserved word of the typed Lua
grammar, and until the runtime pin carries runtime PR 691 the parse error is
only visible in the lint summary count.