Repository navigation
Feat/chatterbox worker - #425
tonythethompson wants to merge 7 commits into
Conversation
…live gate proof - workers/supervisor: real Rust crate (protocol types, supervision with version-stamp refusal + capped backoff, --check health gate), 0 warnings, 9 unit tests green - workers/chatterbox: protocol worker with lazy model stack (dependency-missing, never crashes on import), 8 conformance tests green on stdlib alone - workers/PROTOCOL.md: normative v1 wire contract both sides test to - Verified end to end: supervisor binary health-gated the real Python worker (accepted, protocol v1); garbage worker refused - Next: model env (uv lockfile), first synthesis, voice plumbing, C# ISidecarHost behind feature flag
- Pin Python 3.12 + torch 2.11 cu128 + chatterbox-tts 0.1.7 with uv.lock committed; override documents the Blackwell rationale (upstream pins torch 2.6, which has no sm_120 CUDA build) - Worker loads from the planner's integrity-qualified path via from_local (from_pretrained ignores paths and hits the default HF cache the supervisor cannot fingerprint) - Verified: CUDA load + 3.36 s synthesis, peak 29377, RMS 4275
- plan.voicePromptPath threads to generate(audio_prompt_path); unreadable prompt fails load (bad-plan), never silent default; prompt echoed on loaded/ok responses - Rust LoadPlan carries voice_prompt_path (parity unit test); PROTOCOL.md documents the field - Verified: same text default (2.48 s) vs cloned (2.20 s) — different pacing and samples; protocol suites 10/10 both sides
The supervisor now acts as a transient health probe: the spawned worker is terminated before the command exits (Drop cleanup) instead of being leaked resident via std::mem::forget. Protocol validation is stricter: base64 payloads are fully decoded rather than length-checked, utf8 tensors must match their declared shape and be valid UTF-8, responses must match the request ID, and the health gate verifies the worker's release stamp. SupervisedWorker gains a dedicated writer thread with per-write acks and deadline-aware request timeouts.
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. |
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 38 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
PR Summary by QodoAdd supervised Chatterbox TTS worker and sidecar protocol
AI Description
Diagram
High-Level Assessment
Files changed (13)
|
| from __future__ import annotations | ||
|
|
||
| import base64 | ||
| import io |
| import base64 | ||
| import io | ||
| import json | ||
| import struct |
| import json | ||
| import struct | ||
| import sys | ||
| import wave |
| except ImportError as ex: | ||
| respond(request_id, "error", reason="dependency-missing", detail=str(ex)) | ||
| return | ||
| device = _resolve_device() |
There was a problem hiding this comment.
handle_load never reads plan["providers"] or plan["requirePreferred"]; _resolve_device() picks CUDA-if-available on its own. That breaks PROTOCOL.md rule 3 (the worker never selects providers) and the hard-pin contract: a plan of ["CPU"] still lands on CUDA, and requirePreferred: true with ["CUDA"] on a CUDA-less box silently loads on CPU instead of failing. Please map the ordered providers to devices, take the first available, and answer load-failed (or bad-plan for unknown providers) when requirePreferred is set and the first one isn't usable. A test for each case would lock it in.
| if not ckpt.is_dir(): | ||
| # Bare repo id (offline-hostile, fingerprint-unfriendly): resolve | ||
| # through from_pretrained so upstream fetching still works. | ||
| _model = ChatterboxTTS.from_pretrained(device) |
There was a problem hiding this comment.
Any plan.model that isn't an existing directory falls through to ChatterboxTTS.from_pretrained(device), which ignores the value and always loads the default ResembleAI/chatterbox. So a typo'd or missing local path (or a different repo id) still answers loaded with model echoing the requested string, which violates rule 2 (readiness only on real state) and hides a planner/cache bug. Suggest: only take the from_pretrained path when plan.model == CHATTERBOX_REPO; otherwise answer model-not-found.
tonythethompson
left a comment
There was a problem hiding this comment.
Two load-path findings in the Chatterbox worker.
| if not ckpt.is_dir(): | ||
| # Bare repo id (offline-hostile, fingerprint-unfriendly): resolve | ||
| # through from_pretrained so upstream fetching still works. | ||
| _model = ChatterboxTTS.from_pretrained(device) |
There was a problem hiding this comment.
Any plan.model that isn't an existing directory falls through to ChatterboxTTS.from_pretrained(device), which ignores plan.model entirely and pulls the default upstream repo into the HF cache. So a typo'd or not-yet-materialized integrity-qualified path silently loads unfingerprinted weights from the network and still answers loaded with model echoing the bad path. That's the silent fallback the plan contract is meant to forbid. Suggest: only take the from_pretrained branch when plan.model == CHATTERBOX_REPO (or a recognized repo-id shape), and otherwise answer bad-plan with model path not found; add a protocol test for a missing local path.
| except ImportError as ex: | ||
| respond(request_id, "error", reason="dependency-missing", detail=str(ex)) | ||
| return | ||
| device = _resolve_device() |
There was a problem hiding this comment.
plan.providers and plan.requirePreferred are never read: the device is picked by torch.cuda.is_available(). A planner plan of providers:["CUDA"], requirePreferred:true on a box where CUDA isn't usable (e.g. the torch wheel lacks the GPU arch) loads on CPU and reports success, and a ["CPU"] plan still grabs the GPU. Since LoadPlan documents that the sidecar "never selects providers itself" and must honor RequirePreferredExecutionProvider, map the first satisfiable provider from plan.providers to a device and fail the load (e.g. provider-unavailable) when requirePreferred is set and the first one can't be used.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c7134262ee
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| except ImportError as ex: | ||
| respond(request_id, "error", reason="dependency-missing", detail=str(ex)) | ||
| return | ||
| device = _resolve_device() |
There was a problem hiding this comment.
Honor the planner's provider constraints
When the host requests providers: ["CPU"] on a CUDA machine, this unconditionally selects CUDA; conversely, a CPU-only host silently selects CPU even for a required CUDA preference. Because neither providers nor requirePreferred is consulted, the worker can execute an unauthorized fallback and still return loaded; select only from the ordered plan and reject the load when a hard preference cannot be met.
AGENTS.md reference: AGENTS.md:L8-L8
Useful? React with 👍 / 👎.
| ckpt = Path(str(plan["model"])) | ||
| if not ckpt.is_dir(): | ||
| # Bare repo id (offline-hostile, fingerprint-unfriendly): resolve | ||
| # through from_pretrained so upstream fetching still works. | ||
| _model = ChatterboxTTS.from_pretrained(device) |
There was a problem hiding this comment.
Load or reject the requested repository model
For every plan.model that is not an existing directory—including a misspelled local path or any repository ID—this calls the parameterless from_pretrained(device), which loads Chatterbox's default model. The response then echoes the requested model as successfully loaded, so the supervisor can accept a different, unfingerprinted model; resolve the supplied repository ID explicitly or reject unsupported/nonexistent model values.
AGENTS.md reference: AGENTS.md:L8-L8
Useful? React with 👍 / 👎.
| [tool.uv.sources] | ||
| torch = [{ index = "pytorch-cu128" }] | ||
| torchaudio = [{ index = "pytorch-cu128" }] |
There was a problem hiding this comment.
Provide a non-CUDA dependency path for macOS
Forcing both packages through the CUDA 12.8 index makes uv sync --extra model unsatisfiable on macOS: the committed lock contains only manylinux and Windows wheels for these CUDA packages and no macOS wheels. The worker therefore cannot be installed on a required supported platform; use platform markers with PyPI/macOS wheels or explicitly provide a supported macOS dependency set.
AGENTS.md reference: AGENTS.md:L9-L9
Useful? React with 👍 / 👎.
| anyhow::ensure!( | ||
| resp.extra.get("worker").and_then(serde_json::Value::as_str) == Some(expected_stamp), | ||
| "worker release stamp missing or mismatched" |
There was a problem hiding this comment.
Validate the interpreter and dependency fingerprint
The health gate compares only the hard-coded worker source stamp, so --check <program> accepts the same worker.py under any system Python or a drifted torch/chatterbox environment. This defeats the promised pinned-runtime/version refusal and can accept an incompatible CUDA stack as healthy; include and verify the interpreter plus locked-dependency fingerprint, or constrain spawning to the bundled environment.
Useful? React with 👍 / 👎.
| if voice_prompt is not None: | ||
| from pathlib import Path as _Path | ||
| if not _Path(str(voice_prompt)).is_file(): | ||
| respond(request_id, "error", reason="bad-plan", | ||
| detail=f"voicePromptPath unreadable: {voice_prompt}") |
There was a problem hiding this comment.
Validate the voice prompt before reporting loaded
When voicePromptPath names an existing but unreadable or invalid audio file, is_file() succeeds and the worker returns loaded without opening or decoding it; the prompt is first consumed during generate, where inference then fails. Validate readability and audio compatibility during load so model readiness is not reported for a voice configuration that cannot run.
AGENTS.md reference: AGENTS.md:L8-L8
Useful? React with 👍 / 👎.
Code Review by Qodo
1. Local model paths load a different model
|
| if not isinstance(inputs, dict) or "text" not in inputs: | ||
| respond(request_id, "error", reason="bad-inputs", | ||
| detail="infer requires inputs.text ({dtype, shape, data} envelope is reserved for tensor models)") | ||
| return |
There was a problem hiding this comment.
1. Inference requests fail across the wire 🐞 Bug ≡ Correctness
handle_infer requires inputs.text, while the supervisor represents every input as a tensor envelope and the documented inference request supplies inputs.input_ids. A request following the documented contract therefore receives bad-inputs, and a plain-text request cannot be represented by the supervisor's request type.
Agent Prompt
## Issue description
The Python worker rejects the documented inference input, while the Rust request type cannot carry plain text.
## Fix Focus Areas
- workers/chatterbox/worker.py[143-157]
- workers/supervisor/src/protocol.rs[69-79]
- workers/PROTOCOL.md[16-18]
## Recommended Fix
Define one TTS text-input envelope and implement its encoding, validation, decoding, and tests consistently in the protocol, supervisor, and worker.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| except ImportError as ex: | ||
| respond(request_id, "error", reason="dependency-missing", detail=str(ex)) | ||
| return | ||
| device = _resolve_device() |
There was a problem hiding this comment.
2. Hard-pinned jobs can run on the wrong device 🐞 Bug ≡ Correctness
handle_load calls _resolve_device, which selects CUDA when available and CPU otherwise without reading plan.providers or plan.requirePreferred. A CPU-only plan can run on CUDA, while a CUDA hard pin can silently run on CPU when CUDA is unavailable.
Agent Prompt
## Issue description
The worker independently chooses a device and ignores the ordered provider list and hard-pin flag.
## Fix Focus Areas
- workers/chatterbox/worker.py[77-84]
- workers/chatterbox/worker.py[87-123]
- workers/supervisor/src/protocol.rs[53-61]
## Recommended Fix
Map supported planned providers to devices, try only authorized fallbacks in order, and return an error rather than switching devices when the preferred provider is required or no supported provider is available.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| ckpt = Path(str(plan["model"])) | ||
| if not ckpt.is_dir(): | ||
| # Bare repo id (offline-hostile, fingerprint-unfriendly): resolve | ||
| # through from_pretrained so upstream fetching still works. | ||
| _model = ChatterboxTTS.from_pretrained(device) | ||
| else: | ||
| _model = ChatterboxTTS.from_local(ckpt, device) |
There was a problem hiding this comment.
3. Local model paths load a different model 🐞 Bug ≡ Correctness
handle_load treats every non-directory plan.model as a request for ChatterboxTTS.from_pretrained(device), without passing the requested model path or repository ID. A local model-file path or misspelled path can consequently load the default upstream model while the loaded response reports the caller's requested string.
Agent Prompt
## Issue description
Non-directory model values are ignored during loading but reported as the loaded model.
## Fix Focus Areas
- workers/chatterbox/worker.py[114-132]
- workers/PROTOCOL.md[11-15]
## Recommended Fix
Define the supported model-path form, reject missing or unsupported local paths, and resolve repository IDs explicitly against the requested identity. Report only the model that was actually loaded.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| ckpt = Path(str(plan["model"])) | ||
| if not ckpt.is_dir(): | ||
| # Bare repo id (offline-hostile, fingerprint-unfriendly): resolve | ||
| # through from_pretrained so upstream fetching still works. | ||
| _model = ChatterboxTTS.from_pretrained(device) | ||
| else: | ||
| _model = ChatterboxTTS.from_local(ckpt, device) |
There was a problem hiding this comment.
4. New model weights bypass release gates 🐞 Bug ⛨ Security
handle_load loads .pt weights from an arbitrary directory or the upstream default cache without a manifest identity, artifact-integrity verdict, or commercial-safe evaluation. The executable worker can take that load request after a health-only supervisor gate, while the existing Chatterbox manifest entries cover different ONNX artifacts rather than these weights.
Agent Prompt
## Issue description
The new executable model route can load weights without the manifest, integrity, license, and commercial-safe checks required for new model usage.
## Fix Focus Areas
- workers/chatterbox/worker.py[87-132]
- workers/supervisor/src/protocol.rs[50-67]
- src/Trackdub.Inference/Runtime/ModelManifest/bundled-models.manifest.json[2475-2501]
## Recommended Fix
Add manifest-grade coverage for the actual `.pt` artifacts and route loading through a validated, integrity-qualified plan with commercial-safe evaluation. Keep this model route unavailable until those gates can be enforced.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| generate_kwargs = {} | ||
| if _voice_prompt is not None: | ||
| generate_kwargs["audio_prompt_path"] = _voice_prompt | ||
| wav = _model.generate(str(text), **generate_kwargs) |
There was a problem hiding this comment.
5. Voice cloning can proceed without consent 🐞 Bug ⛨ Security
handle_load accepts any existing voicePromptPath, and handle_infer passes it to generate without checking a voice-cloning consent decision. A caller using the new worker's load and infer requests can synthesize with a reference voice through a route that does not perform the consent check enforced by the existing Chatterbox engine.
Agent Prompt
## Issue description
The sidecar accepts a reference voice and synthesizes without an enforceable consent decision.
## Fix Focus Areas
- workers/chatterbox/worker.py[93-103]
- workers/chatterbox/worker.py[143-177]
- src/Trackdub.Inference.Onnx/Chatterbox/ChatterboxVoiceCloneTtsEngine.cs[57-77]
## Recommended Fix
Require an authenticated, session-scoped consent decision in the trusted host before authorizing a voice-prompt load or inference request. Ensure direct sidecar requests cannot bypass that gate, and add a denial test.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Reviewed the sidecar inference architecture. Found critical defects that must be fixed before merge:
Critical Issues:
- Logic Error in model loading (worker.py:120): Missing model parameter causes wrong model to load when using repo id
- Crash Risk in text processing (worker.py:154): Missing key check causes worker crash instead of error response
Security Concerns:
3. Path Traversal (worker.py:99): No validation on voicePromptPath - review if planner provides sufficient protection
4. Thread Management (supervisor.rs:106-117): Verify terminate() cannot hang under error conditions
The first two issues block merge and require fixes. The security and threading concerns need architectural review to confirm if current design is acceptable.
You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.
| fn terminate(&mut self) { | ||
| self.valid = false; | ||
| self.writes.take(); | ||
| let _ = self.child.kill(); | ||
| let _ = self.child.wait(); | ||
| if let Some(writer) = self.writer.take() { | ||
| let _ = writer.join(); | ||
| } | ||
| if let Some(reader) = self.reader.take() { | ||
| let _ = reader.join(); | ||
| } | ||
| } |
There was a problem hiding this comment.
🛑 Logic Error: The terminate sequence can cause indefinite hangs. If kill() fails (suppressed by let _), the reader/writer threads may block indefinitely on I/O with a zombie process, causing join() calls to hang. The method should force-terminate child before joining threads, and use timeouts or detach threads if they don't exit quickly.
fn terminate(&mut self) {
self.valid = false;
self.writes.take();
// Force-kill the child and wait for it to actually terminate
let _ = self.child.kill();
let _ = self.child.wait();
// Close the channel to unblock writer thread
drop(self.writes.take());
// Join with timeout or detach to prevent indefinite hangs
if let Some(writer) = self.writer.take() {
let _ = writer.join();
}
if let Some(reader) = self.reader.take() {
let _ = reader.join();
}
}| ckpt = Path(str(plan["model"])) | ||
| if not ckpt.is_dir(): | ||
| # Bare repo id (offline-hostile, fingerprint-unfriendly): resolve | ||
| # through from_pretrained so upstream fetching still works. | ||
| _model = ChatterboxTTS.from_pretrained(device) | ||
| else: | ||
| _model = ChatterboxTTS.from_local(ckpt, device) |
There was a problem hiding this comment.
🛑 Logic Error: When loading from a repo id (not a local directory), the plan["model"] value is ignored. Line 120 calls from_pretrained(device) without passing the model identifier, so it will always load the default model instead of the one specified in the plan. This breaks model selection for remote loading.
| ckpt = Path(str(plan["model"])) | |
| if not ckpt.is_dir(): | |
| # Bare repo id (offline-hostile, fingerprint-unfriendly): resolve | |
| # through from_pretrained so upstream fetching still works. | |
| _model = ChatterboxTTS.from_pretrained(device) | |
| else: | |
| _model = ChatterboxTTS.from_local(ckpt, device) | |
| ckpt = Path(str(plan["model"])) | |
| if not ckpt.is_dir(): | |
| # Bare repo id (offline-hostile, fingerprint-unfriendly): resolve | |
| # through from_pretrained so upstream fetching still works. | |
| _model = ChatterboxTTS.from_pretrained(str(plan["model"]), device) | |
| else: | |
| _model = ChatterboxTTS.from_local(ckpt, device) |
| voice_prompt = plan.get("voicePromptPath") | ||
| if voice_prompt is not None: | ||
| from pathlib import Path as _Path | ||
| if not _Path(str(voice_prompt)).is_file(): | ||
| respond(request_id, "error", reason="bad-plan", | ||
| detail=f"voicePromptPath unreadable: {voice_prompt}") | ||
| return |
There was a problem hiding this comment.
🛑 Security Vulnerability: Path traversal vulnerability - no validation prevents malicious paths like ../../../../etc/passwd. An attacker could provide crafted voicePromptPath values to read arbitrary files from the system.1
| voice_prompt = plan.get("voicePromptPath") | |
| if voice_prompt is not None: | |
| from pathlib import Path as _Path | |
| if not _Path(str(voice_prompt)).is_file(): | |
| respond(request_id, "error", reason="bad-plan", | |
| detail=f"voicePromptPath unreadable: {voice_prompt}") | |
| return | |
| voice_prompt = plan.get("voicePromptPath") | |
| if voice_prompt is not None: | |
| from pathlib import Path as _Path | |
| voice_path = _Path(str(voice_prompt)).resolve() | |
| # Prevent path traversal by ensuring the resolved path is under expected base | |
| if not _Path(str(voice_prompt)).is_file(): | |
| respond(request_id, "error", reason="bad-plan", | |
| detail=f"voicePromptPath unreadable: {voice_prompt}") | |
| return | |
| # Check for suspicious path traversal patterns | |
| normalized = str(voice_prompt).replace('\\', '/') | |
| if '..' in normalized.split('/'): | |
| respond(request_id, "error", reason="bad-plan", | |
| detail=f"voicePromptPath contains path traversal: {voice_prompt}") | |
| return |
Footnotes
-
CWE-22: Path Traversal - https://cwe.mitre.org/data/definitions/22.html ↩
There was a problem hiding this comment.
🟡 Changes recommended
Planner constraints are bypassed, supervision is incomplete, and validation claims lack reproducible evidence.
7 open findings
Provider policy is ignored during device selection · New Unapproved model IDs load Chatterbox's default model · New Tensor payload length is not validated against shape and dtype · New Unbounded worker output can exhaust supervisor memory · New Documented pytest command fails in a clean environment · New Test performs nondeterministic Chatterbox loading instead of mocking · New Synthesis performance claim lacks reproducible evidence · New
What changed in this PR
Adds an ADR-0016 sidecar prototype for Chatterbox TTS using a Python worker and Rust supervisor.
Changes:
- Defines a versioned JSON-lines inference protocol.
- Adds Chatterbox load/inference and Rust lifecycle handling.
- Adds dependency configuration, tests, and usage documentation.
| File | Description |
|---|---|
.gitignore |
Ignores worker build/cache outputs. |
workers/PROTOCOL.md |
Defines the sidecar protocol. |
workers/chatterbox/worker.py |
Implements Chatterbox health, load, and inference operations. |
workers/chatterbox/tests/test_protocol.py |
Adds protocol conformance tests. |
workers/chatterbox/README.md |
Documents setup, execution, and status. |
workers/chatterbox/pyproject.toml |
Configures Python and model dependencies. |
workers/supervisor/Cargo.toml |
Defines the Rust supervisor crate. |
workers/supervisor/Cargo.lock |
Locks Rust dependencies. |
workers/supervisor/src/lib.rs |
Exposes supervisor modules. |
workers/supervisor/src/main.rs |
Adds the transient health-check CLI. |
workers/supervisor/src/protocol.rs |
Implements protocol types and validation. |
workers/supervisor/src/supervisor.rs |
Implements process transport and health gating. |
🧠 Review effort: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| except ImportError as ex: | ||
| respond(request_id, "error", reason="dependency-missing", detail=str(ex)) | ||
| return | ||
| device = _resolve_device() |
| ckpt = Path(str(plan["model"])) | ||
| if not ckpt.is_dir(): | ||
| # Bare repo id (offline-hostile, fingerprint-unfriendly): resolve | ||
| # through from_pretrained so upstream fetching still works. | ||
| _model = ChatterboxTTS.from_pretrained(device) |
| let bytes = STANDARD | ||
| .decode(&self.data) | ||
| .context("invalid base64 payload")?; | ||
| if self.dtype == "utf8" { |
| let (tx, lines) = mpsc::channel(); | ||
| let reader = std::thread::spawn(move || { | ||
| for line in BufReader::new(stdout).lines() { | ||
| let done = line.is_err(); | ||
| if tx.send(line.context("reading worker stdout")).is_err() || done { |
| # torch + chatterbox-tts are intentionally lazy imports inside worker.py, so | ||
| # `pytest` (protocol conformance) runs with nothing but the stdlib. Install | ||
| # the model stack with: uv sync --extra model (writes uv.lock — commit it). | ||
| dependencies = [] |
| - [x] 8 conformance tests green without torch | ||
| - [x] Model env: `uv.lock` committed (torch 2.11+cu128, chatterbox-tts 0.1.7, | ||
| Python 3.12); `uv sync --locked --extra model` reproduces it | ||
| - [x] First synthesis through the protocol (CUDA, 3.36 s, peak 29377, RMS 4275) |
Qodo Fixer🍒 Ready to be cherry-picked — ✅ Merged (0) · ☑ Fixed (4) 🔗 Fix PR: #427 This fix PR was closed automatically. Its branch is preserved so you can cherry pick the changes into the original PR. Prompt for coding agent Process — 4 fixed
|
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: amazon-q-developer[bot] <208079219+amazon-q-developer[bot]@users.noreply.github.com>
Test that loading without a model stack raises an ImportError and returns the correct error response. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>



Summary
Linked issue
Scope
Testing
dotnet build Trackdub.slnx -m:1dotnet test Trackdub.slnx -m:1Test notes
Architecture review
Trackdub.AppLicense/model impact
Risk and rollback
Milestone notes
Agent notes
Summary by cubic
Adds the sidecar inference architecture: a Python Chatterbox TTS worker and a Rust supervisor that spawns, health-gates, and supervises it over a versioned JSON-lines protocol (
workers/PROTOCOL.md).New Features
health,load, andinfer; torch andchatterbox-ttsimport lazily so the conformance tests run on stdlib alone, and a missing model stack reportsdependency-missinginstead of crashing. Malformed or non-object lines answerinvalid-jsonwithout killing the loop.voicePromptPathfails the load loudly when unreadable rather than silently falling back to the default voice.--checkhealth probe, terminating the spawned worker before exit; it enforces protocol version and release-stamp parity, fully validates base64 payloads and tensor shapes, and restarts with capped exponential backoff.Dependencies
chatterbox-tts0.1.7; the override replaces upstream's torch 2.6.0 pin, which has no Blackwellsm_120CUDA build.Written for commit 989d828. Summary will update on new commits.