Five-slice navigable map of the entire codebase under
docs/architecture-map/. Each slice is a self-contained
breadcrumbed sub-tree:
01-frontend (16) Svelte/SvelteKit UI
02-tauri-runtime (26) src-tauri commands + lifecycle
03-audio-transcription (16) audio + transcription crates
04-llm-formatting-mcp (19) llm, ai-formatting, mcp, cloud
05-core-storage-hotkey-build core, storage, hotkey, workspace,
(26) CI, dev glue
Plus master README.md and data-flow-end-to-end.md tracing
audio bytes from microphone to FTS5 search to MCP read.
Generated by 5 parallel subagents on 2026/05/09 against
HEAD 3c47000. Each page has YAML frontmatter, file:line code
refs, sibling cross-links, plain-English summaries.
Aggregated debt surfaced (full lists in master README):
RB-08 macOS power assertion, schema head drift v14 vs v15,
VAD blocked on ort version conflict, streaming primitives
not wired into live.rs, no prompt versioning, MCP has no
auth, cloud-providers in-memory keystore, SettingsPage
2 484 LOC, commands/live.rs 1 737 LOC, dual theme system,
brand rename to Lumenote pending across the codebase.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.6 KiB
name, type, slice, last_verified
| name | type | slice | last_verified |
|---|---|---|---|
| Model manager (download, verify, locate) | architecture-map-page | 03-audio-transcription | 2026/05/09 |
Model manager (download, verify, locate)
Where you are: Architecture map → Audio + Transcription → Model manager
Plain English summary. Speech models live on disk under a per-platform path. model_manager resolves those paths, checks "is this model fully downloaded", lists what is downloaded, and downloads missing files with HTTP Range resume + per-chunk SHA256 verification. A single in-process HashSet reservation prevents concurrent downloads of the same model. After a successful download a .magnotia-verified manifest is written so a subsequent process restart can short-circuit re-hashing.
At a glance
- Crate:
magnotia-transcription - Path:
crates/transcription/src/model_manager.rs - LOC: 617 (production code + extensive in-tree download server tests)
- External deps:
reqwest 0.12(rustls-tls, stream),futures-util 0.3(StreamExt),sha2 0.10. Path resolution comes frommagnotia_core::paths::app_paths. Catalogue frommagnotia_core::model_registry. - Internal callers (best effort, slice 2 reconciles): the
modelsTauri command consumes all five public functions. The frontend Settings → Models view drivesdownloadwith a progress callback.
Public surface:
pub fn models_dir() -> PathBuf—crates/transcription/src/model_manager.rs:42. Resolves to%LOCALAPPDATA%/magnotia/modelson Windows,~/.magnotia/modelson Unix (delegates tomagnotia_core::paths::app_paths).pub fn model_dir(id: &ModelId) -> PathBuf—crates/transcription/src/model_manager.rs:47.pub fn is_downloaded(id: &ModelId) -> bool—crates/transcription/src/model_manager.rs:52.pub fn list_downloaded() -> Vec<ModelId>—crates/transcription/src/model_manager.rs:63.pub async fn download(id: &ModelId, progress: impl Fn(DownloadProgress) + Send + 'static) -> Result<()>—crates/transcription/src/model_manager.rs:78.
Private helpers:
DownloadReservation— RAII guard (model_manager.rs:12).verified_manifest_path/verified_manifest_matches/write_verified_manifest(model_manager.rs:115–:153).sha256_of_file(model_manager.rs:157).download_file(model_manager.rs:178).
What's in here
Path resolution
models_dir and model_dir defer entirely to magnotia_core::paths::app_paths(). Anything platform-specific lives in slice 5; this crate just consumes the resolution.
Download reservation
DownloadReservation::acquire(id) (model_manager.rs:17) inserts the model id into a process-wide HashSet guarded by a LazyLock<Mutex<...>>. Returns Err(MagnotiaError::DownloadFailed("download already in progress for {id}")) if the id is already present. Drop removes the entry. This protects against re-entry from the same process; cross-process races are still possible but the atomic .part → dest rename closes the most damaging window.
is_downloaded
model_manager.rs:52. Two-step:
- Every file declared in the registry entry must exist at
model_dir(id).join(filename). - The verified-manifest at
model_dir.join(".magnotia-verified")must record the samefilename\tsha256\tsizetriple per file.
A missing or stale manifest causes is_downloaded to return false even if the bytes are correct. The next download call will revalidate and rewrite the manifest. A truncated or tampered file fails the existence + size check, so a subsequent download redownloads it.
list_downloaded
model_manager.rs:63. Filters the static magnotia_core::model_registry::all_models() catalogue by is_downloaded.
download (the orchestrator)
model_manager.rs:78. For each ModelFile in the registry entry:
- If
destexists, hash it withsha256_of_file. Match → skip the download. Mismatch → delete and re-fetch. Hash IO error → propagate asMagnotiaError::DownloadFailed. - Otherwise call
download_file(file, dest, id, &progress).
After all files are present and verified, write_verified_manifest writes the version\t1 header and per-file filename\tsha256\tsize lines.
download_file (single file with resume + hash)
model_manager.rs:178. Implements the Buzz-style resume pattern:
- Build a
reqwest::Clientwith a 30 s connect timeout. - If
dest.with_extension("part")exists, send aRange: bytes={existing}-header. - Inspect the response status:
206 Partial Content→ server is resuming. Continue appending.200 OKafter a Range request → server ignored Range. Discard the stale.partand start fresh (regression-tested atmodel_manager.rs:494).4xx/5xx(non-resume path) →MagnotiaError::DownloadFailed("download returned HTTP {status} for {filename}"). Crucial becausereqwestdoes not auto-error on bad status; without this check, a 404 body would be streamed into.partand renamed over the destination (regression atmodel_manager.rs:556).- Other status on resume → propagate as unexpected.
- Determine
total_bytesfromContent-Range(resume) orContent-Length(fresh). - Hasher state: when resuming, hash the on-disk
.partfirst to catch the hash up to where the network stream is about to resume. Otherwise start fresh. - Stream the body in chunks, writing each to the file, updating the running SHA256, emitting
DownloadProgressper percent step. - On stream end, finalise the hash. Mismatch → remove
.partandMagnotiaError::DownloadFailed("SHA256 mismatch for {filename}: expected ..., got ..."). std::fs::rename(.part, dest)— atomic finalise.
sha256_of_file
model_manager.rs:157. 8 KiB buffer streaming hash. Used by download to validate an existing complete file before trusting it.
Data flow
ModelId
└─ models_dir / model_dir → on-disk directory
└─ is_downloaded:
all files exist AND verified-manifest matches
→ true / false
└─ list_downloaded:
filter all_models() by is_downloaded
└─ download:
acquire reservation
for file in entry.files:
if dest exists:
sha256_of_file → match? skip : delete + re-fetch
else:
download_file:
[.part exists? Range request : full GET]
stream body → file + hasher + progress callback
match SHA256 → atomic rename
else → cleanup, error
write_verified_manifest
drop reservation
Watch-outs
- Reservation is per-process. Two Magnotia instances (dev + release, two cargo runs) racing on the same model dir can collide. The
.part → destatomic rename is the only thing standing between them and a torn file. DownloadProgressis emitted per percent step, not per byte. A 10 MB file at 4G download speeds emits ~100 events; a 5 GB file emits the same 100 events. UI must not assume tight cadence.- Manifest schema is
version\t1+filename\tsha256\tsizelines. Bumping the version is a manual operation; a stale manifest after a registry update will simply trigger a redownload (waste, not corruption). - Cross-process file locking is not used. A user opening Settings → Models in two windows of the same desktop session will trip the in-process reservation, but two physical machines syncing the same network home would not be detected.
- Reqwest connect timeout is 30 s, no read timeout. A stalled mid-stream connection blocks indefinitely. Acceptable for a foreground UI flow today; would need a
tokio::time::timeoutwrap if the download ever became headless. ModelFile::sha256is&'static str. Theleakhelper in tests reflects that production code reads a static catalogue compiled into the binary. Tampering with the catalogue requires a code change, not a runtime config.reqwestis configureddefault-features = false, features = ["rustls-tls", "stream"]. No native-tls fallback. Linux without ca-certificates will fail at connect.models_diris the user-data dir, not a cache dir. A "clear cache" button in the OS would not nuke models. Documented behaviour.
See also
- Transcription engines overview —
load_whisper/load_parakeetare called againstmodel_dir(id)outputs. - Tests and fixtures — in-tree TcpListener-backed servers that exercise the resume + sha-mismatch + 5xx paths.
magnotia_core::model_registry::{find_model, all_models, ModelEntry, ModelFile}(slice 5) — the source of URLs and hashes.magnotia_core::paths::app_paths(slice 5) — platform path resolution.magnotia_core::types::DownloadProgress(slice 5) — the progress event shape.