Group
Purpose
The group as its members think of it — one membership, one current key — assembled from the epoch revisions the member has been able to unseal from their inbox. Because membership travels sealed under each epoch's key and is dropped only into the inboxes of that epoch's members, the revisions a member holds are exactly the epochs they were let into: a founder holds the whole chain, a later joiner holds only a suffix, and that shorter reading is not an error but their honest view. The revisions arrive in no promised order; they are the players here, and their interaction — each revision naming the one it succeeds — is what assembles the group before anything is answered. The assembly is also the guard: a lineage is a chain, never a tree, and only the founder mints — so two revisions naming the same parent are not a merge to attempt but substitution evidence, refused with the fork as proof; and revisions of two different lineages are no group at all, refused rather than ignored. Over the assembled chain the group answers what members ask — who is in it now, which epoch is current, whom its reader has ever seen, and the epochs themselves, eldest first: what a seat reads by, and the epochs a conversation's copies travel under. The group a member can assemble is the access model in one reading — the revisions they hold carry exactly the key ids they can ever read.
Role Players
- revisions: GroupRevisions — the epoch revisions of one lineage the member has unsealed, in no promised order; the players whose interaction through parent references assembles the group
Role Methods
revisions
- chain: provides the
GroupRevisionsassembled eldest first — the one revision whose named parent none of the others answers to, followed by the revision naming it as parent, and so on to the end. It fails when two revisions name the same parent — a forked lineage is substitution evidence, carried in a substitutionAlarm, never a merge — and when the revisions name different lineages, because a group is one chain and a stray epoch is refused, not ignored. Result: theGroupRevisionsassembled eldest first; fails when the lineage forks or mixes - founding: provides the eldest held
GroupRevision— the assembled chain's first: the founding revision for a founder, the invitation epoch for a later joiner. Result: the eldest heldGroupRevision; fails when the lineage forks or mixes, and when no revision is held - current: provides the latest
GroupRevision— the assembled chain's end, the epoch new messages are sealed under. Result: the latestGroupRevision; fails when the lineage forks or mixes, and when no revision is held
Functionalities
epochs
Yields the group's epochs — the GroupRevisions assembled eldest first: what a seat reads by, and
the epochs a conversation's copies travel under.
Result: the GroupRevisions assembled eldest first; fails when the lineage forks or mixes
founder
Reports the founder's member name — the sub the eldest held revision records as its creator, which
is the founder's on every revision, because only the founder mints.
Result: the founder's member name as Text; fails when the lineage forks or mixes
current members
Reports the member names of the current revision — the group as it stands.
Result: each member name as Text; fails when the lineage forks or mixes
current revision
Reports the latest GroupRevision — the assembled chain's end, the epoch new messages are sealed
under.
Result: the latest GroupRevision; fails when the lineage forks or mixes, and when no revision is held
seen members
Produces the Members named across the assembled chain, each once, in the order they first appear —
every epoch's roster, not only the latest: a message from a closed epoch is still read, and its
speaker still has a name.
Result: the Members named across the chain, each once, in first-appearance order; fails when the lineage forks or mixes
Scenarios
Delivery order is not epoch order
Given: revisions delivered as "g-7421.2" (parent "g-7421", members "anne.bruun" and "sofie.lind", key "k-0150") then "g-7421" (no parent, members "anne.bruun", key "k-0142") When: epochs is asked Then: the epochs read "g-7421" then "g-7421.2" — assembled by parent references, not by arrival
A later joiner's suffix still assembles
Given: revisions "g-7421.4" (parent "g-7421.3", members "anne.bruun" and "sofie.lind", key "k-0264") and "g-7421.3" (parent "g-7421.2", members "anne.bruun", "sofie.lind", and "karin.dahl", key "k-0163") — a suffix whose eldest names a parent no held revision answers to When: epochs is asked Then: the epochs read "g-7421.3" then "g-7421.4" — the eldest held revision is the one whose named parent none of the others answers to, and the shorter reading is the joiner's honest view, not an error
The group as it stands after a removal
Given: revisions "g-7421" (members "anne.bruun", key "k-0142"), "g-7421.2" (members "anne.bruun" and "sofie.lind", key "k-0150"), "g-7421.3" (members "anne.bruun", "sofie.lind", and "karin.dahl", key "k-0163"), and "g-7421.4" (members "anne.bruun" and "sofie.lind", key "k-0264"), in any order When: current members and current revision are asked Then: the current members are "anne.bruun" and "sofie.lind", and the current revision is "g-7421.4" under key id "k-0264"
The founder is the founding revision's creator
Given: revisions "g-7421" (members "anne.bruun", key "k-0142", created by "anne.bruun") and "g-7421.2" (members "anne.bruun" and "sofie.lind", key "k-0150") When: founder is asked Then: the answer is "anne.bruun"
The reader's seen members span the epochs
Given: revisions "g-7421" (members "anne.bruun", key "k-0142"), "g-7421.2" (members "anne.bruun" and "sofie.lind", key "k-0150"), "g-7421.3" (members "anne.bruun", "sofie.lind", and "karin.dahl", key "k-0163"), and "g-7421.4" (members "anne.bruun" and "sofie.lind", key "k-0264") When: seen members is asked Then: the answer is anne.bruun, sofie.lind, and karin.dahl, each once, in the order they first appear — karin.dahl's removal since does not unname her
An empty group has no current revision
Given: no revisions When: current revision is asked Then: it fails because no revision is held — there is no epoch to stand as current
A forked lineage is refused as evidence
Given: revisions "g-7421" (members "anne.bruun", key "k-0142"), "g-7421.4" (parent "g-7421", members "anne.bruun", key "k-0264"), and "g-7421.4b" (parent "g-7421", members "anne.bruun", key "k-0271")
When: epochs is asked
Then: the assembly fails because two revisions name the parent "g-7421" — a substitution Alarm carries the fork as evidence
Two lineages are no group
Given: revisions "g-7421" (no parent, lineage "g-7421", members "anne.bruun", key "k-0142") and "g-9001" (no parent, lineage "g-9001", members "karin.dahl", key "k-0301") When: epochs is asked Then: the assembly fails because the revisions name different lineages — a stray epoch is refused, not ignored