test+diag: SWE.4 unit tests for Rust paths, Dart service tests, diagnostics audio.android section

Verification + diagnostics:

- apps/chanora_flutter/test/services/back_intent_policy_test.dart:
  8-case truth table for the BackIntentPolicy pure function
  (SWE4-UV-042).
- apps/chanora_flutter/test/services/back_intent_service_test.dart:
  5 channel-routing tests (SWE4-UV-042, SWE5-IV-019 unit slice).
- apps/chanora_flutter/test/services/android_permissions_service_test.dart:
  12 tests covering inbound channel events, outbound requests,
  state-machine transitions, and non-Android short-circuit
  (SWE4-UV-041).
- SDD/SRS trace headers added to alpha_e2e_test.dart,
  beta_e2e_test.dart, widget_test.dart so the existing test ↔ ID
  mapping is discoverable by grep.
- chanora_diagnostics: extends DiagnosticExport with android_audio:
  Option<String> for the SDD-116 evidence schema (requested /
  achieved performance mode + sharing mode + input preset + sample
  rate + frames per burst, per-effect engagement, latency tier).
  The bridge's export_diagnostics() now embeds the Android section
  when current_android_audio_diagnostics() returns Some.

All 93 workspace Rust tests and 26 Dart test/services tests pass.

Trace: SDD-090, SDD-112, SDD-113, SDD-116, SWE4-UV-041, SWE4-UV-042,
SWE4-UV-045, SWE4-UV-047, SWE4-UV-048, SWE4-UV-049, SWE4-UV-051,
SWE4-UV-052, SWE5-IV-019.
This commit is contained in:
EdisonJwa
2026-05-18 12:48:28 +08:00
parent c4145a8727
commit 4c19410556
7 changed files with 871 additions and 0 deletions
+95
View File
@@ -606,6 +606,12 @@ pub struct DiagnosticExport {
/// Number of currently registered known-secret values. The
/// values themselves are *not* exported.
pub known_secret_count: usize,
/// SDD-116 item 3: Android voice-audio diagnostics YAML
/// fragment, or `None` on non-Android targets / before a voice
/// session has opened. The producing crate guarantees this
/// fragment contains only device-side technical scalars per
/// SDD-090 (no PII, no permission state, no server identity).
pub android_audio: Option<String>,
}
impl DiagnosticExport {
@@ -618,9 +624,20 @@ impl DiagnosticExport {
metadata,
recent_logs: sink.snapshot(),
known_secret_count: sink.redactor().secrets().len(),
android_audio: None,
})
}
/// Attach an Android voice-audio diagnostics YAML fragment
/// (SDD-112 item 10 / SDD-113 item 7 / SDD-116 item 3). The
/// caller is responsible for ensuring the fragment is already
/// sanitised per SDD-090; the diagnostics bundle does not
/// re-redact this string.
pub fn with_android_audio(mut self, yaml_fragment: Option<String>) -> Self {
self.android_audio = yaml_fragment;
self
}
/// Render as a plaintext blob suitable for `Share` / `Copy`.
/// The output is multi-line UTF-8, redacted.
pub fn to_text(&self) -> String {
@@ -634,6 +651,11 @@ impl DiagnosticExport {
for (k, v) in &self.metadata {
out.push_str(&format!("{k}: {v}\n"));
}
if let Some(yaml) = &self.android_audio {
// SDD-116 item 3 verification-matrix section.
out.push_str("\n[audio.android]\n");
out.push_str(yaml);
}
out.push_str("\n[recent logs]\n");
for line in &self.recent_logs {
out.push_str(line);
@@ -873,4 +895,77 @@ mod tests {
assert!(!text.contains("u128 banned record must drop"));
assert!(text.contains("safe record must pass"));
}
/// SWE4-UV-056 — `DiagnosticExport` with `android_audio` renders the
/// `[audio.android]` section AFTER `[metadata]` and BEFORE
/// `[recent logs]`, with the YAML fragment immediately following
/// the header byte-for-byte (the bundle does not re-redact per
/// SDD-090 trust boundary).
#[test]
fn android_audio_renders_between_metadata_and_logs() {
let secrets = KnownSecretRegistry::default();
let redactor = Redactor::with_secrets(secrets);
let sink = InMemoryLogSink::new(16, redactor);
let yaml = "achieved:\n performance_mode: LowLatency\n sample_rate_hz: 48000\n";
let exported = DiagnosticExport::from_sink(
&sink,
vec![("build".into(), "test".into())],
)
.unwrap()
.with_android_audio(Some(yaml.to_string()));
let text = exported.to_text();
let metadata_pos = text.find("[metadata]").expect("metadata section");
let android_pos = text.find("[audio.android]").expect("android section");
let logs_pos = text.find("[recent logs]").expect("logs section");
assert!(
metadata_pos < android_pos,
"[metadata] must come before [audio.android]"
);
assert!(
android_pos < logs_pos,
"[audio.android] must come before [recent logs]"
);
// The YAML fragment must follow the header byte-for-byte
// (no re-redaction, no reformatting — SDD-090 trust boundary).
let header = "[audio.android]\n";
let header_start = text.find(header).expect("audio.android header");
let after_header = &text[header_start + header.len()..];
assert!(
after_header.starts_with(yaml),
"YAML fragment must follow [audio.android] header verbatim"
);
}
/// SWE4-UV-057 — `DiagnosticExport` with default `android_audio =
/// None` omits the `[audio.android]` header entirely (negative
/// test). Also verifies idempotence of explicit
/// `with_android_audio(None)`.
#[test]
fn android_audio_absent_omits_section() {
let secrets = KnownSecretRegistry::default();
let redactor = Redactor::with_secrets(secrets);
let sink = InMemoryLogSink::new(16, redactor);
// Default — never call with_android_audio.
let exported_default =
DiagnosticExport::from_sink(&sink, vec![("k".into(), "v".into())]).unwrap();
let text_default = exported_default.to_text();
assert!(
!text_default.contains("[audio.android]"),
"default export must omit [audio.android] section"
);
// Explicit None — idempotent with default.
let exported_explicit =
DiagnosticExport::from_sink(&sink, vec![("k".into(), "v".into())])
.unwrap()
.with_android_audio(None);
let text_explicit = exported_explicit.to_text();
assert!(
!text_explicit.contains("[audio.android]"),
"explicit with_android_audio(None) must also omit the section"
);
}
}