Environment: Windows 11, bash shell. Project root: J:\CLAUDE\PROJECTS\Wakeword (master).

PROBLEM
The training pipeline depends on a multi-source negatives generator. In the production container today, edge-tts-based generation (`_generate_confusable_negatives` and `_generate_speech_negatives` in `src/violawake_sdk/tools/train.py`) silently fails — the catch-and-log-error pattern around them returns 0 files, but the live edge-tts API reachability check passes (`edge_tts.list_voices()` returns 322 voices from inside the container). So the synthesis itself is failing, just silently.

We currently work around this by mounting `./corpus/` (LibriSpeech + MUSAN) into the container so Source 4 supplies negatives. **But:**
- This means anyone running the backend stack on a fresh machine without the corpus directory will hit the same "0 negatives" failure.
- The corpus is ~5GB+ and isn't committed to git.
- Real customers running the SaaS won't have this mount in their setup.

CRITICAL CONSTRAINTS
- Do NOT use PowerShell with complex quoting (a previous Codex run died on this).
- Use Read tool / `head` / `tail` / `sed -n` / `grep` for inspection.
- NEVER `git add -A`, `git push`. Stage explicit files. Commit logically.

INVESTIGATE FIRST
1. Read `src/violawake_sdk/tools/train.py` around `_generate_confusable_negatives` and `_generate_speech_negatives` (use `grep -n "def _generate" src/violawake_sdk/tools/train.py` to find line numbers).
2. Read `_edge_tts_synthesize` (line ~262) to understand the call pattern.
3. Trigger a test edge-tts synthesis from inside the running `wakeword-backend-1` container:
   ```bash
   docker exec wakeword-backend-1 python -c "
   import asyncio, edge_tts, tempfile
   from pathlib import Path
   async def t():
       out = Path(tempfile.mkstemp(suffix='.mp3')[1])
       c = edge_tts.Communicate('hello world', 'en-US-JennyNeural')
       await c.save(str(out))
       print('size:', out.stat().st_size)
   asyncio.run(t())
   "
   ```
   If this fails, you've found the exact error. If it succeeds, the failure is in something OUR code does (e.g., async-loop reuse, voice list filtering, MP3-to-WAV conversion).
4. Look at backend container logs around a recent failed training job for the silent error from `_generate_*` functions: `docker logs wakeword-backend-1 --tail 1000 | grep -iE "FAILED|edge.tts|generation|confusable" | head -30`.

DECIDE & IMPLEMENT
Based on what you find, pick the minimum-touch fix:

A. **If edge-tts truly works in our container path**, the `_generate_*_negatives` functions have a bug that swallows real errors. Fix the bug + log properly so future failures are visible.

B. **If edge-tts fails for a structural reason** (event loop, asyncio threading, audio conversion deps missing), fix the root cause OR add a clear `logger.error` with the actual exception so we know what's wrong, AND ensure the corpus-fallback path works without the user having to know.

C. **If neither works in this container**, the training pipeline must:
   - Document `corpus/` as a required deploy artifact in `docs/DEPLOYMENT.md`
   - Add a startup health check in `entrypoint.sh` that warns if no corpus AND no edge-tts will produce zero negatives, BEFORE the user submits a training job.
   - OR: bundle a small, committed "minimum viable negatives corpus" (e.g., 50 LibriSpeech clips + 50 MUSAN clips) into the SDK package itself so training never starves regardless of deploy environment.

Pick whichever path is most truthful given the evidence. Prefer (A) — fix the bug — if the edge-tts test from step 3 works.

PROVE IT
1. After your fix, restart the backend (no rebuild needed if you only changed Python source — the volume mount of `/sdk` should hot-reload, BUT the SDK is pip-installed at /usr/local/lib/python3.11/site-packages/violawake_sdk so you actually DO need a rebuild OR an in-container `pip install -e /sdk`. Check the Dockerfile to see how the SDK is installed.).
2. Either:
   - Trigger a real training job via the test pipeline (`python tests/live/full_pipeline_e2e.py`) to confirm negatives are now generated AND/OR the failure is loud and clear.
   - OR run a focused test that calls `_generate_confusable_negatives` directly inside the container.
3. Show the diff and the commit SHA(s).

REPORT
- Root cause (A/B/C above + specific evidence).
- What you changed and where.
- Commit SHA(s).
- Whether further follow-up is needed (e.g., if you punt on (C), document what's still required).

Time budget: ~25 min.
