The switch. A subgroup's ChillDKG and its key state now happen in the parent's
Marmot room; the sibling NIP-17 room derived from the child's admins is gone, and
so is the rule that one admin set could hold one subgroup forever.
This is what `docs/subgroups.md` deferred in "Why not the parent's Marmot room"
and said to revisit. The reason to wait was `docs/mls-skipped-keys.md` -- a DKG
cannot finish until every participant takes part, so one group event lost to the
skipped-keys bug stalls it for everybody -- and that is being fixed. The two
transports and the two guards this needs landed in the three commits before it.
**`SelectSubgroupAdminsViewModel` stops standing up a room.** It opens the ritual
in `loaded.parentRoom`, p-tagged to the picked admins, and hands back to the
parent's transcript. That transcript is where the other admins were going to
answer from anyway -- `observeCeremoniesAwaitingYou` and
`observeProposalsAwaitingYou` are room-scoped and were already looking there -- so
the coordinator now lands in the same place as everybody else rather than in a
room only they knew was coming.
The name and the admin set ride on the proposal and land on the session, which is
what `1e7ddd84`'s two columns are for. `MarmotGroupName.of` still puts the `#` on
at the two sites that use the name, and is still idempotent, so the certificate
the parent signs and the room `MarmotGroupCreation` creates are called the same
thing.
**`SubgroupManager.ceremonyRoomIdFor` is deleted and `refuseCeremonyRoom` becomes
`refuseSubgroup`.** There is no ceremony room to derive or to name a refusal
after. The refusal it made -- "this group already has a subgroup run by exactly
these members", forever -- narrows to a ceremony over those admins under that
parent that is still *running*, on `getLatestSubgroupSessionFor`.
That refusal is worth keeping for a reason the old one did not have. It is not
that two subgroups over the same people are forbidden; it is that nobody can
answer for two live ceremonies at once. Every admin would be asked twice, on two
ladders, for two keys, one of which nobody will make a room from. A *finished* one
means that subgroup exists, and asking for another is a legitimate ask that the
derived room made impossible.
**Three reads move from the room to the ceremony**, all of them in the ritual
screen, and each would have been silently wrong in the parent's room:
- `proposeBirthCertificate`'s `adminPublicKeys` -- the room's roster would ask the
parent to certify itself as its own child;
- `createAdminGroup`'s `members` -- it would welcome the whole parent into the
subgroup;
- the name both of those carry -- it would be the parent's.
`DkgRitualUIState.ritualMembers` follows, so the progress ladder draws the
ceremony's participants rather than a row per parent member, and does not report a
ceremony waiting on people it was never with. Profiles still come off the room,
which for a subgroup is the parent and holds every admin the ceremony can have.
**Nothing new is offered on a Marmot room's detail screen.** The shared-key button
is still gated on `mlsGroupState == null`, and the comment there now says why that
is still right: offering a ceremony to a room is offering it a key of its own, and
a Marmot room's id is derived from a key it already has. Its subgroups' ceremonies
are reached from the transcript line that names the one they are about, which is
the only thing that can say which.
`SubgroupManagerJvmTest` follows the refusal. The two tests built on
`ceremonyRoomIdFor` become tests of what actually distinguishes ceremonies now --
another subgroup's and the room's own do not block one, a live one over the same
admins does, a finished one does not -- and the ordering test moves to
`DkgSession.formatParticipants`, which is where "the same set however it was
assembled" now has to hold.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>