Guides
Roadmap
Milestones express dependency order and implementation history. They are not a stable-release scorecard. The stabilization program is now authoritative for priority, evidence language, and P0/P1/P2 gates; it freezes nonessential feature expansion until the everyday messaging path and trust gates are proven. “Implemented” below means a production path exists, usually with automated evidence. It does not mean field-qualified, independently interoperable, independently reviewed, or stable. Build order details per crate: 09: Implementation Guide.
| Milestone | Status | Principal remaining gate |
|---|---|---|
| M0–M2 | Implemented + automated evidence | Independent vectors/review and stabilization regressions |
| M3 | Implemented + partial automated evidence | Clean-install distinct-NAT journey, adversarial/field first-contact qualification, durable mailbox qualification |
| M4 | Implemented + partial automated evidence | Physical two-radio field qualification |
| M5 | Implemented Beta surfaces | Hands-on mobile, lifecycle, accessibility, localization, and install qualification |
| M6 | Partial | Signed/reproducible updates, external review, operator readiness; expansion work deferred |
M0: Design framework (implemented; review remains)
Deliverable: the documentation set in docs/: threat model, architecture, crypto
spec, transport spec, identity model, storage model, ADRs, implementation guide.
Acceptance: docs internally consistent; every architectural decision has an ADR; implementation guide sufficient for a competent Rust developer to start M1 without design questions.
M1: Cryptographic core (kult-crypto) (implemented; assurance open)
Workspace scaffolding + the full crypto layer: primitives wiring, hybrid PQXDH handshake, Double Ratchet with header encryption, fingerprints, key serialization.
Acceptance:
- All test obligations of 04: Cryptography §11 green in CI
(KATs, ratchet property tests, fuzz targets running,
cargo-denyclean). #![forbid(unsafe_code)]; every secret type zeroizes; API compiles asno_std+alloc.- Two in-memory parties complete handshake and exchange 10 000 messages under random
loss/reorder within
MAX_SKIP.
M2: Protocol & storage (kult-protocol, kult-store) (implemented; assurance open)
Envelope codec, padding buckets, fragmentation/reassembly, delivery tokens, sealed sender; encrypted SQLite storage with the full key hierarchy; sneakernet bundle import/export (first working transport, needs no networking).
Acceptance:
- Two nodes exchange messages via bundle files end-to-end (write → export → import → read), surviving process restarts (queue persistence).
- Fragmentation round-trips at MTU 180 B with 30 % random fragment loss via NACK/retry.
- Fuzzers on envelope + bundle parsers; storage passes "copied DB file leaks nothing but sizes" review checklist.
M3: Internet transport & headless node (kult-transport, kult-node) (implemented; stabilization open)
The kult-node runtime is implemented per the build order in
09: Implementation Guide §2: delivery engine
(queued→sent→delivered on encrypted receipts, passive retry, 30-day failure, dedup,
out-of-order stash), transport scheduler, session lifecycle, command/event API,
running over the sneakernet carrier. The libp2p carrier's first slice is also in:
QUIC (primary) and TCP+Noise+Yamux (fallback) with an envelope request-response
protocol reporting honest next-hop acks; two nodes exchange messages and receipts
over localhost, and the scheduler prefers it over slower carriers. The discovery
plane is in: a Kademlia DHT (bootstrap from any user-supplied peer, nothing
hardcoded) carrying whole-bundle-signed prekey records under the kult-address
digest, so a node adds a contact from the address string alone and the delivery
engine resolves missing return paths (sealed sender reveals none) from the
peer's record. Mailbox relays are in: any node can volunteer durable bounded
store-and-forward on /komms/mailbox/2; recipients register rotating
delivery tokens as accept-filters and collect leased pages on reconnect.
Senders deposit sealed envelopes the scheduler ranks below direct paths.
Accepted means the relay transaction committed. Exact rows remain until the
endpoint durably stages them and acknowledges their random ids. Opaque
indexes, row binding, restart persistence, exact partial acknowledgement,
expiry, overload, failpoint, multi-operator deduplication, and aggregate-only
status tests pin this content-blind custody contract (ADR-0007, ADR-0032).
NAT traversal is in as the pinned trio: AutoNAT dial-back probes report each
node's reachability (nat_status), a private node reserves a Circuit Relay v2
slot at any public peer (reserve_relay, every node volunteers bounded relay
service, and a fresh relay self-confirms its own address via AutoNAT seconds
after its first peer connects), the returned circuit address is handed out as
an ordinary multiaddr hint, and DCUtR upgrades relayed connections to direct
ones by hole punching. The headless daemon is in: kultd (its own crate,
application A3) runs the full node over the internet carrier: tick loop,
DHT bootstrap and bundle publication, automatic NAT probing with relay
reservation, mailbox check-ins, optional mailbox serving and sneakernet
spool, and exposes the node's command/event API as newline-delimited JSON
RPC on a mode-0600 local Unix socket, with kult as the matching CLI
client; the RPC acceptance test drives two daemons to verified delivery
through their sockets alone. mDNS LAN auto-discovery closes out M3: since
libp2p-mdns still pins the RUSTSEC-flagged hickory-proto 0.25 (and this
workspace ignores no vulnerabilities), the libp2p mDNS discovery profile is
implemented in-tree (ADR-0008), a strict, bounded DNS responder whose
discoveries seed the Kademlia routing table, so two nodes on one LAN
deliver messages and run the whole discovery plane (prekey
publish/lookup) with zero bootstrap configuration and no internet at all.
libp2p integration (QUIC, TCP fallback, Kademlia, relay v2, DCUtR), prekey bundles on DHT, mailbox relays, transport scheduler, headless daemon with local RPC.
The existing localhost, LAN, configured-peer, and automated NAT/relay evidence does not close the everyday clean-install claim. Fresh application defaults currently require deliberate bootstrap/mailbox configuration. Mailbox v2 has local crash-safe persistence evidence but no qualified public operator, upgrade/backup incident exercise, cost observation, or real-network matrix. ADR-0030 first-contact admission has local automated evidence but still lacks independent adversarial, physical-device battery/background, accessibility, and operator-path qualification. Those assurance rows remain P0 gates.
Stable acceptance (open):
- Two nodes behind distinct NATs exchange messages with no manual configuration beyond sharing kult addresses.
- Recipient offline → message deposited at relay → delivered on reconnect; relay observably stores only sealed envelopes (verified by inspection test).
- LAN-only (no internet) delivery works via mDNS.
M4: Off-grid Meshtastic bridge (in progress)
USB-serial + TCP Meshtastic radio integration, private app port, runtime MTU computation, priority classes, selective retransmission, internet↔mesh bridging.
The carrier core is in: MeshtasticTransport (behind the meshtastic feature
of kult-transport) speaks the standard client protocol to a stock-firmware
radio over any byte stream (USB-serial, TCP, or an in-memory duplex in tests)
via the official meshtastic crate (the published protobuf definitions through
a generated client, per the implementation guide). Sealed envelopes ride the
private application port; the frame budget is the protobuf-pinned 233-byte
Data.payload cap, so the delivery engine's existing fragmentation path needs
no mesh-specific logic, and a ratcheted 192-bucket text message crosses the
mesh in ≤ 2 LoRa frames, pinned end-to-end (encrypt → fragment → framed
client protocol → fake radio → reassemble → decrypt) by an integration test.
Airtime is its own reviewed unit (airtime): the Semtech time-on-air formula
under known-answer tests, and a rolling one-hour duty-cycle budget sized from
the radio's reported region (EU868/EU433/UA433/UA868 → 10 %) that refuses
over-budget sends honestly with a retry hint instead of silently hogging the
mesh. The delivery engine's mesh policies are in (§4.2 rules 2–3 of the
transport spec): the outbound queue flushes in priority order (text >
receipts > handshakes), payloads over 4 KiB are held off airtime-budgeted
links with honest feedback (AwaitingFasterLink, "will send when a faster
link exists") and go out the first tick a faster carrier appears, and
selective retransmission works end to end: a receiver stuck missing
fragment indices NACKs them (inside an ordinary encrypted receipt, paced to
respect airtime), and the sender retransmits exactly the missing fragments,
never the whole message. The daemon is wired: kultd --meshtastic-serial /dev/ttyUSB0 (or --meshtastic-tcp host:4403) attaches a stock radio as a
carrier (an unreachable configured radio is a hard startup error), kult … --mesh broadcast sets mesh delivery hints, and an end-to-end test drives
two daemons (mDNS off, no bootstrap, mesh hints only) to verified
delivery through their RPC sockets with the (fake) radios as the sole
shared medium. Internet↔mesh bridging is in (§4.2 rule 5, mechanism in
ADR-0009): a node with both carriers forwards sealed envelopes it cannot
claim by delivery token: mesh-heard foreign traffic becomes mailbox
deposits at its configured relays (its own mailbox service deposited into
locally), internet-side deposits for unregistered tokens enter a bounded
transit buffer and are flooded over LoRa after the bridge's own traffic,
with content-id dedup, split horizon, and caps on every axis; kultd
bridges by default whenever a radio is attached (--no-bridge opts out),
and the acceptance test drives the full village topology (a mesh-only
node, an internet-only node, and a token-blind bridge between them) to
verified delivered states in both directions through RPC sockets alone.
The hardware-in-loop nightly is in as code: an #[ignore]d acceptance test
(crates/kultd/tests/hil.rs) drives two daemons attached to real
stock-firmware radios on USB-serial (mDNS off, no bootstrap, radios the
only shared medium) through handshake, delivery, receipts, and a ratcheted
reply, failing loudly (never green) on a misconfigured bench; a nightly
workflow runs it on a self-hosted bench runner, armed by the HIL_BENCH
repository variable so it skips cleanly until the bench exists. The bench
runbook (hardware, radio prep, runner registration, security posture) is
10: HIL Bench. Remaining: standing up the physical
two-radio bench and letting the nightly measure the on-air acceptance
criteria below.
Acceptance:
- Two phones/laptops with stock-firmware Meshtastic radios, all other networking disabled, exchange verified E2EE messages multi-hop.
- Text message in the 192 B bucket fits ≤ 2 LoRa frames (measured).
- A node with both mesh and internet bridges queued traffic in both directions.
- Duty-cycle accounting respects EU868 limits (logged and enforced).
M5: Applications (kult-ffi, desktop, mobile Beta) (in progress)
UniFFI bindings; Tauri desktop app; Android/iOS Beta shells. UX for verification (QR safety numbers), contact requests, delivery states, transport indicators, QR pairing and verification; animated message-bundle QR remains planned.
The bindings layer is in: kult-ffi exposes exactly the node's command/event
API (implementation guide §3.5) through UniFFI proc-macros: typed records and
enums for contacts, messages, delivery states, status, and events; blocking
methods a shell dispatches off its UI thread; events pushed to an
application-registered listener on a dedicated thread. Behind the surface sits
an embedded in-process runtime (ADR-0010): one constructor opens the encrypted
store and starts the same composition kultd runs (internet carrier with
mDNS, DHT bootstrap and bundle publication, NAT probing with relay
reservation, mailbox check-ins, optional sneakernet spool and (feature-gated)
Meshtastic radio with bridging) so iOS/Android, where no separate daemon can
run, get the full node from a library call. Ids cross the boundary as hex
strings, prekey bundles as bytes (QR payloads), and errors verbatim, never a
fake success. The crate's e2e test drives two nodes through the public FFI
surface alone: pairing by bundle exchange, verified delivered states via
listener events, history, safety numbers, restart persistence, honest
errors, and cargo run -p kult-ffi --features bindgen --bin uniffi-bindgen
generates the Kotlin/Swift sources. Backup/restore is in (ADR-0011/ADR-0012):
the current encrypted KKR10 file carries the public stable-account trust
anchor, contacts, ordinary
history, group state and signed authority, user-authored sealed local metadata,
note-to-self history, linked-device manifests/endpoints/convergence winners,
ADR-0031 discovery capability/generation, and session-reset markers, sealed
via Argon2id under a 24-word BIP-39 mnemonic (wordlist and codec in-tree in
kult-crypto, KAT-tested against the reference vectors). Ratchet sessions, prekey secrets,
and reusable physical-device credentials are
deliberately excluded. Recovery revokes every device active in the backup,
mints a fresh sole active device, and turns each reset marker
into a proactive OPK-less re-handshake on its first tick, so messaging
resumes in both directions without the user sending first. Exposed at every
front door: kult backup / Op::Backup (file written 0600, mnemonic shown
exactly once), kultd --restore on first run, and kult-ffi's
export_backup + restore constructor, each pinned by its own layer's
round-trip test (store, node, RPC, FFI). The same constructor accepts
KKR1–KKR7 only after a separately reviewed fresh-authority ceremony and
publishes a new-identity local archive; it never resumes the copied-root
account. The desktop app is in
(application A1, apps/desktop): a Tauri shell over kult-ffi's embedded
runtime (the exact surface the mobile shells consume, dogfooded on
the desktop) with a dependency-free HTML/CSS/JS frontend (no bundler, no
npm) behind a strict CSP with zero plugins or webview capabilities. It
covers the M5 UX list end to end: create/unlock/restore at the gate,
out-of-band pairing by bounded, order-independent animated Base45
prekey-bundle QR frames or pasteable hex (interoperable with kult bundle/kult add; legacy single-code Base45 and hex QRs remain accepted)
or by kult address via DHT lookup, conversations with the node's
honest delivery ladder rendered verbatim (queued → sent → delivered
or delivery failed after 30 days,
plus the mesh "held, will send when a faster link exists" verdict),
safety-number verification with matching digits + QR on both ends and a
visible verified badge, key-change surfacing on session re-establishment,
transport indicators (NAT verdict, mDNS LAN peers, queue and
bridge-transit depths, live listen addresses), delivery-hint editing
(multiaddr/relay/spool/mesh-broadcast), and backup export with the
mnemonic shown exactly once. Network settings persist as secret-free
settings.json (the same knobs as kultd flags, radios included). The
app is its own cargo workspace so the GUI dependency tree stays out of
the core's lockfile and cargo-deny surface (it carries its own, equally
strict deny config and local release gates); all shell behavior lives in a
webview-agnostic layer pinned by a two-node end-to-end test: pairing by
compact scanned QR or legacy hex, events as the webview receives them, verification,
and the backup → mnemonic → restore flow. The Android Beta is in
(application A2, apps/android): a Kotlin shell over the same kult-ffi
runtime, generated bindings compiled fresh from the crate at build time
(never committed). Its structure mirrors the desktop split: every
behavior lives in a plain-JVM :core module (session layer + bindings)
pinned by JVM tests including a two-node e2e against the host-built
library (pairing by compact scanned bundle QR, verified delivered states via
listener events, safety numbers, backup → restore → automatic
re-handshake, no emulator involved); the :app module is UI only. It
covers the M5 UX list: create/unlock/restore gate, pairing by camera-
scanned QR (CameraX + pure-Java ZXing, no Google services, F-Droid
friendly), pasted hex, or kult address via DHT, conversations rendering
the node's honest delivery ladder (including the mesh "held" verdict),
safety-number verification with matching digits + QR across platforms,
key-change surfacing, transport indicators, hint editing, secret-free
settings.json (same file format as desktop), mnemonic-shown-once backup
export with OS cloud backup disabled, and a foreground service keeping
delivery alive in the background. Native libraries cross-compile via
cargo-ndk; the local release matrix runs the :core e2e and assembles/lints the
debug APK when the SDK/NDK is installed, while per-push CI assembles a real debug
APK in addition to the SDK-free host suite. Android
sender-key group UX is also implemented: a distinct group list/create flow,
dedicated history/chat/member surface, truthful per-recipient outbound
delivery rows, and a JVM acceptance scenario with a real offline member.
The iOS Beta is in (application A2, apps/ios): a Swift shell over the same
kult-ffi runtime, generated bindings compiled fresh from the crate at
build time (never committed). Its structure mirrors the other shells'
split: every behavior lives in the KommsCore Swift package (session
layer + bindings) pinned by tests that run on plain Linux or macOS with
no Xcode, including a two-node e2e against the host-built library
(pairing by scanned bundle hex, verified delivered states via listener
events, safety numbers, backup → restore → automatic re-handshake, no
simulator involved); the SwiftUI KommsApp is UI only. It covers the M5
UX list: create/unlock/restore gate, pairing by camera-scanned QR,
pasted hex, or kult address via DHT, conversations rendering the node's
honest delivery ladder (including the mesh "held" verdict),
safety-number verification with matching digits + QR across platforms,
key-change surfacing, transport indicators, hint editing, secret-free
settings.json (same file format as the other shells), and
mnemonic-shown-once backup export via the share sheet with the data
directory excluded from iCloud backup. The sender-key group front door is
also implemented: a distinct group list/create flow, dedicated history/chat and
member-management surfaces, truthful per-recipient outbound delivery rows,
and a host acceptance scenario with a real offline member. QR rendering is
CoreImage and scanning is AVFoundation. The app has zero third-party dependencies;
the only library it links is the workspace's own Rust core, built into
KultFFI.xcframework by a script for device/simulator targets. The local release
matrix runs the KommsCore e2e, assembles the xcframework, and builds the app
for an unsigned iOS Simulator destination on a full Xcode host. The explicitly
authorized hosted macOS job now repeats that Simulator build on each push while
the IOS_APP_CI=1 repository gate remains enabled. The app target's earlier
SwiftUI initializer and
SystemConfiguration.framework linkage failures are fixed and guarded by that
build. Remaining: a full hands-on SwiftUI messaging pass and an on-device run;
background delivery and store distribution stay M6.
B14 screen security is implemented across the shared capability contract and all
three shells. Protection starts before unlock and is not user-disableable:
Android applies FLAG_SECURE to every activity, iOS obscures inactive snapshots
and live-captured scenes while documenting the still-screenshot limit, and
desktop requests best-effort native content protection, covers on focus loss,
and provides Ctrl/Cmd+Shift+L rapid lock. Automated parity and native build
gates are in; the remaining M5 hands-on qualification records real device,
OS, window-server, and compositor results per
13: Screen Security.
B15 incognito keyboard behavior is implemented across the shared capability contract and every textual input. Android applies the documented no-personalized-learning request to every editor; iOS disables correction on all SwiftUI editors and uses secure passphrase/mnemonic entry; desktop applies the full webview input-hint set to every classified editable text control. Automated field inventories and native build gates are in. Manual first-/third-party keyboard evidence follows 14: Incognito Keyboard without treating absence of later suggestions as proof of non-retention.
C7 live audio calls are implemented through the bounded content-v1 CallControl
shape, transient account/device-aware node state, and one authenticated
/komms/call/1 substream on an observed fresh direct QUIC connection. The
transport and every shell refuse TCP, relay-only, mailbox, sneakernet, and
airtime-budgeted paths; no call attempt becomes queued delayed work. Fresh
per-call secrets derive directional media keys without exporting ratchet keys,
and authenticated sequence/timestamp/key-phase records bound replay, jitter,
and unsent audio. Strict RPC/CLI, UniFFI, desktop, Android, and iOS expose the
same ring/answer/decline/cancel/hangup and bidirectional Opus lifecycle. Controls
and media are absent from chat history, search, backup, C2 sync, and remote
notification previews. Real distinct-NAT/DCUtR, sustained network, battery,
audio-route, background/lock, and physical Android/iOS device evidence remain
M5 release qualification; video begins only after the audio matrix passes. See
23: Live Audio Calls and
ADR-0013.
Stable acceptance (open): a non-technical user can install desktop + mobile builds, exchange QR verification with a friend, and message over internet, LAN, and mesh with truthful delivery/security indicators. Backup/restore round-trips.
M6: Hardening & reach (in progress)
Sender-key groups polish → OpenMLS for large groups; censorship-resistant transports
(obfuscation, arti/Tor); an optional Freenet carrier research track; panic wipe;
reproducible builds;
external security audit of kult-crypto + kult-protocol; F-Droid and store
distribution.
A production-readiness slice is implemented with automated evidence. Runtime synchronization in
kult-transport and kult-ffi recovers poisoned locks rather than cascading a
panic. kultd owns structured tracing output under the content-free logging
policy in 09 §4b, and passphrases/restore mnemonics
can arrive through owner-only files checked on the opened file descriptor and
held in zeroizing memory. The locked workspaces declare and compile-test MSRV
1.88. Per-push CI now includes that MSRV gate, a real Android debug APK, and the
currently authorized iOS Simulator build; a weekly workflow rechecks advisories
for both Cargo workspaces, the core on macOS, and an informational coverage
snapshot. All build surfaces identify as 0.4.2, with Android and iOS build
number 6. The exact v0.4.2 hosted validation matrix passed and its desktop,
Android, unsigned iOS Simulator, checksum, and revision-bound evidence assets
were published as an explicitly unsigned test-only Beta exception. The public
test set is not production signing, named-platform qualification, or stable
evidence.
The next release-control slice is also implemented locally: immutable action, container, bootstrap, and packaging-tool pins; read-only tag builds; Android application dependency locks and artifact verification; exact-class builder/signing/qualification records; aggregate CycloneDX SBOM; deterministic safe evidence archives; controlled and independently administered reproduction records; hosted-attestation wiring; empty draft creation; exact completed-asset verification; and separately protected production publication. The 0.4.2 test exception did not exercise that protected path. Production keys, store roles, signed platform artifacts, independent reproduction, supported-system upgrade/rollback evidence, and production/stable publication remain open. No updater, bit-for-bit reproducibility, or stable distribution claim is made. See 54: 0.4.2 Unsigned Test Release, 24: Local Release Gate, 39: Release Security and Recovery, and 40: Release Evidence Bundles.
C2 multi-device and ADR-0026 authority are implemented with automated local
evidence: the stable root is separately held offline, KDA2 transitions
require a strict majority of the prior active set, recovery creates a higher
epoch and one fresh device, and every physical endpoint keeps independent
pairwise/group cryptographic state. Explicit authenticated bundles converge an
allowlisted set of owned-device state without cloud infrastructure. Forks and
same-epoch recovery conflicts remain visible and fail closed; root-free KKR10
contains no account, device, prekey, ratchet, sender-chain, link, or resumable
delivery secret. Physical-device, sudden-power-loss, independent security, and
independent interoperability qualification remain P0 gates. See
22: Linked Devices.
The optional Hybrid Infrastructure Layer is proposed as an independent M6 adoption track under ADR-0017 through ADR-0019: explicit Sovereign/Private/ Standard modes, rotating post-pairing rendezvous, and capability-gated content-free native wake. It is not implementation-ready until those ADRs are accepted. The track must preserve the Google-free Android artifact, keep DHT/ QR as first-contact discovery and volunteer mailboxes as durable delivery, and pass a blackhole test proving that loss of every optional service leaves the existing direct, LAN, mesh, mailbox, and sneakernet paths intact.
An optional Freenet carrier is also proposed as a separate M6 research and
adoption track under
ADR-0025. It must add Freenet's
replicated contract network as one more store-and-forward carrier behind the
existing Transport contract; it must not replace Komms identity, hybrid-PQ
cryptography, encrypted local storage, delivery receipts, native shells,
libp2p, LAN, mailbox, mesh, or sneakernet paths. The first implementation
deliverable after ADR review is a desktop-only two-node spike against local
Freenet Core instances, not a Freenet-native rewrite or a production security
claim.
The ADR must pin:
- per-device, per-direction, epoch-scoped inbox contracts so a permanent Freenet contract id does not become a stable Komms identity or conversation handle;
- ciphertext-only contract state containing the same padded sealed envelopes transports already carry, with no plaintext, identity key, contact name, message kind, ratchet state, or long-term delivery token;
- a deterministic merge model with strict byte/count/age quotas, replay deduplication, spam admission controls, bounded attachment chunks, and honest handling of state that remote peers may retain after local expiry;
- native Komms integration through the local or explicitly bundled Freenet Core API, while keeping a browser-distributed Freenet UI and delegate port as a separate possible future product rather than silently replacing the reviewable native boundary;
- reproducible contract/delegate artifacts, stable key-derived application identity, a registered predecessor lineage, and a tested state/secret migration path before any public Freenet contract is treated as durable;
- an explicit metadata warning: Freenet may improve availability and censorship resistance but does not by itself hide contract existence, access timing, activity volume, or the user's network participation;
- desktop-first scope while Freenet's public distribution remains desktop-only; Android/iOS support waits for a supportable mobile Core, background-execution qualification, and the ordinary Komms human visual gate; and
- exclusion of live-call media: Freenet may carry ordinary sealed call-control messages only through the existing queue, while C7 media continues to require a direct authenticated QUIC path.
Freenet carrier acceptance: two native Komms desktop nodes using separate
local Freenet Core instances exchange sealed text, encrypted receipts, and
bounded attachment chunks through Freenet as their only shared carrier; offline
store-and-forward and restart recovery pass; an observer of contract state sees
only bounded padded ciphertext and documented coarse metadata; disabling or
blackholing Freenet loses no queued message and falls back to the unchanged
Komms carriers; no Freenet next-hop acknowledgement is rendered as end-to-end
delivered; and the UI labels the carrier experimental until the Freenet
network, mobile support, metadata analysis, migrations, and independent
security review meet the Komms threat model.
Sender-key groups v1 is in through the core stack (ADR-0012, construction pinned
in 04: Cryptography §6): per-member forward-ratcheting
chains in kult-crypto with the pairwise delay-tolerance bounds, group message
bodies whose only routing metadata (key_id ‖ iteration) is sealed under a
members-only header key so intermediaries see uniformly random bytes, and the
single ciphertext fanned out in ordinary per-member envelopes: relays,
mailboxes, receipts, NACKs, and bridging carry group traffic without knowing it
is group traffic. Membership is creator-managed with a monotonic generation
counter; every control message is one announce shape (group state + the
sender's chain snapshot frozen at entitlement time) that resends on a paced
timer until the ordinary encrypted receipt acknowledges it, so an envelope lost
on a lossy carrier never leaves a member permanently deaf to a sender. Removal
re-keys the group secret and rotates every remaining chain (the removed member
gets a notice that deliberately carries nothing else); rotation also triggers
on leave, on a message-count threshold (PCS), and on restore. Current KKR10
backups carry group identities and history but
never chains: a restored node announces a fresh chain, and co-members
redistribute theirs on the re-handshake, both directions pinned by the
kult-node e2e suite (groups_e2e.rs) alongside encrypt-once-on-the-wire,
per-member delivery ladders, newcomer-reads-no-history, and removed-member
exclusion. The shared front door is also in: kultd RPC, the kult CLI, and
kult-ffi expose group records, history, events, membership operations, and
honest per-member delivery state, pinned by rpc_e2e.rs and ffi_e2e.rs.
Desktop, Android, and iOS group UX are implemented, including truthful
per-recipient partial-delivery rows and shell-level acceptance coverage.
Remaining for groups is the M6 list above.
The versioned message-content foundation is implemented:
ADR-0014 is accepted and implemented
with a permanent legacy-text decode path, encrypted capability negotiation,
bounded typed Text frames, stable encrypted content ids, scoped deduplication,
and honest durable retention of unknown future kinds across the node, store,
RPC, UniFFI, and shells.
The Attachment core proposed by ADR-0015 is implemented: bounded manifests and bulk records, independently sealed resumable chunks, explicit consent/cancel/reject state, pairwise and encrypt-once group transfer, quota-bound sealed-file storage, KKR6 media exclusion, streamed export, and a hard no-airtime bulk class. The F4 application-facing carrier verdict is now shared across the node scheduler, RPC/CLI, and UniFFI as an expiring snapshot with change events; attachment activation consumes that same verdict. Bounded path-based attachment import/export, render-safe transfer records and events, and consent/reject/cancel/pause/resume now cross RPC/CLI and typed UniFFI with pairwise and encrypt-once group acceptance coverage. The desktop shell uses native caller-selected paths, Android uses Storage Access Framework streams, and iOS uses security-scoped document-provider URLs; both mobile shells stage only bounded app-private copies. All three expose pairwise/group send, per-object verified-byte progress and state, lifecycle controls, and protected caller-selected export. F3 shell delivery is implemented across the three Beta surfaces: generic files use explicit local confirmation, older sealed previews remain renderable, and canonical edited PNG primaries are validated and rendered only through protected transient paths. Each shell exposes its actual interruption/resume policy. Restart acceptance proves verified progress survives node shutdown; Android's foreground service continues data-sync work while backgrounded, desktop continues while open or minimized, and iOS resumes on foreground without claiming unsupported continuous execution.
B2 recorded audio is implemented end to end on top of that unchanged F3/F4 path. Desktop, Android, and iOS record only while foregrounded, stop into a local review with no autoplay, show locally derived duration/waveform and the current carrier explanation, and require explicit send or discard. All three canonicalize to one source-metadata-omitting profile—mono signed 16-bit little-endian PCM WAV at 16 kHz, bounded to 60 seconds / 1,920,044 bytes—and deliver exact bytes pairwise or through the encrypt-once sender-key group flow. Protected playback and failure, interruption, lock, restart, and orphan cleanup are covered. The ADR-0015 invariant remains absolute: mesh-only recorded audio waits for a faster link and emits zero bulk airtime frames.
B16 still-image editing is implemented end to end without changing F3, F4, wire metadata, crypto, or transport behavior. One path-based Rust/UniFFI helper owns the 32 MiB / 4096-edge / 12-megapixel decode limits, EXIF-orientation normalization, integer crop/quarter-turn/region semantics, RGBA PNG output that omits source metadata, and create-new protection. Desktop, Android SAF, and iOS security-scoped pickers all stage protected app-private originals, show the exact final asset, support free/preset crop, rotation, and user-positioned blur or pixelation, and require explicit send or discard. Only the canonical final enters F3; cleanup covers denial, cancellation, failure, low storage, background/lock, shutdown, and restart orphans. Generic non-image files now show and atomically recheck the same authoritative F4 explanation. Pairwise and sender-key group acceptance proves exact bytes, source metadata omission, wrapper determinism, protected receiver rendering/export, and zero manifest/chunk/range or other bulk mesh airtime. Video, cloud/generative editing, filters, face recognition, project files, and protocol changes remain out of scope.
C1 generic non-image presentation is now implemented without changing ADR-0015's wire or carrier contract. One shared Rust policy classifies untrusted filename and media-type hints, forces active, mismatched, unknown, or nameless objects to export-only, and permits only explicit warned OS handoff for reviewed matching types. RPC/CLI, UniFFI, desktop, Android, and iOS expose the same typed decision; cross-language fixtures and local two-node lifecycle tests cover exact export, resume, no auto-open, protected temporary cleanup, and zero new delivery work. Hands-on Android/iOS interaction remains an M5 qualification gate. See 17: Safe File Presentation.
B17 group mentions are implemented end to end under
ADR-0016. The immutable kind 0x0003
preserves exact fallback UTF-8 plus canonical sorted, non-overlapping UTF-8 byte
ranges targeting stable group peers; the whole shape remains authenticated,
encrypted, and padded inside ADR-0014 content. The node binds review to the exact
current roster and fresh per-peer capability intersection, so one sender-key
ciphertext is emitted only when every co-member supports Mention. Otherwise each
shell offers an explicit ordinary-text fallback with zero semantic signal.
RPC/CLI and UniFFI accept exact peer targets and ranges rather than display names.
Desktop, Android, and iOS provide accessible roster pickers, preserve readable
copy/search/history after roster or petname changes, and emit only a private
endpoint-local notification hint when the authenticated target is the local peer.
There is no server push or online-delivery guarantee, and no mention data was
added to envelopes, transports, DHT records, delivery tokens, or public OS
previews.
B9 safe text formatting is implemented without changing ADR-0014 content, storage,
backups, capabilities, envelopes, or transports. One bounded kult-node
formatter derives emphasis, strong, inline/fenced code, quote, and list blocks
from exact authenticated source and composes B17 mention ranges as inert
highlights. Strict RPC/CLI and UniFFI expose only render-safe text roles; desktop,
Android, and iOS use native inert text primitives for pairwise, group,
note-to-self, and scheduled history. Raw HTML, links, image syntax, URLs, and
remote fetches stay literal. The shared corpus pins malicious input, bidi,
complexity fallback, readable old-client source, and plain-text copy. See
16: Safe Text Formatting.
The F5 sealed local-metadata foundation is implemented in kult-store: typed and
bounded conversation, folder, pin, label, draft, preference, and custom-icon
records use an isolated storage key and reveal no local organization keys in a
copied database. User-authored metadata and sealed note-to-self history are
included in current KKR10 backups. Note-to-self text is implemented through
every shell under one reserved identity; folders, conversation pins, labels,
appearance, and bounded custom icons re-encoded without source metadata are
implemented as separate local experiences.
B5 private contact rename is implemented end to end through kult-node, strict
RPC/CLI, UniFFI, desktop, Android, and iOS. The peer key remains the only identity
and mutation target. Petnames are private local contact-record fields,
NFC-normalized and bounded to 256 UTF-8 bytes; duplicate names are permitted.
The shared assessment reports normalization plus duplicate,
mixed-script/confusable, bidirectional-control, and invisible-character risks,
and warned mutations require explicit acceptance. Rename emits one local event,
survives restart and KKR10, and produces zero discovery, notification, queue,
envelope, capability, or transport work. Optional signed self-display
suggestions remain a separate unimplemented bundle-format/compatibility program.
B13 private custom icons are implemented end to end across the existing F5 record,
kult-node, RPC/CLI, UniFFI, desktop, Android, and iOS. Exact contact, group,
folder, and note-to-self targets render generated initials when absent or after a
safe read failure. Eight bundled glyphs and selected local JPEG/PNG inputs become
strict 256×256 RGBA PNGs after bounded orientation/crop/resize and re-encoding
that omits source metadata. Per-record, count, and 64 MiB aggregate quotas are
enforced at the sealed-store boundary; KKR10 preserves canonical records. Icons
create no remote lookup, peer sync, envelope, capability, queue, notification,
or transport work.
B10 private local conversation folders are implemented end to end across the
unchanged F5 record contract, kult-node, RPC/CLI, UniFFI, desktop, Android,
and iOS. Cryptorandom stable IDs remain separate from exact duplicate-capable
UTF-8 names and durable manual order. Pairwise, group, and note-to-self targets
have at most one folder assignment; All and Unfiled are virtual views. Atomic
create, rename, complete-set reorder, move/unfile, delete cascade, stale cleanup,
and folder-first composition with independent B18 label filters create zero
network or transport work. Limits are 128 folders, 8,192 assignments, and 256
UTF-8 bytes per name. KKR10 preserves exact identity, order, membership, and
stale behavior. Folders never synchronize to contacts or services; C2 can
converge them only between authorized devices of the same account.
B11 private local conversation pins are implemented end to end across the unchanged
F5 record contract, kult-node, RPC/CLI, UniFFI, desktop, Android, and iOS.
Pins use exact typed pairwise, group, and note-to-self identities, with one pin
per conversation and a fixed limit of 8,192. Idempotent append/unpin, atomic
complete-set reorder including stale records, u32 order compaction, exact
stale cleanup, and same-identity reactivation preserve durable intent. Folder
selection and B18 label filtering run before the leading pinned block; pinned
and unpinned rows then use deterministic manual/activity/typed-ID ordering.
Every operation creates zero network, transport, notification, or cryptographic
work. Portability is limited to KKR10 and authenticated own-device C2 sync;
message pins remain separate work.
B12 private appearance is implemented end to end across the unchanged F5 UI
preference record, kult-node, strict RPC/CLI, UniFFI, desktop, Android, and
iOS. The exact system, light, and dark vocabulary defaults safely to System,
persists at appearance.theme, emits one local change event only on mutation,
and survives restart and KKR10 restore with zero delivery or transport work.
All shells apply a non-sensitive pre-unlock cache and then treat the sealed value
as authoritative; desktop uses semantic CSS roles, Android native DayNight
resources, and iOS adaptive system colors. Native high-contrast/reduced-motion
signals remain live, shared reference palettes meet WCAG text contrast, and
security or delivery meaning always retains non-color cues.
B18 private labels are implemented end to end across the unchanged F5 record
contract, kult-node, RPC/CLI, UniFFI, desktop, Android, and iOS. Labels target
stable pairwise, group, and note-to-self conversation IDs; message labels remain
deferred. Definitions use cryptorandom 16-byte IDs, exact bounded UTF-8 names,
and the canonical neutral, red, orange, yellow, green, teal, blue,
purple, and pink tokens. The shared limits are 128 live definitions, 8,192
live assignments, 32 labels per conversation, and 256 UTF-8 bytes per name.
Duplicate names are disambiguated by color and deterministic local order.
Atomic deletion, stale-record diagnostics, and deterministic match-any/match-all
filters remain local presentation behavior and create zero network or transport
work. KKR10 preserves exact identity, ordering, and membership. There is no
shared taxonomy or contact/service synchronization; C2 can converge labels only
between authorized devices of the same account.
Durable scheduled pairwise and group text is implemented end to end. The sealed scheduled outbox does not advance a ratchet or create transport work early; RPC/CLI, UniFFI, desktop, Android, and iOS expose create/list/edit/cancel and the activation lifecycle. Every shell renders scheduled rows separately from the ordinary queued, sent, and delivered ladder while converting only the display/editor to local time.
C4 disappearing text and view-once attachments are implemented for pairwise and
sender-key groups across protocol, sealed lifecycle storage, node, relay/bridge/
queue/fragment retention, strict RPC/CLI, UniFFI, desktop, Android, and iOS.
ADR-0021 binds an exact local deadline to an hour-aligned envelope-v2 relay
deletion hint. Expiry and first reveal remove exact history/media and retain a
sealed terminal tombstone; KKR6 excludes live ephemeral plaintext/manifests/
media and includes those tombstones. Current KKR10 preserves terminal
tombstones; legacy backup reset preserves no live ephemeral content. The UI
promises removal only from this device, never remote erasure or screenshot
prevention. Automated Android APK and iOS simulator builds cover compilation;
real-device interaction on both platforms remains part of the hands-on M5 gate. See
19: Disappearing Messages and View-Once Attachments.
C5 fixed-electorate group polls are implemented across protocol, node, RPC/CLI,
UniFFI, desktop, Android, and iOS. Group-AEAD-protected immutable creation,
visible vote heads, and creator-claimed closure converge deterministically after
delay, duplicates, reorder, membership change, restart, and current KKR10
restore. Legacy backup reset omits groups rather than re-authenticating their
history under a fresh identity.
Current sender-key groups wrap the one shared ciphertext with a distinct
recipient origin tag, preventing one member from voting or closing as another
to an honest recipient. Released legacy history keeps its weaker label. Poll
events render as cards rather than empty chat rows and never claim anonymity.
See
20: Group Polls and
ADR-0029.
C6 owner/admin/member authority is implemented across the same complete surface.
Capability-gated legacy upgrade creates a canonical signed full state; one owner
serializes direct actions and generation-bound signed admin requests. Ownership
certificates form a verified chain, conflicting same-generation states use the
smallest authenticated event id, and a losing transfer fork cannot advance an
accepted replica. Upgrade, rename, roles, transfer, membership, and signed poll
moderation advance the generation and re-key. Current KKR10 carries the
authority record and consumed request ids. The new-identity legacy-backup
boundary omits groups. See
21: Group Roles, Ownership, and Moderation and
ADR-0023.
Acceptance: audit findings triaged with public report; reproducible-build attestation for all release artifacts.
Beta implementation: real-time audio calls
The C7 audio path is implemented across the shared core and Beta application surfaces under the strict direct-QUIC and transient-state contract above. Video remains unimplemented until real-network and physical-device audio qualification passes. Details and constraints: 11: Feature Scope and 23: Live Audio Calls.
Explicitly not scheduled
Cryptocurrency anything, federation with other networks, and any feature that requires mandatory exclusive project-operated infrastructure. Optional, replaceable, content-blind convenience services remain subject to ADR-0017 through ADR-0019. Each broader exception would need a compelling ADR.
For the wider product-feature triage (which messenger-app features fit the model and under what constraints, and where each maps onto these milestones), see 11: Feature Scope.