Membership
Purpose
The group's life — founding it, bringing a member in, taking a member out, and picking up the key
a membership change delivered. Membership is never edited: every change mints the next
GroupRevision under a fresh key and the old epoch stands untouched. One ceremony underlies every
change: gate on the founder, mint the next epoch, seal the record, deliver to each member's own
inbox — the record, and the key wrapped to them alone — and the actor's ring adopts the key it
minted. Founding is the same ceremony at the lineage's beginning: no parent, the founder its sole
member, and nothing fans out because there is no one else yet. Nothing is posted to a shared feed;
each member learns of a new epoch only in their own dead-drop. Only the founder tends the group —
the founder is the one member who can read the whole chain, so the check is the founder's to make
from what they hold. Removal takes nothing back: the future is sealed under a key the removed
member never receives, their exclusion arithmetic, not authority.
The delivered key admits itself on arrival: adopting is one motion the delivery drives — unwrap
the key, unseal the membership record that lies beside it with that same key, and confirm the
epoch actually names the collector before the key is kept. A delivery whose epoch, once unsealed,
does not name its recipient means something upstream lied, and the adoption refuses with an
impossible-delivery Alarm rather than keeping a key the collector was never meant to hold.
The full backbone surrounds these acts with its trust ceremony — inviter signatures, passphrase commitments, vouching — deferred in this scoped example; what is kept is the part the keys enforce.
Role Players
- group: Group — the group as the actor's device has unsealed it; the reading the founder gate works from, and the player that mints
- ring: KeyRing — the actor's ring, opened; the keeper of the keys, yielding the next ring when one is adopted
- relay: Relay — the swarm every delivery is dropped into and the actor's inbox is collected from
Props
- actor: the
Memberacting — the founder when the group is tended, any member when a delivery is adopted
Role Methods
group
- succeeded: given the members
Members, the keyGroupKey, and a revision idText, provides the minted nextGroupRevision— parent the group's current revision, none at the lineage's beginning, those members, under that key's id, created by the actor. The mint every change shares, stated once. Result: the mintedGroupRevision
ring
- adopted: given the key
GroupKey, provides the nextKeyRing, holding it alongside every key already held. Result: the nextKeyRing
relay
- dropped: given the recipient
Member, the recordSealedRevision, and the wrappedWrappedKey, drops all of it into that member's own inbox — how a membership change reaches a member who was not in the room. Result: None - recorded: given the recipient
Memberand the recordSealedRevision, drops the record into that member's own inbox with no key beside it — the actor's own delivery: the record they minted is their record too, and their key is already on the ring. The self-record rule every ceremony follows, stated once. Result: None - collected: given the collector
Member, provides theWrappedKeyswaiting in that member's own inbox paired with theSealedRevisionswaiting there. Result: the waitingWrappedKeyspaired with the waitingSealedRevisions
Functionalities
found
Given a revision id Text and the key GroupKey, composes the founding GroupRevision through
the group's mint — the revision id as its own lineage, no parent, the actor its sole member and
creator, under that key's id — seals it under that key, records it to the actor's own inbox, and
yields the actor's next KeyRing, now holding the first epoch's key. It fails when the ring is
sealed — founding happens with the passkey present, like everything else that touches a key.
Result: the actor's next KeyRing; fails when the ring is sealed
add
Given the joiner Member, a revision id Text, and the key GroupKey, confirms through the
group that the actor is the founder and that the joiner is not already among the current
revision's members. Then it mints the next GroupRevision — the current revision's members plus
the joiner, under that key's id — seals it under that key, and delivers to each member of the new
epoch: to the joiner the record dropped with an invitation WrappedKey, to each continuing
member the record dropped with a rotation WrappedKey, and to the actor the record alone. It
yields the actor's next KeyRing, holding the new epoch's key. It fails when the actor is not
the founder, and when the joiner is already a member.
Result: the actor's next KeyRing; fails when the actor is not the founder or the joiner is already a member
remove
Given the leaver Member, a revision id Text, and the key GroupKey, confirms through the
group that the actor is the founder and that the leaver is among the current revision's members.
Then it mints the next GroupRevision — the current revision's members without the leaver, under
that key's id — seals it under that key, and delivers to each remaining member the record dropped
with a rotation WrappedKey, the actor receiving the record alone — and to the leaver nothing,
ever: no key they could open the future with, no record naming the epoch that stands without
them. It yields the actor's next KeyRing, holding the new epoch's key. It fails when the actor
is not the founder, and when the leaver is not a member.
Result: the actor's next KeyRing; fails when the actor is not the founder or the leaver is not a member
adopt
Given a key id Text, collects the actor's own inbox through the relay, takes the wrapped key
carrying that key id, unwraps it, unseals with the unwrapped key the sealed revision that opens
under it, confirms that revision names the actor among its members, and yields the actor's next
KeyRing, holding the key. It fails when nothing in the actor's inbox carries that key id, and
it fails raising an impossible-delivery Alarm when the unsealed revision does not name the
actor — in that case the key is not kept.
Result: the actor's next KeyRing; fails when nothing in the inbox carries the key id, and when the unsealed revision does not name the actor
Scenarios
Anne founds the group for case 7421
Given: a Membership with actor anne.bruun, a relay, an empty group, and anne's opened ring holding no keys
When: found is performed with revision id "g-7421" and the GroupKey "k-0142"
Then: a SealedRevision under key id "k-0142", sealing the founding revision "g-7421" with "anne.bruun" its sole member, is dropped into anne.bruun's inbox — the membership readable by no one without the key — and the yielded ring holds "k-0142"
Founding with a sealed ring fails
Given: a Membership with actor anne.bruun, a relay, an empty group, and anne's ring sealed
When: found is performed with revision id "g-7421" and the GroupKey "k-0142"
Then: the founding fails because the ring is sealed, and nothing is dropped
The actor's own record survives the ceremony
Given: a Membership with actor anne.bruun, a relay, a group holding the founding "g-7421" created by "anne.bruun" with member "anne.bruun" under key "k-0142", and anne's opened ring holding "k-0142"
When: add is performed with joiner karin.dahl, revision id "g-7421.2", and the GroupKey "k-0150"
Then: the relay delivered the new SealedRevision to anne.bruun's own inbox as well as to karin.dahl's — the record the actor minted is her record too, and no WrappedKey travelled to anne.bruun
Anne brings Karin in, and Sofie's key rotates
Given: a Membership with actor anne.bruun, a relay, a group holding the founding "g-7421" created by "anne.bruun" and the current "g-7421.2" with members "anne.bruun" and "sofie.lind" under key "k-0150", and anne's opened ring holding "k-0142" and "k-0150"
When: add is performed with joiner karin.dahl, revision id "g-7421.3", and the GroupKey "k-0163"
Then: karin.dahl's inbox gains an invitation WrappedKey of "k-0163" and the SealedRevision of "g-7421.3"; sofie.lind's inbox gains a rotation WrappedKey of "k-0163" and the same SealedRevision; anne.bruun's inbox gains the same SealedRevision and no WrappedKey — hers is the record too, the key already on her ring; and the yielded ring holds "k-0163"
Only the founder invites
Given: a Membership with actor sofie.lind, a relay, and a group whose founding "g-7421" was created by "anne.bruun" with a current revision "g-7421.3"
When: add is performed with joiner mette.krog, revision id "g-7421.4", and the GroupKey "k-0180"
Then: the adding fails because the actor is not the founder, and nothing is dropped
A member already in the group is not added again
Given: a Membership with actor anne.bruun, a relay, and a group whose founding "g-7421" was created by "anne.bruun" and whose current "g-7421.2" has members "anne.bruun" and "sofie.lind"
When: add is performed with joiner sofie.lind, revision id "g-7421.3", and the GroupKey "k-0163"
Then: the adding fails because the joiner is already a member, and nothing is dropped
The remover's own record survives the ceremony
Given: a Membership with actor anne.bruun, a relay, a group holding the founding "g-7421" created by "anne.bruun" and the current "g-7421.2" with members "anne.bruun" and "sofie.lind" under key "k-0150", and anne's opened ring holding "k-0142" and "k-0150"
When: remove is performed with leaver sofie.lind, revision id "g-7421.3", and the GroupKey "k-0163"
Then: the relay delivered the new SealedRevision to anne.bruun's own inbox — the record the actor minted is her record too — and nothing at all travelled to sofie.lind
Karin is removed, and the future is sealed away from her
Given: a Membership with actor anne.bruun, a relay, a group whose founding "g-7421" was created by "anne.bruun" and whose current "g-7421.3" has members "anne.bruun", "sofie.lind", and "karin.dahl" under key "k-0163", and anne's opened ring
When: remove is performed with leaver karin.dahl, revision id "g-7421.4", and the GroupKey "k-0264"
Then: sofie.lind's inbox gains a rotation WrappedKey of "k-0264" and the SealedRevision of "g-7421.4" with members "anne.bruun" and "sofie.lind"; karin.dahl's inbox gains nothing; and the yielded ring holds "k-0264"
Removing someone who is not a member is refused
Given: a Membership with actor anne.bruun, a relay, and a group whose founding "g-7421" was created by "anne.bruun" and whose current "g-7421.3" has members "anne.bruun", "sofie.lind", and "karin.dahl"
When: remove is performed with leaver mette.krog, revision id "g-7421.4", and the GroupKey "k-0264"
Then: the removal fails because the leaver is not a member, and nothing is dropped
Only the founder removes
Given: a Membership with actor sofie.lind, a relay, and a group whose founding "g-7421" was created by "anne.bruun" with a current revision "g-7421.3"
When: remove is performed with leaver karin.dahl, revision id "g-7421.4", and the GroupKey "k-0264"
Then: the removal fails because the actor is not the founder, and nothing is dropped
Sofie adopts the rotated key, which opens the record beside it
Given: a Membership with actor sofie.lind, a relay whose feed for sofie.lind's inbox holds a rotation WrappedKey of "k-0264" and a SealedRevision under key id "k-0264" sealing "g-7421.4" with members "anne.bruun" and "sofie.lind", an empty group, and sofie's opened ring holding "k-0150" and "k-0163"
When: adopt is performed with key id "k-0264"
Then: the yielded ring holds "k-0264" alongside her earlier keys — the delivered key unsealed the record beside it and confirmed her in the epoch
A delivery whose epoch does not name its recipient is refused
Given: a Membership with actor karin.dahl, a relay whose feed for karin.dahl's inbox holds a rotation WrappedKey of "k-0264" and a SealedRevision under key id "k-0264" sealing "g-7421.4" with members "anne.bruun" and "sofie.lind", an empty group, and karin's opened ring holding "k-0163"
When: adopt is performed with key id "k-0264"
Then: the adoption fails raising an impossible-delivery Alarm — the unsealed revision does not name her — and her ring does not gain "k-0264"
Nothing waiting means nothing to adopt
Given: a Membership with actor sofie.lind, a relay whose feed for sofie.lind's inbox holds no deliveries, an empty group, and sofie's opened ring When: adopt is performed with key id "k-0264" Then: the adoption fails because nothing in her inbox carries "k-0264"