Kgothatso Ngako 7d54fcdc34 docs: say which rooms an adoption picker can offer, and who is actually deciding
The note said "add a parent group opens a picker of candidate rooms" and left the
list unspecified, which is the easy place to design the wrong screen. It is not the
groups that exist, and not the groups C's admins can name.

**A certificate is proposed inside the adopting group, so the list is the selector's
own rooms.** `FrostSigningManager.proposeSigning` runs in P2, so a candidate has to be
a Marmot room this device already administers and holds a share of -- which is
`SubgroupManager.refuseCeremonyRoom`'s first three checks with the target moved, plus
C itself, C's existing parents and C's descendants removed. The admin test reads
`MarmotGroupData.adminPubkeys` rather than `Participant.adminAt`, for the reason that
function already gives: the flag is written only by `processGroupMembershipChanges` on
an arriving commit, so on a room this device created it is null for everybody.

So a group cannot be offered as a parent by somebody who is not in it. The overlap
requirement the note already states turns up here as a list length, and an empty
picker is a fact about the member rather than a failure -- the remedy is a person, not
a button.

**The filter costs one MLS decode per room and two indexed reads.** `toMlsGroup()`
deserialises the whole group state out of hex to reach `adminPubkeys`. `canSign` is
cheap for exactly the rooms that pass, because `completedKey`'s first branch is a
key-state lookup and a session lookup by id; its `getKeyHoldingSessions` fallback, with
a `marmotGroupId` derivation per session, fires only for rooms with no key state, which
fail the filter anyway. Built the way
`SelectChatRoomMembersViewModel.scheduleKeyPackageEventSynchronization` builds its
prefetch -- off the main thread, filling in as it resolves.

**Picking a parent is making an offer, not taking a decision.** The ordering is forced
-- the key state carries the certificate -- so P2's quorum commits first and C's quorum
answers second. Whoever picks P2 is not choosing on C's behalf; C's admins decide at
step 2 by signing the re-announced key state or not. That is why the `ProposedEvent`
30326 arm is the most important small change in the design, and the note now says so
from this direction too.

**And it exposes a gap the founding flow does not have.** Founding has the same
ordering -- `proposeBirthCertificate` runs before the child's quorum has signed
anything -- but a subgroup's admins have at least *seen* the intent, because
`DkgRitualEvents` puts `parent_group` on the ceremony proposal and the ritual screen
names the group. Adoption has no ceremony and so no hint, which means the first thing
an admin of C would hear is a request for their signature.

`ChatMessage.applyInnerEvent`'s 30329 arm should grow a second branch for it. Today it
calls `SubgroupManager.record`, which asks `certifies(event, subgroup, chatRoomId)` --
the certificate's parent must be the room it arrived in -- and drops everything else. A
certificate that instead names *this* room as its subject, and verifies against the
parent it names, is the arriving offer and gets its own line: "#Ekklesia has offered to
adopt this group", beside today's "The group made #Translators a subgroup". Both
branches are fully verified and neither needs a lookup, since `certifies` checks a
signature against a room id and a room id is a public key. Nothing is stored --
`GroupSignedEvent`'s foreign key onto `ChatRoom` forbids it and nothing needs it,
because the certificate reaches C's devices for real inside the key state at step 2. The
line is notice, not evidence, and must read as an offer rather than as a fact about C's
lineage.

**Two entry points are defensible and they produce an identical certificate.** C's
detail screen beside the parent card, mirroring the parent screen's "add subgroup"; or
P2's beside that button, where the proposal actually lands. C's is the better default,
because the intent originates with the group looking for a parent and its quorum is the
one that decides. P2's is worth adding later, with the same guards read in the other
direction and a candidate list of "Marmot rooms I hold a key state for", since that is
where `subgroup_key` comes from.

Two tests follow: the candidate filter in `SubgroupManagerJvmTest` -- a room this
device is in but does not administer is not offered, and neither is one it administers
but holds no share of -- and the new arm's branch in `ChatMessageJvmTest`. Sizing moves
from six tests to eight and from one text file to two.

Docs only -- no code changed, so there are no test counts to report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 16:46:40 +02:00
2026-07-05 23:21:02 +02:00
2026-07-28 09:10:20 +02:00
2026-03-24 04:47:19 +02:00
2026-03-23 01:41:39 +02:00
2026-03-23 01:41:39 +02:00
2026-03-23 01:41:39 +02:00

This is a Kotlin Multiplatform project targeting Android, iOS, Desktop (JVM).

  • /composeApp is for code that will be shared across your Compose Multiplatform applications. It contains several subfolders:

    • commonMain is for code that’s common for all targets.
    • Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app, the iosMain folder would be the right place for such calls. Similarly, if you want to edit the Desktop (JVM) specific part, the jvmMain folder is the appropriate location.
  • /iosApp contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform, you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.

Build and Run Android Application

To build and run the development version of the Android app, use the run configuration from the run widget in your IDE’s toolbar or build it directly from the terminal:

  • on macOS/Linux
    ./gradlew :composeApp:assembleDebug
    
  • on Windows
    .\gradlew.bat :composeApp:assembleDebug
    

Build and Run Desktop (JVM) Application

To build and run the development version of the desktop app, use the run configuration from the run widget in your IDE’s toolbar or run it directly from the terminal:

  • on macOS/Linux
    ./gradlew :composeApp:run
    
  • on Windows
    .\gradlew.bat :composeApp:run
    

Build and Run iOS Application

To build and run the development version of the iOS app, use the run configuration from the run widget in your IDE’s toolbar or open the /iosApp directory in Xcode and run it from there.


Learn more about Kotlin Multiplatform…

Description
Mantra as a kotlin multiplatform project
https://mantra.press
Readme 13 MiB
Languages
Kotlin 100%