chore: stabilize current head before Phase 10a QC
This commit is contained in:
@@ -15,5 +15,7 @@ pub async fn decode_and_resample(path: &Path) -> Result<AudioSamples> {
|
||||
resample_to_16khz(&audio)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| magnotia_core::error::MagnotiaError::AudioDecodeFailed(format!("Task join error: {e}")))?
|
||||
.map_err(|e| {
|
||||
magnotia_core::error::MagnotiaError::AudioDecodeFailed(format!("Task join error: {e}"))
|
||||
})?
|
||||
}
|
||||
|
||||
@@ -99,7 +99,9 @@ fn decode_media_stream(
|
||||
.ok_or_else(|| MagnotiaError::AudioDecodeFailed("Unknown sample rate".into()))?;
|
||||
|
||||
if sample_rate == 0 {
|
||||
return Err(MagnotiaError::AudioDecodeFailed("Invalid sample rate: 0".into()));
|
||||
return Err(MagnotiaError::AudioDecodeFailed(
|
||||
"Invalid sample rate: 0".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let track_id = track.id;
|
||||
@@ -166,7 +168,9 @@ fn decode_media_stream(
|
||||
}
|
||||
|
||||
if samples.is_empty() {
|
||||
return Err(MagnotiaError::AudioDecodeFailed("No audio data decoded".into()));
|
||||
return Err(MagnotiaError::AudioDecodeFailed(
|
||||
"No audio data decoded".into(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(AudioSamples::new(samples, sample_rate, 1))
|
||||
|
||||
@@ -77,7 +77,9 @@ impl StreamingResampler {
|
||||
INPUT_CHUNK,
|
||||
1, // mono
|
||||
)
|
||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("StreamingResampler init failed: {e}")))?;
|
||||
.map_err(|e| {
|
||||
MagnotiaError::AudioDecodeFailed(format!("StreamingResampler init failed: {e}"))
|
||||
})?;
|
||||
|
||||
Ok(Self::Sinc {
|
||||
resampler,
|
||||
@@ -142,7 +144,9 @@ impl StreamingResampler {
|
||||
|
||||
let input = vec![chunk];
|
||||
let result = resampler.process(&input, None).map_err(|e| {
|
||||
MagnotiaError::AudioDecodeFailed(format!("StreamingResampler flush failed: {e}"))
|
||||
MagnotiaError::AudioDecodeFailed(format!(
|
||||
"StreamingResampler flush failed: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
let Some(mut out) = result.into_iter().next() else {
|
||||
|
||||
@@ -42,8 +42,9 @@ impl WavWriter {
|
||||
};
|
||||
let file = std::fs::File::create(path).map_err(MagnotiaError::Io)?;
|
||||
let buffered = BufWriter::new(file);
|
||||
let inner = hound::WavWriter::new(buffered, spec)
|
||||
.map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV create failed: {e}"))))?;
|
||||
let inner = hound::WavWriter::new(buffered, spec).map_err(|e| {
|
||||
MagnotiaError::Io(std::io::Error::other(format!("WAV create failed: {e}")))
|
||||
})?;
|
||||
Ok(Self {
|
||||
inner,
|
||||
samples_since_flush: 0,
|
||||
@@ -76,9 +77,9 @@ impl WavWriter {
|
||||
/// `Self::DEFAULT_FLUSH_EVERY_SAMPLES` — but may do so at natural
|
||||
/// boundaries (end-of-utterance, UI events) for tighter recovery.
|
||||
pub fn flush(&mut self) -> Result<()> {
|
||||
self.inner
|
||||
.flush()
|
||||
.map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV flush failed: {e}"))))?;
|
||||
self.inner.flush().map_err(|e| {
|
||||
MagnotiaError::Io(std::io::Error::other(format!("WAV flush failed: {e}")))
|
||||
})?;
|
||||
self.samples_since_flush = 0;
|
||||
Ok(())
|
||||
}
|
||||
@@ -110,14 +111,14 @@ pub fn write_wav(path: &Path, audio: &AudioSamples) -> Result<()> {
|
||||
for &sample in audio.samples() {
|
||||
let clamped = sample.clamp(-1.0, 1.0);
|
||||
let int_sample = (clamped * i16::MAX as f32) as i16;
|
||||
writer
|
||||
.write_sample(int_sample)
|
||||
.map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV write failed: {e}"))))?;
|
||||
writer.write_sample(int_sample).map_err(|e| {
|
||||
MagnotiaError::Io(std::io::Error::other(format!("WAV write failed: {e}")))
|
||||
})?;
|
||||
}
|
||||
|
||||
writer
|
||||
.finalize()
|
||||
.map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV finalize failed: {e}"))))?;
|
||||
writer.finalize().map_err(|e| {
|
||||
MagnotiaError::Io(std::io::Error::other(format!("WAV finalize failed: {e}")))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -91,7 +91,10 @@ fn resolve_app_data_dir() -> PathBuf {
|
||||
return PathBuf::from(xdg).join("magnotia");
|
||||
}
|
||||
}
|
||||
PathBuf::from(home).join(".local").join("share").join("magnotia")
|
||||
PathBuf::from(home)
|
||||
.join(".local")
|
||||
.join("share")
|
||||
.join("magnotia")
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
@@ -112,7 +115,10 @@ mod tests {
|
||||
let paths = AppPaths {
|
||||
app_data_dir: PathBuf::from("/tmp/magnotia-test"),
|
||||
};
|
||||
assert_eq!(paths.database_path(), PathBuf::from("/tmp/magnotia-test/magnotia.db"));
|
||||
assert_eq!(
|
||||
paths.database_path(),
|
||||
PathBuf::from("/tmp/magnotia-test/magnotia.db")
|
||||
);
|
||||
assert_eq!(
|
||||
paths.speech_model_dir(&ModelId::new("whisper-base-en")),
|
||||
PathBuf::from("/tmp/magnotia-test/models/whisper-base-en")
|
||||
|
||||
@@ -162,7 +162,9 @@ static TEST_OVERRIDE: Mutex<Option<PowerState>> = Mutex::new(None);
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_get_override() -> Option<PowerState> {
|
||||
*TEST_OVERRIDE.lock().expect("power test override mutex poisoned")
|
||||
*TEST_OVERRIDE
|
||||
.lock()
|
||||
.expect("power test override mutex poisoned")
|
||||
}
|
||||
|
||||
/// Drop-guard that resets `TEST_OVERRIDE` to `None` on drop (including on unwind).
|
||||
@@ -174,7 +176,9 @@ struct OverrideGuard;
|
||||
#[cfg(test)]
|
||||
impl Drop for OverrideGuard {
|
||||
fn drop(&mut self) {
|
||||
*TEST_OVERRIDE.lock().expect("power test override mutex poisoned") = None;
|
||||
*TEST_OVERRIDE
|
||||
.lock()
|
||||
.expect("power test override mutex poisoned") = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +192,9 @@ impl Drop for OverrideGuard {
|
||||
pub(crate) fn with_override<R>(state: Option<PowerState>, body: impl FnOnce() -> R) -> R {
|
||||
static TEST_LOCK: Mutex<()> = Mutex::new(());
|
||||
let _lock = TEST_LOCK.lock().expect("power TEST_LOCK poisoned");
|
||||
*TEST_OVERRIDE.lock().expect("power test override mutex poisoned") = state;
|
||||
*TEST_OVERRIDE
|
||||
.lock()
|
||||
.expect("power test override mutex poisoned") = state;
|
||||
let _guard = OverrideGuard;
|
||||
body()
|
||||
}
|
||||
@@ -226,7 +232,10 @@ mod tests {
|
||||
let dir = tempdir().unwrap();
|
||||
write_supply(dir.path(), "AC", "Mains", "0");
|
||||
write_supply(dir.path(), "BAT0", "Battery", "0");
|
||||
assert_eq!(parse_power_state_from_dir(dir.path()), PowerState::OnBattery);
|
||||
assert_eq!(
|
||||
parse_power_state_from_dir(dir.path()),
|
||||
PowerState::OnBattery
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -39,8 +39,7 @@ impl ProcessLister {
|
||||
/// Refresh the process table in place and return the current
|
||||
/// lowercased executable names.
|
||||
pub fn snapshot(&mut self) -> Vec<String> {
|
||||
self.system
|
||||
.refresh_processes(ProcessesToUpdate::All, true);
|
||||
self.system.refresh_processes(ProcessesToUpdate::All, true);
|
||||
self.system
|
||||
.processes()
|
||||
.values()
|
||||
|
||||
@@ -113,7 +113,9 @@ mod tests {
|
||||
fn with_thread_env_lock<R>(body: impl FnOnce() -> R) -> R {
|
||||
use std::sync::Mutex;
|
||||
static THREAD_ENV_LOCK: Mutex<()> = Mutex::new(());
|
||||
let _lock = THREAD_ENV_LOCK.lock().expect("tuning thread-env lock poisoned");
|
||||
let _lock = THREAD_ENV_LOCK
|
||||
.lock()
|
||||
.expect("tuning thread-env lock poisoned");
|
||||
body()
|
||||
}
|
||||
|
||||
@@ -126,8 +128,10 @@ mod tests {
|
||||
with_thread_env_lock(|| {
|
||||
std::env::remove_var("MAGNOTIA_INFERENCE_THREADS");
|
||||
let n = inference_thread_count(Workload::Llm, false);
|
||||
assert!(n >= MIN_INFERENCE_THREADS && n <= MAX_INFERENCE_THREADS,
|
||||
"got {n}, expected within [{MIN_INFERENCE_THREADS}, {MAX_INFERENCE_THREADS}]");
|
||||
assert!(
|
||||
(MIN_INFERENCE_THREADS..=MAX_INFERENCE_THREADS).contains(&n),
|
||||
"got {n}, expected within [{MIN_INFERENCE_THREADS}, {MAX_INFERENCE_THREADS}]"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,8 +179,10 @@ mod tests {
|
||||
std::env::remove_var("MAGNOTIA_INFERENCE_THREADS");
|
||||
crate::power::with_override(Some(PowerState::OnAc), || {
|
||||
let n = inference_thread_count(Workload::Llm, true);
|
||||
assert!(n <= GPU_FLOOR_LLM.max(MIN_INFERENCE_THREADS),
|
||||
"Llm with GPU offload should clamp to {GPU_FLOOR_LLM}, got {n}");
|
||||
assert!(
|
||||
n <= GPU_FLOOR_LLM.max(MIN_INFERENCE_THREADS),
|
||||
"Llm with GPU offload should clamp to {GPU_FLOOR_LLM}, got {n}"
|
||||
);
|
||||
assert!(n >= MIN_INFERENCE_THREADS);
|
||||
});
|
||||
});
|
||||
@@ -196,11 +202,12 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
const _: () = assert!(GPU_FLOOR_WHISPER >= GPU_FLOOR_LLM);
|
||||
|
||||
#[test]
|
||||
fn whisper_gpu_floor_is_at_least_llm_gpu_floor() {
|
||||
// Architectural invariant: whisper retains CPU work even when
|
||||
// GPU-offloaded; its floor must not be lower than the LLM's.
|
||||
assert!(GPU_FLOOR_WHISPER >= GPU_FLOOR_LLM);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -172,8 +172,8 @@ impl LlmEngine {
|
||||
// follow-up in docs/superpowers/specs/2026-05-09-battery-gpu-aware-
|
||||
// thread-tuning-design.md (§ Out of scope).
|
||||
let gpu_offloaded = use_gpu && gpu_layers >= model.n_layer();
|
||||
let thread_count = i32::try_from(inference_thread_count(Workload::Llm, gpu_offloaded))
|
||||
.unwrap_or(4);
|
||||
let thread_count =
|
||||
i32::try_from(inference_thread_count(Workload::Llm, gpu_offloaded)).unwrap_or(4);
|
||||
let ctx_params = LlamaContextParams::default()
|
||||
.with_n_ctx(Some(
|
||||
NonZeroU32::new(n_ctx).expect("n_ctx must be non-zero"),
|
||||
|
||||
@@ -182,7 +182,9 @@ pub async fn update_transcript(
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| MagnotiaError::StorageError(format!("Update transcript failed: {e}")))?;
|
||||
.map_err(|e| {
|
||||
MagnotiaError::StorageError(format!("Update transcript failed: {e}"))
|
||||
})?;
|
||||
Ok(res.rows_affected())
|
||||
}
|
||||
(Some(t), None) => {
|
||||
@@ -191,7 +193,9 @@ pub async fn update_transcript(
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| MagnotiaError::StorageError(format!("Update transcript failed: {e}")))?;
|
||||
.map_err(|e| {
|
||||
MagnotiaError::StorageError(format!("Update transcript failed: {e}"))
|
||||
})?;
|
||||
Ok(res.rows_affected())
|
||||
}
|
||||
(None, Some(ttl)) => {
|
||||
@@ -200,7 +204,9 @@ pub async fn update_transcript(
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| MagnotiaError::StorageError(format!("Update transcript failed: {e}")))?;
|
||||
.map_err(|e| {
|
||||
MagnotiaError::StorageError(format!("Update transcript failed: {e}"))
|
||||
})?;
|
||||
Ok(res.rows_affected())
|
||||
}
|
||||
(None, None) => Ok(0),
|
||||
@@ -484,7 +490,9 @@ pub async fn complete_subtask_and_check_parent(pool: &SqlitePool, subtask_id: &s
|
||||
.bind(&pid)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| MagnotiaError::StorageError(format!("Auto-complete parent failed: {e}")))?;
|
||||
.map_err(|e| {
|
||||
MagnotiaError::StorageError(format!("Auto-complete parent failed: {e}"))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -708,7 +716,9 @@ pub async fn set_implementation_rule_enabled(
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| MagnotiaError::StorageError(format!("Set implementation rule enabled failed: {e}")))?;
|
||||
.map_err(|e| {
|
||||
MagnotiaError::StorageError(format!("Set implementation rule enabled failed: {e}"))
|
||||
})?;
|
||||
|
||||
get_implementation_rule(pool, id).await?.ok_or_else(|| {
|
||||
MagnotiaError::StorageError(format!(
|
||||
@@ -731,7 +741,9 @@ pub async fn mark_implementation_rule_fired(
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| MagnotiaError::StorageError(format!("Mark implementation rule fired failed: {e}")))?;
|
||||
.map_err(|e| {
|
||||
MagnotiaError::StorageError(format!("Mark implementation rule fired failed: {e}"))
|
||||
})?;
|
||||
|
||||
get_implementation_rule(pool, id).await?.ok_or_else(|| {
|
||||
MagnotiaError::StorageError(format!(
|
||||
@@ -745,7 +757,9 @@ pub async fn delete_implementation_rule(pool: &SqlitePool, id: &str) -> Result<(
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| MagnotiaError::StorageError(format!("Delete implementation rule failed: {e}")))?;
|
||||
.map_err(|e| {
|
||||
MagnotiaError::StorageError(format!("Delete implementation rule failed: {e}"))
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2511,24 +2525,21 @@ mod tests {
|
||||
let removed = prune_error_log(&pool, 90).await.unwrap();
|
||||
assert_eq!(removed, 1, "only the 200-day row should be pruned");
|
||||
|
||||
let remaining: Vec<String> = sqlx::query_scalar(
|
||||
"SELECT context FROM error_log ORDER BY id ASC",
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let remaining: Vec<String> =
|
||||
sqlx::query_scalar("SELECT context FROM error_log ORDER BY id ASC")
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(remaining, vec!["recent".to_string(), "older".to_string()]);
|
||||
|
||||
// A 14-day window prunes the 30-day row too.
|
||||
let removed = prune_error_log(&pool, 14).await.unwrap();
|
||||
assert_eq!(removed, 1);
|
||||
|
||||
let remaining: Vec<String> = sqlx::query_scalar(
|
||||
"SELECT context FROM error_log",
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let remaining: Vec<String> = sqlx::query_scalar("SELECT context FROM error_log")
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(remaining, vec!["recent".to_string()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ pub use database::{
|
||||
list_profiles, list_recent_completions, list_recent_errors, list_subtasks, list_tasks,
|
||||
list_transcripts, list_transcripts_paged, log_error, mark_implementation_rule_fired,
|
||||
prune_error_log, record_feedback, search_transcripts, set_implementation_rule_enabled,
|
||||
set_setting,
|
||||
set_task_energy, uncomplete_task, update_profile, update_task, update_transcript,
|
||||
set_setting, set_task_energy, uncomplete_task, update_profile, update_task, update_transcript,
|
||||
update_transcript_meta, DailyCompletionCount, ErrorLogRow, FeedbackRow, FeedbackTargetType,
|
||||
ImplementationRuleRow, InsertTranscriptParams, ProfileRow, ProfileTermRow,
|
||||
RecordFeedbackParams, TaskRow, TranscriptRow,
|
||||
|
||||
@@ -553,7 +553,9 @@ async fn run_migrations_slice(pool: &SqlitePool, migrations: &[(i64, &str, &str)
|
||||
)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| MagnotiaError::StorageError(format!("Schema version table creation failed: {e}")))?;
|
||||
.map_err(|e| {
|
||||
MagnotiaError::StorageError(format!("Schema version table creation failed: {e}"))
|
||||
})?;
|
||||
|
||||
let current: i64 = sqlx::query_scalar("SELECT COALESCE(MAX(version), 0) FROM schema_version")
|
||||
.fetch_one(pool)
|
||||
@@ -1178,7 +1180,8 @@ mod tests {
|
||||
.map(|r| r.get::<String, _>("detail"))
|
||||
.collect();
|
||||
assert!(
|
||||
plan.iter().any(|row| row.contains("idx_transcripts_profile_created")),
|
||||
plan.iter()
|
||||
.any(|row| row.contains("idx_transcripts_profile_created")),
|
||||
"planner should use the composite index, got plan: {plan:?}",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -116,7 +116,10 @@ fn verified_manifest_path(dir: &Path) -> PathBuf {
|
||||
dir.join(".magnotia-verified")
|
||||
}
|
||||
|
||||
fn verified_manifest_matches(entry: &magnotia_core::model_registry::ModelEntry, dir: &Path) -> bool {
|
||||
fn verified_manifest_matches(
|
||||
entry: &magnotia_core::model_registry::ModelEntry,
|
||||
dir: &Path,
|
||||
) -> bool {
|
||||
let manifest = match std::fs::read_to_string(verified_manifest_path(dir)) {
|
||||
Ok(contents) => contents,
|
||||
Err(_) => return false,
|
||||
|
||||
@@ -70,9 +70,7 @@ impl TranscriptionProvider for LocalProviderAdapter {
|
||||
.map(|c| c.sample_rate)
|
||||
.unwrap_or(magnotia_core::constants::WHISPER_SAMPLE_RATE),
|
||||
channels: local.map(|c| c.channels).unwrap_or(1),
|
||||
initial_prompt_supported: local
|
||||
.map(|c| c.supports_initial_prompt)
|
||||
.unwrap_or(false),
|
||||
initial_prompt_supported: local.map(|c| c.supports_initial_prompt).unwrap_or(false),
|
||||
language_hint_supported: true,
|
||||
streaming_supported: false,
|
||||
cost_class: CostClass::Free,
|
||||
|
||||
@@ -65,7 +65,9 @@ impl Transcriber for WhisperRsBackend {
|
||||
);
|
||||
|
||||
let mut state = self.ctx.create_state().map_err(|e| {
|
||||
MagnotiaError::TranscriptionFailed(WhisperBackendError::State(e.to_string()).to_string())
|
||||
MagnotiaError::TranscriptionFailed(
|
||||
WhisperBackendError::State(e.to_string()).to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut params = FullParams::new(SamplingStrategy::Greedy { best_of: 1 });
|
||||
|
||||
@@ -30,7 +30,10 @@ fn jfk_transcription_benchmark() {
|
||||
let sample_rate = u32::from_le_bytes(bytes[24..28].try_into().unwrap());
|
||||
let channels = u16::from_le_bytes(bytes[22..24].try_into().unwrap());
|
||||
let bits = u16::from_le_bytes(bytes[34..36].try_into().unwrap());
|
||||
eprintln!("[bench] wav spec: {} Hz, {} ch, {}-bit", sample_rate, channels, bits);
|
||||
eprintln!(
|
||||
"[bench] wav spec: {} Hz, {} ch, {}-bit",
|
||||
sample_rate, channels, bits
|
||||
);
|
||||
assert_eq!(sample_rate, 16_000, "expected 16 kHz wav");
|
||||
assert_eq!(channels, 1, "expected mono");
|
||||
assert_eq!(bits, 16, "expected 16-bit PCM");
|
||||
@@ -48,7 +51,10 @@ fn jfk_transcription_benchmark() {
|
||||
);
|
||||
|
||||
let rss_before_load_kb = read_rss_kb();
|
||||
eprintln!("[bench] RSS before model load: {} MB", rss_before_load_kb / 1024);
|
||||
eprintln!(
|
||||
"[bench] RSS before model load: {} MB",
|
||||
rss_before_load_kb / 1024
|
||||
);
|
||||
|
||||
let load_start = Instant::now();
|
||||
let ctx = WhisperContext::new_with_params(&model_path, WhisperContextParameters::default())
|
||||
@@ -57,9 +63,11 @@ fn jfk_transcription_benchmark() {
|
||||
eprintln!("[bench] model load: {:.2}s", load_dur.as_secs_f64());
|
||||
|
||||
let rss_after_load_kb = read_rss_kb();
|
||||
eprintln!("[bench] RSS after model load: {} MB (delta +{} MB)",
|
||||
eprintln!(
|
||||
"[bench] RSS after model load: {} MB (delta +{} MB)",
|
||||
rss_after_load_kb / 1024,
|
||||
(rss_after_load_kb.saturating_sub(rss_before_load_kb)) / 1024);
|
||||
(rss_after_load_kb.saturating_sub(rss_before_load_kb)) / 1024
|
||||
);
|
||||
|
||||
let mut state = ctx.create_state().expect("whisper state");
|
||||
let mut params = FullParams::new(SamplingStrategy::Greedy { best_of: 1 });
|
||||
@@ -110,12 +118,20 @@ fn jfk_transcription_benchmark() {
|
||||
let rss_final_kb = read_rss_kb();
|
||||
eprintln!("[bench] RSS final: {} MB", rss_final_kb / 1024);
|
||||
|
||||
eprintln!("");
|
||||
eprintln!();
|
||||
eprintln!("=== SUMMARY ===");
|
||||
eprintln!("audio: {:.2}s", audio_secs);
|
||||
eprintln!("model_load: {:.2}s", load_dur.as_secs_f64());
|
||||
eprintln!("cold xc: {:.2}s RTF={:.3}", cold_dur.as_secs_f64(), cold_dur.as_secs_f64() / audio_secs);
|
||||
eprintln!("warm xc: {:.2}s RTF={:.3}", warm_dur.as_secs_f64(), warm_dur.as_secs_f64() / audio_secs);
|
||||
eprintln!(
|
||||
"cold xc: {:.2}s RTF={:.3}",
|
||||
cold_dur.as_secs_f64(),
|
||||
cold_dur.as_secs_f64() / audio_secs
|
||||
);
|
||||
eprintln!(
|
||||
"warm xc: {:.2}s RTF={:.3}",
|
||||
warm_dur.as_secs_f64(),
|
||||
warm_dur.as_secs_f64() / audio_secs
|
||||
);
|
||||
eprintln!("RSS peak: {} MB", rss_final_kb / 1024);
|
||||
}
|
||||
|
||||
@@ -124,7 +140,9 @@ fn read_rss_kb() -> u64 {
|
||||
let s = std::fs::read_to_string(format!("/proc/{pid}/status")).unwrap_or_default();
|
||||
for line in s.lines() {
|
||||
if let Some(rest) = line.strip_prefix("VmRSS:") {
|
||||
return rest.trim().split_whitespace().next()
|
||||
return rest
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.and_then(|n| n.parse::<u64>().ok())
|
||||
.unwrap_or(0);
|
||||
}
|
||||
|
||||
@@ -82,14 +82,7 @@ fn whisper_thread_count_sweep() {
|
||||
for (label, power, gpu_offloaded_for_helper) in panels {
|
||||
env::set_var("MAGNOTIA_POWER_STATE_OVERRIDE", power);
|
||||
let helper_pick = inference_thread_count(Workload::Whisper, gpu_offloaded_for_helper);
|
||||
run_sweep_panel(
|
||||
label,
|
||||
helper_pick,
|
||||
&ctx,
|
||||
&samples,
|
||||
audio_secs,
|
||||
&targets,
|
||||
);
|
||||
run_sweep_panel(label, helper_pick, &ctx, &samples, audio_secs, &targets);
|
||||
}
|
||||
env::remove_var("MAGNOTIA_POWER_STATE_OVERRIDE");
|
||||
}
|
||||
@@ -102,10 +95,8 @@ fn run_sweep_panel(
|
||||
audio_secs: f64,
|
||||
targets: &[i32],
|
||||
) {
|
||||
eprintln!("");
|
||||
eprintln!(
|
||||
"=== n_threads scaling: {label} (helper picks: {helper_pick}) ==="
|
||||
);
|
||||
eprintln!();
|
||||
eprintln!("=== n_threads scaling: {label} (helper picks: {helper_pick}) ===");
|
||||
eprintln!("n_threads | xc_time | RTF | speedup_vs_1");
|
||||
eprintln!("----------|---------|--------|-------------");
|
||||
let mut baseline_dur: Option<f64> = None;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
name: Phase 10a stabilization gate after post-handover commits
|
||||
description: Current-head stabilization checkpoint before resuming Phase 10a QC/dogfood. Covers the two commits ahead of origin/main, verification results, and scope decision for dormant Phase A orchestrator work.
|
||||
type: audit
|
||||
tags: [phase10a, stabilization, release, orchestrator, verification]
|
||||
created: 2026/05/10
|
||||
status: green-with-manual-qc-remaining
|
||||
author: Hermes Agent on behalf of Jake Sames
|
||||
---
|
||||
|
||||
# Phase 10a — Stabilization Gate
|
||||
|
||||
## Scope
|
||||
|
||||
This checkpoint updates the 2026/04/25 handover and static Phase 10a audit for the current local `main`, which is two commits ahead of `origin/main`.
|
||||
|
||||
Current local-only commits inspected:
|
||||
|
||||
- `c95a5da` — `docs(roadmap): bookmark SIE + adjacent embeddings/rerank/extract servers for PKM phase`
|
||||
- `6bc8acc` — `feat(transcription): Phase A — engine protocol + registry + orchestrator (clean-room)`
|
||||
|
||||
## Findings
|
||||
|
||||
### PKM roadmap bookmark
|
||||
|
||||
Low release risk. The SIE / embeddings / rerank / extraction note is documentation-only and explicitly marked deferred. It should not affect v0.1 runtime or QA scope.
|
||||
|
||||
### Phase A transcription orchestrator
|
||||
|
||||
Medium architectural scope, low immediate runtime risk.
|
||||
|
||||
The commit adds:
|
||||
|
||||
- `crates/cloud-providers/src/provider.rs`
|
||||
- `crates/transcription/src/registry.rs`
|
||||
- `crates/transcription/src/orchestrator.rs`
|
||||
- wiring exports/dependencies plus `KNOWN-ISSUES.md` KI-06
|
||||
|
||||
The implementation is deliberately dormant for current app paths. Existing `transcribe_file`, live transcription, and meeting flows still call the existing local engine path directly. KI-06 documents the required follow-up: build the registry at app boot, inject `Arc<Orchestrator>` into `AppState`, and migrate command call sites through the orchestrator with mock-provider coverage.
|
||||
|
||||
Decision for v0.1 release flow: keep the Phase A commit only if the dormant boundary remains green; do not expand scope into Phase A.1 before Phase 10a unless a direct release blocker requires it.
|
||||
|
||||
## Verification run
|
||||
|
||||
Command run from repo root:
|
||||
|
||||
```bash
|
||||
cargo fmt --check && \
|
||||
cargo clippy --workspace --all-targets -- -D warnings && \
|
||||
cargo test --workspace && \
|
||||
npm run check && \
|
||||
npm run build
|
||||
```
|
||||
|
||||
Final result: PASS.
|
||||
|
||||
Notes:
|
||||
|
||||
- Initial run failed `cargo fmt --check`; `cargo fmt` was applied.
|
||||
- Current Rust/Clippy toolchain also surfaced a few warning-as-error issues unrelated to the new orchestrator surface:
|
||||
- `manual_range_contains` in `crates/core/src/tuning.rs`
|
||||
- `assertions_on_constants` in `crates/core/src/tuning.rs`
|
||||
- `println_empty_string` in transcription benchmark tests
|
||||
- `trim_split_whitespace` in transcription benchmark helper
|
||||
- Those were fixed minimally to restore `-D warnings` cleanliness.
|
||||
|
||||
## Files changed by stabilization
|
||||
|
||||
Rust formatting touched multiple existing files. Semantic clippy fixes were limited to:
|
||||
|
||||
- `crates/core/src/tuning.rs`
|
||||
- `crates/transcription/tests/jfk_bench.rs`
|
||||
- `crates/transcription/tests/thread_sweep.rs`
|
||||
|
||||
This audit file records why those changes exist.
|
||||
|
||||
## Recommendation
|
||||
|
||||
Resume the original Phase 10a plan now, with this added gate satisfied:
|
||||
|
||||
1. Current HEAD is green.
|
||||
2. Post-handover commits are inspected.
|
||||
3. Phase A orchestrator work is classified as dormant / post-v0.1 follow-up, not a reason to expand release scope.
|
||||
|
||||
Next work remains the manual/runtime Phase 10a checklist:
|
||||
|
||||
- Dogfood walkthrough from `HANDOVER.md`
|
||||
- Runtime keyboard / focus / contrast / reduced-motion checks
|
||||
- RB-08 macOS power-assertion verification by Rachmann
|
||||
- Clean-install test once release artefacts are available
|
||||
- Manual GitHub Actions build workflow before tagging v0.1.0
|
||||
@@ -59,4 +59,3 @@ pub async fn record_feedback(
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
|
||||
@@ -1677,7 +1677,10 @@ mod tests {
|
||||
&message,
|
||||
);
|
||||
|
||||
assert!(!delivered, "send must report not delivered when result_channel errors");
|
||||
assert!(
|
||||
!delivered,
|
||||
"send must report not delivered when result_channel errors"
|
||||
);
|
||||
assert!(result_listener_lost, "result_listener_lost must be set");
|
||||
assert!(
|
||||
stop_flag.load(Ordering::Relaxed),
|
||||
|
||||
@@ -319,7 +319,6 @@ pub struct LanguageSupportInfo {
|
||||
pub language_count: u16,
|
||||
}
|
||||
|
||||
|
||||
/// Compile-time target signalling used by `compose_accelerators`.
|
||||
/// Split out so the pure-function behaviour is testable without `cfg!`
|
||||
/// appearing in the test matrix.
|
||||
|
||||
@@ -249,4 +249,3 @@ pub async fn transcribe_file(
|
||||
"raw_text": raw_text,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,3 @@ pub async fn check_for_update(window: tauri::WebviewWindow) -> Result<Option<Str
|
||||
ensure_main_window(&window)?;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,7 @@ use tauri::Manager;
|
||||
|
||||
use magnotia_core::types::EngineName;
|
||||
use magnotia_llm::LlmEngine;
|
||||
use magnotia_storage::{
|
||||
database_path, get_setting, init as init_db, prune_error_log, set_setting,
|
||||
};
|
||||
use magnotia_storage::{database_path, get_setting, init as init_db, prune_error_log, set_setting};
|
||||
|
||||
/// How long to retain `error_log` rows. Pruned once on startup.
|
||||
/// 90 days is long enough to triage "this happened a few weeks ago"
|
||||
@@ -117,7 +115,11 @@ fn ensure_x11_on_wayland() {
|
||||
// magnotia idle CPU 12.30% → 2.80% of one core and idle GPU 17% → 10%.
|
||||
// Apples to both X11 and Wayland sessions; users can opt back in by
|
||||
// setting WEBKIT_DISABLE_DMABUF_RENDERER=0 explicitly.
|
||||
set_if_unset("WEBKIT_DISABLE_DMABUF_RENDERER", "1", "iGPU idle-cost workaround");
|
||||
set_if_unset(
|
||||
"WEBKIT_DISABLE_DMABUF_RENDERER",
|
||||
"1",
|
||||
"iGPU idle-cost workaround",
|
||||
);
|
||||
|
||||
// If the user is on a Wayland session, also force WebKitGTK + GDK
|
||||
// onto X11 via XWayland. Idempotent: if a value is already set, keep
|
||||
|
||||
Reference in New Issue
Block a user