Write a complete, single-file async TTS (text-to-speech) job pipeline in Python 3.11+ (asyncio, stdlib only) OR Node.js (no external deps beyond stdlib). It accepts text jobs and processes them through a mock synthesizer with bounded concurrency.

### API
- `async def submit(text: str, voice: str) -> str` — enqueue a job, return a job id immediately (non-blocking).
- `async def drain()` — wait until all queued AND in-flight jobs are finished.
- `async def cancel(job_id)` — cancel a queued job; if in-flight, mark for cancellation when safe.
- A way to register a callback that receives job lifecycle events: `queued`, `started`, `completed`, `failed`, `cancelled`.

### Mock synthesizer
Provide `async def mock_synthesize(text: str) -> bytes` that sleeps a random 50–300ms and returns `b"\x00" * len(text)` (or similar). Make it fail ~10% of the time (raise a synthetic error) so retry logic is exercised.

### Requirements
1. **Bounded concurrency:** at most 4 jobs synthesize at once.
2. **Backpressure:** when the queue length exceeds 100, `submit` must reject immediately with a clear error (not block, not grow unbounded).
3. **Retry on failure:** a job that fails must retry up to 3 times with exponential backoff (e.g. 0.1s, 0.2s, 0.4s). After exhausting retries it emits `failed`.
4. **Clean cancellation:** cancelling must not leak tasks or leave the worker pool in a bad state; `drain()` must always return.
5. **Thread/async safety:** no shared mutable state races between submit, workers, and callbacks.

### Included test
Include an `async def main()` test that:
- a) Submits 50 jobs, drains, and asserts exactly 4-or-fewer ran concurrently at any time (record max concurrency).
- b) Proves the 100-job backpressure cap rejects when exceeded.
- c) Submits a batch, cancels one mid-flight, drains, and asserts no jobs were dropped/duplicated and the pool is still usable afterward.

Provide clean, well-commented code that runs directly via `python file.py`.
