ba851680cee73f7899224c3f808779673b075d43
175 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 3770815fbf |
agent: lumotia — v0.1 release-completion run
Closes the code-side v0.1 ship gate. All quality gates green: cargo fmt/clippy/test (~327 tests), npm check (0/0), vitest 13/13, scripts/dogfood-rebrand-drill.sh 8/8. Phase F — first-run onboarding promoted to v0.1 - FirstRunPage with skip-to-main + failure recovery + event recording - Six onboarding commands (record/list/has-completed + lumotia_events) - Storage migration v17 (onboarding_events + lumotia_events tables) UI hardening (in-scope items from v0.1-ui-hardening.md) - StatusPill + PostCaptureCard components, 21st preview entry - Sidebar recording-as-sacred-state (opacity + aria-disabled, reduced-motion) - Settings 6-section regroup + Help section + Activation log + Privacy toggle - Error-state copy sweep (DictationPage + SettingsPage, plain-language) - Global :focus-visible rule, textarea outlines restored - Ctrl+K / Ctrl+, / Escape bindings in +layout LLM resilience - rule_based_extract_tasks (regex-free imperative-verb extractor) + extract_tasks_with_fallback wrapper — task extraction never returns zero - tokio::time::timeout(120s) wraps cleanup/tags/tasks commands Release artefacts - LICENSE (canonical AGPL-3.0), CHANGELOG (Keep-a-Changelog format) - v0.1-release-notes, privacy-and-ai-use, install-warnings, tester-onboarding-kit, tester-acceptance-runbook, code-signing-setup, apple-silicon-rb08-runbook, virtual-audio-setup, v0.1-contrast-audit - Workspace versioning + AGPL spdx; npm exact-pin (10 ranges removed) - AppImage SHA-256 sidecar in build.yml - README v0.1 section + Reporting-issues; canonical repo slug Closure pass — items moved from human-required to code-complete - KI-02 Linux idle inhibit: zbus 5 → org.freedesktop.login1.Manager.Inhibit - KI-03 Windows sleep prevention: SetThreadExecutionState(ES_CONTINUOUS|...) - acquire/release_idle_inhibit Tauri commands, wired in DictationPage - Diagnostic-bundle frontend wire-up (Settings → Help button) - WCAG-AA contrast fix via .btn-filled-text utility (no token changes) - 8 destructive-action sites wrapped in plain-language confirm() guards - KNOWN-ISSUES.md + v0.1-known-limitations.md updated (KI-02/03 fixed) Scripts - pre-tag-verify.sh, tag-day.sh, smoke-linux + driver - parse-diagnostic-bundle.sh, parse-activation-log.py Per-item audit trail: docs/release/v0.1-completion-status.md Remaining: W-01…W-08 (signing certs, hardware probes, smoke matrix, tester recruitment) — see docs/release/v0.1-known-limitations.md. |
|||
| 401b6c3654 |
agent: lumotia — Phase B.9 strip Qwen <think>…</think> reasoning before JSON-envelope scan
Phase B.9 audit of commit
|
|||
| 813f024cdb |
agent: lumotia — Phase B.8 bridge storage events into tracing subscriber
Phase B.8 audit of commits |
|||
| f252c1b50e |
agent: lumotia — Phase B.7 close unload-during-load TOCTOU on LlmEngine
Phase B.7 audit of commit
|
|||
| 20ef6c459b |
agent: lumotia — Phase B.4 close restore-during-purge race via atomic DELETE RETURNING
Phase B.4 audit of commits |
|||
| 31e3f5a099 |
agent: lumotia — Phase B.3 unlink .part on ResumeUnsupported so retry can recover
Phase B.3 audit of commit
|
|||
| 643985d2a8 |
agent: lumotia — Phase B.2 fix misleading "force-abort" doc + test name on supervisor shutdown
Phase B.2 audit of commit
|
|||
| 18a64f5c56 |
agent: lumotia — Phase A.3 remove dead migration_sentinel method + fix architecture-map claim
Phase A.3 finding: AppPaths::migration_sentinel was added with intent
during the rebrand-architecture phase but never wired to any caller.
Exhaustive grep across crates/ src-tauri/ src/ docs/ surfaces:
- 1 definition (paths.rs)
- 1 architecture-map description that ASSERTS the method is in use
- 0 production callers
- 0 test references
Both boot-time migrations (migrate_legacy_data_dir +
migrate_tauri_app_data_dir_with_paths) are idempotent by construction:
each re-probes the legacy path via Path::exists() on every boot and
short-circuits on the steady state. A sentinel file would optimise the
probe but is not required for correctness; one syscall per legacy
candidate at startup is negligible.
Per the atomiser principle of removing dead surface area rather than
keeping stale promises:
- Delete AppPaths::migration_sentinel entirely
- Update docs/architecture-map/.../core-paths.md to describe the actual
idempotency model (re-probing) rather than the sentinel pattern that
was never implemented
- Steer future migrations toward storage/src/migrations.rs schema_version
(transactional, survives backup/restore) rather than reintroducing
filesystem sentinels
Verification:
- cargo test -p lumotia-core paths::: 17/17 (no test relied on the method)
- cargo clippy -p lumotia-core --all-targets -- -D warnings: clean
Phase A.4 (stray-magnotia string scan): clean. Every magnotia reference
in the tree is legitimate — migration source paths, documentation, or
test fixtures. The rebrand cascade was thorough.
|
|||
| 43d319fd5a |
agent: lumotia — Phase A.1+A.2 rebrand migration tests + copy_dir_recursive hardening
Phase A of dogfood verification for the Magnotia -> Lumotia rebrand
cascade. The existing in-crate unit tests prove the migration copies
bytes correctly; this commit closes the gaps an atomiser-grade review
would flag.
Phase A.1 — end-to-end integration test (crates/storage/tests/legacy_db_migration.rs):
Seeds a real on-disk magnotia.db via lumotia_storage::init (which runs
every schema migration head-to-tail), inserts a transcript via the
public API, drops the pool, runs migrate_legacy_data_dir_with_pairs,
then re-opens the migrated lumotia.db and asserts the transcript is
queryable. Three scenarios covered:
1. Legacy-only -> migrate -> reopen -> row survives. Also verifies a
non-DB companion file is carried along by the directory rename.
2. Idempotency: first boot migrates, user writes new data, second
boot is a no-op and BOTH rows survive.
3. Both-paths-present: refuses to merge, target's empty DB is
preserved, legacy retained on disk as a backup.
Wires the test surface by renaming the previously-private
migrate_legacy_data_dir_inner to pub migrate_legacy_data_dir_with_pairs
(mirroring migrate_tauri_app_data_dir_with_paths in the sibling
tauri_app_data_migration module).
Phase A.2a — copy_dir_recursive hardening (crates/core/src/paths.rs):
Pre-existing footgun: the fall-through branch called std::fs::copy()
on any DirEntry that was not a symlink or a directory. On Unix that
includes FIFOs, sockets, and char/block device nodes. Opening a FIFO
for read with no writer attached blocks forever — a stale debug FIFO
in the user's ~/.magnotia tree would silently hang first launch.
The branch now explicitly distinguishes is_file() (real regular file
-> copy) from anything else (-> Err with ErrorKind::Unsupported,
naming the offending path). Migration becomes re-runnable once the
user cleans up the offending node. Same-filesystem rename via
std::fs::rename is atomic and unaffected; only the EXDEV fallback path
touches the new guard.
Phase A.2b — three adversarial probes (crates/core/src/paths.rs tests):
- FIFO inside the legacy tree: copy_dir_recursive must return an
Unsupported error WITHOUT hanging. Test bounded by a 5s wall clock
+ a worker thread so a regression to the old fall-through would
surface as a panic, not a stalled CI job.
- Unreadable file (mode 0000): copy_dir_recursive must surface
PermissionDenied, not silently skip. Skips its core assertion under
euid 0 (root bypasses DAC permissions, would mask the regression).
- Dangling symlink (target nonexistent): symlink is recreated at
destination with link target preserved verbatim; the migration
does NOT try to dereference and does NOT abort the rest of the copy.
Verification:
- cargo fmt --check: clean
- cargo clippy --workspace --all-targets -- -D warnings: clean
- cargo test --workspace: 409 passed, 0 failed (up from 405 pre-commit;
3 storage integration tests + 3 paths adversarial + 1 net carry-over)
|
|||
| 27661c816e |
agent: lumotia — pin rust toolchain + workspace clippy/fmt sweep
rust-toolchain.toml pins to stable 1.94.1 so contributors and CI runners share the exact rustc / rustfmt / clippy versions. Without the pin, every machine surfaces a different lint set depending on its local install — six pre-existing lints showed up on 1.94.1 that 1.93-era HANDOVER reported clean. Clippy fixes (all pre-existing, not introduced by feature work): - crates/storage/src/database.rs: std::iter::repeat().take() -> repeat_n() - crates/llm/src/lib.rs (docs): "+ frontends" was parsed as a markdown bullet continuation by rustdoc, breaking doc-lazy-continuation. Reworded to "and". - crates/llm/src/lib.rs (loop): while-let-on-iterator -> for-loop. - src-tauri/src/commands/security.rs: .iter().any(|a| *a == x) -> .contains(&x). - src-tauri/src/lib.rs: io::Error::new(Other, e) -> io::Error::other(e). - src-tauri/src/tauri_app_data_migration.rs: drop function-tail `return`s inside cfg blocks; each platform's block now ends with a tail expression. cargo fmt sweep across the workspace. Mechanical layout-only changes; no semantics affected. Workspace gates after this commit: - cargo fmt --check: clean - cargo clippy --workspace --all-targets -- -D warnings: clean - cargo test --workspace: 405/0 (will become 409/0 with Phase A.1+A.2) |
|||
| 65abfa2ed9 |
agent: code-atomiser-fix — span propagation across live + model-load spawns (Obs-3)
Before this commit `grep -rIn '#\[instrument\|.instrument(\|in_current_span()'`
returned zero matches across the entire workspace. Every tokio::spawn
and thread::spawn lost its parent span, so structured fields recorded
at the call site (session_id, chunk_id, model_id) did not propagate to
log lines emitted inside the spawn. During concurrent-session incidents
the operator could not correlate a runaway log line back to the request
that started it.
Targeted four highest-value join points:
* src-tauri/src/commands/live.rs::run_live_session
#[tracing::instrument(skip_all, fields(session_id, engine, language))]
Attaches the span to the spawn_blocking worker so every per-chunk
warning carries the session id that owns it.
* src-tauri/src/commands/live.rs::maybe_dispatch_chunk
Manual span attach pattern (#[instrument] can't decorate a closure):
capture the parent span before thread::spawn, .enter() it on the new
OS thread, then open an "inference" child span with chunk_id +
duration_secs. Without this, whisper backend warnings appear
unparented and a runaway chunk can't be traced back to its session.
* src-tauri/src/commands/models.rs::ensure_model_loaded
#[instrument(skip_all, fields(model_id, engine, concurrent))]
Multi-second load + sequential-GPU guard logs now carry the model
in flight as a structured field.
* crates/llm/src/lib.rs::load_model
#[instrument(skip_all, fields(model_id, use_gpu))]
Same rationale for LLM loads. Tags llama-backend init lines and
GPU sequential-guard events with the model identifier.
Storage/audio/hotkey/MCP crates left uninstrumented in this commit —
future sweep. The four sites above are the canonical concurrent-load
correlation points; everything else fans out from them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| cde985d0c1 |
agent: code-atomiser-fix — narrow LlmEngine critical section + drop old model first (Race-3, Lifecycle-1)
Race-3 (conf 86): `LlmEngine::load_model` previously held the inner
`std::sync::Mutex` for the entire `LlamaBackend::init` +
`LlamaModel::load_from_file` call (5-15 s on a cold load). `is_loaded()`,
`loaded_model()`, and `loaded_model_id()` all take that same mutex and
are called from sync Tauri handlers (`get_llm_status`, `check_llm_model`,
`delete_llm_model`, `test_llm_model`) WITHOUT `spawn_blocking`. During a
first-run load, parallel `refreshLlmStatus()` polls from the frontend
parked tokio worker threads on the std-mutex; a handful of concurrent
status polls was enough to deadlock the default `num_cpus`-sized Tauri
runtime.
Lifecycle-1 (conf 80): same function held the OLD `Arc<LlamaModel>` in
`guard.model` during the new `load_from_file` call, so a model swap
peaked at ~2x VRAM. A 27B Q4 (~17 GB) swap OOMed a 24 GB card even
though either model fit alone.
Fix: redesign `load_model` so the slow llama-cpp work happens OUTSIDE
the mutex.
1. Short crit section: compare against currently-loaded triple —
return Ok on match (no-op fast path).
2. CAS a new `loading: AtomicBool` from false → true. If a load is
already in flight, refuse with the new `EngineError::AlreadyLoading`
rather than starting a parallel one. A `LoadingGuard` RAII drop
clears the flag on every exit path including panic.
3. Short crit section: drop the OLD model Arc (releasing VRAM via
`llama_free_model`) BEFORE the new load begins — fixes Lifecycle-1.
4. Heavy `LlamaBackend::init` + `LlamaModel::load_from_file` run
OUTSIDE the mutex.
5. Short crit section: install the new state.
`is_loaded()`, `loaded_model()`, `loaded_model_id()` now only contend
on the brief state-mutation sections, not the multi-second file load.
A new `is_loading()` accessor exposes the in-flight state for callers
that need to distinguish "loading" from "not loaded".
Backend lifecycle: `LlamaBackend::init` is process-singleton —
llama-cpp-2 enforces this via `LLAMA_BACKEND_INITIALIZED: AtomicBool`
and returns `BackendAlreadyInitialized` on a second call. The Drop impl
DOES call `llama_backend_free` and flips the flag back, so re-init
would technically work, but we keep the backend Arc resident across
loads/unloads to avoid init/free churn. The Lifecycle-1 fix drops only
the model Arc, NOT the backend Arc — dropping the backend mid-swap
would still work (the new load would re-init), but the comment trail
documents the singleton contract for the next reader.
`is_loaded()` now reports false briefly during a swap window (model
dropped, new model not yet installed). Documented in the doc comment.
Callers wanting "model X is loaded" must check `loaded_model_id()`
against their target, not just `is_loaded()`.
Regression tests added in `crates/llm/src/lib.rs::tests`:
- `is_loaded_does_not_block_on_slow_load`: holds the lock-discipline
open via a Barrier and asserts `is_loaded()` / `loaded_model_id()`
return in ≤50 ms while the slow section is mid-flight. Verified
that a pre-fix structure (`std::sync::Mutex` held across a 500 ms
sleep) makes the probe block ~450 ms; the new structure makes it
return in microseconds.
- `second_concurrent_load_is_refused`: two concurrent
`__test_run_with_lock_discipline` calls; the second gets
`EngineError::AlreadyLoading` and never reaches its op closure.
Doubles as the TOCTOU guard for Race-4/5 at the engine layer.
A `pub(crate) #[cfg(test)] __test_run_with_lock_discipline` helper
exposes the locking skeleton (loading flag + drop-old-model + slow op
outside lock + install) without requiring a real GGUF on disk; this is
the harness the regression tests use.
Verification: cargo test --workspace + npm run check both green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| e0e9a6e17a |
agent: code-atomiser-fix — require Transcriber::transcribe_sync_with_abort (Lifecycle-2)
The trait's default implementation of transcribe_sync_with_abort fell
through to plain transcribe_sync, silently dropping the abort flag for
any backend that did not explicitly override it. That re-introduced the
|
|||
| 15b74db747 |
agent: code-atomiser-fix — soft-delete transcripts with audio cleanup (Rev-2, Rev-3)
Two interlocking reversibility kills, fixed as one bundle:
Rev-2 (hard-DELETE transcripts, no trash) — delete_transcript previously
issued DELETE FROM transcripts WHERE id = ?, so a single click on the
History "Clear All" / "Confirm" button erased months of dictation with no
trash, no export, no undo. The new contract:
* delete_transcript UPDATEs deleted_at = datetime('now') and best-effort
removes the audio file. Idempotent on repeat call.
* Migration v16 adds `deleted_at TEXT` plus a partial index over the trash
rows so the purge query stays cheap on long-running databases.
* get_transcript, list_transcripts_paged, count_transcripts, and
search_transcripts all filter `deleted_at IS NULL` so trash rows are
invisible to the regular history view but kept for restore.
* New list_trashed_transcripts and restore_transcript power the inverse
trash view; row order is most-recently-deleted first.
* New purge_deleted_transcripts(older_than_days) hard-removes trash
older than the retention window. Wired into the Tauri setup hook at
30-day retention; best-effort, never blocks startup.
Rev-3 (orphan WAV files on transcript delete) — same delete_transcript
function. Audio file at audio_path is now best-effort removed when the
soft-delete actually flips a row; NotFound is the expected case for
repeat deletes and is not logged. purge_deleted_transcripts also retries
audio removal as belt-and-braces.
Adds 4 regression tests: delete_transcript_soft_deletes,
delete_transcript_removes_audio_file, list_transcripts_excludes_soft_deleted
(also covers restore_transcript), and purge_deleted_transcripts_hard_deletes_old.
Plus migration_v16_adds_deleted_at_column_and_index already in place.
Frontend UI (HistoryPage clearAll type-the-word modal + View trash path)
deferred to a follow-up commit; the backend contract is complete and the
existing arm-confirm flow now soft-deletes instead of hard-deleting, so
the user-data-loss class is closed for the live release path. Rev-4
(SettingsPage deleteProfile routing) also deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 1068ad9c7d |
agent: code-atomiser-fix — hotkey supervisor rearchitecture (Race-1, Race-2, TOCTOU)
Fixes three interlocking concurrency leaks in the evdev hotkey listener flagged by the atomiser full-sweep. Every spawned task is now owned by a SupervisorHandle that broadcasts cooperative shutdown and joins every JoinHandle with a 2s per-task timeout on stop(). Per-device attachment is now insert-before-spawn under one mutex hold, closing the TOCTOU window. The Tauri command layer now stores the forwarder JoinHandle alongside the listener so reconfigures join it cleanly instead of leaking one permanent forwarder per hotkey change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 9f67ab2d86 |
agent: code-atomiser-fix — atomic model download + manifest (Rev-1, Rev-5)
Two reversibility kills in the model-download path both followed the
same pattern: SHA mismatch on an existing file triggered
`remove_file(&dest)` BEFORE the network round-trip. A network blip /
power loss between the unlink and the eventual `rename(.part, dest)`
left users with neither the old (corrupt-but-readable) model nor a
fresh one — 1.5-20 GB redownload from scratch with no fallback.
Rev-1 (crates/llm/src/model_manager.rs):
- Extract the existing-file decision into `download_to`, drop the
pre-emptive unlink. `download_impl` already writes via `.part`
and atomically renames; the rename overwrites on success and
leaves dest untouched on failure.
- Regression test `download_failure_preserves_existing_file` plants
a sentinel "OLD" file at dest, points at a 500-returning server,
and asserts dest still exists with original contents after the
failed download.
Rev-5 (crates/transcription/src/model_manager.rs):
- Drop the pre-emptive unlink in the outer `download()` SHA-mismatch
branch. Same atomic rename via `download_file`.
- Make `write_verified_manifest` atomic: write to `.tmp`, fsync,
rename. Previous direct `fs::write` truncates-then-writes, so a
crash mid-write left an empty/torn manifest and triggered a full
GB-sized redownload on next boot.
- `download_file_failure_preserves_existing_dest_file` and
`manifest_write_is_atomic` regression tests added.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| afbd33d33e |
agent: code-atomiser-fix — test_llm_model respects caller GPU preference (Race-8)
Add an Option<bool> use_gpu parameter to test_llm_model with the same default-true semantics as load_llm_model. Hard-coding true triggered an engine tear-down/rebuild when a parallel load_llm_model(use_gpu=false) was in flight (LlmEngine::load_model triples on id+path+use_gpu) and silently flipped the user's GPU mode underneath the test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 094b533ef2 |
agent: code-atomiser-fix — surface capture-thread drops + bypass validation requeue cap
dropped_chunks was incremented on cpal-callback channel-full and validation requeue overflow but never read by the live session, so the UI's dropped_audio_ms missed callback-level losses entirely. Architecture doc had flagged this as a TODO. Also: the 350ms validation buffer was requeued via try_send into the same 32-slot channel, silently dropping past the cap on small-buffer audio hosts (WASAPI exclusive, low-latency ALSA at 256 frames -> ~65 chunks). Fix: live runtime reads MicrophoneCapture::dropped_chunks() on each recv_audio tick (LiveSessionRuntime::poll_capture_drops) and converts the per-chunk-duration delta into the dropped_audio_ms surfaced to the UI overload status. Per-chunk duration is derived from the most recent AudioChunk's sample_rate + samples-per-channel so it adapts to whatever rate cpal is delivering at. Validation requeue moved from try_send into the bounded channel onto a VecDeque<AudioChunk> returned alongside the Receiver; ActiveCapture drains the replay buffer before reading rx in recv_audio, bypassing the 32-slot cap entirely. Architecture doc updated to remove the TODO and document the new pre-roll path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 5725836f40 |
agent: code-atomiser-fix — cancellable Whisper inference + bounded drain + lock-over-await
Three findings chain: thread::spawn discarded the JoinHandle and gave no cancellation route into the whisper backend; drain_inference busy- polled with no timeout; stop_live_transcription_session held the lifecycle AsyncMutex across the await of a non-cancellable spawn_blocking handle. Together: a single wedged inference (ggml deadlock, GPU stall) bricked every future start/stop until app restart. Fix: each inference task carries an Arc<AtomicBool> abort_flag; the flag is wired into whisper-rs::FullParams::set_abort_callback_safe so the spawned blocking thread checks it and exits cleanly. drain_inference is bounded by a deadline (3 x chunk_duration, min 2s); on expiry the flag is set, the receiver is dropped, and a typed Error::InferenceTimeout status is surfaced. Drop for InferenceTask asserts the abort flag so any '?'-propagation or panic unwind closes the cancellation route without relying on the explicit drain path. stop_live_transcription_session restructured so the lifecycle guard is dropped BEFORE the JoinHandle is awaited; start_live_transcription_session releases the guard explicitly after installing the RunningLiveSession. Unit test added: dropping_inference_task_sets_abort_flag covers the Race-A regression directly. A real Race-B drain-timeout test would require a wedged whisper-rs, which is hard to fixture; that path is covered by the SAFETY comment and cargo build + manual smoke. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 3f4e5cc9a4 |
agent: code-atomiser-fix — migrate Tauri app_data_dir on bundle-identifier change
Commit |
|||
| 6ca94cbff0 |
agent: code-atomiser-fix — paths.rs multi-legacy-candidate migration + copy_dir_recursive symlink loop
Two reversibility defects in `crates/core/src/paths.rs`: Defect A (multi-legacy-candidate orphan): `resolve_app_data_dir` and `legacy_and_target_paths` short-circuited on the first legacy candidate, allowing two reachable orphan scenarios on Linux. With both `~/.magnotia` and `~/.local/share/magnotia` the shim migrated only the dot-home variant, leaving the XDG legacy invisible forever. With a stray `~/.lumotia` alongside a freshly migrated `~/.local/share/lumotia`, the resolver kept returning the dot-home path, orphaning the XDG target. `legacy_and_target_paths` now returns `Vec<(legacy, target)>`, probing every legacy variant the platform supports. The migration driver in `src-tauri/src/lib.rs` loops over the Vec and emits per-candidate tracing. A new `resolve_app_data_dir_strict` + `check_target_ambiguity` API refuses to start when more than one target candidate exists post-migration, surfacing both paths to the user via the setup hook instead of silently picking one. Regression tests: `migrate_handles_both_dot_home_and_xdg`, `resolve_app_data_dir_refuses_on_multiple_targets`. Defect B (copy_dir_recursive symlink loop on EXDEV migration): `entry.metadata()` follows symlinks, so a directory symlink reported is_dir==true and recursed unconditionally. A self-referential or ancestor-targeting directory symlink would loop until the disk filled. Switched to `entry.file_type()` (symlink-aware), re-ordered branches so `is_symlink()` is checked first, and routed all symlinks through symlink-creation (Unix + Windows) rather than recursive copy. Regression tests: `copy_dir_recursive_does_not_loop_on_self_referential_dir_symlink`, `copy_dir_recursive_preserves_directory_symlinks`. 14/14 paths tests green. Full workspace cargo test green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| ab5f6ab995 |
agent: code-atomiser-fix — repair sed-scar provenance from 26c7307
The Magnotia->Lumotia rebrand commit
|
|||
| 2491c7a7dd |
agent: lumotia-rebrand — fix Codex cross-model review blockers
Codex independent review found 11 blockers post-cascade. All addressed.
CRITICAL (data-loss / crash):
10. crates/core/src/paths.rs — migrate_legacy_data_dir_inner used
fs::rename which fails with EXDEV when source + target are on
different filesystems (encrypted-home, bind mounts, separate
$XDG_DATA_HOME partition). Combined with the Phase 5 QC fix that
made migration errors fatal, this would crash on first launch
for any user whose data dir spans filesystems. Added
rename_or_copy_tree() that falls back to copy_dir_recursive +
remove_dir_all on CrossesDevices / errno 18 (EXDEV). Symlinks
preserved verbatim. Same fallback applied to magnotia.db ->
lumotia.db inside the dir.
11. Added 4 unit tests: copy_dir_recursive preserves nested
structure, rename_or_copy_tree same-filesystem happy path,
is_cross_device classifies CrossesDevices kind + raw errno 18.
Doc residuals (blockers 1-9):
1. crates/cloud-providers/Cargo.toml — "Wyrdnote pending rebrand"
description.
2. crates/cloud-providers/src/provider.rs — module docs + test
fixture Wyrdnote refs.
3. crates/transcription/src/orchestrator.rs — module docs + test
fixture Wyrdnote refs.
4. docs/roadmap/2026-05-10-pkm-phase-tooling-shortlist.md — phase
name + outputs/wyrdnote path refs.
5. docs/architecture-map/04-llm-formatting-mcp/llm-tests.md —
MAGNOTIA_LLM_TEST_MODEL env var.
6. .../cloud-providers-stubs.md — MAGNOTIA_API_KEY_*.
7. docs/architecture-map/03-audio-transcription/tests-and-fixtures.md
— MAGNOTIA_WHISPER_*.
8. docs/gpu-tuning/plan.md — MAGNOTIA_BENCH_RUN.
9. docs/superpowers/specs/2026-05-09-battery-gpu-aware-thread-tuning-
design.md + corresponding plan — MAGNOTIA_POWER_STATE_OVERRIDE,
MAGNOTIA_INFERENCE_THREADS.
cargo test --workspace: 343 pass / 0 fail (up from 339; +4 EXDEV
fallback tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 26c7307607 |
agent: lumotia-rebrand — docs, scripts, root config, residuals
Phase 9 of the rebrand cascade. Sweep covers everything the Phase 8
frontend pass deliberately skipped: docs/, root markdown, scripts,
Cargo.toml descriptions, code comments that survived earlier
word-boundary sed, plus a handful of identifiers caught on the final
verify pass.
transcription-app changes:
- README.md, HANDOVER.md, KNOWN-ISSUES.md, run.sh — magnotia/Magnotia
-> lumotia/Lumotia.
- docs/ — sweep across all subdirs except docs/handovers/ (preserved
as immutable audit trail). Includes architecture-map references
to magnotia_core::*, magnotia_storage::*, etc. now pointing at
lumotia_*; dev-setup.md tracing output examples (lumotia_startup
target); brief/ + superpowers/ + issues/ + whisper-ecosystem/ +
audit/.
- Cargo.toml descriptions on 9 crates (core, audio, cloud-providers,
hotkey, llm, mcp, plus referenced others).
- crates/core/src/{error,hardware,recommendation,paths}.rs +
crates/audio/src/wav.rs + crates/llm/src/model_manager.rs +
crates/cloud-providers/src/keystore.rs + crates/mcp/src/lib.rs —
doc comments and a model-manager user-agent string.
- Caught on final pass: BroadcastChannel("magnotia_task_sync") -> ...
("lumotia_task_sync"); magnotia_locale i18n localStorage key
renamed + migration shim added; CSS keyframe names
magnotiaPulse / magnotiaBar / magnotiaFade renamed in the design-
system kit; magnotia_viewer_item / magnotia_viewer_mode handoff
keys renamed in HistoryPage + viewer/+page.svelte; src/assets/
wordmark.svg text.
- src-tauri/src/lib.rs comment cleanup ("magnotia era" was sed'd
to "lumotia era" earlier — restored).
Preserved (intentional):
- crates/core/src/paths.rs — keeps "magnotia" / "Magnotia" / ".magnotia"
legacy detection strings in legacy_and_target_paths() so the
migration shim can still find user data from the magnotia era.
- src/lib/stores/{page,focusTimer}.svelte.ts + src/lib/i18n/index.ts
— migration call sites reference the legacy magnotia keys
deliberately.
- docs/handovers/ — historical audit trail.
cargo build --workspace passes. npm run check: 0 errors / 0 warnings
(3958 files). cargo test --workspace: 339 pass / 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 9336286e3c |
agent: lumotia-rebrand — fix QC blockers for phase 5
Phase 5 QC found two blockers + four advisories. All addressed: B1 (FATAL) — Migration error now aborts startup instead of silently continuing past it. Without this fix a transient EACCES / EXDEV / ENOSPC would log a warning, init_db would create a fresh empty lumotia dir, and the user would appear to lose their transcripts. B2 (FATAL) — Linux dot-home vs XDG mismatch. The old probe returned ~/.magnotia as legacy but the caller passed app_data_dir() as the new path — which could be $XDG_DATA_HOME/lumotia. fs::rename across filesystems would EXDEV-fail; even when it succeeded the user's storage convention silently changed. Refactored: legacy_and_target_paths() returns the (legacy, target) pair together. Dot-home legacy lands in ~/.lumotia; XDG-set legacy lands in $XDG_DATA_HOME/lumotia; XDG-default legacy lands in ~/.local/share/lumotia. macOS / Windows / non-tier-1 unchanged. migrate_legacy_data_dir() now takes no argument; src-tauri caller updated. A1 — Removed dead new_db.exists() check inside rename_db_file_if_present (unreachable: rename_db is called only AFTER the legacy dir was just renamed to the new path, so a stray lumotia.db there is impossible). A2 — Added 4 unit tests for migrate_legacy_setting_keys: lone-magnotia, both-present (orphan delete), no-magnotia, idempotent. A3 — migrate_legacy_setting_keys now returns (renamed, orphans_deleted). When both keys exist, the legacy magnotia row is DELETED in the same transaction (lumotia row is authoritative). A4 — Added dot-home convention regression test in paths::tests. cargo build --workspace passes. cargo test --workspace: 339 pass, 0 fail (up from 334 in the original Phase 5 commit; +5 new tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 86f83b7a45 |
agent: lumotia-rebrand — data dir migration shim + paths.rs rename
Phase 5 of the rebrand cascade per locked decision D1 (migrate in place).
crates/core/src/paths.rs:
- Hardcoded subdir strings renamed: magnotia/Magnotia -> lumotia/Lumotia
across all four OS branches (Linux XDG + dot-legacy, macOS Application
Support, Windows LOCALAPPDATA, fallback dot-dir).
- Database filename: magnotia.db -> lumotia.db.
- Test path fixtures renamed: /tmp/magnotia-test -> /tmp/lumotia-test.
- New MigrationStatus enum (Migrated / TargetAlreadyExists / NoLegacyFound).
- New migrate_legacy_data_dir() that probes the platform-correct legacy
magnotia path, renames it to the lumotia equivalent via fs::rename, and
also renames magnotia.db -> lumotia.db inside if found. Idempotent: safe
to call on every boot. Refuses to overwrite an existing lumotia dir to
protect user data.
- Four new unit tests covering all branches via the test-friendly
migrate_legacy_data_dir_inner that takes an explicit legacy path.
crates/storage/src/database.rs:
- New migrate_legacy_setting_keys(pool) that renames any settings rows
with key matching magnotia_* to lumotia_*. Single SQL UPDATE with
NOT EXISTS guard so it leaves rows alone if a lumotia_ row already
exists. Re-exported from lib.rs.
src-tauri/src/lib.rs:
- Calls migrate_legacy_data_dir(&app_data_dir()) at the start of setup()
BEFORE database_path() resolves the now-renamed dir. Logs migration
outcome to lumotia_startup tracing target.
- Calls migrate_legacy_setting_keys(&db) immediately after init_db
returns. Logs only when rows are actually renamed.
src-tauri/src/{lib,commands/{diagnostics,rituals,transcripts}}.rs +
crates/storage/src/database.rs:
- All in-code references to magnotia_preferences, magnotia_history (in
comments), and magnotia_morning_triage_last_shown renamed to lumotia_*.
cargo build --workspace passes. cargo test --workspace: 334 pass, 0 fail
(up from 330; +4 paths::tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 42f4d07e48 |
agent: lumotia-rebrand — drop MagnotiaError prefix, now lumotia_core::Error
Phase 3 of the rebrand cascade per locked decision D4. MagnotiaError -> Error in crates/core/src/error.rs (the crate name already qualifies it). 92 usages across 14 .rs files renamed via word-boundary sed. One collision required disambiguation: lumotia_storage already had its own local Error type (introduced by the slop-pass Area A residuals work). crates/storage/src/error.rs aliases the imported core error as CoreError on import; the From<Error> for CoreError boundary impl and the CoreError::Storage construction site use the alias. cargo build --workspace passes. cargo test --workspace: 330 pass, 0 fail. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| ce6dc1e728 |
agent: lumotia-rebrand — fix QC blockers for phase 2
Phase 2 QC found three explicit blockers + a broader sweep needed:
Explicit (QC-named):
- crates/mcp/src/lib.rs:15 — SERVER_NAME public MCP wire identity
- crates/transcription/build.rs:59 — panic message prefix
- crates/llm/tests/content_tags_smoke.rs:7 — docstring -p flag
Swept (string literals + dev env vars + doc comments + test fixtures):
- crates/mcp/src/main.rs — eprintln log prefixes
- src-tauri/src/commands/diagnostics.rs — diagnostic filename + MAGNOTIA_VERSION
- src-tauri/src/commands/audio.rs — recording filename pattern lumotia-<secs>-...wav
- src-tauri/src/commands/fs.rs — test placeholder path
- crates/transcription/src/model_manager.rs — .lumotia-verified marker
- crates/storage/src/database.rs — lumotia-storage-ro-<pid> temp dirs + doc comments
- crates/cloud-providers/src/keystore.rs — LUMOTIA_API_KEY_<PROVIDER> env var
- crates/audio/src/{wav,decode}.rs — lumotia_test_* / lumotia_decode_* test fixtures
- crates/core/src/tuning.rs — LUMOTIA_INFERENCE_THREADS env var
- All MAGNOTIA_LLM_TEST_MODEL / MAGNOTIA_WHISPER_TEST_* env vars
- Doc comments referencing crate names
Excluded (intentional Phase 4/5 scope):
- magnotia_preferences, magnotia_history, magnotia_morning_triage_last_shown
DB setting keys (Phase 5 paths.rs migration)
- magnotia_startup tracing target (Phase 4)
- crates/core/src/paths.rs (Phase 5 wholesale rewrite + migration shim)
cargo build --workspace passes. cargo test --workspace: 330 pass, 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 089349d966 |
agent: lumotia-rebrand — rust workspace crates magnotia-* -> lumotia-*
Phase 2 of the rebrand cascade. Renames all 9 workspace crates from magnotia-* to lumotia-* plus the src-tauri binary crate name: - magnotia-ai-formatting -> lumotia-ai-formatting - magnotia-audio -> lumotia-audio - magnotia-cloud-providers -> lumotia-cloud-providers - magnotia-core -> lumotia-core - magnotia-hotkey -> lumotia-hotkey - magnotia-llm -> lumotia-llm - magnotia-mcp -> lumotia-mcp - magnotia-storage -> lumotia-storage - magnotia-transcription -> lumotia-transcription - magnotia -> lumotia (src-tauri binary) - magnotia_lib -> lumotia_lib (src-tauri lib target) Crate directories (crates/audio/ etc.) stay as-is; only the Cargo.toml [package] name field changes plus all consumer module imports (magnotia_core -> lumotia_core, etc.). Remaining magnotia_* references at this point are intentional and scoped to later phases: tracing targets (Phase 4), DB setting keys magnotia_preferences/magnotia_history (Phase 5). cargo build --workspace passes. cargo test --workspace: 330 pass, 0 fail. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| bc2db91520 |
agent: lumotia-rebrand — fix QC blockers for phase 0
Phase 0 QC found two real blockers in the baseline commits: 1. crates/llm/src/lib.rs:215 — early-break guard was inverted. The guard fired when grammar.is_some() (where grammar already enforces shape, making the check redundant) and was disabled for grammar.is_none() (the content-tags path that actually needs it). Flipped to grammar.is_none() so the JSON-envelope short-circuit fires for free-form JSON generation as the commit message implied. 2. src/lib/pages/FilesPage.svelte — handleExport() success path had no toast, asymmetric with DictationPage which does. Added the toasts import and a toasts.success() call after the native save so both export surfaces give consistent feedback. cargo test -p magnotia-llm --lib: 21 pass. npm run check: 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 1d71e8e361 |
refactor(llm): remove GBNF grammar, switch to JSON-envelope extractor
extract_content_tags now generates with grammar=None and parses the response via a manual brace-counting JSON envelope extractor that handles Qwen <think>...</think> prefixes and trailing stop tokens. Five new unit tests. Bumps llama-cpp-2 to 0.1.146. Explicit features=[] on tauri dependency (no-op). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| a36ae7e068 |
agent: remove legacy string storage error variant
Commit
|
|||
| 52565ea8b8 |
agent: area A commit 1 — typed magnotia_storage::Error + boundary
Commit 1 of 2 for engine-slop residuals Area A.
Adds a typed `magnotia_storage::Error` enum and rewires every error
construction site in the storage crate to use it. The crate boundary
into `magnotia_core::error::MagnotiaError` is handled by a
`From<storage::Error> for MagnotiaError` impl that lives inside the
storage crate (not in core) to avoid a `core -> storage` dependency
cycle. The old `MagnotiaError::StorageError(String)` variant is kept
in this commit as an unused placeholder; commit 2 deletes it.
New types in crates/storage/src/error.rs:
- pub enum Error { DatabaseOpen, Migration, Query, NotFound,
InvalidReference, Filesystem } with #[derive(thiserror::Error)]
- pub enum OpenOp { Connect, ReadOnlyConnect, ForeignKeysPragma }
- pub enum MigrationStep { SchemaVersionTableCreate, SchemaVersionQuery,
TxBegin, Apply, RecordVersion, Commit }
- pub enum Entity { Transcript, Task, Profile, ImplementationRule,
Feedback } — seeded only with entities that have a real
NotFound/InvalidReference case in the codebase
- pub type Result<T> = std::result::Result<T, Error>
- impl Error { pub fn kind() -> StorageKind, pub fn
operation_label() -> Cow<'static, str> }
- impl From<Error> for MagnotiaError
New types in crates/core/src/error.rs:
- MagnotiaError::Storage { kind: StorageKind, operation: String,
detail: String } with #[error("{detail}")] so the boundary doesn't
double-prefix Display output
- pub enum StorageKind { DatabaseOpen, Migration, Query, NotFound,
InvalidReference, Filesystem } #[derive(Serialize)]
#[serde(rename_all = "snake_case")] for future Area E
Storage crate dependency added: thiserror = "1"
Migration scope:
- migrations.rs: 6 sites → Error::Migration with structured step + version
- database.rs: 72 sites broken down as:
* 3 → Error::DatabaseOpen (init / readonly / pragma)
* 1 → Error::Filesystem (init's create_dir_all with path context)
* ~58 → Error::Query with operation labels matching the prior message
stem in snake_case; transaction sub-steps use dotted labels
("complete_subtask_and_check_parent.commit_transaction" etc.)
so transaction internals are not collapsed
* 5 → Error::NotFound (transcript / task×2 / implementation_rule×2)
* 5 → Error::InvalidReference (insert_transcript unknown profile;
Default profile rename/delete invariants ×3; feedback rating
value validation)
Survey ambiguities resolved per the locked answers:
- Q1 (schema_version_query): Migration step, not Query
- Q2 (transaction sub-steps): preserved granularity with dotted operation
labels; no collapsing
- Q3 (Entity cardinality): seeded with the 3 from the survey + 2 more
discovered during migration (ImplementationRule, Feedback), per
"add when an actual case needs it"
- Q4 (operation label type): Cow<'static, str>
- Q5 (Serialize): storage::Error is not serializable; flattens only at
the MagnotiaError boundary
- Q6 (re-exports): pub use error::{Entity, Error, MigrationStep, OpenOp,
Result} in storage::lib.rs; StorageKind belongs to magnotia_core
Two scope-discovered additions beyond the original 5 variants:
- Error::Filesystem { path, source } variant + matching StorageKind —
required because init() now returns storage::Result<SqlitePool> and
the create_dir_all call needs a typed path; doing this via
From<io::Error> for storage::Error would have lost the path so it's
explicit
- Entity::ImplementationRule and Entity::Feedback — two NotFound /
InvalidReference sites the original survey missed in the rule-CRUD
and feedback-validation areas
Verification:
- cargo fmt --all
- cargo check --workspace --all-targets — clean
- cargo test -p magnotia-storage — 60 passed, 0 failed
- cargo test --workspace — all green, ~330 tests
- rg 'StorageError\(' crates/ src-tauri/src/ — only hit is the variant
declaration in core that commit 2 will delete
- rg 'Other\(String' crates/storage/src/ — zero
- rg 'format!\("[A-Z].* failed' crates/storage/src/ — zero
Commit 2 will delete MagnotiaError::StorageError(String) once this is
in. Pre-existing working-tree churn in crates/llm/, src/lib/pages/,
src/lib/utils/saveMarkdown.ts and the untracked phase10a dogfood notes
deliberately left unstaged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 48c483894b |
agent: finish tracing migration for hotkey logs
Follow-up to |
|||
| 184214b60a |
agent: engine slop residuals B — eprintln → tracing sweep
Replaces 22 production eprintln! sites with structured tracing events across 8 files. Closes Area B of the post-prognosis residuals plan (docs/superpowers/plans/2026-05-12-engine-slop-residuals.md). Files touched (22 sites): - crates/hotkey/src/linux.rs (2) — hotplug watcher degraded-mode warnings - crates/ai-formatting/src/pipeline.rs (1) — LLM cleanup fallback warning - src-tauri/src/commands/transcription.rs (1) — chunking dispatch info - src-tauri/src/commands/diagnostics.rs (1) — crashes-dir setup warning - src-tauri/src/commands/tasks.rs (1) — malformed feedback row warning - src-tauri/src/commands/power.rs (3) — App Nap acquire/release/fail - src-tauri/src/commands/models.rs (5) — Whisper warmup lifecycle - src-tauri/src/commands/live.rs (8) — session start, chunk dispatch, per-chunk delivery, inference errors, worker disconnects, listener loss, status-channel cascade Levels: error for unrecoverable failures (inference disconnect, panic, status cascade), warn for recoverable degradation (LLM fallback, malformed rows, App Nap fail, hotplug watcher fail), info for lifecycle (session start, chunk processed, App Nap acquire/release, warmup complete, chunking dispatch), debug for per-chunk noise (speech-gate skip, chunk dispatch). Two new dependencies and two new filter targets: - tracing = "0.1" added to crates/hotkey and crates/ai-formatting - Default EnvFilter in src-tauri/src/lib.rs::init_tracing extended with magnotia_hotkey=info,magnotia_ai_formatting=info so the new targets emit at the default level Out of scope (intentional, left as-is): - crates/mcp/src/main.rs — CLI binary, stderr is the log contract (module docstring) so the JSON-RPC stdout stream stays clean - crates/*/tests/*.rs and crates/core/examples/tuning_log_demo.rs — test/example diagnostic output relies on --nocapture stdio semantics Discovery during sweep (not fixed — separate follow-up): hotkey crate has 6 existing log:: calls (log::error/warn/info/debug) but the workspace builds tracing-subscriber without the tracing-log feature, so those events are currently silent. Worth a follow-up to either add the tracing-log bridge or migrate hotkey's existing log:: calls to tracing::. Verification: - cargo fmt --all - cargo check --workspace --all-targets — clean - cargo test --workspace — 330+ tests, zero failures - rg eprintln! src-tauri/src/commands/ crates/hotkey/src/ crates/ai-formatting/src/ — zero hits Pre-existing working-tree churn in crates/llm/, src/lib/pages/, src/lib/utils/saveMarkdown.ts and the untracked phase10a dogfood notes deliberately left unstaged per Jake's instruction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| db654deecc |
agent: engine slop pass — DSP, typed errors, regex parsing, tracing, audit fixes
External code review on 2026-05-12 rated the codebase 4/10 across audio DSP, error typing, JS injection, env-var safety, ALSA parsing, and async logging. This commit lands the prognosis-level fixes plus three audit follow-ups. Audio/DSP: - StreamingResampler/rubato confirmed in the live capture path - regression test at 12 kHz (rms < 0.01, ~40 dB) catches naive decimation - near-Nyquist test at 9 kHz (rms < 0.05, ~26 dB) exercises transition band Core errors: - Other(String) removed; ProviderNotRegistered introduced - Io variant restructured as struct with kind/message/raw_os_error - FileNotFound display quotes paths - Configuration variant removed (unused) Core types: - ModelId, EngineName backed by Cow<'static, str>; const borrowed ctor - Megabytes::from_gb takes u64 (was f64) - AudioSamples::sample_rate is NonZeroU32; zero-rate defensive branch removed Capture: - /proc/asound/cards parsing rewritten as anchored regex (OnceLock) - regression test covers product names with embedded colons - monitor_pattern_detection test restored alongside the regex test - DEAD_SILENCE_FLOOR promoted to module-level with rationale - DEVICE_VALIDATION_MS, SILENCE_RMS_FLOOR documented with field-observation rationale - RMS validation loop made idiomatic - eprintln! migrated to tracing with structured fields and targets Tauri startup: - unsafe std::env::set_var removed; ensure_x11_on_wayland renamed to warn_if_x11_env_unset_on_wayland (launcher/wrapper owns env-var contract) - DB init + log prune + preferences load collapsed to one block_on - build_preferences_script rewrites JS injection from JSON.parse string to direct object literal plus malformed-JSON guard and unit tests - WebKitGTK microphone auto-grant logs warning at startup - tracing subscriber initialised at top of run() (warn,magnotia=info,... on stderr; honors RUST_LOG); previously eprintln→tracing migration was silent because no subscriber existed Filename counter: - RECORDING_COUNTER uses SeqCst Tests: cargo test --workspace --lib green (322 passed, 0 failed across 10 crates). Three independent audits (original cleanup → Wren → fresh Codex subagent) concur on no critical findings. Deferred to docs/superpowers/plans/2026-05-12-engine-slop-residuals.md: storage-layer typed errors, remaining eprintln→tracing sweep, capture actor-model refactor, property-based DSP testing, frontend/backend error boundary cleanup. |
|||
| b463c32f17 | chore: stabilize current head before Phase 10a QC | |||
| 6bc8acccce |
feat(transcription): Phase A — engine protocol + registry + orchestrator (clean-room)
Introduces the architectural spine for the Wyrdnote engine layer per the spec at outputs/wyrdnote/2026-05-10-engine-architecture-spec.md in the CORBEL-Main workspace, Codex-reviewed 2026/05/10. Clean-room derivative of VoiceInk's TranscriptionService / TranscriptionServiceRegistry / TranscriptionPipeline shape. Built from the architectural pattern only; no GPL-3.0 code, comments, or identifiers lifted. Wyrdnote names, control flow, and trait signatures are original. What this lands: crates/cloud-providers/src/provider.rs (new, 184 lines) TranscriptionProvider async trait. Object-safe via async_trait so Arc<dyn TranscriptionProvider> is the canonical shape. Lives in cloud-providers (not transcription) per D7 so an OEM licensee implementing the trait depends only on this crate plus magnotia-core (no transcription internals leaked through the trait surface). Surrounding types: ProviderId (lower-kebab-case), ProviderKind, NetworkRequirement, CostClass, ProviderCapabilities, EngineProfile, ProviderTranscript. Compile-time object-safety witness in tests. crates/cloud-providers/Cargo.toml (modified) Adds async-trait + serde dependencies. crates/cloud-providers/src/lib.rs (modified) Re-exports the provider surface. crates/transcription/src/registry.rs (new, 183 lines) EngineRegistry: catalogue of providers keyed by ProviderId. Push-style registration, default-provider id captured at construction. Read-only after boot. Full unit-test coverage: empty default, register/get round-trip, default-resolves-after-registration, re-register replaces, ids enumeration. crates/transcription/src/orchestrator.rs (new, 286 lines) LocalProviderAdapter wraps Arc<LocalEngine>, presents async TranscriptionProvider upward via tokio::task::spawn_blocking. Per D7 the adapter lives in the orchestrator, NOT as impl TranscriptionProvider for LocalEngine — this keeps the dependency edge one-directional and avoids leaking async-runtime requirements onto the synchronous Transcriber trait. Orchestrator: single transcribe() entry point; resolves a provider from the registry, derives TranscriptionOptions from the EngineProfile, dispatches. Returns a clear error when an unregistered provider is named. Unit tests use a mock CannedProvider to validate dispatch, error handling, and option routing without booting a real model. crates/transcription/Cargo.toml (modified) Depends on magnotia-cloud-providers + async-trait. Dev-dep tokio gains macros + rt-multi-thread features for #[tokio::test]. crates/transcription/src/lib.rs (modified) Exports Orchestrator, EngineRegistry, LocalProviderAdapter, and re-exports the provider trait surface so downstream crates depend only on magnotia-transcription for both local engines and the trait. KNOWN-ISSUES.md (modified) Adds KI-06: existing dictation, live, and meeting commands still call LocalEngine::transcribe_sync directly via pick_engine. The orchestrator path is dormant until a Phase A.1 follow-up commit migrates the call sites. Cloud providers (Phase G) cannot be exercised end-to-end until the rewire lands. Phasing rationale: this commit lands the abstractions cleanly with full test coverage; the rewire is a separate diff so the chunking + post-processing logic in transcribe_file moves without inflating this commit beyond review fidelity. Test results: 9 new tests pass (orchestrator: dispatches, errors-on- unregistered, routes-initial-prompt, object-safe; registry: empty-default, round-trip, default-resolves, re-register, ids-list). Pre-existing 59 transcription tests + 5 cloud-providers tests still green. cargo check --workspace clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
|
|
3c47000ea9 |
chore(core): add tuning_log_demo example for live heuristic validation
Run with: cargo run -p magnotia-core --example tuning_log_demo Initialises a tracing-subscriber fmt layer so the inference_thread_count INFO event is rendered to stderr. Exercises all 8 (workload, gpu_offloaded) combos under three power scenarios: AC override, battery override, and the real sysfs probe. Used to validate the heuristic on 2026-05-09 against the spec's 6c12t truth table — all 6 rows match. Adds tracing-subscriber as a [dev-dependencies] for the example. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
052265b3c2 |
chore(llm): document trivial-true cross-check as observability gap
Per the final reviewer's suggestion: the gpu_offloaded cross-check (gpu_layers >= model.n_layer()) is trivially true when use_gpu since we always pass u32::MAX. The check documents intent and is future- proofed if we ever pass specific N, but a future reader might simplify it away as dead code. Inline comment points at the spec's out-of-scope follow-up for true residency observability. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4f3c063008 |
chore(core): tidy review nits — alphabetical lib.rs, import placement, docstring
End-of-phase cleanup of items flagged across review passes for tasks 1.1, 1.2, and 1.3: 1. crates/core/src/lib.rs: pub mod power moved to alphabetical position (between paths and process_watch). 2. crates/core/src/power.rs: use statements moved to top of file (above the PowerState enum) per Rust convention. 3. crates/core/src/power.rs: parse_power_state_from_dir docstring tightened to acknowledge that read_dir.flatten() silently skips per-entry errors. Theoretical hazard only on world-readable sysfs, but the previous wording overstated the guarantee. No behavioural change. All 40 magnotia-core tests still green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a58c2f0994 |
test(transcription): thread_sweep prints power-aware 4-panel table
Extends the JFK thread-count sweep to print four labelled panels (AC+CPU, AC+GPU, battery+CPU, battery+GPU) driven by MAGNOTIA_POWER_STATE_OVERRIDE. Each panel shows the helper's predicted thread count for its (power, gpu) combination alongside the empirical RTF table for n_threads = [1, 2, 4, physical, logical, maybe 8]. The actual whisper context is initialised once (Vulkan if compiled in and resolvable, CPU otherwise), so the RTF rows themselves are produced by the same backend across panels. The CPU vs GPU axis controls only the helper-pick column, which is the empirical question we want answered: is the LLM=2 / Whisper=4 GPU floor right? Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7eb69e6251 |
refactor(core): remove old inference_thread_count facade
The MIN_INFERENCE_THREADS, MAX_INFERENCE_THREADS, and inference_thread_count() defined in constants.rs were superseded by the equivalents in tuning.rs (Task 2.1) and are no longer called by any production code. Both call sites (whisper backend Task 4.1, LLM generate Task 5.1) have migrated. Constants.rs now holds only audio/mel/RAM/VAD/download constants; inference-tuning concerns live in tuning.rs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
0a503be38d |
feat(llm): LLM threads use Workload::Llm + GPU offload detection
Wires the LLM call site through the new tuning helper. gpu_offloaded reflects intent (use_gpu) cross-checked against the loaded model's layer count: u32::MAX (when use_gpu) is trivially >= any model's n_layer, but the explicit comparison is future-proofed if we ever pass a specific N instead of u32::MAX. Note: the call site is in generate() not load_model() as the plan suggested. Context params (and thus thread count) are constructed per-inference, not per model load, since n_ctx depends on prompt size. The implementer adapted correctly. The old magnotia_core::constants::inference_thread_count import is replaced. Task 6.1 removes the constants helper now that both call sites have migrated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e715da3b54 |
feat(transcription): whisper threads use Workload::Whisper + GPU detection
Wires the whisper backend through the new tuning helper. gpu_offloaded
combines a compile-time feature check (whisper-vulkan, in default
features) with a runtime libvulkan probe via the hardware module. If
libvulkan1 is missing on Linux, whisper-rs's vulkan backend silently
falls back to CPU, so we should not reduce threads in that case.
The old magnotia_core::constants::inference_thread_count import is
replaced by tuning::{inference_thread_count, Workload}. The constants
module's helper is removed in Task 6.1 once the LLM call site has
also migrated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
b991355cb0 |
feat(core): port vulkan_loader_available from src-tauri
Adds libloading = "0.8" to magnotia-core dependencies and moves vulkan_loader_available() into magnotia-core::hardware so non-Tauri crates (transcription, llm) can probe the Vulkan loader without depending on the Tauri binary. Test asserts the call completes on any host regardless of whether libvulkan is installed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
9c85c25b7f |
feat(core): per-process INFO log on first thread-count call per tuple
Adds a OnceLock<Mutex<HashSet>> cache in tuning.rs. inference_thread_count now emits a single tracing::info! line the first time each (workload, on_battery, gpu_offloaded) tuple is seen in the process, recording the resolved thread count and which clamps fired (battery, gpu). Also derives Hash on Workload and tightens the GPU clamp to only record the "gpu" clamp when it actually reduces chosen. Smoke test added. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
6b456b1766 |
feat(core): GPU-offload clamp with per-workload floor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
847dc755ac |
feat(core): inference_thread_count halves on battery
Adds battery-state awareness to the thread-count helper: when probe_power_state() returns OnBattery, chosen is halved before the [MIN, MAX] clamp. Also removes the leading underscore from the workload/gpu_offloaded parameters (they are silenced via let _ = ... until the GPU-clamp task). New test battery_halves_thread_count verifies on_battery <= on_ac and >= MIN when the host has more than MIN physical cores. Restructured to sequential with_override calls (not nested) to avoid re-entrant deadlock on power::TEST_LOCK. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
02a6cb24ce |
fix(core): serialise tuning env-var tests behind a lock
Without a lock, env_var_bypasses_clamps (set_var + remove_var) and matches_existing_clamp_when_no_clamps_apply (remove_var) race under cargo's parallel test runner. Task 2.2 will add another remove_var call, compounding the race. Adds a #[cfg(test)] static THREAD_ENV_LOCK and a closure-style with_thread_env_lock helper, mirroring power::with_override. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |