Two guards in `FrostSigningManager` that were correct only while every ceremony
ran in a room of its own. A parent's Marmot room is about to host its subgroups'
ceremonies, and both of these read a ceremony's room as though that could only
mean one thing. Neither fails loudly.
**`completedKey`'s last fallback is "a ceremony held in this very room", and that
stops being the room's own key.** The fallback is not decoration: a room's
`GroupKeyState` is signed before the room exists and filed as it is created, so
every member welcomed after that -- an invite, a reinstall -- has a room and no
state, and lands here. The parent's room will hold a *completed* ceremony whose
threshold key belongs to the child, so those members would resolve the child's key
for the parent and the group would author events as its own subgroup, with a valid
signature and nothing on screen to say so. The certificate a subgroup is born with
is exactly one of those events.
`getLatestOwnSessionForChatRoom` is the same query with `parentChatRoomId IS
NULL`. A ceremony run to make a subgroup is never the room's own key, and that
column is all that has to be read to know it.
**`signingPath` has one case it cannot self-check, and there are now two rooms in
it.** Everywhere else a candidate path is right exactly when walking it reaches
the room, which makes the function self-checking rather than trusting -- and the
path decides what key the group signs as, so it must never come off a proposal.
The exception is the room a ceremony ran in, signing the statement that lets the
room the ceremony's key derives be created. That room is not derived from the key
at all, so nothing rederives.
It used to mean "a NIP-17 room whose ceremony is its own", gated on
`mlsGroupState == null`. It now also means "the parent's room, where the ceremony
claims that parent" -- without which a subgroup's key state would be signed as the
bare threshold key, an identity no room answers to.
`key.parentChatRoomId` is an unverified claim off a proposal and admitting it here
grants nothing. The signature it enables is by the *child's* key over the
*child's* own id, both derived from a ceremony every signer contributed to and
approved twice. A member who put a false parent on a proposal ends up with a key
state for a room made from a key they helped make, which is what telling the truth
would have got them.
Both inputs are still read from this device's own database, so a proposer chooses
nothing: naming some other ceremony this device holds a share for gets no path,
and a session with no path signs as the threshold key.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>