#![forbid(unsafe_code)]
mod error;
pub mod robot;
pub mod synth;
pub use error::{FttsError, FttsExitCode};
pub use robot::{EventType, validate_event, validate_ndjson};
use std::collections::BTreeMap;
use std::ffi::OsString;
use std::fs;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use std::sync::OnceLock;
#[cfg(test)]
use clap::CommandFactory;
use clap::{Parser, Subcommand, ValueEnum};
use ftts_artifacts::census::{ExpectedTensor, WeightsManifest};
use ftts_artifacts::converter::{
StreamingConversionPlan, TensorConversion, TensorStoragePolicy, convert_safetensors_streaming,
};
use ftts_artifacts::fttsq::{AccessClass, MappedFttsq};
use ftts_artifacts::safetensors::Dtype;
use ftts_core::{NormalizationMode, NormalizationOptions, SynthesisRequest};
use ftts_kernels::mmap::MappedFile;
use serde_json::{Value, json};
const ROBOT_SCHEMA_VERSION: u8 = 1;
const SCAFFOLD_ADMISSION_TEXT_LIMIT_BYTES: usize = 1_048_576;
const MODEL_BASENAME: &str = "qwen3-tts-12hz-0.6b-base.fttsq";
const PINNED_MAIN_WEIGHTS_FILENAME: &str = "model.safetensors";
const PINNED_MAIN_WEIGHTS_SHA256: &str =
"180b3b10eb1c9f1b4db7806d5475bae3071c0243c299d49926bab1da3b6946f6";
const PINNED_MODEL_REVISION: &str = "5d83992436eae1d760afd27aff78a71d676296fc";
const PINNED_MAIN_TENSOR_COUNT: usize = 478;
const PINNED_TENSOR_INVENTORY: &str = include_str!("../pinned/TENSOR_INVENTORY.json");
const PINNED_MODEL_CONFIG: &str = include_str!("../pinned/model_config.json");
const APACHE_LICENSE: &str = include_str!("../pinned/QWEN_APACHE_LICENSE");
const PINNED_MODEL_MANIFEST: &str = include_str!("../pinned/model_manifest.json");
const DEFAULT_MODEL_CACHE_SUBDIR: &str = ".cache/franken_tts/model";
const ENVIRONMENT_VARIABLES: [&str; 11] = [
"FTTS_MODEL_DIR",
"FTTS_DEFAULT_VOICE",
"FTTS_THREADS",
"FTTS_PROFILE",
"FTTS_PACKET_FRAMES",
"FTTS_MATH_MODE",
"FTTS_QUANT",
"FTTS_FORCE_ARCH",
"FTTS_NUMA",
"FTTS_MAX_FRAMES",
"FTTS_MEMORY_BUDGET_MB",
];
pub fn cli_main() -> ExitCode {
let cli = match Cli::try_parse() {
Ok(cli) => cli,
Err(error) => {
let exit_code = match error.kind() {
clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => {
FttsExitCode::Success
}
_ => FttsExitCode::Usage,
};
let _ = error.print();
return exit_code.as_exit_code();
}
};
let mut stdin = io::stdin().lock();
let mut stdout = io::stdout().lock();
let mut stderr = io::stderr().lock();
match dispatch(cli, environment(), &mut stdin, &mut stdout, &mut stderr) {
Ok(()) => FttsExitCode::Success.as_exit_code(),
Err(error) => {
let _ = writeln!(stderr, "error: {error}");
error.exit_code().as_exit_code()
}
}
}
#[derive(Debug, Parser)]
#[command(
name = "ftts",
version,
about = "Pure-Rust Qwen3-TTS command-line interface",
long_about = "FrankenTTS is stateless by default: synthesis history is never persisted. \
Use `ftts robot schema` for the versioned NDJSON contract.",
arg_required_else_help = true
)]
struct Cli {
#[arg(long, global = true, value_enum)]
profile: Option<ExecutionProfile>,
#[arg(long, global = true, value_enum)]
packet_frames: Option<PacketFrames>,
#[arg(long, global = true, value_enum)]
math_mode: Option<MathMode>,
#[arg(long, global = true, value_enum)]
voice_pack: Option<VoicePackProfile>,
#[arg(long, global = true, value_enum)]
normalize: Option<NormalizeMode>,
#[arg(long, global = true, value_name = "DIR")]
trace: Option<PathBuf>,
#[arg(long, global = true)]
seed: Option<u64>,
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
Say(SayArgs),
Enroll(EnrollArgs),
Voice(VoiceArgs),
Convert(ConvertArgs),
Pull(PullArgs),
Robot(RobotArgs),
Doctor(DoctorArgs),
}
#[derive(Debug, clap::Args)]
struct SayArgs {
#[arg(value_name = "TEXT")]
text: Option<String>,
#[arg(value_name = "OUTPUT", conflicts_with_all = ["stream", "output"])]
output_positional: Option<PathBuf>,
#[arg(long, value_name = "PATH", conflicts_with = "text")]
file: Option<PathBuf>,
#[arg(long, value_name = "PATH")]
model: Option<PathBuf>,
#[arg(long, value_name = "PATH")]
voice: Option<PathBuf>,
#[arg(short = 'o', long, value_name = "PATH", conflicts_with = "stream")]
output: Option<PathBuf>,
#[arg(long, value_enum)]
stream: Option<StreamMode>,
#[arg(long)]
check: bool,
}
#[derive(Debug, clap::Args)]
struct EnrollArgs {
#[arg(value_name = "REFERENCE_AUDIO")]
reference_audio: PathBuf,
#[arg(long, value_name = "PATH")]
model: Option<PathBuf>,
#[arg(short = 'o', long, value_name = "PATH", conflicts_with = "default")]
output: Option<PathBuf>,
#[arg(long, conflicts_with = "output")]
default: bool,
#[arg(long)]
force: bool,
}
#[derive(Debug, clap::Args)]
struct VoiceArgs {
#[command(subcommand)]
command: VoiceCommand,
}
#[derive(Debug, Subcommand)]
enum VoiceCommand {
Inspect { path: PathBuf },
}
#[derive(Debug, clap::Args)]
struct ConvertArgs {
#[arg(value_name = "SOURCE")]
source: PathBuf,
#[arg(short = 'o', long, value_name = "PATH")]
output: PathBuf,
}
#[derive(Debug, clap::Args)]
struct PullArgs {
#[arg(long, value_name = "PATH")]
model: Option<PathBuf>,
#[arg(long)]
force: bool,
}
#[derive(Debug, clap::Args)]
struct RobotArgs {
#[command(subcommand)]
command: RobotCommand,
}
#[derive(Clone, Debug, Subcommand)]
enum RobotCommand {
Schema,
Health,
Backends,
Selftest,
}
#[derive(Debug, clap::Args)]
struct DoctorArgs {
#[arg(long)]
json: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum ExecutionProfile {
Interactive,
Balanced,
Throughput,
Strict,
}
impl ExecutionProfile {
const fn as_str(self) -> &'static str {
match self {
Self::Interactive => "interactive",
Self::Balanced => "balanced",
Self::Throughput => "throughput",
Self::Strict => "strict",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum PacketFrames {
#[value(name = "1")]
One,
#[value(name = "2")]
Two,
#[value(name = "4")]
Four,
Auto,
}
impl PacketFrames {
const fn as_str(self) -> &'static str {
match self {
Self::One => "1",
Self::Two => "2",
Self::Four => "4",
Self::Auto => "auto",
}
}
const fn frames_per_packet(self) -> u8 {
match self {
Self::One => 1,
Self::Two => 2,
Self::Four | Self::Auto => 4,
}
}
const fn samples_per_packet(self) -> usize {
self.frames_per_packet() as usize * ftts_core::audio::SAMPLES_PER_FRAME
}
const fn default_for(profile: ExecutionProfile) -> Self {
match profile {
ExecutionProfile::Interactive => Self::One,
ExecutionProfile::Balanced => Self::Four,
ExecutionProfile::Throughput => Self::Auto,
ExecutionProfile::Strict => Self::Four,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum MathMode {
Strict,
Fast,
}
impl MathMode {
const fn as_str(self) -> &'static str {
match self {
Self::Strict => "strict",
Self::Fast => "fast",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum VoicePackProfile {
Portable,
Private,
Minimal,
}
impl VoicePackProfile {
const fn as_str(self) -> &'static str {
match self {
Self::Portable => "portable",
Self::Private => "private",
Self::Minimal => "minimal",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum NormalizeMode {
Verbatim,
Conservative,
LocaleAware,
}
impl NormalizeMode {
const fn as_str(self) -> &'static str {
match self {
Self::Verbatim => "verbatim",
Self::Conservative => "conservative",
Self::LocaleAware => "locale-aware",
}
}
}
impl From<NormalizeMode> for NormalizationMode {
fn from(mode: NormalizeMode) -> Self {
match mode {
NormalizeMode::Verbatim => Self::Verbatim,
NormalizeMode::Conservative => Self::Conservative,
NormalizeMode::LocaleAware => Self::LocaleAware,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum StreamMode {
Raw,
}
#[derive(Debug, Default)]
struct Environment {
values: BTreeMap<&'static str, Option<OsString>>,
stage_budget_values: BTreeMap<OsString, OsString>,
}
impl Environment {
fn from_process() -> Self {
let values = ENVIRONMENT_VARIABLES
.into_iter()
.map(|name| (name, std::env::var_os(name)))
.collect();
let stage_budget_values = std::env::vars_os()
.filter(|(name, _)| {
name.to_str().is_some_and(|name| {
name.starts_with("FTTS_STAGE_BUDGET_") && name.ends_with("_MS")
})
})
.collect();
Self {
values,
stage_budget_values,
}
}
fn value(&self, name: &'static str) -> Option<&str> {
self.values.get(name)?.as_deref()?.to_str()
}
fn documented_values(&self) -> BTreeMap<String, Option<String>> {
let mut values = self
.values
.iter()
.map(|(name, value)| {
(
(*name).to_owned(),
value
.as_ref()
.map(|value| value.to_string_lossy().into_owned()),
)
})
.collect::<BTreeMap<_, _>>();
values.insert("FTTS_STAGE_BUDGET_*_MS".to_owned(), None);
values.extend(self.stage_budget_values.iter().map(|(name, value)| {
(
name.to_string_lossy().into_owned(),
Some(value.to_string_lossy().into_owned()),
)
}));
values
}
}
fn environment() -> &'static Environment {
static ENVIRONMENT: OnceLock<Environment> = OnceLock::new();
ENVIRONMENT.get_or_init(Environment::from_process)
}
#[derive(Debug)]
struct EffectiveSettings {
profile: ExecutionProfile,
packet_frames: PacketFrames,
math_mode: MathMode,
voice_pack: VoicePackProfile,
normalize: NormalizeMode,
}
impl EffectiveSettings {
fn resolve(cli: &Cli, environment: &Environment) -> Result<Self, FttsError> {
let profile = cli
.profile
.or(parse_env_value(
environment.value("FTTS_PROFILE"),
"FTTS_PROFILE",
ExecutionProfile::value_variants(),
)?)
.unwrap_or(ExecutionProfile::Balanced);
let packet_frames = cli
.packet_frames
.or(parse_env_value(
environment.value("FTTS_PACKET_FRAMES"),
"FTTS_PACKET_FRAMES",
PacketFrames::value_variants(),
)?)
.unwrap_or_else(|| PacketFrames::default_for(profile));
let math_mode = cli
.math_mode
.or(parse_env_value(
environment.value("FTTS_MATH_MODE"),
"FTTS_MATH_MODE",
MathMode::value_variants(),
)?)
.unwrap_or(MathMode::Fast);
let voice_pack = cli.voice_pack.unwrap_or(VoicePackProfile::Portable);
let normalize = cli.normalize.unwrap_or(NormalizeMode::Verbatim);
Ok(Self {
profile,
packet_frames,
math_mode,
voice_pack,
normalize,
})
}
fn normalization_options(&self) -> NormalizationOptions {
NormalizationOptions {
mode: self.normalize.into(),
..NormalizationOptions::default()
}
}
}
fn parse_env_value<T>(
value: Option<&str>,
name: &str,
variants: &'static [T],
) -> Result<Option<T>, FttsError>
where
T: ValueEnum + Copy,
{
match value {
None => Ok(None),
Some(value) => T::from_str(value, true).map(Some).map_err(|_| {
let choices = variants
.iter()
.filter_map(|variant| variant.to_possible_value())
.map(|variant| variant.get_name().to_owned())
.collect::<Vec<_>>()
.join(", ");
FttsError::Usage(format!("invalid {name}={value:?}; use one of: {choices}"))
}),
}
}
fn dispatch(
cli: Cli,
environment: &Environment,
stdin: &mut dyn Read,
stdout: &mut dyn Write,
stderr: &mut dyn Write,
) -> Result<(), FttsError> {
match &cli.command {
Command::Say(args) => run_say(&cli, args, environment, stdin, stdout, stderr),
Command::Enroll(args) => run_enroll(args, environment, stdout),
Command::Voice(VoiceArgs {
command: VoiceCommand::Inspect { path },
}) => run_voice_inspect(path, stdout),
Command::Convert(args) => run_convert(&cli, args, environment, stdout, stderr),
Command::Pull(args) => run_pull(args, environment, stdout),
Command::Robot(args) => run_robot(args.command.clone(), environment, stdout),
Command::Doctor(args) => run_doctor(args, environment, stdout),
}
}
#[derive(Clone, Debug)]
struct PinnedMainTensor {
name: String,
dtype: Dtype,
shape: Vec<usize>,
access_class: AccessClass,
storage: TensorStoragePolicy,
}
fn run_convert(
cli: &Cli,
args: &ConvertArgs,
environment: &Environment,
stdout: &mut dyn Write,
stderr: &mut dyn Write,
) -> Result<(), FttsError> {
let run = robot::RunContext::generate();
let outcome = run_convert_events(cli, args, environment, &run, &mut |event| {
write_json_line(stdout, event)
});
if let Err(error) = &outcome {
let mut event = run.event(robot::EventType::RunError);
event.insert("exit_code".to_owned(), json!(error.exit_code().as_u8()));
event.insert("kind".to_owned(), json!(error.exit_code().description()));
event.insert("message".to_owned(), json!(error.to_string()));
event.insert("remediation".to_owned(), json!(error.remediation()));
event.insert("elapsed_ms".to_owned(), json!(run.elapsed_ms()));
write_json_line(stderr, &Value::Object(event))?;
}
outcome
}
fn run_convert_events(
cli: &Cli,
args: &ConvertArgs,
environment: &Environment,
run: &robot::RunContext,
emit: &mut dyn FnMut(&Value) -> Result<(), FttsError>,
) -> Result<(), FttsError> {
let settings = EffectiveSettings::resolve(cli, environment)?;
let mut start = run.event(robot::EventType::RunStart);
start.insert("command".to_owned(), json!("convert"));
start.insert("profile".to_owned(), json!(settings.profile.as_str()));
start.insert(
"packet_frames".to_owned(),
json!(settings.packet_frames.as_str()),
);
start.insert("math_mode".to_owned(), json!(settings.math_mode.as_str()));
start.insert("stateless".to_owned(), json!(true));
start.insert("seed".to_owned(), json!(cli.seed));
start.insert("model".to_owned(), Value::Null);
start.insert("voice".to_owned(), Value::Null);
emit(&Value::Object(start))?;
let mut seq = 0_u64;
emit_stage(run, emit, "source_preflight", "begin", &mut seq)?;
let source = resolve_pinned_main_source(&args.source)?;
let mapping = MappedFile::open(&source).map_err(|error| {
FttsError::Input(format!(
"cannot memory-map pinned source checkpoint {}: {error}",
source.display()
))
})?;
let (manifest, plan) = pinned_main_conversion_plan()?;
let staging = conversion_staging_path(&args.output)?;
emit_stage(run, emit, "source_preflight", "end", &mut seq)?;
emit_stage(run, emit, "convert", "begin", &mut seq)?;
let destination = std::fs::File::options()
.write(true)
.create_new(true)
.open(&staging)
.map_err(|error| {
FttsError::Input(format!(
"cannot create conversion staging artifact {}: {error}; the output path is never overwritten",
staging.display()
))
})?;
let destination = convert_safetensors_streaming(
mapping.as_slice(),
&manifest,
&plan,
destination,
)
.map_err(|error| {
FttsError::ArtifactFormat(format!(
"conversion failed before publication: {error}; staging artifact retained at {}",
staging.display()
))
})?;
destination.sync_all().map_err(|error| {
FttsError::ArtifactFormat(format!(
"cannot sync converted artifact at {}: {error}; staging artifact retained",
staging.display()
))
})?;
drop(destination);
emit_stage(run, emit, "convert", "end", &mut seq)?;
emit_stage(run, emit, "verify", "begin", &mut seq)?;
let verified = MappedFttsq::open(&staging).map_err(|error| {
FttsError::ArtifactFormat(format!(
"converted staging artifact did not pass digest re-read: {error}; retained at {}",
staging.display()
))
})?;
if verified.reader().source_sha256() != PINNED_MAIN_WEIGHTS_SHA256 {
return Err(FttsError::ArtifactFormat(format!(
"converted staging artifact recorded an unexpected source digest {}; retained at {}",
verified.reader().source_sha256(),
staging.display()
)));
}
drop(verified);
std::fs::rename(&staging, &args.output).map_err(|error| {
FttsError::ArtifactFormat(format!(
"converted artifact verified but could not be published from {} to {}: {error}; staging artifact retained",
staging.display(),
args.output.display()
))
})?;
emit_stage(run, emit, "verify", "end", &mut seq)?;
let mut complete = run.event(robot::EventType::RunComplete);
complete.insert("exit_code".to_owned(), json!(FttsExitCode::Success.as_u8()));
complete.insert("elapsed_ms".to_owned(), json!(run.elapsed_ms()));
complete.insert("frames".to_owned(), json!(0));
complete.insert("audio_bytes".to_owned(), json!(0));
emit(&Value::Object(complete))
}
fn resolve_pinned_main_source(source: &Path) -> Result<PathBuf, FttsError> {
let source = if source.is_dir() {
source.join(PINNED_MAIN_WEIGHTS_FILENAME)
} else {
source.to_owned()
};
if !source.is_file() {
return Err(FttsError::Input(format!(
"pinned main checkpoint {} does not exist or is not a file; pass model.safetensors or its containing directory",
source.display()
)));
}
if source.file_name().and_then(|name| name.to_str()) != Some(PINNED_MAIN_WEIGHTS_FILENAME) {
return Err(FttsError::Input(format!(
"this converter accepts the pinned main checkpoint named {PINNED_MAIN_WEIGHTS_FILENAME}, not {}",
source.display()
)));
}
Ok(source)
}
fn conversion_staging_path(output: &Path) -> Result<PathBuf, FttsError> {
if output.exists() {
return Err(FttsError::Input(format!(
"refusing to overwrite existing output {}; choose a new -o path",
output.display()
)));
}
let parent = output.parent().unwrap_or_else(|| Path::new("."));
let file_name = output
.file_name()
.and_then(|name| name.to_str())
.ok_or_else(|| {
FttsError::Usage("conversion output must name a file, not a directory".to_owned())
})?;
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|error| FttsError::Generic(format!("system clock is before UNIX_EPOCH: {error}")))?
.as_nanos();
let staging = parent.join(format!(
".{file_name}.fttsq-converting-{}-{nonce}",
std::process::id()
));
if staging.exists() {
return Err(FttsError::Input(format!(
"conversion staging path already exists {}; inspect or move it before retrying",
staging.display()
)));
}
Ok(staging)
}
fn pinned_main_conversion_plan() -> Result<(WeightsManifest, StreamingConversionPlan), FttsError> {
let specs = pinned_main_tensor_specs()?;
let manifest = WeightsManifest::from_expectations(
"Qwen/Qwen3-TTS-12Hz-0.6B-Base main checkpoint",
specs
.iter()
.map(|spec| ExpectedTensor::new(&spec.name, spec.shape.clone(), spec.dtype)),
);
let model_config = serde_json::from_str(PINNED_MODEL_CONFIG).map_err(|error| {
FttsError::Generic(format!(
"checked-in pinned model config is invalid JSON: {error}"
))
})?;
let q8_count = specs
.iter()
.filter(|spec| spec.storage == TensorStoragePolicy::Q8PerOutputChannel)
.count();
let mut plan = StreamingConversionPlan::new(
"qwen3-tts-12hz-0.6b-base",
PINNED_MAIN_WEIGHTS_SHA256,
)
.license_notice(pinned_license_notice())
.model_config(model_config)
.quantization_manifest(json!({
"source": {
"repository": "Qwen/Qwen3-TTS-12Hz-0.6B-Base",
"revision": PINNED_MODEL_REVISION,
"file": PINNED_MAIN_WEIGHTS_FILENAME,
"sha256": PINNED_MAIN_WEIGHTS_SHA256,
},
"q8_recipe": "symmetric per-output-channel int8; zero_point=0; scale=max_abs(row)/127",
"q8_tensor_count": q8_count,
"verbatim_tensor_count": specs.len() - q8_count,
"q8_scope": "talker and residual-code-microdecoder attention/MLP projection matrices only",
"verbatim_scope": "norms, heads, embeddings, speaker path, and every tensor outside the reviewed Q8 projection set",
}));
for spec in specs {
let conversion = match spec.storage {
TensorStoragePolicy::Verbatim => {
TensorConversion::verbatim(&spec.name, &spec.name, spec.access_class)
}
TensorStoragePolicy::Q8PerOutputChannel => {
TensorConversion::q8_per_output_channel(&spec.name, &spec.name, spec.access_class)
}
};
plan = plan.tensor(conversion);
}
Ok((manifest, plan))
}
fn pinned_main_tensor_specs() -> Result<Vec<PinnedMainTensor>, FttsError> {
let inventory: Value = serde_json::from_str(PINNED_TENSOR_INVENTORY).map_err(|error| {
FttsError::Generic(format!(
"checked-in tensor inventory is invalid JSON: {error}"
))
})?;
if inventory.get("source_pin").and_then(Value::as_str)
!= Some(&format!(
"Qwen/Qwen3-TTS-12Hz-0.6B-Base@{PINNED_MODEL_REVISION}"
))
{
return Err(FttsError::Generic(
"checked-in tensor inventory does not name the pinned Qwen3-TTS revision".to_owned(),
));
}
let records = inventory
.get("tensors")
.and_then(Value::as_array)
.ok_or_else(|| {
FttsError::Generic("checked-in tensor inventory lacks tensors[]".to_owned())
})?;
let mut specs = Vec::new();
for record in records {
if record.get("source").and_then(Value::as_str) != Some(PINNED_MAIN_WEIGHTS_FILENAME) {
continue;
}
let name = required_inventory_string(record, "name")?.to_owned();
let dtype = match required_inventory_string(record, "dtype")? {
"BF16" => Dtype::Bf16,
"F32" => Dtype::F32,
other => {
return Err(FttsError::Generic(format!(
"pinned main inventory has unsupported dtype {other:?} for {name}"
)));
}
};
let shape = record
.get("shape")
.and_then(Value::as_array)
.ok_or_else(|| {
FttsError::Generic(format!("pinned inventory tensor {name} lacks shape[]"))
})?
.iter()
.map(|dimension| {
dimension
.as_u64()
.and_then(|dimension| usize::try_from(dimension).ok())
.ok_or_else(|| {
FttsError::Generic(format!(
"pinned inventory tensor {name} has a non-usize shape dimension"
))
})
})
.collect::<Result<Vec<_>, _>>()?;
let storage = if is_q8_projection(&name) {
TensorStoragePolicy::Q8PerOutputChannel
} else {
TensorStoragePolicy::Verbatim
};
specs.push(PinnedMainTensor {
access_class: main_access_class(&name)?,
name,
dtype,
shape,
storage,
});
}
if specs.len() != PINNED_MAIN_TENSOR_COUNT {
return Err(FttsError::Generic(format!(
"pinned main inventory contains {} tensors, expected {PINNED_MAIN_TENSOR_COUNT}",
specs.len()
)));
}
Ok(specs)
}
fn required_inventory_string<'a>(record: &'a Value, field: &str) -> Result<&'a str, FttsError> {
record.get(field).and_then(Value::as_str).ok_or_else(|| {
FttsError::Generic(format!(
"checked-in tensor inventory record lacks string {field:?}"
))
})
}
fn is_q8_projection(name: &str) -> bool {
(name.starts_with("talker.model.layers.")
|| name.starts_with("talker.code_predictor.model.layers."))
&& [
".self_attn.q_proj.weight",
".self_attn.k_proj.weight",
".self_attn.v_proj.weight",
".self_attn.o_proj.weight",
".mlp.gate_proj.weight",
".mlp.up_proj.weight",
".mlp.down_proj.weight",
]
.iter()
.any(|suffix| name.ends_with(suffix))
}
fn main_access_class(name: &str) -> Result<AccessClass, FttsError> {
if name == "talker.model.text_embedding.weight" {
Ok(AccessClass::ColdTextEmbedding)
} else if name.starts_with("speaker_encoder.") {
Ok(AccessClass::EnrollmentSpeakerEncoder)
} else if name.starts_with("talker.code_predictor.")
|| name == "talker.model.codec_embedding.weight"
{
Ok(AccessClass::HotRecurrentMicrodecoder)
} else if name.starts_with("talker.model.")
|| name.starts_with("talker.codec_head.")
|| name.starts_with("talker.text_projection.")
{
Ok(AccessClass::HotRecurrentTalker)
} else {
Err(FttsError::Generic(format!(
"pinned main tensor {name} has no reviewed access-class assignment"
)))
}
}
fn pinned_license_notice() -> String {
format!(
"This artifact contains model weights derived from\n\
Qwen3-TTS-12Hz-0.6B-Base (https://huggingface.co/Qwen/Qwen3-TTS-12Hz-0.6B-Base)\n\
and code derived from QwenLM/Qwen3-TTS (https://github.com/QwenLM/Qwen3-TTS).\n\n\
Copyright 2026 Alibaba Cloud\n\n\
Licensed under the Apache License, Version 2.0.\n\
http://www.apache.org/licenses/LICENSE-2.0\n\n\
CHANGES: the original bfloat16 weights were converted to franken_tts's\n\
quantized .fttsq container. Tensors were requantized according to the\n\
artifact's quantization manifest; protected tensors remain verbatim.\n\
The model graph is re-implemented in Rust.\n\n\
Apache License, Version 2.0:\n\n{APACHE_LICENSE}"
)
}
fn run_say(
cli: &Cli,
args: &SayArgs,
environment: &Environment,
stdin: &mut dyn Read,
stdout: &mut dyn Write,
stderr: &mut dyn Write,
) -> Result<(), FttsError> {
let run = robot::RunContext::generate();
let outcome = if args.stream == Some(StreamMode::Raw) {
run_say_events(cli, args, environment, stdin, &run, stdout, &mut |event| {
write_json_line(stderr, event)
})
} else {
let mut discard = io::sink();
run_say_events(
cli,
args,
environment,
stdin,
&run,
&mut discard,
&mut |event| write_json_line(stdout, event),
)
};
if let Err(error) = &outcome {
let mut event = run.event(robot::EventType::RunError);
event.insert("exit_code".to_owned(), json!(error.exit_code().as_u8()));
event.insert("kind".to_owned(), json!(error.exit_code().description()));
event.insert("message".to_owned(), json!(error.to_string()));
event.insert("remediation".to_owned(), json!(error.remediation()));
event.insert("elapsed_ms".to_owned(), json!(run.elapsed_ms()));
write_json_line(stderr, &Value::Object(event))?;
}
outcome
}
fn emit_stage(
run: &robot::RunContext,
emit: &mut dyn FnMut(&Value) -> Result<(), FttsError>,
name: &str,
state: &str,
seq: &mut u64,
) -> Result<(), FttsError> {
let mut event = run.event(robot::EventType::Stage);
event.insert("name".to_owned(), json!(name));
event.insert("seq".to_owned(), json!(*seq));
event.insert("state".to_owned(), json!(state));
event.insert("elapsed_ms".to_owned(), json!(run.elapsed_ms()));
event.insert("budget_ms".to_owned(), Value::Null);
*seq += 1;
emit(&Value::Object(event))
}
fn run_say_events(
cli: &Cli,
args: &SayArgs,
environment: &Environment,
stdin: &mut dyn Read,
run: &robot::RunContext,
raw_audio: &mut dyn Write,
emit: &mut dyn FnMut(&Value) -> Result<(), FttsError>,
) -> Result<(), FttsError> {
let settings = EffectiveSettings::resolve(cli, environment)?;
let mut start = run.event(robot::EventType::RunStart);
start.insert("command".to_owned(), json!("say"));
start.insert("profile".to_owned(), json!(settings.profile.as_str()));
start.insert(
"packet_frames".to_owned(),
json!(settings.packet_frames.as_str()),
);
start.insert("math_mode".to_owned(), json!(settings.math_mode.as_str()));
start.insert("stateless".to_owned(), json!(true));
start.insert("seed".to_owned(), json!(cli.seed));
start.insert("model".to_owned(), json!(args.model.as_deref()));
start.insert(
"voice".to_owned(),
json!(args.voice.as_ref().map(|path| path.display().to_string())),
);
emit(&Value::Object(start))?;
let mut seq = 0u64;
emit_stage(run, emit, "resolve", "begin", &mut seq)?;
let text = read_text(args, stdin)?;
let model = resolve_model(args.model.as_deref(), environment)?;
let voice = resolve_requested_voice(args.voice.as_deref(), environment)?;
emit_stage(run, emit, "resolve", "end", &mut seq)?;
let request = SynthesisRequest::new(text)
.with_normalization_options(settings.normalization_options())
.with_normalization_trace(cli.trace.is_some());
let mut prepared = run.event(robot::EventType::TextPrepared);
prepared.insert("normalize".to_owned(), json!(settings.normalize.as_str()));
prepared.insert(
"unicode_version".to_owned(),
json!(ftts_model_qwen::tokenizer::unicode_version()),
);
prepared.insert("char_count".to_owned(), json!(request.text.chars().count()));
prepared.insert(
"trace_requested".to_owned(),
json!(request.trace_normalization),
);
emit(&Value::Object(prepared))?;
let requested_output: Option<PathBuf> = args
.output
.clone()
.or_else(|| args.output_positional.clone());
let output_plan = requested_output
.as_deref()
.map(OutputPlan::for_path)
.transpose()?;
emit_stage(run, emit, "admission", "begin", &mut seq)?;
let admission = admission_plan(&request.text, &settings)?;
emit_stage(run, emit, "admission", "end", &mut seq)?;
if args.check {
let event = json!({
"schema_version": ROBOT_SCHEMA_VERSION,
"event": "check_complete",
"run_id": run.run_id(),
"model": model,
"voice": voice,
"profile": settings.profile.as_str(),
"packet_frames": settings.packet_frames.as_str(),
"math_mode": settings.math_mode.as_str(),
"voice_pack": settings.voice_pack.as_str(),
"normalize": settings.normalize.as_str(),
"normalization_trace_requested": request.trace_normalization,
"seed": cli.seed,
"trace": cli.trace.as_ref().map(|path| path.display().to_string()),
"output": requested_output.as_ref().map(|path| path.display().to_string()),
"admission": admission,
});
emit(&event)?;
let mut complete = run.event(robot::EventType::RunComplete);
complete.insert("exit_code".to_owned(), json!(FttsExitCode::Success.as_u8()));
complete.insert("elapsed_ms".to_owned(), json!(run.elapsed_ms()));
complete.insert("frames".to_owned(), json!(0));
complete.insert("audio_bytes".to_owned(), json!(0));
emit(&Value::Object(complete))?;
return Ok(());
}
let raw_stream = args.stream == Some(StreamMode::Raw);
let mut audio = match (&output_plan, raw_stream) {
(Some(plan), false) => AudioOutput::wav(&plan.wav_path)?,
(None, true) => AudioOutput::raw(),
(None, false) => {
return Err(FttsError::Usage(
"`ftts say` has nowhere to put the audio; add an output path (`ftts say \"text\" \
out.wav`, or `-o PATH`) or `--stream raw` for PCM on stdout"
.to_owned(),
));
}
(Some(_), true) => unreachable!("clap enforces the conflict"),
};
emit_stage(run, emit, "load", "begin", &mut seq)?;
let bundle = synth::ModelBundle::resolve(Path::new(&model))?;
let voice_path = voice
.as_deref()
.map(PathBuf::from)
.or_else(|| {
let candidate = bundle.root.join("default.spk");
candidate.is_file().then_some(candidate)
})
.ok_or_else(|| {
FttsError::Usage(
"`ftts say` needs a real voice source; pass --voice PATH, set \
FTTS_DEFAULT_VOICE=PATH, or run `ftts enroll REF.wav --model MODEL_DIR --default`. \
Qwen3-TTS Base has no preset speaker, so a missing default is refused rather than \
fabricated."
.to_owned(),
)
})?;
let speaker = synth::speaker_from_voice(&bundle, &voice_path)?;
let loaded = synth::LoadedModel::load(&bundle)?;
emit_stage(run, emit, "load", "end", &mut seq)?;
let observer = |_event: ftts_core::SynthesisEvent| {};
emit_stage(run, emit, "synthesis", "begin", &mut seq)?;
let engine = ftts_core::TtsEngine::from_process_environment()
.map_err(|error| FttsError::Generic(format!("cannot start the engine: {error}")))?;
let cancellation = ftts_core::CancellationToken::new();
let audio_result = synth::synthesize(
&loaded,
&engine,
&request,
&speaker,
cli.seed.unwrap_or(0),
&cancellation,
&observer,
)?;
emit_stage(run, emit, "synthesis", "end", &mut seq)?;
let packet_samples = settings.packet_frames.samples_per_packet();
let packet_frame_count = settings.packet_frames.frames_per_packet();
emit_stage(run, emit, "output", "begin", &mut seq)?;
for packet in audio_result.pcm.chunks(packet_samples) {
let event = audio.write_packet(packet, raw_audio, run.run_id(), packet_frame_count)?;
emit(&event)?;
}
let audio_bytes = audio.byte_offset();
let samples = audio.finish()?;
if let Some(plan) = &output_plan {
plan.finalize()?;
}
emit_stage(run, emit, "output", "end", &mut seq)?;
let mut complete = run.event(robot::EventType::RunComplete);
complete.insert("exit_code".to_owned(), json!(FttsExitCode::Success.as_u8()));
complete.insert("elapsed_ms".to_owned(), json!(run.elapsed_ms()));
complete.insert("frames".to_owned(), json!(audio_result.frames));
complete.insert("audio_bytes".to_owned(), json!(audio_bytes));
complete.insert("samples".to_owned(), json!(samples));
complete.insert(
"duration_ms".to_owned(),
json!(samples * 1000 / u64::from(ftts_core::audio::SAMPLE_RATE_HZ)),
);
complete.insert(
"prepared_token_count".to_owned(),
json!(audio_result.prepared_token_count),
);
emit(&Value::Object(complete))?;
Ok(())
}
pub enum AudioSink {
Wav(Box<ftts_core::audio::WavWriter<fs::File>>),
RawPcm,
None,
}
pub struct AudioOutput {
sink: AudioSink,
byte_offset: u64,
samples_written: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum OutputFormat {
Wav,
M4a,
Mp3,
Flac,
}
#[derive(Clone, Debug)]
struct OutputPlan {
final_path: PathBuf,
wav_path: PathBuf,
format: OutputFormat,
}
impl OutputPlan {
fn for_path(path: &Path) -> Result<Self, FttsError> {
let extension = path
.extension()
.and_then(|extension| extension.to_str())
.map(str::to_ascii_lowercase);
let format = match extension.as_deref() {
Some("wav") | None => OutputFormat::Wav,
Some("m4a" | "aac") => OutputFormat::M4a,
Some("mp3") => OutputFormat::Mp3,
Some("flac") => OutputFormat::Flac,
Some(other) => {
return Err(FttsError::Usage(format!(
"unsupported output extension `.{other}`; use .wav (native), .m4a, .mp3, or \
.flac (system encoder)"
)));
}
};
let wav_path = if format == OutputFormat::Wav {
path.to_path_buf()
} else {
let mut staging = path.as_os_str().to_owned();
staging.push(".ftts-staging.wav");
PathBuf::from(staging)
};
Ok(Self {
final_path: path.to_path_buf(),
wav_path,
format,
})
}
fn finalize(&self) -> Result<(), FttsError> {
if self.format == OutputFormat::Wav {
return Ok(());
}
let wav = self.wav_path.as_os_str();
let target = self.final_path.as_os_str();
let attempts: &[(&str, Vec<&std::ffi::OsStr>)] = &match self.format {
OutputFormat::M4a => [
(
"afconvert",
vec![
"-f".as_ref(),
"m4af".as_ref(),
"-d".as_ref(),
"aac".as_ref(),
wav,
target,
],
),
(
"ffmpeg",
vec![
"-y".as_ref(),
"-loglevel".as_ref(),
"error".as_ref(),
"-i".as_ref(),
wav,
"-c:a".as_ref(),
"aac".as_ref(),
target,
],
),
],
OutputFormat::Mp3 => [
(
"lame",
vec!["--quiet".as_ref(), "-V2".as_ref(), wav, target],
),
(
"ffmpeg",
vec![
"-y".as_ref(),
"-loglevel".as_ref(),
"error".as_ref(),
"-i".as_ref(),
wav,
"-codec:a".as_ref(),
"libmp3lame".as_ref(),
"-q:a".as_ref(),
"2".as_ref(),
target,
],
),
],
OutputFormat::Flac => [
(
"flac",
vec![
"--totally-silent".as_ref(),
"-f".as_ref(),
"-o".as_ref(),
target,
wav,
],
),
(
"ffmpeg",
vec![
"-y".as_ref(),
"-loglevel".as_ref(),
"error".as_ref(),
"-i".as_ref(),
wav,
"-c:a".as_ref(),
"flac".as_ref(),
target,
],
),
],
OutputFormat::Wav => unreachable!("handled above"),
};
let mut tried = Vec::new();
for (tool, arguments) in attempts {
match std::process::Command::new(tool).args(arguments).status() {
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
tried.push(*tool);
}
Err(error) => {
return Err(FttsError::Generic(format!(
"audio encoder `{tool}` could not run: {error}; the synthesized WAV is \
preserved at {}",
self.wav_path.display()
)));
}
Ok(status) if status.success() => {
let _ = fs::remove_file(&self.wav_path);
return Ok(());
}
Ok(status) => {
return Err(FttsError::Generic(format!(
"audio encoder `{tool}` exited with {status}; the synthesized WAV is \
preserved at {}",
self.wav_path.display()
)));
}
}
}
Err(FttsError::Generic(format!(
"no system audio encoder found for {} (tried: {}); install one or use a .wav output. \
The synthesized WAV is preserved at {}",
self.final_path.display(),
tried.join(", "),
self.wav_path.display()
)))
}
}
impl AudioOutput {
pub fn wav(path: &Path) -> Result<Self, FttsError> {
let file = fs::File::create(path).map_err(|error| {
FttsError::Generic(format!(
"cannot create audio output {}: {error}",
path.display()
))
})?;
let writer = ftts_core::audio::WavWriter::new(file, ftts_core::audio::SAMPLE_RATE_HZ)
.map_err(|error| {
FttsError::Generic(format!(
"cannot write WAV header to {}: {error}",
path.display()
))
})?;
Ok(Self {
sink: AudioSink::Wav(Box::new(writer)),
byte_offset: 0,
samples_written: 0,
})
}
#[must_use]
pub const fn raw() -> Self {
Self {
sink: AudioSink::RawPcm,
byte_offset: 0,
samples_written: 0,
}
}
#[must_use]
pub const fn none() -> Self {
Self {
sink: AudioSink::None,
byte_offset: 0,
samples_written: 0,
}
}
#[must_use]
pub const fn sink_name(&self) -> &'static str {
match self.sink {
AudioSink::Wav(_) => "file",
AudioSink::RawPcm => "stdout",
AudioSink::None => "none",
}
}
#[must_use]
pub const fn byte_offset(&self) -> u64 {
self.byte_offset
}
pub fn write_packet(
&mut self,
pcm: &[f32],
raw: &mut dyn Write,
run_id: &str,
frame_count: u8,
) -> Result<Value, FttsError> {
let offset_before = self.byte_offset;
let bytes = (pcm.len() * 2) as u64;
match &mut self.sink {
AudioSink::Wav(writer) => writer.write_samples(pcm).map_err(|error| {
FttsError::Generic(format!("cannot write audio samples: {error}"))
})?,
AudioSink::RawPcm => {
let mut buffer = Vec::with_capacity(pcm.len() * 2);
for sample in pcm {
buffer
.extend_from_slice(&ftts_core::audio::sample_to_i16(*sample).to_le_bytes());
}
raw.write_all(&buffer).map_err(|error| {
FttsError::Generic(format!("cannot write raw PCM: {error}"))
})?;
}
AudioSink::None => {}
}
self.byte_offset += bytes;
self.samples_written += pcm.len() as u64;
let mut event = robot::EventType::AudioChunk.event();
event.insert("run_id".to_owned(), json!(run_id));
event.insert("byte_offset".to_owned(), json!(offset_before));
event.insert("bytes".to_owned(), json!(bytes));
event.insert(
"duration_ms".to_owned(),
json!((pcm.len() as u64) * 1000 / u64::from(ftts_core::audio::SAMPLE_RATE_HZ.max(1))),
);
event.insert("packet_frames".to_owned(), json!(frame_count.to_string()));
event.insert("sink".to_owned(), json!(self.sink_name()));
Ok(Value::Object(event))
}
pub fn finish(self) -> Result<u64, FttsError> {
let samples = self.samples_written;
if let AudioSink::Wav(writer) = self.sink {
writer.finish().map_err(|error| {
FttsError::Generic(format!("cannot finalize the WAV header: {error}"))
})?;
}
Ok(samples)
}
}
fn read_text(args: &SayArgs, stdin: &mut dyn Read) -> Result<String, FttsError> {
let text = match (&args.text, &args.file) {
(Some(text), None) if text == "-" => read_utf8(stdin, "stdin")?,
(Some(text), None) => text.clone(),
(None, Some(path)) if path == Path::new("-") => read_utf8(stdin, "stdin")?,
(None, Some(path)) => fs::read_to_string(path).map_err(|error| {
FttsError::Input(format!(
"cannot read text file {}: {error}; use `ftts say --file PATH --check --model PATH`",
path.display()
))
})?,
(None, None) => {
return Err(FttsError::Usage(
"missing text; use `ftts say TEXT`, `ftts say --file PATH`, or `ftts say -`".to_owned(),
));
}
(Some(_), Some(_)) => unreachable!("clap enforces the conflict"),
};
if text.trim().is_empty() {
return Err(FttsError::Input(
"text is empty; provide non-whitespace UTF-8 text to `ftts say`".to_owned(),
));
}
Ok(text)
}
fn read_utf8(reader: &mut dyn Read, source: &str) -> Result<String, FttsError> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).map_err(|error| {
FttsError::Input(format!(
"cannot read {source}: {error}; retry with readable UTF-8 input"
))
})?;
String::from_utf8(bytes).map_err(|error| {
FttsError::Input(format!(
"{source} is not valid UTF-8: {error}; transcode it before `ftts say`"
))
})
}
fn resolve_model(explicit: Option<&Path>, environment: &Environment) -> Result<String, FttsError> {
resolve_model_from(
explicit,
&model_search_paths(environment),
default_pull_model_dir(environment).as_deref(),
)
}
fn resolve_model_from(
explicit: Option<&Path>,
searched: &[PathBuf],
pull_dir: Option<&Path>,
) -> Result<String, FttsError> {
if let Some(path) = explicit {
if path.is_dir() {
return Ok(path.display().to_string());
}
return resolve_existing_file(path, "model artifact")
.map(|path| path.display().to_string());
}
if let Some(path) = searched.iter().find(|path| path.is_file()) {
return Ok(path.display().to_string());
}
if let Some(directory) = searched
.iter()
.filter_map(|path| path.parent())
.find(|directory| directory.join("model.safetensors").is_file())
{
return Ok(directory.display().to_string());
}
if let Some(directory) = pull_dir
&& directory.is_dir()
&& synth::ModelBundle::resolve(directory).is_ok()
{
return Ok(directory.display().to_string());
}
let searched = searched
.iter()
.map(|path| path.display().to_string())
.collect::<Vec<_>>()
.join(", ");
Err(FttsError::ModelNotFound(format!(
"no model artifact was found; searched: [{searched}]; run `ftts pull` to fetch the model \
(~2.0 GB), or pass --model PATH or set FTTS_MODEL_DIR"
)))
}
fn resolve_optional_file(path: Option<&Path>, label: &str) -> Result<Option<String>, FttsError> {
path.map(|path| resolve_existing_file(path, label).map(|path| path.display().to_string()))
.transpose()
}
fn resolve_requested_voice(
explicit: Option<&Path>,
environment: &Environment,
) -> Result<Option<String>, FttsError> {
if let Some(path) = explicit {
return resolve_optional_file(Some(path), "voice source");
}
environment
.value("FTTS_DEFAULT_VOICE")
.map(Path::new)
.map(|path| resolve_existing_file(path, "FTTS_DEFAULT_VOICE"))
.transpose()
.map(|path| path.map(|path| path.display().to_string()))
}
fn run_enroll(
args: &EnrollArgs,
environment: &Environment,
stdout: &mut dyn Write,
) -> Result<(), FttsError> {
let _ = args.force;
let model = resolve_model(args.model.as_deref(), environment)?;
let bundle = synth::ModelBundle::resolve(Path::new(&model))?;
let output = match (&args.output, args.default) {
(Some(path), false) => path.clone(),
(None, true) => bundle.root.join("default.spk"),
(None, false) => {
return Err(FttsError::Usage(
"`ftts enroll` needs -o PATH or --default; enrollment never overwrites a voice source"
.to_owned(),
));
}
(Some(_), true) => unreachable!("clap enforces the conflict"),
};
let speaker = synth::speaker_from_voice(&bundle, &args.reference_audio)?;
synth::write_speaker_vector_new(&output, &speaker)?;
writeln!(
stdout,
"enrolled x-vector from {} to {}{}",
args.reference_audio.display(),
output.display(),
if args.default {
"; `ftts say` will use it when --voice is absent"
} else {
""
},
)
.map_err(|error| FttsError::Generic(format!("cannot write enrollment result: {error}")))
}
#[derive(Clone, Debug)]
struct ModelManifestFile {
asset: String,
dest: String,
sha256: String,
bytes: u64,
}
#[derive(Clone, Debug)]
struct ModelManifest {
model_id: String,
release_tag: String,
repo: String,
files: Vec<ModelManifestFile>,
}
impl ModelManifest {
fn embedded() -> Result<Self, FttsError> {
Self::parse(PINNED_MODEL_MANIFEST)
}
fn parse(text: &str) -> Result<Self, FttsError> {
let value: Value = serde_json::from_str(text).map_err(|error| {
FttsError::ArtifactFormat(format!("model manifest is not valid JSON: {error}"))
})?;
if value["schema_version"].as_u64() != Some(1) {
return Err(FttsError::ArtifactFormat(format!(
"model manifest schema_version {} is not the supported 1",
value["schema_version"]
)));
}
let model_id = manifest_string(&value, "model_id")?;
let release_tag = manifest_string(&value, "release_tag")?;
let repo = manifest_string(&value, "repo")?;
let files = value["files"]
.as_array()
.filter(|files| !files.is_empty())
.ok_or_else(|| {
FttsError::ArtifactFormat("model manifest needs a non-empty files array".to_owned())
})?
.iter()
.map(parse_manifest_file)
.collect::<Result<Vec<_>, _>>()?;
Ok(Self {
model_id,
release_tag,
repo,
files,
})
}
fn download_url(&self, file: &ModelManifestFile) -> String {
format!(
"https://github.com/{}/releases/download/{}/{}",
self.repo, self.release_tag, file.asset
)
}
fn total_bytes(&self) -> u64 {
self.files.iter().map(|file| file.bytes).sum()
}
}
fn manifest_string(value: &Value, field: &str) -> Result<String, FttsError> {
value[field]
.as_str()
.filter(|text| !text.is_empty())
.map(str::to_owned)
.ok_or_else(|| {
FttsError::ArtifactFormat(format!(
"model manifest field {field} must be a non-empty string"
))
})
}
fn parse_manifest_file(value: &Value) -> Result<ModelManifestFile, FttsError> {
let asset = manifest_string(value, "asset")?;
if asset.contains('/') || asset.contains('\\') {
return Err(FttsError::ArtifactFormat(format!(
"manifest asset {asset:?} must be a bare release-asset name"
)));
}
let dest = manifest_string(value, "dest")?;
validate_manifest_dest(&dest)?;
let sha256 = manifest_string(value, "sha256")?;
if !is_sha256_hex(&sha256) {
return Err(FttsError::ArtifactFormat(format!(
"manifest sha256 for {asset} must be 64 lowercase hex characters"
)));
}
let bytes = value["bytes"]
.as_u64()
.filter(|bytes| *bytes > 0)
.ok_or_else(|| {
FttsError::ArtifactFormat(format!(
"manifest bytes for {asset} must be a positive integer"
))
})?;
Ok(ModelManifestFile {
asset,
dest,
sha256,
bytes,
})
}
fn validate_manifest_dest(dest: &str) -> Result<(), FttsError> {
let path = Path::new(dest);
let traversal_free = path
.components()
.all(|component| matches!(component, std::path::Component::Normal(_)));
if path.is_absolute() || dest.contains('\\') || !traversal_free {
return Err(FttsError::ArtifactFormat(format!(
"manifest dest {dest:?} must be a relative path with no traversal; it is joined under the model directory"
)));
}
Ok(())
}
fn is_sha256_hex(text: &str) -> bool {
text.len() == 64
&& text
.bytes()
.all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f'))
}
fn default_pull_model_dir(environment: &Environment) -> Option<PathBuf> {
if let Some(first) = environment
.value("FTTS_MODEL_DIR")
.and_then(|dirs| std::env::split_paths(dirs).next())
.filter(|path| !path.as_os_str().is_empty())
{
return Some(first);
}
std::env::var_os("HOME").map(|home| PathBuf::from(home).join(DEFAULT_MODEL_CACHE_SUBDIR))
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum PullDecision {
Skip,
Download,
}
fn pull_decision(dest: &Path, file: &ModelManifestFile, force: bool) -> PullDecision {
if force {
return PullDecision::Download;
}
let Ok(metadata) = fs::metadata(dest) else {
return PullDecision::Download;
};
if !metadata.is_file() || metadata.len() != file.bytes {
return PullDecision::Download;
}
match ftts_artifacts::sha256::hex_digest_file(dest) {
Ok(digest) if digest == file.sha256 => PullDecision::Skip,
_ => PullDecision::Download,
}
}
fn download_with_curl(url: &str, staging: &Path) -> Result<(), FttsError> {
let outcome = std::process::Command::new("curl")
.args(["-L", "--fail", "--retry", "3", "-sS", "-o"])
.arg(staging)
.arg(url)
.status();
match outcome {
Err(error) if error.kind() == io::ErrorKind::NotFound => Err(FttsError::Generic(
"`ftts pull` downloads with the system `curl`, which was not found on PATH; \
install curl and retry, or download the release assets by hand"
.to_owned(),
)),
Err(error) => Err(FttsError::Generic(format!("cannot run curl: {error}"))),
Ok(status) if status.success() => Ok(()),
Ok(status) => Err(FttsError::Generic(format!(
"curl failed downloading {url} ({status}); check network access and retry `ftts pull`"
))),
}
}
fn verify_pulled_file(path: &Path, file: &ModelManifestFile) -> Result<(), FttsError> {
let metadata = fs::metadata(path).map_err(|error| {
FttsError::Generic(format!(
"cannot stat downloaded {}: {error}",
path.display()
))
})?;
if metadata.len() != file.bytes {
return Err(FttsError::ArtifactFormat(format!(
"downloaded {} is {} bytes, expected {}; the incomplete download was discarded, retry `ftts pull`",
file.asset,
metadata.len(),
file.bytes
)));
}
let digest = ftts_artifacts::sha256::hex_digest_file(path).map_err(|error| {
FttsError::Generic(format!(
"cannot hash downloaded {}: {error}",
path.display()
))
})?;
if digest != file.sha256 {
return Err(FttsError::ArtifactFormat(format!(
"downloaded {} carries sha256 {digest}, expected {}; the corrupt download was discarded, retry `ftts pull`",
file.asset, file.sha256
)));
}
Ok(())
}
fn pull_staging_path(dest: &Path) -> PathBuf {
let mut name = dest.file_name().map(OsString::from).unwrap_or_default();
name.push(".part");
dest.with_file_name(name)
}
fn pull_one_file(
manifest: &ModelManifest,
file: &ModelManifestFile,
dest: &Path,
) -> Result<(), FttsError> {
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent).map_err(|error| {
FttsError::Generic(format!(
"cannot create model directory {}: {error}",
parent.display()
))
})?;
}
let staging = pull_staging_path(dest);
let url = manifest.download_url(file);
let outcome =
download_with_curl(&url, &staging).and_then(|()| verify_pulled_file(&staging, file));
if let Err(error) = outcome {
let _ = fs::remove_file(&staging);
return Err(error);
}
fs::rename(&staging, dest).map_err(|error| {
FttsError::Generic(format!(
"downloaded {} verified but could not be published to {}: {error}",
file.asset,
dest.display()
))
})
}
fn run_pull(
args: &PullArgs,
environment: &Environment,
stdout: &mut dyn Write,
) -> Result<(), FttsError> {
let manifest = ModelManifest::embedded()?;
let destination = match &args.model {
Some(path) => path.clone(),
None => default_pull_model_dir(environment).ok_or_else(|| {
FttsError::Usage(
"cannot choose a model directory: pass --model PATH, or set FTTS_MODEL_DIR or HOME"
.to_owned(),
)
})?,
};
writeln!(
stdout,
"pulling {} ({} files, {} bytes) into {}",
manifest.model_id,
manifest.files.len(),
manifest.total_bytes(),
destination.display()
)
.map_err(output_error)?;
for file in &manifest.files {
let dest = destination.join(&file.dest);
match pull_decision(&dest, file, args.force) {
PullDecision::Skip => writeln!(
stdout,
"{} ({} bytes): already present, verified",
file.dest, file.bytes
)
.map_err(output_error)?,
PullDecision::Download => {
writeln!(stdout, "{} ({} bytes): downloading", file.dest, file.bytes)
.map_err(output_error)?;
pull_one_file(&manifest, file, &dest)?;
writeln!(stdout, "{} ({} bytes): verified", file.dest, file.bytes)
.map_err(output_error)?;
}
}
}
writeln!(stdout, "model ready at {}", destination.display()).map_err(output_error)
}
fn resolve_existing_file<'a>(path: &'a Path, label: &str) -> Result<&'a Path, FttsError> {
if path.is_file() {
Ok(path)
} else {
Err(FttsError::ModelNotFound(format!(
"{label} {} does not exist or is not a file; use an existing PATH",
path.display()
)))
}
}
fn admission_plan(text: &str, settings: &EffectiveSettings) -> Result<Value, FttsError> {
if text.len() > SCAFFOLD_ADMISSION_TEXT_LIMIT_BYTES {
return Err(FttsError::BudgetTimeout(format!(
"text is {} bytes, above the Phase-0 admission bound of {} bytes; split the document before retrying",
text.len(),
SCAFFOLD_ADMISSION_TEXT_LIMIT_BYTES
)));
}
let characters = text.chars().count();
let estimated_prompt_tokens = u64::try_from(characters).unwrap_or(u64::MAX);
let policy = ftts_core::process_engine_config().admission;
match policy.admit(estimated_prompt_tokens) {
Ok(plan) => Ok(json!({
"status": "accepted",
"scope": "preflight on an ESTIMATED prompt length; the binding decision is the \
engine's, taken after tokenization",
"text_bytes": text.len(),
"text_characters": characters,
"estimated_prompt_tokens": estimated_prompt_tokens,
"predicted_max_frames": plan.predicted_max_frames,
"predicted_peak_bytes": plan.predicted_peak_bytes,
"budget_bytes": plan.budget_bytes,
"binding_constraint": plan.binding_constraint.as_str(),
"packet_frames": settings.packet_frames.as_str(),
"profile": settings.profile.as_str(),
})),
Err(rejection) => Err(FttsError::BudgetTimeout(rejection.to_string())),
}
}
fn run_voice_inspect(path: &Path, stdout: &mut dyn Write) -> Result<(), FttsError> {
let path = resolve_existing_file(path, "voice pack")?;
write_json_line(
stdout,
&json!({
"schema_version": ROBOT_SCHEMA_VERSION,
"event": "voice_inspect",
"path": path.display().to_string(),
"status": "header_inspection_pending_artifact_reader",
}),
)
}
fn run_robot(
command: RobotCommand,
environment: &Environment,
stdout: &mut dyn Write,
) -> Result<(), FttsError> {
let event = match command {
RobotCommand::Schema => robot::schema_document(robot::DOCUMENTED_ENVIRONMENT),
RobotCommand::Health => {
let searched = model_search_paths(environment);
let found = searched.iter().find(|path| looks_like_model_artifact(path));
let mut object = robot::EventType::Health.event();
object.insert("status".to_owned(), json!("phase0_skeleton"));
object.insert("model_loaded".to_owned(), json!(false));
object.insert("model_present".to_owned(), json!(found.is_some()));
object.insert(
"model_path".to_owned(),
json!(found.map(|path| path.display().to_string())),
);
object.insert(
"model_dir".to_owned(),
json!(environment.value("FTTS_MODEL_DIR")),
);
object.insert(
"searched".to_owned(),
json!(
searched
.iter()
.map(|path| path.display().to_string())
.collect::<Vec<_>>()
),
);
object.insert("stateless_default".to_owned(), json!(true));
object.insert(
"threads".to_owned(),
json!(
environment
.value("FTTS_THREADS")
.and_then(|value| value.parse::<u64>().ok())
),
);
object.insert(
"recommended_command".to_owned(),
json!("ftts say --check --model PATH TEXT"),
);
Value::Object(object)
}
RobotCommand::Backends => {
let mut object = robot::EventType::Backends.event();
object.insert(
"available".to_owned(),
json!(
ftts_kernels::int8::Int8Tier::available()
.iter()
.map(|tier| tier.as_str())
.collect::<Vec<_>>()
),
);
object.insert(
"dispatched".to_owned(),
json!(ftts_kernels::int8::Int8Tier::dispatch().as_str()),
);
object.insert("isa_features".to_owned(), json!(detected_isa_features()));
let plan = ftts_kernels::int8::autotuned_plan();
object.insert(
"kernel_plan".to_owned(),
json!({
"version": 0,
"decode_gemv": plan.decode_gemv.as_str(),
"batch_gemm": plan.batch_gemm.as_str(),
"persisted": false,
}),
);
object.insert("pool_sizing".to_owned(), Value::Null);
object.insert(
"force_arch".to_owned(),
json!(environment.value("FTTS_FORCE_ARCH")),
);
Value::Object(object)
}
RobotCommand::Selftest => {
let report = ftts_kernels::selftest::run_selftest();
let checks: Vec<Value> = report
.checks
.iter()
.map(|check| {
json!({
"row": check.row.id,
"scope": check.row.scope.as_str(),
"census_tensor": check.row.census_tensor,
"reduction_k": check.row.reduction_k,
"tier": check.tier.as_str(),
"contract": check.contract.as_str(),
"dispatched": check.tier == report.dispatched,
"accumulator_i32": check.accumulator_i32,
"reference_i64": check.reference_i64,
"passed": check.passed,
})
})
.collect();
let mut object = robot::EventType::Selftest.event();
object.insert(
"status".to_owned(),
json!(if report.passed() { "passed" } else { "failed" }),
);
object.insert("reason".to_owned(), Value::Null);
object.insert("checks".to_owned(), json!(checks));
Value::Object(object)
}
};
write_json_line(stdout, &event)
}
fn model_search_paths(environment: &Environment) -> Vec<PathBuf> {
let mut searched = environment
.value("FTTS_MODEL_DIR")
.map(std::env::split_paths)
.map(|paths| {
paths
.map(|path| path.join(MODEL_BASENAME))
.collect::<Vec<_>>()
})
.unwrap_or_default();
if let Some(home) = std::env::var_os("HOME") {
let home = PathBuf::from(home);
searched.push(home.join(".cache/franken_tts/models").join(MODEL_BASENAME));
searched.push(home.join(DEFAULT_MODEL_CACHE_SUBDIR).join(MODEL_BASENAME));
}
searched
}
fn looks_like_model_artifact(path: &Path) -> bool {
use std::io::Read as _;
let Ok(mut file) = fs::File::open(path) else {
return false;
};
let mut magic = [0u8; 5];
file.read_exact(&mut magic).is_ok() && &magic == b"FTTSQ"
}
fn detected_isa_features() -> Vec<&'static str> {
let mut features = Vec::new();
#[cfg(target_arch = "aarch64")]
{
if std::arch::is_aarch64_feature_detected!("neon") {
features.push("neon");
}
if std::arch::is_aarch64_feature_detected!("dotprod") {
features.push("dotprod");
}
if std::arch::is_aarch64_feature_detected!("i8mm") {
features.push("i8mm");
}
}
#[cfg(target_arch = "x86_64")]
{
if std::arch::is_x86_feature_detected!("avx2") {
features.push("avx2");
}
if std::arch::is_x86_feature_detected!("avxvnni") {
features.push("avx-vnni");
}
if std::arch::is_x86_feature_detected!("avx512vnni") {
features.push("avx512-vnni");
}
}
features
}
fn run_doctor(
args: &DoctorArgs,
environment: &Environment,
stdout: &mut dyn Write,
) -> Result<(), FttsError> {
let report = json!({
"schema_version": ROBOT_SCHEMA_VERSION,
"status": "phase0_skeleton",
"stateless_default": true,
"persistent_history": false,
"environment": environment.documented_values(),
"recommended_command": "ftts robot schema",
});
if args.json {
write_json_line(stdout, &report)
} else {
writeln!(stdout, "FrankenTTS Phase-0 CLI skeleton")
.and_then(|_| writeln!(stdout, "stateless default: yes"))
.and_then(|_| writeln!(stdout, "model loaded: no"))
.and_then(|_| writeln!(stdout, "next: ftts robot schema"))
.map_err(output_error)
}
}
fn write_json_line(writer: &mut dyn Write, value: &Value) -> Result<(), FttsError> {
serde_json::to_writer(&mut *writer, value)
.map_err(|error| FttsError::Generic(format!("cannot serialize CLI JSON: {error}")))?;
writer.write_all(b"\n").map_err(output_error)
}
fn output_error(error: io::Error) -> FttsError {
FttsError::Generic(format!("cannot write CLI output: {error}"))
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
const CLAP_SURFACE_SNAPSHOT: &str = "commands=say,enroll,voice,convert,pull,robot,doctor\nrobot=schema,health,backends,selftest\nsay=file,model,voice,output,stream,check\npull=model,force\nglobal=profile,packet-frames,math-mode,voice-pack,normalize,trace,seed\n";
#[test]
fn clap_surface_matches_snapshot() {
let command = Cli::command();
let commands = command
.get_subcommands()
.map(|command| command.get_name())
.collect::<Vec<_>>()
.join(",");
let robot = command
.get_subcommands()
.find(|command| command.get_name() == "robot")
.expect("robot subcommand")
.get_subcommands()
.map(|command| command.get_name())
.collect::<Vec<_>>()
.join(",");
let say = command
.get_subcommands()
.find(|command| command.get_name() == "say")
.expect("say subcommand")
.get_arguments()
.filter_map(|argument| argument.get_long())
.collect::<Vec<_>>()
.join(",");
let pull = command
.get_subcommands()
.find(|command| command.get_name() == "pull")
.expect("pull subcommand")
.get_arguments()
.filter_map(|argument| argument.get_long())
.collect::<Vec<_>>()
.join(",");
let global = command
.get_arguments()
.filter_map(|argument| argument.get_long())
.filter(|argument| *argument != "help")
.collect::<Vec<_>>()
.join(",");
let actual = format!(
"commands={commands}\nrobot={robot}\nsay={say}\npull={pull}\nglobal={global}\n"
);
assert_eq!(actual, CLAP_SURFACE_SNAPSHOT);
}
#[test]
fn argument_file_and_stdin_text_are_identical() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../README.md");
let expected = fs::read_to_string(&root).expect("checked-in README");
let from_argument = read_text(
&SayArgs {
output_positional: None,
text: Some(expected.clone()),
file: None,
model: None,
voice: None,
output: None,
stream: None,
check: true,
},
&mut Cursor::new(Vec::<u8>::new()),
)
.expect("argument text");
let from_file = read_text(
&SayArgs {
output_positional: None,
text: None,
file: Some(root),
model: None,
voice: None,
output: None,
stream: None,
check: true,
},
&mut Cursor::new(Vec::<u8>::new()),
)
.expect("file text");
let from_stdin = read_text(
&SayArgs {
output_positional: None,
text: Some("-".to_owned()),
file: None,
model: None,
voice: None,
output: None,
stream: None,
check: true,
},
&mut Cursor::new(expected.as_bytes()),
)
.expect("stdin text");
assert_eq!(from_argument, from_file);
assert_eq!(from_argument, from_stdin);
}
#[test]
fn check_plan_is_deterministic_and_marks_its_scope() {
let settings = EffectiveSettings {
profile: ExecutionProfile::Strict,
packet_frames: PacketFrames::Four,
math_mode: MathMode::Strict,
voice_pack: VoicePackProfile::Portable,
normalize: NormalizeMode::Conservative,
};
let first = admission_plan("hello", &settings).expect("admission plan");
let second = admission_plan("hello", &settings).expect("admission plan");
assert_eq!(first, second);
assert_eq!(first["status"], "accepted");
assert!(
first["scope"]
.as_str()
.unwrap_or_default()
.contains("ESTIMATED")
);
}
#[test]
fn the_cli_preflight_and_the_engine_agree_on_the_same_request() {
let settings = EffectiveSettings {
profile: ExecutionProfile::Balanced,
packet_frames: PacketFrames::Four,
math_mode: MathMode::Strict,
voice_pack: VoicePackProfile::Portable,
normalize: NormalizeMode::Verbatim,
};
let text = "a moderately sized utterance for admission";
let plan = admission_plan(text, &settings).expect("preflight admits");
let policy = ftts_core::process_engine_config().admission;
let engine = policy
.admit(text.chars().count() as u64)
.expect("engine admits the same request");
assert_eq!(plan["predicted_peak_bytes"], engine.predicted_peak_bytes);
assert_eq!(plan["predicted_max_frames"], engine.predicted_max_frames);
assert_eq!(plan["budget_bytes"], engine.budget_bytes);
assert_eq!(
plan["binding_constraint"],
engine.binding_constraint.as_str()
);
}
#[test]
fn a_wav_sink_writes_a_playable_file_and_conforming_audio_chunk_events() {
let dir = std::env::temp_dir().join(format!("ftts-wav-sink-{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("temp dir");
let path = dir.join("out.wav");
let frame: Vec<f32> = (0..1_920)
.map(|i| (i as f32 / 1_920.0 * std::f32::consts::TAU).sin() * 0.5)
.collect();
let mut sink = AudioOutput::wav(&path).expect("wav sink");
let mut discard = Vec::new();
let first = sink
.write_packet(&frame, &mut discard, "run-1", 1)
.expect("packet 1");
let second = sink
.write_packet(&frame, &mut discard, "run-1", 1)
.expect("packet 2");
assert!(robot::validate_event(&first).is_empty(), "{first:?}");
assert!(robot::validate_event(&second).is_empty(), "{second:?}");
assert_eq!(first["sink"], "file");
assert_eq!(first["byte_offset"], 0);
assert_eq!(first["bytes"], 1_920 * 2);
assert_eq!(first["duration_ms"], 80, "1,920 samples at 24 kHz is 80 ms");
assert_eq!(second["byte_offset"], 1_920 * 2);
let samples = sink.finish().expect("finish");
assert_eq!(samples, 1_920 * 2);
let bytes = std::fs::read(&path).expect("read wav");
assert_eq!(&bytes[0..4], b"RIFF");
assert_eq!(&bytes[8..12], b"WAVE");
let declared = u32::from_le_bytes(bytes[40..44].try_into().expect("data size"));
assert_eq!(declared as usize, 1_920 * 2 * 2);
assert_eq!(bytes.len(), 44 + 1_920 * 2 * 2);
assert!(
discard.is_empty(),
"a file sink must not also emit raw PCM to the stream"
);
}
#[test]
fn a_raw_sink_writes_pcm_to_the_stream_and_never_mixes_it_with_events() {
let mut sink = AudioOutput::raw();
let mut raw = Vec::new();
let pcm = vec![0.5f32; 4];
let event = sink
.write_packet(&pcm, &mut raw, "run-1", 1)
.expect("packet");
assert!(robot::validate_event(&event).is_empty(), "{event:?}");
assert_eq!(event["sink"], "stdout");
assert_eq!(raw.len(), 8, "four 16-bit samples");
let first = i16::from_le_bytes([raw[0], raw[1]]);
assert_eq!(first, ftts_core::audio::sample_to_i16(0.5));
assert!(
!raw.windows(2).any(|w| w == b"{\""),
"raw PCM stream must never contain an event object"
);
}
#[test]
fn a_none_sink_still_reports_conforming_events() {
let mut sink = AudioOutput::none();
let mut discard = Vec::new();
let event = sink
.write_packet(&[0.0f32; 960], &mut discard, "run-1", 2)
.expect("packet");
assert!(robot::validate_event(&event).is_empty(), "{event:?}");
assert_eq!(event["sink"], "none");
assert_eq!(event["packet_frames"], "2");
assert!(discard.is_empty());
assert_eq!(sink.finish().expect("finish"), 960);
}
#[test]
fn a_health_violation_renders_as_a_contract_conforming_robot_event() {
let silent =
ftts_core::HealthEvent::Violation(ftts_core::health::HealthViolation::OutputSilent {
silent_millis: 1_500,
});
let event = robot::health_violation_event("run-1", silent, 42);
assert!(
robot::validate_event(&event).is_empty(),
"{:?}",
robot::validate_event(&event)
);
assert_eq!(event["event"], "health_violation");
assert_eq!(event["violation"], "output_silent");
assert_eq!(event["invalidates_output"], true);
assert!(event["detail"].as_str().expect("detail").contains("1500"));
assert!(event["remedy"].as_str().expect("remedy").len() > 40);
let demoted =
ftts_core::HealthEvent::Violation(ftts_core::health::HealthViolation::KernelDemoted {
from: ftts_core::health::KernelTier::Optimized("i8mm"),
to: ftts_core::health::KernelTier::Scalar,
});
let event = robot::health_violation_event("run-1", demoted, 43);
assert!(robot::validate_event(&event).is_empty());
assert_eq!(event["invalidates_output"], false);
for event in [
ftts_core::HealthEvent::BudgetExceeded,
ftts_core::HealthEvent::Cancelled,
] {
let rendered = robot::health_violation_event("run-1", event, 44);
assert!(robot::validate_event(&rendered).is_empty());
assert_eq!(rendered["invalidates_output"], true);
}
}
#[test]
fn normalization_defaults_to_verbatim_conformance_mode() {
let cli = Cli {
profile: None,
packet_frames: None,
math_mode: None,
voice_pack: None,
normalize: None,
trace: None,
seed: None,
command: Command::Robot(RobotArgs {
command: RobotCommand::Health,
}),
};
assert_eq!(
EffectiveSettings::resolve(&cli, &Environment::default())
.expect("default settings")
.normalize,
NormalizeMode::Verbatim
);
assert_eq!(
EffectiveSettings::resolve(&cli, &Environment::default())
.expect("default settings")
.normalization_options(),
NormalizationOptions::default(),
"CLI defaults must use the same verbatim options as the library"
);
}
#[test]
fn cli_normalization_modes_map_to_shared_engine_options() {
for (cli_mode, engine_mode) in [
(NormalizeMode::Verbatim, NormalizationMode::Verbatim),
(NormalizeMode::Conservative, NormalizationMode::Conservative),
(NormalizeMode::LocaleAware, NormalizationMode::LocaleAware),
] {
let settings = EffectiveSettings {
profile: ExecutionProfile::Balanced,
packet_frames: PacketFrames::Four,
math_mode: MathMode::Strict,
voice_pack: VoicePackProfile::Portable,
normalize: cli_mode,
};
assert_eq!(settings.normalization_options().mode, engine_mode);
}
}
#[test]
fn pinned_main_conversion_plan_preserves_the_reviewed_q8_boundary() {
let specs = pinned_main_tensor_specs().expect("checked-in main inventory parses");
let (_manifest, _plan) =
pinned_main_conversion_plan().expect("checked-in main conversion plan builds");
assert_eq!(specs.len(), PINNED_MAIN_TENSOR_COUNT);
assert_eq!(
specs
.iter()
.filter(|spec| spec.storage == TensorStoragePolicy::Q8PerOutputChannel)
.count(),
231,
"28 talker + 5 microdecoder layers times seven attention/MLP projections"
);
let text_embedding = specs
.iter()
.find(|spec| spec.name == "talker.model.text_embedding.weight");
assert!(
text_embedding.is_some(),
"pinned inventory must contain the text embedding"
);
if let Some(text_embedding) = text_embedding {
assert_eq!(text_embedding.storage, TensorStoragePolicy::Verbatim);
assert_eq!(text_embedding.access_class, AccessClass::ColdTextEmbedding);
}
let talker_projection = specs
.iter()
.find(|spec| spec.name == "talker.model.layers.0.mlp.down_proj.weight");
assert!(
talker_projection.is_some(),
"pinned inventory must contain the talker projection"
);
if let Some(talker_projection) = talker_projection {
assert_eq!(
talker_projection.storage,
TensorStoragePolicy::Q8PerOutputChannel
);
assert_eq!(
talker_projection.access_class,
AccessClass::HotRecurrentTalker
);
}
let micro_projection = specs
.iter()
.find(|spec| spec.name == "talker.code_predictor.model.layers.0.mlp.down_proj.weight");
assert!(
micro_projection.is_some(),
"pinned inventory must contain the microdecoder projection"
);
if let Some(micro_projection) = micro_projection {
assert_eq!(
micro_projection.storage,
TensorStoragePolicy::Q8PerOutputChannel
);
assert_eq!(
micro_projection.access_class,
AccessClass::HotRecurrentMicrodecoder
);
}
let primary_embedding = specs
.iter()
.find(|spec| spec.name == "talker.model.codec_embedding.weight");
assert!(
primary_embedding.is_some(),
"pinned inventory must contain the primary-code embedding"
);
if let Some(primary_embedding) = primary_embedding {
assert_eq!(
primary_embedding.access_class,
AccessClass::HotRecurrentMicrodecoder,
"the primary-code embedding feeds residual depth one every frame"
);
}
let primary_head = specs
.iter()
.find(|spec| spec.name == "talker.codec_head.weight");
assert!(
primary_head.is_some(),
"pinned inventory must contain the primary-code head"
);
if let Some(primary_head) = primary_head {
assert_eq!(primary_head.storage, TensorStoragePolicy::Verbatim);
assert_eq!(primary_head.access_class, AccessClass::HotRecurrentTalker);
}
let text_projection = specs
.iter()
.find(|spec| spec.name == "talker.text_projection.linear_fc1.weight");
assert!(
text_projection.is_some(),
"pinned inventory must contain the text-projection MLP"
);
if let Some(text_projection) = text_projection {
assert_eq!(text_projection.storage, TensorStoragePolicy::Verbatim);
assert_eq!(
text_projection.access_class,
AccessClass::HotRecurrentTalker
);
}
let head = specs
.iter()
.find(|spec| spec.name == "talker.code_predictor.lm_head.0.weight");
assert!(
head.is_some(),
"pinned inventory must contain the residual-code head"
);
if let Some(head) = head {
assert_eq!(head.storage, TensorStoragePolicy::Verbatim);
assert_eq!(head.access_class, AccessClass::HotRecurrentMicrodecoder);
}
let speaker = specs
.iter()
.find(|spec| spec.name == "speaker_encoder.fc.weight");
assert!(
speaker.is_some(),
"pinned inventory must contain the speaker encoder"
);
if let Some(speaker) = speaker {
assert_eq!(speaker.storage, TensorStoragePolicy::Verbatim);
assert_eq!(speaker.access_class, AccessClass::EnrollmentSpeakerEncoder);
}
}
#[test]
fn conversion_notice_carries_changes_and_the_full_license() {
let notice = pinned_license_notice();
assert!(notice.contains("Copyright 2026 Alibaba Cloud"));
assert!(notice.contains("CHANGES: the original bfloat16 weights were converted"));
assert!(notice.contains("Apache License"));
assert!(notice.contains("TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION"));
}
#[test]
fn convert_refusal_still_emits_a_versioned_robot_lifecycle() {
let cli = Cli {
profile: None,
packet_frames: None,
math_mode: None,
voice_pack: None,
normalize: None,
trace: None,
seed: None,
command: Command::Robot(RobotArgs {
command: RobotCommand::Health,
}),
};
let args = ConvertArgs {
source: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml"),
output: PathBuf::from("never-created.fttsq"),
};
let mut stdout = Vec::new();
let mut stderr = Vec::new();
let error = run_convert(
&cli,
&args,
&Environment::default(),
&mut stdout,
&mut stderr,
)
.expect_err("the non-pinned source must be refused");
assert_eq!(error.exit_code(), FttsExitCode::Input);
let stdout = String::from_utf8(stdout).expect("NDJSON stdout");
let stderr = String::from_utf8(stderr).expect("NDJSON stderr");
assert!(robot::validate_ndjson(&stdout).is_empty());
assert!(robot::validate_ndjson(&stderr).is_empty());
let stdout_events = stdout
.lines()
.map(|line| serde_json::from_str::<Value>(line).expect("JSON event"))
.collect::<Vec<_>>();
assert_eq!(stdout_events[0]["event"], "run_start");
assert_eq!(stdout_events[1]["event"], "stage");
assert_eq!(
serde_json::from_str::<Value>(stderr.trim()).expect("run error")["event"],
"run_error"
);
}
#[test]
fn say_check_emits_a_versioned_admission_outcome() {
let model = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml");
let cli = Cli {
profile: Some(ExecutionProfile::Balanced),
packet_frames: Some(PacketFrames::Four),
math_mode: Some(MathMode::Strict),
voice_pack: Some(VoicePackProfile::Portable),
normalize: Some(NormalizeMode::Conservative),
trace: None,
seed: Some(7),
command: Command::Robot(RobotArgs {
command: RobotCommand::Health,
}),
};
let args = SayArgs {
text: Some("checked text".to_owned()),
output_positional: None,
file: None,
model: Some(model),
voice: None,
output: None,
stream: None,
check: true,
};
let mut stdin = Cursor::new(Vec::<u8>::new());
let mut stdout = Vec::new();
let mut stderr = Vec::new();
run_say(
&cli,
&args,
&Environment::default(),
&mut stdin,
&mut stdout,
&mut stderr,
)
.expect("check path");
assert!(stderr.is_empty());
let text = String::from_utf8(stdout).expect("utf-8 events");
assert!(
robot::validate_ndjson(&text).is_empty(),
"emitted stream violates the contract: {:?}",
robot::validate_ndjson(&text)
);
let events: Vec<Value> = text
.lines()
.map(|line| serde_json::from_str(line).expect("one JSON object per line"))
.collect();
let names: Vec<&str> = events
.iter()
.map(|event| event["event"].as_str().expect("event name"))
.collect();
assert_eq!(
names,
vec![
"run_start",
"stage",
"stage",
"text_prepared",
"stage",
"stage",
"check_complete",
"run_complete",
],
"the skeleton lifecycle must flow end-to-end on the empty pipeline"
);
let run_id = events[0]["run_id"]
.as_str()
.expect("run_start carries run_id");
assert!(!run_id.is_empty());
assert!(events.iter().all(|event| event["run_id"] == run_id));
assert!(
events
.iter()
.all(|event| event["schema_version"] == ROBOT_SCHEMA_VERSION)
);
let seqs: Vec<u64> = events
.iter()
.filter(|event| event["event"] == "stage")
.map(|event| event["seq"].as_u64().expect("seq"))
.collect();
assert_eq!(seqs, vec![0, 1, 2, 3]);
let check = &events[6];
assert_eq!(check["admission"]["status"], "accepted");
assert!(
check["admission"]["predicted_peak_bytes"].is_u64(),
"the engine-backed plan reports a real predicted peak"
);
assert_eq!(check["normalization_trace_requested"], false);
let prepared = &events[3];
assert_eq!(prepared["char_count"], "checked text".chars().count());
assert!(prepared["unicode_version"].is_string());
assert!(
!text.contains("checked text"),
"the event stream must not carry the user's text"
);
assert_eq!(events[7]["exit_code"], 0);
}
#[test]
fn a_newline_inside_a_field_cannot_break_ndjson_framing() {
let run = robot::RunContext::with_id("r-test");
let error = FttsError::Generic("first\nsecond".to_owned());
let mut event = run.event(robot::EventType::RunError);
event.insert("exit_code".to_owned(), json!(error.exit_code().as_u8()));
event.insert("kind".to_owned(), json!(error.exit_code().description()));
event.insert("message".to_owned(), json!(error.to_string()));
event.insert("remediation".to_owned(), json!(error.remediation()));
event.insert("elapsed_ms".to_owned(), json!(0));
let value = Value::Object(event);
let mut buffer = Vec::new();
write_json_line(&mut buffer, &value).expect("serializes");
let text = String::from_utf8(buffer).expect("utf-8");
assert_eq!(
text.lines().count(),
1,
"framing broken by an embedded newline"
);
assert!(robot::validate_ndjson(&text).is_empty());
let parsed: Value = serde_json::from_str(text.trim_end()).expect("still one object");
assert!(
parsed["message"].as_str().expect("message").contains('\n'),
"the newline must survive as data, not be stripped"
);
}
#[test]
fn pinned_copies_match_the_truth_pack_canonicals() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
for (canonical, embedded, name) in [
(
"docs/truth-pack/TENSOR_INVENTORY.json",
PINNED_TENSOR_INVENTORY,
"TENSOR_INVENTORY.json",
),
(
"docs/truth-pack/snapshots/hf/config.json",
PINNED_MODEL_CONFIG,
"model_config.json",
),
(
"docs/truth-pack/snapshots/gh/LICENSE",
APACHE_LICENSE,
"QWEN_APACHE_LICENSE",
),
] {
match std::fs::read_to_string(root.join(canonical)) {
Ok(bytes) => assert_eq!(
bytes, embedded,
"pinned/{name} drifted from {canonical}; re-copy it"
),
Err(_) => eprintln!(
"SKIP pinned-copy check for {name}: {canonical} absent (no repo checkout)"
),
}
}
}
#[test]
fn embedded_model_manifest_is_wellformed_and_agrees_with_the_converter_pin() {
let manifest = ModelManifest::embedded().expect("embedded manifest parses");
assert_eq!(manifest.model_id, "qwen3-tts-12hz-0.6b-base");
assert_eq!(manifest.release_tag, "model-qwen3-tts-v1");
assert_eq!(manifest.repo, "Dicklesworthstone/franken_tts");
assert_eq!(manifest.files.len(), 7);
for file in &manifest.files {
assert!(
is_sha256_hex(&file.sha256),
"{} carries a malformed digest",
file.asset
);
assert!(file.bytes > 0, "{} has no pinned size", file.asset);
let dest = Path::new(&file.dest);
assert!(!dest.is_absolute(), "{} dest is absolute", file.asset);
assert!(
dest.components()
.all(|component| matches!(component, std::path::Component::Normal(_))),
"{} dest can traverse out of the model directory",
file.asset
);
}
let main = manifest
.files
.iter()
.find(|file| file.dest == MODEL_BASENAME)
.expect("manifest carries the canonical artifact");
assert_eq!(
manifest.download_url(main),
"https://github.com/Dicklesworthstone/franken_tts/releases/download/model-qwen3-tts-v1/qwen3-tts-12hz-0.6b-base.fttsq"
);
assert!(
!manifest
.files
.iter()
.any(|file| file.dest == PINNED_MAIN_WEIGHTS_FILENAME),
"pull must not fetch the raw main checkpoint alongside the canonical artifact"
);
let dests: Vec<&str> = manifest
.files
.iter()
.map(|file| file.dest.as_str())
.collect();
for required in [
MODEL_BASENAME,
"speech_tokenizer/model.safetensors",
"vocab.json",
"merges.txt",
"tokenizer_config.json",
] {
assert!(dests.contains(&required), "manifest is missing {required}");
}
}
#[test]
fn malformed_model_manifests_are_refused_with_the_field_named() {
fn manifest_with(
schema_version: u64,
asset: &str,
dest: &str,
sha256: &str,
bytes: u64,
) -> String {
json!({
"schema_version": schema_version,
"model_id": "m",
"release_tag": "t",
"repo": "owner/repo",
"files": [{"asset": asset, "dest": dest, "sha256": sha256, "bytes": bytes}],
})
.to_string()
}
let good_sha = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
ModelManifest::parse(&manifest_with(1, "a.bin", "a.bin", good_sha, 1))
.expect("well-formed manifest parses");
for (label, text) in [
(
"unsupported schema_version",
manifest_with(2, "a.bin", "a.bin", good_sha, 1),
),
(
"short sha256",
manifest_with(1, "a.bin", "a.bin", "abc123", 1),
),
(
"uppercase sha256",
manifest_with(1, "a.bin", "a.bin", &good_sha.to_uppercase(), 1),
),
(
"zero bytes",
manifest_with(1, "a.bin", "a.bin", good_sha, 0),
),
(
"absolute dest",
manifest_with(1, "a.bin", "/etc/passwd", good_sha, 1),
),
(
"traversal dest",
manifest_with(1, "a.bin", "../escape.bin", good_sha, 1),
),
(
"asset with a path separator",
manifest_with(1, "dir/a.bin", "a.bin", good_sha, 1),
),
(
"empty files array",
json!({
"schema_version": 1,
"model_id": "m",
"release_tag": "t",
"repo": "owner/repo",
"files": [],
})
.to_string(),
),
] {
let error = ModelManifest::parse(&text)
.expect_err(&format!("a manifest with {label} must be refused"));
assert_eq!(error.exit_code(), FttsExitCode::ArtifactFormat, "{label}");
}
}
#[test]
fn pull_skips_only_a_file_matching_both_pinned_size_and_digest() {
let dir = std::env::temp_dir().join(format!("ftts-pull-decision-{}", std::process::id()));
fs::create_dir_all(&dir).expect("temp dir");
let payload = b"pinned payload";
let file = ModelManifestFile {
asset: "a.bin".to_owned(),
dest: "a.bin".to_owned(),
sha256: ftts_artifacts::sha256::hex_digest(payload),
bytes: payload.len() as u64,
};
let dest = dir.join("a.bin");
let _ = fs::remove_file(&dest);
assert_eq!(
pull_decision(&dest, &file, false),
PullDecision::Download,
"absent file must download"
);
fs::write(&dest, payload).expect("write verified payload");
assert_eq!(
pull_decision(&dest, &file, false),
PullDecision::Skip,
"matching size and digest must skip"
);
assert_eq!(
pull_decision(&dest, &file, true),
PullDecision::Download,
"--force must re-download even a verified file"
);
fs::write(&dest, b"pinned_payload").expect("write same-length corruption");
assert_eq!(
pull_decision(&dest, &file, false),
PullDecision::Download,
"a same-length corruption must be caught by the digest"
);
fs::write(&dest, b"short").expect("write truncation");
assert_eq!(
pull_decision(&dest, &file, false),
PullDecision::Download,
"a truncated file must be caught by the size check"
);
}
#[test]
fn model_resolution_prefers_explicit_then_searched_then_the_pull_directory() {
let root = std::env::temp_dir().join(format!("ftts-resolve-order-{}", std::process::id()));
let bundle = root.join("bundle");
for relative in [
"model.safetensors",
"speech_tokenizer/model.safetensors",
"vocab.json",
"merges.txt",
"tokenizer_config.json",
] {
let path = bundle.join(relative);
fs::create_dir_all(path.parent().expect("bundle parent")).expect("bundle dirs");
fs::write(&path, b"").expect("bundle file");
}
assert!(
synth::ModelBundle::resolve(&bundle).is_ok(),
"five empty files must satisfy the resolver's is_file checks"
);
let searched_artifact = root.join("searched").join(MODEL_BASENAME);
fs::create_dir_all(searched_artifact.parent().expect("searched parent"))
.expect("searched dir");
fs::write(&searched_artifact, b"").expect("searched artifact");
let searched = vec![searched_artifact.clone()];
let absent = vec![root.join("absent").join(MODEL_BASENAME)];
assert_eq!(
resolve_model_from(Some(&bundle), &searched, Some(&bundle)).expect("explicit"),
bundle.display().to_string()
);
assert_eq!(
resolve_model_from(None, &searched, Some(&bundle)).expect("searched"),
searched_artifact.display().to_string()
);
assert_eq!(
resolve_model_from(None, &absent, Some(&bundle)).expect("pull fallback"),
bundle.display().to_string()
);
let incomplete = root.join("incomplete");
fs::create_dir_all(&incomplete).expect("incomplete dir");
let error = resolve_model_from(None, &absent, Some(&incomplete))
.expect_err("an empty pull directory must not resolve");
assert_eq!(error.exit_code(), FttsExitCode::ModelNotFound);
assert!(error.to_string().contains("ftts pull"), "{error}");
assert!(error.to_string().contains("2.0 GB"), "{error}");
assert!(error.to_string().contains("FTTS_MODEL_DIR"), "{error}");
}
#[test]
fn the_pull_directory_default_prefers_the_env_override() {
let mut environment = Environment::default();
environment
.values
.insert("FTTS_MODEL_DIR", Some(OsString::from("/tmp/env-model-dir")));
assert_eq!(
default_pull_model_dir(&environment),
Some(PathBuf::from("/tmp/env-model-dir"))
);
if std::env::var_os("HOME").is_some() {
let fallback = default_pull_model_dir(&Environment::default())
.expect("HOME is set, so a default exists");
assert!(
fallback.ends_with(DEFAULT_MODEL_CACHE_SUBDIR),
"{fallback:?}"
);
}
}
}