# 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
  `<storage_dir>/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<OutPacket>` for outbound frames.
  - `ProtocolClient::take_voice_in()` returns a one-shot
    `mpsc::Receiver<InboundVoice>` 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<CoreError>`.
- `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<CoreError>` 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<Option<ProtocolClient>>`.
- `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<chanora_core::CoreError>`.
  - 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.
