docs(spec): baseline 0.9.5 — v1 audio + PTT lifecycle redesign

Add SysRS-303/304, SysDes-149/150/151, SRS-204/205/206/207,
SAD-081/082/083, SDD-094/095/096/097, DEC-029/030.

Captures the v1 lifecycle redesign:
- Drop manual Start-audio button; audio engine is bound to voice-channel
  join/leave (ensure_running on first join, shutdown_if_idle on last
  leave). Output stream opens regardless of mic-permission state so
  listen-only is a first-class flow.
- TransmitMode enum (Ptt / Continuous / VoiceActivity-reserved).
  Default Ptt on fresh install. Persisted per identity.
- PTT release tail: 200 ms default (0-500 ms configurable) before
  transmit gate closes, avoiding clipped trailing syllables.
- Hard-mute toggle overrides transmit gate regardless of mode/PTT.
- Bridge surface: drop start_audio/stop_audio; add
  voice_join(channel_id) / voice_leave() and BridgeEvent::VoiceState.

DEC-029 rejects Flutter global-hotkey packages (hotkey_manager,
super_hot_key) for PTT: they wrap RegisterHotKey/RegisterEventHotKey
which consume the key and don't fire key-up, wrong primitive for PTT.
Native Rust DesktopPttBackend (SDD-083/084/085) stays authoritative.

DEC-030 defers Voice Activity Detection to P1. RMS / WebRTC VAD /
Silero VAD trade-off review (binary-size, dependency-surface, CPU
profile) postponed; TransmitMode::VoiceActivity reserved on the enum
surface so a P1 increment is non-breaking.

Validator clean: 304/151/207/83/97 IDs, strict layered sourcing
preserved, no new warnings beyond the pre-existing 35 old-package-name
filenames.
This commit is contained in:
EdisonJwa
2026-05-15 22:46:51 +08:00
parent f9d20d8585
commit dfa84ee7bb
9 changed files with 232 additions and 2 deletions
@@ -197,3 +197,17 @@ SysDes-142..148 -> SYS4-SIV-016
|---|---|---|
| 0.9.3 | 2026-05-15 | Initial baseline-candidate architecture for capability-based desktop Push-to-Talk. Codifies the owner rulings for PTT-OPEN-001 through PTT-OPEN-006 as DEC-023 through DEC-028. |
| 0.9.4 | 2026-05-15 | Promoted the Linux GNOME-Wayland backend from probe-only to the live `CreateSession` + `BindShortcuts` + `Activated` / `Deactivated` session flow. Owns an async tokio task with a dedicated `zbus::Connection`; publishes descriptor transitions through a `watch::Sender` consumed by `chanora_core::ChanoraSession::start_audio`. Adds the privacy-safe `trigger_description` classifier and the cancellation / failure path (downgrade to `L0Focused` + re-emit). Updates the Flutter UI to skip the in-app capture dialog on the portal backend (Q3a) and surface a SnackBar redirecting the user to the desktop's own dialog. |
## 8. v0.9.5 lifecycle and mode-picker addendum
This addendum captures the v1 audio + PTT lifecycle refactor recorded in SysRS-303..304, SysDes-149..151, SRS-204..207, SAD-081..083, and SDD-094..097. It does not change the desktop PTT backend design captured in §1–§7; it constrains how the backends are owned and how the rest of the audio path is exposed to the UI.
1. **No "Start audio" button anywhere.** The audio engine lifecycle is bound to voice-channel membership. Input and output streams open on the user's first voice-channel join of the session and close on the last voice-channel leave. The bridge surface drops `start_audio` / `stop_audio` and exposes `voice_join(channel_id) -> Result<(), VoiceError>` and `voice_leave() -> Result<(), VoiceError>`; `AudioEngine::ensure_running()` is invoked implicitly by `voice_join` and is idempotent. The Voice Bar renders from `BridgeEvent::VoiceState { in_channel, transmit_mode, mute, release_tail_ms }` and contains no manual-start affordance. Microphone permission is requested at app first launch rather than at first channel join; the output stream opens regardless of permission state, so listen-only is a first-class flow.
2. **Transmit modes for v1 are `Ptt` and `Continuous`; `VoiceActivity` is reserved but not implemented.** `TransmitMode` is a `#[repr(u8)]` enum mirrored across the bridge; the default on fresh install is `Ptt`; the setting is persisted per identity in the identity store. The settings page renders `VoiceActivity` as a disabled "coming soon" radio row until DEC-030 is resolved and a VAD backend is allocated.
3. **200 ms PTT release tail (configurable 0500 ms).** A `ReleaseTailTimer` tokio adapter sits between the `DesktopPttBackend` `transmit` signal and `AudioTransmitGate::set()`. Key-down forwards `true` immediately; key-up starts a sleep of `release_tail_ms` (default 200, matching the TeamSpeak / Mumble default) before the gate closes; a key-down within the sleep window cancels the pending close. The tail affects only `transmit_active`; `capture_active` is unaffected. The adapter is independent of the missed-key-up watchdog (SAD-079 / SDD-092).
4. **Hard-mute overrides everything.** The Voice Bar hard-mute toggle is applied by the `TransmitModeSelector` as a final clamp on `transmit_active` and takes precedence over the active transmit mode, the PTT key state, and every other internal signal. Hard-mute is in-session only and resets on the next voice-channel join.
5. **Cross-references.** **DEC-029** locks the v1 PTT capture path to the native Rust `DesktopPttBackend` trait + per-OS implementations (SDD-083 / SDD-084 / SDD-085) and rejects Flutter global-hotkey packages such as `hotkey_manager` and `super_hot_key`, which wrap the OS `RegisterHotKey` semantics, consume the key, drop key-up events, and skip mouse side-buttons. **DEC-030** defers Voice Activity Detection to P1 pending a backend trade-off review (RMS vs WebRTC VAD vs Silero VAD); the enum surface is preserved so a P1 increment is non-breaking.
| Version | Date | Description |
|---|---|---|
| 0.9.5 | 2026-05-15 | Added the v0.9.5 lifecycle and mode-picker addendum capturing the no-manual-start audio engine bound to voice-channel membership, the v1 transmit-mode set (`Ptt` default + `Continuous`, with `VoiceActivity` reserved per DEC-030), the 200 ms (0500 ms) PTT release tail, and the hard-mute override. Cross-references DEC-029 (native PTT backend locked) and DEC-030 (VAD deferred to P1). |
+35
View File
@@ -1364,6 +1364,33 @@ Communication evidence is maintained through change history and baseline approva
- Source SRS: SRS-200
- Verification method: Unit Test, Platform Test, UI Review
**SAD-081**: The software architecture shall remove the `start_audio` and `stop_audio` bridge calls from the public bridge surface and shall replace them with `voice_join(channel_id) -> Result<(), VoiceError>` and `voice_leave() -> Result<(), VoiceError>`. The audio engine's `ensure_running()` private method shall be invoked from the `voice_join` handler on first call of the session and shall be idempotent; the `voice_leave` handler shall call a private `shutdown_if_idle()` that closes the input and output streams when no voice channels remain. The Voice Bar UI shall not surface any manual-start affordance and shall instead key off the bridge event stream `BridgeEvent::VoiceState { in_channel, transmit_mode, mute, release_tail_ms }`, which the audio subsystem publishes on every lifecycle transition.
- Status: Baseline Candidate
- Type: Software Architecture Item
- Stage: P0 / MVP
- Allocated to: Audio, Bridge, Flutter UI
- Source SRS: SRS-204
- Verification method: Integration Test, UI Review
**SAD-082**: The software architecture shall add a `ReleaseTailTimer` element to the audio subsystem, owned by the `PttController` and acting as an adapter between the `DesktopPttBackend` `transmit` signal and the `AudioTransmitGate::set()` call. On key-down the adapter forwards `true` immediately; on key-up the adapter starts a tokio sleep of `release_tail_ms` (default 200, configurable 0500); a key-down within the sleep window cancels the pending close; a timer expiration calls `AudioTransmitGate::set(false)`. The adapter is independent of the missed-key-up watchdog defined in SAD-079 and does not interact with `capture_active`.
- Status: Baseline Candidate
- Type: Software Architecture Item
- Stage: P0 / MVP
- Allocated to: Audio
- Source SRS: SRS-206
- Verification method: Unit Test, Integration Test
**SAD-083**: The software architecture shall add a `TransmitModeSelector` element owned by the audio engine that consults the current `TransmitMode` from the identity store on every state evaluation. In `Ptt` mode the selector defers to the `PttController` + `ReleaseTailTimer` pipeline; in `Continuous` mode the selector sets `transmit_active = !mute` while `in_channel` is true and clears `transmit_active` otherwise; in `VoiceActivity` mode (placeholder per DEC-030) the selector shall behave identically to `Continuous` until a VAD implementation is allocated in a later baseline. The selector shall be the only writer of `transmit_active` other than the missed-key-up watchdog defined in SAD-079, and the hard-mute toggle defined in SRS-207 shall be applied by the selector as a final clamp on the output value.
- Status: Baseline Candidate
- Type: Software Architecture Item
- Stage: P0 / MVP
- Allocated to: Audio
- Source SRS: SRS-205, SRS-207
- Verification method: Unit Test, Integration Test
## 27. Updated SRS-to-SAD Coverage Matrix
| SRS Range | SAD Coverage |
@@ -1372,6 +1399,7 @@ Communication evidence is maintained through change history and baseline approva
| SRS-185 through SRS-194 | Covered by `SAD-061` through `SAD-070` |
| SRS-195 through SRS-199, SRS-201 through SRS-203 | Covered by `SAD-071` through `SAD-079` |
| SRS-200 | Covered by `SAD-080` |
| SRS-204 through SRS-207 | Covered by `SAD-081` through `SAD-083` |
## Baseline Candidate 0.9.1 Update
@@ -1400,3 +1428,10 @@ Communication evidence is maintained through change history and baseline approva
| Version | Date | Description |
|---|---|---|
| 0.9.4 | 2026-05-15 | Added `SAD-080` to close the SRS-200 coverage gap surfaced by a P0 traceability audit. The earlier baseline matrix folded SRS-200 under the `SRS-195..203 -> SAD-071..079` range without giving it a dedicated SAD item; the new entry restores the strict one-to-many SRS-to-SAD discipline (SRS-200 is now sourced by SAD-080; SAD-080 is in turn covered by the new SDD-093). No other rows mutate; the existing PTT SAD items (071..079) retain their original SRS sources. |
## Baseline Candidate 0.9.5 Update
| Version | Date | Description |
|---|---|---|
| 0.9.5 | 2026-05-15 | Added v1 audio + PTT lifecycle architecture items SAD-081 through SAD-083 sourced from SRS-204..207: dropped the `start_audio` / `stop_audio` bridge calls and replaced them with `voice_join(channel_id)` / `voice_leave()` keyed off implicit engine init plus `BridgeEvent::VoiceState`; added the `ReleaseTailTimer` adapter on the `DesktopPttBackend``AudioTransmitGate` path (default 200 ms, range 0500 ms, key-down within window cancels the pending close); added the `TransmitModeSelector` element as the sole writer of `transmit_active` (other than the SAD-079 watchdog) with hard-mute applied as a final clamp and `VoiceActivity` mapped to `Continuous` until DEC-030 resolves. Strict layered sourcing preserved (`SAD -> SRS` only). |
+44
View File
@@ -1122,6 +1122,42 @@ The registry supports consistency between architecture, detailed design, impleme
- Source SAD: SAD-080
- Verification method: Unit Test, Platform Test (Windows / macOS / Linux), UI Review
**SDD-094**: `chanora_bridge::voice::voice_join(channel_id: ChannelId) -> Result<(), VoiceError>` and `chanora_bridge::voice::voice_leave() -> Result<(), VoiceError>` shall be the only public-bridge voice lifecycle calls. Internally `chanora_audio::engine::AudioEngine::ensure_running()` shall be idempotent and shall open the cpal input and output streams on the first call of the session; `chanora_audio::engine::AudioEngine::shutdown_if_idle()` shall be called from `voice_leave` and shall close both streams when no voice channels remain. The Flutter widget tree shall subscribe to `BridgeEvent::VoiceState { in_channel, transmit_mode, mute, release_tail_ms }` through the existing FRB stream and shall render the Voice Bar from that stream. The pre-existing `start_audio` and `stop_audio` FRB symbols shall be deleted from `crates/chanora_bridge/src/api.rs` together with their generated bindings; the corresponding caller in `apps/chanora_flutter/lib/main.dart` shall be removed.
- Status: Draft
- Type: Software Detailed Design Item
- Stage: P0 / MVP
- Software unit: `chanora_bridge::voice`, `chanora_audio::engine::AudioEngine`, `BridgeEvent::VoiceState`, `apps/chanora_flutter/lib/main.dart`
- Source SAD: SAD-081
- Verification method: Integration Test, UI Review
**SDD-095**: `chanora_audio::TransmitMode` shall be a `#[repr(u8)]` Rust enum with variants `Ptt = 0`, `Continuous = 1`, `VoiceActivity = 2`. Persistence shall be provided by `chanora_storage::IdentityStore::set_transmit_mode(&self, mode: TransmitMode)` and `chanora_storage::IdentityStore::get_transmit_mode(&self) -> TransmitMode`, the latter returning `TransmitMode::Ptt` on first read for a fresh install. The bridge surface shall carry the enum across the FFI boundary via `mirror` codegen as `BridgeTransmitMode`. The Flutter settings page in `apps/chanora_flutter/lib/widgets/voice_settings.dart` shall render three radio rows; the `VoiceActivity` row shall be rendered with `enabled: false` and a "Coming soon" trailing label until DEC-030 is resolved and an implementation is allocated.
- Status: Draft
- Type: Software Detailed Design Item
- Stage: P0 / MVP
- Software unit: `chanora_audio::TransmitMode`, `chanora_storage::IdentityStore::{set_transmit_mode, get_transmit_mode}`, `BridgeTransmitMode`, `apps/chanora_flutter/lib/widgets/voice_settings.dart`
- Source SAD: SAD-083
- Verification method: Unit Test, UI Review
**SDD-096**: `chanora_audio::release_tail::ReleaseTailTimer` shall be a tokio-task-owning struct exposing `arm(&self, gate: AudioTransmitGate, tail_ms: u32)`, `key_down(&self)`, `key_up(&self)`, and `cancel(&self)`. Internally the struct shall hold a `tokio::sync::watch::Sender<bool>` plus a `JoinHandle<()>` for the pending close. `key_down` shall abort any pending close handle and shall set `transmit_active = true` via the gate; `key_up` shall abort the prior close handle and shall spawn a new task that sleeps for `tail_ms` milliseconds and then sets `transmit_active = false`. The struct shall use no `unsafe`, no mutex, and shall rely only on atomic refcounts on the watch handle. The configuration value `release_tail_ms` shall live in the identity store next to `transmit_mode`, with a default of 200 and validated range 0500 inclusive on every write.
- Status: Draft
- Type: Software Detailed Design Item
- Stage: P0 / MVP
- Software unit: `chanora_audio::release_tail::ReleaseTailTimer`, `chanora_storage::IdentityStore` (`release_tail_ms`)
- Source SAD: SAD-082
- Verification method: Unit Test, Integration Test
**SDD-097**: `apps/chanora_flutter/lib/widgets/voice_bar.dart` shall replace the existing `_AudioControls` widget in `apps/chanora_flutter/lib/main.dart` as the rendering surface for voice state. Its input shall be the `BridgeEvent::VoiceState` stream; its children, in render order, shall be: the channel-name pill (visible only when `in_channel` is true); the mode badge (`PTT: <key_label>` / `Continuous` / `Voice activity (coming soon)`); the hard-mute toggle button; the release-tail and threshold disclosure; the level meter; and the PTT capability badge already specified in SDD-091. The "Start audio" button shall be removed from the widget tree. The settings dialog in `apps/chanora_flutter/lib/widgets/voice_settings.dart` shall render the `TransmitMode` radio group, the bind-key button, and a release-tail slider over the range 0500 ms with step 25 ms; the dialog shall never expose a "Start audio" affordance.
- Status: Draft
- Type: Software Detailed Design Item
- Stage: P0 / MVP
- Software unit: `apps/chanora_flutter/lib/widgets/voice_bar.dart`, `apps/chanora_flutter/lib/widgets/voice_settings.dart`, `apps/chanora_flutter/lib/main.dart`
- Source SAD: SAD-081, SAD-083
- Verification method: UI Review, Widget Test
## 11. Updated SAD-to-SDD Coverage Matrix
| SAD Range | SDD Coverage |
@@ -1130,6 +1166,7 @@ The registry supports consistency between architecture, detailed design, impleme
| SAD-061 through SAD-070 | Covered by `SDD-071` through `SDD-080` |
| SAD-071 through SAD-079 | Covered by `SDD-081` through `SDD-092` |
| SAD-080 | Covered by `SDD-093` |
| SAD-081 through SAD-083 | Covered by `SDD-094` through `SDD-097` |
## Baseline Candidate 0.9.1 Update
@@ -1158,3 +1195,10 @@ The registry supports consistency between architecture, detailed design, impleme
| Version | Date | Description |
|---|---|---|
| 0.9.4 | 2026-05-15 | Added `SDD-093` covering `SAD-080`. `SDD-093` fixes the `PttInputClass` enum surface, the cross-backend rebind contract for `MouseSideButton`, and the Flutter side-button capture path against the `PointerEvent.buttons` bitmask. Strict layered sourcing preserved (`SDD -> SAD` only). |
## Baseline Candidate 0.9.5 Update
| Version | Date | Description |
|---|---|---|
| 0.9.5 | 2026-05-15 | Added v1 audio + PTT lifecycle detailed design SDD-094 through SDD-097 sourced from SAD-081..083: bridge surface drops `start_audio` / `stop_audio` and exposes `chanora_bridge::voice::voice_join` / `voice_leave` with idempotent `AudioEngine::ensure_running` and `shutdown_if_idle`, `chanora_audio::TransmitMode` as `#[repr(u8)]` enum persisted via `chanora_storage::IdentityStore::{set,get}_transmit_mode` and mirrored across FRB as `BridgeTransmitMode`, `chanora_audio::release_tail::ReleaseTailTimer` as a tokio-task-owning adapter on the watch handle (no `unsafe`, no mutex; configurable 0500 ms via `release_tail_ms`), and the new `apps/chanora_flutter/lib/widgets/voice_bar.dart` + `voice_settings.dart` surfaces replacing the legacy `_AudioControls` widget. Strict layered sourcing preserved (`SDD -> SAD` only). |
+45 -1
View File
@@ -2852,15 +2852,52 @@ This SysDes version covers all known SysRS requirements from `SysRS-001` through
- ASPICE SYS.3 alignment: Architecture constraints
- Allocated SysRS: SysRS-298
**SysDes-149**: The system architecture shall define a `TransmitMode` element carried as an enum at the audio + bridge + UI boundary with variants `Ptt`, `Continuous`, and a reserved `VoiceActivity` placeholder that has no allocated implementation in this baseline (deferred per DEC-030). The active mode shall be persisted in the identity store; the UI shall surface `VoiceActivity` as a disabled "coming soon" option until an implementation is allocated in a later baseline.
- Status: Baseline Candidate
- Type: Subsystem Interface
- Stage: P0 / MVP
- Allocated to: Audio Engine, Bridge, Flutter UI
- Downstream artifact: SRS, SAD, SDD, Verification
- Verification method: Unit Test, UI Review
- Verification owner: Audio Owner + UX Owner
- ASPICE SYS.3 alignment: Element behaviour, interface definition
- Allocated SysRS: SysRS-303
**SysDes-150**: The audio engine lifecycle shall be allocated to voice-channel membership: the input and output streams shall open on the user's first voice-channel join of the session and shall close on the last voice-channel leave. The output-stream open shall be independent of the microphone-permission state so listen-only remains a first-class flow. No manual start affordance shall be exposed at any system interface (bridge, UI, or otherwise). A user-facing hard-mute element shall force the transmit gate closed and shall take precedence over the active transmit mode, the PTT key state, and every other internal signal.
- Status: Baseline Candidate
- Type: Lifecycle Allocation
- Stage: P0 / MVP
- Allocated to: Audio Engine, Bridge, Flutter UI
- Downstream artifact: SRS, SAD, SDD, Verification
- Verification method: Integration Test, UI Review
- Verification owner: Audio Owner + Software Architect
- ASPICE SYS.3 alignment: Element behaviour, lifecycle
- Allocated SysRS: SysRS-303
**SysDes-151**: A release-tail element shall be allocated to the audio subsystem as a system-level timer adapter on the `transmit_active` atomic defined in SysDes-144, parameterised by a configurable release-tail period (default 200 ms; user-configurable range 0500 ms). When the PTT backend signals key-up, the adapter shall delay the `true → false` transition of `transmit_active` by the configured period; the adapter shall not affect `capture_active`.
- Status: Baseline Candidate
- Type: Subsystem Element
- Stage: P0 / MVP
- Allocated to: Audio Engine
- Downstream artifact: SRS, SAD, SDD, Verification
- Verification method: Unit Test, Integration Test
- Verification owner: Audio Owner
- ASPICE SYS.3 alignment: Element behaviour
- Allocated SysRS: SysRS-304
## 17. Updated SysRS Coverage Statement
This SysDes version covers all known SysRS requirements from `SysRS-001` through `SysRS-302`.
This SysDes version covers all known SysRS requirements from `SysRS-001` through `SysRS-304`.
| SysRS Range | SysDes Coverage |
|---|---|
| SysRS-001 through SysRS-285 | Covered by inherited SysDes baseline `SysDes-001` through `SysDes-132` |
| SysRS-286 through SysRS-295 | Covered by `SysDes-133` through `SysDes-141` |
| SysRS-296 through SysRS-302 | Covered by `SysDes-142` through `SysDes-148` |
| SysRS-303 through SysRS-304 | Covered by `SysDes-149` through `SysDes-151` |
## Baseline Candidate 0.9.1 Update
@@ -2882,3 +2919,10 @@ This SysDes version covers all known SysRS requirements from `SysRS-001` through
| Version | Date | Description |
|---|---|---|
| 0.9.3 | 2026-05-15 | Added desktop PTT subsystem allocation SysDes-142 through SysDes-148: Platform PTT Backend layer, typed `PttCapabilityLevel` cross-interface, audio-pipeline split into `capture_active` and `transmit_active`, per-OS backend allocation (Windows Raw-Input ladder, macOS permission-aware, Linux GNOME-Wayland portal), diagnostics sanitizer for raw key events, UI capability badge, release-record evidence requirement. |
## Baseline Candidate 0.9.5 Update
| Version | Date | Description |
|---|---|---|
| 0.9.5 | 2026-05-15 | Added v1 audio + PTT lifecycle allocation SysDes-149 through SysDes-151 sourced from SysRS-303 / SysRS-304: `TransmitMode` enum element (`Ptt` / `Continuous` / reserved `VoiceActivity` per DEC-030) at the audio + bridge + UI boundary, audio engine lifecycle bound to voice-channel membership with no manual start affordance and a listen-only path independent of mic permission, hard-mute override element, and the release-tail timer adapter (default 200 ms, range 0500 ms) on `transmit_active`. Strict layered sourcing preserved (`SysDes -> SysRS` only). |