Commit Graph
6 Commits
Author SHA1 Message Date
Palash DebnathandClaude Fable 5 0ee62b2261 feat(gallery): save gallery voices as profiles, with validated audio references (#1542)
* feat(gallery): save gallery voices as profiles, with validated audio references

Work-in-progress lifted from the concurrent gallery session at the
owner's request (its uncommitted working tree, preserved verbatim from
base 92b1ee5d; safety snapshot remains at rescue/gallery-wip):

- gallery voices can be saved as local profiles: audio is copied into
  the profile store with content-addressed filenames, existing profiles
  are detected and refreshed only when the source clip changed
- backend/core/audio_validation.py: symlink-rejecting, root-contained
  resolution for persisted profile WAV references, with tests
- archetype/community routers and the Voice Gallery UI updated for the
  save-as-profile handoff (spec: docs/specs/longform/26-gallery-use-handoff.md)
- locale updates for the new gallery strings across all 21 files

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: drop a stray local screenshot script that rode in with the tree copy

* fix(community): explain the tolerated Content-Length parse failure; drop an unused import

CodeQL on #1542: the empty except now says why it is safe (the streamed
byte counter enforces the same cap regardless), and the test file loses
an unused Path import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gallery): review findings — copy outside the write lock, no stale completions

CodeRabbit on #1542, all findings addressed:
- the profile-audio copy stages to a .part temp BEFORE BEGIN IMMEDIATE
  and publishes via atomic os.replace inside it — other backend writers
  no longer block for the duration of an audio copy; a mid-copy failure
  leaves no temp droppings and no profile row (both pinned by tests)
- VoiceGallery async ops carry per-operation generation tokens: a
  preview or save-as-profile that resolves after unmount (or after a
  newer operation) can no longer play audio, redirect into a workspace,
  or touch state — three fail-before regression tests
- VoiceGalleryActions imports the page at test runtime; the e2e locator
  uses a stable data-testid instead of a translated string; symlink
  tests skip cleanly where the OS can't create symlinks; the changelog
  line carries its PR ref

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: static ffmpeg fallback when the chocolatey feed is down

Third feed outage to break a PR run (2026-07-20, 2026-07-28, today —
three attempts, three 'installed 0/1'). Chocolatey is a distribution
channel, not the dependency: after the retry loop exhausts, fetch the
static gyan.dev build from its GitHub release mirror and put it on
PATH — same binary, no feed in the path. URL verified live (HTTP 200).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:48:05 +00:00
Palash DebnathandClaude Opus 5 a89c5e8fb3 test: patch the model_manager module main actually uses (#1269) (#1324)
`tests/backend/conftest.py` purges `main` / `core*` / `api*` / `services*` from `sys.modules` after every test it owns, and `backend/tests/` modules bind `import services.model_manager as mm` at COLLECTION time — so in a combined `pytest tests/ backend/tests/` session the alias and the live module are two different objects. Two tests read the wrong one; CI's split invocation hid both.

- `test_lifespan_shutdown_mid_load_is_clean_and_clears_sentinel` patched the stale alias then drove main's lifespan, which loads through the live module. It now runs inside a purge/restore context and resolves from `sys.modules` after importing main, so the failure is deterministic standalone.
- The autouse `_clean_model_manager_shutdown_state` fixture cleaned only `sys.modules` while `test_shutdown_state_isolation.py` dirtied the alias. It now cleans the live module and any module-typed alias in the requesting test module — the idiom already used there for `asr_backend` — taking both the `import x.y as z` binding (package attribute) and the `sys.modules` entry.
- New fail-before/pass-after pair guards the alias half of the fixture in isolation.

Test-infrastructure only; no production code touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 04:22:23 -07:00
Palash DebnathandClaude Opus 5 850ae8e192 test: reset model-manager shutdown state between backend tests (#1320)
* test: reset model-manager shutdown state between backend tests

Two leaks, one of them mine.

1. `model_manager._shutting_down` is a module-global Event and the GPU pool is a
   module-global executor. Any test that runs the app lifespan flips both on the
   way out (begin_shutdown + _reset_gpu_pool) and nothing puts them back —
   correct in production, where the process is ending; wrong across a combined
   session. A test arriving with the flag set finds a shut-down executor, so its
   first run_in_executor raises "cannot schedule new futures after shutdown",
   which the preload path classifies as benign and swallows. The symptom is a
   load that silently never starts. Reset before AND after: before so an
   inherited flag cannot decide the test, after so a test that legitimately
   shuts down does not hand it on.

2. tests/test_torch_compile_path_gate.py assigned services.settings_store into
   sys.modules directly instead of via monkeypatch.setitem. That leaks
   process-wide out of collection and breaks every later import of the real
   module. backend/tests/test_no_module_stubs.py exists to catch exactly that,
   and caught it — I introduced it two commits ago while isolating the Settings
   gate for a review finding.

#1269 stays open for its last failure, which is a different root cause:
test_lifespan_shutdown_mid_load fails because a reload fixture in tests/
replaces services.model_manager, so the test patches one module object while
main's lifespan uses another (verified: `same=False`). That is the duplicate-
module class, not a state leak, and needs its own fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test: let the shutdown-state reset fail loudly

CodeRabbit Major: the broad try/except meant a reset that raised left the next
test with stale shutdown or executor state — precisely the order-dependent
failure the fixture exists to remove, while looking like it had worked. That is
the same silent-fail-open shape as the watermark and ffmpeg bugs fixed earlier
in this cycle.

If reset_shutdown_flag() or _reset_gpu_pool() can raise, that is a real problem
in model_manager and it should be loud.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test: assert the shutdown-state reset fixture actually resets (CodeRabbit)

Ordered pair: one test leaves the module globals exactly as the lifespan
leaves them, the next asserts it arrived clean — delete the fixture and the
second fails. Plus a mechanical guard that the reset stays un-swallowed, so
a future try/except cannot make the fixture look like it worked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 01:30:06 -07:00
debpalashandClaude Fable 5 83f943bead fix: bot-review harvest (16 findings) + deterministic style/locale CI + reviewer configs
Harvested and verified every CodeRabbit/Greptile finding from PRs #1175,
#1189, #1192, #1195: 16 real ones fixed (fallback ASR preflight bypass,
VRAM release on stream exit, typed 409 parity, uv env independence,
path-privacy in errors, MCP clone_voice hardening, CaptureWidget WS
guard, test hygiene), 4 refuted with evidence, rest documented as
deliberate design or deferred.

Deterministic CI replaces hand-enforcement: tests/test_changelog_style.py
(quiet one-liner format) and tests/test_locale_parity.py (21-locale
key/placeholder lockstep with a ratchet baseline) — the latter surfaced
and fixes 151 already-broken locale strings. CodeRabbit/Greptile carry
the house rules via .coderabbit.yaml + greptile.json; CLAUDE.md gains
the harvest-before-merge and never-accept-as-is rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 09:47:41 +05:30
debpalashandClaude Fable 5 63fd497caf feat: TTS-only first run, platform-curated ASR, guided OS permissions, parakeet-mlx
Only the TTS model (~2.4 GB) is required on first run; ASR models are
per-platform curated picks (curated_on in models.yaml) installed on demand.
Every transcription surface returns a typed asr_model_missing error with a
one-click download CTA instead of silently pulling multi-GB Whisper weights.
Settings -> Models is a grouped, platform-aware catalog. New guided
permissions UX (wizard System Check + Settings -> Permissions + mic
pre-flight) with native mic-state checks and OS settings deep-links. New
parakeet-mlx engine brings Parakeet TDT v3 to Apple Silicon (language-gated
capture preference so multilingual dictation never regresses). Docs:
expressive-speech page, Flush/Unload + CPU-fallback triage, clone-length FAQ.
Hardening: preflight fails open for custom model pins, ROCm curation no
longer inherits NVIDIA picks, Windows mic probe reads the NonPackaged
consent key, CaptureWidget setup race fixed, offline-cache CI simulation
fixes so empty-cache runners stay green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:20:54 +05:30
3f62471bad test: pay down export-router test debt + kill two test-order pollution classes (#1081)
Four pieces of test debt, root-caused and hardened:

1. exports.py test coverage (was: zero dedicated tests): new
   tests/test_exports_api.py (26 tests) covering /export, /export/record,
   /export/history, /export/reveal — happy paths, traversal/containment
   guards (incl. symlink escape), destination validation, error mapping,
   and the mp4 watermark-overlay branch with its plain-copy fallback.
   Two real bugs found and fixed in the router:
   - _safe_destination checked isabs() on realpath()'s output, which is
     always absolute — dead check; a relative destination silently exported
     to a cwd-dependent location instead of the documented 400.
   - _safe_source let "." / ".." through the basename guard (caught only
     later by realpath containment as a confusing 404); now 400 up front.

2. CI-Linux fp16 default-dtype leak (test_prefers_vocals_over_mix,
   test_final_dub_track_and_seg_wav_are_watermarked): not reproducible on
   macOS — instrumenting torch.set_default_dtype across both tests records
   zero non-fp32 sets locally. Both tests now carry an opt-in
   torch_dtype_isolation fixture (save/restore, so the leak can never
   spread), and the conftest guard is demoted to pure insurance. A cheap
   permanent recorder wraps torch.set_default_dtype /
   set_default_tensor_type once torch appears and captures the setter's
   stack only on a non-fp32 set; both fixtures print that stack when they
   fire, so the next CI occurrence names the exact culprit call chain.

3. Test-order pollution (both reported combos): root cause was
   collection-time sys.modules stubbing in backend/tests — seven modules
   installed bare ModuleType stubs for core.config (and test_capture_ws.py
   for services.model_manager/asr_backend/ffmpeg_utils, now all lazily
   imported by the router anyway). pytest imports test modules during
   collection, so the stubs leaked process-wide before any test ran:
   - combo (a): monkeypatch.setattr("core.config.OUTPUTS_DIR", ...) in
     test_longform_e2e died with AttributeError (core never gets a .config
     attribute when the import is satisfied straight from sys.modules).
   - combo (b): test_router_smoke's `from main import app` died with
     ImportError: cannot import name 'find_ffmpeg' (unknown location).
   Fix at source: new backend/tests/conftest.py sets a hermetic
   OMNIVOICE_DATA_DIR (mirroring tests/conftest.py, #878) and the real
   core.config is imported everywhere — zero sys.modules surgery. New
   backend/tests/test_no_module_stubs.py guards the whole class (verified
   fail-before/pass-after against the old stub). Stale rationale comments
   in pyproject.toml and ci.yml updated to match.

4. batched_tts.py TODO(#312): investigated, comment corrected only —
   #312 is closed (the live routes are engine-aware); this module has zero
   call sites and stays an unintegrated experiment. See PR notes.

Full tests/ suite: 2796 passed. backend/tests standalone: 130 passed.
Both pollution combos re-run green in the reported orderings.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 23:10:35 +05:30