[#202] Default STT model → large-v3-turbo-q5_0 (fresh installs only) - #207
Conversation
+82 MB over `small` (547 vs 465) buys turbo-class accuracy, and cold load stays at `small`'s level — 0.29 s, not the unquantized build's 6.3 s, which is the objection that had ruled turbo out. RTF 0.31, no FallingBehind in any run. Measurements are the operator's, from #111/docs/CALIBRATION.md. Migration — the settings FILE is the discriminator, not the field. Anything reaching serde came off disk, so it is an install that has run before and keeps what it had; only a fresh install (no file → AppSettings::default()) takes the new default. Files predating #110 have no sttModel at all, and those users never chose `small` — they were defaulted into it — so treating "absent" as consent to a 547 MB download would be exactly the silent switch the ticket rules out. The sanitize clamp goes to the legacy model for the same reason: a hand-edited junk value is no more consent than an absent one. The trade-off is real and is stated in the code: an existing user who never touched the setting stays on `small` until they pick the new model in the sheet, where it appears with its size. Opt-in beats an unrequested download. The webview mirror deliberately does NOT track the new default — sanitizedSttModel only ever sees settings loaded from disk, so it mirrors migrated_stt_model, not DEFAULT_MODEL. CALIBRATION.md's recommendation is superseded in place rather than rewritten: the original recommended the UNQUANTIZED build, and the note records why that was right at the time and what changed. Its RTF row carries only the mean and cold load the operator published — the per-fixture values were not measured, so they are blank rather than back-filled. Integrity untouched: no change to pointer_url, HF_REPO, or the SHA-256 check. model_family() already strips the -q suffix, so the quantized build inherits the large-v3 floors with no change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
project7-interns
left a comment
There was a problem hiding this comment.
Verdict: REQUEST CHANGES
Epic Alignment: FAIL
The documented migration says every existing install keeps its prior model, but the invalid-settings recovery path still routes an existing install to the new fresh-install default.
Checked (evidence)
- Structural gate: PR body contains EPIC Alignment, Self-Verification, and Deviations sections.
- Context: #202 requires fresh installs to get q5_0 while existing selections are preserved; #107's direction is evidence-backed default changes without destabilizing shipped behavior.
- Verified migration paths in src-tauri/src/settings.rs:121-140, 193-201, 329-360.
- Riskiest part: distinguishing a missing settings file from an unreadable existing file.
- Kill-list: one correctness finding below.
- CI: gh pr checks 207 -> color-guard/no-stub-gate pass; app-macos, packages-linux, release-invariants pending.
Findings
- [blocking] A corrupt existing settings file silently becomes a fresh-install q5_0 configuration.
- File: src-tauri/src/settings.rs:193-201
- Why it fails: load() maps any read/parse failure to AppSettings::default(), whose stt_model is now large-v3-turbo-q5_0. An existing user whose settings.json is unreadable is therefore pushed into the 547 MB download, contradicting the PR's “every existing install keeps what it was running” migration contract and the explicit anti-silent-switch rationale.
- Do instead: distinguish missing path from an existing-but-unreadable file; recover existing-file defaults with migrated_stt_model() (or preserve a safe legacy settings fallback) and add a test covering invalid JSON at an existing path.
Decision
The allow-list, fresh default, valid-file migration, documentation, and picker changes align, but the damaged-existing-file path violates the migration contract. Fix that path and re-request review after all CI checks complete.
RE1: load() fell back to AppSettings::default() on a file that exists but will not parse, so a single corrupt byte silently switched a working install onto a 547 MB download — the exact outcome this migration exists to prevent, and a direct contradiction of the contract I stated in the PR body. load() now distinguishes the two cases it had collapsed: ErrorKind::NotFound is a fresh install and takes the new default; a file that exists but cannot be read or parsed takes existing_install_default() — defaults everywhere else, legacy model. An unreadable file (permissions, I/O) goes the conservative way for the same reason: something is on disk. The damaged-file test now asserts the distinction in both directions rather than just "some defaults": equal to existing_install_default(), NOT equal to default(). Extended the offline serde harness to cover it, and seeded the old behaviour back — the corrupt case returns q5_0 and the assertion panics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RE2 — REQUEST CHANGESPR #207 (Closes #202) @ Blocking: a corrupt settings file on an EXISTING install gets the 547 MB downloadThe PR's stated invariant (
That holds for a file that parses. It does not hold for a file that doesn't. Ok(text) => serde_json::from_str::<AppSettings>(&text)
.map(AppSettings::sanitized)
.unwrap_or_default(), // <-- parse failure lands HERE
Err(_) => AppSettings::default(),
Verified empirically, not by code read. I replicated Your four documented rows all behave exactly as claimed. The two you did not test are the ones that break. Why this blocks rather than gets noted. It contradicts three things at once:
Probability is low — writes are atomic ( Suggested fix (either shape; ~2 lines): .unwrap_or_else(|_| serde_json::from_str::<AppSettings>("{}")
.expect("empty object parses")
.sanitized())Parsing Checked (evidence) — everything else verifiedThe migration design is correct where it parses, and the file-not-field discriminator is the right idea. The clamp cannot strand a fresh install. Rust and web stay aligned, and the TS fallback is deliberately not tracking Scope holds: no floor changes ( Gates: Your two judgment calls — both right
On the trade-off you flagged for argumentKeeping untouched existing installs on Fix the corrupt-file path and I will re-confirm the delta immediately. (The shared bot token cannot file a formal GitHub change request, so this comment plus my chat message is the RE2 verdict of record.) |
@re2 independently found the same corrupt-file gap @re1 did, and named a second shape I had not listed: a settings write truncated mid-flight. It takes the same parse-failure arm, so f7b4320 already fixed it — but "already covered by the same branch" is a claim, and the point of this batch has been not shipping those. Now it has its own row in the test and the harness, and it is the realistic corruption: save_atomic makes hand-edits the rarer path, but a truncated file is what an interrupted write leaves. Kept existing_install_default() rather than @re2's suggested parse-of-"{}": identical result for every field, but explicit about intent and with no .expect() on a hardcoded literal in the load path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
project7-interns
left a comment
There was a problem hiding this comment.
Verdict: APPROVE
Epic Alignment: PASS
PR #207 implements #202's evidence-backed fresh-install default while preserving existing selections and the Rust/web allow-list and integrity contracts.
Checked (evidence)
- Reviewed final delta f7b4320..2ef605a: src-tauri/src/settings.rs adds explicit truncated-write coverage for the conservative existing-install branch.
- Verified migration source: missing path uses AppSettings::default() (q5_0); parse/read failures use existing_install_default() (legacy small); valid explicit choices remain accepted.
- Verified docs and picker: docs/CALIBRATION.md publishes the measured q5_0 mean/cold-load row without inventing fixture values; app-settings.ts exposes ~547 MB and preserves all prior options.
- Riskiest part: fresh-vs-existing settings discrimination, including corrupt and truncated files; the real app-macos suite covers these assertions.
- Kill-list: scanned final changed ranges — clean.
- CI: gh pr checks 207 -> all five checks pass (app-macos, packages-linux, release-invariants, color-guard, no-stub-gate).
Findings
None.
Decision
The migration guard is now explicit and tested in the authoritative app suite, the model/default and picker changes match #202, and all live CI checks pass.
RE2 — APPROVE (at
|
Closes #202
Switches the fresh-install default STT model to
large-v3-turbo-q5_0and exposes it in the picker. No existing install is moved.EPIC Alignment
settings.rs:77↔app-settings.ts:39-50);DEFAULT_MODEL(model.rs:33) remains the single source for the fresh-install default and the engine's download-failure fallback; no floor or integrity-check change.cca0796) — this PR rebases on itsdocs/CALIBRATION.mdand supersedes its model recommendation, as [feat] Default STT model → large-v3-turbo-q5_0 (547 MB): full-turbo accuracy at near-small download and load time #202 requires.The migration decision (scope item 3)
Fresh installs get the new default; every existing install keeps what it was running.
The discriminator is the settings file, not the field:
settings.jsonload()→AppSettings::default()large-v3-turbo-q5_0sttModel(predates #110)migrated_stt_model()smallsanitized()→migrated_stt_model()smallload()→existing_install_default()smallThe corrupt/unreadable row is RE1's finding (#1461), fixed in
f7b4320:load()previously collapsed "no file" and "unusable file" into oneAppSettings::default(), so a single corrupt byte silently moved a working install onto the new download — contradicting the contract stated right here.ErrorKind::NotFoundis now the only branch treated as a fresh install.Why absence is treated as "existing install", not "unset": serde only reaches that default when a file was read, so anything landing there has run before. Users with pre-#110 files never chose
small— they were defaulted into it — and treating that as consent to a 547 MB download on next session start is precisely the silent switch #202 rules out. The clamp for junk values goes the same way for the same reason.The trade-off, stated rather than buried: an existing user who never touched the setting stays on
smalluntil they pick the new model in the Settings sheet, where it appears with its size. Opt-in beats an unrequested download — but it does mean the accuracy win doesn't reach existing users automatically.Self-Verification
livecap-appcan't build here, so the serde surface (DEFAULT_MODEL, both default fns, the field attribute,Default, andsanitized) was extracted verbatim into an offline cargo harness with the realserde/serde_jsonand executed. All five rows of the table above printed and asserted:#[serde(default = "default_stt_model")](the naive implementation) makes the absent-field row returnlarge-v3-turbo-q5_0; restoringunwrap_or_default()inload()makes the corrupt-file row do the same. Both guards are load-bearing, not decorative.cargo test -p livecap-core --lib→ 62 passed (runs on Linux; coversmodel_filename/MODEL_NAMEShandling of the quantized name).pnpm test:app→ 170 passed (169 + 1 new);rustfmt --checkonsettings.rsstill shows the same 5 diffs asmainafter the fix;pnpm -r --filter './packages/*' test→ archive 110, engine 270.pnpm lint,pnpm typecheck(both configs) → exit 0../scripts/no-stub-gate.sh,./scripts/color-guard.sh→ pass.rustfmt --checkonsettings.rs: 5 diffs, identical to the count onmain— none introduced by this change (verified by diffing the findings against a stashed tree; one comment was reflowed to the file's trailing-comment style to keep it that way).settings.rs's four migration assertions) execute only underapp-macos. The harness above covers the same logic on the same inputs, but the authoritative run is CI.What changed
model.rs:33—DEFAULT_MODEL→large-v3-turbo-q5_0, with the measured justification in the doc comment.settings.rs— allow-list gains the model; newmigrated_stt_model()+LEGACY_STT_MODEL; field default and the sanitize clamp both point at it; four migration assertions added.app-settings.ts— picker entryLarge v3 Turbo (compact)/~547 MB;sanitizedSttModel'ssmallfallback documented as deliberately not tracking the new default.main.ts:209— first-paint placeholder matches the fresh-install default (display-only;get_settingsoverrides it).docs/CALIBRATION.md— recommendation superseded in place with a note saying what the earlier one said and why it changed; RTF table gains aq5_0row.test/stt-model.test.ts— list membership, the size string, and the migration-fallback rationale.Deviations
q5_0RTF row publishes only the mean and cold load. [feat] Default STT model → large-v3-turbo-q5_0 (547 MB): full-turbo accuracy at near-small download and load time #202 supplies 0.31 / 0.29 s; per-fixture f1/f2/f3 values were never measured, so they are left blank with a note rather than back-filled. Inventing them would be the exact defect [#111] STT calibration: reproducible model bench + measured findings #199 spent four review rounds removing.mediumandlarge-v3-turboremain selectable (scope item 5); nothing was removed from either list.pointer_url/HF_REPO/SHA-256 untouched, no caption content logged.