# Changelog All notable changes to Chanora will be documented in this file. This project is expected to follow a Conventional Commits style workflow. ## [Unreleased] ## [v1.0.0-rc.1] — MVP Public release candidate This is the first release candidate for the MVP public release per DEC-001 sequencing. The remaining work before `v1.0.0` is DEC-012 legal sign-off (see `docs/governance/legal-review-readiness.md`) and the staged platform promotions in `docs/governance/staged-release-plan.md`. ### Added - **Platform keyring backing for the per-install Data Encryption Key.** `chanora_storage::IdentityFileStore` now stores the 32-byte DEK in the OS keyring (Linux Secret Service via D-Bus, macOS Keychain, Windows Credential Manager, iOS Keychain) when one is available, and transparently falls back to the v0.4 file-fallback at `/identity.dek` otherwise. Existing file-fallback installs are migrated into the keyring on first run when the bus is reachable; the on-disk DEK copy is removed once the keyring acknowledges the write. - **`CHANORA_DISABLE_KEYRING=1`** environment override forces the file-fallback path. Used by tests and headless / CI hosts where a real keyring call would prompt the user or block on a missing D-Bus session. - **`chanora_storage::Crypto` trait + `IdentityFileStore::crypto()`** give callers an envelope encrypt / decrypt pair anchored on the per-install DEK. Used by `BookmarkRepository::with_crypto`. - **Encrypted server passwords for bookmarks.** New `password_blob` BLOB column (schema v2). When the bookmark repository is constructed with a `Crypto` handle, server passwords are ChaCha20-Poly1305-encrypted under the same per-install DEK as the identity. Legacy `password TEXT` rows are still read for backward compatibility and lifted into `password_blob` on the next `update()`. `chanora_core::ChanoraSession::init_storage` wires this automatically. - **`SessionEvent::SnapshotChanged` now triggers on any tree mutation**, not just count changes. The watchdog probe hashes channels (id + parent + order + name) and clients (id + channel + name); any change to those bits — including in-channel client moves and channel renames — emits the event. - **In-app About dialog** with the public product name (DEC-018), the non-affiliation statement (DEC-019), the dual-license declaration (DEC-020), and a pointer to the `NOTICE` file for third-party attribution. New AppBar info icon opens the dialog. - **`docs/governance/legal-review-readiness.md`** — the engineering-side handoff package for the DEC-012 legal / trademark / licensing review. - **`docs/governance/staged-release-plan.md`** — the staging schedule for DEC-002's five-platform MVP target. Linux + Android sideload promote to GA on DEC-012 sign-off; Windows / macOS / iOS gate on per-platform build host availability. ### Changed - **`chanora_storage` test count rises from 8 to 10**: encrypted bookmark password round-trip and legacy-plaintext-bookmark upgrade scenarios. - **`chanora_core` test count rises from 5 to 7**: signature detects in-channel move; signature is stable under input-vector reordering. - **`NOTICE` refreshed** for the MVP product-code dependency set (`chacha20poly1305`, `rand`, `zeroize`, `keyring`, `connectivity_plus`, `path_provider`, `freezed_annotation`, the removed `linux-keyutils` / `hound`, etc.). The PoC-era enumeration is preserved upstream in git history. - **`chanora_protocol::lib`** now also re-exports `ChannelId` and `ClientId` for downstream signature / hashing helpers. ### Fixed - The watchdog signature blind spot from v0.4: same-count snapshots no longer suppress `SnapshotChanged`. ### Security - DEK now sits behind the OS session lock on every supported desktop platform; the v0.4 two-file weakness is closed for those installs. Android, iOS, and other platforms without keyring reach fall back to the v0.4 file model; `RISK-PoC-002` remains open for them. ### Notes (MVP scope honesty) - DEC-012 legal review has *not* been performed. `v1.0.0-rc.1` is not the public release; it is the candidate that the review signs off on (or rejects) before `v1.0.0` is tagged. - Crash reporting is intentionally disabled (DEC-017). Repository grep for `sentry|crashlytics|bugsnag` returns zero hits in the MVP product code. - Automatic diagnostic upload remains forbidden (DEC-016). The user-initiated export path is the only way logs leave the device. - Android Keystore-backed DEK and iOS-side `AVAudioSession.Mode.voiceChat` engagement are deferred to v1.1. - iOS, Windows, and macOS release binaries are not built into `v1.0.0-rc.1`. The release page ships Linux x86_64 + Android arm64-v8a only; the other three platforms are source-buildable and promote per `staged-release-plan.md`. ## [v0.4.0-beta.2] — External Beta Server password input, channel join via tap, self mute (mic + speaker), master output gain slider, SQLite bookmarks with save / connect / delete, ChaCha20-Poly1305-encrypted identity at rest with `identity.dek` file, Android `MODE_IN_COMMUNICATION` routing, default log filter trims `tsproto::resend` chatter, GitHub Actions CI on every push. ## [v0.3.0-beta.1] — Internal Beta (polish) A.1 cross-platform DNS, A.2 identity persistence, A.3 redacted diagnostic export, A.4 SnapshotChanged event, A.5 mobile voice- preset flag, A.6 reconnect supervisor with watchdog + UI banner, A.6.1 OS connectivity signal drives reconnect timing. ## [v0.2.0-beta.1] — Internal Beta first build - **Voice in/out wired end-to-end through the Flutter UI.** Per DEC-001 this reaches the Internal Beta milestone. Build hash: see the `v0.2.0-beta.1` git tag. - `crates/chanora_audio/` promoted from scaffold to a working engine: - cpal-based capture (mic gain, linear resampling to 48 kHz, mono down-mix) and playback (48 kHz stereo, requested config). - `audiopus::Encoder` for Opus VoIP encoding (20 ms / 960-sample mono frames). - `tsclientlib::audio::AudioHandler` for the decode + per-client jitter buffer + mix on the playback side. - Push-to-talk gate: encoder is bypassed entirely when PTT is off, so no spurious silence frames leak out. - Graceful playback-only fallback: if the host has no usable mic (typical for headless CI / users who deny the mic permission), capture logs a warning and the engine continues with output only. `AudioEngine::capture_active()` exposes this for the UI. - Live counters: `frames_sent` / `frames_received` / `ptt()`. - `crates/chanora_protocol/` extended with voice channels: - `ProtocolClient::voice_out()` returns a clonable `mpsc::Sender` for outbound frames. - `ProtocolClient::take_voice_in()` returns a one-shot `mpsc::Receiver` of decoded `S2C` / `S2CWhisper` packets, with the originating `from_client` ID extracted. - Re-exports the few `tsproto_packets::packets` types (`OutAudio`, `OutPacket`, `InAudioBuf`, `AudioData`, `CodecType`, `Direction`) that `chanora_audio` legitimately needs. This is the **only** deliberate cross-crate type re-export; per SAD-067 the audio path is performance-sensitive and a parallel type hierarchy would force a copy per 20 ms frame. - Connection task interleaves outbound voice (drained first per loop iteration), event pumping, and control-request handling. - `core/chanora_core::ChanoraSession` audio API: - `start_audio(AudioEngineConfig)` — starts the engine attached to the active connection. Idempotent. - `set_ptt(bool)` — toggles transmission. No-op without an engine. - `audio_stats()` → `(frames_sent, frames_received, ptt_active)`. - `disconnect()` now stops the engine before disconnecting the protocol task. - `crates/chanora_bridge/` audio surface: - `start_audio()`, `set_ptt(active)`, `audio_stats()` Dart-callable commands. - `BridgeAudioStats { frames_sent, frames_received, ptt_active }` DTO. - Mapped `CoreError::AudioNotStarted` and `CoreError::Audio(_)` arms in `BridgeError::From`. - `apps/chanora_flutter/`: - Beta UI rewrite of `main.dart`: "Start audio" button after connect; hold-to-talk button with pressed/released visual state; live audio-stats line below the PTT (`TX … frames • RX … frames • PTT on/off`). - ARB key set expanded with `startAudioAction`, `pttHoldToTalk`, `pttTransmitting`, `audioStatsLine` in both `en` and `zh-Hans`. - `test/beta_e2e_test.dart` exercises the full Dart → FRB → chanora_bridge → chanora_core → chanora_audio path against `cn.teamspeak.app`. Verifies connect, audio start, PTT toggle, disconnect. - `flutter_rust_bridge.yaml` now sets `local: true` so the codegen resolves the workspace member's library name correctly. Without this, the generated Dart side fell back to `libUNKNOWN.so` and failed to load the cdylib. ### Changed - `flutter_rust_bridge.yaml`: added `local: true`. - `chanora_bridge::api`: `BridgeError::From` now maps `CoreError::AudioNotStarted` to `BridgeError::InvalidCommand` and `CoreError::Audio(_)` to `BridgeError::Connection`. - `apps/chanora_flutter/test/widget_test.dart`: banner-string expectations updated from "Alpha build" to "Beta build" and from "Alpha 版本" to "Beta 版本". - `docs/governance/product-decision-register.md` bumped to v0.9.7 with a Beta-milestone change-history entry. No decision rows change. - `docs/governance/poc-results-summary.md` bumped to v0.6.0 with a Beta-milestone change-history entry; RISK-PoC-005 updated to reflect Beta progress. ### Notes (Beta scope honesty) - DSP chain (AEC / AGC / NS / HPF per DEC-007..010) is **not yet implemented**. `AudioEffects` exists as a struct but its filters are no-ops in v0.2.0-beta.1. Real DSP is queued for Beta+ work. - The capture resampler is a simple linear interpolator. Production quality requires a proper resampler in Beta+. - Identity is still ephemeral per connect; persistence via `chanora_storage` is queued. - No live event stream into Dart yet — the UI fetches snapshots and audio stats on a timer instead of subscribing to push events. - `chanora_diagnostics` is still a scaffold; no redaction wired into `tracing` yet. - Audio engine is desktop-only in this Beta. Mobile bundle of the bridge cdylib + UI verification was proven by the PoC but is not re-built into product code in this milestone. ### Carry-over from Alpha (v0.1.0-alpha.1) - **First Alpha build wires the connect → snapshot → disconnect cycle end-to-end from the Flutter UI to a live TeamSpeak-compatible server via the typed Flutter/Rust bridge.** Per DEC-001 this is the Internal Alpha milestone; Audio (voice in/out) is deferred to Beta. - `crates/chanora_protocol/` promoted from a scaffold to a working adapter. Public surface: - `ConnectConfig`, `ProtocolClient`, `ProtocolError`. - DTO module exposing `ChannelId`, `ClientId`, `ChannelInfo`, `ClientInfo`, `ServerSnapshot` — all owned primitives and `String`s; no `tsclientlib::*` types leak (SAD-067). - Tokio task owns the `tsclientlib::Connection`; public handle communicates via `mpsc` requests + `oneshot` replies. - Connect waits for the initial `BookEvents` snapshot, then pumps events for ~2 s so the subscribed channel tree settles before the first snapshot is served. - Promoted from `poc/tsclientlib-connect-spike`. - `core/chanora_core::ChanoraSession` now drives the protocol crate with a typed `connect`/`snapshot`/`is_connected`/`disconnect` API. Enforces the DEC-006 single-connection invariant via an internal `tokio::sync::Mutex>`. - `crates/chanora_bridge/` wired against `flutter_rust_bridge` 2.12.0 (DEC-014). Compiled as `cdylib + staticlib + rlib`. Exposes: - `bridge_init()` (FRB lifecycle), `connect()`, `snapshot()`, `disconnect()`, `is_connected()`. - Typed `BridgeChannel`, `BridgeClient`, `BridgeSnapshot` DTOs; `BridgeError` with `From`. - A process-wide `tokio::Runtime` + `ChanoraSession` via `OnceLock`, used by every async command. - The crate's `#![forbid(unsafe_code)]` lint was lifted to `#![warn(missing_docs)]` only, with a doc-comment explanation that the FRB-generated glue legitimately uses unsafe at the FFI boundary; hand-written code in the crate is still expected to avoid `unsafe`. - `flutter_rust_bridge.yaml` at the repo root drives codegen for the bridge. - Generated Dart bindings under `apps/chanora_flutter/lib/src/rust/{api.dart,frb_generated*.dart,lib*.dart}`. - Generated Rust glue under `crates/chanora_bridge/src/frb_generated.rs`. - `apps/chanora_flutter/lib/main.dart` rewritten as the Alpha UI: - Form: server address + nickname, both pre-populated for convenience. - Connect button → calls FRB → enters connecting state → shows snapshot. - Snapshot view: server welcome banner (preserved verbatim per ADR-008), `N channels • M online` count, ordered channel list with clients indented under their channel. - Refresh and Disconnect actions in the app bar. - `apps/chanora_flutter/lib/l10n/app_{en,zh}.arb` expanded with the Alpha key set: `homeNotProductionReadyBanner` (now says "Alpha build"), `fieldServerHost`, `fieldNickname`, `connectAction`, `disconnectAction`, `refreshAction`, `statusIdle`, `statusConnecting`, `statusConnected`, `statusError`, `channelsHeading`, `clientsHeading`, `countChannelsAndClients`. - `flutter_localizations`, `intl`, `flutter_rust_bridge`, `freezed_annotation` added to dependencies; `freezed` and `build_runner` added to dev_dependencies. - `apps/chanora_flutter/test/alpha_e2e_test.dart` runs the full Dart → FRB → Rust → tsclientlib → network → server path against `cn.teamspeak.app`. Verifies the snapshot contains a non-empty server name and a non-empty channel list, that `isConnected()` flips true → false across the disconnect, and that a re-fetched snapshot agrees on the server name. Passes in ~2.5 s. - `core/chanora_core/tests/alpha_smoke.rs` runs the same path from the Rust side; tagged `#[ignore]` so `cargo test --workspace` doesn't hit the network by default. Run with `--ignored alpha_smoke`. ### Changed - `chanora_core::CoreError` no longer wraps `chanora_bridge::BridgeError`; the relationship is the other way around (bridge maps from core). This removes a cyclic `chanora_core` ↔ `chanora_bridge` dependency introduced when the bridge crate gained `chanora_core` as a dep. - `chanora_bridge` lints relaxed from `#![forbid(unsafe_code)]` to `#![warn(missing_docs)]` (documented above). ### LICENSE files (carry-over from earlier in this branch) - `LICENSE-APACHE` — Apache License Version 2.0 text (DEC-020). - `LICENSE-MIT` — MIT License text (DEC-020). - Initial repository foundation files. - Documentation-first project structure. - `justfile` with `format`, `lint`, `test`, `verify-docs`, and `security-scan` targets, completing `repository-bootstrap-plan` v0.1.0 §3. - `poc/tsclientlib-connect-spike/` — PoC proving protocol feasibility via `tsclientlib`. Verified against `cn.teamspeak.app` on 2026-05-13. - `poc/flutter_rust_bridge_hello/` — PoC proving the Flutter↔Rust command and event-stream boundary via `flutter_rust_bridge` 2.12.0. Verified on Linux desktop on 2026-05-13. - `poc/secure-storage-spike/` — PoC proving platform secure storage via a typed `SecretStorageRepository` trait and a Linux adapter selecting between Secret Service (libsecret) and kernel keyutils. Audit checks SS-AUD-001/002/003/005/006 and SS-TC-003 verified on 2026-05-13. - `poc/sqlite-storage-spike/` — PoC proving SRS-089's "embedded data store + migration mechanism" acceptance criteria: forward-only schema migrator tracked via `PRAGMA user_version`, repository pattern with `BookmarkRepository` / `SettingsRepository` traits over `LocalDatabaseRepository`. 11/11 tests verified on 2026-05-13. - `poc/diagnostics-redaction-spike/` — PoC proving the diagnostic redaction policy from `diagnostic-redaction-audit-report.md`: typed policy + regex rules + literal known-secret registry + bundle redaction. Audit cases REDACT-TC-001..010 verified on 2026-05-13. - `poc/audio-capture-playback-spike/` — PoC proving platform audio capture/playback via cpal. Desktop half (Linux + PipeWire) empirically verified end-to-end on 2026-05-13; mobile half closed separately by `poc/audio-capture-playback-android-spike`. - `poc/audio-capture-playback-android-spike/` — PoC closing the mobile half of the audio capture/playback PoC plan entry. Rust cdylib + JNI + Kotlin Android app; cpal targets Android's Oboe backend (AAudio). Verified end-to-end on a physical Motorola Moto G Stylus 5G (2023) running Android 14 arm64-v8a on 2026-05-13: 500 ms 440 Hz sine wave driven out the device speaker (22,050 frames at 44.1 kHz) and 1 s captured from the microphone into a valid 85,292-byte RIFF/WAVE mono 16-bit PCM file pulled via `adb exec-out run-as`. - `poc/README.md` summarising PoC status against `docs/architecture/proof-of-concept-plan.md`. ### Changed - **DEC-020 license resolved.** Chanora is now dual-licensed under **Apache-2.0 OR MIT** (recipient's choice), the standard Rust-ecosystem permissive model. Compatible with every direct dependency (`tsclientlib`, `flutter_rust_bridge`, `cpal`, `rusqlite`, `keyring`, etc.) and with the Flutter framework's BSD-3-Clause. `LICENSE` rewritten as a dual-license aggregator pointing at `LICENSE-APACHE` and `LICENSE-MIT`. `NOTICE` rewritten with current direct-dependency attributions. `README.md` §License updated. - `docs/governance/product-decision-register.md` bumped to v0.9.6: DEC-020 status promoted from Open to Accepted. §4 license row updated. §6 collapsed: there is no longer any open decision — DEC-012 legal review remains as a *work* item, not a pending decision. Change-history entry added. - `docs/governance/poc-results-summary.md` bumped to v0.4.0: RISK-PoC-003 closed. DEC-020 row moved from the "Still open" section into the closed table. - `docs/governance/product-decision-register.md` bumped to v0.9.5: owner confirmation on all 17 previously-Proposed decisions (DEC-001..010, 012, 015..019, 021). Sixteen were Accepted as recommended; two were modified by the owner — **DEC-004** Android minimum raised from API 24 to **API 28**, and **DEC-015** product language for MVP expanded from English-only to **English + Chinese (Simplified)**. DEC-020 license remains Open / Deferred and is now the only public-release-gating decision outstanding. §4 renamed "Recommended" → "Accepted MVP Defaults" with MODIFIED rows annotated. §6 collapsed to the single remaining DEC-020 item. §7 dated and statused for every decision. - `docs/governance/poc-results-summary.md` bumped to v0.3.0: RISK-PoC-004 closed by the owner-confirmation pass; new RISK-PoC-006 (Android `minSdk` move 24 → 28 for product code) and RISK-PoC-007 (MVP language expansion to en + zh-Hans) added. - `docs/architecture/proof-of-concept-plan.md` bumped to v0.3.0 to promote the audio PoC from PARTIAL PASS to PASS after the Android mobile half was closed; all six PoC plan entries are now PASS. - `docs/governance/product-decision-register.md` bumped to v0.9.4 to promote DEC-011.1 mobile half from Deferred to Accepted (Android), keeping iOS Deferred. - `docs/governance/poc-results-summary.md` bumped to v0.2.0: audio row promoted to PASS, RISK-PoC-001 narrowed from "mobile audio" to "iOS audio only", Android toolchain added to the toolchain table. - `poc/audio-capture-playback-spike/VERIFICATION.md` updated to point at the Android spike for the mobile half. - `poc/README.md` updated to list both audio spike directories. - `docs/architecture/proof-of-concept-plan.md` bumped to v0.2.0 to record PoC outcomes (5 PASS, 1 PARTIAL) and add a Status column. - `docs/security/secure-storage-audit-report.md` bumped to v0.9.3: SS-AUD-001/002/003/005/006 status set to PoC Pass with evidence pointers; SS-TC-003 (Linux) Actual Result populated and Status set to PoC Pass; findings SS-FIND-001..003 added; non-Linux test cases marked Deferred. - `docs/security/diagnostic-redaction-audit-report.md` bumped to v0.9.3: REDACT-TC-001..010 status set to PoC Pass with evidence pointers; export bundle policy §5 populated; findings REDACT-FIND-001..003 added. - `docs/governance/product-decision-register.md` bumped to v0.9.3: owner-confirmed decisions recorded — DEC-014 Accepted (`flutter_rust_bridge` 2.x pinned), DEC-013.1 Accepted (`rusqlite` bundled), DEC-013.2 Accepted (Linux Secret Service preferred, keyutils fallback), DEC-011.1 Accepted (desktop `cpal`) / Deferred (mobile), DEC-022 Accepted (canonical implementation directory layout per README sketch + SAD §7.2), DEC-020 explicitly Deferred and remains a public-release blocker. ### Added (governance) - `docs/governance/poc-results-summary.md` v0.1.0 — single-page reviewer-facing summary of the PoC phase, the toolchain exercised, the owner decisions taken, the audit coverage, and the open risks RISK-PoC-001..005. ### Fixed - N/A ### Security - N/A ## Versioning note The project implementation has not reached a public release version yet.