A subgroup's ceremony started and nobody else could see it. The session row was
written, the transcript said so, and the shared-key screen showed "start a
ceremony" as though nothing had happened.
**Only the coordinator arrives knowing it is a subgroup.** They come from the
picker, which puts `parentChatRoomId` on the route. Everybody else reaches that
screen from the room -- the transcript's ritual notice, or the group's details --
and neither has a purpose to hand it, so both construct `DkgRitualRoute(chatRoomId)`
with nothing. When the previous commit scoped the room's session lookup by purpose,
`observeLatestSessionForChatRoom(room, null)` started meaning "the ceremony that is
*not* for a subgroup", and a subgroup's session stopped being visible to every
member but the one who opened it.
**Scoping belongs where scoping is the question being asked**, which is "may I
open another" -- `proposeRitual`'s guard and `refuseCeremonyRoom`. Those keep
`getLatestSessionFor(room, parent)`. The screen asks a different question, "what is
happening in this room", and there is only one honest answer to it: whatever
ceremony is there. So the room's lookup goes back to being room-scoped and the
purpose is read *off the session that turns up* rather than required in order to
find it.
That is also the better shape. A member who did not open the subgroup has no idea
it is one until the proposal they were sent arrives, so the purpose could never
have been an input on their side.
**The certificate watchers move to follow the session.** They cannot start at init
any more -- there is no parent to watch until one is known -- so they are
cancelled and restarted from the session collector when the purpose changes, the
way the message watcher already is. An ordinary ceremony passes null and gets
nothing watched.
**Three actions were reading the route as well**, which is the same bug one step
on. `docs/subgroups.md` says only step 1 belongs to the coordinator: the key state
and the room are open to any of the subgroup's admins, and they arrive by room. So
`proposeBirthCertificate`, `proposeKeyState` and `createAdminGroup` now take the
parent from the resolved state rather than the constructor -- otherwise a second
admin finishing the flow would have created an ordinary `#admins` room with no
parentage on it.
Two tests in `RobustRoomKeyCeremonyTest`, both of which fail against the previous
commit: a subgroup's ceremony is found by the room alone and carries the purpose
with it, and a room holds its own ceremony and a subgroup's at once without either
being mistaken for the other or a third being opened.
397 common tests, 715 jvm tests, `m3Audit` meets every budget.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>