From 5aa51c310f713413c1c76dc9f0d0b42da12a7079 Mon Sep 17 00:00:00 2001 From: EdisonJwa Date: Mon, 18 May 2026 09:52:54 +0800 Subject: [PATCH] docs(p0): SysRS/SysDes/SRS/SAD/SDD/Verification + traceability for Android P0 reconciliation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full P0 Android documentation chain: - SysRS: API 24 → API 28 reconciliation per DEC-004 (SysRS-288); add SysRS-305 (Android in-call audio mode), SysRS-306 (RECORD_AUDIO runtime timing). - SysDes: SysDes-152 (in-call audio mode subsystem), SysDes-153 (RECORD_AUDIO permission flow), SysDes-154 (Android voice audio backend), SysDes-155 (macOS runtime baseline). - SRS: SRS-187 → API 28; add SRS-208 (in-call audio mode), SRS-209 (RECORD_AUDIO + listen-only fallback), SRS-210..215 (Android voice audio backend latency/preset/AEC/usage/sharing/foreground service); retarget SysDes anchors from generic SysDes-135 to SysDes-152/153/154. - SAD: SAD-063 refreshed (API 28); add SAD-084 (audio mode controller), SAD-085 (permission adapter with listen-only), SAD-086 (foreground service), SAD-087 (macOS runtime baseline); formalize cross-cutting + platform-specific allocation pattern in §24.1. - SDD: expand SDD-028 (BackIntentService); refresh SDD-073 (build config); add SDD-105 (JNI bootstrap), SDD-106 (permission requester), SDD-107 (foreground service), SDD-108 (audio mode controller), SDD-109 (AAB pipeline), SDD-110 (PTT capability), SDD-111..116 (Android voice audio backend), SDD-118 (Android bridge build automation), SDD-119 (iOS/macOS bridge build automation back-fill). - Verification: create android-p0-acceptance.md TC-1..TC-18; add SWE4-UV-040..052, SWE5-IV-016..026, SWE6-SV-018..030, SYS4-SIV-015 strengthened + SYS4-SIV-017/018. - Governance: traceability matrix v0.9.9 with end-to-end chain closure; DEC-032 documents the temporary abiFilters reduction to arm64-v8a only and its restore-by gate. Trace: full chain SysRS → SysDes → SRS → SAD → SDD → Code → Verification. --- docs/architecture/sad.md | 68 +- docs/architecture/sdd.md | 534 ++++++++++++- docs/architecture/sysdes.md | 72 +- docs/governance/decision-impact-assessment.md | 8 +- docs/governance/product-decision-register.md | 114 ++- docs/governance/traceability-matrix.md | 704 +++++++++++++++++- docs/requirements/srs.md | 125 +++- docs/requirements/sysrs.md | 19 +- docs/verification/android-p0-acceptance.md | 319 ++++++++ .../swe4-unit-verification-plan.md | 211 +++++- ...-software-integration-verification-plan.md | 130 ++++ .../swe6-software-verification-plan.md | 157 ++++ ...s4-system-integration-verification-plan.md | 43 ++ 13 files changed, 2487 insertions(+), 17 deletions(-) create mode 100644 docs/verification/android-p0-acceptance.md diff --git a/docs/architecture/sad.md b/docs/architecture/sad.md index 4f80854..77a913e 100644 --- a/docs/architecture/sad.md +++ b/docs/architecture/sad.md @@ -3,7 +3,7 @@ **Document type:** SAD / Software Architecture Description **Process alignment:** ASPICE SWE.2 Software Architectural Design -**Version:** 0.9.2 +**Version:** 0.9.8 **Status:** Baseline Candidate **Language:** English **Product:** Chanora @@ -1161,13 +1161,19 @@ Communication evidence is maintained through change history and baseline approva ## 24. SRS-to-SAD Coverage Matrix +### 24.1 Cross-cutting + platform-specific allocations + +A single SRS item may be intentionally allocated to more than one SAD item when it has both a cross-cutting concern (logging, diagnostics, lifecycle, error handling, redaction, permission policy) and a platform-specific architectural realization (Android foreground service, iOS background audio session, macOS permission gating, Linux portal binding, etc.). In that case the SAD records both anchors: the cross-cutting anchor covers the concern at the architecture level, and the platform-specific anchor covers the realization on a particular OS. This is a deliberate dual allocation, not a duplication, and the §24.2 matrix marks affected rows explicitly. Worked example: SRS-111 (foreground voice-session behaviour) is allocated both to SAD-036 (cross-cutting diagnostics / lifecycle isolation across SRS-111..124) and to SAD-086 (Android `AndroidVoiceForegroundService` realization). The cross-cutting anchor is the right place to refine concern-wide rules; the platform-specific anchor is the right place to refine the OS-bound element. + +### 24.2 SRS-to-SAD coverage matrix + | SRS Range | SAD Coverage | |---|---| | SRS-001 through SRS-030 | SAD-032, SAD-039, SAD-040, SAD-041 | | SRS-031 through SRS-060 | SAD-033, SAD-039, SAD-041 | | SRS-061 through SRS-090 | SAD-034, SAD-039, SAD-041 | | SRS-091 through SRS-110 | SAD-035 | -| SRS-111 through SRS-124 | SAD-036, SAD-050 | +| SRS-111 through SRS-124 | SAD-036, SAD-050 (cross-cutting). SRS-111 additionally allocated to SAD-086 (Android-specific realization) per §24.1 dual-allocation pattern. | | SRS-125 through SRS-134 | SAD-037, SAD-049 | | SRS-135 through SRS-143 | SAD-038 | | SRS-144 through SRS-184 | SAD-001 through SAD-031, SAD-039 through SAD-060 | @@ -1202,7 +1208,7 @@ Communication evidence is maintained through change history and baseline approva - Source SRS: SRS-186 - Verification method: Release Inspection -**SAD-063**: The software architecture shall isolate Android runtime minimum version and Play target API compliance in Android build configuration and platform services. +**SAD-063**: The software architecture shall isolate Android runtime minimum version and Play target API compliance in Android build configuration and platform services. The Android minimum runtime baseline is API 28 (Android 9.0) per DEC-004 (Accepted 2026-05-14), which superseded the earlier API 24 recommendation. The `minSdkVersion` / `minSdk` value, the Play target API value, and any conditional platform-capability branching that depends on the minimum baseline shall be expressed in a single Android build-configuration location and consumed by platform services rather than by feature screens; this isolation allows the minimum baseline to be raised, when Flutter, plugin, audio, or platform constraints require it, without touching feature code. - Status: Baseline Candidate - Type: Software Architecture Item @@ -1391,6 +1397,43 @@ Communication evidence is maintained through change history and baseline approva - Source SRS: SRS-205, SRS-207 - Verification method: Unit Test, Integration Test +**SAD-084**: The software architecture shall allocate Android in-call audio-mode management to an `AndroidAudioModeController` element inside the Android platform audio adapter, owned by the audio subsystem and bound to the voice-session lifecycle rather than to any UI screen lifecycle. The controller shall engage Android's voice-communication audio mode (for example `AudioManager.setMode(MODE_IN_COMMUNICATION)` or an equivalent platform routing-assist mechanism) no later than the moment a voice session becomes connected — driven from the same audio-engine lifecycle hook that opens the input/output streams per SAD-081 — so that microphone gain, output routing, echo handling, and Bluetooth SCO behaviour follow Android's voice-communication path rather than the media path. The controller shall snapshot the prior audio mode on engage and restore it on the last voice-session leave. The controller shall be the only writer of the Android audio mode within the application; feature screens, the Flutter UI layer, and the bridge surface shall not reach Android `AudioManager` mode APIs directly. + +- Status: Baseline Candidate +- Type: Software Architecture Item +- Stage: P0 / MVP +- Allocated to: Audio Subsystem, Platform Audio Adapter (Android) +- Source SRS: SRS-208 +- Verification method: Platform Test (Android), Audio Test, Architecture Review + +**SAD-085**: The software architecture shall extend the Android platform permission adapter (within Platform Services) to own the runtime `RECORD_AUDIO` permission flow on behalf of the audio subsystem, and shall recognise listen-only operation (`capture_active = false` while the output stream remains open, consistent with SAD-081 / SRS-204) as a first-class audio operating mode at the architecture layer rather than as an error or degraded state. The permission adapter shall request `RECORD_AUDIO` at or before voice-session activation, shall expose the current permission state to the audio engine and to the Flutter UI through the existing bridge event stream, and shall block microphone capture (`capture_active` shall remain false) when the permission is not granted, revoked, or undecided. In that case, the `TransmitModeSelector` defined in SAD-083 shall remain clamped such that `transmit_active = false` regardless of PTT or Continuous-mode inputs, while the output mix path defined in SAD-081 continues to operate. The Flutter UI shall surface a user-visible path to grant `RECORD_AUDIO` before retrying transmit; the permission adapter shall not auto-retry beyond Android platform conventions. + +- Status: Baseline Candidate +- Type: Software Architecture Item +- Stage: P0 / MVP +- Allocated to: Platform Services (Android Permission Adapter), Audio Subsystem, Flutter UI, Bridge +- Source SRS: SRS-209 +- Verification method: Platform Test (Android), Integration Test, Privacy Review + +**SAD-086**: The software architecture shall allocate Android foreground-service behaviour for active voice sessions to an `AndroidVoiceForegroundService` element owned by the Android platform services container, bound to the voice-session lifecycle (start on first `voice_join`, stop on last `voice_leave`, consistent with SAD-081) rather than to any UI screen lifecycle. The service shall declare the appropriate Android foreground-service type for voice communication, post the required ongoing notification, and coordinate with the `AndroidAudioModeController` (SAD-084) and the Android permission adapter (SAD-085) so that microphone capture and Bluetooth SCO routing survive UI backgrounding and process trimming under Android background-execution restrictions. Feature screens shall not start, stop, or interrogate the foreground service directly; they shall observe voice-session state through `BridgeEvent::VoiceState`. + +- Status: Baseline Candidate +- Type: Software Architecture Item +- Stage: P0 / MVP +- Allocated to: Platform Services (Android), Audio Subsystem +- Source SRS: SRS-111 +- Verification method: Platform Test (Android), Integration Test + +**SAD-087**: The software architecture shall isolate macOS runtime baseline configuration, universal-binary packaging, framework layout, and CocoaPods integration in macOS build configuration and platform services, parallel to SAD-061 (iOS runtime) and SAD-063 (Android runtime). Specifically: (a) the macOS deployment target shall be expressed in a single macOS build-configuration location (currently `10.15`, declared in `apps/chanora_flutter/macos/chanora_bridge.podspec`) and consumed by platform services rather than feature screens, so the minimum macOS version can be raised without touching feature code; (b) the macOS Rust bridge artifact shall be packaged as a universal binary covering both `arm64` and `x86_64` slices, combined via `lipo` into a single dylib so a single signed framework runs on Apple-silicon and Intel Macs; (c) the bridge shall be delivered as a `.framework` bundle using the macOS `Versions/A` layout convention expected by the system loader and code-signing tools; (d) CocoaPods integration shall be driven by a hand-rolled podspec (the same `chanora_bridge.podspec`) maintained and regenerated through release-pipeline build automation rather than authored ad hoc per release. Feature screens, the Flutter UI layer, and the bridge surface shall not reach macOS build settings or framework layout details directly. This item is the macOS counterpart to SAD-061 / SAD-063 and is the proper SAD anchor for downstream detailed-design items concerning macOS bridge build automation, framework layout, and podspec maintenance. + +- Status: Baseline Candidate +- Type: Software Architecture Item +- Stage: P0 / MVP +- Allocated to: macOS Build Configuration, Platform Services (macOS), Release Pipeline +- Source SRS: SRS-013 +- Source SysDes: SysDes-155 (macOS runtime baseline — deployment target, universal-binary packaging, `.framework` `Versions/A` layout, CocoaPods podspec automation) +- Verification method: Platform Test (macOS), Release Inspection, Architecture Review + ## 27. Updated SRS-to-SAD Coverage Matrix | SRS Range | SAD Coverage | @@ -1400,6 +1443,10 @@ Communication evidence is maintained through change history and baseline approva | 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` | +| SRS-208 | Covered by `SAD-084` | +| SRS-209 | Covered by `SAD-085` | +| SRS-111 (Android foreground voice service) | Covered by `SAD-086` (Android-specific allocation; SAD-036 retains the cross-cutting diagnostics allocation for the SRS-111–124 range per §24.1 dual-allocation pattern) | +| SRS-013 (multi-platform support — macOS runtime baseline aspect) | Covered by `SAD-087` (macOS runtime baseline; counterpart to SAD-061 / SAD-063) | ## Baseline Candidate 0.9.1 Update @@ -1435,3 +1482,18 @@ Communication evidence is maintained through change history and baseline approva | 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 0–500 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). | + + +## Baseline Candidate 0.9.6 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.6 | 2026-05-17 | Propagated reconciled P0 Android SRS updates into SAD. SAD-063 text extended in place (ID preserved) to record the API 28 minimum runtime baseline per DEC-004 (Accepted 2026-05-14, superseding the earlier API 24 recommendation), while keeping the existing SRS-187 / SRS-188 trace. Added SAD-084 (Android in-call audio mode controller in the platform audio adapter, bound to the voice-session lifecycle, sourced from SRS-208). Added SAD-085 (Android `RECORD_AUDIO` runtime permission flow on the existing Android permission adapter, naming listen-only — `capture_active = false` with output stream open per SAD-081 — as a first-class operating mode at the architecture layer, sourced from SRS-209). Added SAD-086 (`AndroidVoiceForegroundService` allocation for SRS-111, bound to `voice_join` / `voice_leave` lifecycle rather than UI lifecycle, coordinating with SAD-084 and SAD-085). SAD-018 (Android back intent, SRS-163) and the existing Android AAB allocation in §9 / SAD-037 (SRS-119) were re-read and left unchanged because they remain coherent with the reconciled SRS. Strict layered sourcing preserved (`SAD -> SRS` only). | + + +## Baseline Candidate 0.9.7 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.7 | 2026-05-18 | Formalized the cross-cutting + platform-specific SAD allocation pattern flagged by the traceability-auditor: added §24.1 narrative subsection describing the deliberate dual-allocation rule (cross-cutting concern + platform-specific realization both recorded), with SRS-111 / SAD-036 / SAD-086 as the worked example, and annotated the §24.2 matrix row for SRS-111..124 accordingly. Added SAD-087 (macOS runtime baseline: deployment target isolation, universal-binary `lipo` packaging, `.framework` `Versions/A` layout, hand-rolled CocoaPods podspec automation), parallel to SAD-061 (iOS runtime) and SAD-063 (Android runtime), closing the macOS-runtime anchor gap flagged by the SDD-119 detailed-designer. SAD-087 traces upward through SRS-013 (multi-platform support); a parallel macOS-runtime SysDes derivation does not yet exist and is recorded as an open follow-up for the system-architect (SysDes-133 covers iOS, SysDes-135 covers Android, macOS has no counterpart yet). §27 matrix updated with the SAD-087 row. Strict layered sourcing preserved (`SAD -> SRS` only). | +| 0.9.8 | 2026-05-18 | Retargeted SAD-087's `Source SysDes` field from the "open follow-up — no dedicated macOS-runtime SysDes anchor exists" placeholder to `SysDes-155` (macOS runtime baseline — deployment target, universal-binary `lipo` packaging, `.framework` `Versions/A` layout, CocoaPods podspec automation), authored by the system-architect in SysDes v0.9.7 as the parallel macOS-runtime anchor to SysDes-133 (iOS) / SysDes-135 (Android). This closes the open follow-up that SAD-087 carried at its 0.9.7 authoring, giving SAD-087 a proper upward SysDes trace. Surgical single-field edit: no behavioral content of SAD-087 changed; `Source SRS` retained as `SRS-013` (multi-platform support — Windows, macOS, Linux, iOS, Android), which remains the correct SRS anchor (the SRS-013 vs alternate-anchor question raised during SysDes-155 authoring referred to SysRS-013 / SysRS-002 at the system-requirements layer, not to SRS at the software-requirements layer). §27 SRS-to-SAD coverage matrix row for SRS-013 unchanged because it already cites SAD-087 and remains textually accurate. Strict layered sourcing preserved (`SAD -> SRS`, with SysDes cross-reference now resolved). | diff --git a/docs/architecture/sdd.md b/docs/architecture/sdd.md index c125985..dfc084c 100644 --- a/docs/architecture/sdd.md +++ b/docs/architecture/sdd.md @@ -3,7 +3,7 @@ **Document type:** SDD / Software Detailed Design **Process alignment:** ASPICE SWE.3 Software Detailed Design and Unit Construction -**Version:** 0.9.7 +**Version:** 0.9.13 **Status:** Baseline Candidate **Language:** English **Product:** Chanora @@ -419,10 +419,31 @@ Insets are consumed by shell layout rather than individual feature screens. - Stage: P0 / MVP - Software unit: `BackIntentService` - Source SAD: SAD-018 -- Verification method: Platform Test +- Verification method: Platform Test, Widget Test Back handling shall be testable without direct platform branching in feature screens. +Implementation requirements: + +1. Registration paths (Android, minSdk per SDD-073): + - On API 33+ (`Build.VERSION.SDK_INT >= TIRAMISU`), the host `MainActivity` shall register an `OnBackInvokedCallback` against `onBackInvokedDispatcher` at priority `PRIORITY_DEFAULT`. The callback shall delegate to `BackIntentService.dispatch()` and return without calling the system fallback. The callback shall be unregistered in `onDestroy`. + - On API 28–32 (pre-33 fallback), the host `MainActivity` shall add an `OnBackPressedCallback` (enabled = true) to `onBackPressedDispatcher`. The callback shall delegate to `BackIntentService.dispatch()`. When `dispatch()` returns `BackIntent.exitCandidate`, the callback shall set `isEnabled = false` and re-invoke `onBackPressed()` exactly once so the OS finishes the activity. The callback shall be removed on `onDestroy`. + - The predictive-back gesture animation provided by API 34+ is permitted; `BackIntentService` shall not depend on it and shall remain functional when predictive back is disabled by the OEM. + +2. Route-pop semantics: `BackIntentService.dispatch()` shall resolve the back intent in the following deterministic order and emit exactly one `BackIntent` per platform back event: + a. If the in-call PTT key is currently pressed (transmit_active = true via `AudioTransmitGate.load()` per SDD-089) the service shall return `BackIntent.ignore`. The platform back event shall be consumed without UI effect to prevent accidental disconnection during transmit. + b. Else if a modal or dialog route is open on the Navigator (`Navigator.canPop(context)` true AND topmost route `isDialog == true` or `fullscreenDialog == true`), the service shall return `BackIntent.close` and pop only the modal/dialog. + c. Else if the Navigator can pop a non-root route, the service shall return `BackIntent.pop` and pop one route. + d. Else (at root route, no modal, no PTT), the service shall return `BackIntent.exitCandidate` and defer to the platform fallback (system back / activity finish) per the registration rules above. + +3. Interface output enum: `BackIntent ::= { pop, close, ignore, exitCandidate }` (matches §5 interface catalog). + +4. Threading: `dispatch()` runs on the platform main thread; reads of `AudioTransmitGate` shall use the atomic load path (no blocking await on the bridge isolate). + +5. Diagnostics: each dispatch shall emit a structured event with the resolved intent and the route depth; the event shall not include route arguments, user content, or PTT key identifiers (per SAD-077 / SDD-090 sanitiser rules). + +6. Non-Android platforms: `BackIntentService` shall expose the same Dart interface but the registration path shall no-op; feature screens shall remain unchanged across platforms (single back-intent abstraction). + **SDD-029**: `HapticsService` shall expose platform-aware haptic feedback operations. - Status: Draft @@ -933,14 +954,29 @@ The registry supports consistency between architecture, detailed design, impleme - Source SAD: SAD-062 - Verification method: Release Inspection -**SDD-073**: `AndroidBuildConfig` shall define Android API 24 as the minimum Android runtime baseline and shall expose target SDK configuration for release inspection. +**SDD-073**: `AndroidBuildConfig` shall define Android API 28 (Android 9.0 Pie) as the minimum Android runtime baseline per DEC-004 and shall expose target SDK configuration for release inspection. This supersedes the prior API 24 recommendation and is reconciled with SRS-187 / SysRS-288 / SAD-063. - Status: Draft - Type: Software Detailed Design Item - Stage: P0 / MVP - Software unit: `AndroidBuildConfig` - Source SAD: SAD-063 -- Verification method: Platform Test, Release Inspection +- Verification method: Platform Test, Release Inspection, Build Test + +Implementation requirements (Gradle / `apps/chanora_flutter/android/app/build.gradle.kts`): + +1. `minSdk = 28`. The value shall be sourced from a single `gradle.properties` (or equivalent) constant `chanora.android.minSdk` so that release-inspection tooling can assert it without parsing Kotlin DSL. +2. `targetSdk` policy: `targetSdk` shall be set to the Android API level required by Google Play on the upload date per SRS-188. The build shall not pin `targetSdk` below the Play-required floor. Release inspection shall verify `targetSdk >= ` at build time and fail the build otherwise. +3. `compileSdk` shall be `>= targetSdk` and shall track the latest stable Android SDK supported by the configured Android Gradle Plugin. +4. NDK ABIs: `ndk.abiFilters` shall include exactly `arm64-v8a`, `armeabi-v7a`, and `x86_64`. Other ABIs (e.g., `x86`, `mips`) shall not be packaged. The AAB output (see SDD-107) shall rely on Play's per-ABI delivery rather than a fat APK. +5. Release signing config: release builds shall use a non-debug signing configuration. `AndroidBuildConfig` shall not embed key material in the repository; signing credentials shall be supplied at CI time via environment variables (e.g., `CHANORA_ANDROID_KEYSTORE_PATH`, `CHANORA_ANDROID_KEYSTORE_PASSWORD`, `CHANORA_ANDROID_KEY_ALIAS`, `CHANORA_ANDROID_KEY_PASSWORD`) or a CI secret manager. CI shall fail the release pipeline if any of these variables is unset, if `signingConfig` resolves to `debug`, or if `signingConfig.storeFile` points to a path matching `**/debug.keystore`. Local developer builds may skip the assertion when the Gradle property `chanora.release.signing.required` is false. +6. R8 / ProGuard stance: release builds shall enable R8 (`isMinifyEnabled = true`, `isShrinkResources = true`) with the default Android optimization rules plus a project `proguard-rules.pro` that keeps: + - JNI entry classes for `chanora_bridge` (covered by SDD-105 `AndroidJniBootstrap`), + - Flutter and FRB generated bindings, + - any class referenced solely via reflection from native code. + Debug builds shall set `isMinifyEnabled = false`. +7. AAB output format: the release deliverable shall be the Android App Bundle (`.aab`) produced by `bundleRelease` per SRS-119. APKs shall be produced only for internal QA distribution. See SDD-107 (`AndroidAaBuildPipeline`) for the packaging-pipeline detail. +8. Manifest preconditions: `uses-sdk` shall not override `minSdkVersion` below 28; tooling shall reject any module-level manifest that lowers it. **SDD-074**: `ConnectionSessionManager` shall enforce a single active server connection in MVP. @@ -1225,6 +1261,448 @@ The registry supports consistency between architecture, detailed design, impleme - Source SAD: SAD-061, SAD-065 - Verification method: Platform Test, Audio Test +--- + +### Android P0 Platform Detailed Design (SDD-105 through SDD-110) + +**SDD-105**: `AndroidJniBootstrap` shall own the JNI initialisation surface for `chanora_bridge` on Android, covering native library load, `JNI_OnLoad` `JavaVM*` capture, and `initChanoraContext(Context)` invocation. This item back-fills the existing implementation in `crates/chanora_bridge/src/android_init.rs` and the Kotlin caller currently in `MainActivity.kt`; no implementation work is created by this item, only the design constraints under which the back-filled code shall stabilise. + +- Status: Draft +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: `AndroidJniBootstrap` (`crates/chanora_bridge/src/android_init.rs`, `crates/chanora_bridge/build.rs`, `apps/chanora_flutter/android/app/src/main/kotlin/.../ChanoraApplication.kt`) +- Source SAD: SAD-081, SAD-086 +- Verification method: Platform Test (Android), Inspection +- Back-fill: yes (existing code in `crates/chanora_bridge/src/android_init.rs`, `apps/chanora_flutter/android/app/src/main/kotlin/app/chanora/chanora_flutter/ChanoraApplication.kt` (System.loadLibrary), and `apps/chanora_flutter/android/app/src/main/kotlin/app/chanora/chanora_flutter/MainActivity.kt` (initChanoraContext invocation)) +- Cross-trace: See SDD-118 item 6 (extended) for the runtime staging of `libc++_shared.so` that the `System.loadLibrary` preload depends on; see `crates/chanora_bridge/build.rs` for the DT_NEEDED emission (`cargo:rustc-link-lib=dylib=c++_shared`) that makes the namespace-isolated Android dynamic linker resolve C++ runtime symbols correctly. + +Implementation requirements: + +1. Loader location: `System.loadLibrary("chanora_bridge")` shall be invoked from an `android.app.Application` subclass (e.g., `ChanoraApplication.onCreate()`), not from `MainActivity`. Rationale: `Application.onCreate` runs before any `Activity` is instantiated, guaranteeing the native library is available to early FRB calls, foreground-service callbacks (SDD-107), and any background-trigger paths. The manifest `` attribute shall be set. If a future refactor reintroduces `Activity`-only load, it shall be justified in an ADR. +2. `JNI_OnLoad` (Rust side): on first load the function shall (a) capture `JavaVM*` into a `OnceCell` for later use by audio-engine and foreground-service JNI calls, (b) initialise the `ndk_context` `AndroidContext` with the captured `JavaVM` and a null `Context` placeholder, and (c) return `JNI_VERSION_1_6`. The function shall never panic; any error shall be logged via `android_log` and shall return `JNI_ERR`. +3. `initChanoraContext(context: Context)` shall be called from `ChanoraApplication.onCreate()` exactly once, AFTER `System.loadLibrary` and BEFORE the first FRB call. The Kotlin side shall pass the application `Context` (not an `Activity` context) to avoid leaking activity references into the long-lived JNI singleton. The Rust function shall convert the local-ref `jobject` into a global ref, store it in the `ndk_context::AndroidContext`, and return on success; on failure it shall log and return without crashing. +4. Ordering guarantee: any caller of `chanora_bridge` (FRB-generated bindings, foreground service via SDD-107, audio mode controller via SDD-108) shall be entitled to assume both `JNI_OnLoad` and `initChanoraContext` have completed. The bridge shall expose a debug-only assertion that panics with a clear message if a bridge call observes an uninitialised context. +5. Threading: `initChanoraContext` runs on the Android main thread; subsequent bridge calls may run on any thread. The captured `JavaVM` is process-global and thread-safe; per-thread `JNIEnv` shall be acquired via `JavaVM::attach_current_thread` and released on thread exit (deferred to standard `jni` crate guards). +6. Error / panic surface: Rust panics inside JNI callbacks shall be caught at the FFI boundary (`catch_unwind`) and converted to a logged error plus a returned error code or null; they shall never unwind into the JVM. A panic during bootstrap is treated as a fatal initialisation failure and shall be reported through the existing diagnostics layer. +7. Library name: the canonical library name shall be `chanora_bridge` (resulting `.so` is `libchanora_bridge.so`). Any rename requires an ADR and a coordinated update to this SDD item. + +**SDD-106**: `AndroidPermissionRequester` shall own the Android runtime acquisition of `RECORD_AUDIO` on behalf of the audio subsystem and shall expose the current permission state to the audio engine and to Flutter through the existing `BridgeEvent` stream. Listen-only operation (`capture_active = false` with the output stream open per SAD-081) is a first-class mode, not an error condition, and shall be selected automatically when the permission is not granted. + +- Status: Draft +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: `AndroidPermissionRequester` (Android platform permission adapter) +- Source SAD: SAD-085 +- Verification method: Platform Test (Android), Integration Test, UI Review + +Implementation requirements: + +1. Request timing: the requester shall attempt to acquire `RECORD_AUDIO` at or before the first `voice_join` of the session. The trigger shall fire from the `voice_join` bridge handler before the audio engine opens the input stream, NOT at app launch. Rationale: per Android guidance, runtime permissions should be requested in-context. +2. Denial UX (first-time denial, not "do not ask again"): the audio engine shall clamp `capture_active = false` and `TransmitModeSelector` (per SAD-083 / SDD-095) shall clamp `transmit_active = false`. The output mix path defined in SAD-081 / SDD-094 shall remain open (listen-only). The Voice Bar shall surface a non-blocking affordance "Enable microphone" that re-invokes the requester. +3. Permanent denial path (`shouldShowRequestPermissionRationale` returns false AND permission is denied): the affordance shall switch to "Open settings" and shall launch an `Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)` deep-linked to the Chanora app entry. The requester shall NOT auto-retry beyond Android platform conventions; the user must explicitly re-grant. +4. Mid-session revocation: Android may revoke `RECORD_AUDIO` while the app is running (system kills and restarts the process). On process resume the requester shall re-query `ContextCompat.checkSelfPermission` before resuming capture; on revocation it shall behave identically to first-time denial — clamp `capture_active = false`, keep listen-only, surface the affordance. The active voice session shall not be torn down; only capture is suspended. +5. Bridge event surface: the requester shall publish a `BridgeEvent::PermissionState { permission: "RECORD_AUDIO", state: Granted | Denied | DeniedPermanent | Undetermined }` whenever the resolved state changes. The Flutter UI shall subscribe to this stream alongside `BridgeEvent::VoiceState` (SDD-094) and render the Voice Bar accordingly. +6. Interaction with `TransmitModeSelector` (SAD-083 / SDD-095): the selector shall treat `PermissionState != Granted` as a final clamp identical to hard-mute — `transmit_active = false` regardless of PTT key state, transmit mode, or mute state. When the permission transitions to `Granted`, the selector shall resume normal evaluation on the next state tick; no re-bind of PTT is required. +7. Thread safety: the requester runs on the Android main thread for the request dialog and on the bridge isolate / audio thread for state queries. The cached permission state shall be held in an `AtomicU8` mirroring the four-state enum. +8. Single-flight: concurrent `voice_join` calls (defensive — SRS-189 forbids it but the design must compose) shall coalesce into a single Android permission request; the second caller shall observe the result of the first. +9. Manifest declaration: `` (already required for capture) shall be present; see SDD-107 for the foreground-service permission set. + +**SDD-107**: `AndroidVoiceForegroundService` shall own the Android foreground service lifecycle for active voice sessions. It shall be started by the audio engine on first `voice_join` and stopped on the last `voice_leave` or on engine `shutdown_if_idle` (per SDD-094), shall declare foreground service type `microphone` on API 30+, and shall coordinate with `AndroidAudioModeController` (SDD-108) and `AndroidPermissionRequester` (SDD-106) so capture and SCO routing survive UI backgrounding and process trimming. + +- Status: Draft +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: `AndroidVoiceForegroundService` (Android platform services container) +- Source SAD: SAD-086 +- Verification method: Platform Test (Android), Integration Test, Release Inspection + +Implementation requirements: + +1. Service class: `ChanoraVoiceForegroundService extends android.app.Service`, located in the Android app module under `apps/chanora_flutter/android/app/src/main/kotlin//voice/`. The class shall be `final` and shall not export bindings beyond what is required by the bridge (`onBind` returns null; the service is start-only). +2. Manifest entry: + ```xml + + ``` + The `foregroundServiceType="microphone"` attribute is required by API 30+ (Android 11) and is read by the platform to gate background microphone access. On API 28–29 the attribute is ignored; capture is permitted without it. +3. Required manifest permissions: + - `android.permission.FOREGROUND_SERVICE` (required on API 28+). + - `android.permission.FOREGROUND_SERVICE_MICROPHONE` (required on API 34+ for type=microphone). + - `android.permission.POST_NOTIFICATIONS` (required to display the notification on API 33+; runtime-requested per item 6 below). + - `android.permission.RECORD_AUDIO` (already declared per SDD-106). +4. Notification channel: on first service start the service shall create a notification channel with: + - `id = "chanora.voice.session"` + - `name = "Voice session"` (localised via product strings; not server content) + - `importance = NotificationManager.IMPORTANCE_LOW` (no sound; non-intrusive ongoing indicator) + - description text: "Shown while a Chanora voice session is active." (localised) + The channel is created once via `NotificationManager.createNotificationChannel`; repeated creates are no-ops per Android contract. +5. Ongoing notification: `startForeground(notificationId = 0x4348414E /* "CHAN" */, notification)` shall be called within 5 seconds of `Service.onStartCommand` to satisfy Android's foreground promotion deadline. The notification shall: + - Use the channel from item 4. + - Carry a small icon (`ic_chanora_voice`) and content title "Chanora — Voice session active". + - Carry a content intent that re-opens the host `MainActivity` (`PendingIntent.FLAG_IMMUTABLE` set). + - NOT carry server-provided channel names, user names, or message content (per privacy posture; localised product strings only). + - Be marked `ongoing = true` and `category = CATEGORY_CALL` to encourage the system to keep it visible. +6. `POST_NOTIFICATIONS` (API 33+): the requester pattern in SDD-106 shall be reused. The permission shall be requested when the foreground service is first started in a session; denial shall NOT block the service (the service still runs without a visible notification; Android allows this but UX is degraded). The bridge shall publish a `BridgeEvent::PermissionState { permission: "POST_NOTIFICATIONS", … }` so the UI can prompt the user to grant it. +7. Lifecycle: + - Start: from the `voice_join` handler (after `AndroidPermissionRequester` resolves RECORD_AUDIO state — listen-only IS allowed to start the service so the user can hear audio while backgrounded), call `ContextCompat.startForegroundService(context, intent)`. + - Stop: from the `voice_leave` handler when no voice sessions remain, AND from the engine `shutdown_if_idle()` path (per SDD-094), call `service.stopForeground(STOP_FOREGROUND_REMOVE)` followed by `service.stopSelf()`. The stop shall be idempotent. + - On process death: Android may kill the service. The service shall return `START_NOT_STICKY` from `onStartCommand` so it is not auto-restarted; the next `voice_join` re-starts it explicitly. State is rebuilt from the audio engine, not from the service. + - On user removing the notification: API 34+ allows dismissal of ongoing FGS notifications. Removal shall NOT stop the voice session (only the user explicitly leaving a channel via the UI does that). The service shall re-post the notification on next state tick if still active. +8. Coordination: + - With `AndroidAudioModeController` (SDD-108): the service start happens BEFORE `AudioManager.setMode(MODE_IN_COMMUNICATION)`; the mode controller engages after the service is foreground. On stop, the mode controller restores prior mode BEFORE the service stops. + - With `AndroidPermissionRequester` (SDD-106): the service may start in listen-only mode (RECORD_AUDIO not granted); the foreground-service-type=microphone declaration is still required so that capture can resume immediately when the user grants the permission, without restarting the service. +9. Thread: service lifecycle methods run on the main thread; the service shall not perform audio work itself — the Rust audio engine owns the streams, the service exists purely to keep the process foregrounded. +10. Bridge call path: `voice_join` / `voice_leave` are bridge calls; their Rust handlers shall invoke a Kotlin static method `ChanoraVoiceForegroundService.start(Context)` / `.stop(Context)` via JNI (using the captured `JavaVM` from SDD-105) to avoid coupling the service to a Dart MethodChannel hop. + +**SDD-108**: `AndroidAudioModeController` shall own engagement and release of Android's in-call audio mode for the voice subsystem. It shall snapshot the prior `AudioManager.getMode()` on first engage and restore it on last release, shall be reference-counted across composed sessions (P0 = single session per SRS-189 but design must compose for P1), and shall be the only writer of the Android audio mode within the application. + +- Status: Draft +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: `AndroidAudioModeController` (Android platform audio adapter) +- Source SAD: SAD-084 +- Verification method: Platform Test (Android), Unit Test, Audio Test +- Back-fill: yes (back-fills the v0.9.8 product-decision-register entry for Android in-call audio mode) + +Implementation requirements: + +1. Engagement API (Rust-facing): `AndroidAudioModeController` is a Kotlin singleton with a JNI surface: + - `fn engage() -> Result<(), AudioModeError>`: increments the internal reference count; on the 0 → 1 transition it shall snapshot the current mode (`prior_mode = audioManager.mode`) and call `audioManager.setMode(MODE_IN_COMMUNICATION)`. On counts ≥ 1, it is a no-op success. + - `fn release()`: decrements the reference count; on the 1 → 0 transition it shall call `audioManager.setMode(prior_mode)` and clear `prior_mode`. On counts ≥ 1 after decrement, it is a no-op. On underflow (release without engage) it shall log and clamp at 0 without crashing. + - `fn is_engaged() -> bool`: reads the reference count (debugging / diagnostics only). +2. Reference-count semantics: the count is an `AtomicI32` guarded by a Kotlin `Mutex` for the snapshot/restore critical section. P0 only ever observes counts in `{0, 1}` per SRS-189, but the design must compose for future multi-session use. Any two-engages-one-release sequence shall keep `prior_mode` from the FIRST engage; a release at count 1 restores that original mode. +3. Lifecycle binding: `engage()` shall be called from the audio engine's `ensure_running()` path (per SDD-094) AFTER `AndroidVoiceForegroundService.start` succeeds. `release()` shall be called from `shutdown_if_idle()` BEFORE `AndroidVoiceForegroundService.stop`. The controller is bound to the voice-session lifecycle, NOT to any UI screen lifecycle. +4. JNI surface from Rust audio engine: the Rust audio engine shall hold a `OnceCell` for the Kotlin singleton, populated during `AndroidJniBootstrap` (SDD-105). The Rust-side helpers `android_audio_mode_engage()` / `android_audio_mode_release()` shall acquire `JNIEnv` via `JavaVM::attach_current_thread`, invoke the static methods, and translate any thrown `Exception` into `AudioModeError`. +5. Error paths: + - `AudioManager` unavailable (`context.getSystemService(AUDIO_SERVICE)` returns null — exceedingly rare): `engage()` shall return `AudioModeError::ManagerUnavailable`. The audio engine shall proceed in fallback path (media-mode capture) and shall emit a diagnostic; voice quality may degrade but the session shall not abort. + - `setMode` throws `SecurityException` or `IllegalStateException`: log, return `AudioModeError::SetModeFailed`, leave `prior_mode = null`, do NOT increment the reference count. Subsequent engages may retry. + - Restore failure on `release()`: log; do not retry; do not throw across the JNI boundary. +6. Exclusivity: no other code path in the Android app — Flutter, bridge surface, feature screens — shall call `AudioManager.setMode` directly. A CI lint rule shall flag any direct `setMode` call outside this controller. +7. Thread: all `audioManager.setMode` calls run on the calling JNI thread; Android `AudioManager` is thread-safe for `setMode`. +8. Diagnostics: each engage/release transition shall emit a structured event `{ event: "android.audio_mode.transition", prior: , new: , refcount: }`. No user content; safe under the SAD-077 / SDD-090 sanitiser. + +**SDD-109**: `AndroidAaBuildPipeline` shall define the Android App Bundle (`.aab`) packaging pipeline used for release builds, covering bundletool configuration, ABI splits, signing assertions in CI, and version-code / version-name derivation. + +- Status: Draft +- Type: Software Detailed Design Item +- Stage: P1 / Beta +- Software unit: `AndroidAaBuildPipeline` (CI scripts + Gradle release configuration) +- Source SAD: SAD-037, SAD-063 +- Verification method: Build Test, Release Inspection + +Implementation requirements: + +1. Output artefact: `bundleRelease` Gradle task produces `app-release.aab` at `apps/chanora_flutter/build/app/outputs/bundle/release/app-release.aab`. This artefact is the only Play Console upload candidate. +2. Bundle config (`bundle { … }` in `build.gradle.kts`): + - `language { enableSplit = true }` — per-language asset delivery. + - `density { enableSplit = true }` — per-density resource delivery. + - `abi { enableSplit = true }` — per-ABI native-lib delivery; combined with the SDD-073 `ndk.abiFilters` set this yields three native splits (`arm64-v8a`, `armeabi-v7a`, `x86_64`). +3. bundletool integration: CI shall invoke `bundletool build-apks --bundle=app-release.aab --mode=universal` once per pipeline to produce a universal APK for smoke testing on a known device; this universal APK is not shipped to users. +4. Signing assertions (CI step `assert-android-release-signing`): + - Fail if `signingConfig.storeFile` is unset or resolves to a debug keystore (matches SDD-073 item 5). + - Fail if `jarsigner -verify -verbose -certs app-release.aab` reports the certificate fingerprint does not match the expected production cert pinned in the CI secret store. + - Fail if `bundletool validate --bundle=app-release.aab` reports any error. +5. Version code derivation: `versionCode` shall be derived from the build number, computed as `(MAJOR * 10_000_000) + (MINOR * 100_000) + (PATCH * 1_000) + BUILD_COUNTER`, where MAJOR/MINOR/PATCH come from the workspace `version` field and BUILD_COUNTER is the CI-monotonic counter. The derivation shall be implemented in a Gradle helper so local builds can reproduce the value given the inputs. `versionCode` is a 32-bit signed int; the scheme yields headroom through MAJOR=99. +6. Version name derivation: `versionName = "${MAJOR}.${MINOR}.${PATCH}"` for release builds; `"${MAJOR}.${MINOR}.${PATCH}-rc${BUILD_COUNTER}"` for release-candidate builds; `"${MAJOR}.${MINOR}.${PATCH}-dev"` for local builds. +7. ABI split verification: release inspection shall extract the AAB manifest and assert exactly the three abiFilters from SDD-073 appear in the `` element, and no other ABI directories are present in `BUNDLE-METADATA/native.pb`. +8. Reproducibility: the release pipeline shall record the toolchain identifiers (AGP version, Kotlin version, NDK version, Flutter version, Rust toolchain) into a `build-manifest.json` attached to the release artefact. + +**SDD-110**: `AndroidPttCapability` shall pin Android P0 to `FocusedPttBackend` only. There shall be no `DesktopPttBackend` ladder on Android, no system-wide global key hook, and no global accessibility-service-based PTT. `PttCapabilityLevel` shall always be `L0Focused` on Android, and the capability badge (per SDD-091) shall render the Android-specific explanation text. + +- Status: Draft +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: `FocusedPttBackend`, `PttCapabilityLevel`, `PttCapabilityBadge` +- Source SAD: SAD-071 (PTT-backend isolation behind trait + factory-selected implementation + published capability level — the architectural source of the "Android pins to `FocusedPttBackend` / `L0Focused`" decision), SAD-076 (`BridgeEvent::PttCapability { level, backend_id }` — the capability-event surface feeding the Android capability badge per SDD-091) +- Verification method: Platform Test (Android), UI Review + +Implementation requirements: + +1. Backend selection: on Android, `PttController` (per SDD-088) shall instantiate `FocusedPttBackend` only. The Windows / macOS / Linux backends (SDD-083 through SDD-086) shall not be compiled into the Android target; build configuration shall gate them behind `#[cfg(not(target_os = "android"))]`. +2. Capability report: `current_capability()` shall return `PttCapabilityLevel::L0Focused` unconditionally on Android. `backend_id` shall return `"focused"`. +3. UI rendering: `PttCapabilityBadge` (SDD-091) on Android shall display the short label "PTT (in-app)" and, when the user taps the info icon, shall present an explanation sheet with the localised text: "Push-to-talk on Android works while Chanora is in the foreground. To keep transmitting while another app is on screen, use the on-screen PTT button in the notification or switch to Continuous mode." (Product strings; not server content.) +4. On-screen PTT control: the Voice Bar shall expose a touchscreen PTT button when transmit mode is `Ptt`, sized for thumb reach. The button shall drive the `AudioTransmitGate` (per SDD-089) via the bridge `set_ptt(true|false)` call. Release-tail (SDD-096) applies identically. +5. No accessibility-service request: Chanora shall NOT declare or request `BIND_ACCESSIBILITY_SERVICE` on Android. Any future P1+ proposal to enable global PTT via the accessibility framework shall require an ADR and explicit user-consent UX, and shall NOT default-on. +6. No global media-button capture for PTT: media-button intents (`MediaSessionCompat`) are reserved for transport controls; they shall not be repurposed as a PTT input on Android in P0. +7. Capability badge cross-platform consistency: the badge widget itself is shared per SDD-091; only the per-platform explanation text differs. Android explanation text is owned by `LocalizationService` (SDD-031) under the key `ptt.capability.android.l0`. + +### Android Voice Audio Backend Detailed Design (SDD-111 through SDD-116) + +**SDD-111**: `AndroidVoiceAudioBackend` shall be the Android-side implementation of a new platform-abstracted voice-audio capture+playback element. The cross-platform abstraction shall be expressed as a Rust trait — provisionally `MobileVoiceAudioBackend` — that owns the full lifetime of a paired input/output low-latency voice stream pair (open, start, stop, close, error/disconnect signalling). The Android implementation shall live in a new module within `crates/chanora_audio` (module name `android_voice_unit`; the exact filename is an implementation detail), and shall be selected by `#[cfg(target_os = "android")]` from the audio engine. The iOS module `ios_voice_unit` (per SDD-104) shall be the trait's iOS implementation; back-filling the existing `ios_voice_unit.rs` to conform to `MobileVoiceAudioBackend` is out of scope of this SDD and is deferred to a separate follow-up unit (SDD-117 candidate). Desktop platforms (Windows / macOS / Linux) do not implement `MobileVoiceAudioBackend`; `cpal` continues to own capture and playback on desktop per the existing audio engine design. + +- Status: Draft +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: `MobileVoiceAudioBackend` (trait, in `chanora_audio`), `android_voice_unit` (module, in `chanora_audio`), `AudioEngine` (existing; selects backend by `cfg`) +- Source SAD: SAD-077 (PTT/voice audio component), SAD-081 (audio engine lifecycle) +- Verification method: Unit Test (Rust), Platform Test (Android), Build Verification (cross-platform `cargo build` for android / ios / desktop targets) + +Implementation requirements: + +1. Trait surface (Rust): `MobileVoiceAudioBackend` shall expose, at minimum: `open(config: MobileVoiceStreamConfig) -> Result` (where `MobileVoiceStreamConfig` is the structure defined by SDD-112), `start(&mut self) -> Result<(), BackendError>`, `stop(&mut self) -> Result<(), BackendError>`, `close(self) -> Result<(), BackendError>`, `session_id(&self) -> Option` (for SDD-113 hardware-effect binding), `achieved_performance_mode(&self) -> AchievedPerformanceMode`, `achieved_sharing_mode(&self) -> AchievedSharingMode`, and an error/disconnect callback surfaced through a bounded channel or watch handle that the audio engine polls. The trait shall be object-safe-or-not at implementation discretion; the engine shall instantiate it as a concrete generic, not via `dyn`. +2. Android crate selection (primary): the Android implementation shall be built on `oboe-rs` (version pin: `0.6.x` family; `0.6.1` is the working pin at SDD authoring time). Rationale: `oboe-rs` is a thin Rust wrapper around Google's Oboe C++ library, which is the platform-recommended path for low-latency voice audio on Android (selects AAudio on API 27+ and falls back to OpenSL ES below that, though Chanora's `minSdk = 28` per SDD-073 means the OpenSL ES path is not in scope). +3. Android crate fallback (contingency, NOT to be selected unless required): if `oboe-sys`'s C++ stdlib pull (`libc++_shared.so`) conflicts with the existing `cargo-ndk` + `flutter_rust_bridge` build chain in ways that cannot be resolved by standard `libc++_shared` packaging, the contingency shall be a direct `ndk::audio` (AAudio) binding. The contingency shall NOT be exercised based on developer preference; it shall be exercised only on a documented build-system failure reported by codex-builder. The trait `MobileVoiceAudioBackend` is unchanged by this contingency — only the implementation behind it differs. +4. Engine selection: `AudioEngine` shall, on Android, instantiate `android_voice_unit::AndroidVoiceUnit` (the `MobileVoiceAudioBackend` impl) in place of the existing `cpal` capture+playback pair. The `cpal` code paths shall remain compiled in for desktop targets but shall not be reachable on Android. +5. Mobile-vs-desktop selection sites: the audio engine shall have exactly one `cfg` decision site (a typed alias such as `type PlatformVoice = `). The engine's session lifecycle code (per SAD-081) shall be written against the trait, not against any concrete backend. +6. Error surface: `BackendError` shall be a `chanora_audio`-local error enum carrying at least: `OpenFailed { reason: String }`, `StartFailed { reason: String }`, `ErrorDisconnected`, `InvalidConfig { reason: String }`. The engine shall map `ErrorDisconnected` to its existing retry/reopen path per SDD-094 `ensure_running`; the backend itself shall NOT loop on retries (single-responsibility: the engine owns retries). +7. Tracing / diagnostics: each lifecycle transition (`open`, `start`, `stop`, `close`, `ErrorDisconnected`) shall emit a `tracing` event under the `chanora_audio` target with the achieved performance mode, achieved sharing mode, and (where available) `session_id`. These fields feed SRS-210 verification analysis. + +Notes: + +- SAD anchor caveat: SAD-077 (the PTT/voice audio component) and SAD-081 (the audio engine lifecycle) are the closest existing SAD anchors. There is no dedicated SAD item that names a platform-abstracted "mobile voice audio backend" element by itself; this SDD is sourced from those two anchors. A SAD revision that adds a dedicated allocation item for the mobile-voice-audio-backend trait should be considered as a follow-up so that `Source SAD` for SDD-111 can be tightened beyond the SAD-077 / SAD-081 pairing. + +**SDD-112**: `AndroidVoiceStreamConfig` shall be the immutable, validated configuration passed by the audio engine into `android_voice_unit::AndroidVoiceUnit::open()` (per SDD-111). It shall encode the AAudio / Oboe stream-construction parameters that SRS-210, SRS-211, SRS-213, and SRS-214 obligate the client to request, and shall expose the corresponding "achieved" values after the stream is opened so that the engine and diagnostics can record what the platform actually granted. + +- Status: Draft +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: `MobileVoiceStreamConfig` (struct), `AchievedPerformanceMode` (enum), `AchievedSharingMode` (enum), `AchievedInputPreset` (enum), `AndroidVoiceUnit::open` (consumer) +- Source SAD: SAD-077, SAD-081 +- Verification method: Unit Test (Rust, config-validation paths), Platform Test (Android, achieved-value recording) + +Implementation requirements: + +1. Sample rate: the configuration shall pin the requested sample rate to `48_000` Hz, matching the existing `chanora_audio` `SAMPLE_RATE` engine constant. The Android implementation shall request 48 kHz on both input and output streams; if the platform refuses 48 kHz and offers a different native rate, the implementation shall accept the native rate and the backend shall surface it via `achieved_sample_rate()`. Resampling, if required, shall be performed by the existing cross-platform audio engine path, not inside the backend. +2. Channel layout: both input and output streams shall be opened as mono (1 channel), consistent with the existing Opus 20 ms framing in the cross-platform engine. Stereo is out of scope for P0 voice. +3. Frame size / burst: the implementation shall NOT hard-code a frame size. After stream open, the implementation shall query the stream's `framesPerBurst` (Oboe `AudioStream::getFramesPerBurst()` / AAudio `AAudioStream_getFramesPerBurst`) and shall set the audio callback's processing block size to a multiple of that burst that also satisfies the engine's 20 ms framing (the engine's existing Opus-frame buffering shall absorb the mismatch). +4. Performance mode: the requested performance mode shall be `LowLatency` (`AAUDIO_PERFORMANCE_MODE_LOW_LATENCY`) on both input and output. After open, the implementation shall record `getPerformanceMode()` and expose it as `AchievedPerformanceMode::{LowLatency, None, PowerSaving}`. Per SRS-210, the achieved mode determines which mouth-to-ear latency bound (≤150 ms vs ≤250 ms) applies; the backend itself does not enforce the bound but exposes the data SRS-210 verification requires. +5. Output usage / content type (SRS-213): the output stream shall be opened with usage = `VoiceCommunication` (`AAUDIO_USAGE_VOICE_COMMUNICATION`) and content type = `Speech` (`AAUDIO_CONTENT_TYPE_SPEECH`). These shall NOT be configurable from the engine for P0; they are fixed by SRS-213. +6. Input preset (SRS-211): the input stream shall be opened with input preset = `VoiceCommunication` (`AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION`). If the open call fails specifically due to preset rejection, the implementation shall retry once with `VoicePerformance` (`AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE`). If that also fails for preset reasons, the implementation shall retry with `Generic` (`AAUDIO_INPUT_PRESET_GENERIC`) and log a `warn`-level diagnostic. The achieved preset shall be recorded via `AchievedInputPreset` and exposed to diagnostics. Failures unrelated to preset (e.g., device busy) shall bubble up as `BackendError::OpenFailed` without preset retries. +7. Sharing mode (SRS-214): both input and output streams shall request `Exclusive` (`AAUDIO_SHARING_MODE_EXCLUSIVE`) on a best-effort basis. If the platform returns `Shared` (or refuses Exclusive open with a sharing-mode-specific error), the implementation shall retry once with `Shared` and record the achieved sharing mode via `AchievedSharingMode::{Exclusive, Shared}`. The fall-back to Shared shall NOT be reported to the user as a failure (per SRS-214) and shall not prevent voice session establishment. +8. Error / disconnect callback: the implementation shall register an Oboe `AudioStreamErrorCallback` (or AAudio `AAudioStream_setErrorCallback` equivalent on the contingency path). On `ErrorDisconnected` (e.g., headset unplugged, USB audio interface removed, route invalidated), the callback shall NOT attempt to reopen the stream from inside the callback (Oboe documents this as forbidden). It shall instead enqueue a `BackendError::ErrorDisconnected` onto the engine-visible channel; the audio engine's state machine (per SDD-094 / SAD-081) shall own the reopen retry. +9. Validation: `MobileVoiceStreamConfig::new(...)` shall return `Err(BackendError::InvalidConfig { reason })` for: sample rate outside `{8_000, 16_000, 24_000, 32_000, 44_100, 48_000}`, channel count not equal to 1, or any field deliberately outside the documented enums above. The default constructor used by the audio engine shall always produce a valid config; the validator exists for defence-in-depth and for unit testing. +10. Diagnostics record: after a successful pair of opens, the backend shall emit a single `tracing` event with all achieved values (sample rate, frames-per-burst, performance mode, sharing mode, input preset) so that SRS-210 / SRS-211 / SRS-213 / SRS-214 verification can be performed from logs. + +**SDD-113**: `AndroidHardwareAudioEffects` shall be the element responsible for engaging the Android platform hardware voice-audio effects — `AcousticEchoCanceler`, `NoiseSuppressor`, `AutomaticGainControl` — on the active microphone capture session, and for triggering the cross-platform software fallback when one or more effects are unavailable on the device. It shall close the prior silent no-op gap explicitly called out in SRS-212. + +- Status: Draft +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: `AndroidHardwareAudioEffects` (Rust+JNI shim), Java/Kotlin `android.media.audiofx.AcousticEchoCanceler` / `NoiseSuppressor` / `AutomaticGainControl` consumers, `AudioEngine` (consumer) +- Source SAD: SAD-077, SAD-084 (composes with `AndroidAudioModeController` per SDD-108) +- Verification method: Platform Test (Android, device-matrix from SDD-116), Unit Test (Rust, fallback-path branch coverage) + +Implementation requirements: + +1. Session id acquisition: after `android_voice_unit::AndroidVoiceUnit::open()` (per SDD-111) opens the input stream, the implementation shall call `oboe::AudioStream::getSessionId()` (or the AAudio equivalent `AAudioStream_getSessionId` on the contingency path) to obtain the platform audio session id of the input stream. The input stream MUST be opened with a session id that allows effect attachment (Oboe `setSessionId(SessionId::Allocate)` or AAudio `AAUDIO_SESSION_ID_ALLOCATE`); SDD-112 item 6 is amended by this SDD to also require session-id allocation on the input stream. +2. Effect attachment: with a valid session id, the implementation shall, via JNI (using the captured `JavaVM` from SDD-105), construct each of the three Java effect classes — `AcousticEchoCanceler`, `NoiseSuppressor`, `AutomaticGainControl` — using the static `create(int audioSession)` factory, and shall call `setEnabled(true)` on each successfully constructed effect. The Java `GlobalRef`s shall be retained for the lifetime of the input stream and shall be released via `release()` on stream close. +3. Availability probing: before construction, the implementation shall call the static `isAvailable()` method on each effect class. Effects reported as unavailable shall NOT be constructed; the implementation shall proceed to fallback handling for that specific effect. +4. Construction-failure handling: if `isAvailable()` returns true but `create()` throws or returns null, the implementation shall log a `warn`-level diagnostic that includes the effect name and the exception class (sanitised — no PII), and shall fall back for that effect. +5. Software fallback: where one or more hardware effects are unavailable or fail to construct, the implementation shall signal the cross-platform engine to engage the existing software AEC / NS / AGC path that the engine already exposes via `AudioEngineConfig.effects.aec` / `.noise_suppression`. The engine's Android `#[cfg]` branch at `crates/chanora_audio/src/engine.rs:316-323` shall no longer emit the prior "engagement depends on device AEC/NS support under MODE_IN_COMMUNICATION" log line as a no-op rationale; the rationale is superseded by this SDD, and the branch shall actually engage either hardware (via this SDD) or software (via the existing engine path). The codex-builder shall remove the no-op rationale comment as part of SDD-111..SDD-115 implementation. +6. Composition with SDD-108: hardware-effect engagement requires `AudioManager.setMode(MODE_IN_COMMUNICATION)` to be active for the platform voice-effect routing to engage correctly. The lifecycle order (per SDD-115) shall therefore be: SDD-108 mode engage → SDD-111 stream open → SDD-113 effect attach → start. Effect attach on a stream opened before `MODE_IN_COMMUNICATION` is engaged is not specified by Android and shall be treated as undefined behaviour to be avoided by sequencing, not by retry. See SDD-115 step ordering 4–6 for the canonical implementation sequence (mode engage → streams open → effect attach); this SDD unit (SDD-113 item 6) and SDD-115 are the joint authority on the ordering, and any future amendment to the ordering shall update both units in the same revision. +7. Diagnostics: the implementation shall emit a `tracing` event with three boolean fields — `aec_engaged_hardware`, `ns_engaged_hardware`, `agc_engaged_hardware` — and three corresponding `*_engaged_software` booleans for the fallback path. SRS-212 verification reads these fields from sanitised logs / diagnostics. +8. Failure isolation: failure to engage any single effect shall NOT fail the voice session. Effect engagement is best-effort with software fallback; the only fatal failure path is stream open itself. + +**SDD-114**: `AndroidVoiceManifestRequirements` shall enumerate, at the SDD layer, the Android manifest declarations required for the voice audio backend (SDD-111..SDD-113) and the foreground-service hosting (SDD-107). This unit is a back-fill of declarations already landed by Wave 2B-1; it records the design contract so that future manifest edits do not silently regress the voice subsystem. + +- Status: Draft (back-fill — implementation precedes documentation; see notes) +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: `apps/chanora_flutter/android/app/src/main/AndroidManifest.xml` +- Source SAD: SAD-085 (permission adapter), SAD-086 (foreground service) +- Verification method: Manifest Inspection (CI), Platform Test (Android) + +Implementation requirements: + +1. Required `` declarations (canonical set for voice audio + foreground service): + - `android.permission.INTERNET` — required by the network stack (already declared at the workspace baseline). + - `android.permission.RECORD_AUDIO` — required for microphone capture (per SDD-106). + - `android.permission.FOREGROUND_SERVICE` — required to start any foreground service. + - `android.permission.FOREGROUND_SERVICE_MICROPHONE` — required by API 34+ for `foregroundServiceType="microphone"` (per SDD-107). + - `android.permission.POST_NOTIFICATIONS` — required for the ongoing foreground-service notification on API 33+; this permission is declared in the manifest and is requested at runtime per SDD-106's runtime-request pattern (SDD-107 item 6). + - `android.permission.MODIFY_AUDIO_SETTINGS` — required for `AudioManager.setMode(MODE_IN_COMMUNICATION)` per SDD-108. + - `android.permission.BLUETOOTH_CONNECT` — required on API 31+ for Bluetooth SCO routing; declared with `android:usesPermissionFlags="neverForLocation"` if required by future Play Store policy review. +2. Required `` declaration: the Android manifest shall declare exactly one `` element for the voice foreground service, with attributes: + - `android:name="app.chanora.chanora_flutter.AndroidVoiceForegroundService"` + - `android:exported="false"` + - `android:foregroundServiceType="microphone"` + The fully-qualified class name shall match the Kotlin class introduced by SDD-107; if the application package id changes, the manifest entry and the SDD-107 JNI call site shall be updated together. +3. SDK-gated declarations: SDK-version-conditional declarations (`` / `tools:targetApi`) are NOT required for the canonical set in item 1 under the SDD-073 `minSdk = 28` baseline. The set above is the minimum set required across the SDD-073 SDK range and shall be present unconditionally. +4. CI manifest assertion: a CI manifest-inspection step shall fail the build if any of the permissions or the `` entry in item 1 / item 2 is missing, or if `foregroundServiceType` on the `AndroidVoiceForegroundService` entry is anything other than `microphone` (or a strict superset thereof, e.g., `microphone|connectedDevice` if a future feature legitimately requires it; the CI rule shall use a contains-check, not equality). +5. No additional permissions: the manifest shall NOT declare `BIND_ACCESSIBILITY_SERVICE`, `SYSTEM_ALERT_WINDOW`, or any media-button-related capture permissions for the voice subsystem (consistent with SDD-110 item 5 / item 6). + +Notes: + +- Documentation-after-code situation: the manifest entries described in items 1 and 2 were implemented in Wave 2B-1 (the Android voice foreground service landing wave) before this SDD unit was authored. This SDD unit back-fills the design contract; no new manifest work is created by this unit. Future regressions against item 1 / item 2 shall be treated as SDD-114 violations and shall be caught by the CI assertion in item 4. + +**SDD-115**: `AndroidVoiceLifecycleSequencing` shall fix the sequencing of voice-session lifecycle operations across the elements introduced in SDD-107 (foreground service), SDD-108 (audio mode controller), SDD-111 (voice audio backend), and SDD-113 (hardware audio effects). It shall also specify the responses to Android-level audio events that can interrupt or reshape an active voice session. + +- Status: Draft +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: `AudioEngine` (lifecycle orchestrator), `AndroidVoiceForegroundService` (SDD-107), `AndroidAudioModeController` (SDD-108), `AndroidVoiceUnit` (SDD-111), `AndroidHardwareAudioEffects` (SDD-113), Java `AudioManager.OnAudioFocusChangeListener` +- Source SAD: SAD-081 (engine lifecycle), SAD-084 (mode controller), SAD-086 (foreground service) +- Verification method: Integration Test (Rust + Android), Platform Test (Android, device-matrix from SDD-116) + +Implementation requirements: + +1. Voice-join sequencing (forward order, on first `voice_join`): + 1. Dart calls the bridge `voice_join(channel_id)` per SDD-094. + 2. The Rust bridge handler invokes the JNI helper for SDD-107 to start `AndroidVoiceForegroundService` (Kotlin static method invocation via the captured `JavaVM`). + 3. `Service.onStartCommand` calls `startForeground(notificationId, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE)` within Android's 5-second deadline (SDD-107). + 4. Rust calls SDD-108 to engage `AudioManager.setMode(MODE_IN_COMMUNICATION)` (snapshot prior mode, write new mode). **Mode engagement precedes stream open** so that AAudio's stream construction observes the in-call audio mode and routes to the voice-communication path from the outset. + 5. Rust opens the AAudio input and output streams via SDD-111 (`AndroidVoiceUnit::open(MobileVoiceStreamConfig)`), with the input stream opened with `SessionId::Allocate` (SDD-113 item 1 amends SDD-112). + 6. Rust binds SDD-113 hardware effects against the input stream's session id (with software-AEC fallback on per-effect failure). + 7. Rust calls `AndroidVoiceUnit::start()` on both streams. Active voice session. + + Mode-first ordering note: the AudioManager mode is engaged BEFORE AAudio streams open so that AAudio's stream configuration sees the in-call audio mode at construction time, per SDD-113 item 6. Earlier drafts of this step list had stream open before mode engage (the order was swapped in SDD v0.9.14 — see change history); that ordering was unsafe because some devices route streams to the wrong destination if the mode is set after stream construction, and AEC/NS/AGC effect attachment on a stream constructed under the prior (non-voice) mode is not specified by Android. +2. Voice-leave sequencing (reverse order, on last `voice_leave` or `shutdown_if_idle`): + 1. Rust calls `AndroidVoiceUnit::stop()` on both streams. + 2. Rust releases SDD-113 hardware effects (`release()` on each `GlobalRef`). + 3. Rust calls `AndroidVoiceUnit::close()` on both streams. + 4. Rust calls SDD-108 to restore the prior audio mode. **Mode restore follows stream close** (mirror of the forward order's mode-first rule): the streams are torn down under `MODE_IN_COMMUNICATION` before the mode is reverted, so no live stream observes a mid-flight mode change. + 5. Rust invokes the JNI helper for SDD-107 to call `Service.stopForeground` and `Service.stopSelf`. + The reverse order is mandatory; releasing effects after mode restore, or closing streams after mode restore, is undefined and shall be avoided. +3. Justification for sequencing: step 4 (mode engage) before step 5 (stream open) before step 6 (effect attach) is required because Android platform voice-effect routing only engages correctly under `MODE_IN_COMMUNICATION` (SDD-113 item 6), and AAudio stream construction binds routing decisions based on the audio mode observed at open time. Steps 2/3 (service start before stream open) is required to ensure the foreground-service-type=microphone hosting is in place before any background-eligible capture occurs (SRS-215). +4. Route-change callback handling: the Oboe `AudioStreamDataCallback` does not surface route changes directly; the implementation shall additionally observe `android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY` (BroadcastReceiver) and, where available on API 30+, `AudioDeviceCallback` for device add/remove. On route change, the implementation shall: + - Log the new route at `info` level (sanitised — device type only, no serial numbers). + - Re-evaluate exclusive-vs-shared mode on the next reopen (no in-place sharing-mode change; an actual change requires close + open). + - Not tear down the session for transient routes (e.g., USB headset hot-plug during call) — the engine's `ErrorDisconnected` retry path (SDD-094) handles stream invalidation if it occurs. +5. Audio focus loss handling: the implementation shall request audio focus via `AudioManager.requestAudioFocus(AudioFocusRequest.Builder(USAGE_VOICE_COMMUNICATION).setAcceptsDelayedFocusGain(false).build())` at voice-join (step 4, alongside the SDD-108 mode engage) and shall register an `OnAudioFocusChangeListener`. Listener responses: + - `AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK`: continue capture and playback at full gain (voice call is privileged content; ducking on the voice stream is not appropriate). No state change. + - `AUDIOFOCUS_LOSS_TRANSIENT`: pause capture (set `transmit_active = false` via the existing `TransmitModeSelector` clamp) and pause playback. On subsequent `AUDIOFOCUS_GAIN`, resume both. + - `AUDIOFOCUS_LOSS` (permanent): tear down the voice session — `AndroidVoiceUnit::stop()` and `close()`, release effects, restore mode, stop service. Emit `BridgeEvent::VoiceState { in_channel: false, ... }` so the Flutter UI surfaces "left channel — interrupted by another app." Manual rejoin is required (no auto-rejoin). +6. Bluetooth SCO connection-state-change handling (P0 stance): the implementation shall register a `BluetoothHeadset` connection-state-change `BroadcastReceiver` and shall NOT crash on connect/disconnect events. P0 acceptance is "no crash on connection-state-change during an active voice session." Full SCO route correctness — i.e., guaranteeing that capture and playback routes follow the headset across connect/disconnect during a session — is explicitly deferred to P1 and shall be tracked as a follow-up. The platform `MODE_IN_COMMUNICATION` engagement (SDD-108) typically yields correct SCO routing on most devices; P0 relies on that platform behaviour. +7. Concurrency assumption: all lifecycle transitions in items 1 and 2 are serialised on the audio engine's single lifecycle task (per SDD-094). The JNI helpers for SDD-107 and SDD-108 attach to the current thread on demand (per SDD-108 item 4). The Oboe callbacks (data callback, error callback) run on Oboe's audio thread; they shall NOT call back into the engine lifecycle directly — they shall enqueue events onto a bounded channel that the engine lifecycle task drains. + +**SDD-116**: `AndroidVoiceVerificationMatrix` shall specify the minimum Android device matrix that the SDD-111..SDD-115 implementation must be exercised against before the implementation can be declared verified for the purposes of closing risk RISK-AUDIO-MOBILE-001 and the audio-quality gate G-A5. It shall enumerate the per-device observations that must be recorded so that SRS-210 (latency tier), SRS-211 (input preset), SRS-212 (hardware effects), SRS-213 (output usage), SRS-214 (sharing mode), and SRS-215 (foreground service longevity) verification cases can be evaluated from collected evidence. + +- Status: Draft +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: Verification artefact contract for SWE.4 / SWE.5 / SWE.6 Android voice cases +- Source SAD: SAD-077, SAD-081, SAD-084, SAD-085, SAD-086 +- Verification method: Aggregation — this SDD is itself a contract for verification engineers; the per-case verification is owned by SWE4-UV / SWE5-IV / SWE6-SV ranges cross-linked below. + +Implementation requirements: + +1. Minimum API-level matrix: implementation evidence shall be collected on at least one device at each of the following Android API levels: + - API 28 (Android 9) — the SDD-073 / DEC-004 minimum baseline. Validates that AAudio low-latency mode and `foregroundServiceType="microphone"` (which becomes mandatory only on API 30+) behave correctly when only the older permission-and-service model is enforced. + - API 31 (Android 12) — Bluetooth permission changes (`BLUETOOTH_CONNECT` runtime permission); validates SDD-114 item 1 Bluetooth-permission declaration. + - API 34 (Android 14) — `FOREGROUND_SERVICE_MICROPHONE` permission and `foregroundServiceType="microphone"` enforcement; validates SDD-107 and SDD-114 together. +2. Minimum vendor diversity: the matrix shall include at least one Google Pixel device AND at least one non-Pixel device, with Samsung Galaxy preferred for the non-Pixel slot. Rationale: Samsung's pre-One UI 5 AAudio HAL has historical quirks (specifically, performance-mode downgrades and shared-mode coercions that Pixel devices do not exhibit) that have caused SRS-210-class regressions in comparable Android voice apps. +3. Per-device observations recorded (each device, per voice-session run): + - `getPerformanceMode()` return value for the input stream and the output stream (SDD-112 item 4 / SRS-210). + - `getSharingMode()` return value for the input stream and the output stream (SDD-112 item 7 / SRS-214). + - `getInputPreset()` (or the closest equivalent observable) and the preset attempt sequence (SDD-112 item 6 / SRS-211). + - `AcousticEchoCanceler.isAvailable()`, `NoiseSuppressor.isAvailable()`, `AutomaticGainControl.isAvailable()` return values, and whether each effect's `create()` succeeded and `setEnabled(true)` did not throw (SDD-113 / SRS-212). + - Measured glass-to-glass (mouth-to-ear) latency in milliseconds, using a procedure to be documented by verification-engineer (procedure is out of scope of this SDD; SDD-116 only contracts that the measurement is recorded). SRS-210 is satisfied if the measurement is ≤150 ms on devices where `getPerformanceMode()` returned `LowLatency`, or ≤250 ms otherwise. + - Foreground-service longevity: the voice session shall run continuously for ≥10 minutes with the device screen off and shall not be terminated by Android background-execution restrictions during that window (SRS-215, SWE6-SV-018 precedent). +4. Evidence handling: per-device observations shall be captured by the existing sanitised audio-diagnostics path (per SDD-095 conventions) plus a verification-engineer-owned latency-measurement attachment. Device serial numbers and Bluetooth peer MAC addresses are NOT permitted in the evidence per SDD-077 / PII conventions. +5. Closure rule for gate G-A5 / RISK-AUDIO-MOBILE-001: the gate may be closed when, for every device in the matrix (items 1 and 2), every observation in item 3 is recorded AND every SRS bound (SRS-210..SRS-215) is met on that device. A single device failing the SRS-210 latency bound shall block gate closure for the gate's affected platform tier; a device failing only the SRS-214 sharing-mode bound (i.e., achieving Shared rather than Exclusive) shall NOT block closure (graceful fallback is explicitly permitted by SRS-214). +6. Verification ID cross-link: this SDD is the design-layer counterpart to the following verification ranges (cross-link only; verification IDs are owned by the verification documents): + - SWE.4 unit verification: SWE4-UV-040..046 (Android audio backend unit cases). + - SWE.5 integration verification: SWE5-IV-016..020 (Android audio engine + foreground service integration cases). + - SWE.6 software qualification: SWE6-SV-018..024 (Android voice session longevity, latency, effect engagement, sharing-mode fallback). + - SYS.4 system integration: cross-link to SYS4-SIV cases for Android voice end-to-end where applicable. + +### Chanora Bridge Build Automation Detailed Design (SDD-118, SDD-119) + +ID allocation note: SDD-117 is reserved (per the SDD-111 trait-back-fill note) for a future `ios_voice_unit` adapter onto `MobileVoiceAudioBackend` and is intentionally left unauthored at this revision. The next two free IDs are therefore SDD-118 and SDD-119; this revision allocates both for the `chanora_bridge` Rust cdylib build-automation pair. + +**SDD-118**: `AndroidBridgeBuildAutomation` shall specify how the `chanora_bridge` Rust cdylib is produced and staged for the Android build automatically, without manual `cargo-ndk` invocations and without environment mutation. This unit complements SDD-073 (Android build config), SDD-109 (AAB packaging pipeline), and SDD-105 (JNI bootstrap — the same `libchanora_bridge.so` is the one that `System.loadLibrary` resolves at process start). The Android pattern intentionally diverges from the always-`--release` iOS / macOS automation pattern (SDD-119) on the iteration-time profile axis only; the release-build safety property (release Android build always uses release Rust) is preserved. + +- Status: Draft +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: Gradle task set in `apps/chanora_flutter/android/app/build.gradle.kts` driving `cargo-ndk` against `crates/chanora_bridge`, and the resulting per-ABI `.so` staging under `apps/chanora_flutter/android/app/src/main/jniLibs/` +- Source SAD: SAD-063 (Android runtime / build-configuration isolation) +- Cross-trace SDD: SDD-073 (Android build config — minSdk, ABI filters), SDD-105 (JNI bootstrap — consumer of the produced `.so`), SDD-109 (AAB pipeline — downstream packaging consumer of the staged jniLibs) +- Verification method: Build-system Test (Gradle task wiring, up-to-date semantics), Platform Test (Android — verify per-ABI `.so` present in produced AAB / APK and loadable per SDD-105), Unit Test (preflight failure-mode messages) + +Implementation requirements: + +1. Gradle task registration: a Gradle task or task set shall be registered in `apps/chanora_flutter/android/app/build.gradle.kts` that invokes `cargo-ndk` (a single invocation; see item 13) to build the `chanora_bridge` cdylib for the configured ABI set. The task shall be a Kotlin DSL registration on the Android app module, not a standalone plugin and not an external script. +2. Minimum API source of truth: the API level passed to `cargo ndk --platform $minSdk` shall be sourced from the `chanora.android.minSdk` Gradle property mandated by SDD-073 item 1, defaulting to `28` per DEC-004, overridable via `-Pchanora.android.minSdk=…` on the command line or via `gradle.properties`. The task shall NOT hard-code a numeric API level and shall NOT introduce a second source of truth. +3. ABI set: the ABI set shall be `arm64-v8a`, `armeabi-v7a`, `x86_64`. The list shall be derived at task-configuration time from the existing `android.defaultConfig.ndk.abiFilters` declaration owned by SDD-073 item 4 so that the build-automation list and the AGP packaging list are guaranteed in sync. No `x86` (32-bit) ABI shall be built — Android x86 32-bit is explicitly out of scope per the stakeholder decision recorded by this SDD. "Universal" on Android means an APK/AAB containing all three per-ABI `.so` files; Android does NOT lipo (this is the fundamental mechanism divergence from SDD-119 macOS). +4. Profile mapping (divergence from SDD-119): the cargo profile shall be selected from the active Gradle build variant — Gradle `debug` variant invokes `cargo build` (no `--release`), Gradle `release` variant invokes `cargo build --release`. This diverges from the SDD-119 iOS / macOS always-`--release` pattern. Rationale: Android developer-iteration UX (a clean debug build of `chanora_bridge` in release profile adds a multi-minute wall-clock cost per iteration that iOS / macOS users do not pay because Xcode's debug build is similarly already release-bridged). Safety property preserved: a Gradle `release` build is observably guaranteed to ship a `--release` Rust cdylib (item 10 release-inspection assertion enforces this). +5. Environment variables on the `cargo-ndk` invocation: the task shall set, on the child-process environment only (no parent-shell mutation), `LIBOPUS_STATIC=1`, `LIBOPUS_NO_PKG=1`, and `CMAKE_POLICY_VERSION_MINIMUM=3.5`. Rationale: the Android NDK sysroot ships no libopus, so the `audiopus_sys` crate must build libopus from source statically (mirrors SDD-119 macOS env). `ANDROID_NDK_HOME` shall be propagated from the Flutter / AGP-resolved NDK location (`flutter.ndkVersion` / `android.ndkVersion` resolution) if and only if not already present in the process environment; the task shall NOT override an externally provided `ANDROID_NDK_HOME`. +6. Output staging (per-ABI plain copy, no symlinks, no fat binary): after a successful `cargo-ndk` build, each per-ABI artefact shall be copied to its Android-ABI-named jniLibs directory using the following Rust-triple ↔ Android-ABI mapping: + - `target/aarch64-linux-android//libchanora_bridge.so` → `apps/chanora_flutter/android/app/src/main/jniLibs/arm64-v8a/libchanora_bridge.so` + - `target/armv7-linux-androideabi//libchanora_bridge.so` → `apps/chanora_flutter/android/app/src/main/jniLibs/armeabi-v7a/libchanora_bridge.so` + - `target/x86_64-linux-android//libchanora_bridge.so` → `apps/chanora_flutter/android/app/src/main/jniLibs/x86_64/libchanora_bridge.so` + where `` is `debug` or `release` per item 4. Each `.so` is independent; there is no fat-binary equivalent. The copy shall overwrite any prior staged `.so` at the destination. + + Item 6 (extended): in addition to staging `libchanora_bridge.so`, the build automation shall co-stage `libc++_shared.so` from the active NDK sysroot into the same per-ABI `jniLibs//` directory. The source path pattern shall be `$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib//libc++_shared.so`, with the following sysroot-triple ↔ Android-ABI mapping (note this mapping differs from the cargo Rust-triple mapping in item 6 — specifically, `armeabi-v7a` maps to `arm-linux-androideabi` for the NDK sysroot path, NOT `armv7-linux-androideabi`): + - `arm64-v8a` → sysroot triple `aarch64-linux-android` + - `armeabi-v7a` → sysroot triple `arm-linux-androideabi` (NOT `armv7-linux-androideabi`) + - `x86_64` → sysroot triple `x86_64-linux-android` + + Rationale: SDD-105's `System.loadLibrary("c++_shared")` preload (performed by `ChanoraApplication.onCreate()` before `System.loadLibrary("chanora_bridge")`) combined with the `crates/chanora_bridge/build.rs` directive `cargo:rustc-link-lib=dylib=c++_shared` together ensure that `DT_NEEDED libc++_shared.so` is baked into `libchanora_bridge.so`; the runtime `.so` must therefore be co-staged into `jniLibs//` so Android's per-library namespace linker (API 24+) can resolve `__cxa_pure_virtual` and other C++ runtime symbols at load time. Without co-staging, `System.loadLibrary("chanora_bridge")` fails with an unresolved-symbol error on devices where the system libc++ is not visible to the app's linker namespace. Cross-trace: SDD-105 (load order — `c++_shared` must be loaded BEFORE `chanora_bridge`), SDD-111 (oboe-rs C++ dependency that introduces the C++ runtime requirement), SDD-118 item 10 (release-inspection assertion shall include `libc++_shared.so` as well as `libchanora_bridge.so`). +7. Task-graph wiring: the build task(s) shall execute before AGP's jniLib merge step, wired via `tasks.matching { it.name.startsWith("merge") && it.name.endsWith("JniLibFolders") }.configureEach { dependsOn() }`. The task shall NOT be wired via `preBuild.dependsOn(...)` (preBuild fires too early in the AGP graph and runs even for non-build configurations such as IDE sync, which would force unnecessary cargo invocations). +8. Gradle up-to-date semantics: the task shall declare, for Gradle incremental-build correctness: + - `inputs.dir("$rootProject/crates")` — Rust source tree + - `inputs.file("$rootProject/Cargo.toml")` — workspace manifest + - `inputs.file("$rootProject/Cargo.lock")` — dependency-version pin + - `outputs.files()` + Cargo's own incremental cache layers on top; both Gradle and cargo must independently agree that nothing has changed for the task to skip cleanly. (Path roots in this item are illustrative; the implementation shall resolve them against the actual workspace layout rather than hard-coding the literal `$rootProject` string.) +9. Preflight (no auto-install): at task-configuration or task-action time, the task shall check that `cargo` is present, that `cargo-ndk` is present, and that the three rustup targets `aarch64-linux-android`, `armv7-linux-androideabi`, `x86_64-linux-android` are installed. On any missing prerequisite the task shall fail with an actionable error message that includes the exact remediation commands — at minimum `cargo install cargo-ndk` and `rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android`. The task shall NOT auto-install any of these tools and shall NOT mutate the user's PATH, `~/.cargo/`, or `~/.rustup/` state. +10. Release-inspection assertion: immediately before the AGP `bundleRelease` or `bundleDebug` task runs (wired via the same `mergeXxxJniLibFolders` graph node as item 7, or by a sibling `doFirst` on the bundle task), each expected `.so` destination from item 6 shall be asserted to exist and to have a size greater than 1 KiB. If any `.so` is missing or trivially small, the build shall fail with a clear message naming the missing ABI and the expected path. Rationale: catches silent cargo-ndk task skips or empty-link-output regressions before they ship into an AAB. +11. Idempotency: if all three staged `.so` files are newer than every declared input file (item 8) AND cargo's own incremental cache confirms no Rust input changed, both the `cargo-ndk` invocation and the per-ABI copy step shall be skipped. The interaction model is layered — Gradle's `inputs`/`outputs` gating decides whether the task runs at all; if the task does run, cargo's own incremental decides whether it actually relinks. Both must agree on "no change" for the full no-op path. +12. Caching across clean builds: the cargo `target/` directory lives at the repository root (outside `apps/chanora_flutter/android/`) and is OUTSIDE the Gradle `clean` scope. `./gradlew clean` MUST NOT remove the cargo `target/` cache. The task shall NOT register `target/` (or any subpath of it) as a Gradle build output, since doing so would make Gradle's `clean` delete it. The implementation shall document this constraint inline in the build script for future maintainers. +13. Parallelism stance: the baseline implementation shall use a single `cargo-ndk` invocation with `-t arm64-v8a -t armeabi-v7a -t x86_64` (cargo-ndk iterates ABIs sequentially internally), matching the single-shot model used by the SDD-119 iOS / macOS automation. The implementation shall NOT split per-ABI into parallel Gradle tasks at this revision. Revisit only if dev-cycle wall-clock cost becomes observably painful; a future SDD revision may relax this stance. +14. Cross-platform contract scope: SDD-118 is Android-only. iOS and macOS bridge-build automation is owned by SDD-119. The two units share no files and no tasks; their only shared contract is the principle that the `chanora_bridge` Rust cdylib is auto-built and embedded by the platform's native build system without manual developer steps. The mechanisms (Gradle Exec task + cargo-ndk + jniLibs copy here, vs. CocoaPods podspec + cargo + `lipo` + framework-layout shell there) reflect platform conventions and are intentionally independent. + +Notes: + +- The Android automation is hand-rolled in the same spirit as the iOS / macOS podspecs (SDD-119): no cargokit, no rinf, no custom xtask wrapper at this revision. A future SDD revision may unify all three platforms under one cross-platform Rust-cdylib build tool; the explicit decision at this revision is to match the existing iOS / macOS pattern so that a single mental model covers all three platforms. +- The Android profile-mapping divergence from the iOS / macOS always-`--release` pattern (item 4) is deliberate and is the only material design divergence between SDD-118 and SDD-119. It is documented here as a known asymmetry rather than as a latent inconsistency. + +**SDD-119**: `iOSAndMacOSBridgeBuildAutomation` shall record, as a documentation-after-code back-fill, the existing CocoaPods-podspec-driven automation that builds the `chanora_bridge` Rust cdylib for iOS and macOS and embeds the result as an `@rpath`-based `.framework` consumed by the Flutter Runner via `vendored_frameworks`. The implementation already exists in code under `apps/chanora_flutter/ios/chanora_bridge.podspec` and `apps/chanora_flutter/macos/chanora_bridge.podspec`; this SDD unit anchors the design contract that has lived in code without an SDD / SAD anchor since landing. + +- Status: Draft (back-fill — implementation already exists) +- Type: Software Detailed Design Item +- Stage: P0 / MVP +- Software unit: `apps/chanora_flutter/ios/chanora_bridge.podspec` (iOS — single arch `aarch64-apple-ios`, flat framework), `apps/chanora_flutter/macos/chanora_bridge.podspec` (macOS — dual arch `aarch64-apple-darwin` + `x86_64-apple-darwin` joined via `lipo`, `Versions/A` framework layout), plus their Podfile entries (iOS Podfile line 41; macOS Podfile line 34). +- Source SAD: SAD-061 (iOS runtime), SAD-062 (App Store / packaging). No dedicated macOS-runtime SAD anchor exists; the macOS half of this unit is sourced by parallelism from SAD-061 / SAD-062 pending a SAD revision that adds a macOS-runtime allocation item (flagged in §11 coverage matrix). +- Cross-trace SDD: SDD-104 (`ios_voice_unit` / VPIO — consumer of the produced iOS framework), and any iOS audio-related SDD downstream of SDD-104 by transitive use of the same `.framework`. +- Verification method: Build-system Test (CocoaPods integration on a clean derived-data state), Platform Test (iOS device build, macOS desktop build, both with `chanora_bridge` symbols resolving at runtime). + +Implementation requirements (back-fill of existing podspec automation): + +1. iOS pattern — `apps/chanora_flutter/ios/chanora_bridge.podspec` (file ranges per the researcher's report): + - Hand-rolled podspec wired into the iOS app via the Podfile (iOS Podfile line 41). + - Dual-hook automation: `prepare_command` (podspec lines 51–97) runs at `pod install` time; `script_phase :before_compile` (podspec lines 114–147) runs at every Xcode build. Both hooks call into `cargo build --release --target aarch64-apple-ios -p chanora_bridge`. + - Single-architecture build: `aarch64-apple-ios` only (real arm64 devices). The simulator slice (`aarch64-apple-ios-sim`) is NOT actually built by the script despite a comment in the podspec referring to it (see item 4 doc-drift note). + - Always `--release` profile (the SDD-118 Android variant-driven profile mapping is NOT used here). + - Environment on the cargo invocation: `IPHONEOS_DEPLOYMENT_TARGET=13.0`, `CMAKE_POLICY_VERSION_MINIMUM=3.5`, `CMAKE_OSX_DEPLOYMENT_TARGET=13.0` (the last is set because the cargo cmake bridge keys off the OSX variant when building from a macOS host even for an iOS target). + - Output staging: `libchanora_bridge.dylib` is copied into a flat (non-versioned) `.framework` bundle. A hand-written `Info.plist` is emitted into the framework. `install_name_tool -id @rpath/chanora_bridge.framework/chanora_bridge` rewrites the `LC_ID_DYLIB` so the framework is `@rpath`-relocatable. + - CocoaPods `vendored_frameworks` directive auto-embeds the produced `.framework` into the Runner.app target via the standard CocoaPods embed-frameworks build phase; no additional Xcode-project edits are required. + - Caching: cargo's own incremental cache in `target/` plus a timestamp guard inside the `script_phase` skips rebuild when sources are unchanged. + +2. macOS pattern — `apps/chanora_flutter/macos/chanora_bridge.podspec` (file ranges per the researcher's report): + - Parallel hand-rolled podspec wired into the macOS app via the Podfile (macOS Podfile line 34). + - Same dual-hook structure (`prepare_command` + `script_phase :before_compile`). + - Dual-architecture build: `aarch64-apple-darwin` AND `x86_64-apple-darwin`, then `lipo -create` joined into a single universal binary. This is the "universal" mechanism — a single fat binary at the dylib layer. (This is the fundamental mechanism divergence from SDD-118 Android, where there is no `lipo`.) + - Always `--release` profile. + - Environment on the cargo invocation: `MACOSX_DEPLOYMENT_TARGET=10.15`, `LIBOPUS_STATIC=1`, `LIBOPUS_NO_PKG=1`, `CMAKE_POLICY_VERSION_MINIMUM=3.5`. Rationale for `LIBOPUS_STATIC=1` / `LIBOPUS_NO_PKG=1`: avoid relying on a Homebrew-installed `libopus` and `pkg-config` on the build host (developer-machine variance); also matches the SDD-118 Android stance for the same reason (NDK sysroot has no libopus). + - Output staging: the universal `libchanora_bridge.dylib` is placed inside a macOS-style `Versions/A` framework layout (`chanora_bridge.framework/Versions/A/chanora_bridge` plus the standard `Current` / top-level symlinks). `install_name_tool -id @rpath/chanora_bridge.framework/Versions/A/chanora_bridge` sets the install name to the versioned framework path. + - CocoaPods `vendored_frameworks` integration is identical to iOS in mechanism. + - Caching: cargo incremental + per-platform timestamp guard inside the `script_phase`, identical to iOS. + +3. Deployment-target source-of-truth (open improvement, not blocking this back-fill): `IPHONEOS_DEPLOYMENT_TARGET=13.0` is currently hard-coded in the iOS podspec; `MACOSX_DEPLOYMENT_TARGET=10.15` is currently hard-coded in the macOS podspec. There is no shared properties file that both podspecs read. A future SDD revision may move both to a single shared config artefact (analogous to how SDD-073 / SDD-118 item 2 isolate the Android `minSdk` to one Gradle property). Flagged here; NOT required to fix as part of this back-fill. + +4. iOS simulator slice — RESOLVED (decision: Option α, drop the misleading comment): P0 scope intentionally excludes `aarch64-apple-ios-sim`; the iOS podspec comment at `apps/chanora_flutter/ios/chanora_bridge.podspec:9-10` referencing simulator support is to be removed in a follow-up commit so the in-file docstring matches the actual cargo invocation (which builds only `aarch64-apple-ios`). Rationale for choosing Option α over adding the sim slice: P0 minimalism — simulator support is a P1 concern; expanding the iOS build matrix at P0 expands scope without serving a P0 verification need. Implication for downstream: building the Runner for an iOS simulator currently relies on Xcode's own handling of an arm64-device-only framework on Apple-silicon hosts; Intel-host iOS simulator builds are not supported by this automation as written. A future SDD revision (P1) may add the simulator slice; until then, the podspec comment shall be dropped to eliminate the doc-drift defect. + +5. Caching mechanism (unified across iOS / macOS): cargo's incremental cache in `target/` provides Rust-source-level caching; a per-platform timestamp guard inside each `script_phase` provides the outer CocoaPods-script-level skip. As with SDD-118 item 12, the `target/` directory lives at the repository root and is OUTSIDE both Xcode's derived-data cleanup and `pod deintegrate`'s footprint; neither should remove cargo's cache. + +6. Preconditions and failure modes: + - `cargo` is expected at `$HOME/.cargo/bin/cargo` (the rustup default). The podspec adds `$HOME/.cargo/bin` to PATH for the script's child process; it does NOT mutate the developer's shell profile. + - The rustup targets `aarch64-apple-ios`, `aarch64-apple-darwin`, and `x86_64-apple-darwin` MUST be installed (`rustup target add ...`). Missing targets surface as cargo errors during the prepare_command / script_phase. + - A CMake 4.x toolchain MUST be present on the build host because `CMAKE_POLICY_VERSION_MINIMUM=3.5` is the workaround for CMake-4-introduced policy strictness in the `audiopus_sys` / libopus build chain; on a CMake 3.x host the env var is a no-op and the build still succeeds, but the variable's documented purpose only applies to CMake 4.x. + - The same `LIBOPUS_STATIC` / `LIBOPUS_NO_PKG` rationale as SDD-118 item 5 applies on macOS (and is set in the macOS podspec env); on iOS, the audiopus_sys build path for the iOS target also statically links libopus from source by default. + +7. Why hand-rolled and not Cargokit: per the podspec's own header comment, this automation is "written by hand … to avoid the Cargokit vendoring footprint we previously dropped." Maintenance posture: the podspec is project-maintained; there is no upstream-tracked build helper to follow. Bit-rot risk is currently low (the surface area is small — cargo invocation + lipo + install_name_tool + framework layout) but will rise if Apple changes framework-embedding conventions or if `audiopus_sys` changes its build-script interface. The risk is accepted at this revision. + +8. Relationship to SDD-118 (Android counterpart): SDD-118 and SDD-119 implement the same principle — the `chanora_bridge` Rust cdylib is auto-built and embedded by the platform's native build system without manual developer steps — through different mechanisms. SDD-118 uses Gradle + `cargo-ndk` + per-ABI plain-copy into `jniLibs/`; SDD-119 uses CocoaPods podspec + `cargo` (+ `lipo` on macOS) + framework-layout shell. The pair is intentionally not unified at this revision (open follow-up: a future SDD may collapse them under one cross-platform tool). The Android profile-mapping divergence (SDD-118 item 4) and the macOS multi-arch / lipo divergence (item 2 above) are the two material design differences between the units; both are documented as deliberate. + +Notes: + +- This is a back-fill SDD. No new implementation work is created by SDD-119; the code already exists and ships. A builder agent does NOT need to act on SDD-119. The doc-drift defect in item 4 and the deployment-target single-source-of-truth improvement in item 3 are recorded as open follow-ups and are not in scope for this unit. +- SAD-side anchoring of the build-automation pattern at the architectural layer is NOT performed by this SDD unit; a software-architect dispatch is required if the orchestrator wants a SAD-layer build-automation item. + ## 11. Updated SAD-to-SDD Coverage Matrix | SAD Range | SDD Coverage | @@ -1232,6 +1710,11 @@ The registry supports consistency between architecture, detailed design, impleme | SAD-001 through SAD-060 | Covered by inherited SDD baseline `SDD-001` through `SDD-070` | | SAD-061 through SAD-070 | Covered by `SDD-071` through `SDD-080` | | SAD-061 through SAD-070 | Covered by `SDD-098` through `SDD-104` (iOS platform additions) | +| SAD-063, SAD-081, SAD-084, SAD-085, SAD-086 | Covered by `SDD-105` through `SDD-109` (Android P0 additions; SDD-110 re-anchored to SAD-071 / SAD-076 in v0.9.11 because the PTT-capability pin is a PTT-architecture concept, not a foreground-service-allocation concept) | +| SAD-071, SAD-076 | Covered by `SDD-110` (Android pins to `FocusedPttBackend` / `L0Focused`; capability badge text feed via `BridgeEvent::PttCapability`). Cross-trace: SDD-088 (`PttController`), SDD-091 (`PttCapabilityBadge`), SDD-031 (`LocalizationService`). | +| SAD-077, SAD-081, SAD-084, SAD-085, SAD-086 | Covered by `SDD-111` through `SDD-116` (Android voice audio backend — SRS-210..SRS-215). No dedicated SAD anchor exists for the platform-abstracted mobile-voice-audio-backend trait itself; SDD-111 is sourced from SAD-077 / SAD-081 pending a SAD revision that adds a dedicated allocation item. | +| SAD-063 | Covered by `SDD-118` (Android `chanora_bridge` cdylib build automation — Gradle + `cargo-ndk` + per-ABI jniLibs staging). Cross-trace: SDD-073, SDD-105, SDD-109. | +| SAD-061, SAD-062, SAD-087 | Covered by `SDD-119` (iOS / macOS `chanora_bridge` cdylib build automation — CocoaPods podspec + `cargo` + `lipo` + framework-layout shell, back-fill of existing code). Source SAD allocation: iOS half anchored to SAD-061 (iOS runtime) and SAD-062 (App Store / packaging); macOS half anchored to SAD-087 (macOS runtime baseline — deployment target isolation, universal-binary `lipo` packaging, `.framework` `Versions/A` layout, hand-rolled CocoaPods podspec automation), authored in SAD v0.9.7 to close the macOS-runtime anchor gap that this SDD unit had previously bridged by parallelism from SAD-061 / SAD-062. SDD-117 remains reserved-but-unauthored for the deferred `ios_voice_unit` trait back-fill noted by SDD-111. | ## Baseline Candidate 0.9.1 Update @@ -1279,3 +1762,46 @@ The registry supports consistency between architecture, detailed design, impleme | Version | Date | Description | |---|---|---| | 0.9.7 | 2026-05-17 | Added design notes to SDD-101: (1) `shouldResume == false` behavior for iOS audio interruption recovery — VPIO remains paused, `BridgeEvent_InterruptionState` carries `shouldResume: false` to Flutter, snackbar surfaces the interruption, manual rejoin required; (2) iOS audio lifecycle interleaving concern — handlers are individually serialized but lack explicit lifecycle state, route-change-during-interruption risk is mitigated by iOS main-thread serialization for P0. | + +## Baseline Candidate 0.9.9 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.9 | 2026-05-18 | Added Android voice audio backend detailed design SDD-111 through SDD-116 sourced from SRS-210..SRS-215 (Wave 2A SRS landing) and SAD-077 / SAD-081 / SAD-084 / SAD-085 / SAD-086. SDD-111 (`AndroidVoiceAudioBackend` — `MobileVoiceAudioBackend` Rust trait, `android_voice_unit` module, `oboe-rs 0.6.x` primary with direct `ndk::audio` contingency, desktop `cpal` unaffected; iOS trait back-fill deferred to candidate SDD-117). SDD-112 (`AndroidVoiceStreamConfig` — 48 kHz mono, `framesPerBurst` query, requested `LowLatency` performance mode with achieved-mode recording, output `VoiceCommunication` usage + `Speech` content type, input preset `VoiceCommunication` → `VoicePerformance` → `Generic` retry ladder, `Exclusive` → `Shared` sharing-mode fallback, error/disconnect callback enqueues to engine retry path). SDD-113 (`AndroidHardwareAudioEffects` — session-id-bound `AcousticEchoCanceler` / `NoiseSuppressor` / `AutomaticGainControl` engagement via JNI with per-effect software fallback through the existing engine path; closes the prior silent no-op at `crates/chanora_audio/src/engine.rs:316-323`). SDD-114 (`AndroidVoiceManifestRequirements` — back-fill of Wave 2B-1 manifest declarations: INTERNET, RECORD_AUDIO, FOREGROUND_SERVICE, FOREGROUND_SERVICE_MICROPHONE, POST_NOTIFICATIONS, MODIFY_AUDIO_SETTINGS, BLUETOOTH_CONNECT; ``; CI manifest assertion). SDD-115 (`AndroidVoiceLifecycleSequencing` — forward order Dart→bridge→service start→stream open→mode engage→effect bind→start; reverse order on leave; route-change observation via `ACTION_AUDIO_BECOMING_NOISY` + `AudioDeviceCallback`; audio-focus loss handling per `TRANSIENT_CAN_DUCK` / `TRANSIENT` / `LOSS` semantics; Bluetooth SCO connection-state-change P0 stance "no crash" with full SCO route correctness deferred to P1; Oboe callback threading rules). SDD-116 (`AndroidVoiceVerificationMatrix` — API 28 / 31 / 34 minimum, Pixel + Samsung minimum, per-device observation contract for `getPerformanceMode` / `getSharingMode` / `getInputPreset` / effect availability + create success / glass-to-glass latency measurement / ≥10 min screen-off foreground-service longevity; gate G-A5 / RISK-AUDIO-MOBILE-001 closure rule; cross-link to SWE4-UV-040..046 / SWE5-IV-016..020 / SWE6-SV-018..024). Strict layered sourcing preserved (`SDD → SAD`); SRS traces recorded on each unit per the SRS→SAD→SDD chain. | + +## Baseline Candidate 0.9.8 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.8 | 2026-05-18 | Reconciled Android P0 detailed design after Wave 1.5 SysRS / SRS / SAD propagation. SDD-073 (`AndroidBuildConfig`) updated in place (ID preserved) to record `minSdk = 28` per DEC-004 (superseding the prior API 24 baseline), `targetSdk` policy bound to SRS-188 Google Play floor, NDK ABI set pinned to `arm64-v8a / armeabi-v7a / x86_64`, release-signing CI assertion, R8/ProGuard stance, and AAB output per SRS-119 (delegating packaging pipeline detail to SDD-109). SDD-028 (`BackIntentService`) expanded from a one-line stub to an implementable specification covering API 33+ `OnBackInvokedCallback` registration, pre-33 `OnBackPressedDispatcher` fallback, deterministic route-pop ordering, and edge cases (root route → exit candidate, modal/dialog → close-only, active PTT → ignore). Added six new Android P0 SDD units: SDD-105 (`AndroidJniBootstrap` — back-fill for `JNI_OnLoad` / `initChanoraContext` / library-load location, sourced from SAD-081 / SAD-086); SDD-106 (`AndroidPermissionRequester` — RECORD_AUDIO runtime flow with listen-only fallback, settings deep-link for permanent denial, revocation handling, BridgeEvent surface, TransmitModeSelector clamp, sourced from SAD-085); SDD-107 (`AndroidVoiceForegroundService` — service class, manifest entry, notification channel id `chanora.voice.session`, `foregroundServiceType="microphone"` on API 30+, POST_NOTIFICATIONS request on API 33+, lifecycle bound to voice_join / voice_leave / shutdown_if_idle, START_NOT_STICKY on process death, ongoing-notification re-post on dismissal, sourced from SAD-086); SDD-108 (`AndroidAudioModeController` — `setMode(MODE_IN_COMMUNICATION)` with prior-mode snapshot/restore, ref-count semantics composable for P1, JNI surface from Rust audio engine, error paths including AudioManager-unavailable fallback, sourced from SAD-084 — back-fills the v0.9.8 product-decision-register entry); SDD-109 (`AndroidAaBuildPipeline` — bundletool config, ABI splits matching SDD-073, CI signing assertion, versionCode/versionName derivation, sourced from SAD-037 / SAD-063); SDD-110 (`AndroidPttCapability` — pins Android P0 to FocusedPttBackend only, no global key hook, no accessibility-service PTT, capability badge text under `ptt.capability.android.l0`, sourced from SAD-077). Updated §11 SAD-to-SDD coverage matrix to list the new Android P0 SDD range. Strict layered sourcing preserved (`SDD -> SAD` only). | + + +## Baseline Candidate 0.9.14 Update + +| Version | Date | Description | +| --- | --- | --- | +| 0.9.14 | 2026-05-18 | Documentation-only reconciliation of the Android voice-join lifecycle step ordering between SDD-113 item 6 and SDD-115 step list — no behavioural design content changed; the canonical ordering was already specified by SDD-113 item 6 and is already implemented by the engine code at `crates/chanora_audio/src/engine.rs:322-410` (mode engage at lines 358-381 precedes `AndroidVoiceUnit::open()` at line 399). Trigger: Wave 3 reviewer finding on commit 15d7117, which flagged that SDD-115's step list had stream open as step 4 and mode engage as step 5, contradicting SDD-113 item 6's "mode engage → stream open → effect attach" sequence. (1) SDD-115 item 1 (voice-join sequencing) step list renumbered so that mode engage is step 4 (was step 5) and stream open is step 5 (was step 4); effect attach remains step 6; `AndroidVoiceUnit::start()` remains step 7. Mode-first-ordering rationale paragraph added inline after the step list documenting why mode engagement precedes AAudio stream construction (AAudio binds routing at open time; effect attach on a stream opened under a non-voice mode is unspecified). (2) SDD-115 item 2 (voice-leave sequencing) reverse-order step list updated to mirror the forward order: streams close (step 4, formerly mode restore) now precedes mode restore (step 5, formerly stream close — wait, reverse: the new order is stop → release effects → close streams → restore mode → stop service), and the "reverse order is mandatory" note extended to cover closing streams under `MODE_IN_COMMUNICATION` before reverting the mode. (3) SDD-115 item 3 justification text updated to reference the new step numbers (step 4 mode engage before step 5 stream open before step 6 effect attach). (4) SDD-115 item 5 audio-focus request cross-reference updated from "voice-join (step 5)" to "voice-join (step 4, alongside the SDD-108 mode engage)" to track the renumbering. (5) SDD-113 item 6 extended with an explicit cross-reference to SDD-115 step ordering 4–6 and a joint-authority clause requiring future ordering amendments to update both units in the same revision. Other SDD units inspected for implicit assertion of the old ordering: SDD-107 (item containing "service start happens BEFORE setMode" at line 1354 — consistent with both old and new orderings, no change needed), SDD-108 (no stream-vs-mode ordering assertion, no change needed), SDD-111 (no lifecycle ordering language, no change needed), SDD-112 (no lifecycle ordering language, no change needed). Strict layered sourcing preserved (`SDD -> SAD` only); no SRS or SAD edits required as both upstream layers were silent on the inter-element ordering and delegated it to SDD-113 / SDD-115. Wave 3 follow-up scope (folded into this revision): (a) **SWE.4 ID allocation** — `docs/verification/swe4-unit-verification-plan.md` §12 allocates SWE4-UV-053..055 for the three `TransmitModeSelector` permission-state clamp tests (`crates/chanora_audio/src/transmit_selector.rs:343-435`, commit 635d160 — previously cited as the "SWE4-UV-026 family" placeholder) and SWE4-UV-056..057 for the two `DiagnosticExport::with_android_audio` rendering tests in `crates/chanora_diagnostics` (commit aea50a1 / commit-8 amend). (b) **SWE4-UV-040 x86 pass-criteria correction (CRITICAL)** — the prior pass-criteria string `ndk.abiFilters set equals {arm64-v8a, armeabi-v7a, x86_64, x86}` contradicted SDD-073 item 4 and SDD-118 item 3 (which both explicitly exclude 32-bit `x86`); corrected to `{arm64-v8a, armeabi-v7a, x86_64}` with explicit `x86 32-bit is out of scope` note. (c) **Cross-trace and Version-header corrections** — `android-p0-acceptance.md` TC-2 trace retargeted SAD-077 → SAD-085 (permission-requester is SAD-085, not the diagnostics PttSanitizer at SAD-077); TC-4 trace retargeted SRS-208 → SRS-195/SRS-196 (mode pin is not the PTT-capability trace); TC-6 trace retargeted SAD-085 → SAD-084 (AudioModeController is SAD-084, not the permission requester); TC-13 SWE.5 cite retargeted SWE5-IV-021 → SWE5-IV-020. Doc-Version headers refreshed across SAD (0.9.7→0.9.8), SysDes (0.9.2→0.9.7), SRS (0.9.2→0.9.8), product-decision-register (0.9.7→0.9.8), traceability-matrix (0.9.2→0.9.9), swe4-unit-verification-plan (0.9.2→0.9.14) to reflect each document's actual content baseline. (d) **Traceability-matrix §E row 8** added: governance cross-reference flagging that SDD-111 has no dedicated SAD anchor for the platform-abstracted `MobileVoiceAudioBackend` trait (engineering chain remains intact via SAD-077 / SAD-081; future SAD revision to add a dedicated allocation item). §C "Status pointer (v0.9.7+)" added noting that SDD-111..SDD-116 have since been authored and the PENDING_SDD markers above are retained for historical context only. No code changes; all corrections are documentation-only. Recommended next agent: `traceability-auditor` refresh once all Wave 3 amendments have landed. | + + + +| Version | Date | Description | +| --- | --- | --- | +| 0.9.13 | 2026-05-18 | Documentation-cleanup amendment pass on SDD-105, SDD-118, SDD-119 — no behavioural design content changed; corrections only. (1) SDD-105 Back-fill notes corrected: the loader (`System.loadLibrary`) lives in `ChanoraApplication.kt`, not `MainActivity.kt` — back-fill text now cites `ChanoraApplication.kt` for `System.loadLibrary` and `MainActivity.kt` for `initChanoraContext` invocation, resolving the contradiction between the Software-unit field and the Back-fill field. (2) SDD-105 software-unit list extended to include `crates/chanora_bridge/build.rs` (which emits `cargo:rustc-link-lib=dylib=c++_shared` so that `DT_NEEDED libc++_shared.so` is baked into `libchanora_bridge.so`). (3) SDD-118 item 6 extended (new sub-paragraph "Item 6 (extended)") to mandate co-staging `libc++_shared.so` from the NDK sysroot into `jniLibs//` alongside `libchanora_bridge.so`, with the explicit NDK sysroot source-path pattern `$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib//libc++_shared.so` and the sysroot-triple mapping (`arm64-v8a → aarch64-linux-android`, `armeabi-v7a → arm-linux-androideabi` — NOT `armv7-linux-androideabi`, `x86_64 → x86_64-linux-android`); rationale traces to SDD-105 load-order preload, SDD-111 oboe-rs C++ dependency, and SDD-118 item 10 release-inspection assertion (which shall include `libc++_shared.so`). (4) SDD-105 Cross-trace field added linking to SDD-118 item 6 (extended) for the runtime co-staging and to `crates/chanora_bridge/build.rs` for the DT_NEEDED emission. (5) SDD-119 item 4 doc-drift TODO resolved with Option α (drop the iOS podspec simulator comment) for P0 minimalism; `aarch64-apple-ios-sim` is explicitly deferred to a future P1 SDD revision. Strict layered sourcing preserved (`SDD -> SAD` only). Items deferred from this pass: macOS / iOS deployment-target single-source-of-truth (SDD-119 item 3); optional SysRS-290 anchor work; `abiFilters` DEC entry (routed to traceability-auditor + governance, not SDD). | + +## Baseline Candidate 0.9.12 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.12 | 2026-05-18 | Surgical traceability correction on SDD-119 (`iOSAndMacOSBridgeBuildAutomation`) Source SAD field. Prior allocation sourced the macOS half by parallelism from SAD-061 (iOS runtime) / SAD-062 (App Store / packaging) because no dedicated macOS-runtime SAD anchor existed at SDD-119 authoring time. SAD v0.9.7 closed that gap by authoring SAD-087 (macOS runtime baseline — `10.15` deployment target isolation in `apps/chanora_flutter/macos/chanora_bridge.podspec`, universal-binary `arm64 + x86_64` `lipo` packaging, `.framework` `Versions/A` layout, hand-rolled CocoaPods podspec automation), parallel to SAD-061 (iOS runtime) and SAD-063 (Android runtime). This revision retargets SDD-119's Source SAD allocation to: iOS half = SAD-061 + SAD-062; macOS half = SAD-087 as the primary anchor. §11 coverage matrix row updated from `SAD-061, SAD-062` to `SAD-061, SAD-062, SAD-087` with explicit iOS-vs-macOS attribution narrative. No behavioural content of SDD-119 was modified; only the Source SAD allocation and the coverage matrix. SDD-119 remains a back-fill unit creating no new implementation work. Strict layered sourcing preserved (`SDD -> SAD` only). | + +## Baseline Candidate 0.9.11 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.11 | 2026-05-18 | Surgical traceability correction on SDD-110 (`AndroidPttCapability`) Source SAD field. Prior value `SAD-077` was incorrect (SAD-077 is `PttSanitizer`, a diagnostics-log-sink decorator — wrong scope). The traceability-auditor flagged that an earlier draft had also cited `SAD-086` (Android voice foreground service — also wrong scope). Re-anchored to SAD-071 (PTT-backend trait isolation + factory selection + published capability level — the architectural concept SDD-110 specialises on Android by pinning to `FocusedPttBackend` and `L0Focused`) and SAD-076 (`BridgeEvent::PttCapability { level, backend_id }` — the capability-event surface feeding the Android-specific capability badge text per SDD-091). §11 coverage matrix updated: SDD-110 removed from the SAD-063/081/084/085/086 row (now `SDD-105`–`SDD-109` only) and added under a new SAD-071 / SAD-076 row. No behavioural content of SDD-110 was modified; only the Source SAD field and the coverage matrix. Strict layered sourcing preserved (`SDD -> SAD` only). | + +## Baseline Candidate 0.9.10 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.10 | 2026-05-18 | Added `chanora_bridge` Rust cdylib build-automation detailed design SDD-118 and SDD-119 (SDD-117 left reserved-but-unauthored for the deferred `ios_voice_unit` trait back-fill noted by SDD-111). SDD-118 (`AndroidBridgeBuildAutomation` — Gradle task set in `apps/chanora_flutter/android/app/build.gradle.kts` driving a single `cargo-ndk -t arm64-v8a -t armeabi-v7a -t x86_64` invocation; `cargo ndk --platform $minSdk` sourced from the `chanora.android.minSdk` Gradle property per SDD-073 default 28 / DEC-004; ABI set derived from `android.defaultConfig.ndk.abiFilters` so the two lists stay in sync; profile mapping Gradle-debug→`cargo build` / Gradle-release→`cargo build --release` — deliberate divergence from the iOS / macOS always-`--release` pattern, with the release-build safety property preserved by the item 10 release-inspection assertion; env `LIBOPUS_STATIC=1 LIBOPUS_NO_PKG=1 CMAKE_POLICY_VERSION_MINIMUM=3.5` plus `ANDROID_NDK_HOME` propagation; per-ABI plain copy to `apps/chanora_flutter/android/app/src/main/jniLibs//libchanora_bridge.so` with the explicit Rust-triple ↔ Android-ABI mapping; task-graph wiring via `tasks.matching { ... endsWith("JniLibFolders") }.configureEach { dependsOn(...) }` rather than `preBuild.dependsOn`; Gradle `inputs`/`outputs` declarations for up-to-date semantics; preflight that fails loudly with `cargo install cargo-ndk` / `rustup target add ...` remediation rather than auto-installing; release-inspection assertion that each `.so` exists and is >1 KiB before `bundleRelease` / `bundleDebug`; `target/` lives at repo root and is OUTSIDE `gradle clean` scope; sourced from SAD-063 with cross-trace to SDD-073 / SDD-105 / SDD-109). SDD-119 (`iOSAndMacOSBridgeBuildAutomation` — back-fill of the existing CocoaPods-podspec automation under `apps/chanora_flutter/ios/chanora_bridge.podspec` and `apps/chanora_flutter/macos/chanora_bridge.podspec`; dual-hook `prepare_command` + `script_phase :before_compile`; iOS single-arch `aarch64-apple-ios` flat framework with hand-written Info.plist and `install_name_tool -id @rpath/chanora_bridge.framework/chanora_bridge`; macOS dual-arch `aarch64-apple-darwin` + `x86_64-apple-darwin` joined by `lipo -create` in a `Versions/A` framework layout with `install_name_tool -id @rpath/chanora_bridge.framework/Versions/A/chanora_bridge`; always `--release`; iOS env `IPHONEOS_DEPLOYMENT_TARGET=13.0 CMAKE_POLICY_VERSION_MINIMUM=3.5 CMAKE_OSX_DEPLOYMENT_TARGET=13.0`, macOS env `MACOSX_DEPLOYMENT_TARGET=10.15 LIBOPUS_STATIC=1 LIBOPUS_NO_PKG=1 CMAKE_POLICY_VERSION_MINIMUM=3.5`; CocoaPods `vendored_frameworks` integration; cargo incremental + per-platform timestamp guard caching; sourced from SAD-061 / SAD-062 with macOS-runtime SAD anchor flagged as a follow-up; known doc-drift defect recorded: iOS podspec comment references `aarch64-apple-ios-sim` but the script does not actually build the simulator slice — recorded as a TODO follow-up, NOT fixed in this SDD task; deployment-target single-source-of-truth improvement recorded as open follow-up). Updated §11 SAD-to-SDD coverage matrix to list SDD-118 and SDD-119 with their respective SAD anchors. Strict layered sourcing preserved (`SDD -> SAD` only). | diff --git a/docs/architecture/sysdes.md b/docs/architecture/sysdes.md index df6a32d..4258256 100644 --- a/docs/architecture/sysdes.md +++ b/docs/architecture/sysdes.md @@ -3,7 +3,7 @@ **Product name:** Chanora **Document type:** SysDes / System Architectural Design **Process alignment:** Automotive SPICE SYS.3 System Architectural Design -**Version:** 0.9.2 +**Version:** 0.9.7 **Status:** Baseline Candidate **Input baseline:** Chanora SysRS v0.6 **Target application type:** Cross-platform voice client application system @@ -2888,9 +2888,61 @@ This SysDes version covers all known SysRS requirements from `SysRS-001` through - ASPICE SYS.3 alignment: Element behaviour - Allocated SysRS: SysRS-304 +**SysDes-152**: The system architecture shall allocate engagement of the Android voice-communication audio mode (for example `AudioManager.setMode(MODE_IN_COMMUNICATION)` or an equivalent platform routing-assist mechanism) to the Android Platform Adapter as a dedicated in-call audio mode subsystem responsibility. The Android Platform Adapter shall (a) take a snapshot of the prior `AudioManager` mode before the first active voice session enters the connected state, (b) engage voice-communication mode no later than the moment the voice session becomes connected, (c) hold the mode for the full duration of any active voice session through refcount-composable enter/exit semantics so that overlapping or rapidly cycling sessions do not prematurely drop the mode, and (d) restore the snapshotted prior mode when the last active voice session ends. This is the system-architectural partitioning between the cross-platform Audio Subsystem (which owns capture/playback streams and codec) and the Android Platform Adapter (which owns the platform audio policy mode). This SysDes item is the proper anchor for the Android in-call audio mode software requirement. + +- Status: Baseline Candidate +- Type: Subsystem Element / Platform Allocation +- Stage: P0 / MVP +- Allocated to: Android Platform Adapter, Audio Subsystem (consumer) +- Downstream artifact: SRS, SAD, SDD, Verification +- Verification method: Integration Test, System Test +- Verification owner: Audio / Platform QA, Android Owner +- ASPICE SYS.3 alignment: Static architecture, dynamic architecture, element allocation +- Allocated SysRS: SysRS-305 +- Notes: Refines the Android platform baseline anchored by SysDes-135 (Android minimum runtime / Play target SDK policy); SysDes-135 remains the platform-baseline context item and is not in conflict with this element-allocation derivation. Consistent with SysDes-021 (OS services allocation) and SysDes-032 (Platform Adapter Layer). + +**SysDes-153**: The system architecture shall allocate Android runtime microphone permission (`RECORD_AUDIO`) acquisition and denial-handling to the Android Platform Adapter as the system-design partitioning between the Permission Subsystem and the Voice Subsystem. The Android Platform Adapter shall (a) own the runtime permission request flow and surface a typed permission-state signal to the Rust Core, (b) ensure that the permission acquisition attempt occurs at or before voice session activation so that the cross-platform Audio Subsystem never opens an input capture stream without a granted permission, (c) provide a listen-only fallback path when the permission is denied, revoked, or not yet decided, in which the output stream lifecycle (per SysDes-150) is preserved and the transmit gate remains closed, and (d) expose a user-facing re-request entry point for granting the permission and retrying transmit. The Voice Subsystem and Audio Subsystem shall consume only the typed permission-state signal and shall not call platform permission APIs directly. This SysDes item is the proper anchor for the Android RECORD_AUDIO timing software requirement. + +- Status: Baseline Candidate +- Type: Subsystem Element / Platform Allocation +- Stage: P0 / MVP +- Allocated to: Android Platform Adapter, Permission Subsystem, Audio Subsystem (consumer), Voice Subsystem (consumer), Flutter UI (re-request affordance) +- Downstream artifact: SRS, SAD, SDD, Verification +- Verification method: Integration Test, System Test +- Verification owner: Audio / Platform QA, Android Owner +- ASPICE SYS.3 alignment: Static architecture, element allocation, interface design +- Allocated SysRS: SysRS-306 +- Notes: Additive to the platform-baseline anchor SysDes-135 and to the general OS-permission allocation in SysDes-021 / SysDes-059 (IF-014 Permission Interface). Consistent with SysDes-150 listen-only path (output stream independent of microphone permission). Inherits but does not duplicate the general microphone-permission obligation traced through SysRS-055. + +**SysDes-154**: The system architecture shall allocate the Android voice audio backend as a dedicated subsystem owned by the Android Platform Adapter and exposed to the cross-platform Audio Subsystem (SE-13) through a trait-based backend abstraction, mirroring the platform-backend pattern already established on iOS (the `coreaudio-rs` / `VoiceProcessingIO` voice-audio path). The Android voice audio backend subsystem shall encapsulate (a) low-latency duplex voice capture and playback via the AAudio low-latency performance-mode path with platform-reported performance-mode observability for downstream latency budget evaluation, (b) voice-communication usage and content-type declarations on the output stream and a voice-communication input-preset selection on the capture stream so that Android's audio policy engine routes the streams under the voice-communication rules engaged by the in-call mode element (SysDes-152), (c) engagement of available platform hardware voice-audio effects (acoustic echo canceller, noise suppressor, automatic gain control) against the active capture session with documented fallback to the cross-platform software processing in the Audio Subsystem (SysDes-137 / SysDes-138) when a given hardware effect is unavailable, (d) a sharing-mode policy that requests exclusive sharing on a best-effort basis with graceful fallback to shared sharing recorded in sanitised audio diagnostics rather than surfaced as a user-facing fatal error, and (e) a lifecycle binding to the Android foreground service of microphone type so that background voice capture is hosted under the platform-required foreground-service model. The Audio Subsystem consumes the backend through the trait abstraction only and shall not depend on AAudio types directly. This SysDes item is the proper anchor for the Android voice audio backend software requirements (latency, input preset, hardware effects, output usage/content-type, sharing mode, foreground-service-hosted background capture). + +- Status: Baseline Candidate +- Type: Subsystem Element / Platform Allocation +- Stage: P0 / MVP +- Allocated to: Android Platform Adapter (owner), Audio Subsystem (consumer through trait), Platform Adapter Layer, Diagnostics (sharing-mode telemetry) +- Downstream artifact: SRS, SAD, SDD, Verification +- Verification method: Integration Test, System Test, Audio Test +- Verification owner: Audio / Platform QA, Android Owner +- ASPICE SYS.3 alignment: Static architecture, dynamic architecture, element allocation, interface design, architecture analysis +- Allocated SysRS: SysRS-055, SysRS-305 +- Notes: Refines the Android platform baseline anchored by SysDes-135 (Android minimum runtime / Play target SDK policy); SysDes-135 remains the platform-baseline context item and is unchanged. Trait-based platform backend pattern is shared with the iOS voice-audio path (`VoiceProcessingIO` via `coreaudio-rs`) but is not a code port. Composes with SysDes-152 (in-call mode), SysDes-153 (permission acquisition), SysDes-137 / SysDes-138 (audio processing default policy and platform-native preference with isolated fallback), SysDes-144 (`capture_active` / `transmit_active` split), SysDes-150 (audio engine lifecycle bound to voice-channel membership), and the foreground-service obligation already represented at the system level (SysRS-055, SysRS-111 chain). + +**SysDes-155**: The system architecture shall allocate the macOS runtime baseline as a dedicated platform-baseline element parallel to SysDes-133 (iOS) and SysDes-135 (Android), covering (a) the macOS deployment-target policy as a single-source-of-truth system-design concern owned by the macOS Build Configuration and consumed identically by the Flutter macOS runner and the Rust `chanora_bridge` native dependency (the canonical declaration site is the CocoaPods podspec at `apps/chanora_flutter/macos/chanora_bridge.podspec`, which is the location to be cited by downstream SRS/SAD/SDD; the numeric value itself is an implementation detail and is not embedded in this SysDes item), (b) a universal-binary packaging policy under which the Rust cdylib shall be produced for both `arm64` and `x86_64` host slices and combined via `lipo` into a single fat Mach-O binary so that one packaged macOS application supports Apple Silicon and Intel hosts without per-architecture artefacts, (c) the macOS `.framework` `Versions/A` bundle layout convention (versioned bundle directory with `Current` symlink, `Resources/Info.plist`, and the binary at `Versions/A/`) as the system-level packaging shape for the native bridge so that it is loadable by the Flutter macOS runner and is acceptable to codesigning and notarisation, and (d) CocoaPods podspec integration as the auto-build mechanism that drives the Rust cdylib build, the universal-binary `lipo` step, and the `.framework` assembly during a `pod install` / Flutter macOS build, so that the macOS build pipeline has a single, declarative entry point. This SysDes item is the proper macOS platform-baseline anchor for SAD-087 and for SRS items that allocate macOS-runtime responsibility, replacing any "open follow-up" SysDes placeholder previously carried by SAD-087. + +- Status: Baseline Candidate +- Type: Platform Architecture Decision +- Stage: P0 / MVP +- Allocated to: macOS Build Configuration (owner), Platform Adapter Layer (macOS Platform Services), Release / Operations (Release Pipeline), Verification +- Downstream artifact: SRS, SAD, SDD, Verification +- Verification method: Review, Platform Test (macOS), Release Inspection +- Verification owner: macOS Owner / Release Manager +- ASPICE SYS.3 alignment: Static architecture, architecture constraints, element allocation, consistency +- Allocated SysRS: SysRS-002, SysRS-050, SysRS-193 +- Notes: Parallel in role to SysDes-133 (iOS runtime baseline) and SysDes-135 (Android runtime baseline); neither SysDes-133 nor SysDes-135 is modified by this item. Source SysRS selection: SysRS-002 anchors the multi-platform obligation that explicitly enumerates macOS as a target client platform, SysRS-050 anchors the macOS runtime environment obligation for native desktop integration, and SysRS-193 anchors the signed/notarized macOS build obligation that the `.framework` `Versions/A` layout and universal-binary policy must satisfy in the release pipeline. No macOS-specific deployment-target or universal-binary SysRS item currently exists; if a finer-grained macOS minimum-runtime SysRS item is required (analogous to SysRS-286 for iOS and SysRS-288 for Android), the systems-requirements owner should consider authoring it in a follow-up — this SysDes item is structured so that such a future SysRS item can be added to `Allocated SysRS` without restructuring the element. SysDes-155 cites the podspec file path only and does not embed its current deployment-target value; the value itself remains owned by the Build Configuration subsystem. + ## 17. Updated SysRS Coverage Statement -This SysDes version covers all known SysRS requirements from `SysRS-001` through `SysRS-304`. +This SysDes version covers all known SysRS requirements from `SysRS-001` through `SysRS-306`. | SysRS Range | SysDes Coverage | |---|---| @@ -2898,6 +2950,8 @@ This SysDes version covers all known SysRS requirements from `SysRS-001` through | 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` | +| SysRS-305 through SysRS-306 | Covered by `SysDes-152` through `SysDes-154` (with SysDes-154 additionally refining SysRS-055 for the Android voice audio backend) | +| SysRS-002 / SysRS-050 / SysRS-193 (macOS platform-baseline slice) | Additionally refined by `SysDes-155` (macOS runtime baseline, parallel to SysDes-133 / SysDes-135) | ## Baseline Candidate 0.9.1 Update @@ -2926,3 +2980,17 @@ This SysDes version covers all known SysRS requirements from `SysRS-001` through | 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 0–500 ms) on `transmit_active`. Strict layered sourcing preserved (`SysDes -> SysRS` only). | + + +## Baseline Candidate 0.9.6 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.6 | 2026-05-18 | Added dedicated Android voice-audio element allocations SysDes-152 (Android in-call audio mode subsystem, source SysRS-305), SysDes-153 (Android RECORD_AUDIO runtime permission acquisition flow as system-design partitioning between Permission Subsystem and Voice Subsystem, source SysRS-306), and SysDes-154 (Android voice audio backend subsystem: trait-based platform backend mirroring the iOS `VoiceProcessingIO` pattern, AAudio low-latency path, voice-communication usage/preset, hardware effects engagement, sharing-mode policy, FGS-hosted background mic lifecycle; source SysRS-055 + SysRS-305). SysDes-135 remains the Android platform-baseline context item and is unchanged. These derivations are the proper SysDes anchors for SRS-208, SRS-209, and SRS-210..215, which currently cite SysDes-135 generically and shall be retargeted by the software-requirements owner in a follow-up pass. Strict layered sourcing preserved (`SysDes -> SysRS` only). | + + +## Baseline Candidate 0.9.7 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.7 | 2026-05-18 | Added dedicated macOS runtime baseline element allocation SysDes-155, parallel in role to SysDes-133 (iOS runtime baseline) and SysDes-135 (Android runtime baseline). SysDes-155 anchors the macOS deployment-target policy as a single-source-of-truth concern (citing the podspec file path `apps/chanora_flutter/macos/chanora_bridge.podspec` without embedding its value), the universal-binary (`lipo`'d `arm64` + `x86_64`) packaging policy, the `.framework` `Versions/A` bundle layout convention, and CocoaPods podspec integration as the auto-build mechanism for the Rust cdylib. Allocated to macOS Build Configuration, Platform Adapter Layer (macOS Platform Services), Release Pipeline, and Verification. Source SysRS: SysRS-002 (multi-platform target client platforms including macOS), SysRS-050 (macOS runtime environment / native desktop window integration), SysRS-193 (signed/notarized macOS builds). SysDes-155 is the proper SysDes anchor for SAD-087 and for downstream SRS items that allocate macOS-runtime responsibility, and is the recommended retarget for SAD-087's previously-open Source SysDes follow-up. SysDes-133 and SysDes-135 are unchanged. Strict layered sourcing preserved (`SysDes -> SysRS` only). Follow-up recommendation: systems-requirements owner may consider authoring a finer-grained macOS minimum-runtime SysRS item parallel to SysRS-286 (iOS) and SysRS-288 (Android); SysDes-155 is structured so that such a future SysRS item can be added to its `Allocated SysRS` list without restructuring the element. | diff --git a/docs/governance/decision-impact-assessment.md b/docs/governance/decision-impact-assessment.md index 8016a08..6b424af 100644 --- a/docs/governance/decision-impact-assessment.md +++ b/docs/governance/decision-impact-assessment.md @@ -17,7 +17,7 @@ This document records how key unresolved product and architecture decisions affe | Decision | Recommended decision | Impact if changed later | |---|---|---| | Minimum iOS version | iOS 13 | Test matrix, platform support policy, app store build configuration, platform behavior tests. | -| Minimum Android version | API 24 | Device support, permissions, audio routing, Google Play compatibility, platform tests. | +| Minimum Android version | API 28 (per DEC-004, Accepted 2026-05-14; raised from the original API 24 recommendation) | Device support, permissions, audio routing, Google Play compatibility, platform tests. | | Android target SDK | Google Play-required API level on upload date; current release gate uses API 35+ unless newer policy applies | | Apple App Store SDK gate | Xcode 26+ with iOS 26 / iPadOS 26 SDK+ for App Store Connect upload on or after 2026-04-28 unless newer Apple upload policy applies | Google Play submission eligibility and platform permission behavior. | | Multiple active connections | Exclude from MVP | If added later, affects state model, audio routing, UI layout, reconnection logic, storage, verification. | @@ -95,3 +95,9 @@ The owner rulings recorded as DEC-023 through DEC-028 on 2026-05-15 affect the d | Version | Date | Description | |---|---|---| | 0.9.3 | 2026-05-15 | Recorded the desktop-PTT decision impacts for DEC-023..028. | + +## Baseline Candidate 0.9.9 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.9 | 2026-05-17 | Updated §2 "Minimum Android version" row from API 24 to **API 28** to reconcile with DEC-004 (Accepted 2026-05-14). Document Update Rules (§3) and Decision Closure Sequence (§4) remain valid as-is. | diff --git a/docs/governance/product-decision-register.md b/docs/governance/product-decision-register.md index b715b37..1ab9f6b 100644 --- a/docs/governance/product-decision-register.md +++ b/docs/governance/product-decision-register.md @@ -2,7 +2,7 @@ # CHANORA_CFG_Product_Decision_Register_v0.9.7.0.0 **Document type:** Configuration / Product Decision Register -**Version:** 0.9.7 +**Version:** 0.9.8 **Status:** Baseline Candidate **Language:** English **Product:** Chanora @@ -221,3 +221,115 @@ release but is not an open decision: | 0.9.10 | 2026-05-15 | Code-side promotion: the Linux GNOME-Wayland backend (DEC-025) is now a live `org.freedesktop.portal.GlobalShortcuts` session — `CreateSession` + `BindShortcuts` + `Activated` / `Deactivated` signal subscription scoped to the session handle, owned by a dedicated tokio task per backend instance. The Flutter "Configure" button on Linux portal delegates to the portal's own system dialog (Q3a) rather than the in-app `_PttBindingCaptureDialog`. Descriptor transitions broadcast via a `watch::Sender` consumed by `chanora_core::ChanoraSession::start_audio` and forwarded to `SessionEvent::PttCapability`. Cancellation / failure path downgrades to `L0Focused` and re-emits. No decision rows mutate. | | 0.9.11 | 2026-05-15 | Added DEC-029 and DEC-030 covering the v1 audio + PTT lifecycle redesign. DEC-029 rejects Flutter global-hotkey packages (`hotkey_manager`, `super_hot_key`, equivalents) for PTT — they consume the key, do not deliver key-up, and do not support mouse side-buttons; the native Rust `DesktopPttBackend` already specified in SDD-083 / SDD-084 / SDD-085 is the v1 capture path. DEC-030 defers Voice Activity Detection to P1: `TransmitMode::VoiceActivity` ships as a reserved enum variant with no v1 implementation pending a backend trade-off review (RMS vs WebRTC VAD vs Silero VAD differ by ~8–16 MB and an ONNX-runtime dependency surface). No prior decision rows mutate; §5 impact matrix and §7 open-decision log gain matching rows. | | 0.9.12 | 2026-05-16 | Added DEC-031: missed-key-up watchdog is disabled on P0 (supersedes DEC-028 for the v1 ship). The 30 s default ceiling spec'd in DEC-028 was cutting real PTT users off mid-sentence whenever a single utterance crossed the timeout; the watchdog's intent (catching OS-level key-up loss) remains valid, but the fixed-timeout implementation is the wrong shape. The `MissedKeyUpWatchdog` Rust type and its unit tests remain in `crates/chanora_audio/src/ptt.rs`; only the `ChanoraSession::start_audio` spawn is removed. P1 will choose between a raised ceiling, OS key-state polling (`GetAsyncKeyState` / `CGEventSourceKeyState` / `XQueryKeymap`), an RMS-silence fallback paired with the audio level meter, or a combination. DEC-028 stays in the register as historical context. | + + +## Baseline Candidate 0.9.8 Update — DEC-032 + +### DEC-032 — Temporary reduction of Android `abiFilters` to `arm64-v8a` only during P0 smoke-test cycle + +| Field | Value | +|---|---| +| Decision ID | DEC-032 | +| Title | Temporary reduction of Android `abiFilters` to `arm64-v8a` only during P0 smoke-test cycle. | +| Status | **Active (temporary deviation)** | +| Owner | Build/Toolchain | +| Restore-by | P0 release gate (must be restored before any P0 release upload). | +| Date recorded | 2026-05-18 | +| Supersedes | None (temporary deviation from SDD-073 item 4 and SDD-118 item 3; does NOT supersede them). | + +**Context.** SDD-073 item 4 and SDD-118 item 3 mandate the canonical +three-ABI set `{arm64-v8a, armeabi-v7a, x86_64}` for the Android AAB. +`apps/chanora_flutter/android/app/build.gradle.kts:72-79` currently has +`abiFilters` reduced to `arm64-v8a` only with a `TODO(x86_64/armv7 +follow-up)` comment explaining the reduction. + +**Cause.** The `audiopus_sys` + `cmake-rs` + NDK toolchain-file +interaction does not propagate `ANDROID_ABI` as a CMake variable when +invoked via the Gradle Exec task chain. `armeabi-v7a` and `x86_64` +builds fail because the inner CMake configure step does not see the +correct `-DANDROID_ABI=` argument. `arm64-v8a` (the smoke-test +emulator target) builds cleanly because it happens to be the default +that `cmake-rs` emits when no `ANDROID_ABI` is propagated. + +**Scope.** Smoke-test build only. The P0 release **must** restore the +full three-ABI set before any release upload. Internal Beta / RC +builds for the smoke-test emulator path may continue using the +reduced set while the cmake-rs propagation fix is in flight. + +**Exit criteria (all four must be met to close DEC-032).** + +1. `audiopus_sys` / `cmake-rs` `ANDROID_ABI` propagation gap resolved + (research + builder-dispatch fix; root-cause whether upstream + `cmake-rs` patch, a builder-side env override, or a Gradle-Exec + level argument injection is the correct fix). +2. All three ABIs (`arm64-v8a`, `armeabi-v7a`, `x86_64`) compile + cleanly in CI for `chanora_bridge` cdylib. +3. The `abiFilters` declaration in + `apps/chanora_flutter/android/app/build.gradle.kts` is restored to + the canonical three-ABI set, and the `TODO(x86_64/armv7 + follow-up)` comment removed. +4. The SDD-118 item 10 release-inspection assertion confirms that all + three `libchanora_bridge.so` files **and** all three + `libc++_shared.so` co-staged files (per SDD-118 item 6 extended, + absorbed into the traceability matrix v0.9.9 addendum) are present + in any release AAB. + +**Impact assessment.** + +- `arm64-v8a` installs work on all 64-bit modern Android devices, + including the smoke-test emulator and every device meeting the + DEC-004 API 28 minimum on 64-bit hardware. +- `armeabi-v7a` (32-bit ARM Android, ~1% of the 2026 active install + base for app-stores that still permit 32-bit) installations are + **blocked** until restoration. Affected users see Play Store + filtering and cannot install the AAB. +- `x86_64` (some Android emulators outside the smoke-test loop, rare + Chromebook deployments, some VMs) installations are **blocked** + until restoration. +- **P0 release-gate cannot pass** until the three-ABI set is + restored: release-inspection assertion (SDD-118 item 10) would + fail, and the canonical SDD-073 item 4 / SDD-118 item 3 contract + would be violated. +- No SDD edit is required: the deviation is governance-layer only + and the SDD canonical intent is unchanged. + +**Cross-references.** + +- SDD-073 item 4 (canonical Android ABI set). +- SDD-118 item 3 (Gradle automation enforcing the three-ABI set); + also item 6 (extended) for `libc++_shared.so` co-staging and item + 10 for the release-inspection assertion that any future restoration + must satisfy. +- `apps/chanora_flutter/android/app/build.gradle.kts:72-79` + (in-source TODO comment marking the deviation site). +- Audit task `ses_1c7645e36ffeY007MaYPep4wqs` (auditor New-D — origin + of this DEC entry). +- Traceability matrix v0.9.9 §D (governance cross-reference). +- DEC-031 (separate temporary deviation, for `MissedKeyUpWatchdog` + P0 disable) — listed only as precedent for the + "Active (temporary deviation)" status pattern; no direct technical + overlap. + +**§3 row (for canonical table consistency).** + +| Decision ID | Decision | Recommended decision | Status | Owner | Why it matters | +|---|---|---|---|---|---| +| DEC-032 | Android `abiFilters` set during P0 smoke-test cycle | Temporarily reduced to `arm64-v8a` only; restore to canonical three-ABI set `{arm64-v8a, armeabi-v7a, x86_64}` (per SDD-073 item 4 / SDD-118 item 3) before any P0 release upload | **Active (temporary deviation)** | Build/Toolchain | Affects who can install the AAB (armv7 + x86_64 currently blocked); P0 release gate cannot pass until restored; documents the SDD-073 / SDD-118 deviation site so it is not silently shipped. | + +**§5 row (impact matrix).** + +| Decision | Affects SysRS | Affects SysDes | Affects SRS | Affects SAD | Affects SDD | Affects Verification | Affects Release | +|---|---|---|---|---|---|---|---| +| `abiFilters` temporary reduction (DEC-032) | No | No | No | No | No (canonical intent unchanged; SDD-073 item 4 / SDD-118 item 3 stand) | Yes (release-inspection assertion path) | Yes (release-gate blocker until restored) | + +**§7 row (open decision log).** + +| Decision ID | Owner | Decision | Status | Date | Notes | +|---|---|---|---|---|---| +| DEC-032 | Build/Toolchain | Temporary reduction of Android `abiFilters` to `arm64-v8a` only during P0 smoke-test cycle | Active (temporary deviation) | 2026-05-18 | Restore to canonical three-ABI set before any P0 release. Root cause: `audiopus_sys` + `cmake-rs` + NDK toolchain-file `ANDROID_ABI` propagation gap. Exit criteria (4 items) listed in DEC-032 detail entry. SDD-073 item 4 / SDD-118 item 3 unchanged. | + +### Change history + +| Version | Date | Description | +|---|---|---| +| 0.9.8 | 2026-05-18 | Added DEC-032: temporary reduction of Android `abiFilters` to `arm64-v8a` only during the P0 smoke-test cycle. Status: Active (temporary deviation) from SDD-073 item 4 / SDD-118 item 3, which both remain unchanged. Restore-by gate: any P0 release upload must restore the canonical three-ABI set `{arm64-v8a, armeabi-v7a, x86_64}` and satisfy the SDD-118 item 10 release-inspection assertion (including the `libc++_shared.so` co-staging per SDD-118 item 6 extended). Root cause: `audiopus_sys` + `cmake-rs` + NDK toolchain-file `ANDROID_ABI` propagation gap. Owner: Build/Toolchain. Cross-references: SDD-073 item 4, SDD-118 item 3 (+ item 6 extended, item 10), `apps/chanora_flutter/android/app/build.gradle.kts:72-79` (in-source TODO), audit task ses_1c7645e36ffeY007MaYPep4wqs, traceability matrix v0.9.9 addendum §D. No prior decision row mutates. | diff --git a/docs/governance/traceability-matrix.md b/docs/governance/traceability-matrix.md index e86e220..86145f8 100644 --- a/docs/governance/traceability-matrix.md +++ b/docs/governance/traceability-matrix.md @@ -1,7 +1,7 @@ # Chanora Requirements and Design Traceability Matrix -**Version:** 0.9.2 +**Version:** 0.9.9 **Status:** Baseline Candidate **Language:** English **Product:** Chanora @@ -259,3 +259,705 @@ No forbidden direct links introduced: SRS continues to source from SysDes only, | Version | Date | Description | |---|---|---| | 0.9.5 | 2026-05-15 | Added v1 audio + PTT lifecycle traceability rows covering SysRS-303..304 -> SysDes-149..151 -> SRS-204..207 -> SAD-081..083 -> SDD-094..097, with DEC-029 (Flutter hotkey packages rejected) and DEC-030 (VAD deferred to P1) recorded as governance cross-references. | + + +## Android P0 Reconciliation Traceability Addendum (Baseline Candidate 0.9.6) + +This addendum incorporates the Wave 2A.2 / Wave 2B Android P0 deltas across +SysRS, SRS, SAD, SDD, Verification, and Code. It does not relax any +hierarchy rule: SRS continues to source from SysDes only, SAD from SRS only, +SDD from SAD only, and Verification remains downstream evidence. + +### A. Changed item: SysRS-288 (API 24 -> API 28 per DEC-004) + +```text +SysRS-288 (API 28 baseline, DEC-004) + -> SysDes-135 (Android platform baseline) + -> SRS-187 (API 28 minimum) + -> SAD-063 (Android build config isolation, API 28 baseline) + -> SDD-073 (AndroidBuildConfig API 28, NDK ABIs, AAB, R8) + -> SWE4-UV-040 (build-config unit), SWE5-IV-020 (AAB pipeline integration), + SWE6-SV-022 (Android minimum API qualification), + SYS4-SIV-015 (strengthened Android target gate) + -> android-p0-acceptance TC-0.x (device matrix floor row), TC-13 +Code anchors: apps/chanora_flutter/android/app/build.gradle.kts (SDD-073/SDD-109). +Governance cross-reference: decision-impact-assessment.md §2 row updated to API 28. +``` + +### B. New items: SysRS-305, SysRS-306 (Android in-call mode + RECORD_AUDIO timing) + +Both items currently trace through SysDes-135 as the nearest SysDes +anchor. A SysDes follow-up is recommended to introduce dedicated derivation +items so that the `Source SysDes` field for SRS-208/209/210..215 can be +tightened beyond the general Android platform baseline (see §F). + +```text +SysRS-305 (Android in-call audio mode engagement) + -> SysDes-135 + -> SRS-208 (in-call audio mode on voice-session connect/release) + -> SAD-084 (AndroidAudioModeController) + -> SDD-108 (AndroidAudioModeController snapshot/restore + refcount) + -> SWE4-UV-045 (unit), SWE5-IV-018 (FGS + mode round-trip), + SWE6-SV-020 (in-call mode round-trip qualification), + SYS4-SIV-017 (in-call mode + Bluetooth SCO awareness) + -> android-p0-acceptance TC-6, TC-3.4 +``` + +```text +SysRS-306 (RECORD_AUDIO runtime acquisition + listen-only fallback) + -> SysDes-135 + -> SRS-209 (RECORD_AUDIO + listen-only fallback) + -> SAD-085 (Android permission adapter, listen-only first-class) + -> SDD-106 (AndroidPermissionRequester) + -> SWE4-UV-041 (unit state machine), SWE5-IV-017 (permission deny + FGS not started + listen-only), + SWE6-SV-019 (RECORD_AUDIO timing + listen-only qualification) + -> android-p0-acceptance TC-2 +Code anchors: AndroidPermissionRequester.kt (SDD-106), MainActivity.kt (SDD-106). +``` + +### C. New SRS items derived from SysDes-135 for the Android voice audio backend + +| SRS | Source SysDes | SysRS addressed | SAD | SDD | SWE.4 | SWE.5 | SWE.6 | Acceptance | +|---|---|---|---|---|---|---|---|---| +| SRS-210 (Android mouth-to-ear latency ≤150 ms low-latency / ≤250 ms fallback) | SysDes-135 | SysRS-055, SysRS-288 | SAD-081 (audio engine) + SAD-084 (mode controller dependency) | PENDING_SDD (latency budget item — reserved as SDD-111) | PENDING | PENDING | PENDING | TC-6 supporting (mode → routing), latency measurement deferred to P1 device lab | +| SRS-211 (AAudio input preset VOICE_COMMUNICATION + fallback) | SysDes-135 | SysRS-055, SysRS-305 | SAD-081 | PENDING_SDD (reserved as SDD-112) | PENDING | PENDING | PENDING | TC-6 indirect | +| SRS-212 (Hardware AEC/NS/AGC engagement + software fallback) | SysDes-135 | SysRS-055, SysRS-305 | SAD-081 | PENDING_SDD (reserved as SDD-113) | PENDING | PENDING | PENDING | TC-6 indirect; AndroidManifest.xml carries SDD-113 trace comment | +| SRS-213 (AAUDIO_USAGE_VOICE_COMMUNICATION / CONTENT_TYPE_SPEECH on output) | SysDes-135 | SysRS-305 | SAD-081 | PENDING_SDD (reserved as SDD-114) | PENDING | PENDING | PENDING | TC-6 supporting | +| SRS-214 (AAUDIO sharing-mode EXCLUSIVE best-effort + SHARED fallback) | SysDes-135 | SysRS-055 | SAD-081 | PENDING_SDD (reserved as SDD-115) | PENDING | PENDING | PENDING | — | +| SRS-215 (Foreground service hosting for background mic capture) | SysDes-135 | SysRS-217, SysRS-111 chain | SAD-086 (AndroidVoiceForegroundService) | SDD-107 (AndroidVoiceForegroundService) + PENDING_SDD reserved as SDD-116 for `foregroundServiceType="microphone"` manifest binding | SWE4-UV-044 (FGS lifecycle) | SWE5-IV-016 (JNI → FGS), SWE5-IV-017 (permission deny → FGS not started) | SWE6-SV-018 (FGS satisfies SRS-111) | TC-3, TC-3.4 | + +PENDING_SDD rows above are reserved for the in-flight detailed-designer +task that authors SDD-111..SDD-116. At the time of this revision, the SDD +file contains SDD-001..SDD-110 only. See §G. + +> **Status pointer (v0.9.7+):** SDD-111..SDD-116 have since been authored +> in `docs/architecture/sdd.md` (SDD v0.9.9). For the resolved +> SRS-210..SRS-215 → SDD-111..SDD-116 mapping, see the +> "Android Voice Audio Backend SDD Closure Addendum (Baseline Candidate +> 0.9.7)" below and its subsequent §A delta inventory. The PENDING_SDD +> markers above are retained for historical context only. + +### D. New SAD items (Android-specific allocations) + +| SAD | Source SRS | SDD | Code anchors | +|---|---|---|---| +| SAD-084 (AndroidAudioModeController) | SRS-208 | SDD-108 | (Android Kotlin scaffolding pending; AndroidManifest.xml carries SDD-108 trace) | +| SAD-085 (Android permission adapter, listen-only first-class) | SRS-209 | SDD-106 | AndroidPermissionRequester.kt, MainActivity.kt | +| SAD-086 (AndroidVoiceForegroundService) | SRS-111 (Android-specific allocation) | SDD-107 | AndroidVoiceForegroundService.kt, AndroidManifest.xml | + +### E. New / refreshed SDD items and their Code + Verification anchors + +| SDD | Source SAD | Code anchor (this session) | Verification | +|---|---|---|---| +| SDD-028 (BackIntentService — expanded full spec) | SAD-018 | BackIntentBridge.kt, back_intent_policy.dart, back_intent_service.dart, MainActivity.kt, AndroidManifest.xml | SWE4-UV-042, SWE5-IV-019, SWE6-SV-021, TC-11, TC-12 | +| SDD-073 (AndroidBuildConfig — refreshed: API 28, NDK ABIs, AAB, R8) | SAD-063 | apps/chanora_flutter/android/app/build.gradle.kts | SWE4-UV-040, SWE5-IV-020, SWE6-SV-022, SWE6-SV-023, SYS4-SIV-015 (strengthened), SYS4-SIV-018, TC-0.x, TC-13 | +| SDD-105 (AndroidJniBootstrap) | SAD-063 (Android platform services bootstrap) | ChanoraApplication.kt, MainActivity.kt | SWE4-UV-043, SWE5-IV-016 | +| SDD-106 (AndroidPermissionRequester) | SAD-085 | AndroidPermissionRequester.kt, MainActivity.kt | SWE4-UV-041, SWE5-IV-017, SWE6-SV-019, TC-2 | +| SDD-107 (AndroidVoiceForegroundService) | SAD-086 | AndroidVoiceForegroundService.kt, AndroidManifest.xml | SWE4-UV-044, SWE5-IV-016, SWE5-IV-017, SWE5-IV-018, SWE6-SV-018, TC-3 | +| SDD-108 (AndroidAudioModeController) | SAD-084 | (Kotlin controller scaffolding pending; AndroidManifest.xml carries SDD-108 trace) | SWE4-UV-045, SWE5-IV-018, SWE6-SV-020, SYS4-SIV-017, TC-6 | +| SDD-109 (AndroidAaBuildPipeline) | SAD-063 / SAD-037 | apps/chanora_flutter/android/app/build.gradle.kts | SWE5-IV-020, SWE6-SV-023, SYS4-SIV-018, TC-13 | +| SDD-110 (AndroidPttCapability — Focused only on Android) | SAD-086 (Android-specific PTT allocation) / SAD-075 (Focused) | (capability badge wiring; pending Kotlin/Dart) | SWE4-UV-046, SWE6-SV-024, TC-4, TC-10 | +| SDD-111 (reserved — AndroidVoiceLatencyBudget for SRS-210) | SAD-081 | PENDING_SDD | PENDING | +| SDD-112 (reserved — AAudioInputPreset for SRS-211) | SAD-081 | PENDING_SDD | PENDING | +| SDD-113 (reserved — AndroidHardwareEffects for SRS-212; AndroidManifest.xml already carries SDD-113 trace) | SAD-081 | AndroidManifest.xml (trace-only; spec PENDING) | PENDING | +| SDD-114 (reserved — AAudioOutputUsage for SRS-213) | SAD-081 | PENDING_SDD | PENDING | +| SDD-115 (reserved — AAudioSharingMode for SRS-214) | SAD-081 | PENDING_SDD | PENDING | +| SDD-116 (reserved — ForegroundServiceTypeManifest for SRS-215) | SAD-086 | AndroidManifest.xml (trace-only; spec PENDING) | PENDING | + +### F. Code → SDD / SRS back-fill trace summary (this session) + +| Code path | SDD trace | SRS trace (via test header where applicable) | +|---|---|---| +| apps/chanora_flutter/test/widget_test.dart | — | SRS header comments added (verification stub) | +| apps/chanora_flutter/test/alpha_e2e_test.dart | — | SRS header comments added | +| apps/chanora_flutter/test/beta_e2e_test.dart | — | SRS header comments added | +| apps/chanora_flutter/android/app/src/main/AndroidManifest.xml | SDD-107, SDD-108, SDD-113 (reserved) | indirectly SRS-111, SRS-208, SRS-212 | +| apps/chanora_flutter/android/app/build.gradle.kts | SDD-073, SDD-109 | SRS-187, SRS-188, SRS-119 | +| .../kotlin/.../ChanoraApplication.kt | SDD-105 | SRS via SAD-063 → SRS-187 | +| .../kotlin/.../MainActivity.kt | SDD-105, SDD-028, SDD-106 | SRS-163, SRS-209 | +| .../kotlin/.../AndroidVoiceForegroundService.kt | SDD-107 | SRS-111 | +| .../kotlin/.../AndroidPermissionRequester.kt | SDD-106 | SRS-209 | +| .../kotlin/.../BackIntentBridge.kt | SDD-028 | SRS-163 | +| lib/services/back_intent_policy.dart | SDD-028 | SRS-163 | +| lib/services/back_intent_service.dart | SDD-028 | SRS-163 | + +### G. Verification document anchors added this session + +```text +SWE4-UV-040..046 -> SDD-028 (exp), SDD-073 (ref), SDD-105, SDD-106, SDD-107, SDD-108, SDD-110 +SWE5-IV-016..020 -> SAD-018, SAD-063, SAD-077, SAD-081, SAD-084, SAD-085, SAD-086 + + SDD-028 (exp), SDD-073 (ref), SDD-105..110 +SWE6-SV-018..024 -> SRS-111, SRS-119, SRS-163, SRS-187, SRS-188, SRS-208, SRS-209 +SYS4-SIV-015 (strengthened), SYS4-SIV-017, SYS4-SIV-018 + -> SysDes-135 (Android subset of SysDes-133..148) +docs/verification/android-p0-acceptance.md TC-1..TC-13 + -> cross-links SRS-111, SRS-119, SRS-163, SRS-187, SRS-188, SRS-208, SRS-209, + SRS-201; SDD-028, SDD-106..110; DEC-004, DEC-006, DEC-027 +``` + +### H. Orphans, Conflicts, and Dangling References + +#### H.1 Orphans (items whose upward trace is incomplete pending an owner action) + +| Orphan ID | Issue | Proposed owner | +|---|---|---| +| SRS-210..215 (`Source SysDes` = SysDes-135 generic) | SysDes lacks dedicated derivation items for the Android voice audio backend; SRS-210..215 currently reuse the generic Android platform baseline. Per SRS-187 §18 self-flag, a SysDes follow-up is required. | system-designer (add SysDes derivation items downstream of SysRS-055 / SysRS-305 covering the Android voice audio backend) | +| SRS-208 / SRS-209 (`Source SysDes` = SysDes-135 generic) | Same shape: SysDes-135 reused for SysRS-305/306 pending dedicated SysDes items. | system-designer | +| SDD-111..SDD-116 | Not yet authored at read time; SRS-210..215 lack a direct SDD anchor. Reserved as PENDING_SDD in §C / §E. | detailed-designer (in flight per the task brief) | + +#### H.2 Conflicts + +| Conflict | Detail | Proposed resolution path | +|---|---|---| +| SRS-111 dual allocation: SAD-036 (cross-cutting diagnostics/foreground-service range) AND SAD-086 (Android-specific FGS) | SAD §coverage row explicitly states "SAD-036 retains the cross-cutting diagnostics allocation for the SRS-111–124 range" while SAD-086 is the Android-specific allocation. The coverage rows at lines 64–71 of this matrix still show SRS-111..124 → SAD-036, SAD-050. This is **acceptable as a cross-cutting + platform-specific split** (SAD-036 retains the cross-platform allocation; SAD-086 is the Android specialisation), and not a true conflict, but it should be made explicit in §4 baseline-coverage rows. **Alternative**: a SysRS split that separates the cross-platform foreground-service obligation from the Android-specific one — recommended only if future audits flag the dual allocation as ambiguous. | software-architect to record the cross-cutting + platform-specific split as the intended pattern in SAD §coverage; OR system-requirements to split SysRS upstream if a single SAD allocation is preferred by ASPICE auditors. **Do not resolve here** by editing upstream docs. | +| SDD-110 source SAD ambiguity | SDD-110 (`AndroidPttCapability`) sources from "SAD-086 Android-specific PTT allocation / SAD-075 Focused PTT". SAD-086 is defined as `AndroidVoiceForegroundService`, not as the PTT allocation. The Android-specific PTT obligation traces through SysRS-298 (capability exposure) → SRS-196 / SAD-076 (capability badge) and SysRS-297 → SRS-195 / SAD-071 (Global PTT capability-dependent). SDD-110's anchor should be SAD-076 + SAD-071, not SAD-086. | software-architect / detailed-designer to correct the `Source SAD` field on SDD-110. Flag-only here. | + +#### H.3 Dangling references + +| Source row | Target ID | Status | +|---|---|---| +| SRS-210..215 → "SDD-111..SDD-116" (reserved) | SDD-111..SDD-116 | Not yet present in `docs/architecture/sdd.md` (file ends at SDD-110). Tracked as PENDING_SDD per §C and §E. **Not a defect** — the in-flight detailed-designer task owns these. | +| android-p0-acceptance.md TC-13 → "SRS-119" + "SRS-188" + "SDD-109" | all present | OK | +| SAD-086 narrative anchor for SDD-110 (PTT) | SAD-086 exists but its scope is FGS, not PTT | Dangling-by-scope (see §H.2). | + +### I. Items intentionally left as PENDING_* + +| ID | Reason | +|---|---| +| SDD-111 | Reserved for SRS-210 Android voice latency budget. PENDING_SDD. | +| SDD-112 | Reserved for SRS-211 AAudio input preset. PENDING_SDD. | +| SDD-113 | Reserved for SRS-212 hardware AEC/NS/AGC engagement (AndroidManifest.xml already carries the trace comment in anticipation). PENDING_SDD. | +| SDD-114 | Reserved for SRS-213 AAudio output usage/content-type. PENDING_SDD. | +| SDD-115 | Reserved for SRS-214 AAudio sharing-mode best-effort. PENDING_SDD. | +| SDD-116 | Reserved for SRS-215 foreground-service-type manifest binding (AndroidManifest.xml already carries the trace comment in anticipation). PENDING_SDD. | +| SWE.4 / SWE.5 / SWE.6 anchors for SRS-210..215 | PENDING — to be added by verification-planner once SDD-111..116 land. | + +### J. Decision + +**TRACEABILITY_GAPS_REMAIN.** The chain SysRS-288 / 305 / 306 → SRS-187 / +208 / 209 → SAD-063 / 084 / 085 / 086 → SDD-073 / 106 / 107 / 108 → Code +→ SWE.4 / SWE.5 / SWE.6 / SYS.4 / android-p0-acceptance is **closed end +to end**. The gaps that remain are: + +1. **SDD-111..SDD-116 not yet authored** — blocks the downward chain from + SRS-210..215 to Code and Verification. Owner: detailed-designer (in + flight per task brief). +2. **SysDes lacks dedicated derivation items for the Android voice audio + backend and for SysRS-305 / SysRS-306** — SRS-208..215 currently reuse + SysDes-135 as a generic anchor. Owner: system-designer. +3. **SDD-110 `Source SAD` field cites SAD-086** (which is the FGS + allocation) instead of the SAD-071 / SAD-076 PTT capability allocations. + Owner: detailed-designer (one-line correction in SDD). + +No code-without-SDD-or-upstream-trace condition was found: every code +anchor added this session carries an SDD or SRS reference comment, and +every SDD reference resolves to an existing SDD item (SDD-028, SDD-073, +SDD-105..110). **BLOCKED_REQUIREMENT_GAP is not raised.** + +### K. Recommended next agents + +1. **detailed-designer** — author SDD-111..SDD-116 covering SRS-210..215; + correct the `Source SAD` field on SDD-110 to cite SAD-071 / SAD-076. +2. **system-designer** — introduce dedicated SysDes derivation items for + SysRS-305 / SysRS-306 and for the Android voice audio backend so that + SRS-208..215 can tighten their `Source SysDes` away from the generic + SysDes-135. +3. **verification-planner** — once SDD-111..116 land, extend SWE.4 / SWE.5 + / SWE.6 to cover SRS-210..215 and add the corresponding android-p0 + acceptance test rows. +4. **software-architect** — formalise the SAD-036 + SAD-086 cross-cutting + + platform-specific allocation pattern explicitly in SAD §coverage so + it is not re-flagged as a dual-allocation conflict by future audits. + +| Version | Date | Description | +|---|---|---| +| 0.9.6 | 2026-05-18 | Added Android P0 reconciliation addendum incorporating SysRS-288 (API 28 per DEC-004), SysRS-305 (in-call audio mode), SysRS-306 (RECORD_AUDIO timing); SRS-187 (refreshed), SRS-208, SRS-209, SRS-210..215; SAD-063 (refreshed), SAD-084, SAD-085, SAD-086; SDD-028 (expanded), SDD-073 (refreshed), SDD-105..SDD-110, with SDD-111..SDD-116 reserved as PENDING_SDD; SWE4-UV-040..046, SWE5-IV-016..020, SWE6-SV-018..024, SYS4-SIV-015 (strengthened) + SYS4-SIV-017..018, and android-p0-acceptance.md TC-1..TC-13. Code back-fill traces recorded for AndroidManifest.xml, build.gradle.kts, ChanoraApplication.kt, MainActivity.kt, AndroidVoiceForegroundService.kt, AndroidPermissionRequester.kt, BackIntentBridge.kt, back_intent_policy.dart, back_intent_service.dart, and the three Flutter test files. Three gaps flagged: SDD-111..116 in flight, SysDes dedicated derivation items pending for SysRS-305/306 + Android voice audio backend, SDD-110 `Source SAD` field cites wrong SAD ID. TRACEABILITY_GAPS_REMAIN (no BLOCKED_REQUIREMENT_GAP). | + + +## Android Voice Audio Backend SDD Closure Addendum (Baseline Candidate 0.9.7) + +This addendum closes the `PENDING_SDD` markers recorded in §C / §E / §I of +the 0.9.6 addendum, after `docs/architecture/sdd.md` was extended with +SDD-111..SDD-116 (Android voice audio backend detailed design, sourced +from SAD-077 / SAD-081 / SAD-084 / SAD-085 / SAD-086 per the SDD v0.9.9 +revision). Per the strict hierarchy rules in §1, SRS-210..SRS-215 continue +to source from SysDes only; SDD-111..SDD-116 continue to source from SAD +only. No forbidden direct links are introduced. + +### A. SRS-210..SRS-215 → SDD-111..SDD-116 closure mapping + +The following table supersedes the corresponding rows in §C of the 0.9.6 +addendum. `Source SysDes` remains SysDes-135 pending the system-designer +follow-up recorded in §B below; the SDD anchors are now concrete. + +| SRS | Source SysDes | SAD | SDD (closed) | SWE.4 / SWE.5 / SWE.6 | Acceptance | +|---|---|---|---|---|---| +| SRS-210 (Android mouth-to-ear latency ≤150 ms low-latency / ≤250 ms fallback) | SysDes-135 | SAD-081 (audio engine) + SAD-084 (mode controller dependency) | SDD-111 (AndroidVoiceAudioBackend) + SDD-112 (AndroidVoiceStreamConfig — performance mode) + SDD-116 (AndroidVoiceVerificationMatrix — latency observation contract) | PENDING (verification-planner) | TC-6 supporting; device-lab latency measurement per SDD-116 | +| SRS-211 (AAudio input preset VOICE_COMMUNICATION + fallback) | SysDes-135 | SAD-081 | SDD-112 (AndroidVoiceStreamConfig — input preset retry ladder) | PENDING (verification-planner) | TC-6 indirect | +| SRS-212 (Hardware AEC/NS/AGC engagement + software fallback) | SysDes-135 | SAD-081 | SDD-113 (AndroidHardwareAudioEffects — session-id-bound engagement + per-effect software fallback) | PENDING (verification-planner) | TC-6 indirect; AndroidManifest.xml carries SDD-113 trace comment | +| SRS-213 (AAUDIO_USAGE_VOICE_COMMUNICATION / CONTENT_TYPE_SPEECH on output) | SysDes-135 | SAD-081 | SDD-112 (AndroidVoiceStreamConfig — output usage + content type) | PENDING (verification-planner) | TC-6 supporting | +| SRS-214 (AAUDIO sharing-mode EXCLUSIVE best-effort + SHARED fallback) | SysDes-135 | SAD-081 | SDD-112 (AndroidVoiceStreamConfig — sharing-mode fallback) | PENDING (verification-planner) | — | +| SRS-215 (Foreground service hosting for background mic capture) | SysDes-135 | SAD-086 (AndroidVoiceForegroundService) | SDD-107 (AndroidVoiceForegroundService) + SDD-114 (AndroidVoiceManifestRequirements — `foregroundServiceType="microphone"` manifest binding) + SDD-115 (AndroidVoiceLifecycleSequencing — service-start-before-stream-open ordering) | SWE4-UV-044, SWE5-IV-016/017/018, SWE6-SV-018 | TC-3, TC-3.4 | + +### B. Code-anchor refresh for SDD-114 and SDD-115 + +The 0.9.6 §F table is extended with the following anchors that newly +resolve to concrete SDD items (replacing the PENDING_SDD reservations for +SRS-212 / SRS-215 manifest binding): + +| Code path | SDD trace | SRS trace (indirect) | Status | +|---|---|---|---| +| apps/chanora_flutter/android/app/src/main/AndroidManifest.xml | SDD-105 (application element), SDD-107, SDD-108, SDD-113, SDD-114 (back-fill: all voice-subsystem manifest declarations), SDD-116 (device-matrix observation contract — trace-only, no manifest delta) | SRS-111, SRS-208, SRS-212, SRS-215 | Confirmed: SDD-105 application-element trace and SDD-107/108/113 traces present after Wave 2B-1 + fast-builder fix | +| apps/chanora_flutter/android/app/src/main/kotlin/app/chanora/chanora_flutter/MainActivity.kt | SDD-105 (bootstrap), SDD-028 (back-intent), SDD-106 (permission requester, with M-4 fix) | SRS-163, SRS-209 | Confirmed after fast-builder M-4 fix | +| crates/chanora_audio/src/engine.rs | SDD-115 (AndroidVoiceLifecycleSequencing — forward/reverse order; supersedes the prior no-op rationale comment at engine.rs:316-323 per SDD-113 item 5) | SRS-210..215 (via SDD-115) | PENDING_CODE — lifecycle sequencing implementation not yet landed; SDD-115 anchor recorded here so future code edit carries the trace | + +### C. Remaining open issues (carried forward from prior auditor pass) + +The closure of SDD-111..SDD-116 does not resolve three orthogonal issues +that remain owned by other agents. They are recorded here unchanged from +the 0.9.6 §H / §J reading, with proposed owners restated for clarity. + +| # | Open issue | Proposed owner | Resolution scope | +|---|---|---|---| +| 1 | **SDD-110 `Source SAD` field cites SAD-086 (FGS), should cite SAD-071 (Global PTT capability-dependent) and/or SAD-076 (capability badge).** SDD-110 anchors the Android PTT capability; SAD-086 is the FGS allocation, not the PTT allocation. The matrix continues to record SDD-110's current `Source SAD` field as authored (SAD-086 / SAD-075). This is an SDD-content fix, NOT a matrix fix. | detailed-designer | One-line correction to SDD-110's `Source SAD` field in `docs/architecture/sdd.md`. Do not edit upstream from the matrix. | +| 2 | **SysDes-135 generic reuse for SysRS-305, SysRS-306, and SRS-208..SRS-215.** SysDes-135 is the Android platform baseline; using it as the direct upstream for the in-call mode, RECORD_AUDIO timing, and voice-audio-backend SRS items is overloaded. SRS hierarchy discipline is preserved (SRS continues to source from SysDes only), but the SysDes anchor will be tightened in the next SysDes revision by introducing dedicated derivation items downstream of SysRS-055 / SysRS-305 / SysRS-306. | system-architect (system-designer) | Add dedicated SysDes items; then SRS-208..215 `Source SysDes` fields can be re-pointed in a follow-up SRS revision. | +| 3 | **SRS-111 dual allocation: SAD-036 (cross-cutting diagnostics/foreground-service range) AND SAD-086 (Android-specific FGS).** Acknowledged as the intended cross-cutting + platform-specific allocation pattern, not a true conflict. The pattern should be formalised explicitly in SAD §coverage so future audits do not re-flag it. | software-architect | Add an explicit "cross-cutting + platform-specific allocation" note to SAD §coverage. Do not resolve here. | + +### D. Decision + +**TRACEABILITY_GAPS_REMAIN.** The PENDING_SDD blockage that prevented +SRS-210..SRS-215 from reaching the SDD layer is now closed: every SRS in +that range has at least one concrete SDD anchor (SDD-111..SDD-116, plus +the previously-landed SDD-107 for SRS-215). The end-to-end chain +SysRS → SysDes → SRS → SAD → SDD now closes for SRS-210..SRS-215. + +Remaining gaps are non-blocking with respect to BLOCKED_REQUIREMENT_GAP: + +1. **SWE.4 / SWE.5 / SWE.6 verification anchors for SRS-210..SRS-215** — + PENDING for verification-planner now that SDD-111..SDD-116 are + authored. Acceptance test coverage in `android-p0-acceptance.md` + TC-1..TC-13 already cross-links SRS-210..215 indirectly via TC-6 and + TC-3 / TC-3.4; dedicated SWE.4 / SWE.5 / SWE.6 rows are still owed. +2. **`crates/chanora_audio/src/engine.rs` lifecycle sequencing + implementation per SDD-115** — PENDING_CODE; SDD-115 anchor recorded + in §B above so the future code edit is pre-traced and the prior + no-op rationale comment at engine.rs:316-323 is superseded per + SDD-113 item 5. +3. **Open issues 1–3 from §C above** (SDD-110 `Source SAD` correction, + SysDes-135 generic reuse tightening, SRS-111 dual-allocation pattern + formalisation) — each carries a named proposed owner. None are + matrix-layer fixes. + +**BLOCKED_REQUIREMENT_GAP is not raised.** No code anchor exists without +either an SDD reference or an upstream-traceable SRS comment. The +`engine.rs` SDD-115 PENDING_CODE row in §B is a forward-looking trace +reservation for a code change that has not been made; it is not a +code-without-SDD condition. + +### E. Recommended next agents + +1. **verification-planner** — extend SWE.4 / SWE.5 / SWE.6 to cover + SRS-210..SRS-215 against SDD-111..SDD-116, and add corresponding + android-p0-acceptance test rows (particularly latency measurement + per SDD-116, hardware-effect attach evidence per SDD-113, and + lifecycle ordering per SDD-115). +2. **detailed-designer** — correct the `Source SAD` field on SDD-110 + in `docs/architecture/sdd.md` from SAD-086 to SAD-071 / SAD-076 (one- + line change; see open issue 1 in §C). +3. **system-architect** — introduce dedicated SysDes derivation items + downstream of SysRS-055 / SysRS-305 / SysRS-306 covering the Android + voice audio backend, in-call mode engagement, and RECORD_AUDIO + timing, so that SRS-208..SRS-215 can tighten their `Source SysDes` + fields away from the generic SysDes-135 (see open issue 2 in §C). +4. **software-architect** — formalise the SAD-036 + SAD-086 cross- + cutting + platform-specific allocation pattern explicitly in SAD + §coverage (see open issue 3 in §C). +5. **codex-builder** — implement the SDD-115 lifecycle sequencing in + `crates/chanora_audio/src/engine.rs`, superseding the prior no-op + rationale comment at engine.rs:316-323 per SDD-113 item 5 (see §B + PENDING_CODE row). + +| Version | Date | Description | +|---|---|---| +| 0.9.7 | 2026-05-18 | Closed PENDING_SDD markers for SRS-210..SRS-215 after SDD-111..SDD-116 landed in `docs/architecture/sdd.md` (Android voice audio backend detailed design, SDD v0.9.9). Concrete SRS → SDD mapping recorded in §A: SRS-210 → SDD-111+112+116; SRS-211 → SDD-112; SRS-212 → SDD-113; SRS-213 → SDD-112; SRS-214 → SDD-112; SRS-215 → SDD-107+114+115. Code-anchor refresh in §B added SDD-114 trace to AndroidManifest.xml (back-fill of Wave 2B-1 manifest declarations), confirmed SDD-105 application-element trace and SDD-106 M-4 fix on MainActivity.kt after fast-builder pass, and recorded SDD-115 as PENDING_CODE on `crates/chanora_audio/src/engine.rs` (lifecycle sequencing implementation not yet landed; SDD-113 item 5 supersedes the prior no-op rationale comment). Three open issues carried forward unchanged: SDD-110 `Source SAD` field cites SAD-086 instead of SAD-071 / SAD-076 (owner: detailed-designer); SysDes-135 generic reuse for SysRS-305/306 + SRS-208..215 (owner: system-architect); SRS-111 dual allocation SAD-036 + SAD-086 (owner: software-architect). TRACEABILITY_GAPS_REMAIN; BLOCKED_REQUIREMENT_GAP not raised. | + + +## End-to-End Closure Addendum (Baseline Candidate 0.9.8) + +This addendum incorporates the deltas accumulated since the 0.9.7 addendum +across SysDes, SRS, SAD, SDD, and Code. It records the resolution of the +three open issues carried forward by the 0.9.7 audit and re-runs the +end-to-end traceability check for the six chains exercised by the most +recent sub-wave. Strict hierarchy discipline is preserved at every layer +(`SysDes -> SysRS`, `SRS -> SysDes`, `SAD -> SRS`, `SDD -> SAD`, +verification downstream of all four). + +### A. Delta inventory (since 0.9.7) + +#### A.1 SysDes (`docs/architecture/sysdes.md`, baseline 0.9.6 → 0.9.7) + +| New SysDes | Subsystem | Source SysRS | Replaces (generic anchor) | +|---|---|---|---| +| SysDes-152 | Android in-call audio mode subsystem | SysRS-305 | SysDes-135 (generic Android baseline) | +| SysDes-153 | Android RECORD_AUDIO runtime permission acquisition flow | SysRS-306 | SysDes-135 (generic) | +| SysDes-154 | Android voice audio backend subsystem | SysRS-055 (+ SysRS-305 refinement) | SysDes-135 (generic) | +| SysDes-155 | macOS runtime baseline (parallel to SysDes-133 iOS / SysDes-135 Android) | SysRS-002, SysRS-050, SysRS-193 | none (new platform-baseline element) | + +SysDes §17 SysRS-coverage statement extended to record the +SysRS-305/306 → SysDes-152..154 mapping and the SysRS-002/050/193 macOS-slice +→ SysDes-155 mapping; SysDes-135 retained unchanged as the platform-baseline +context item. + +#### A.2 SRS (`docs/requirements/srs.md`, baseline 0.9.7 → 0.9.8) + +`Source SysDes` retargeted on the eight Android voice-audio SRS items: + +| SRS | Primary `Source SysDes` (new) | Secondary `Source SysDes` | +|---|---|---| +| SRS-208 | SysDes-152 | SysDes-135 (context) | +| SRS-209 | SysDes-153 | SysDes-135 (context) | +| SRS-210 | SysDes-154 | SysDes-135 (context) | +| SRS-211 | SysDes-154 | SysDes-135 (context) | +| SRS-212 | SysDes-154 | SysDes-135 (context) | +| SRS-213 | SysDes-154 | SysDes-152 (in-call-mode routing precondition), SysDes-135 (context) | +| SRS-214 | SysDes-154 | SysDes-135 (context) | +| SRS-215 | SysDes-154 | SysDes-135 (context) | + +No SRS text/behavioural content was modified. + +#### A.3 SAD (`docs/architecture/sad.md`, baseline 0.9.6 → 0.9.7 → 0.9.8) + +- §24.1 "Cross-cutting + platform-specific allocations" subsection added + with the SRS-111 ↔ SAD-036 + SAD-086 worked example. §24.2 SRS-111..124 + matrix row annotated to record the dual allocation explicitly. +- SAD-087 (macOS runtime baseline; deployment-target isolation in + `apps/chanora_flutter/macos/chanora_bridge.podspec`, universal-binary + `arm64+x86_64` `lipo` packaging, `.framework` `Versions/A` layout, + hand-rolled CocoaPods podspec automation) added as the macOS counterpart + to SAD-061 (iOS) and SAD-063 (Android). `Source SRS = SRS-013` + (multi-platform support). +- SAD-087's initial `Source SysDes = open follow-up` retargeted in the + same session to `SysDes-155`. +- §27 SRS-to-SAD coverage matrix updated to add the SRS-013 → SAD-087 row. + +#### A.4 SDD (`docs/architecture/sdd.md`, baseline 0.9.10 → 0.9.11 → 0.9.12) + +- SDD-110 (`AndroidPttCapability`) `Source SAD` corrected from the + incorrect `SAD-077` (PttSanitizer) and the previously-flagged `SAD-086` + (FGS) to `SAD-071` (PTT-backend trait isolation + capability publish) + + `SAD-076` (`BridgeEvent::PttCapability` event surface). §11 coverage + matrix moved SDD-110 from the SAD-063/081/084/085/086 row to a new + SAD-071 / SAD-076 row. **Resolves 0.9.7 open issue 1.** +- SDD-119 (`iOSAndMacOSBridgeBuildAutomation`) `Source SAD` extended from + `SAD-061, SAD-062` to `SAD-061, SAD-062, SAD-087` with explicit + iOS-vs-macOS attribution. §11 coverage matrix row updated. + +#### A.5 Code (since prior auditor pass) + +| Code path | Trace tags | Notes | +|---|---|---| +| `apps/chanora_flutter/android/app/src/main/kotlin/app/chanora/chanora_flutter/AndroidPermissionRequester.kt` | SDD-106 §1..§8, SRS-209 | M-1 fix: SharedPreferences-backed `has-ever-requested` flag (SDD-106 §3, §4). | +| `apps/chanora_flutter/android/app/src/main/kotlin/app/chanora/chanora_flutter/BackIntentBridge.kt` | SDD-028 (§1, §4) | M-2 popToSystem idempotency, M-3 object→class refactor, X-2 channel constants pulled from `MethodChannels`. | +| `apps/chanora_flutter/android/app/src/main/kotlin/app/chanora/chanora_flutter/MainActivity.kt` | SDD-028, SDD-105, SDD-106 | Owns the `BackIntentBridge` instance and `AndroidPermissionRequester` lifecycle wiring. | +| `apps/chanora_flutter/android/app/src/main/kotlin/app/chanora/chanora_flutter/MethodChannels.kt` | SDD-028, SAD-018 | New constants `BACK_INTENT`, `METHOD_BACK_INTENT`, `METHOD_POP_TO_SYSTEM` (X-2 fix). | +| `apps/chanora_flutter/android/app/build.gradle.kts` | SDD-118 items 1–13, SDD-073, SDD-105, SDD-109 | Gradle automation for chanora_bridge cdylib (cargo-ndk, three ABIs, jniLibs staging, preflight, release-inspection assertion). | +| `crates/chanora_audio/src/android_voice_unit.rs` (new) | SDD-111..SDD-115 | AndroidVoiceUnit. | +| `crates/chanora_audio/src/mobile_voice_backend.rs` (new) | SDD-111, SDD-112 | `MobileVoiceAudioBackend` trait + `AndroidVoiceStreamConfig`. | +| `crates/chanora_audio/src/engine.rs` | SDD-111, SDD-113, SDD-115; `android_engage_voice_communication` path for SDD-108 | Android backend integration; forward/reverse lifecycle sequencing closed. | +| `crates/chanora_audio/Cargo.toml` | SDD-111 | `oboe = "0.6"` dependency added. | + +The 0.9.7 `engine.rs` SDD-115 PENDING_CODE row is **closed** by the +forward/reverse lifecycle sequencing that now lives in `engine.rs` +(start path at lines ~307–388, teardown path at lines ~748–772, helper +`android_engage_voice_communication` at line 1494). + +### B. End-to-end chain verification + +Each chain below is rebuilt top-to-bottom from the present state of the +repository. Evidence citations refer to file paths and (where useful) +the line locations of trace tags. "CLOSED" means every layer between +SysRS and Verification/Code has a concrete, in-document anchor. + +| # | Chain | Status | Evidence | +|---|---|---|---| +| 1 | SysRS-305 → SysDes-152 → SRS-208 → SAD-084 → SDD-108 → `engine.rs::android_engage_voice_communication` → SWE4-UV-045 / SWE5-IV-018 / SWE6-SV-020 / TC-6 | **CLOSED** | SysDes-152 narrative in `sysdes.md`; SRS-208 `Source SysDes: SysDes-152` at `srs.md:2633`; SAD-084 in `sad.md`; SDD-108 + §11 row in `sdd.md` (v0.9.8 entry); `engine.rs:1494` `android_engage_voice_communication`; 0.9.6 addendum §B records the verification anchors. | +| 2 | SysRS-306 → SysDes-153 → SRS-209 → SAD-085 → SDD-106 → `AndroidPermissionRequester.kt` → SWE4-UV-041 / SWE5-IV-017 / SWE6-SV-019 / TC-2 | **CLOSED** | SysDes-153 in `sysdes.md`; SRS-209 `Source SysDes: SysDes-153` at `srs.md:2645`; SAD-085 in `sad.md`; SDD-106 in `sdd.md` (v0.9.8); `AndroidPermissionRequester.kt` lines 18, 65, 83, 102, 142, 158, 175, 193, 217, 258, 267 (SDD-106 trace tags incl. M-1 SharedPreferences fix); 0.9.6 addendum §B records verification anchors. | +| 3 | SysRS-055 + SysRS-305 → SysDes-154 → SRS-210..215 → SAD-081 (+SAD-077 for SRS-212 narrative / SAD-086 for SRS-215 FGS aspect) → SDD-111..SDD-116 → `crates/chanora_audio/src/android_voice_unit.rs` (+ `mobile_voice_backend.rs`, `engine.rs`) → SWE4-UV-047..052 / SWE5-IV-021..026 / SWE6-SV-025..030 / TC-14..18 | **CLOSED (engineering layers)** **/ PENDING (verification rows)** | SysDes-154 in `sysdes.md`; SRS-210..215 `Source SysDes: SysDes-154 (primary)` at `srs.md:2670, 2682, 2694, 2706 (+SysDes-152 sec.), 2718, 2730`; SDD-111..116 authored in `sdd.md` per the 0.9.7 addendum §A; code anchors `android_voice_unit.rs`, `mobile_voice_backend.rs`, `engine.rs` (SDD-111/113/115 traces lines 147, 307, 339, 367, 383, 748, 766), `Cargo.toml` line 69 (`oboe = "0.6"`). Verification SWE.4/5/6 rows for SRS-210..215 remain PENDING_VERIFICATION (carried from 0.9.7 §D item 1; owner: verification-planner). | +| 4 | SysRS-002 + SysRS-050 + SysRS-193 → SysDes-155 → SRS-013 (macOS slice) → SAD-087 → SDD-119 → `apps/chanora_flutter/macos/chanora_bridge.podspec` | **CLOSED (engineering layers)** | SysDes-155 narrative in `sysdes.md:2930` (citing the podspec by path, value not embedded); SAD-087 in `sad.md:1427`, `Source SysDes: SysDes-155` per `sad.md:1499` (v0.9.8 history entry); SDD-119 `Source SAD: SAD-061 + SAD-062 + SAD-087` per `sdd.md:1773` (v0.9.12 history entry) and §11 coverage row; podspec exists at the cited path. Note: SDD-119 is an explicit back-fill unit (per its own narrative); the podspecs do not carry an SDD-119 trace tag in-source — acceptable for a back-fill anchor and not a defect. | +| 5 | SDD-118 (Android auto-build) → `apps/chanora_flutter/android/app/build.gradle.kts`. Upstream chain SDD-118 → SAD-063, cross-traces to SDD-073, SDD-105, SDD-109. | **CLOSED** | `build.gradle.kts` lines 154, 165, 171, 176, 183, 190, 200, 207, 212, 217, 226, 238, 252, 259, 263, 270, 273, 276, 277, 291, 297, 304, 324, 332, 344, 356, 365, 373, 380, 393 all carry SDD-118 trace tags (items 1–13). SDD-118 narrative in `sdd.md` v0.9.10 entry sources from SAD-063 with cross-trace to SDD-073 / SDD-105 / SDD-109. | +| 6 | SDD-119 (iOS/macOS auto-build) → `apps/chanora_flutter/ios/chanora_bridge.podspec` + `apps/chanora_flutter/macos/chanora_bridge.podspec`. Upstream chain SDD-119 → SAD-061 + SAD-062 + SAD-087. | **CLOSED** | SDD-119 narrative in `sdd.md` v0.9.10 + v0.9.12 entries; both podspec files exist at the cited paths. Back-fill anchor; in-source trace tags not required (and not present), per the SDD-119 narrative. | +| 7 | SDD-110 (AndroidPttCapability) → SAD-071 + SAD-076 anchors | **CLOSED** | `sdd.md` v0.9.11 history entry records the SAD-077 → SAD-071 + SAD-076 retarget; §11 coverage matrix updated accordingly. **0.9.7 open issue 1 closed.** | + +### C. Resolved open issues (from prior auditor passes) + +| # | Prior issue | Resolution | +|---|---|---| +| 1 | **SDD-110 `Source SAD` mis-anchor** (0.9.6 §H.2, 0.9.7 §C row 1) — cited SAD-086 (FGS) / SAD-077 (PttSanitizer), should cite SAD-071 + SAD-076. | **CLOSED** in `sdd.md` v0.9.11 entry. Section 11 coverage matrix moved SDD-110 to a new SAD-071/SAD-076 row. | +| 2 | **SysDes-135 generic reuse for SysRS-305 / SysRS-306 / Android voice audio backend** (0.9.6 §H.1, 0.9.7 §C row 2). | **CLOSED** by SysDes v0.9.6 (added SysDes-152, SysDes-153, SysDes-154) and SRS v0.9.8 (retargeted SRS-208/209/210..215 `Source SysDes` away from SysDes-135 to SysDes-152/153/154 with SysDes-135 retained only as secondary/context). | +| 3 | **SRS-111 dual allocation (SAD-036 + SAD-086) pattern not formalised** (0.9.6 §H.2, 0.9.7 §C row 3). | **CLOSED** by SAD v0.9.7 (added §24.1 "Cross-cutting + platform-specific allocations" subsection with SRS-111 / SAD-036 / SAD-086 as the worked example; §24.2 SRS-111..124 matrix row annotated to record the dual allocation explicitly). | +| 4 | **SDD-115 `engine.rs` lifecycle sequencing PENDING_CODE** (0.9.7 §B / §D item 2). | **CLOSED** by `engine.rs` (forward sequence lines 307–388 invoking `chanora_android_start_voice_service` → `android_engage_voice_communication` → `AndroidVoiceUnit::open` → `start`; reverse sequence lines 748–772). | +| 5 | **SAD-087 `Source SysDes` open follow-up** (created by SAD v0.9.7). | **CLOSED** in same session by SAD v0.9.8 retargeting `Source SysDes` to SysDes-155. | + +### D. Remaining open issues + +| # | Issue | Severity | Proposed owner | +|---|---|---|---| +| 1 | **SWE.4 / SWE.5 / SWE.6 verification rows for SRS-210..SRS-215** are still PENDING — SDD-111..SDD-116 are authored and code anchors exist (`android_voice_unit.rs`, `mobile_voice_backend.rs`, `engine.rs`, `Cargo.toml`), but dedicated unit / integration / qualification verification rows have not yet been added. Acceptance coverage is via TC-6, TC-3, TC-3.4 indirectly. | Medium — engineering chain is closed end-to-end, but verification evidence for SRS-210..215 is currently indirect. Does NOT raise BLOCKED_REQUIREMENT_GAP because code is fully traced to SDD and upstream. | verification-planner | +| 2 | **SDD-119 known iOS podspec doc-drift** — the iOS podspec comment mentions `aarch64-apple-ios-sim` but the script does not build the simulator slice (recorded as a TODO in SDD-119 narrative itself; not a traceability defect). | Low — internal to SDD-119, no upstream/downstream linkage broken. | detailed-designer or codex-builder (per SDD-119's own TODO). | +| 3 | **SDD-119 macOS deployment-target single-source-of-truth** is recorded as an open follow-up improvement in SDD-119; SysDes-155 already cites the podspec as the canonical location, so the SysDes/SAD/SDD chain is aligned, but the SoT improvement work is still TODO. | Low — chain is closed; this is an internal hardening item. | detailed-designer (improvement, not defect). | +| 4 | **Optional finer-grained macOS minimum-runtime SysRS item** parallel to SysRS-286 (iOS) and SysRS-288 (Android) was recommended by SysDes-155 itself; current chain is closed via SysRS-002 / SysRS-050 / SysRS-193 (multi-platform + macOS runtime + signed/notarized macOS build), so absence is non-blocking. | Low — optional refinement. | system-requirements owner (optional). | + +### E. Orphan inventory + +Zero new orphans introduced by this sub-wave. The four 0.9.7-listed +PENDING items (SDD-111..116 reservations) are all closed: + +| ID range | Prior status | Current status | Evidence | +|---|---|---|---| +| SDD-111..SDD-116 | PENDING_SDD (0.9.6) | AUTHORED (0.9.7 §A) | `sdd.md` body + 0.9.7 addendum §A mapping. | +| `engine.rs` SDD-115 anchor | PENDING_CODE (0.9.7 §B) | LANDED | `engine.rs:307–388` + `:748–772` + `:1494`. | +| SAD-087 `Source SysDes` | open follow-up (SAD 0.9.7) | RESOLVED to SysDes-155 (SAD 0.9.8) | `sad.md:1499`. | +| SDD-110 `Source SAD` | flagged conflict (0.9.6, 0.9.7) | CORRECTED to SAD-071 + SAD-076 (SDD 0.9.11) | `sdd.md:1779`. | + +Total orphans / dangling references / conflicts remaining: **0**. + +### F. Dangling references + +None at this revision. The previously dangling `SAD-086 → SDD-110` +scope-mismatch reference is resolved by the SDD-110 retarget. The +previously dangling SDD-111..116 PENDING anchors are all resolved. The +SDD-119 macOS-anchor follow-up is closed by SAD-087 + SysDes-155. + +### G. Conflicts + +None at this revision. The 0.9.6 SRS-111 dual-allocation conflict was +formalised as a deliberate cross-cutting + platform-specific allocation +pattern in SAD §24.1 (v0.9.7), and is no longer a conflict. + +### H. Strict-hierarchy discipline check + +- SRS-208..215 source from SysDes only (SysDes-152/153/154 + SysDes-135 + as context); no direct SysRS source. +- SAD-084..087 source from SRS only; no direct SysDes link. +- SDD-106, SDD-108, SDD-110, SDD-111..116, SDD-118, SDD-119 source + from SAD only; no direct SysRS / SysDes / SRS link. +- Verification artifacts remain downstream evidence and do not + participate in the engineering hierarchy. + +No forbidden direct links were introduced by this sub-wave. + +### I. BLOCKED_REQUIREMENT_GAP check + +No code anchor exists without an SDD trace and an upstream-traceable +reference: + +- `AndroidPermissionRequester.kt`, `BackIntentBridge.kt`, `MainActivity.kt`, + `MethodChannels.kt`: SDD-106 / SDD-028 / SDD-105 traces present in-source, + resolving upward to SAD-085 / SAD-018 / SAD-063 → SRS-209 / SRS-163 / SRS-187. +- `build.gradle.kts`: SDD-118 / SDD-073 / SDD-109 traces present, resolving + upward to SAD-063. +- `crates/chanora_audio/src/android_voice_unit.rs`, `mobile_voice_backend.rs`, + `engine.rs`, `Cargo.toml`: SDD-111..SDD-115 traces present, resolving upward + to SAD-081 (+ SAD-077 / SAD-084 / SAD-086 for cross-cutting aspects) → + SRS-210..215. + +**BLOCKED_REQUIREMENT_GAP is not raised.** + +### J. Decision + +**TRACEABILITY_OK (engineering hierarchy)** — the SysRS → SysDes → SRS → +SAD → SDD → Code chain is closed end-to-end for every item exercised by +this sub-wave (chains 1–7 in §B). The three open issues flagged by the +prior auditor (SDD-110 mis-anchor, SysDes-135 generic reuse, SRS-111 +dual-allocation pattern) and the two PENDING markers carried by 0.9.7 +(`engine.rs` SDD-115 PENDING_CODE, SAD-087 `Source SysDes` follow-up) +are all resolved. + +**TRACEABILITY_GAPS_REMAIN (verification only)** — dedicated SWE.4 / +SWE.5 / SWE.6 rows for SRS-210..SRS-215 are still pending against the +now-authored SDD-111..SDD-116 anchors. Acceptance coverage in +`android-p0-acceptance.md` (TC-6, TC-3, TC-3.4) provides indirect +coverage, so this gap is non-blocking for the engineering chain but +should be closed by the verification owner. + +**Overall decision: TRACEABILITY_OK for the engineering layers +(SysRS → SDD → Code); TRACEABILITY_GAPS_REMAIN for SRS-210..SRS-215 +verification rows only.** No BLOCKED_REQUIREMENT_GAP raised. + +### K. Recommended next agents + +1. **verification-planner** — author SWE.4 / SWE.5 / SWE.6 rows for + SRS-210..SRS-215 against SDD-111..SDD-116 (specifically: latency + observation per SDD-116, hardware-effect attach per SDD-113, + lifecycle ordering per SDD-115, input/output AAudio stream configs + per SDD-112, sharing-mode fallback per SDD-112, FGS manifest binding + per SDD-114). Add corresponding `android-p0-acceptance.md` TC rows + if dedicated tests are warranted beyond the indirect TC-6 / TC-3 / TC-3.4 + coverage already recorded. +2. **detailed-designer or codex-builder** — address the SDD-119 iOS + podspec doc-drift TODO (`aarch64-apple-ios-sim` comment vs. script). + Low priority. +3. **detailed-designer** — pursue the SDD-119 macOS deployment-target + single-source-of-truth improvement (optional, internal hardening). +4. **system-requirements owner** (optional) — author a finer-grained + macOS minimum-runtime SysRS item parallel to SysRS-286 / SysRS-288 if + future audits prefer a dedicated SysRS anchor over the current + SysRS-002 + SysRS-050 + SysRS-193 composite. + +If items 1–4 above are deferred, **no further action is required to +preserve end-to-end engineering-layer traceability** at this revision. + +| Version | Date | Description | +|---|---|---| +| 0.9.8 | 2026-05-18 | End-to-end closure addendum. Incorporated SysDes v0.9.7 deltas (SysDes-152 Android in-call audio mode, SysDes-153 Android RECORD_AUDIO permission flow, SysDes-154 Android voice audio backend, SysDes-155 macOS runtime baseline); SRS v0.9.8 retargeting of SRS-208..215 `Source SysDes` from generic SysDes-135 to SysDes-152/153/154 (with SysDes-135 retained as secondary/context, and SysDes-152 as secondary on SRS-213 for output-usage routing precondition); SAD v0.9.7 + 0.9.8 deltas (§24.1 cross-cutting + platform-specific allocation pattern formalised with SRS-111 / SAD-036 / SAD-086 worked example; SAD-087 macOS runtime baseline added; SAD-087 `Source SysDes` retargeted to SysDes-155 in same session); SDD v0.9.11 + 0.9.12 deltas (SDD-110 `Source SAD` corrected from SAD-077 / SAD-086 to SAD-071 + SAD-076; SDD-119 `Source SAD` extended to include SAD-087 for the macOS half); and Code deltas across AndroidPermissionRequester.kt (M-1 SharedPreferences fix), BackIntentBridge.kt (M-2/M-3/X-2 fixes), MainActivity.kt, MethodChannels.kt (new channel constants), build.gradle.kts (SDD-118 Gradle automation), crates/chanora_audio/src/android_voice_unit.rs (new), mobile_voice_backend.rs (new), engine.rs (SDD-115 lifecycle sequencing landed; `android_engage_voice_communication` path for SDD-108), Cargo.toml (`oboe = "0.6"`). All three open issues flagged by the prior auditor are resolved: (1) SDD-110 mis-anchor → corrected; (2) SysDes-135 generic reuse → tightened to SysDes-152/153/154; (3) SRS-111 dual-allocation pattern → formalised in SAD §24.1. PENDING_CODE for `engine.rs` SDD-115 → landed. SAD-087 `Source SysDes` open follow-up → resolved to SysDes-155. End-to-end engineering chain (SysRS → SysDes → SRS → SAD → SDD → Code) verified CLOSED for chains 1–7 (SysRS-305 → TC-6, SysRS-306 → TC-2, SysRS-055 + SysRS-305 → engine.rs/android_voice_unit.rs, SysRS-002/050/193 → podspec, SDD-118 → build.gradle.kts, SDD-119 → podspec, SDD-110 → SAD-071 + SAD-076). Decision: TRACEABILITY_OK (engineering hierarchy) / TRACEABILITY_GAPS_REMAIN (SWE.4/5/6 rows for SRS-210..215 are PENDING for verification-planner; non-blocking, indirect acceptance coverage via TC-6 / TC-3 / TC-3.4 already present). BLOCKED_REQUIREMENT_GAP is NOT raised: every code anchor carries an SDD trace and resolves upstream. Strict hierarchy preserved at every layer. | + + +## SDD v0.9.13 Absorption + SRS-210..SRS-215 Verification Closure Addendum (Baseline Candidate 0.9.9) + +This addendum absorbs the SDD v0.9.13 documentation-cleanup amendments +into the matrix, closes the PENDING verification-row markers for +SRS-210..SRS-215 against concrete SWE.4/5/6 IDs that now exist in the +verification baselines, adds `crates/chanora_bridge/build.rs` to the +code-anchor table, and cross-references the newly-allocated DEC-032 +(temporary `abiFilters` reduction). Strict layered sourcing is +preserved (`SDD -> SAD` only); no upstream documents were edited. + +### A. SDD v0.9.13 deltas absorbed + +| SDD change | Matrix impact | +|---|---| +| SDD-105 software-unit list extended to include `crates/chanora_bridge/build.rs`, `ChanoraApplication.kt` (loader: `System.loadLibrary`), and `MainActivity.kt` (`initChanoraContext` invocation) | §E SDD-105 row code-anchors and 0.9.6 §F back-fill table extended (see §B and §C below). The prior 0.9.6 / 0.9.7 entries that listed only `ChanoraApplication.kt, MainActivity.kt` for SDD-105 are superseded by §B. | +| SDD-105 new Cross-trace bullet → SDD-118 item 6 (extended) — `libc++_shared.so` runtime co-staging + DT_NEEDED preload contract | Recorded in §B. SDD-105's `Source SAD` is unchanged (still SAD-063). | +| SDD-118 new "Item 6 (extended)" sub-paragraph mandating `libc++_shared.so` co-staging from NDK sysroot into `jniLibs//` with explicit sysroot-triple mapping | Recorded in §B; `build.gradle.kts` continues to carry the SDD-118 trace tags. Release-inspection assertion (SDD-118 item 10) now also covers `libc++_shared.so`. | +| SDD-119 item 4 simulator doc-drift RESOLVED (Option α: drop `aarch64-apple-ios-sim` comment) | §D row 2 of 0.9.8 closed. Removed from the remaining-open-issues list (see §E). | + +No SDD-content was authored by this addendum; the above merely records +the matrix-side absorption of changes already present in SDD v0.9.13. + +### B. Code-anchor table — new and extended rows + +The following rows extend the 0.9.6 §F and 0.9.8 §A.5 code-anchor +tables to absorb SDD v0.9.13: + +| Code path | SDD trace | SRS trace (indirect) | Notes | +|---|---|---|---| +| `crates/chanora_bridge/build.rs` | SDD-105 (software unit; emits `cargo:rustc-link-lib=dylib=c++_shared` so `DT_NEEDED libc++_shared.so` is baked into `libchanora_bridge.so`) — Cross-trace: SDD-118 item 6 (extended) for the runtime co-staging contract | SRS via SAD-063 → SRS-187 | New row introduced by SDD v0.9.13. Anchored to SDD-105. | +| `apps/chanora_flutter/android/app/src/main/kotlin/app/chanora/chanora_flutter/ChanoraApplication.kt` | SDD-105 (loader: `System.loadLibrary` for `libchanora_bridge.so`) | SRS via SAD-063 → SRS-187 | Re-confirmed as the canonical loader site by SDD v0.9.13; supersedes the prior MainActivity-as-loader wording. | +| `apps/chanora_flutter/android/app/src/main/kotlin/app/chanora/chanora_flutter/MainActivity.kt` | SDD-105 (`initChanoraContext` invocation only — NOT `System.loadLibrary`), SDD-028, SDD-106 | SRS-163, SRS-209 | Loader responsibility moved to `ChanoraApplication.kt`; MainActivity retains `initChanoraContext` only per SDD v0.9.13 item 1. | +| `apps/chanora_flutter/android/app/build.gradle.kts` | SDD-073, SDD-105 (load-order preload via co-staging), SDD-109, SDD-118 items 1–13 incl. **item 6 (extended)** — `libc++_shared.so` co-staging from NDK sysroot into `jniLibs//`; release-inspection assertion (item 10) extended to cover `libc++_shared.so` | SRS-187, SRS-188, SRS-119 | Trace tags already present in source; SDD-118 item 6 (extended) is absorbed here for the matrix record. Cross-references DEC-032 (§D) for the current temporary `abiFilters` reduction to arm64-v8a only. | + +### C. SRS-210..SRS-215 verification-row closure (replaces PENDING markers) + +The 0.9.6 §C, 0.9.7 §A, and 0.9.8 §D row 1 PENDING markers for the +SWE.4 / SWE.5 / SWE.6 rows of SRS-210..SRS-215 are CLOSED. The +verification documents now enumerate the concrete IDs (existence +verified by grep against `swe4-unit-verification-plan.md`, +`swe5-software-integration-verification-plan.md`, +`swe6-software-verification-plan.md`). + +| SRS | SWE.4 (unit) | SWE.5 (integration) | SWE.6 (qualification) | Replaces | +|---|---|---|---|---| +| SRS-210 (mouth-to-ear latency tier) | SWE4-UV-051 | SWE5-IV-021 | SWE6-SV-025 | 0.9.6 §C PENDING | +| SRS-211 (AAudio input preset + fallback) | SWE4-UV-048 | SWE5-IV-021 | SWE6-SV-026 | 0.9.6 §C PENDING | +| SRS-212 (hardware AEC/NS/AGC + software fallback) | SWE4-UV-050 | SWE5-IV-022 | SWE6-SV-027 | 0.9.6 §C PENDING | +| SRS-213 (AAUDIO_USAGE_VOICE_COMMUNICATION / CONTENT_TYPE_SPEECH output) | SWE4-UV-047, SWE4-UV-052 | SWE5-IV-021 | SWE6-SV-028 | 0.9.6 §C PENDING | +| SRS-214 (sharing-mode EXCLUSIVE best-effort + SHARED fallback) | SWE4-UV-049 | SWE5-IV-021 | SWE6-SV-029 | 0.9.6 §C PENDING | +| SRS-215 (foreground-service-hosted background mic capture) | SWE4-UV-044 | SWE5-IV-018 | SWE6-SV-030 (device-matrix expansion; SWE6-SV-018 remains single-device baseline) | 0.9.6 §C PENDING | + +Existence confirmation: + +- SWE.4 IDs SWE4-UV-044, -047, -048, -049, -050, -051, -052 present in + `docs/verification/swe4-unit-verification-plan.md`. +- SWE.5 IDs SWE5-IV-018, -021, -022 present in + `docs/verification/swe5-software-integration-verification-plan.md`. +- SWE.6 IDs SWE6-SV-025, -026, -027, -028, -029, -030 present in + `docs/verification/swe6-software-verification-plan.md`. + +No `NEEDS_VERIFICATION_ID_AUTHORING` flag is raised: every recommended +ID exists in the corresponding verification baseline. + +### D. DEC-032 governance cross-reference + +DEC-032 (temporary reduction of Android `abiFilters` to `arm64-v8a` +only during the P0 smoke-test cycle) is recorded in +`docs/governance/product-decision-register.md`. It is a temporary +deviation from SDD-073 item 4 and SDD-118 item 3 (which mandate the +three-ABI set `{arm64-v8a, armeabi-v7a, x86_64}`). The deviation is +governance-layer only — neither SDD-073 item 4 nor SDD-118 item 3 is +mutated, and the existing matrix rows that anchor `build.gradle.kts` +to SDD-073 / SDD-118 / SDD-109 are unchanged. + +Cross-reference scope: + +- SDD-073 item 4 (canonical three-ABI set) +- SDD-118 item 3 (Gradle automation enforcing the three-ABI set) +- DEC-032 (active temporary deviation; restore-by gate = P0 release) +- Audit task `ses_1c7645e36ffeY007MaYPep4wqs` (auditor New-D) + +Status: **Active (temporary deviation)** until the audiopus_sys + +cmake-rs + NDK toolchain-file ANDROID_ABI propagation fix lands, all +three ABIs compile cleanly in CI, the `abiFilters` declaration is +restored to the three-ABI set, and the release-inspection assertion +confirms all three `.so` files (plus `libc++_shared.so` per SDD-118 +item 6 extended) are staged in any release AAB. + +### E. Open-issue inventory refresh + +The 0.9.8 §D remaining-open-issues list updates as follows: + +| 0.9.8 # | Issue | Current status | +|---|---|---| +| 1 | SWE.4 / SWE.5 / SWE.6 rows for SRS-210..SRS-215 pending | **CLOSED by §C above.** Concrete IDs replace PENDING markers in every cell. | +| 2 | SDD-119 known iOS podspec doc-drift (`aarch64-apple-ios-sim` comment) | **CLOSED.** SDD v0.9.13 item 5 resolved the doc-drift with Option α (drop the comment); simulator support explicitly deferred to a future P1 SDD revision. | +| 3 | SDD-119 macOS deployment-target single-source-of-truth | **OPEN (carried).** Internal hardening item; owner: detailed-designer. Intentionally not changed by this addendum. | +| 4 | Optional finer-grained macOS minimum-runtime SysRS item parallel to SysRS-286 / SysRS-288 | **OPEN (carried, deferred).** SysRS-290 anchor work explicitly deferred per the task brief; current chain via SysRS-002 / SysRS-050 / SysRS-193 remains adequate. Owner: system-requirements (optional). | + +New open follow-ups recorded by this addendum: + +| # | Issue | Severity | Proposed owner | +|---|---|---|---| +| 5 | **DEC-032 restore-by gate** — `abiFilters` must return to the canonical three-ABI set before P0 release. Tracked in `product-decision-register.md` DEC-032. | High (release-gate) but non-traceability — engineering chain to SDD-073 item 4 / SDD-118 item 3 remains intact. | Build/Toolchain owner. | +| 6 | **`MissedKeyUpWatchdog` production-design follow-up to unblock SWE4-UV-045** — DEC-031 disables the watchdog on P0, and unit verification of the Android `AudioMode` lifecycle (SWE4-UV-045) interacts with the P1 redesign space. Recorded here as a governance cross-reference; not an upstream-document edit. | Low (P1 scope; no P0 blocker). | Audio Owner / detailed-designer (per DEC-031 P1 redesign). | +| 7 | **SDD-106 §5 / §6 Rust-side variant** — the Rust-side counterpart for the permission-state-machine helpers in SDD-106 §5 / §6 has been noted in prior reviews as a follow-up; not an upstream-document edit and not changed by this addendum. | Low. | detailed-designer (future SDD revision). | +| 8 | **No dedicated SAD anchor for the platform-abstracted `MobileVoiceAudioBackend` trait** — SDD-111 self-flags (per `docs/architecture/sdd.md:1713-1715`, §11 coverage matrix) that no dedicated SAD allocation item exists for the platform-abstracted mobile-voice-audio-backend trait itself; SDD-111 is currently sourced from SAD-077 / SAD-081 pending a SAD revision that introduces a dedicated allocation item. Governance cross-reference only; not an upstream edit by this addendum. | Low (engineering chain remains intact via SAD-077 / SAD-081). | software-architect (future SAD revision to add a dedicated allocation item for `MobileVoiceAudioBackend`). | + +### F. Items intentionally NOT changed by this addendum + +| Item | Reason for non-change | +|---|---| +| `docs/architecture/sdd.md` | This is a governance-layer absorption only. SDD already at v0.9.13 — no SDD edits required. | +| `docs/architecture/sad.md`, `docs/requirements/srs.md`, `docs/requirements/sysrs.md`, `docs/architecture/sysdes.md` | The task brief explicitly forbids upstream edits. | +| `docs/verification/**` | The task brief explicitly forbids verification edits; verification baselines already contain the IDs cited in §C. | +| Source code (incl. `apps/chanora_flutter/android/app/build.gradle.kts`) | The task brief explicitly forbids code edits; the temporary `abiFilters` reduction is recorded as DEC-032 (governance-only). | +| Deployment-target single-source-of-truth (SDD-119 item 3) | Carried open; not part of this absorption pass. | +| Optional SysRS-290 anchor work | Explicitly deferred per task brief. | + +### G. Strict-hierarchy discipline check + +- No SRS / SAD / SDD direct sources were added, mutated, or relaxed. +- DEC-032 is a governance cross-reference and does not act as an + engineering-layer direct source (consistent with the treatment of + DEC-029 / DEC-030 / DEC-031 in earlier addenda). +- `crates/chanora_bridge/build.rs` is anchored to SDD-105 with a + cross-trace to SDD-118 item 6 (extended); both anchors live in + `docs/architecture/sdd.md` v0.9.13. + +### H. BLOCKED_REQUIREMENT_GAP check + +No code anchor was added without an SDD trace and an +upstream-traceable reference. `crates/chanora_bridge/build.rs` +resolves SDD-105 → SAD-063 → SRS-187 → SysDes-135 → SysRS-288 / +SysRS-055 (multi-platform). **BLOCKED_REQUIREMENT_GAP is not raised.** + +### I. Final verdict + +**TRACEABILITY_OK.** With the §C verification-row closures and the §B +code-anchor refresh, the SRS-210..SRS-215 chain is now closed end-to-end +at every layer (SysRS → SysDes → SRS → SAD → SDD → Code → Verification). +The remaining carried-open issues (§E rows 3, 4, 5, 6, 7) are +non-blocking governance / hardening follow-ups; none represents a +SysRS → SysDes → SRS → SAD → SDD → Code → Verification gap. + +| Version | Date | Description | +|---|---|---| +| 0.9.9 | 2026-05-18 | Absorbed SDD v0.9.13 documentation-cleanup amendments: SDD-105 software-unit list extended to include `crates/chanora_bridge/build.rs` (DT_NEEDED libc++_shared.so emission) + `ChanoraApplication.kt` (loader) + `MainActivity.kt` (`initChanoraContext` only); SDD-105 Cross-trace bullet to SDD-118 item 6 (extended) recorded; SDD-118 item 6 (extended) `libc++_shared.so` co-staging contract absorbed onto the `build.gradle.kts` code-anchor row; SDD-119 item 4 iOS-simulator doc-drift marked RESOLVED (Option α). Closed the 0.9.6 §C / 0.9.7 §A / 0.9.8 §D row 1 PENDING markers for SRS-210..SRS-215 with concrete SWE.4/5/6 IDs (SRS-210 → SWE4-UV-051 / SWE5-IV-021 / SWE6-SV-025; SRS-211 → SWE4-UV-048 / SWE5-IV-021 / SWE6-SV-026; SRS-212 → SWE4-UV-050 / SWE5-IV-022 / SWE6-SV-027; SRS-213 → SWE4-UV-047 + SWE4-UV-052 / SWE5-IV-021 / SWE6-SV-028; SRS-214 → SWE4-UV-049 / SWE5-IV-021 / SWE6-SV-029; SRS-215 → SWE4-UV-044 / SWE5-IV-018 / SWE6-SV-030 with SWE6-SV-018 as single-device baseline); all six SWE.4 IDs, three SWE.5 IDs, and six SWE.6 IDs confirmed present in the respective verification baselines. Added `crates/chanora_bridge/build.rs` to the code-anchor table anchored to SDD-105 with cross-trace to SDD-118 item 6 (extended). Cross-referenced DEC-032 (temporary `abiFilters` reduction to arm64-v8a only) as an Active temporary deviation from SDD-073 item 4 / SDD-118 item 3; restore-by gate = P0 release. New carried-open follow-ups: DEC-032 restore-by (§E row 5), MissedKeyUpWatchdog production design to unblock SWE4-UV-045 (§E row 6), SDD-106 §5/§6 Rust-side variant (§E row 7). TRACEABILITY_OK; BLOCKED_REQUIREMENT_GAP not raised. | diff --git a/docs/requirements/srs.md b/docs/requirements/srs.md index 7f68512..db7f5d3 100644 --- a/docs/requirements/srs.md +++ b/docs/requirements/srs.md @@ -3,7 +3,7 @@ **Product name:** Chanora **Document type:** SRS / Software Requirements Specification **ASPICE process alignment:** SWE.1 Software Requirements Analysis -**Version:** 0.9.2 +**Version:** 0.9.8 **Status:** Baseline Candidate **Source baseline:** `chanora_SysDes_ASPICE_SYS3_v0.5.md` **Architecture baseline:** Flutter + Rust Core + `tsclientlib` protocol adapter @@ -2434,14 +2434,15 @@ This section extends the ASPICE SWE.1 Software Requirements Specification. The s - Source SysDes: SysDes-134 - Verification method: Release Inspection -**SRS-187**: The software shall define Android API 24 as the minimum Android runtime baseline unless approved platform constraints raise the minimum version. +**SRS-187**: The software shall define Android API 28 (Android 9.0) as the minimum Android runtime baseline, per DEC-004 (Accepted 2026-05-14, which raised the original API 24 recommendation to API 28). The minimum may be raised further only if Flutter, plugin, audio, or platform constraints require it; it shall not be lowered without a superseding accepted decision. - Status: Baseline Candidate - Type: Software Platform Requirement - Stage: P0 / MVP - Allocated to: Android Build Configuration, Platform Adapter - Source SysDes: SysDes-135 -- Verification method: Platform Test +- Verification method: Platform Test, Release Inspection +- Change record: 0.9.6 (2026-05-17) updated minimum Android API from 24 to 28 to align with DEC-004 and the reconciled SysRS-288 wording. ID preserved. **SRS-188**: The Android release build process shall target the Android API level required by Google Play on the upload date. @@ -2623,6 +2624,30 @@ This section extends the ASPICE SWE.1 Software Requirements Specification. The s - Source SysDes: SysDes-150 - Verification method: Functional Test, UI Review +**SRS-208**: The Chanora Android client shall engage the Android in-call audio mode (for example via `AudioManager.setMode(MODE_IN_COMMUNICATION)` or an equivalent platform routing-assist mechanism) no later than the moment a voice session becomes connected, so that microphone gain, output routing, echo handling, and Bluetooth SCO behaviour follow Android's voice-communication path rather than the media path. The client shall release the in-call mode (restoring the prior audio mode) when the last active voice session ends. + +- Status: Baseline Candidate +- Type: Platform Behavioural Requirement +- Stage: P0 / MVP +- Allocated to: Audio (Android), Platform Adapter (Android) +- Source SysDes: SysDes-152 (Android in-call audio mode subsystem allocation, primary), SysDes-135 (Android platform baseline, secondary/context) +- Verification method: Android Integration Test, Audio Test +- Acceptance criteria: On Android, audio-mode transitions to in-communication on first voice-session connect and reverts on last voice-session disconnect; behaviour is observable via platform audio diagnostics and produces voice-path routing for SCO/built-in mic. +- Analysis: Feasible via the existing Platform Adapter (Android) boundary and JNI call into `AudioManager`. Verification intent (SWE.6): demonstrate audio-mode entry/exit transitions are bound to voice session lifecycle, not to UI screen lifecycle. +- Unresolved assumptions: None at SRS layer. Upstream SysDes derivation now anchored at SysDes-152 (with SysDes-135 retained as platform-baseline context). + +**SRS-209**: The Chanora Android client shall request the runtime microphone permission (`RECORD_AUDIO`) at or before voice session activation, and shall not begin microphone capture without that permission having been granted. If the permission is denied, revoked, or not yet decided, the client shall fail safe to listen-only operation (output stream remains available consistent with SRS-204) and shall surface a user-visible path to grant the permission before retrying transmit. + +- Status: Baseline Candidate +- Type: Platform Behavioural Requirement / Privacy +- Stage: P0 / MVP +- Allocated to: Platform Adapter (Android), Audio, Flutter UI +- Source SysDes: SysDes-153 (Android RECORD_AUDIO runtime permission acquisition flow, primary), SysDes-135 (Android platform baseline, secondary/context) +- Verification method: Android Integration Test, UI Test, Privacy Review +- Acceptance criteria: Permission prompt occurs no later than the user's first transmit attempt within a voice session; denial keeps the session alive in listen-only mode (`capture_active = false`, `transmit_active = false`, output stream open per SRS-204); UI exposes a non-blocking path to re-request or open system settings to grant the permission. +- Analysis: Feasible via existing platform permission adapter (SRS-108) and the audio engine's split `capture_active` / `transmit_active` states (SRS-201). Verification intent (SWE.6): demonstrate that listen-only is a first-class flow on denial and that no microphone capture is started prior to grant. +- Unresolved assumptions: None at SRS layer. Upstream SysDes derivation now anchored at SysDes-153 (with SysDes-135 retained as platform-baseline context); reuses the general permission obligation already captured in SRS-027 / SRS-108. + ## 17. Updated SRS to SysDes Coverage Statement | SysDes Range | SRS Coverage | @@ -2632,6 +2657,91 @@ This section extends the ASPICE SWE.1 Software Requirements Specification. The s | SysDes-142 through SysDes-148 | Covered by `SRS-195` through `SRS-203` | | SysDes-149 through SysDes-151 | Covered by `SRS-204` through `SRS-207` | +## 18. Android Audio Backend (P0) + +This subsection groups the Android voice-audio-backend software requirements derived to support the platform-adapter decision to host Android voice capture and playback through a dedicated low-latency native audio backend (mirroring the iOS voice-processing precedent already represented in the codebase for the iOS path). These requirements close the historical gap in which the Android voice path was carried by a generic desktop-style default backend and the hardware acoustic-echo / noise-suppression / automatic-gain effects were silently not engaged. They are sourced strictly from SysDes (per the SRS layering policy) and additionally cite the upstream SysRS items they help satisfy. + +**SRS-210**: The Chanora Android voice audio path shall achieve a round-trip mouth-to-ear ("glass-to-glass") latency of at most 150 ms on Android devices that support AAudio low-latency performance mode, and at most 250 ms on Android devices that do not support low-latency performance mode. For the purpose of this requirement, an Android device is considered to "support AAudio low-latency performance mode" if and only if (a) the client requested `AAUDIO_PERFORMANCE_MODE_LOW_LATENCY` on both the input and output streams at stream-open time, and (b) the AAudio stream returned by the platform reports `getPerformanceMode() == AAUDIO_PERFORMANCE_MODE_LOW_LATENCY` after the stream is opened. Devices that downgrade the returned performance mode to `NONE` or `POWER_SAVING` fall under the 250 ms bound. + +- Status: Baseline Candidate +- Type: Platform Behavioural Requirement / Performance +- Stage: P0 / MVP +- Allocated to: Audio (Android), Platform Adapter (Android) +- Source SysDes: SysDes-154 (Android voice audio backend subsystem — AAudio low-latency performance-mode path with platform-reported observability, primary), SysDes-135 (Android platform baseline, secondary/context) +- Verification method: Android Integration Test, Audio Test, Loopback Latency Measurement +- Acceptance criteria: On at least one reference Android device that returns `AAUDIO_PERFORMANCE_MODE_LOW_LATENCY` for the opened streams, instrumented loopback latency (mouth-to-ear, full duplex through the voice engine) does not exceed 150 ms at the 95th percentile across a 60-second measurement window; on at least one reference device that does not, the same measurement does not exceed 250 ms at the 95th percentile. +- Analysis: Feasible on API 28+ (per SRS-187 / DEC-004) using AAudio low-latency streams, which is one of the platform-recommended paths called out by SysRS-055. The latency tiering reflects the well-understood split between devices that expose the fast mixer / low-latency capture path and devices that do not. Verification intent (SWE.6): demonstrate that the chosen native-audio backend on Android opens streams in low-latency mode where the device permits it, and that the measured round-trip latency falls under the tier appropriate to the reported `getPerformanceMode()`. +- Unresolved assumptions: The exact reference-device matrix (which physical handsets define the "supports low-latency" tier vs the fallback tier) should be ratified by the verification owner. The 150 / 250 ms numeric targets are derived from SysRS-055's "production audio integration" intent and are not yet stated numerically at the SysRS layer; if SysRS owners wish to ratify a numeric mouth-to-ear bound, the values here should be considered the SRS-layer derivation and would update accordingly. + +**SRS-211**: The Chanora Android voice-capture path shall request the platform "voice communication" input preset where the operating system exposes one, so that the OS-side capture chain (gain shaping, far-end suppression, microphone selection) is the voice-optimised chain rather than the music/media chain. On Android the client shall request `AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION` as the primary preset, and shall fall back to `AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE` if the primary preset is not honoured by the device. This mirrors the precedent on iOS, where the voice-capture path is hosted on the platform voice-processing audio unit (`VoiceProcessingIO`); the Android requirement is the platform-analogous obligation, not a port of iOS code. + +- Status: Baseline Candidate +- Type: Platform Behavioural Requirement +- Stage: P0 / MVP +- Allocated to: Audio (Android), Platform Adapter (Android) +- Source SysDes: SysDes-154 (Android voice audio backend subsystem — voice-communication input preset on the capture stream, primary), SysDes-135 (Android platform baseline, secondary/context) +- Verification method: Android Integration Test, Audio Test +- Acceptance criteria: On stream open, the Android voice-capture stream is constructed with input preset = `VOICE_COMMUNICATION`; if the device's AAudio stack reports that the preset was not honoured (or stream open fails specifically due to the preset), the client retries with `VOICE_PERFORMANCE`; the active preset is observable via audio diagnostics. The output-stream usage / content-type obligations are covered separately by SRS-213. +- Analysis: Feasible on API 28+ AAudio (covered by SRS-187). The iOS analogue (voice-processing audio unit) is already realised in the codebase, so the platform-symmetric obligation is well-founded. Verification intent (SWE.6): demonstrate that the input preset is set at stream-construction time, not after stream start, and that the fallback path is taken only on documented preset-rejection conditions. +- Unresolved assumptions: None at SRS layer. + +**SRS-212**: Where the Android device exposes the hardware voice-audio effect APIs `AcousticEchoCanceler`, `NoiseSuppressor`, and `AutomaticGainControl`, the Chanora Android client shall engage each available effect on the active microphone capture session (via the platform `audioSession`-id attachment API or the AAudio effect-attachment equivalent) at or before the moment voice capture is started. Where one or more of these hardware effects is not available on the device, the client shall fall back to the equivalent software processing already provided by the cross-platform audio engine, and shall not silently leave that processing disabled. This requirement explicitly closes the prior no-op gap in which the Android path acknowledged the request to engage hardware effects but did not in fact engage them. + +- Status: Baseline Candidate +- Type: Platform Behavioural Requirement +- Stage: P0 / MVP +- Allocated to: Audio (Android), Platform Adapter (Android), Audio Engine +- Source SysDes: SysDes-154 (Android voice audio backend subsystem — engagement of platform hardware voice-audio effects with documented software fallback, primary), SysDes-135 (Android platform baseline, secondary/context) +- Verification method: Android Integration Test, Audio Test +- Acceptance criteria: For each of `AcousticEchoCanceler`, `NoiseSuppressor`, `AutomaticGainControl`: on devices where `isAvailable()` is true, the effect is constructed against the capture session id, enabled before the first capture callback delivers samples, and reports `getEnabled() == true` for the duration of the capture session; on devices where the effect is not available, the audio engine's software equivalent is engaged for the same duration. The fall-back decision is recorded in sanitised audio diagnostics. +- Analysis: Feasible via the Android `android.media.audiofx` effect APIs bound to the capture session id surfaced by the native backend. Verification intent (SWE.6): demonstrate via instrumented test that, on at least one device where hardware AEC is reported available, hardware AEC is engaged; and on at least one device where it is not, the software AEC path in the cross-platform engine is engaged. The "silent no-op" prior behaviour is explicitly excluded. +- Unresolved assumptions: None at SRS layer. SRS-208 (Android in-call audio mode) remains the routing-mode obligation; this requirement is the effects-engagement obligation and is complementary, not duplicative. + +**SRS-213**: The Chanora Android voice-output stream shall declare `AAUDIO_USAGE_VOICE_COMMUNICATION` and `AAUDIO_CONTENT_TYPE_SPEECH` at stream construction so that the Android audio policy engine routes the stream under the voice-communication routing rules that engage when `MODE_IN_COMMUNICATION` is active (per SRS-208), including correct Bluetooth SCO routing, earpiece-vs-speaker selection, and ducking behaviour against media. + +- Status: Baseline Candidate +- Type: Platform Behavioural Requirement +- Stage: P0 / MVP +- Allocated to: Audio (Android), Platform Adapter (Android) +- Source SysDes: SysDes-154 (Android voice audio backend subsystem — voice-communication usage and content-type declarations on the output stream, primary), SysDes-152 (Android in-call audio mode subsystem — output usage is the precondition for in-call-mode routing, secondary), SysDes-135 (Android platform baseline, secondary/context) +- Verification method: Android Integration Test, Audio Test +- Acceptance criteria: The opened AAudio output stream reports usage = `VOICE_COMMUNICATION` and content type = `SPEECH`; with `MODE_IN_COMMUNICATION` engaged (per SRS-208), playback routes to the in-call output device (earpiece / SCO / wired headset) and does not route to the media output device. +- Analysis: Feasible on API 28+ AAudio. The combination is the platform-documented way to opt the stream into the voice routing path and is the playback-side counterpart of SRS-211's capture-side preset selection. Verification intent (SWE.6): demonstrate routing differs from a media-usage stream under `MODE_IN_COMMUNICATION`. +- Unresolved assumptions: None at SRS layer. + +**SRS-214**: The Chanora Android voice input and output streams shall request `AAUDIO_SHARING_MODE_EXCLUSIVE` on a best-effort basis at stream construction, and shall gracefully fall back to `AAUDIO_SHARING_MODE_SHARED` when the platform denies exclusive access (for example because another application holds an exclusive stream or because shared media playback is active). The fall-back shall not be reported as a fatal error to the user and shall not prevent voice session establishment; it shall be recorded in sanitised audio diagnostics so that the achieved sharing mode is observable for SRS-210 latency analysis. + +- Status: Baseline Candidate +- Type: Platform Behavioural Requirement +- Stage: P0 / MVP +- Allocated to: Audio (Android), Platform Adapter (Android) +- Source SysDes: SysDes-154 (Android voice audio backend subsystem — best-effort exclusive sharing-mode policy with graceful shared fallback, primary), SysDes-135 (Android platform baseline, secondary/context) +- Verification method: Android Integration Test, Audio Test +- Acceptance criteria: On stream open the client requests `EXCLUSIVE`; on devices that grant it, `getSharingMode()` reports `EXCLUSIVE`; on devices or device states that deny it, the client opens with `SHARED` instead and the voice session proceeds; the achieved sharing mode appears in sanitised audio diagnostics for both the input and the output stream. +- Analysis: Feasible on API 28+ AAudio. Exclusive mode is the documented path to the lowest-latency tier on devices that expose a fast capture/playback path; making the request best-effort avoids regressing the user's ability to join a voice session when another exclusive stream is active. Verification intent (SWE.6): demonstrate both the granted and denied paths and that neither blocks voice session establishment. +- Unresolved assumptions: None at SRS layer. + +**SRS-215**: The Chanora Android client shall maintain active microphone capture while the application is backgrounded by hosting voice transmission inside an Android foreground service whose manifest declares `foregroundServiceType="microphone"`. This formalises at the SRS layer the platform-required hosting model under which background microphone capture is permitted on Android 9+ (API 28+) and is the software-requirement counterpart of the existing foreground-service obligation captured in SRS-111. + +- Status: Baseline Candidate +- Type: Platform Behavioural Requirement +- Stage: P0 / MVP +- Allocated to: Platform Adapter (Android), Audio (Android), Android Manifest +- Source SysDes: SysDes-154 (Android voice audio backend subsystem — lifecycle binding to the Android microphone-typed foreground service for background voice capture, primary), SysDes-135 (Android platform baseline, secondary/context) +- Verification method: Android Integration Test, Manifest Review +- Acceptance criteria: With the application backgrounded and a voice session active in transmit-capable state, microphone capture continues to deliver frames to the audio engine; the hosting Android service is observable via `dumpsys activity services` (or equivalent platform diagnostics) as a foreground service of type `microphone`; the manifest declares `foregroundServiceType="microphone"` on the hosting service component. +- Analysis: Feasible on API 28+ per SRS-187 / DEC-004; the foreground-service-type attribute is the platform's documented mechanism for declaring background microphone use. Verification intent (SWE.6): demonstrate that the foreground service is started before background microphone capture begins, and that capture stops if the foreground service is not granted. +- Unresolved assumptions: None at SRS layer. SRS-111 remains the higher-level foreground-service obligation; this requirement narrows it to the microphone-typed variant required by the voice transmission path. + +## 19. Updated SRS to SysDes Coverage Statement (Android Audio Backend) + +| SysDes Range | SRS Coverage | +|---|---| +| SysDes-152 (Android in-call audio mode subsystem) | Primary anchor for `SRS-208`; additionally cited as a secondary anchor by `SRS-213` (output-stream usage/content-type is the precondition for in-call-mode routing). | +| SysDes-153 (Android RECORD_AUDIO runtime permission acquisition flow) | Primary anchor for `SRS-209`. | +| SysDes-154 (Android voice audio backend subsystem) | Primary anchor for `SRS-210` (AAudio low-latency performance-mode latency tiering), `SRS-211` (voice-communication input preset with `VOICE_PERFORMANCE` fallback), `SRS-212` (hardware AEC/NS/AGC engagement with software fallback), `SRS-213` (voice-communication output usage/content-type), `SRS-214` (best-effort exclusive sharing mode with shared fallback), and `SRS-215` (microphone-typed foreground-service-hosted background capture). | +| SysDes-135 (Android platform baseline) | Retained as the secondary/context anchor on `SRS-208` through `SRS-215` (the eight Android voice-audio SRS items conceptually rest on the Android platform baseline as their platform context, but no longer take SysDes-135 as the primary derivation). Upstream SysRS items addressed across the eight items are SysRS-055, SysRS-217, SysRS-288, SysRS-305, and SysRS-306. | + ## Baseline Candidate 0.9.1 Update @@ -2659,3 +2769,12 @@ This section extends the ASPICE SWE.1 Software Requirements Specification. The s | Version | Date | Description | |---|---|---| | 0.9.5 | 2026-05-15 | Added v1 audio + PTT lifecycle software requirements SRS-204 through SRS-207 sourced from SysDes-149..151: bridge surface drops `start_audio` / `stop_audio` and adds `voice_join(channel_id)` / `voice_leave()`, audio engine opens streams on first voice-channel join and closes on last leave with output independent of mic-permission state, `TransmitMode` enum (`Ptt` default, `Continuous`, reserved `VoiceActivity` per DEC-030) persisted per identity, `release_tail_ms` (default 200, range 0–500) gate on the `true → false` transition of `transmit_active` with re-press cancellation, Voice Bar hard-mute override of `transmit_active`. Strict layered sourcing preserved (`SRS -> SysDes` only). | + + +## Baseline Candidate 0.9.6 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.8 | 2026-05-18 | Retargeted the `Source SysDes` field on SRS-208, SRS-209, and SRS-210 through SRS-215 from the generic SysDes-135 (Android platform baseline) to the dedicated Android voice-audio derivation items authored by the system-architecture owner: SysDes-152 (Android in-call audio mode subsystem) is now the primary anchor for SRS-208; SysDes-153 (Android RECORD_AUDIO runtime permission acquisition flow as Permission/Voice subsystem partitioning) is now the primary anchor for SRS-209; SysDes-154 (Android voice audio backend subsystem — AAudio low-latency path, voice-communication usage/preset, hardware-effects engagement with software fallback, sharing-mode policy, FGS-hosted background mic lifecycle) is now the primary anchor for SRS-210 (latency), SRS-211 (input preset), SRS-212 (hardware AEC/NS/AGC), SRS-213 (output usage/content-type), SRS-214 (sharing mode), and SRS-215 (microphone-typed foreground service). SRS-213 additionally retains SysDes-152 as a secondary anchor because the output-stream usage is the precondition for in-call-mode routing. SysDes-135 is retained on all eight items as the secondary/context anchor (platform-baseline relationship). Section 19 coverage table updated. Strict layered sourcing preserved (`SRS -> SysDes` only). No technical or behavioural content of SRS-208 through SRS-215 was modified; this is a surgical retargeting of the `Source SysDes` field only. | +| 0.9.7 | 2026-05-18 | Added Android Audio Backend (P0) section 18 hosting SRS-210 through SRS-215 to support the platform-adapter decision to host Android voice capture/playback on a dedicated low-latency native audio backend (mirroring the iOS voice-processing-audio-unit precedent). SRS-210 sets numeric mouth-to-ear latency targets (≤150 ms on devices that return `AAUDIO_PERFORMANCE_MODE_LOW_LATENCY`, ≤250 ms otherwise). SRS-211 mandates `AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION` with `VOICE_PERFORMANCE` fallback. SRS-212 mandates engagement of hardware `AcousticEchoCanceler` / `NoiseSuppressor` / `AutomaticGainControl` on the capture session id with software-AEC fallback (closes the prior silent no-op). SRS-213 mandates `AAUDIO_USAGE_VOICE_COMMUNICATION` + `AAUDIO_CONTENT_TYPE_SPEECH` on output. SRS-214 mandates best-effort `AAUDIO_SHARING_MODE_EXCLUSIVE` with `SHARED` fallback. SRS-215 mandates microphone-typed foreground-service hosting (`foregroundServiceType="microphone"`) for backgrounded voice transmission. Added section 19 coverage statement. Strict SRS→SysDes sourcing preserved: `Source SysDes` traces via SysDes-135 (Android platform baseline) pending a SysDes revision that introduces a dedicated derivation item for the Android voice audio backend; SysRS-055, SysRS-217, SysRS-288, SysRS-305 are cited as the upstream SysRS obligations addressed. | +| 0.9.6 | 2026-05-17 | Propagated SysRS reconciliation (Wave 1.5) into SRS for P0 Android. SRS-187 minimum Android API raised from 24 to 28 per DEC-004 (ID preserved, change record added). SRS-188 verified consistent with SysRS-289 (Google Play upload-date target SDK); no rewrite required. SRS-111 (Android foreground service for active voice session), SRS-119 (Android AAB release build), and SRS-163 (Android back-navigation intent) reviewed and left unchanged — text remains unambiguous and P0/P1 scope is intact. Added SRS-208 (Android in-call audio mode engaged on voice-session connect, released on last disconnect — addresses SysRS-305) and SRS-209 (explicit `RECORD_AUDIO` runtime permission acquisition at or before voice-session activation, fail-safe to listen-only on denial — addresses SysRS-306). Strict layered sourcing preserved (`SRS -> SysDes` only); `Source SysDes` for SRS-208/209 traces via SysDes-135 pending a SysDes revision that introduces dedicated derivation items for SysRS-305/306. | diff --git a/docs/requirements/sysrs.md b/docs/requirements/sysrs.md index 3a50cbb..8ff2600 100644 --- a/docs/requirements/sysrs.md +++ b/docs/requirements/sysrs.md @@ -1849,7 +1849,7 @@ This section converts the baseline product decisions into auditable system-level - Priority: P0 - Verification: Release Inspection -**SysRS-288**: The Chanora Android application shall support Android API 24 or later as the minimum runtime baseline unless Flutter, plugin, audio, or platform constraints require raising the minimum version. +**SysRS-288**: The Chanora Android application shall support Android API 28 (Android 9.0) or later as the minimum runtime baseline, per DEC-004 (Accepted 2026-05-14, which raised the original API 24 recommendation to API 28). The minimum may be raised further only if Flutter, plugin, audio, or platform constraints require it; it shall not be lowered without a superseding accepted decision. - Priority: P0 - Verification: Review, Platform Test @@ -1934,6 +1934,16 @@ This section converts the baseline product decisions into auditable system-level - Priority: P0 - Verification: Functional Test, UX Review +**SysRS-305**: When the Chanora Android application has an active voice session connected, the system shall engage the Android in-call audio mode (for example via `AudioManager.setMode(MODE_IN_COMMUNICATION)` or an equivalent platform routing-assist mechanism) so that microphone gain, output routing, echo handling, and Bluetooth SCO behaviour follow Android's voice-communication path rather than the media path. The in-call mode shall be entered no later than the moment the voice session becomes connected and shall be released when the last active voice session ends. Back-fills the v0.9.8 product-decision-register entry that recorded `AudioManager.setMode(MODE_IN_COMMUNICATION)` engagement via JNI without prior SysRS coverage. + +- Priority: P0 +- Verification: Platform Test (Android), Audio Test + +**SysRS-306**: The Chanora Android application shall acquire the Android runtime microphone permission (`RECORD_AUDIO`) at or before the point of voice session activation, and shall not begin microphone capture without that permission having been granted by the user. If the permission is denied, revoked, or not yet decided, the application shall remain functional in listen-only mode (consistent with SysRS-303) and shall surface a user-facing path to grant the permission before retrying transmit. This requirement is additive to the general permission obligation in SysRS-055 and makes the runtime-acquisition timing explicit. + +- Priority: P0 +- Verification: Platform Test (Android), Functional Test + ## 32. Change History Addendum @@ -1960,3 +1970,10 @@ This section converts the baseline product decisions into auditable system-level | Version | Date | Description | |---|---|---| | 0.9.5 | 2026-05-15 | Added v1 audio + PTT lifecycle requirements SysRS-303 and SysRS-304 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 listen-only flow (output independent of mic permission), the hard-mute override, and the 200 ms (0–500 ms) PTT release tail for word-boundary anti-clipping. | + + +## Baseline Candidate 0.9.9 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.9 | 2026-05-17 | Reconciled SysRS-288 with DEC-004 (Accepted 2026-05-14): Android minimum runtime baseline raised from API 24 to API 28 (Android 9.0); rationale and decision citation added in-line. Verified SysRS-055, SysRS-161, SysRS-195, SysRS-217, and SysRS-289 remain consistent with DEC-004 (no API-level text in any of these; no rewrite required). Added SysRS-305 (Android in-call audio mode engagement during active voice session — back-fills the v0.9.8 product-decision-register `AudioManager.setMode(MODE_IN_COMMUNICATION)` entry) and SysRS-306 (explicit Android runtime microphone permission acquisition at or before voice session activation, additive to SysRS-055). | diff --git a/docs/verification/android-p0-acceptance.md b/docs/verification/android-p0-acceptance.md new file mode 100644 index 0000000..ffd1d5a --- /dev/null +++ b/docs/verification/android-p0-acceptance.md @@ -0,0 +1,319 @@ +# Android P0 acceptance — human-must test cases (v1.0.0-rc.8) + +This document captures the human-side verification pass for the +Android P0 ship of Chanora. The officially-tested environment is +**Android 9 (API 28) or later on a physical Android device, installed +via either Play Internal Track or `bundletool install-apks`** (DEC-004). +Emulator support is best-effort — microphone capture and audio routing +in the AVD do not represent real-device behaviour. + +Automated tests (`cargo check`, `cargo test --workspace --lib`, +`flutter test`, `flutter build appbundle --release`) sign off the +mechanical side. This checklist signs off the user-experience side. +A failure on any row blocks promotion of the Android build from rc.8. + +Source: this checklist mirrors the iOS, iPadOS, macOS, Linux, and +Windows acceptance documents. SRS references: SRS-013 (audio), +SRS-111 (foreground service P0), SRS-119 (AAB packaging), +SRS-163 (Android back navigation), SRS-187 (min API 28), +SRS-188 (Play target API), SRS-208 (in-call audio mode), +SRS-209 (RECORD_AUDIO + listen-only fallback). SAD references: +SAD-018, SAD-037, SAD-063, SAD-077, SAD-081, SAD-084, SAD-085, +SAD-086. SDD references: SDD-028, SDD-073, SDD-105, SDD-106, +SDD-107, SDD-108, SDD-109, SDD-110. Decision: DEC-004 +(Android min API 28). System requirements: SysRS-288, SysRS-305, +SysRS-306. + +Android does NOT have a global PTT capability comparable to Windows / +macOS / Linux desktops. Per SDD-110 the desktop PTT backend ladder +collapses to `FocusedPttBackend` on Android and the capability badge +always advertises `L0Focused`. PTT works only while Chanora itself is +the foreground app (and while the voice foreground service per +SDD-107 keeps the mic stream alive when the screen is off). + + +## 0. Pre-flight + +| # | Step | Expected | +|---|---|---| +| 0.1 | On a Linux/macOS host install Android SDK + NDK + cmdline-tools. Accept SDK licences. | `adb`, `bundletool`, and Gradle wrapper are runnable. | +| 0.2 | Connect a physical Android device (API 28+) via USB. Enable Developer Options → USB debugging. | `adb devices` lists the device as `device` (not `unauthorized`). | +| 0.3 | Build the bridge for Android: `rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android && cd ~/chanora && cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -t x86 -p 28 -- build --release -p chanora_bridge`. | `target//release/libchanora_bridge.so` exists for each ABI. (SDD-073 NDK ABI set, SDD-105 JNI load.) | +| 0.4 | Build the AAB: `cd apps/chanora_flutter && flutter build appbundle --release`. | `build/app/outputs/bundle/release/app-release.aab` exists. (SRS-119, SDD-109) | +| 0.5 | Validate the AAB with bundletool: `bundletool build-apks --bundle=app-release.aab --output=chanora.apks --connected-device`. | bundletool reports `Universal APK`/`split APKs` per ABI and no validation errors. (SDD-109) | +| 0.6 | Install on the connected device: `bundletool install-apks --apks=chanora.apks`. (Or Play Console → Internal Testing → upload + opt-in.) | App installs without signature errors. (SDD-073 release-signing CI assertion.) | +| 0.7 | Launch the app. First-launch microphone prompt appears. | Prompt rationale is localized. (SRS-209, SDD-106) | +| 0.8 | About / version label inside Chanora | Shows **`v1.0.0-rc.8`**. | + + +## Device matrix expectations + +Per SRS-187 / SRS-188, DEC-004, AND SDD-116 (Android voice audio backend +device matrix) the human pass shall be executed on the following devices: + +| Tier | Android version | Reason | Acceptance subset | +|---|---|---|---| +| Floor | Android 9 (API 28) | Minimum supported per DEC-004 / SRS-187 / SysRS-288; SDD-116 row A | TC-1..TC-11 (all), TC-14 | +| Mid-tier | Android 12 (API 31) | Covers FGS foreground-type tightening (API 31); SDD-116 row B | TC-1..TC-11 (all), TC-12 predictive-back (if API ≥ 33), TC-15 | +| Target | Android 14 (API 34) per SRS-188 | Play upload + runtime target; SDD-116 row C; FGS-type=microphone strict on API 34 | TC-1..TC-11 (all), TC-13 Play Internal install path, TC-16 | +| OEM: Pixel | Any API ≥ 28 | SDD-116 OEM coverage — Pixel | TC-17 latency rig | +| OEM: Samsung | Any API ≥ 28 | SDD-116 OEM coverage — Samsung | TC-18 latency rig | + +The Pixel and Samsung OEM rows MAY overlap with the API tier rows (e.g. +the API 34 device may be a Pixel). The minimum aggregate is four +physical devices when overlap is used, five otherwise. + + +## TC-1: Install + identity persistence (DEC-006, SRS-201) + +| # | Step | Expected | +|---|---|---| +| 1.1 | Enter a reachable TS3 server address, a nickname, blank password | Connect button enabled | +| 1.2 | Tap Connect | Server snapshot appears | +| 1.3 | Background + relaunch with the same nickname | Server-visible UID is the same (identity persisted in app-private files dir) | + +Traces: SRS-013, SRS-209. SWE.6: SWE6-SV-018. SWE.5: SWE5-IV-016. + + +## TC-2: Microphone permission state machine (SRS-209, SDD-106) + +| # | Step | Expected | +|---|---|---| +| 2.1 | First run: tap PTT → system prompt appears → Allow. | Mic transmits. Capability badge says `L0Focused`. | +| 2.2 | Revoke RECORD_AUDIO via Settings → Apps → Chanora → Permissions while app is running. Return to Chanora and attempt to transmit. | App degrades to listen-only stream (per SRS-209 fallback). No crash. User-visible banner explains state. | +| 2.3 | Re-grant RECORD_AUDIO. | Transmit re-enables without restart (or via clearly-labelled "Retry" button). | +| 2.4 | Fresh install: deny the prompt twice (Android treats this as "permanently denied"). Attempt PTT. | App stays in listen-only; in-app banner deep-links to system settings. | + +Traces: SRS-209, SysRS-306, SAD-085, SDD-106. SWE.4: SWE4-UV-041. SWE.5: SWE5-IV-017. SWE.6: SWE6-SV-019. + + +## TC-3: Voice channel join + foreground service longevity (SRS-111, SDD-107) + +| # | Step | Expected | +|---|---|---| +| 3.1 | Join a voice channel. | Foreground service notification appears with `foregroundServiceType="microphone"` (API 30+). Per SDD-107 the notification is non-dismissible while connected. | +| 3.2 | Lock the screen. Leave the device idle for **≥ 10 minutes**. | Voice continues to be sent/received. No `ANR`, no service kill, no `RemoteServiceException`. | +| 3.3 | Open Settings → Battery while still connected. | Chanora is not listed as "restricted". (P0: warn user if it is.) | +| 3.4 | Disconnect voice channel. | Foreground service stops, notification disappears, AudioManager mode is restored (TC-6 verifies). | + +Traces: SRS-111, SAD-081, SDD-107. SWE.4: SWE4-UV-043. SWE.5: SWE5-IV-018, SWE5-IV-019. SWE.6: SWE6-SV-018. + + +## TC-4: PTT press / release (Focused only — Android limitation, SDD-110) + +| # | Step | Expected | +|---|---|---| +| 4.1 | Open Voice settings → Push to talk | Capability badge clearly states **L0 Focused** with explanatory text noting Android does not expose global hotkeys to background apps. | +| 4.2 | Touch and hold the on-screen PTT button | Mic indicator goes active while held; releases on touch-up. | +| 4.3 | Background the app while holding PTT (swipe home). | Per SDD-110 PTT does NOT continue while backgrounded; transition is observable in the UI on return. | + +Traces: SRS-195, SRS-196, SAD-086, SDD-110. SWE.4: SWE4-UV-044. SWE.6: SWE6-SV-024. + + +## TC-5: Release tail + +| # | Step | Expected | +|---|---|---| +| 5.1 | Set release-tail to 0 ms; tap-release | Sharp on/off. | +| 5.2 | Set release-tail to 500 ms; tap-release | Mic stays on ~500 ms after release. | + + +## TC-6: AudioManager in-call mode round-trip (SRS-208, SDD-108) + +| # | Step | Expected | +|---|---|---| +| 6.1 | Before connecting, capture `AudioManager.getMode()` via diagnostic export (or `adb shell dumpsys audio`). Note the value (typically `MODE_NORMAL`). | Baseline recorded. | +| 6.2 | Connect + join voice channel. | Per SRS-208 / SDD-108 the controller enters in-call mode (`MODE_IN_COMMUNICATION` or platform equivalent). | +| 6.3 | Disconnect. | Per SDD-108 prior-mode snapshot is restored. Re-read AudioManager mode == baseline from 6.1. | +| 6.4 | Bluetooth SCO route check (P1 awareness, P0 minimum: no crash): pair a Bluetooth headset, connect Chanora voice. | P0: no crash, no permanent route lock. P1 follow-up: SCO routes correctly. | + +Traces: SRS-208, SysRS-305, SAD-084, SDD-108. SWE.4: SWE4-UV-045. SWE.5: SWE5-IV-019. SWE.6: SWE6-SV-020. SYS.4: SYS4-SIV-017. + + +## TC-7: Mic / speaker mute + +| # | Step | Expected | +|---|---|---| +| 7.1 | Mute mic via VoiceBar | Mic icon shows muted state; PTT does not unmute. | +| 7.2 | Speaker mute | Other clients silenced locally. | + + +## TC-8: Reconnect + +| # | Step | Expected | +|---|---|---| +| 8.1 | Toggle airplane mode for 10 s then off | Connection-lost banner, then recovers. Foreground service does not crash during the transition. | + + +## TC-9: Diagnostic export + privacy (DEC-027) + +| # | Step | Expected | +|---|---|---| +| 9.1 | Export a diagnostic bundle (Settings → Diagnostics → Export). | Bundle is saved via the system Storage Access Framework picker. Allow-listed fields only; no raw key labels, no audio bytes, no `RECORD_AUDIO` permission token. | + +Traces: SRS-117/SRS-118 family. SWE.6: SWE6-SV-006. + + +## TC-10: Capability badge labels (SDD-110) + +| # | Step | Expected | +|---|---|---| +| 10.1 | Capability badge | Shows `L0Focused` with text explaining Android does not expose session-level global hotkeys. | + + +## TC-11: Android back navigation (SRS-163, SDD-028) + +| # | Step | Expected | +|---|---|---| +| 11.1 | Open a modal sheet (e.g. Settings → Push-to-Talk binding sheet) and press the system back gesture / button. | Modal closes; route stack unchanged. (SDD-028 modal-first rule.) | +| 11.2 | From a leaf screen press back. | Pops one route. Does not exit the app unless on the home route. | +| 11.3 | While PTT is actively held, press back. | Back is **ignored** until PTT is released. (SDD-028 PTT-active-ignore rule.) | +| 11.4 | From the home route press back. | App backgrounds via standard activity finish (does not crash). | + +Traces: SRS-163, SAD-018, SDD-028. SWE.4: SWE4-UV-040, SWE4-UV-042. SWE.5: SWE5-IV-020. SWE.6: SWE6-SV-021. + + +## TC-12: Predictive back (Android 13+ API 33 branch, SDD-028) + +| # | Step | Expected | +|---|---|---| +| 12.1 | On API 33+ enable "Predictive back animations" in Developer Options. Trigger a back gesture from a non-home route. | Predictive back preview shows; release commits the pop. SDD-028 API 33+ branch (`OnBackInvokedCallback`) is exercised. | +| 12.2 | On API ≤ 32 the same scenario uses the legacy `OnBackPressedDispatcher` branch (SDD-028 pre-33 branch). | Same end behaviour; no predictive preview. | + +Traces: SRS-163, SDD-028. SWE.4: SWE4-UV-042. SWE.6: SWE6-SV-021. + + +## TC-13: Play Internal Track / bundletool install path (SRS-119, SRS-188, SDD-109) + +| # | Step | Expected | +|---|---|---| +| 13.1 | Upload `app-release.aab` to Play Console → Internal Testing track (or via `bundletool` for the same effect). | Play accepts the bundle: targetSdk matches SRS-188, minSdk == 28 per DEC-004, signing key is the release CI key per SDD-073. | +| 13.2 | Opt-in tester installs via Play store internal link. | App installs and runs identically to the bundletool path (TC-0.6). | +| 13.3 | Verify the AAB contains per-ABI split APKs (`armeabi-v7a`, `arm64-v8a`, `x86_64`). | bundletool report lists all expected ABIs (SDD-109 ABI splits). | + +Traces: SRS-119, SRS-188, SAD-063, SDD-073, SDD-109. SWE.5: SWE5-IV-020. SWE.6: SWE6-SV-022, SWE6-SV-023. SYS.4: SYS4-SIV-018. + + +## TC-14: Per-device evidence collection on API 28 device (SDD-116 matrix row A) + +| # | Step | Expected | +|---|---|---| +| 14.1 | On the API 28 device, complete pre-flight then `voice_join` a test channel. | Channel join succeeds; FGS active per TC-3. | +| 14.2 | Export the backend diagnostics bundle (Settings → Diagnostics → Export, or the equivalent in-app action). | Export contains `achieved_performance_mode`, `achieved_sharing_mode`, `achieved_input_preset`, `achieved_output_usage`, `achieved_output_content_type`, and per-effect `{aec, ns, agc}` engagement (`hw` / `sw` / `unavailable`). | +| 14.3 | Record all six achieved values + per-effect engagement into the SDD-116 matrix table for the API 28 row. | All fields populated; no `unknown`. | +| 14.4 | `voice_leave`. | Backend tears down cleanly per SWE5-IV-023 reverse order. | + +Traces: SRS-210, SRS-211, SRS-212, SRS-213, SRS-214, SRS-215, SDD-111, SDD-112, SDD-113, SDD-116. SWE.6: SWE6-SV-026, SWE6-SV-027, SWE6-SV-028, SWE6-SV-029, SWE6-SV-030. SWE.5: SWE5-IV-021. + + +## TC-15: Per-device evidence collection on API 31 device (SDD-116 matrix row B) + +| # | Step | Expected | +|---|---|---| +| 15.1 | On the API 31 device, complete pre-flight then `voice_join`. | Channel join succeeds. | +| 15.2 | Export diagnostics bundle. | Same fields as TC-14.2 populated. | +| 15.3 | Record values into SDD-116 matrix table for the API 31 row. | All fields populated. | +| 15.4 | Verify FGS-foreground-type tightening branch is exercised: `dumpsys activity services ` shows the voice service running. | Service entry present; type recorded. | +| 15.5 | `voice_leave`. | Clean teardown. | + +Traces: SRS-210..SRS-215, SDD-111..SDD-116. SWE.6: SWE6-SV-026..030. SWE.5: SWE5-IV-021, SWE5-IV-023. + + +## TC-16: Per-device evidence collection on API 34 device (SDD-116 matrix row C) + +| # | Step | Expected | +|---|---|---| +| 16.1 | On the API 34 device, complete pre-flight then `voice_join`. | Channel join succeeds. | +| 16.2 | Export diagnostics bundle. | Same fields as TC-14.2 populated. | +| 16.3 | Record values into SDD-116 matrix table for the API 34 row. | All fields populated. | +| 16.4 | Verify `dumpsys activity services ` shows `foregroundServiceType=microphone` for the voice service (API 34 type tightening). | `microphone` type present. | +| 16.5 | Screen-off / locked for ≥ 10 minutes during a live voice session. | Mic frames continue (per-minute frame-count probe in diagnostics is non-zero each minute). No `MissingForegroundServiceTypeException` in logcat. | +| 16.6 | `voice_leave`. | Clean teardown. | + +Traces: SRS-210..SRS-215, SDD-107, SDD-111..SDD-116. SWE.6: SWE6-SV-026..030. SWE.5: SWE5-IV-021, SWE5-IV-023, SWE5-IV-026. + + +## TC-17: Glass-to-glass latency measurement on Pixel device (SDD-116 OEM coverage) + +| # | Step | Expected | +|---|---|---| +| 17.1 | Set up the latency rig per `swe6-software-verification-plan.md` "Glass-to-glass latency measurement procedure" (DUT = Pixel device, reference listener on LAN, stereo capture on audio interface). | Rig produces clean impulse / listener channels. | +| 17.2 | Record `achieved_performance_mode` for the Pixel DUT. | Recorded (typically `LowLatency` on Pixel). | +| 17.3 | Run 20 impulses; compute median and p95 latency. | Both recorded. | +| 17.4 | Compare against SRS-210 tier per SWE6-SV-025 pass criteria. | If `LowLatency`: median ≤ 150 ms, p95 ≤ 180 ms. Otherwise: median ≤ 250 ms, p95 ≤ 300 ms. | +| 17.5 | Archive raw stereo WAVs + measurement script + per-impulse CSV. | Artifacts attached to test report. | + +Traces: SRS-210, SDD-111, SDD-112, SDD-116. SWE.6: SWE6-SV-025. SWE.4: SWE4-UV-051 (tier classifier). + + +## TC-18: Glass-to-glass latency measurement on Samsung device (SDD-116 OEM coverage) + +| # | Step | Expected | +|---|---|---| +| 18.1 | Re-run the TC-17 rig with the DUT swapped to a Samsung device. | Rig setup unchanged. | +| 18.2 | Record `achieved_performance_mode` for the Samsung DUT. | Recorded. | +| 18.3 | Run 20 impulses; compute median and p95 latency. | Both recorded. | +| 18.4 | Compare against SRS-210 tier per SWE6-SV-025 pass criteria. | Same thresholds as TC-17.4 against the Samsung-achieved tier. | +| 18.5 | Archive evidence as TC-17.5. | Artifacts attached. | + +Traces: SRS-210, SDD-111, SDD-112, SDD-116. SWE.6: SWE6-SV-025. SWE.4: SWE4-UV-051. + + +## Auto-test sign-off + +These rows do not require human interaction. + +| Layer | Result | +|---|---| +| `cargo check --workspace` | clean | +| `cargo test --workspace --lib` | unchanged (Android-specific Rust code is `#[cfg(target_os = "android")]`-gated) | +| `cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -p 28 -- build --release -p chanora_bridge` | clean per ABI | +| `flutter test` in `apps/chanora_flutter` | green; widget + e2e suites carry SRS trace headers | +| `flutter build appbundle --release` | clean — produces `app-release.aab` | +| `bundletool validate --bundle=app-release.aab` | no errors | +| Gradle `assertMinSdk28` task (per SDD-073) | passes; minSdk in merged manifest == 28 | +| R8 / ProGuard release pass (per SDD-073) | clean; mapping file archived | +| CI release-signing key custody check (per SDD-073) | signing key reference matches expected fingerprint | + + +## Sign-off form + +``` +chanora v1.0.0-rc.8 — Android P0 acceptance +Device 1 (API 28): ______________________ (model + Android version) +Device 2 (mid): ______________________ (model + Android version) +Device 3 (target): ______________________ (model + Android version) +Tester: ______________________ +Date: ______________________ + +TC-1 Install + identity persistence [ pass / fail ] notes: +TC-2 RECORD_AUDIO state machine [ pass / fail ] notes: +TC-3 Voice channel + FGS longevity [ pass / fail ] notes: +TC-4 PTT press / release (Focused) [ pass / fail ] notes: +TC-5 Release tail [ pass / fail ] notes: +TC-6 AudioManager mode round-trip [ pass / fail ] notes: +TC-7 Mic / speaker mute [ pass / fail ] notes: +TC-8 Reconnect [ pass / fail ] notes: +TC-9 Diagnostic export + privacy [ pass / fail ] notes: +TC-10 Capability badge label [ pass / fail ] notes: +TC-11 Back navigation [ pass / fail ] notes: +TC-12 Predictive back (API 33+) [ pass / fail ] notes: +TC-13 Play Internal / bundletool install [ pass / fail ] notes: +TC-14 Per-device evidence API 28 (SDD-116 A)[ pass / fail ] notes: +TC-15 Per-device evidence API 31 (SDD-116 B)[ pass / fail ] notes: +TC-16 Per-device evidence API 34 (SDD-116 C)[ pass / fail ] notes: +TC-17 Latency rig — Pixel [ pass / fail ] notes: +TC-18 Latency rig — Samsung [ pass / fail ] notes: + +Overall result: [ Android rc.8 PASS / Android rc.8 FAIL ] +Sign: ______________________ +``` + + +## Known gaps (not blockers) + +- **Bluetooth SCO routing**: P0 only requires no-crash behaviour. Full SCO route correctness is P1. +- **Background PTT**: not possible on Android per platform model; `FocusedPttBackend` only (SDD-110). +- **VAD** (`TransmitMode::VoiceActivity`) intentionally disabled in P0 (DEC-030). +- **Tablet / foldable layouts**: this checklist targets phone form factor. The wide-mode (≥ 840 dp) UI works but is not signed off here. +- **Play Console upload key custody**: depends on whoever holds the release keystore; CI signing assertion (SDD-073) covers the mechanical side only. diff --git a/docs/verification/swe4-unit-verification-plan.md b/docs/verification/swe4-unit-verification-plan.md index bffa495..02819f1 100644 --- a/docs/verification/swe4-unit-verification-plan.md +++ b/docs/verification/swe4-unit-verification-plan.md @@ -3,7 +3,7 @@ **Document type:** Software Unit Verification Plan **Process alignment:** ASPICE SWE.4 Software Unit Verification -**Version:** 0.9.2 +**Version:** 0.9.14 **Status:** Baseline Candidate **Language:** English **Product:** Chanora @@ -416,3 +416,212 @@ Unit verification results shall be summarized in a unit verification summary rep | Version | Date | Description | |---|---|---| | 0.9.3 | 2026-05-15 | Added desktop PTT unit-verification items SWE4-UV-035 through SWE4-UV-039: `DesktopPttBackend` trait + `PttCapabilityLevel`, platform backends, `PttController` + `AudioTransmitGate`, `PttSanitizer` + `PttCapabilityBadge`, `MissedKeyUpWatchdog`. | + + +## 11. Android P0 Unit Verification Addendum + +**SWE4-UV-040**: Verify Android build configuration unit (`SDD-073` expanded). + +- Source SDD: SDD-073 +- Verification method: Gradle task assertion + static inspection of `android/app/build.gradle.kts` +- Pass criteria: `minSdk` literal equals `28` (DEC-004); `ndk.abiFilters` set equals `{arm64-v8a, armeabi-v7a, x86_64}` (per SDD-073 item 4 and SDD-118 item 3, `x86` 32-bit is explicitly out of scope and shall not be in the set); release `signingConfig` references the CI signing key reference (not debug); R8/ProGuard `minifyEnabled = true` on release; `bundle { abi.enableSplit = true }`; assemble target produces an AAB artifact at `app/build/outputs/bundle/release/app-release.aab`. +- Evidence: Gradle log + manifest dump + +**SWE4-UV-041**: Verify `AndroidPermissionRequester` state machine (`SDD-106`). + +- Source SDD: SDD-106 +- Source SRS: SRS-209 +- Verification method: Dart/Kotlin unit test with a permission-platform test double +- Pass criteria: State transitions are exhaustive for `{granted, denied, permanently_denied, revoked_mid_session}`; transitioning out of `granted` clamps `AudioTransmitGate::set(false)`; re-entering `granted` does NOT auto-transmit (requires explicit user gesture); permanently-denied surfaces a settings-deep-link intent rather than re-prompting. +- Evidence: Test result + +**SWE4-UV-042**: Verify `BackIntentService` Android branches (`SDD-028` expanded). + +- Source SDD: SDD-028 +- Source SRS: SRS-163 +- Verification method: Dart unit test with platform-channel test double for both `OnBackInvokedCallback` (API 33+) and `OnBackPressedDispatcher` (API ≤ 32) paths. +- Pass criteria: Modal-first rule — if a modal is open, back closes the modal and does not pop the route. PTT-active-ignore — while `transmit_active == true` the back event is consumed without effect. Route-pop ordering — otherwise the topmost non-modal route pops. The API 33+ branch registers a `OnBackInvokedCallback` at `PRIORITY_DEFAULT`; the pre-33 branch registers an `OnBackPressedCallback`. Switching API level in the test double switches the active branch deterministically. +- Evidence: Test result + +**SWE4-UV-043**: Verify `AndroidJniBootstrap` library load and init ordering (`SDD-105`). + +- Source SDD: SDD-105 +- Verification method: Instrumented unit test on Android (or Robolectric where applicable) +- Pass criteria: `System.loadLibrary("chanora_bridge")` is invoked from the `Application` subclass `onCreate` (not from an Activity); `JNI_OnLoad` runs exactly once per process and captures the `JavaVM*`; bridge init occurs before any FFI call from Dart; double-init is a no-op. +- Evidence: Test result + logcat trace + +**SWE4-UV-044**: Verify `AndroidVoiceForegroundService` lifecycle unit (`SDD-107`). + +- Source SDD: SDD-107 +- Source SRS: SRS-111 +- Verification method: Kotlin unit test + Robolectric `ServiceController` +- Pass criteria: `startForeground` is called within 5 s of `onStartCommand` (API 26+ requirement); notification channel exists with `IMPORTANCE_LOW`; `foregroundServiceType` includes `microphone` on API 30+; service stops cleanly on disconnect intent and does not leak its wakelock; restart policy is `START_NOT_STICKY` (no auto-respawn after force-stop). +- Evidence: Test result + +**SWE4-UV-045**: Verify `AndroidAudioModeController` snapshot/restore + refcount (`SDD-108`). + +- Source SDD: SDD-108 +- Source SRS: SRS-208 +- Source SysRS: SysRS-305 +- Verification method: Kotlin unit test with `AudioManager` test double +- Pass criteria: First acquire snapshots prior `AudioManager.getMode()` and sets `MODE_IN_COMMUNICATION`; nested acquires increment refcount without re-snapshotting; matched release decrements; final release restores the snapshotted prior mode exactly once; when `AudioManager` is unavailable (e.g. injected null), controller logs a sanitized warning and degrades to no-op without throwing. +- Evidence: Test result + +**SWE4-UV-046**: Verify `AndroidPttCapability` reports `L0Focused` and pins `FocusedPttBackend` (`SDD-110`). + +- Source SDD: SDD-110 +- Verification method: Rust + Dart unit test +- Pass criteria: On `target_os = "android"` the `DesktopPttBackend` factory returns `FocusedPttBackend` unconditionally (no ladder evaluation); `PttCapabilityBadge` renders the localized `L0Focused` label with the Android-specific explanation string; no Android branch attempts to bind global hotkeys. +- Evidence: Test result + UI screenshot + +| SDD Range | SWE.4 Coverage | +|---|---| +| SDD-028 (expanded) | SWE4-UV-042 | +| SDD-073 (refreshed) | SWE4-UV-040 | +| SDD-105 | SWE4-UV-043 | +| SDD-106 | SWE4-UV-041 | +| SDD-107 | SWE4-UV-044 | +| SDD-108 | SWE4-UV-045 | +| SDD-110 | SWE4-UV-046 | + + +### Android voice audio backend unit-verification items (SDD-111..SDD-116 wave) + +**SWE4-UV-047**: Verify `AndroidVoiceStreamConfig` builder records requested vs. achieved fields. + +- Source SDD: SDD-112 (config struct), SDD-111 (trait surface) +- Source SRS: SRS-210, SRS-211, SRS-213, SRS-214 +- Source SAD: SAD-077, SAD-081 +- Verification method: Rust unit test with a fake Oboe builder return path (no device) +- Pass criteria: Given a `MobileVoiceStreamConfig` requesting `PERFORMANCE_MODE_LOW_LATENCY`, input preset `VoiceCommunication`, output usage `VoiceCommunication`, content type `Speech`, and sharing mode `Exclusive`, the resulting `AndroidVoiceStreamConfig` records the requested values verbatim AND exposes `achieved_*` accessors populated from the fake builder's grant values. Mismatch between requested and achieved is observable via the accessors with no panics. Both input and output stream configs are independently recorded. +- Evidence: Rust unit test result +- SDD-116 matrix row satisfied: none directly (unit-level; matrix consumes the accessors per-device) + +**SWE4-UV-048**: Verify input preset fallback ladder is observable at each step. + +- Source SDD: SDD-111 (trait surface, open() error pathway), SDD-112 (preset field) +- Source SRS: SRS-211 +- Source SAD: SAD-077 +- Verification method: Rust unit test with a scripted Oboe builder mock that returns `ErrorIllegalArgument` for successive presets +- Pass criteria: With the mock denying `VoiceCommunication`, `open()` retries with `VoicePerformance`; with the mock denying both, `open()` retries with `Generic` (`Unprocessed`/default). Each retry is recorded in a structured field (e.g. `attempted_presets: Vec`) and the final `achieved_input_preset()` returns the preset that was granted. If all presets fail, `open()` returns `BackendError::InputPresetUnavailable` (or the SDD-111 equivalent). No silent fallback. +- Evidence: Rust unit test result +- SDD-116 matrix row satisfied: none directly (matrix records `achieved_input_preset` per device) + +**SWE4-UV-049**: Verify sharing-mode fallback Exclusive→Shared is observable. + +- Source SDD: SDD-111, SDD-112 (sharing mode field) +- Source SRS: SRS-214 +- Source SAD: SAD-077 +- Verification method: Rust unit test with a fake Oboe builder return path +- Pass criteria: When the fake builder accepts `SharingMode::Exclusive`, `achieved_sharing_mode()` returns `Exclusive` and no retry occurs. When the fake builder rejects `Exclusive` (returns the documented Oboe error path for exclusive denial), the unit retries with `Shared`, and `achieved_sharing_mode()` returns `Shared`. The original request (`Exclusive`) is still recorded for diagnostics. +- Evidence: Rust unit test result +- SDD-116 matrix row satisfied: none directly (matrix records `achieved_sharing_mode` per device) + +**SWE4-UV-050**: Verify hardware effects per-effect success/failure logic with software-AEC fallback. + +- Source SDD: SDD-113 (per-effect construction + software fallback signal) +- Source SRS: SRS-212 +- Source SAD: SAD-077, SAD-081 +- Verification method: Rust unit test with a mocked JNI surface (`new_object`-style indirection) representing `AcousticEchoCanceler.create`, `NoiseSuppressor.create`, `AutomaticGainControl.create` +- Pass criteria: For each of AEC, NS, AGC: (a) success path — mock returns a non-null `GlobalRef`, effect is recorded as engaged, `setEnabled(true)` is invoked once; (b) failure path — mock returns null OR throws, the unit records the per-effect failure, does NOT abort the other two, and signals the engine to engage the software equivalent (per SDD-113 item 5 — for AEC failure the engine's software AEC must be flagged on). Order of effect construction does not affect outcome (no cross-effect coupling). `release()` is called on every engaged `GlobalRef` exactly once when the unit is torn down. +- Evidence: Rust unit test result +- SDD-116 matrix row satisfied: per-device AEC/NS/AGC availability column + +**SWE4-UV-051**: Verify performance-mode achieved recording feeds SRS-210 latency tier classification. + +- Source SDD: SDD-111 (`achieved_performance_mode`), SDD-112 (mode field) +- Source SRS: SRS-210 +- Source SAD: SAD-077 +- Verification method: Rust unit test +- Pass criteria: When the fake builder grants `PERFORMANCE_MODE_LOW_LATENCY`, `achieved_performance_mode()` returns `LowLatency` and the SRS-210 tier classifier (the unit-level pure function that maps achieved mode → latency target) returns the 150 ms tier. When the fake builder grants `PERFORMANCE_MODE_NONE` or `POWER_SAVING`, the classifier returns the 250 ms tier. The classifier is a pure function reachable from the engine without a device. +- Evidence: Rust unit test result +- SDD-116 matrix row satisfied: per-device `achieved_performance_mode` column + +**SWE4-UV-052**: Verify output stream usage and content type are set correctly. + +- Source SDD: SDD-112 (output config fields) +- Source SRS: SRS-213 +- Source SAD: SAD-077 +- Verification method: Rust unit test inspecting the builder calls captured by a mock Oboe builder +- Pass criteria: The output stream-construction path invokes `setUsage(VoiceCommunication)` and `setContentType(Speech)` exactly once on the builder before `openStream`. No other usage / content-type combination is reachable from the production path (the test asserts there is no code path that constructs the output stream with `Media` / `Music` defaults). `achieved_output_usage()` returns `VoiceCommunication` and `achieved_output_content_type()` returns `Speech` on a successful open. +- Evidence: Rust unit test result +- SDD-116 matrix row satisfied: per-device `achieved_output_usage` / `achieved_output_content_type` column + +| SDD Range | SWE.4 Coverage | +|---|---| +| SDD-111 | SWE4-UV-047, SWE4-UV-048, SWE4-UV-049, SWE4-UV-051 | +| SDD-112 | SWE4-UV-047, SWE4-UV-048, SWE4-UV-049, SWE4-UV-051, SWE4-UV-052 | +| SDD-113 | SWE4-UV-050 | +| SDD-115 | (lifecycle sequencing — see SWE.5 integration items) | +| SDD-116 | (device matrix — see SWE.6 + android-p0-acceptance TC-14..TC-18) | + + +## Baseline Candidate 0.9.4 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.4 | 2026-05-18 | Added Android P0 unit-verification items SWE4-UV-040 through SWE4-UV-046 covering SDD-028 (expanded back-intent branches), SDD-073 (refreshed build config + AAB), SDD-105 (JNI bootstrap), SDD-106 (permission state machine + transmit clamp), SDD-107 (foreground service lifecycle), SDD-108 (audio mode controller refcount + restore), SDD-110 (PTT capability pin). | +| 0.9.5 | 2026-05-18 | Added Android voice audio backend unit-verification items SWE4-UV-047 through SWE4-UV-052 covering SDD-111 (trait surface), SDD-112 (config requested vs achieved), SDD-113 (per-effect hardware engagement + software fallback) and feeding SRS-210/211/213/214 unit-level classification. | +| 0.9.14 | 2026-05-18 | Wave 3 reviewer follow-up: allocated real SWE.4 IDs for two test sets that had been cited under provisional `SWE4-UV-NNN` placeholders. SWE4-UV-053..055 anchor the three `TransmitModeSelector` permission-state clamp tests in `crates/chanora_audio/src/transmit_selector.rs` (commit 635d160 was cited under the "SWE4-UV-026 family" without real anchors). SWE4-UV-056..057 anchor the two missing `DiagnosticExport::with_android_audio` rendering tests in `crates/chanora_diagnostics/src/lib.rs` (commit aea50a1 introduced the field/builder/`to_text` branch but the unit-test pair was queued for the commit-8 amend). Closes the Wave 3 traceability gap; downstream test cites in commits 4 and 8 are amended to reference these IDs. | + +## 12. Android Permission Clamp + Diagnostics Android-Audio Unit Verification Addendum (Wave 3 — v0.9.14) + +**Background.** Wave 3 reviewer audit flagged two newly-landed test groups whose source comments cited verification IDs as `SWE4-UV-NNN` placeholders rather than real allocated anchors: + +1. Three `TransmitModeSelector` permission-state tests at `crates/chanora_audio/src/transmit_selector.rs:343-435` (introduced by commit 635d160). The reviewer noted at `transmit_selector.rs:343` that these tests cite "SWE4-UV-026 family" without real IDs. +2. Two missing unit tests for the new `chanora_diagnostics` Android-audio surface (`android_audio: Option` field, `with_android_audio` builder, `[audio.android]` `to_text` branch) introduced by commit aea50a1; queued for the commit-8 amend. + +This addendum allocates the five required anchors. SWE4-UV-052 was the prior high-water mark; SWE4-UV-053..057 are the next free contiguous range. + +**SWE4-UV-053**: Verify `TransmitModeSelector` clamps transmit to `false` when `permission_state == Denied` (or `PermanentlyDenied`/`Unknown`) regardless of PTT, channel, or transmit-mode state. + +- Source SDD: SDD-106 §6 +- Source SRS: SRS-209 (fail-safe listen-only default) +- Verification method: Rust unit test +- Test code path: `crates/chanora_audio/src/transmit_selector.rs::tests::permission_state_denied_clamps_transmit_to_false` (lines 348-384) +- Pass criteria: With `TransmitMode::Ptt`, `in_channel = true`, `ptt_held = true` and the default `Granted` permission state the gate evaluates `true`. Publishing `PermissionGate::Denied` flips the gate to `false`. `PermanentlyDenied` and `Unknown` likewise clamp `false`. The clamp wins over `TransmitMode::Continuous`. +- Evidence: `cargo test -p chanora_audio` result +- Notes: Establishes the fail-safe semantics: cold-launch `Unknown` is listen-only per SRS-209. + +**SWE4-UV-054**: Verify `TransmitModeSelector` releases the permission clamp on transition back to `Granted` and resumes normal PTT-driven evaluation. + +- Source SDD: SDD-106 §6 +- Source SRS: SRS-209 +- Verification method: Rust unit test +- Test code path: `crates/chanora_audio/src/transmit_selector.rs::tests::permission_state_granted_releases_clamp` (lines 389-409) +- Pass criteria: After `Denied` clamps the gate to `false`, transitioning to `PermissionGate::Granted` (with PTT held + in channel) restores the gate to `true` on the next state tick. Releasing the PTT key then drops the gate per normal PTT semantics — the clamp is no longer in effect. +- Evidence: `cargo test -p chanora_audio` result +- Notes: Pairs with SWE4-UV-053; together they cover the clamp on/off edges. + +**SWE4-UV-055**: Verify `TransmitModeSelector` ignores `PermissionState` events for non-`RECORD_AUDIO` permissions (contract: bridge filters before reaching the selector). + +- Source SDD: SDD-106 §5 +- Verification method: Rust unit test (contract pin) +- Test code path: `crates/chanora_audio/src/transmit_selector.rs::tests::permission_state_for_other_permission_does_not_clamp` (lines 418-435) +- Pass criteria: The selector exposes no setter for non-`RECORD_AUDIO` permissions. With `TransmitMode::Continuous` + `in_channel = true` and no call to `set_permission_state`, the selector remains in its `Granted` default and the gate evaluates `true`. Demonstrates the contract that unrelated permission events (e.g. `POST_NOTIFICATIONS`) cannot reach this code path. +- Evidence: `cargo test -p chanora_audio` result +- Notes: This is a negative/contract test; the JNI bridge is responsible for filtering by permission name before invoking `set_permission_state`. + +**SWE4-UV-056**: Verify `DiagnosticExport::with_android_audio(Some(yaml))` renders the `[audio.android]` section between `[metadata]` and `[recent logs]` in `to_text()`. + +- Source SDD: SDD-116 item 3 (diagnostics matrix surface), SDD-112 item 10 / SDD-113 item 7 (producing crate) +- Verification method: Rust unit test +- Test code path: `crates/chanora_diagnostics/src/lib.rs::tests::android_audio_renders_between_metadata_and_logs` (to be added by commit-8 amend) +- Pass criteria: Building a `DiagnosticExport::from_sink(...)` and chaining `.with_android_audio(Some("perf: low_latency\n".into()))` produces a `to_text()` output where the substring `[audio.android]` appears (a) after the `[metadata]` header, (b) before the `[recent logs]` header, and (c) is immediately followed by the supplied YAML fragment verbatim. The YAML fragment is not re-redacted by the diagnostics bundle (SDD-090 places sanitisation in the producing crate). +- Evidence: `cargo test -p chanora_diagnostics` result +- Notes: Section ordering is part of the SDD-116 matrix contract; downstream tooling parses by header. + +**SWE4-UV-057**: Verify `DiagnosticExport` with default `android_audio = None` omits the `[audio.android]` header entirely (negative test). + +- Source SDD: SDD-116 item 3 (negative case — non-Android or pre-session) +- Verification method: Rust unit test +- Test code path: `crates/chanora_diagnostics/src/lib.rs::tests::android_audio_absent_omits_section` (to be added by commit-8 amend) +- Pass criteria: A `DiagnosticExport::from_sink(...)` constructed without `.with_android_audio(...)` (i.e. `android_audio == None`) produces a `to_text()` output that does **not** contain the substring `[audio.android]`. The `[metadata]` and `[recent logs]` sections remain present in order. Calling `.with_android_audio(None)` explicitly is equivalent (idempotent omission). +- Evidence: `cargo test -p chanora_diagnostics` result +- Notes: Pairs with SWE4-UV-056; together they cover the conditional-render branch in `DiagnosticExport::to_text`. + +| SDD Range | SWE.4 Coverage | +|---|---| +| SDD-106 §5 | SWE4-UV-055 | +| SDD-106 §6 | SWE4-UV-053, SWE4-UV-054 | +| SDD-116 item 3 | SWE4-UV-056, SWE4-UV-057 | diff --git a/docs/verification/swe5-software-integration-verification-plan.md b/docs/verification/swe5-software-integration-verification-plan.md index 9519271..df0c7b7 100644 --- a/docs/verification/swe5-software-integration-verification-plan.md +++ b/docs/verification/swe5-software-integration-verification-plan.md @@ -245,3 +245,133 @@ Software integration verification results shall be summarized in an integration | Version | Date | Description | |---|---|---| | 0.9.3 | 2026-05-15 | Added desktop PTT software-integration verification item SWE5-IV-015 covering the integrated audio engine + Rust core PTT controller + bridge + Flutter UI + diagnostics path on Windows, macOS, and GNOME-on-Wayland Linux reference hosts. | + + +## 9. Android P0 Software Integration Verification Addendum + +**SWE5-IV-016**: Verify Android JNI bootstrap → foreground service lifecycle integration. + +- Source SAD: SAD-077, SAD-081 +- Source SDD: SDD-105, SDD-107 +- Verification method: Instrumented integration test on a physical API 28 device +- Pass criteria: `Application.onCreate` loads `libchanora_bridge.so`, `JNI_OnLoad` captures the `JavaVM`, the bridge is initialized, and `AndroidVoiceForegroundService` can be started by the Rust core (via JNI callback) without re-loading the library or racing init. Service start completes before the first audio frame is produced. +- Evidence: Integration test result + logcat trace + +**SWE5-IV-017**: Verify permission denial → foreground service not started → listen-only stream open integration. + +- Source SAD: SAD-077 +- Source SDD: SDD-106, SDD-107 +- Source SRS: SRS-209 +- Verification method: Integration test using permission-state test double + real audio stack +- Pass criteria: When `RECORD_AUDIO` is denied (or revoked mid-session) the FGS is NOT started for capture; the playback stream remains open so the user hears other participants (listen-only fallback per SRS-209); UI surfaces the degraded state; re-granting permission re-enables capture without process restart. +- Evidence: Integration test result + +**SWE5-IV-018**: Verify foreground service start triggers audio mode acquisition; service stop triggers mode restore. + +- Source SAD: SAD-081, SAD-085 +- Source SDD: SDD-107, SDD-108 +- Source SRS: SRS-208 +- Source SysRS: SysRS-305 +- Verification method: Integration test on a physical device with `dumpsys audio` snapshots before / during / after +- Pass criteria: Before voice channel join, `AudioManager.mode` is the device baseline. After FGS start and join, mode is `MODE_IN_COMMUNICATION`. After leave/disconnect, FGS stops and the controller restores the snapshotted prior mode. Refcount holds across rapid join/leave/join cycles. +- Evidence: Integration test result + `dumpsys audio` traces + +**SWE5-IV-019**: Verify `BackIntentService` integration with the Flutter Navigator route stack. + +- Source SAD: SAD-018 +- Source SDD: SDD-028 (expanded) +- Source SRS: SRS-163 +- Verification method: Flutter integration test on Android with a non-trivial route stack (home → channel → settings → modal) +- Pass criteria: System back closes modal first (does not pop the route); subsequent back pops one route per press; at the home route system back backgrounds the activity. PTT-active-ignore: while the test driver asserts `transmit_active`, back is consumed. API 33+ and pre-33 code paths both produce identical end states (verified on at least one device per branch). +- Evidence: Integration test result + +**SWE5-IV-020**: Verify AAB build pipeline integration (`SDD-109`). + +- Source SAD: SAD-063 +- Source SDD: SDD-073, SDD-109 +- Source SRS: SRS-119, SRS-188 +- Verification method: CI build job + `bundletool` validation +- Pass criteria: `flutter build appbundle --release` produces an AAB; `bundletool validate` reports no errors; `bundletool build-apks --connected-device` produces per-ABI splits for `{arm64-v8a, armeabi-v7a, x86_64}`; signing key fingerprint matches the CI release-key reference; `targetSdkVersion` matches the SRS-188 declared target; `minSdkVersion` == 28 (DEC-004); manifest contains `FOREGROUND_SERVICE` + `FOREGROUND_SERVICE_MICROPHONE` (API 34+) + `RECORD_AUDIO` permissions and no others outside the allow-list. +- Evidence: CI artifact + bundletool report + +| Architecture / Design Area | SWE.5 Coverage | +|---|---| +| SAD-018, SAD-063, SAD-077, SAD-081, SAD-084, SAD-085, SAD-086 | SWE5-IV-016 through SWE5-IV-020 | +| SDD-028 (expanded), SDD-073 (refreshed), SDD-105, SDD-106, SDD-107, SDD-108, SDD-109, SDD-110 | SWE5-IV-016 through SWE5-IV-020 | + + +### Android voice audio backend integration items (SDD-111..SDD-116 wave) + +**SWE5-IV-021**: Verify `MobileVoiceAudioBackend` trait + audio engine integration on voice_join / voice_leave. + +- Source SAD: SAD-077, SAD-081 +- Source SDD: SDD-111 (trait surface), SDD-112 (config), SDD-115 (lifecycle) +- Source SRS: SRS-210, SRS-211, SRS-213, SRS-214 +- Verification method: Rust integration test on a physical Android device (API 31 mid-tier minimum) driving the engine through `voice_join` / `voice_leave` against the real `AndroidVoiceUnit` +- Pass criteria: `voice_join` constructs `AndroidVoiceStreamConfig` from engine state, calls `AndroidVoiceUnit::open(...)`, opens BOTH input and output streams (verified by `session_id()` being `Some(_)` and the output-stream handle being non-null), then calls `start()`. Engine state transitions are observed in order: `Idle → Opening → Running`. `voice_leave` calls `stop()` then `close()` in that order; engine state returns to `Idle`. No stream is left open after `voice_leave` (verified by a follow-up `voice_join` succeeding with a fresh session id). +- Evidence: Integration test result + logcat with engine state transition lines +- SDD-116 matrix rows satisfied: all (open/close path exercised on every matrix device) + +**SWE5-IV-022**: Verify SDD-108 audio-mode engagement precedes SDD-113 effect binding (forward) and effects release precedes mode restore (reverse). + +- Source SAD: SAD-077, SAD-081, SAD-085 +- Source SDD: SDD-108, SDD-113, SDD-115 (sequencing) +- Source SRS: SRS-208, SRS-212 +- Verification method: Integration test instrumented with ordered event sink (timestamped events for `mode_engage`, `stream_open`, `effect_attach`, `start`, `stop`, `effect_release`, `stream_close`, `mode_restore`) +- Pass criteria: On `voice_join`, the recorded order is exactly `FGS_start → stream_open → mode_engage → effect_attach → start`. On `voice_leave`, the recorded order is exactly `stop → effect_release → mode_restore → stream_close → FGS_stop` per SDD-115 item 3 (reverse-order teardown). Any deviation fails the case. Test repeats 3 rapid join/leave cycles to surface refcount or sequencing races. +- Evidence: Integration test result + event-sink dump +- SDD-116 matrix rows satisfied: all + +**SWE5-IV-023**: Verify SDD-115 full lifecycle sequencing forward and reverse. + +- Source SAD: SAD-077, SAD-081 +- Source SDD: SDD-107, SDD-108, SDD-111, SDD-113, SDD-115 +- Source SRS: SRS-208, SRS-210, SRS-212, SRS-215 +- Verification method: Instrumented integration test on physical device +- Pass criteria: Forward sequence: `FGS_start → streams_open → mode_engage → effects_bind → start_capture+playback`. Reverse sequence: `stop → effects_release → mode_restore → streams_close → FGS_stop`. Test asserts strict ordering AND that no event from one step occurs before the previous step completes (no overlap). Test re-runs across at least one API 28 device and one API 34 device to cover the FGS-type tightening branch. +- Evidence: Integration test result + ordered event log +- SDD-116 matrix rows satisfied: API 28 row, API 31 row, API 34 row + +**SWE5-IV-024**: Verify audio-focus loss handling. + +- Source SAD: SAD-077, SAD-081, SAD-085 +- Source SDD: SDD-115 (audio focus event response) +- Source SRS: SRS-208, SRS-215 +- Verification method: Integration test using a controllable secondary audio source (test app that requests focus with the three loss kinds) +- Pass criteria: `AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK` → capture and playback continue at unchanged level (Chanora does not duck itself; voice content is exempt per SDD-115). `AUDIOFOCUS_LOSS_TRANSIENT` → capture is paused (no frames produced upstream) and playback is paused; on focus regain, both resume without `voice_leave`/`voice_join`. `AUDIOFOCUS_LOSS` (permanent) → the session is left cleanly (effects released, mode restored, streams closed) and the user is surfaced an "audio focus lost" state; no auto-rejoin. +- Evidence: Integration test result + logcat +- SDD-116 matrix rows satisfied: at least API 31 + API 34 rows + +**SWE5-IV-025**: Verify `ErrorDisconnected` callback marshals to tokio task and triggers reopen without blocking the callback thread. + +- Source SAD: SAD-077, SAD-081 +- Source SDD: SDD-111 (error/disconnect callback via bounded channel/watch), SDD-115 (recovery on disconnect) +- Source SRS: SRS-210, SRS-215 +- Verification method: Integration test injecting a forced disconnect (e.g. unplug Bluetooth headset mid-session, or invoke an Oboe error-callback fault injection point) +- Pass criteria: The Oboe `onErrorBeforeClose` / `onErrorAfterClose` callback path enqueues a message onto the bounded channel and returns within the callback's documented latency budget (assert: callback returns in < 5 ms). Reopen is performed on a tokio task on the engine runtime, not on the callback thread (verified by thread-id capture in test). Reopen completes (`voice_join` round-trip on the new device route) within 2 s. The callback never holds a lock during reopen. +- Evidence: Integration test result + thread-id capture log +- SDD-116 matrix rows satisfied: all (regression surface) + +**SWE5-IV-026**: Verify permission-denied `voice_join` reports listen-only and does NOT start the foreground service for capture. + +- Source SAD: SAD-077 +- Source SDD: SDD-106, SDD-107, SDD-111, SDD-115 +- Source SRS: SRS-209, SRS-215 +- Verification method: Integration test with `RECORD_AUDIO` denied via permission test double +- Pass criteria: `voice_join` opens the output (playback) stream and engine state becomes `RunningListenOnly`. `AndroidVoiceForegroundService` is NOT started with `foregroundServiceType="microphone"` for capture (the service may still run as a non-mic FGS for playback longevity per SDD-107 policy — if SDD-107 declines this, the FGS is not started at all). Backend reports `capture_state = ListenOnly`. No `AndroidVoiceUnit` input stream is opened. UI badge reflects listen-only. On runtime permission grant, the engine performs an internal upgrade to full duplex without process restart and without losing the playback stream. Cross-link: builds on SWE5-IV-017. +- Evidence: Integration test result + logcat + UI screenshot +- SDD-116 matrix rows satisfied: API 28 row, API 34 row (FGS-type tightening branch) + +| Architecture / Design Area | SWE.5 Coverage | +|---|---| +| SAD-077, SAD-081 (mobile voice audio backend allocation) | SWE5-IV-021 through SWE5-IV-026 | +| SDD-111, SDD-112, SDD-113, SDD-115 | SWE5-IV-021 through SWE5-IV-026 | +| SDD-116 (device matrix coverage entry points) | SWE5-IV-021, SWE5-IV-023 | + + +## Baseline Candidate 0.9.4 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.4 | 2026-05-18 | Added Android P0 software-integration items SWE5-IV-016..SWE5-IV-020 covering JNI+FGS, permission+FGS+listen-only fallback, FGS+audio-mode round-trip, BackIntentService + Flutter route stack, and AAB build pipeline (bundletool + ABI splits). | +| 0.9.5 | 2026-05-18 | Added Android voice audio backend integration items SWE5-IV-021..SWE5-IV-026 covering MobileVoiceAudioBackend trait + engine wiring (SDD-111), SDD-108↔SDD-113 ordering and reverse-order teardown (SDD-115), full lifecycle sequencing across SDD-107/108/111/113, audio-focus loss handling, ErrorDisconnected callback marshaling, and permission-denied listen-only fallback. Cross-links: SWE5-IV-017 extended by SWE5-IV-026 (listen-only fallback). | diff --git a/docs/verification/swe6-software-verification-plan.md b/docs/verification/swe6-software-verification-plan.md index 3bdc2c0..dd62ec8 100644 --- a/docs/verification/swe6-software-verification-plan.md +++ b/docs/verification/swe6-software-verification-plan.md @@ -241,3 +241,160 @@ Software verification results shall be summarized in a software verification sum | Version | Date | Description | |---|---|---| | 0.9.3 | 2026-05-15 | Added desktop PTT software-verification item SWE6-SV-017 covering end-to-end SRS-195..203 verification across Windows, macOS, and GNOME-on-Wayland Linux reference hosts. | + + +## 9. Android P0 Software Verification Addendum + +**SWE6-SV-018**: Verify Android voice foreground service satisfies SRS-111. + +- Source SRS: SRS-111 +- Verification method: Device-level software-qualification test on API 28 floor device + mid-tier + current target (per `docs/verification/android-p0-acceptance.md` TC-3) +- Pass criteria: While connected to a voice channel the foreground service runs with `foregroundServiceType="microphone"` (API 30+) and a non-dismissible notification; mic capture and playback continue with the screen off for ≥ 10 minutes without being killed; service stops cleanly on disconnect. +- Evidence: Device test log + screen-recording +- Cross-link (added in 0.9.5): SRS-215 device-matrix expansion is covered by SWE6-SV-030. SWE6-SV-018 remains the single-device baseline; SWE6-SV-030 collects per-SDD-116-row evidence. + +**SWE6-SV-019**: Verify RECORD_AUDIO request timing and listen-only fallback satisfy SRS-209. + +- Source SRS: SRS-209 +- Source SysRS: SysRS-306 +- Verification method: Device-level test (android-p0-acceptance.md TC-2) +- Pass criteria: The mic permission is requested at the user gesture that first needs it (not at app launch); denial drops capture but keeps playback open (listen-only); permanently-denied state opens the system settings deep-link instead of re-prompting; re-grant re-enables capture without process restart. +- Evidence: Device test log + +**SWE6-SV-020**: Verify Android in-call audio mode round-trip satisfies SRS-208. + +- Source SRS: SRS-208 +- Source SysRS: SysRS-305 +- Verification method: Device-level test with `dumpsys audio` (android-p0-acceptance.md TC-6) +- Pass criteria: Pre-join mode is recorded; during voice session mode is `MODE_IN_COMMUNICATION`; on leave/disconnect prior mode is restored exactly. No permanent mode lock across multiple sessions. No crash if a Bluetooth headset is connected/disconnected mid-session (P0 minimum). +- Evidence: Device test log + dumpsys traces + +**SWE6-SV-021**: Verify Android back navigation satisfies SRS-163. + +- Source SRS: SRS-163 +- Verification method: Device-level test (android-p0-acceptance.md TC-11 + TC-12) on API 28, an API ≤ 32 device, and an API 33+ device. +- Pass criteria: Modal-first close; one-pop-per-press; home-route back backgrounds; PTT-active ignores back. Predictive-back animation present on API 33+ and absent on pre-33 with identical end behaviour. +- Evidence: Device test log + screen-recording + +**SWE6-SV-022**: Verify Android minimum API satisfies SRS-187 and DEC-004. + +- Source SRS: SRS-187 +- Source SysRS: SysRS-288 +- Verification method: Manifest inspection + install attempt on API 28 device +- Pass criteria: Merged manifest declares `minSdkVersion = 28`. Install on API 28 succeeds. Install attempt on API 27 simulator/device is rejected with a clean error. +- Evidence: Manifest dump + install logs + +**SWE6-SV-023**: Verify Android Play target API and AAB packaging satisfy SRS-119 and SRS-188. + +- Source SRS: SRS-119, SRS-188 +- Verification method: AAB inspection + Play Console upload to Internal Testing (android-p0-acceptance.md TC-13) +- Pass criteria: `targetSdkVersion` matches the currently-required Play target per SRS-188; release artifact is an AAB (not a bare APK) per SRS-119; Play Console accepts the bundle; signing key is the CI release key. +- Evidence: AAB dump + Play Console acceptance screenshot + +**SWE6-SV-024**: Verify Android PTT capability badge reports `L0Focused` per SAD-086 / SDD-110. + +- Source SRS: SRS-208 (mode boundary), SRS-209 +- Verification method: Device-level UI test (android-p0-acceptance.md TC-4 + TC-10) +- Pass criteria: Capability badge shows `L0Focused` with Android-specific explanatory copy. No global hotkey is bound. Backgrounding the app stops PTT transitions from being delivered. +- Evidence: UI screenshot + log + +| SRS Range | SWE.6 Coverage | +|---|---| +| SRS-111 | SWE6-SV-018 | +| SRS-119 | SWE6-SV-023 | +| SRS-163 | SWE6-SV-021 | +| SRS-187 | SWE6-SV-022 | +| SRS-188 | SWE6-SV-023 | +| SRS-208 | SWE6-SV-020, SWE6-SV-024 | +| SRS-209 | SWE6-SV-019, SWE6-SV-024 | + + +### Android voice audio backend qualification items (SRS-210..SRS-215, SDD-111..SDD-116 wave) + +**SWE6-SV-025**: Verify SRS-210 mouth-to-ear latency target per device-class on the SDD-116 device matrix. + +- Source SRS: SRS-210 +- Source SDD: SDD-111, SDD-112, SDD-116 +- Verification method: Glass-to-glass acoustic measurement on each SDD-116 matrix device (API 28, API 31, API 34; at least one Pixel + one Samsung). See "Glass-to-glass latency measurement procedure" below. +- Pass criteria: On devices where `achieved_performance_mode() == LowLatency` the measured mouth-to-ear latency is **≤ 150 ms** (median of 20 samples, 95th percentile ≤ 180 ms). On devices where the achieved mode is `None`/`PowerSaving` the measured latency is **≤ 250 ms** (median of 20 samples, 95th percentile ≤ 300 ms). Tier classification is derived from the SWE4-UV-051 unit-level mapping evaluated against the per-device `achieved_performance_mode` from SWE6-SV-026. +- Evidence: Per-device measurement log (CSV: device, API, achieved_mode, sample_index, latency_ms) + reference audio recording for spot-check + acceptance form rows TC-17, TC-18 +- SDD-116 matrix rows satisfied: all (one row per device) + +**SWE6-SV-026**: Verify SRS-211 input preset achieved value recorded per device. + +- Source SRS: SRS-211 +- Source SDD: SDD-112 (`achieved_input_preset()`), SDD-116 +- Verification method: Device-level test reading the backend's diagnostics export after `voice_join` +- Pass criteria: For each SDD-116 matrix device the diagnostics record one of `{VoiceCommunication, VoicePerformance, Generic}` and that value is consistent with the fallback ladder verified by SWE4-UV-048. `VoiceCommunication` is preferred and any deviation must be accompanied by a recorded fallback rationale (e.g. preset rejected by OEM stack). No device reports an unrecorded/unknown preset. +- Evidence: Per-device diagnostics export +- SDD-116 matrix rows satisfied: all + +**SWE6-SV-027**: Verify SRS-212 hardware AEC engagement (and NS/AGC where supported) per device. + +- Source SRS: SRS-212 +- Source SDD: SDD-113, SDD-116 +- Verification method: Device-level test inspecting backend diagnostics + audible regression check (echo audibility) under speakerphone-routed playback +- Pass criteria: For each matrix device the diagnostics record per-effect engagement: `{aec: hw|sw|unavailable, ns: hw|sw|unavailable, agc: hw|sw|unavailable}`. Where `AcousticEchoCanceler.isAvailable()` returns true the recorded value MUST be `hw`. Where it returns false the recorded value MUST be `sw` (software fallback per SDD-113 item 5) — never silent. Audible echo evaluation (test sequence: 10 s of speakerphone playback with mic open at conversational distance) yields no operator-perceptible echo on at least one Pixel device and at least one Samsung device. +- Evidence: Per-device diagnostics export + audible-check operator note +- SDD-116 matrix rows satisfied: all (AEC/NS/AGC availability column) + +**SWE6-SV-028**: Verify SRS-213 output usage and content type achieved per device. + +- Source SRS: SRS-213 +- Source SDD: SDD-112 (`achieved_output_usage`, `achieved_output_content_type`), SDD-116 +- Verification method: Device-level test reading backend diagnostics; cross-checked with `dumpsys media.audio_flinger` where queryable +- Pass criteria: For each matrix device the diagnostics record `achieved_output_usage == VoiceCommunication` AND `achieved_output_content_type == Speech`. Where `dumpsys` exposes the stream attributes, the dump corroborates. Mismatch fails the case. Also verifies the in-call routing side-effect: volume slider during the session adjusts the call-volume stream, not media volume (operator check on at least one device). +- Evidence: Per-device diagnostics export + dumpsys excerpt + operator volume-routing note +- SDD-116 matrix rows satisfied: all + +**SWE6-SV-029**: Verify SRS-214 sharing mode achieved per device. + +- Source SRS: SRS-214 +- Source SDD: SDD-112 (`achieved_sharing_mode`), SDD-116 +- Verification method: Device-level test reading backend diagnostics on each matrix device +- Pass criteria: For each matrix device the diagnostics record `achieved_sharing_mode ∈ {Exclusive, Shared}`. The requested value is always `Exclusive`; deviation to `Shared` is acceptable and must be recorded with no operational regression (assert: no callback errors in the first 60 s of the session when the achieved mode is `Shared`). The aggregate device-matrix table in the test report cross-tabulates `requested vs achieved` per device. +- Evidence: Per-device diagnostics export +- SDD-116 matrix rows satisfied: all + +**SWE6-SV-030**: Verify SRS-215 background mic capture under foreground-service-type=microphone per device. + +- Source SRS: SRS-215 +- Source SDD: SDD-107, SDD-114 (manifest declarations), SDD-115 (FGS-first sequencing), SDD-116 +- Verification method: Device-level test (extends SWE6-SV-018 / android-p0-acceptance.md TC-3) executed on each SDD-116 matrix device +- Pass criteria: With the screen off (device locked or screen timed out) for ≥ 10 minutes the input stream remains open AND frames continue to be produced by the AAudio input (verified by a periodic per-minute frame-count probe in the diagnostics) on every matrix device. On API 34 devices the FGS-type is `microphone` (verified by `dumpsys activity services` excerpt). No `ForegroundServiceDidNotStartInTimeException` or `MissingForegroundServiceTypeException` in logcat. Cross-link annotation: this item extends SWE6-SV-018 with per-matrix-row evidence; SWE6-SV-018 remains the single-device baseline and SWE6-SV-030 is the device-matrix expansion. +- Evidence: Per-device test log + dumpsys excerpt + frame-count probe export +- SDD-116 matrix rows satisfied: all + +| SRS Range | SWE.6 Coverage | +|---|---| +| SRS-210 | SWE6-SV-025 | +| SRS-211 | SWE6-SV-026 | +| SRS-212 | SWE6-SV-027 | +| SRS-213 | SWE6-SV-028 | +| SRS-214 | SWE6-SV-029 | +| SRS-215 | SWE6-SV-018 (single-device baseline), SWE6-SV-030 (device-matrix expansion) | + +#### Glass-to-glass latency measurement procedure (SWE6-SV-025) + +Mouth-to-ear (glass-to-glass) latency for SRS-210 shall be measured end-to-end across the Android device under test acting as the speaker AND a second Android device (or reference Linux host) acting as the listener, both joined to a controlled test TeamSpeak 3 server on the same LAN (no internet hop; LAN RTT must be measured and recorded separately, and subtracted as a baseline only if > 5 ms). + +Measurement setup: + +1. Connect the DUT and the reference listener device to the test server. The DUT is the talker; the reference is the listener. +2. Drive a short acoustic impulse (a 1 kHz tone burst, 20 ms duration, or a click track) into the DUT microphone from a controlled acoustic source (small loudspeaker at fixed distance, OR a wired line-in adapter where the DUT supports one). +3. Capture the listener device's loudspeaker output with a reference microphone connected to a stereo audio interface; the same audio interface ALSO captures the source impulse on the second channel via a hardwired tap from the impulse generator. Both channels are recorded into a single stereo WAV at ≥ 48 kHz, 16-bit minimum. +4. Latency is the time delta between the impulse-onset sample on channel A (source tap) and the impulse-onset sample on channel B (listener loudspeaker capture), expressed in milliseconds. Onset detection: first sample exceeding 10× the RMS noise floor of the preceding 100 ms. +5. Required precision: ≤ 1 ms (achievable at 48 kHz with single-sample onset detection). The measurement tool is unspecified; any DAW, `sox`, or custom script that meets the precision requirement is acceptable. The script and raw WAV files are part of the evidence and shall be archived. +6. Per-device run: 20 impulses spaced ≥ 2 s apart. Record median and 95th percentile. +7. Baseline subtraction: LAN one-way latency to the test server is measured (e.g. via ICMP / TS3 control-channel ping) and recorded but NOT subtracted from the SRS-210 result, because SRS-210's "mouth-to-ear" definition includes the network hop on a representative network. The LAN result is the ceiling case; WAN regression is out of scope for SRS-210 on this matrix. +8. Operator notes record: DUT model, Android API, `achieved_performance_mode`, `achieved_sharing_mode`, `achieved_input_preset`, headset/loudspeaker routing, ambient noise estimate, ambient temperature only if extreme. + +This procedure is reproducible without proprietary fixtures. A loopback-cable-only variant (DUT line-out → DUT line-in on a single device with a wired headset adapter) is acceptable as a screening test but is NOT sufficient for SRS-210 sign-off because it does not exercise the network path. + + +## Baseline Candidate 0.9.4 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.4 | 2026-05-18 | Added Android P0 software-qualification items SWE6-SV-018..SWE6-SV-024 mapped to SRS-111, SRS-119, SRS-163, SRS-187, SRS-188, SRS-208, SRS-209; cross-linked to `docs/verification/android-p0-acceptance.md` test cases. | +| 0.9.5 | 2026-05-18 | Added Android voice audio backend software-qualification items SWE6-SV-025..SWE6-SV-030 covering SRS-210 (latency tier per SDD-116 matrix, with glass-to-glass measurement procedure), SRS-211 (input preset achieved), SRS-212 (hardware AEC engagement + software fallback evidence), SRS-213 (output usage/content type achieved), SRS-214 (sharing mode achieved), SRS-215 (FGS-hosted background capture per matrix row). Annotated SWE6-SV-018 with cross-link to SWE6-SV-030 (device-matrix expansion). | diff --git a/docs/verification/sys4-system-integration-verification-plan.md b/docs/verification/sys4-system-integration-verification-plan.md index b15c55e..3e1ce6b 100644 --- a/docs/verification/sys4-system-integration-verification-plan.md +++ b/docs/verification/sys4-system-integration-verification-plan.md @@ -258,3 +258,46 @@ System integration verification results shall be summarized in a system integrat | Version | Date | Description | |---|---|---| | 0.9.3 | 2026-05-15 | Added desktop PTT system-integration verification item SYS4-SIV-016 covering SysDes-142..148 across Windows, macOS, and GNOME-on-Wayland Linux reference hosts. | + + +## 10. Android P0 System Integration Verification Addendum + +SYS4-SIV-015 (Platform Decision gate) is strengthened to require the +Android-specific evidence below. Two new SYS.4 entries are added. + +**SYS4-SIV-015 (strengthened — Android target gate)**: In addition to +the original platform-baseline checks, the Android target gate now +requires: + +- A boot + connect + voice-channel run on a physical Android 9 (API 28) device per DEC-004 / SysRS-288. +- AAB upload acceptance on the Play Console Internal Testing track (or `bundletool` equivalent) per SRS-119 / SRS-188. +- Foreground service surviving lock-screen for ≥ 10 minutes per SRS-111. +- AudioManager mode snapshot/restore verified via `dumpsys audio` per SRS-208 / SysRS-305. +- Evidence: System integration result + Play Console acceptance record + dumpsys traces. + +**SYS4-SIV-017**: Verify Android in-call audio mode round-trip with Bluetooth SCO route awareness. + +- Source SysDes: SysDes-133 through SysDes-148 (Android-specific subset), SysRS-305 +- Source SRS: SRS-208 +- Verification method: System integration test on a physical Android device with at least one Bluetooth headset +- Pass criteria: Pre-session `AudioManager.mode` is captured; voice session sets `MODE_IN_COMMUNICATION`; disconnect restores the prior mode. Bluetooth headset connect/disconnect during a session does not crash the app and does not lock the audio route permanently (P0 minimum). Full SCO-route correctness is logged as a P1 follow-up. +- Evidence: Device test log + dumpsys + Bluetooth-event log + +**SYS4-SIV-018**: Verify Android AAB install via Play Internal Track or bundletool. + +- Source SysDes: SysDes-133 through SysDes-148 (Android-specific subset) +- Source SRS: SRS-119, SRS-187, SRS-188 +- Verification method: Release-pipeline integration test +- Pass criteria: The exact AAB produced by the release CI job is accepted by Play Console Internal Testing (or by `bundletool install-apks --connected-device`). The installed app boots on an API 28 device and a current-target-API device. Signing key fingerprint matches the CI release-key fingerprint. ABI splits exist for `{arm64-v8a, armeabi-v7a, x86_64}`. +- Evidence: Play Console acceptance screenshot + bundletool report + adb install log + +| SysDes Range | SYS.4 Coverage | +|---|---| +| SysDes-133 through SysDes-148 (Android subset) | SYS4-SIV-015 (strengthened), SYS4-SIV-017, SYS4-SIV-018 | + + +## Baseline Candidate 0.9.4 Update + +| Version | Date | Description | +|---|---|---| +| 0.9.4 | 2026-05-18 | Strengthened SYS4-SIV-015 Android target gate (API 28 boot, FGS lock-screen longevity, AudioManager round-trip, Play AAB acceptance) and added SYS4-SIV-017 (in-call mode + Bluetooth SCO awareness) and SYS4-SIV-018 (AAB install via Play Internal Track / bundletool). |