feat: integrate chat voice and diagnostics client

This commit is contained in:
Edison Jwa
2026-05-23 06:51:55 +09:00
parent 7e28791ec2
commit 7d5d8c2c90
93 changed files with 10273 additions and 3347 deletions
+117
View File
@@ -61,6 +61,14 @@ pub enum DiagnosticsError {
/// to the PoC value so audit grep patterns survive the promotion.
pub const REDACTION_MARKER: &str = "[REDACTED]";
/// SRS-122: In-memory log capacity for release builds.
/// Release builds use a smaller buffer to limit memory footprint and residual data in exports.
#[cfg(not(debug_assertions))]
pub const DEFAULT_LOG_CAPACITY: usize = 256;
/// SRS-122: In-memory log capacity for debug builds.
#[cfg(debug_assertions)]
pub const DEFAULT_LOG_CAPACITY: usize = 4096;
/// Registry of known-secret values that must never appear in logs
/// or exports. Cross-spike contract per SS-AUD-003: the secure
/// storage adapter calls [`Self::register`] every time a secret
@@ -612,6 +620,10 @@ pub struct DiagnosticExport {
/// fragment contains only device-side technical scalars per
/// SDD-090 (no PII, no permission state, no server identity).
pub android_audio: Option<String>,
/// SRS-100: Network connectivity diagnostics summary.
pub network_info: Option<String>,
/// SRS-097: Protocol event recording trace.
pub protocol_events: Vec<String>,
}
impl DiagnosticExport {
@@ -625,6 +637,8 @@ impl DiagnosticExport {
recent_logs: sink.snapshot(),
known_secret_count: sink.redactor().secrets().len(),
android_audio: None,
network_info: None,
protocol_events: Vec::new(),
})
}
@@ -638,6 +652,18 @@ impl DiagnosticExport {
self
}
/// Attach a network connectivity diagnostics fragment (SRS-100).
pub fn with_network_info(mut self, info: Option<String>) -> Self {
self.network_info = info;
self
}
/// Attach protocol event trace (SRS-097).
pub fn with_protocol_events(mut self, events: Vec<String>) -> Self {
self.protocol_events = events;
self
}
/// Render as a plaintext blob suitable for `Share` / `Copy`.
/// The output is multi-line UTF-8, redacted.
pub fn to_text(&self) -> String {
@@ -656,6 +682,17 @@ impl DiagnosticExport {
out.push_str("\n[audio.android]\n");
out.push_str(yaml);
}
if let Some(info) = &self.network_info {
out.push_str("\n[network]\n");
out.push_str(info);
}
if !self.protocol_events.is_empty() {
out.push_str("\n[protocol events]\n");
for ev in &self.protocol_events {
out.push_str(ev);
out.push('\n');
}
}
out.push_str("\n[recent logs]\n");
for line in &self.recent_logs {
out.push_str(line);
@@ -665,6 +702,86 @@ impl DiagnosticExport {
}
}
/// SRS-097/098: Ring-buffer recorder of protocol-level events
/// (connect, disconnect, snapshot changes, channel joins) for
/// diagnostic export and state-sync replay verification.
#[derive(Debug, Clone)]
pub struct ProtocolEventRecorder {
events: Vec<String>,
capacity: usize,
}
impl ProtocolEventRecorder {
/// Create a recorder with the given ring-buffer capacity.
pub fn new(capacity: usize) -> Self {
Self {
events: Vec::with_capacity(capacity),
capacity,
}
}
fn push(&mut self, ts: &str, kind: &str, detail: &str) {
let s = format!("[{ts}] {kind}: {detail}");
if self.events.len() >= self.capacity {
self.events.remove(0);
}
self.events.push(s);
}
/// Record a successful connection.
pub fn record_connected(&mut self, server_name: &str) {
self.push("connect", "connected", server_name);
}
/// Record a graceful or forced disconnect.
pub fn record_disconnected(&mut self, reason: &str) {
self.push("disconnect", "disconnected", reason);
}
/// Record a reconnect attempt.
pub fn record_reconnecting(&mut self, attempt: u64, delay_secs: u64) {
self.push(
"reconnect",
"reconnecting",
&format!("attempt={attempt} delay={delay_secs}s"),
);
}
/// Record a snapshot tree change.
pub fn record_snapshot_changed(&mut self, channels: usize, clients: usize) {
self.push(
"snapshot",
"changed",
&format!("channels={channels} clients={clients}"),
);
}
/// Record a channel join event.
pub fn record_channel_join(&mut self, channel_id: u64, channel_name: &str) {
self.push(
"join",
"channel_joined",
&format!("id={channel_id} name={channel_name}"),
);
}
/// Record a platform lifecycle transition (SRS-138).
pub fn record_lifecycle(&mut self, state: &str) {
self.push("lifecycle", state, "");
}
/// Drain all recorded events and reset the buffer.
pub fn drain(&mut self) -> Vec<String> {
std::mem::take(&mut self.events)
}
}
impl Default for ProtocolEventRecorder {
fn default() -> Self {
Self::new(256)
}
}
#[cfg(test)]
mod tests {
use super::*;