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-deny clean).
  • #![forbid(unsafe_code)]; every secret type zeroizes; API compiles as no_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 KKR1KKR7 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 (queuedsentdelivered 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.

Edit this page on GitHub ↗