# P0 MVP Agent Spec — Build This First ## Objective Create the smallest complete Chanora MVP that can connect to one TS3-compatible server, render channel/user state, support basic voice workflow, persist local non-secret settings, protect secrets, and pass platform P0 acceptance gates. ## Scope Implement only P0/MVP. Treat P1/P2 items as backlog unless the user explicitly requests them. ## Accepted MVP Decisions - DEC-001: Release type = Internal Alpha first, then External Beta, then MVP Public / Store Release - DEC-002: MVP platform scope = MVP target remains Windows, macOS, Linux, Android, and iOS; first release may be staged by channel/platform - DEC-003: Minimum iOS version = iOS 13 minimum for Flutter support baseline; test latest iOS release separately - DEC-004: Minimum Android version = **Android API 28 (Android 9.0)** minimum, raised from the original recommendation of API 24 by explicit owner ruling on 2026-05-14. Rationale: simplifies the audio path (AAudio is unconditionally available from API 26+ and stable from API 28), narrows the TLS / privacy / scoped-storage compatibility surface, and matches typical 2026 Android baselines. The original cpal-on-Oboe Android spike was built with `minSdk = 24` and `cargo-ndk -P 26`; product code in `apps/chanora_flutter` uses `minSdk = 28` and the Android product audio backend is now direct Oboe, so Chanora no longer carries the API 24/25 OpenSL ES fallback branch. - DEC-005: Android target SDK = Target the Google Play-required API level on the upload date; current release gate uses API 35+ unless newer Google policy applies - DEC-006: Multiple server connections in MVP = Not in MVP; support one active server connection per client instance - DEC-007: AEC default state = Enabled by default on platforms/audio backends where supported and stable - DEC-008: AGC default state = Enabled by default, with user setting to disable - DEC-009: Noise suppression default state = Enabled by default, with user setting to disable - DEC-010: High-pass filter default state = Enabled by default - DEC-011: Audio processing implementation path = Use platform-native audio processing first where available; use Rust/WebRTC-style processing as controlled fallback or later architecture option - DEC-011.1: Audio crate choice = `cpal` for desktop (empirically verified on Linux/PipeWire by `poc/audio-capture-playback-spike` on 2026-05-13) and direct `oboe-rs` for Android product code. The earlier cpal-on-Oboe spike remains evidence that Rust audio reaches Oboe/AAudio on a Motorola Moto G Stylus 5G (2023) running Android 14 arm64-v8a, but product code chooses direct Oboe because P0 Android needs explicit `VoiceCommunication` input preset, `VoiceCommunication`/`Speech` output attributes, low-latency/exclusive stream requests with shared fallback, audio-session ids for hardware AEC/NS/AGC binding, and diagnostics of requested/achieved stream values. iOS crate TBD pending an iOS spike that requires macOS + Xcode hardware - DEC-012: Official SDK / trademark / licensing review = Public/store release is blocked until legal confirms TeamSpeak non-affiliation wording, trademark usage, OSS licenses, and `tsclientlib` license posture - DEC-013: Local database choice = Use SQLite or equivalent embedded local database for non-secret local state; secrets remain in platform secure storage - DEC-013.1: SQLite crate = `rusqlite` with the `bundled` feature (SQLite statically linked into the binary; no system libsqlite3 dependency); verified by `poc/sqlite-storage-spike` on 2026-05-13 - DEC-013.2: Linux secure-storage backend policy = Prefer Secret Service (libsecret / gnome-keyring / kwallet / KeePassXC) on Linux; if the default collection is locked or D-Bus is unavailable, fall back to kernel keyutils with a clear user notice. Both backends are "equivalent" per SysRS-053 / SysRS-162; verified by `poc/secure-storage-spike` on 2026-05-13 - DEC-014: Bridge choice = Use a stable typed Flutter/Rust bridge with generated or schema-controlled DTOs; **`flutter_rust_bridge` 2.x pinned** (empirically verified at 2.12.0 by `poc/flutter_rust_bridge_hello` on 2026-05-13) - DEC-015: Product language for MVP = **English + Chinese (Simplified) for MVP**, raised from the original recommendation of English-only by explicit owner ruling on 2026-05-14. Rationale: the demonstrated test-server population (verified live against `cn.teamspeak.app`) and broader TS3 audience include substantial Chinese-speaking users; shipping zh-Hans alongside en at MVP avoids a launch-window UX gap. Architecture remains i18n-ready so additional languages can be added later mechanically. Server-provided content is preserved verbatim and never translated (ADR-008 UTF-8 boundary, DEC-015 server-content rule retained). - DEC-016: Diagnostics upload policy = No automatic upload for MVP; user-initiated local diagnostic export only - DEC-017: Crash reporting = Disabled for MVP unless explicit opt-in provider and privacy policy are approved - DEC-018: Public product name = Chanora - DEC-021: Apple App Store submission SDK = Use Xcode 26 or later and the iOS 26 / iPadOS 26 SDK or later for App Store submission on or after 2026-04-28, unless Apple publishes a newer applicable requirement before upload - DEC-020: License model = **Dual-licensed under Apache-2.0 OR MIT (recipient's choice)**, the standard Rust-ecosystem permissive license model. Accepted on 2026-05-14. Compatible with every direct dependency in the PoC tree (`tsclientlib` MIT-OR-Apache-2.0, `flutter_rust_bridge` MIT, `cpal` Apache-2.0, `rusqlite` MIT, `keyring` MIT-OR-Apache-2.0, etc.) and with the Flutter framework's BSD-3-Clause. The license texts ship as `LICENSE-APACHE` and `LICENSE-MIT` at the repository root; an aggregator `LICENSE` points to both. `NOTICE` enumerates current direct-dependency attributions. The full OSS legal review (transitive deps, license obligations, OSS notices) remains under DEC-012 and is still required before public release. - DEC-022: Canonical implementation directory layout = Accept the README's sketch as canonical: `apps/chanora_flutter/`, `core/chanora_core/`, `crates/chanora_protocol/`, `crates/chanora_audio/`, `crates/chanora_state/`, `crates/chanora_storage/`, `crates/chanora_diagnostics/`, `crates/chanora_bridge/`. Matches SAD §7.2 module decomposition - DEC-023: Windows desktop Global PTT priority = **P0 in MVP.** Resolves PTT-OPEN-001 from `gen2/chanora-desktop-ptt-review-summary-v0.9.2.md`. The Windows backend ladder (Raw Input → low-level keyboard hook → Focused fallback) is mandatory for the first public release; release notes shall not claim Global PTT support on Windows until live measurement confirms a Global level from a non-fallback rung. - DEC-024: macOS desktop Global PTT priority = **P0 in MVP, with explicit permission UX flow.** Resolves PTT-OPEN-002. The macOS backend shall request the Input Monitoring / Accessibility permission, accept user denial gracefully (continue at `L0Focused` without functional regression), and upgrade to Global asynchronously when the user grants the permission. - DEC-025: Officially-tested Linux environment for first public release = **GNOME on Wayland (only).** Resolves PTT-OPEN-003. The Linux backend shall use the freedesktop `org.freedesktop.portal.GlobalShortcuts` interface on GNOME-on-Wayland and fall back to Focused PTT on every other Linux environment (X11, sway, KDE, untested compositors). Release notes shall not claim Global PTT support outside the tested compositor. - DEC-026: Mouse side-button support in first desktop PTT release = **Supported on Windows and macOS; Linux follows whatever the GlobalShortcuts portal exposes.** Resolves PTT-OPEN-004. The Raw Input backend (Windows) and the Event Tap backend (macOS) shall accept Mouse4 / Mouse5 bindings; the Linux portal binding accepts whatever the session exposes and the release notes shall not over-claim. - DEC-027: PTT diagnostics privacy posture = **Capability and availability state only — no raw key codes ever leave the device.** Resolves PTT-OPEN-005. The diagnostic export shall name only `PttCapabilityLevel`, `backend_id`, `bound_input_class`, and `fallback_exercised`; the user's actual key value (scan code, virtual key, keysym) shall never be logged, persisted, or exported. `PttSanitizer` enforces this at write time. - DEC-028: Missed-key-up watchdog requirement = **P0.** Resolves PTT-OPEN-006. The audio engine shall include a missed-key-up watchdog that clears `transmit_active` after a configured ceiling (default 30 s) when no key-up event arrives. The watchdog is a P0 release-gate item rather than an implementation-level concern because the failure mode (stuck transmission after the user has released the binding) is user-visible and reputation-relevant. - DEC-029: Flutter global-hotkey packages rejected for PTT = **Use the native Rust `DesktopPttBackend` trait + per-OS implementations (already specified in SDD-083 / SDD-084 / SDD-085); do not adopt `hotkey_manager`, `super_hot_key`, or any equivalent pub.dev package for PTT.** Rationale: those packages wrap the OS `RegisterHotKey` / `RegisterEventHotKey` semantics — they consume the key (suppressing it from other applications), they do not deliver a key-up event, and they do not support mouse side-buttons. PTT requires the opposite primitive (observe, do not consume, deliver both up and down). - DEC-030: Voice activity detection deferred to P1 = **`TransmitMode::VoiceActivity` ships as a reserved enum variant with no v1 implementation.** The settings UI shows it as a disabled "coming soon" option. The actual implementation choice is deferred to a future baseline. Rationale: three viable backends were compared (RMS energy threshold — trivial but quality-poor; WebRTC VAD via the `webrtc-vad` crate — frozen-but-stable C++ BSD-3 dep, ~200 KB binary, industry baseline; Silero VAD via ONNX Runtime — best quality but ~8–16 MB binary uplift per platform plus an ONNX-runtime dependency surface). The v1 dependency-surface budget does not have room for the trade-off review required to pick correctly. Choosing too early risks either user-visible quality regression (RMS) or a forced ONNX adoption (Silero) before there is a comparable need for ML inference elsewhere (noise suppression, AEC). - DEC-001: Product Owner = Release type sequence - DEC-002: Product Owner / Engineering = MVP platform strategy - DEC-003: Product Owner / iOS Owner = Minimum iOS version - DEC-004: Product Owner / Android Owner = Minimum Android version - DEC-005: Android Owner / Release Manager = Android target SDK - DEC-006: Product Owner / Software Architect = Multiple server connections in MVP - DEC-007: Audio Owner / Product Owner = AEC default - DEC-008: Audio Owner / Product Owner = AGC default - DEC-009: Audio Owner / Product Owner = Noise suppression default - DEC-010: Audio Owner = High-pass filter default - DEC-011: Software Architect / Audio Owner = Audio processing path - DEC-011.1: Software Architect / Audio Owner = Audio crate (desktop / mobile) - DEC-012: Legal / Compliance = SDK/trademark/licensing review - DEC-013: Software Architect / Storage Owner = Local database - DEC-013.1: Software Architect / Storage Owner = SQLite crate - DEC-013.2: Software Architect / Storage Owner / Security Reviewer = Linux secure-storage backend policy - DEC-014: Software Architect = Bridge choice - DEC-015: Product Owner = Product language for MVP - DEC-016: Product Owner / Legal / Security = Diagnostics upload policy - DEC-017: Product Owner / Legal / Security = Crash reporting - DEC-018: Product Owner = Public product name - DEC-020: Product Owner / Legal = License model - DEC-021: iOS Owner / Release Manager = Apple App Store SDK gate - DEC-022: Software Architect = Canonical implementation directory layout - DEC-023: Product Owner / Windows Platform Owner = Windows desktop Global PTT priority - DEC-024: Product Owner / macOS Platform Owner = macOS desktop Global PTT priority - DEC-025: Product Owner / Linux Platform Owner = Officially-tested Linux environment - DEC-026: Product Owner / UX Owner = Mouse side-button support - DEC-027: Security / Privacy Reviewer = PTT diagnostics privacy posture - DEC-028: Audio Owner / Software Architect = Missed-key-up watchdog - DEC-029: Software Architect / Audio Owner = Flutter global-hotkey packages rejected for PTT - DEC-030: Audio Owner / Product Owner = Voice activity detection deferred to P1 ## Required P0 Capability Groups 1. App shell: routing, startup, theme, localization, adaptive layout, platform-service initialization. 2. UI/design: Material 3, Chanora design tokens, responsive desktop/tablet/mobile layouts, accessible controls, keyboard focus, safe areas/insets. 3. Protocol: TS3-compatible adapter through `tsclientlib`; no direct UI coupling to protocol internals. 4. Connection: one active server connection; visible connecting/connected/reconnecting/disconnected/error states. 5. Channel/user tree: preserve Unicode server content; do not translate server names/messages. 6. Voice basics: capture/playback path, push-to-talk where platform-supported, mute/deafen indicators, native processing defaults. 7. Audio defaults: AEC on where stable, AGC on with user toggle, noise suppression on with user toggle, high-pass filter on. 8. Storage: SQLite for non-secret state; platform secure storage for identities/tokens/password-equivalent secrets. 9. Diagnostics: local export only; redact secrets; machine fields remain language-neutral. 10. Builds: reproducible platform builds for Windows, macOS, Linux, Android, and iOS with staged release allowed. 11. Verification: unit, widget, integration, platform acceptance, and release go/no-go evidence. ## P0 Architecture Boundaries - Flutter owns UI, navigation, Material 3 theming, accessibility, and adaptive presentation. - Rust core owns domain state, protocol/session orchestration, storage, diagnostics, and platform-abstracted services. - Bridge owns typed DTOs/events/errors only. Do not leak Rust internals into Flutter or Flutter objects into Rust core. - Platform adapters own OS-specific audio, hotkey, secure storage, build, and permission behavior. ## P0 Definition of Done - User can launch app, enter/connect to a TS3-compatible server, see server/channel/user state, and disconnect cleanly. - App supports the selected P0 voice path without telemetry or automatic upload. - Secrets are never stored in SQLite/plaintext. - English and zh-Hans product strings are available; server-originated Unicode remains unchanged. - All diagnostic exports are local/user-triggered and redacted. - Platform P0 acceptance docs pass or contain explicit blocker records. - Every code change cites at least one requirement or decision ID. ## P0 Requirement Index - Count: 500 - ID groups: SAD-001..SAD-096 (71 ids); SDD-001..SDD-121 (98 ids); SRS-001..SRS-219 (178 ids); SysDes-001..SysDes-158 (153 ids) ## P0 Source Weighting - `requirements/srs.md`: 178 - `architecture/sysdes.md`: 153 - `architecture/sdd.md`: 98 - `architecture/sad.md`: 71 ## P0 Allocation Hotspots - Unallocated: 98 - Flutter UI: 68 - Rust Core: 52 - Verification: 43 - Audio Subsystem: 42 - Software: Audio Subsystem: 39 - System Engineering: 33 - Protocol Adapter: 31 - Diagnostics: 28 - State Sync: 24 - Design System: 22 - Bridge: 21 - Storage: 21 - Software: Diagnostics: 21 - Platform: 20 - Software: Protocol Adapter: 20 - Platform Adapters: 19 - Software: Rust Core: 19 - Audio: 18 - Operations: 17 ## Agent Compression Rule Do not paste all P0 requirement statements into prompt context. Use this file as the execution contract and `phase_index.json` only when an exact ID/source lookup is required.