Kgothatso Ngako 6e97735e47 feat(projects): apply a signed project, and refuse the three ways it can lie
Phase 3 of docs/project-feature.md. Three arms in `ChatMessage.applyInnerEvent`,
which is where every nip30303 kind lands whether it arrived over the wire or was
signed locally -- `FrostSigningManager` reuses this dispatch rather than
repeating it. A project can now become a row; nothing proposes one yet.

The project writes a chat line naming the project and not its creator. On a
group-signed row `senderPublicKey` is the session's coordinator, so "X started
the project" would be the transcript guessing at something the event states
exactly. The memberships write no line, the way a version writes none beside its
artifact: they arrive as later items of the batch that founds the project, which
has already said so, and one added later is covered by the "The group signed
..." line `FrostSigningManager.announce` already writes.

`fromProjectEvent` refuses an event with no name, no creator or no owners. The
third refusal is the one worth having a test for and it is not about malformed
events at all: it is what `ContributorTag.ROLE_OWNER` missing from `KNOWN_ROLES`
looks like from here. The role parses as null, `owners()` comes back empty, the
signature still verifies and nothing is logged, so the refusal is the alarm.
`ProjectApplyJvmTest` reproduces it with a role deliberately not in the set.

A membership whose version or project this device does not hold is refused by
the foreign key, and the refusal is caught here rather than left to escape.
`applySignedEvent` swallows throwables because the signature is valid either
way, so an escaping one would report a session that succeeded as a session that
failed and tell the group to abandon a ceremony whose signature is good. No call
path wraps `applyInnerEvent` in a transaction -- checked, all three -- so
catching it does not poison one.

1381 tests, no failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 19:12:30 +02:00
2026-09-09 17:04:09 +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%