Migrates IndexTTS-2 off the in-process import path and onto the
SubprocessBackend primitive shipped in Plan 02-01. Closes issue #42 with
a structural fix — the parent's transformers>=5.3 and IndexTTS's
transformers<5 now live in separate OS processes and can never collide.
* New: backend/engines/indextts/ — sidecar package (__init__.py hosts
IndexTTS2Backend, main.py is the sidecar entrypoint, bootstrap.py owns
the 3-step venv probe + lazy uv-based bootstrap).
* services.tts_backend: IndexTTS2Backend's in-process body removed;
registry resolves the class lazily via a _LazyRegistry indirection +
PEP 562 __getattr__ re-export. This breaks the import cycle that
arose when both subprocess_backend and tts_backend tried to import
each other at module load.
* docs/engines/indextts.md: install walkthrough + venv resolution order
+ common errors (linked from is_available()'s unavailable message).
* tests:
- test_indextts_backward_compat.py (8) — probe priority, no-spawn
discipline, HF cache marker preservation (ENGINE-07).
- test_indextts_sidecar.py (17) — subclass shape, isolation_mode,
parent-side emotion arbitration (vector/audio/text/description),
coexist-with-OmniVoice (headline #42 closure), env forwarding.
- tests/fixtures/mock_indextts_sidecar.py — stdlib-only sidecar
mimicking the production wire protocol; emits 1 s sine wave.
- test_issue_fixes.py: two obsolete in-process-conflict tests rewritten
to assert the new subprocess contract (no indextts.* import in the
parent).
Hard constraints honored: backend/services/sonitranslate.py and
gpu_sandbox.py are untouched (D1 / D4). Existing v0.2.7 users with
OMNIVOICE_INDEXTTS_DIR and a populated HF cache reach a working
generation with zero re-download and zero re-install.
44 tests pass across the four exercised files. Full suite: 391 passed,
10 skipped, 13 xfailed, 1 xpassed in 57 s. Smoke: 4 passed.
Closes#42. Requirements: ENGINE-02, ENGINE-03, ENGINE-04, ENGINE-07.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>