The README said the engine is held to `engine/test/vectors/protocol-v1.json`, a path from the pre-split subtree layout that does not exist here. The vectors live in flamenet-e2e and are read from there rather than copied, because a copied vector file drifts and a drifting vector file stops being a check. PublishedVectorsTest already searches for it and fails loudly when it cannot find it; the README now says so, and names FLAMENET_VECTORS. CONTRIBUTING records what a JVM test cannot tell you: Conscrypt and the JVM's JCA are different providers, so :engine:test passing proves nothing about a handset, which is what interop/ is for. Also the exception escaping a WebRTC callback that SIGABRTs rather than surfacing, the missing permission that presented as a dead button, and `*/` inside a KDoc reporting an unclosed comment hundreds of lines from its cause. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
67 lines
2.9 KiB
Markdown
67 lines
2.9 KiB
Markdown
# Contributing
|
|
|
|
The Android client for Flamenet Messenger, in Kotlin and Compose. Three modules:
|
|
`engine` (the encryption primitives), `client` (protocol and transport), `app`
|
|
(UI). Feature parity with iOS is tracked in [PARITY.md](PARITY.md) — read it
|
|
before assuming something is missing by accident.
|
|
|
|
## Security bugs do not go in an issue
|
|
|
|
Email **security@flamenetmessenger.com**. See [SECURITY.md](SECURITY.md).
|
|
|
|
## Building
|
|
|
|
JDK 21 and the Android SDK. On a machine with no default Java — which is the
|
|
usual state of a Mac — Gradle reports "Unable to locate a Java Runtime" rather
|
|
than anything about JDKs, so set it explicitly:
|
|
|
|
```bash
|
|
export JAVA_HOME=/opt/homebrew/opt/openjdk@21
|
|
export ANDROID_HOME=$HOME/Library/Android/sdk
|
|
./gradlew :engine:test :client:test :app:testDebugUnitTest
|
|
```
|
|
|
|
Those three suites are the merge gate.
|
|
|
|
🔴 **JVM crypto tests prove nothing about Android.** The JVM's JCA and Android's
|
|
Conscrypt are different providers with different opinions — X25519/X448 need DER
|
|
key specs and API 33 on device, and code that passes `:engine:test` on the JVM
|
|
can throw on a handset. Anything touching key agreement or signatures needs an
|
|
instrumented test, which is why `interop/` exists.
|
|
|
|
Instrumented tests are not in the gate: they need an emulator and a local relay.
|
|
|
|
## What a change needs
|
|
|
|
- **Touching the wire?** iOS and the relay move with you. A field this client
|
|
required and the relay never sent broke groups completely while every unit
|
|
test stayed green. **Diff the wire types first.**
|
|
- **Touching state that arrives incrementally** — avatars, rosters, device
|
|
lists — merge, never replace. Absent from a fetch means "not loaded yet", not
|
|
"deleted".
|
|
- **Touching WebRTC callbacks?** Never let an exception escape one. It does not
|
|
surface as a caught error, it `SIGABRT`s the process. Calls have crashed this
|
|
app twice that way.
|
|
- **Adding a permission?** Declare it *and* verify on device. A missing
|
|
`ACCESS_NETWORK_STATE` presented as a dead button, not as a permission error.
|
|
- **Writing KDoc that mentions a path like `/e2e/*`?** Kotlin does not nest
|
|
block comments, so `*/` inside one closes it and the compiler reports
|
|
"Unclosed comment" at the end of the file, hundreds of lines from the cause.
|
|
|
|
## Releases
|
|
|
|
The signed release APK is built with a keystore that is **not in this
|
|
repository and never will be** — it is public. Signing keys are permanent for
|
|
the life of an installed app: a key change means every existing user must
|
|
uninstall and lose their local history, so the certificate identity is not
|
|
something to fix later.
|
|
|
|
## House rules
|
|
|
|
- Fail closed. Unverifiable signatures, unknown versions and unpinned identities
|
|
are refusals, and the user is told which.
|
|
- Never log message contents, keys, or tokens.
|
|
- No secrets, keystores, or passphrases in the repository.
|
|
- Match the surrounding style. Comments explain why a line exists — usually,
|
|
which attack or which platform quirk it accounts for.
|