macula_group_keyring (macula v13.3.0)

View Source

A node's keyring for the sealed groups it joined (plans/DESIGN_E2E_SEALED_PUBSUB.md §4, §6, §7).

A group is a topic prefix whose second segment is its org. Joining it pulls the group's epochs from the org's distributor, <org>/group_keys_v1, over a call sealed to the distributor's KEM key (confidential => required: a keyless advertisement is never a distributor, since a clear pull would hand the key to every station on the path) and carrying the org's UCAN as the call's own ucan_token. The first trusted distributor that answers is kept for the group, or the one distributor pins.

Every held group is pulled again at a uniformly random instant in its newest epoch's ahead window, event or no event, so the rotation is spread over a third of an epoch and the policy a node holds is at most one epoch old. A pull that fails is retried at once and then with backoff, doubling from 1 second up to a third of an epoch, until one succeeds. The policy is monotonic per node run: once preferred or required has been seen for a prefix, off is ignored.

A publisher seals under the newest held epoch that has started and has not stopped publishing; with none, the group is pulled at once, and the publish fails closed with that pull's error. A subscriber opens an event under a held epoch until its acceptance ends; an unknown id is pulled by id, at most three unknown ids per publisher per epoch, and an unknown_epoch answer is remembered until the id could no longer be accepted anyway.

Only a pull goes through the keyring's process. What it holds is stored in a table it owns, read through a handle (handle/1): publishing under a current epoch, opening under a held one and reading a group's policy never wait on a pull, which can take the distributor's whole deadline, for this group or another.

Refusals and failures are named by the closed set of §7: not_a_member, membership_unknown, unknown_epoch, epoch_expired, and no_distributor for a distributor that cannot be found, reached or understood. Epoch keys are held in memory only.

Summary

Functions

The handle every other function takes: the keyring's process, and the table it stores its groups in.

Join the group Prefix in Realm: pull its current epoch and hold it. A group already held is not pulled again. ucan_token is the org's grant, distributor pins the distributor's node id.

Stop holding the group, and erase its keys.

The epoch Id an event from Publisher was sealed under, if it may be opened now: read from the table when it is held; otherwise the keyring pulls it by id, within its bounds.

The keyring's process, for its owner to end it.

The joined group covering Topic (its longest joined prefix) and that group's policy, or none.

The epoch a publisher seals under now: read from the table while one is current; otherwise the keyring pulls.

Start a keyring over pool. call, now, schedule and uniform replace the pull (as macula:call/6), the clock (milliseconds), the timer and the jitter draw.

Types

join_options/0

-type join_options() :: #{ucan_token => binary(), distributor => <<_:256>>}.

keyring/0

-opaque keyring()

options/0

-type options() ::
          #{pool => pid(),
            now => fun(() -> integer()),
            call => fun((binary(), binary(), map(), map()) -> {ok, term(), map()} | {error, term()}),
            schedule => fun((non_neg_integer(), term()) -> term()),
            cancel => fun((term()) -> term()),
            uniform => fun(() -> float())}.

policy/0

-type policy() :: required | preferred | off.

reason/0

-type reason() :: not_a_member | membership_unknown | unknown_epoch | epoch_expired | no_distributor.

Functions

handle(Pid)

-spec handle(pid()) -> keyring().

The handle every other function takes: the keyring's process, and the table it stores its groups in.

join(_, Realm, Prefix, Opts)

-spec join(keyring(), binary(), binary(), join_options()) ->
              {ok, policy()} | {error, reason() | {invalid_option, group}}.

Join the group Prefix in Realm: pull its current epoch and hold it. A group already held is not pulled again. ucan_token is the org's grant, distributor pins the distributor's node id.

leave(_, Realm, Prefix)

-spec leave(keyring(), binary(), binary()) -> ok.

Stop holding the group, and erase its keys.

open_epoch(_, Realm, Prefix, Id, Publisher)

-spec open_epoch(keyring(), binary(), binary(), <<_:64>>, <<_:256>>) ->
                    {ok, macula_group_epoch:epoch()} | {error, reason() | not_joined}.

The epoch Id an event from Publisher was sealed under, if it may be opened now: read from the table when it is held; otherwise the keyring pulls it by id, within its bounds.

pid(_)

-spec pid(keyring()) -> pid().

The keyring's process, for its owner to end it.

policy(_, Realm, Topic)

-spec policy(keyring(), binary(), binary()) -> {ok, binary(), policy()} | none.

The joined group covering Topic (its longest joined prefix) and that group's policy, or none.

publish_epoch(_, Realm, Prefix)

-spec publish_epoch(keyring(), binary(), binary()) ->
                       {ok, macula_group_epoch:epoch()} | {error, reason() | not_joined}.

The epoch a publisher seals under now: read from the table while one is current; otherwise the keyring pulls.

start_link(Opts)

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

Start a keyring over pool. call, now, schedule and uniform replace the pull (as macula:call/6), the clock (milliseconds), the timer and the jitter draw.