Skip to main content

ftts_artifacts/
fttsq.rs

1//! `.fttsq` — the canonical, portable, quantized model container.
2//!
3//! # What this format is, and what it deliberately is not
4//!
5//! `.fttsq` is **portable and machine-independent**: tensor payloads, quantization scales, the
6//! frozen model config, provenance, and the license notice. It carries **no machine-specific
7//! tiling** — packed kernel layouts and the autotuned plan live in `.fttspack`, which is a
8//! regenerable per-machine cache. That split is the whole point: kernel layouts change often, and
9//! republishing a multi-gigabyte artifact every time a tile shape improves is not a cost we are
10//! willing to pay.
11//!
12//! # Access classes
13//!
14//! Sections are grouped by how the runtime *touches* them, because the paging policy differs by an
15//! order of magnitude between them:
16//!
17//! | Class | Rough size | Access pattern |
18//! |---|---|---|
19//! | [`AccessClass::HotRecurrentMicrodecoder`] | ~110 MB | reread 15× per frame — the residency target |
20//! | [`AccessClass::HotRecurrentTalker`] | ~440 MB | 28 layers, once per frame |
21//! | [`AccessClass::HotCodecDecoder`] | smaller | once per frame, latency-critical |
22//! | [`AccessClass::ColdTextEmbedding`] | ~622 MB | **row-granular**; never paged in wholesale |
23//! | [`AccessClass::EnrollmentSpeakerEncoder`] / [`AccessClass::EnrollmentCodecEncoder`] | optional | enrollment only |
24//! | [`AccessClass::Metadata`] | tiny | config, manifests, license |
25//!
26//! The cold text embedding is 622 MB that a synthesis run touches a few kilobytes of. Faulting it
27//! in as a unit would dominate startup and evict the microdecoder pack that the whole optimization
28//! program is built around, so its class is a load-bearing declaration, not a label.
29//!
30//! # Hardening
31//!
32//! This reader parses attacker-influenced binary blobs — an artifact is just a file someone gives
33//! you. Every length and offset is checked arithmetic against the real file length, section ranges
34//! may not overlap, tensor ranges must lie inside their section, counts and dimensions are capped,
35//! and every section is digest-verified before its bytes are handed out. A malformed artifact is a
36//! **named refusal**, never a partial load that resurfaces later as garbage audio.
37//!
38//! # Layout
39//!
40//! ```text
41//! [0  .. 8 )   magic          b"FTTSQ\0\0\0"
42//! [8  ..12 )   format_version u32 little-endian
43//! [12 ..20 )   directory_len  u64 little-endian
44//! [20 ..20+D)  directory      UTF-8 JSON
45//! [20+D..   )  section payloads, at absolute offsets named in the directory
46//! ```
47//!
48//! Bead: `frankentts-p2-fttsq-format-wsa`.
49
50use std::{collections::BTreeMap, fmt};
51
52use ftts_kernels::mmap::{MappedFile, MemoryAdvice, MemoryAdviceOutcome, MemoryResidency};
53use serde_json::{Value, json};
54
55use crate::sha256::{Sha256, hex_digest, to_hex};
56
57/// File magic. Eight bytes so the directory length lands 8-byte aligned.
58pub const MAGIC: &[u8; 8] = b"FTTSQ\0\0\0";
59
60/// The format version this binary writes and is the newest it can read.
61///
62/// A reader **refuses** anything newer: a future version may relocate bytes this binary would
63/// otherwise misinterpret, and "read it anyway and hope" is how a container format acquires
64/// silent, version-dependent corruption.
65pub const FORMAT_VERSION: u32 = 1;
66
67/// Fixed prefix length: magic + version + directory length.
68pub const HEADER_PREFIX_BYTES: u64 = 20;
69
70/// Largest directory we will parse, guarding against a hostile length prefix.
71///
72/// The real checkpoint's directory is a few hundred KiB. Matches `safetensors::MAX_HEADER_BYTES`
73/// deliberately — two artifact readers with different limits is a bug waiting to be found.
74pub const MAX_DIRECTORY_BYTES: u64 = 64 * 1024 * 1024;
75
76/// Most sections an artifact may declare. Seven access classes, with headroom for splitting.
77pub const MAX_SECTIONS: usize = 64;
78
79/// Most tensors an artifact may declare. The pinned checkpoint has 974.
80pub const MAX_TENSORS: usize = 16_384;
81
82/// Most dimensions one tensor may have. The pinned checkpoint's maximum rank is 4.
83pub const MAX_RANK: usize = 8;
84
85/// Largest single dimension. The largest real one is the 151,936-row text embedding.
86pub const MAX_DIM: u64 = 1 << 32;
87
88/// How the runtime touches a section. Drives the page-in policy.
89///
90/// Unknown values are refused rather than defaulted: a section whose access class we do not
91/// understand is one we cannot page correctly, and guessing "probably hot" for a 622 MB cold
92/// section is exactly the mistake that costs the microdecoder its cache residency.
93#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
94pub enum AccessClass {
95    /// The ~110 MB microdecoder hot pack, reread 15× per frame.
96    HotRecurrentMicrodecoder,
97    /// The ~440 MB talker body, read once per frame across 28 layers.
98    HotRecurrentTalker,
99    /// The codec decoder, read once per frame.
100    HotCodecDecoder,
101    /// The ~622 MB text embedding: lazy, row-granular, never paged in wholesale.
102    ColdTextEmbedding,
103    /// Speaker encoder, needed only during enrollment.
104    EnrollmentSpeakerEncoder,
105    /// Codec encoder, needed only during enrollment.
106    EnrollmentCodecEncoder,
107    /// Config, manifests, and the license notice.
108    Metadata,
109}
110
111impl AccessClass {
112    /// The stable wire string.
113    #[must_use]
114    pub const fn as_str(self) -> &'static str {
115        match self {
116            Self::HotRecurrentMicrodecoder => "HOT_RECURRENT_MICRODECODER",
117            Self::HotRecurrentTalker => "HOT_RECURRENT_TALKER",
118            Self::HotCodecDecoder => "HOT_CODEC_DECODER",
119            Self::ColdTextEmbedding => "COLD_TEXT_EMBEDDING",
120            Self::EnrollmentSpeakerEncoder => "ENROLLMENT_SPEAKER_ENCODER",
121            Self::EnrollmentCodecEncoder => "ENROLLMENT_CODEC_ENCODER",
122            Self::Metadata => "METADATA",
123        }
124    }
125
126    /// Parses a wire string, refusing anything unrecognized.
127    #[must_use]
128    pub fn parse(text: &str) -> Option<Self> {
129        Some(match text {
130            "HOT_RECURRENT_MICRODECODER" => Self::HotRecurrentMicrodecoder,
131            "HOT_RECURRENT_TALKER" => Self::HotRecurrentTalker,
132            "HOT_CODEC_DECODER" => Self::HotCodecDecoder,
133            "COLD_TEXT_EMBEDDING" => Self::ColdTextEmbedding,
134            "ENROLLMENT_SPEAKER_ENCODER" => Self::EnrollmentSpeakerEncoder,
135            "ENROLLMENT_CODEC_ENCODER" => Self::EnrollmentCodecEncoder,
136            "METADATA" => Self::Metadata,
137            _ => return None,
138        })
139    }
140
141    /// Whether this section should be resident during steady-state decode.
142    ///
143    /// Consumed by the page-in policy: hot classes are advised resident, the cold embedding is
144    /// explicitly not, and enrollment sections are only touched by the voice compiler.
145    #[must_use]
146    pub const fn is_hot(self) -> bool {
147        matches!(
148            self,
149            Self::HotRecurrentMicrodecoder | Self::HotRecurrentTalker | Self::HotCodecDecoder
150        )
151    }
152
153    /// Whether the runtime must access this section row-granularly rather than as a unit.
154    #[must_use]
155    pub const fn is_row_granular(self) -> bool {
156        matches!(self, Self::ColdTextEmbedding)
157    }
158}
159
160impl fmt::Display for AccessClass {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        f.write_str(self.as_str())
163    }
164}
165
166/// What the loader should do with a section's pages.
167///
168/// This is the **decision** layer: a total, pure function of the access class, unit-testable with
169/// no syscalls and no `unsafe`. Applying it — `mmap` plus the matching `madvise` — belongs to the
170/// audited unsafe island in `ftts-kernels`, because `ftts-artifacts` is `forbid(unsafe_code)`.
171/// Keeping the two apart means the policy can be argued about and tested here, where getting it
172/// wrong is cheap, rather than inside a syscall wrapper where it is not.
173#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
174pub enum PagePolicy {
175    /// Fault in eagerly and keep resident: `MADV_WILLNEED`.
176    ///
177    /// Only for sections read every frame. The microdecoder pack is the whole reason this variant
178    /// exists — it is reread 15× per frame and its residency is the project's #1 design center.
179    Resident,
180    /// Map lazily and let the kernel fault pages in as they are touched, a row at a time.
181    ///
182    /// **Never** `MADV_WILLNEED`. The text embedding is ~622 MB of which one synthesis touches a
183    /// few kilobytes; prefetching it wholesale would dominate startup and evict the very pages
184    /// [`PagePolicy::Resident`] exists to protect.
185    LazyRowGranular,
186    /// Map lazily, no advice. Enrollment sections a synthesis run never touches.
187    OnDemand,
188}
189
190impl PagePolicy {
191    /// The stable wire string, for `ftts inspect` and the loader's trace events.
192    #[must_use]
193    pub const fn as_str(self) -> &'static str {
194        match self {
195            Self::Resident => "resident",
196            Self::LazyRowGranular => "lazy_row_granular",
197            Self::OnDemand => "on_demand",
198        }
199    }
200
201    /// Whether the loader may issue `MADV_WILLNEED` for this policy.
202    ///
203    /// The single invariant the whole policy exists to enforce. Expressed as its own predicate so
204    /// the syscall island can assert on it directly rather than re-deriving it from the class.
205    #[must_use]
206    pub const fn may_prefetch(self) -> bool {
207        matches!(self, Self::Resident)
208    }
209}
210
211impl fmt::Display for PagePolicy {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        f.write_str(self.as_str())
214    }
215}
216
217impl AccessClass {
218    /// The page-in policy for this access class. Total, and the only place the mapping is defined.
219    #[must_use]
220    pub const fn page_policy(self) -> PagePolicy {
221        match self {
222            Self::HotRecurrentMicrodecoder | Self::HotRecurrentTalker | Self::HotCodecDecoder => {
223                PagePolicy::Resident
224            }
225            Self::ColdTextEmbedding => PagePolicy::LazyRowGranular,
226            // Metadata is tiny and read once at load; enrollment sections are untouched by
227            // synthesis. Neither earns a prefetch that would compete with the hot pack.
228            Self::EnrollmentSpeakerEncoder | Self::EnrollmentCodecEncoder | Self::Metadata => {
229                PagePolicy::OnDemand
230            }
231        }
232    }
233}
234
235/// How a tensor's elements are stored in the container.
236///
237/// Narrow on purpose, and unknown values are refused. The high-precision variants exist because
238/// the quant recipe protects norms, codebooks, and the speaker path — an artifact that claims a
239/// dtype we have never conformed is a refusal, not a best-effort read.
240#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
241pub enum StoredDtype {
242    /// bfloat16, kept verbatim from the checkpoint.
243    Bf16,
244    /// 32-bit float.
245    F32,
246    /// int8 with separate per-group scales.
247    Q8,
248    /// int4, two elements per byte, with separate per-group scales.
249    Q4,
250}
251
252impl StoredDtype {
253    /// The stable wire string.
254    #[must_use]
255    pub const fn as_str(self) -> &'static str {
256        match self {
257            Self::Bf16 => "bf16",
258            Self::F32 => "f32",
259            Self::Q8 => "q8",
260            Self::Q4 => "q4",
261        }
262    }
263
264    /// Parses a wire string, refusing anything unrecognized.
265    #[must_use]
266    pub fn parse(text: &str) -> Option<Self> {
267        Some(match text {
268            "bf16" => Self::Bf16,
269            "f32" => Self::F32,
270            "q8" => Self::Q8,
271            "q4" => Self::Q4,
272            _ => return None,
273        })
274    }
275
276    /// Storage bytes for `elements` values, or `None` on overflow.
277    ///
278    /// Q4 packs two elements per byte and rounds up, so an odd element count still occupies a whole
279    /// trailing byte.
280    #[must_use]
281    pub const fn storage_bytes(self, elements: u64) -> Option<u64> {
282        match self {
283            Self::Bf16 => elements.checked_mul(2),
284            Self::F32 => elements.checked_mul(4),
285            Self::Q8 => Some(elements),
286            Self::Q4 => match elements.checked_add(1) {
287                Some(padded) => Some(padded / 2),
288                None => None,
289            },
290        }
291    }
292}
293
294impl fmt::Display for StoredDtype {
295    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296        f.write_str(self.as_str())
297    }
298}
299
300/// One access-class section: a contiguous, digest-verified byte range.
301#[derive(Clone, Debug, PartialEq, Eq)]
302pub struct SectionEntry {
303    /// Section name, unique within the artifact.
304    pub name: String,
305    /// How the runtime touches it.
306    pub access_class: AccessClass,
307    /// Absolute offset into the file.
308    pub offset: u64,
309    /// Byte length.
310    pub length: u64,
311    /// Lowercase-hex SHA-256 of the section bytes.
312    pub sha256: String,
313}
314
315impl SectionEntry {
316    /// Exclusive end offset, or `None` on overflow.
317    #[must_use]
318    pub const fn end(&self) -> Option<u64> {
319        self.offset.checked_add(self.length)
320    }
321}
322
323/// One tensor, located relative to the start of its section.
324#[derive(Clone, Debug, PartialEq, Eq)]
325pub struct TensorEntry {
326    /// Tensor name, unique within the artifact.
327    pub name: String,
328    /// Name of the section holding it.
329    pub section: String,
330    /// Storage dtype.
331    pub dtype: StoredDtype,
332    /// Logical shape.
333    pub shape: Vec<u64>,
334    /// Offset **relative to the section start**, so a section can move without rewriting tensors.
335    pub offset: u64,
336    /// Byte length; must equal the size implied by shape and dtype.
337    pub length: u64,
338    /// Name of the tensor holding this one's quantization scales, when quantized.
339    pub scales: Option<String>,
340}
341
342impl TensorEntry {
343    /// Element count, or `None` on overflow.
344    #[must_use]
345    pub fn elements(&self) -> Option<u64> {
346        self.shape
347            .iter()
348            .try_fold(1_u64, |acc, &d| acc.checked_mul(d))
349    }
350}
351
352/// What went wrong reading an artifact.
353///
354/// Every variant names the offending section, tensor, or offset: a refusal that does not say what
355/// it refused costs an hour of bisecting a multi-gigabyte file.
356#[derive(Clone, Debug, PartialEq, Eq)]
357pub enum FttsqError {
358    /// File is shorter than the fixed header prefix.
359    TooShort {
360        /// Bytes actually present.
361        length: u64,
362    },
363    /// Magic bytes are not `FTTSQ`.
364    BadMagic {
365        /// The first eight bytes found.
366        found: [u8; 8],
367    },
368    /// The artifact is newer than this binary understands.
369    UnsupportedVersion {
370        /// Version recorded in the file.
371        found: u32,
372        /// Newest version this binary reads.
373        supported: u32,
374    },
375    /// The directory length is implausible or does not fit in the file.
376    DirectoryLength {
377        /// Declared length.
378        declared: u64,
379        /// Cap or file length it violated.
380        limit: u64,
381    },
382    /// The directory is not valid UTF-8 JSON, or is not an object.
383    DirectoryMalformed {
384        /// What the parser said.
385        detail: String,
386    },
387    /// A required field is missing or the wrong type.
388    Field {
389        /// JSON path of the field.
390        path: String,
391        /// What was expected.
392        expected: String,
393    },
394    /// An enum carried a value this binary does not know.
395    UnknownValue {
396        /// JSON path of the field.
397        path: String,
398        /// The unrecognized value.
399        found: String,
400    },
401    /// A declared count or dimension exceeds its cap.
402    LimitExceeded {
403        /// What was being counted.
404        what: String,
405        /// Declared value.
406        found: u64,
407        /// The cap.
408        limit: u64,
409    },
410    /// A byte range overflowed or ran past the end of its container.
411    RangeOutOfBounds {
412        /// What the range belongs to.
413        what: String,
414        /// Start offset.
415        offset: u64,
416        /// Length.
417        length: u64,
418        /// The bound it violated.
419        bound: u64,
420    },
421    /// Two sections claim overlapping bytes.
422    SectionOverlap {
423        /// The earlier section.
424        first: String,
425        /// The later section.
426        second: String,
427    },
428    /// Two tensors in one section claim overlapping bytes.
429    TensorOverlap {
430        /// The earlier tensor.
431        first: String,
432        /// The later tensor.
433        second: String,
434    },
435    /// A name is declared twice.
436    DuplicateName {
437        /// What kind of thing.
438        what: String,
439        /// The repeated name.
440        name: String,
441    },
442    /// A tensor references a section that does not exist.
443    UnknownSection {
444        /// The tensor.
445        tensor: String,
446        /// The section it named.
447        section: String,
448    },
449    /// A tensor's declared length disagrees with its shape and dtype.
450    LengthMismatch {
451        /// The tensor.
452        tensor: String,
453        /// Length the directory declared.
454        declared: u64,
455        /// Length implied by shape and dtype.
456        implied: u64,
457    },
458    /// A section's bytes do not match its recorded digest.
459    DigestMismatch {
460        /// The section.
461        section: String,
462        /// Digest the directory recorded.
463        expected: String,
464        /// Digest the bytes actually produce.
465        actual: String,
466    },
467    /// The mandatory license notice is absent or empty.
468    ///
469    /// Apache-2.0 §4 attaches to every artifact we publish; an artifact without the notice must not
470    /// be readable, or the obligation becomes advisory in practice.
471    LicenseNoticeMissing,
472    /// A streaming writer received bytes for a section other than the next declared one.
473    SectionWriteOutOfOrder {
474        /// Section the stream expected next, or `None` once all sections are complete.
475        expected: Option<String>,
476        /// Section the caller attempted to write.
477        actual: String,
478    },
479    /// A streaming writer was given more bytes than its declared section length.
480    SectionLengthExceeded {
481        /// Section receiving bytes.
482        section: String,
483        /// Length fixed in the conversion plan.
484        declared: u64,
485        /// Total bytes the write would have produced.
486        attempted: u64,
487    },
488    /// A streaming writer was finalized before its current section was complete.
489    SectionIncomplete {
490        /// Section still awaiting bytes.
491        section: String,
492        /// Length fixed in the conversion plan.
493        declared: u64,
494        /// Bytes successfully written so far.
495        written: u64,
496    },
497    /// A filesystem operation failed.
498    ///
499    /// Carries the rendered message rather than [`std::io::Error`] so this enum stays `Clone` and
500    /// `PartialEq` — properties the tests and the fuzz target rely on.
501    Io {
502        /// What was being attempted.
503        operation: String,
504        /// The path involved.
505        path: String,
506        /// The OS error text.
507        detail: String,
508    },
509}
510
511impl fmt::Display for FttsqError {
512    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
513        match self {
514            Self::TooShort { length } => write!(
515                f,
516                "not a .fttsq artifact: {length} bytes is shorter than the {HEADER_PREFIX_BYTES}-byte header"
517            ),
518            Self::BadMagic { found } => {
519                write!(f, "not a .fttsq artifact: magic {found:?} is not {MAGIC:?}")
520            }
521            Self::UnsupportedVersion { found, supported } => write!(
522                f,
523                "artifact format version {found} is newer than this binary supports ({supported}); \
524                 upgrade ftts rather than reading it with a stale layout"
525            ),
526            Self::DirectoryLength { declared, limit } => {
527                write!(f, "directory length {declared} exceeds {limit}")
528            }
529            Self::DirectoryMalformed { detail } => write!(f, "directory is malformed: {detail}"),
530            Self::Field { path, expected } => {
531                write!(f, "directory field `{path}` is missing or not {expected}")
532            }
533            Self::UnknownValue { path, found } => write!(
534                f,
535                "directory field `{path}` has unknown value `{found}`; this artifact needs a newer ftts"
536            ),
537            Self::LimitExceeded { what, found, limit } => {
538                write!(f, "{what} count {found} exceeds the cap of {limit}")
539            }
540            Self::RangeOutOfBounds {
541                what,
542                offset,
543                length,
544                bound,
545            } => write!(
546                f,
547                "{what} range [{offset}, {offset}+{length}) runs past its bound {bound}"
548            ),
549            Self::SectionOverlap { first, second } => write!(
550                f,
551                "sections `{first}` and `{second}` claim overlapping bytes"
552            ),
553            Self::TensorOverlap { first, second } => write!(
554                f,
555                "tensors `{first}` and `{second}` claim overlapping bytes"
556            ),
557            Self::DuplicateName { what, name } => write!(f, "{what} `{name}` is declared twice"),
558            Self::UnknownSection { tensor, section } => write!(
559                f,
560                "tensor `{tensor}` names section `{section}`, which is not declared"
561            ),
562            Self::LengthMismatch {
563                tensor,
564                declared,
565                implied,
566            } => write!(
567                f,
568                "tensor `{tensor}` declares {declared} bytes but its shape and dtype imply {implied}"
569            ),
570            Self::DigestMismatch {
571                section,
572                expected,
573                actual,
574            } => write!(
575                f,
576                "section `{section}` is corrupt: recorded sha256 {expected}, computed {actual}"
577            ),
578            Self::LicenseNoticeMissing => f.write_str(
579                "artifact carries no license_notice; Apache-2.0 §4 requires it on every published \
580                 artifact, so an artifact without one is refused rather than silently accepted",
581            ),
582            Self::SectionWriteOutOfOrder { expected, actual } => match expected {
583                Some(expected) => write!(
584                    f,
585                    "streaming .fttsq writer expected section `{expected}`, not `{actual}`"
586                ),
587                None => write!(
588                    f,
589                    "streaming .fttsq writer is complete and cannot accept section `{actual}`"
590                ),
591            },
592            Self::SectionLengthExceeded {
593                section,
594                declared,
595                attempted,
596            } => write!(
597                f,
598                "section `{section}` declares {declared} bytes but streaming write would reach {attempted}"
599            ),
600            Self::SectionIncomplete {
601                section,
602                declared,
603                written,
604            } => write!(
605                f,
606                "section `{section}` declares {declared} bytes but only {written} were written"
607            ),
608            Self::Io {
609                operation,
610                path,
611                detail,
612            } => write!(f, "{operation} failed for `{path}`: {detail}"),
613        }
614    }
615}
616
617impl std::error::Error for FttsqError {}
618
619/// A parsed, validated `.fttsq` directory over a buffer the reader does not own.
620#[derive(Clone, Debug)]
621pub struct FttsqReader {
622    format_version: u32,
623    model_family: String,
624    source_sha256: String,
625    license_notice: String,
626    model_config: Value,
627    quantization_manifest: Value,
628    sections: Vec<SectionEntry>,
629    tensors: Vec<TensorEntry>,
630    section_index: BTreeMap<String, usize>,
631    tensor_index: BTreeMap<String, usize>,
632}
633
634/// The observable result of applying one access-class policy to a mapped section.
635///
636/// An advice failure is reported but does not reject an otherwise valid artifact: `madvise` is a
637/// performance hint, never an integrity mechanism. The reader still verifies every section digest
638/// before this record is produced, so a failed hint cannot turn corruption into a delayed fault.
639#[derive(Clone, Debug, PartialEq, Eq)]
640pub enum PageAdviceOutcome {
641    /// The access class requires no syscall; the section remains lazily mapped.
642    NotRequested,
643    /// The matching native advisory request succeeded.
644    Applied,
645    /// The section has no bytes to advise.
646    SkippedEmpty,
647    /// The build/platform uses the safe owned-byte fallback instead of an unimplemented OS FFI.
648    Unsupported,
649    /// The OS rejected a performance hint; artifact correctness is unaffected.
650    Failed(String),
651}
652
653/// An observed residency count for one section at a policy boundary.
654///
655/// This records a measurement for the OQ-18 access-class evidence, not a contract with the kernel:
656/// page-cache state can change immediately after `mincore` returns, and a failed observation must
657/// never reject an otherwise valid artifact.
658#[derive(Clone, Debug, PartialEq, Eq)]
659pub enum PageResidencyOutcome {
660    /// The kernel reported the number of resident pages in the section's range.
661    Measured {
662        /// Pages resident at the observation point.
663        resident_pages: usize,
664        /// Pages spanned by this section.
665        total_pages: usize,
666    },
667    /// This build has no audited residency-query implementation.
668    Unsupported,
669    /// The OS rejected the observation; artifact integrity is unaffected.
670    Failed(String),
671}
672
673/// One section's page-in decision and what the loader actually requested.
674#[derive(Clone, Debug, PartialEq, Eq)]
675pub struct PageAdviceApplication {
676    /// Artifact section name.
677    pub section: String,
678    /// The pure access-class policy selected by [`AccessClass::page_policy`].
679    pub policy: PagePolicy,
680    /// The OS hint issued for this section, if the policy calls for one.
681    pub requested: Option<MemoryAdvice>,
682    /// Residency observed immediately before the policy request.
683    pub residency_before: PageResidencyOutcome,
684    /// The outcome of that request.
685    pub outcome: PageAdviceOutcome,
686    /// Residency observed immediately after the policy request and before eager v1 digest checks.
687    pub residency_after: PageResidencyOutcome,
688}
689
690/// A fully verified `.fttsq` held as a read-only mapping (or the safe owned-byte fallback).
691///
692/// The mapping owns the bytes while [`FttsqReader`] owns only the validated directory, so tensor
693/// views borrow the artifact rather than duplicating multi-gigabyte payloads. The loader validates
694/// the directory's structure and ranges, applies bounded OS advice, then verifies every digest
695/// before returning. That ordering lets `MADV_WILLNEED` begin while validation runs; the mapping is
696/// never exposed to callers until the hardened reader has accepted every section digest.
697#[derive(Debug)]
698pub struct MappedFttsq {
699    mapping: MappedFile,
700    reader: FttsqReader,
701    page_advice: Vec<PageAdviceApplication>,
702}
703
704impl MappedFttsq {
705    /// Map, fully validate, and apply the access-class page-in plan to an artifact.
706    ///
707    /// The integrity check remains eager because the v1 format carries a digest per section rather
708    /// than a per-page Merkle tree. Consequently, this loader establishes a *policy* guarantee —
709    /// the cold embedding is never sent `MADV_WILLNEED` — and records the pre-digest residency
710    /// measurement, not a claim that opening v1 avoids every cold-section page fault. A
711    /// page-granular integrity format is required before that stronger claim can be made honestly.
712    ///
713    /// # Errors
714    ///
715    /// Returns a named [`FttsqError`] for mapping, structural, or digest-validation failures.
716    pub fn open(path: impl AsRef<std::path::Path>) -> Result<Self, FttsqError> {
717        let path = path.as_ref();
718        let mapping = MappedFile::open(path).map_err(|error| FttsqError::Io {
719            operation: "memory-map artifact".to_owned(),
720            path: path.display().to_string(),
721            detail: error.to_string(),
722        })?;
723        // Structural validation bounds each requested range before an OS call. The subsequent
724        // digest pass is still mandatory before this mapping can be returned to a caller.
725        let reader = FttsqReader::parse_directory(mapping.as_slice())?;
726        let page_advice = apply_page_in_plan(&mapping, &reader);
727        reader.verify_digests(mapping.as_slice())?;
728        Ok(Self {
729            mapping,
730            reader,
731            page_advice,
732        })
733    }
734
735    /// Validate an artifact held wholly in memory, for targets with no filesystem (wasm32).
736    ///
737    /// Runs the identical structural-then-digest pipeline as [`MappedFttsq::open`]; only the
738    /// byte source differs, so a buffer that verifies here is exactly a file that would have
739    /// verified there.
740    ///
741    /// # Errors
742    ///
743    /// Returns the same named [`FttsqError`] classes as [`MappedFttsq::open`].
744    #[cfg(not(unix))] // the owned-bytes MappedFile backing (and its from_bytes) exists off-unix
745    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, FttsqError> {
746        let mapping = MappedFile::from_bytes(bytes);
747        let reader = FttsqReader::parse_directory(mapping.as_slice())?;
748        let page_advice = apply_page_in_plan(&mapping, &reader);
749        reader.verify_digests(mapping.as_slice())?;
750        Ok(Self {
751            mapping,
752            reader,
753            page_advice,
754        })
755    }
756
757    /// The fully validated directory over this artifact.
758    #[must_use]
759    pub const fn reader(&self) -> &FttsqReader {
760        &self.reader
761    }
762
763    /// The policy application record, in the same priority order as `page_in_plan`.
764    #[must_use]
765    pub fn page_advice(&self) -> &[PageAdviceApplication] {
766        &self.page_advice
767    }
768
769    /// Borrow one tensor's logical bytes without copying the mapped payload.
770    ///
771    /// # Errors
772    ///
773    /// Returns the same named lookup/range errors as [`FttsqReader::tensor_bytes`].
774    pub fn tensor_bytes(&self, name: &str) -> Result<&[u8], FttsqError> {
775        self.reader.tensor_bytes(name, self.mapping.as_slice())
776    }
777
778    /// The artifact's mapped (or fallback-owned) byte length.
779    #[must_use]
780    pub fn len(&self) -> usize {
781        self.mapping.len()
782    }
783
784    /// Whether this artifact is empty.
785    #[must_use]
786    pub fn is_empty(&self) -> bool {
787        self.mapping.is_empty()
788    }
789}
790
791fn apply_page_in_plan(mapping: &MappedFile, reader: &FttsqReader) -> Vec<PageAdviceApplication> {
792    reader
793        .page_in_plan()
794        .into_iter()
795        .map(|(section, policy)| {
796            let requested = match policy {
797                PagePolicy::Resident => Some(MemoryAdvice::WillNeed),
798                PagePolicy::LazyRowGranular => Some(MemoryAdvice::Random),
799                PagePolicy::OnDemand => None,
800            };
801
802            // This is intentionally a runtime assertion rather than a documentation promise. A
803            // future policy refactor must fail immediately if it ever routes the 622 MB cold text
804            // embedding through `MADV_WILLNEED`.
805            assert!(
806                policy.may_prefetch() || requested != Some(MemoryAdvice::WillNeed),
807                "a non-prefetch policy must never issue MADV_WILLNEED"
808            );
809
810            let residency_before = observe_residency(mapping, section.offset, section.length);
811            let outcome = match requested {
812                Some(advice) => match mapping.advise(section.offset, section.length, advice) {
813                    Ok(MemoryAdviceOutcome::Applied) => PageAdviceOutcome::Applied,
814                    Ok(MemoryAdviceOutcome::SkippedEmpty) => PageAdviceOutcome::SkippedEmpty,
815                    Ok(MemoryAdviceOutcome::Unsupported) => PageAdviceOutcome::Unsupported,
816                    Err(error) => PageAdviceOutcome::Failed(error.to_string()),
817                },
818                None => PageAdviceOutcome::NotRequested,
819            };
820            let residency_after = observe_residency(mapping, section.offset, section.length);
821
822            PageAdviceApplication {
823                section: section.name.clone(),
824                policy,
825                requested,
826                residency_before,
827                outcome,
828                residency_after,
829            }
830        })
831        .collect()
832}
833
834fn observe_residency(mapping: &MappedFile, offset: u64, length: u64) -> PageResidencyOutcome {
835    match mapping.resident_pages(offset, length) {
836        Ok(MemoryResidency::Measured {
837            resident_pages,
838            total_pages,
839        }) => PageResidencyOutcome::Measured {
840            resident_pages,
841            total_pages,
842        },
843        Ok(MemoryResidency::Unsupported) => PageResidencyOutcome::Unsupported,
844        Err(error) => PageResidencyOutcome::Failed(error.to_string()),
845    }
846}
847
848impl FttsqReader {
849    /// Parses and fully validates an artifact, **including** every section digest.
850    ///
851    /// Digest verification is not optional here. A reader that can be asked to skip it grows a
852    /// caller that always skips it, and then corruption surfaces as audio rather than as an error.
853    ///
854    /// # Errors
855    ///
856    /// Returns a named [`FttsqError`] for any structural, range, or integrity violation.
857    pub fn open(bytes: &[u8]) -> Result<Self, FttsqError> {
858        let reader = Self::parse_directory(bytes)?;
859        reader.verify_digests(bytes)?;
860        Ok(reader)
861    }
862
863    /// Parses and structurally validates without computing digests.
864    ///
865    /// For inspection tooling (`ftts inspect`) over an artifact whose bytes are not all present —
866    /// listing a remote artifact's tensors, say. Never use this to load weights: it does not prove
867    /// the payload is intact.
868    ///
869    /// # Errors
870    ///
871    /// Returns a named [`FttsqError`] for any structural or range violation.
872    pub fn parse_directory(bytes: &[u8]) -> Result<Self, FttsqError> {
873        Self::parse_directory_for_file_len(bytes, bytes.len() as u64)
874    }
875
876    /// Parses a present header and directory against a declared final file length.
877    ///
878    /// The streaming writer uses this before accepting payload bytes: only the header and
879    /// directory are in memory at that point, but all declared section and tensor ranges must
880    /// already be valid for the eventual artifact length. It stays private so callers cannot
881    /// mistake a structural preflight for a verified artifact load.
882    fn parse_directory_for_file_len(bytes: &[u8], file_len: u64) -> Result<Self, FttsqError> {
883        let present_len = bytes.len() as u64;
884        if present_len < HEADER_PREFIX_BYTES {
885            return Err(FttsqError::TooShort {
886                length: present_len,
887            });
888        }
889
890        let mut magic = [0_u8; 8];
891        magic.copy_from_slice(&bytes[..8]);
892        if &magic != MAGIC {
893            return Err(FttsqError::BadMagic { found: magic });
894        }
895
896        let format_version = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
897        // Version 0 was never issued: accepting it would silently read an unversioned or
898        // zero-filled header as v1. Valid artifacts carry 1..=FORMAT_VERSION.
899        if format_version == 0 || format_version > FORMAT_VERSION {
900            return Err(FttsqError::UnsupportedVersion {
901                found: format_version,
902                supported: FORMAT_VERSION,
903            });
904        }
905
906        let mut length_bytes = [0_u8; 8];
907        length_bytes.copy_from_slice(&bytes[12..20]);
908        let directory_len = u64::from_le_bytes(length_bytes);
909        if directory_len > MAX_DIRECTORY_BYTES {
910            return Err(FttsqError::DirectoryLength {
911                declared: directory_len,
912                limit: MAX_DIRECTORY_BYTES,
913            });
914        }
915        let directory_end =
916            HEADER_PREFIX_BYTES
917                .checked_add(directory_len)
918                .ok_or(FttsqError::DirectoryLength {
919                    declared: directory_len,
920                    limit: u64::MAX,
921                })?;
922        if directory_end > present_len || directory_end > file_len {
923            return Err(FttsqError::DirectoryLength {
924                declared: directory_len,
925                limit: present_len.min(file_len),
926            });
927        }
928
929        // `directory_end` is bounded by `file_len` above, so both casts are in range.
930        let directory_bytes = &bytes[HEADER_PREFIX_BYTES as usize..directory_end as usize];
931        let directory: Value = serde_json::from_slice(directory_bytes).map_err(|error| {
932            FttsqError::DirectoryMalformed {
933                detail: error.to_string(),
934            }
935        })?;
936        let object = directory
937            .as_object()
938            .ok_or_else(|| FttsqError::DirectoryMalformed {
939                detail: "top level is not a JSON object".to_owned(),
940            })?;
941
942        let model_family = required_str(object.get("model_family"), "model_family")?.to_owned();
943        let source_sha256 = required_str(object.get("source_sha256"), "source_sha256")?.to_owned();
944
945        // Apache-2.0 §4 compliance is a read-time gate, not a writer-side courtesy.
946        let license_notice = object
947            .get("license_notice")
948            .and_then(Value::as_str)
949            .unwrap_or_default()
950            .to_owned();
951        if license_notice.trim().is_empty() {
952            return Err(FttsqError::LicenseNoticeMissing);
953        }
954
955        let model_config = object.get("model_config").cloned().unwrap_or(Value::Null);
956        let quantization_manifest = object
957            .get("quantization_manifest")
958            .cloned()
959            .unwrap_or(Value::Null);
960
961        let sections = parse_sections(object.get("sections"), file_len)?;
962        let section_index: BTreeMap<String, usize> = sections
963            .iter()
964            .enumerate()
965            .map(|(index, section)| (section.name.clone(), index))
966            .collect();
967        let tensors = parse_tensors(object.get("tensors"), &sections, &section_index)?;
968        let tensor_index: BTreeMap<String, usize> = tensors
969            .iter()
970            .enumerate()
971            .map(|(index, tensor)| (tensor.name.clone(), index))
972            .collect();
973
974        Ok(Self {
975            format_version,
976            model_family,
977            source_sha256,
978            license_notice,
979            model_config,
980            quantization_manifest,
981            sections,
982            tensors,
983            section_index,
984            tensor_index,
985        })
986    }
987
988    /// Recomputes and checks every section digest against `bytes`.
989    ///
990    /// # Errors
991    ///
992    /// Returns [`FttsqError::DigestMismatch`] naming the first corrupt section.
993    pub fn verify_digests(&self, bytes: &[u8]) -> Result<(), FttsqError> {
994        for section in &self.sections {
995            let payload = self.section_bytes(section, bytes)?;
996            let mut hasher = Sha256::new();
997            hasher.update(payload);
998            let actual = to_hex(&hasher.finish());
999            if actual != section.sha256 {
1000                return Err(FttsqError::DigestMismatch {
1001                    section: section.name.clone(),
1002                    expected: section.sha256.clone(),
1003                    actual,
1004                });
1005            }
1006        }
1007        Ok(())
1008    }
1009
1010    fn section_bytes<'a>(
1011        &self,
1012        section: &SectionEntry,
1013        bytes: &'a [u8],
1014    ) -> Result<&'a [u8], FttsqError> {
1015        let end = section.end().ok_or_else(|| FttsqError::RangeOutOfBounds {
1016            what: format!("section `{}`", section.name),
1017            offset: section.offset,
1018            length: section.length,
1019            bound: bytes.len() as u64,
1020        })?;
1021        if end > bytes.len() as u64 {
1022            return Err(FttsqError::RangeOutOfBounds {
1023                what: format!("section `{}`", section.name),
1024                offset: section.offset,
1025                length: section.length,
1026                bound: bytes.len() as u64,
1027            });
1028        }
1029        Ok(&bytes[section.offset as usize..end as usize])
1030    }
1031
1032    /// The format version the artifact declares.
1033    #[must_use]
1034    pub const fn format_version(&self) -> u32 {
1035        self.format_version
1036    }
1037
1038    /// The model family the artifact was built from.
1039    #[must_use]
1040    pub fn model_family(&self) -> &str {
1041        &self.model_family
1042    }
1043
1044    /// SHA-256 of the upstream checkpoint this artifact was converted from.
1045    #[must_use]
1046    pub fn source_sha256(&self) -> &str {
1047        &self.source_sha256
1048    }
1049
1050    /// The Apache-2.0 §4 attribution notice. Guaranteed non-empty.
1051    #[must_use]
1052    pub fn license_notice(&self) -> &str {
1053        &self.license_notice
1054    }
1055
1056    /// The frozen copy of the upstream model config.
1057    #[must_use]
1058    pub const fn model_config(&self) -> &Value {
1059        &self.model_config
1060    }
1061
1062    /// The per-tensor quantization policy actually applied, which the license notice references.
1063    #[must_use]
1064    pub const fn quantization_manifest(&self) -> &Value {
1065        &self.quantization_manifest
1066    }
1067
1068    /// Every declared section, in declaration order.
1069    #[must_use]
1070    pub fn sections(&self) -> &[SectionEntry] {
1071        &self.sections
1072    }
1073
1074    /// Every declared tensor, in declaration order.
1075    #[must_use]
1076    pub fn tensors(&self) -> &[TensorEntry] {
1077        &self.tensors
1078    }
1079
1080    /// Looks a section up by name.
1081    #[must_use]
1082    pub fn section(&self, name: &str) -> Option<&SectionEntry> {
1083        self.section_index
1084            .get(name)
1085            .and_then(|&index| self.sections.get(index))
1086    }
1087
1088    /// Looks a tensor up by name.
1089    #[must_use]
1090    pub fn tensor(&self, name: &str) -> Option<&TensorEntry> {
1091        self.tensor_index
1092            .get(name)
1093            .and_then(|&index| self.tensors.get(index))
1094    }
1095
1096    /// Sections belonging to one access class.
1097    #[must_use]
1098    pub fn sections_in_class(&self, class: AccessClass) -> Vec<&SectionEntry> {
1099        self.sections
1100            .iter()
1101            .filter(|section| section.access_class == class)
1102            .collect()
1103    }
1104
1105    /// The byte span of one tensor within `bytes`.
1106    ///
1107    /// # Errors
1108    ///
1109    /// Returns [`FttsqError::UnknownSection`] when the tensor's section is missing, or
1110    /// [`FttsqError::RangeOutOfBounds`] when the resolved span leaves the buffer.
1111    pub fn tensor_bytes<'a>(&self, name: &str, bytes: &'a [u8]) -> Result<&'a [u8], FttsqError> {
1112        let tensor = self
1113            .tensor(name)
1114            .ok_or_else(|| FttsqError::UnknownSection {
1115                tensor: name.to_owned(),
1116                section: "<unknown tensor>".to_owned(),
1117            })?;
1118        let section = self
1119            .section(&tensor.section)
1120            .ok_or_else(|| FttsqError::UnknownSection {
1121                tensor: tensor.name.clone(),
1122                section: tensor.section.clone(),
1123            })?;
1124        let payload = self.section_bytes(section, bytes)?;
1125        let end = tensor.offset.checked_add(tensor.length).ok_or_else(|| {
1126            FttsqError::RangeOutOfBounds {
1127                what: format!("tensor `{}`", tensor.name),
1128                offset: tensor.offset,
1129                length: tensor.length,
1130                bound: payload.len() as u64,
1131            }
1132        })?;
1133        if end > payload.len() as u64 {
1134            return Err(FttsqError::RangeOutOfBounds {
1135                what: format!("tensor `{}`", tensor.name),
1136                offset: tensor.offset,
1137                length: tensor.length,
1138                bound: payload.len() as u64,
1139            });
1140        }
1141        Ok(&payload[tensor.offset as usize..end as usize])
1142    }
1143
1144    /// The page-in plan: every section paired with its policy, in the order to apply them.
1145    ///
1146    /// Ordered [`PagePolicy::Resident`] first. Prefetch order matters under memory pressure — the
1147    /// section that gets its `MADV_WILLNEED` in last is the one most likely to find the page cache
1148    /// already full, and that must never be the microdecoder pack.
1149    ///
1150    /// Within the resident group, smaller sections come first: the ~110 MB microdecoder pack lands
1151    /// ahead of the ~440 MB talker, so the reread-15×-per-frame working set wins the race for
1152    /// cache it would otherwise lose to a body read once per frame.
1153    #[must_use]
1154    pub fn page_in_plan(&self) -> Vec<(&SectionEntry, PagePolicy)> {
1155        let mut plan: Vec<(&SectionEntry, PagePolicy)> = self
1156            .sections
1157            .iter()
1158            .map(|section| (section, section.access_class.page_policy()))
1159            .collect();
1160        plan.sort_by_key(|(section, policy)| {
1161            let rank = match policy {
1162                PagePolicy::Resident => 0_u8,
1163                PagePolicy::LazyRowGranular => 1,
1164                PagePolicy::OnDemand => 2,
1165            };
1166            (rank, section.length)
1167        });
1168        plan
1169    }
1170
1171    /// Audits the artifact's tensor directory against an expected inventory.
1172    ///
1173    /// # Errors
1174    ///
1175    /// Returns the itemized [`ArtifactCensus`] when anything is missing, extra, or mis-declared.
1176    pub fn verify_census(&self, manifest: &ArtifactManifest) -> Result<(), Box<ArtifactCensus>> {
1177        let report = manifest.audit(self);
1178        if report.is_green() {
1179            Ok(())
1180        } else {
1181            Err(Box::new(report))
1182        }
1183    }
1184}
1185
1186/// One tensor the artifact is required to carry, and where.
1187///
1188/// Distinct from [`crate::census::ExpectedTensor`] on purpose, and the difference is not
1189/// incidental: that one audits an *upstream* checkpoint, so it speaks
1190/// [`crate::safetensors::Dtype`] (bf16/f32) and has no notion of sections. A `.fttsq` is a
1191/// **quantized** artifact, so its expectations are keyed on [`StoredDtype`] and additionally pin
1192/// the [`AccessClass`] — a tensor that lands in the wrong section still loads and still produces
1193/// correct audio, while quietly destroying the residency the whole optimization program depends on.
1194/// That failure is invisible to a dtype-and-shape census, which is exactly why this one exists.
1195#[derive(Clone, Debug, PartialEq, Eq)]
1196pub struct ExpectedArtifactTensor {
1197    /// Tensor name, exactly as the directory records it.
1198    pub name: String,
1199    /// Required logical shape.
1200    pub shape: Vec<u64>,
1201    /// Required storage dtype after quantization.
1202    pub dtype: StoredDtype,
1203    /// The access class whose section must hold it.
1204    pub access_class: AccessClass,
1205}
1206
1207/// One way an artifact diverged from its expected inventory.
1208#[derive(Clone, Debug, PartialEq, Eq)]
1209pub enum ArtifactFinding {
1210    /// A required tensor is absent. Certain failure downstream.
1211    Missing {
1212        /// The tensor.
1213        name: String,
1214    },
1215    /// The artifact carries a tensor the manifest does not list — the signature of a different
1216    /// checkpoint or a converter that changed its naming.
1217    Extra {
1218        /// The tensor.
1219        name: String,
1220    },
1221    /// Present, but not the shape we compiled kernels for.
1222    ShapeMismatch {
1223        /// The tensor.
1224        name: String,
1225        /// Expected shape.
1226        expected: Vec<u64>,
1227        /// Shape found.
1228        found: Vec<u64>,
1229    },
1230    /// Present, but quantized differently than the recipe says.
1231    DtypeMismatch {
1232        /// The tensor.
1233        name: String,
1234        /// Expected dtype.
1235        expected: StoredDtype,
1236        /// Dtype found.
1237        found: StoredDtype,
1238    },
1239    /// Present and correct, but filed under the wrong access class.
1240    ///
1241    /// The quiet one: audio stays correct while the page-in policy silently becomes wrong.
1242    WrongAccessClass {
1243        /// The tensor.
1244        name: String,
1245        /// Expected class.
1246        expected: AccessClass,
1247        /// Class found.
1248        found: AccessClass,
1249    },
1250    /// A tensor names a section the artifact does not declare.
1251    DanglingSection {
1252        /// The tensor.
1253        name: String,
1254        /// The section it named.
1255        section: String,
1256    },
1257}
1258
1259impl ArtifactFinding {
1260    /// The tensor this finding is about.
1261    #[must_use]
1262    pub fn tensor(&self) -> &str {
1263        match self {
1264            Self::Missing { name }
1265            | Self::Extra { name }
1266            | Self::ShapeMismatch { name, .. }
1267            | Self::DtypeMismatch { name, .. }
1268            | Self::WrongAccessClass { name, .. }
1269            | Self::DanglingSection { name, .. } => name,
1270        }
1271    }
1272
1273    /// Short class label, for counting findings by kind.
1274    #[must_use]
1275    pub const fn class(&self) -> &'static str {
1276        match self {
1277            Self::Missing { .. } => "missing",
1278            Self::Extra { .. } => "extra",
1279            Self::ShapeMismatch { .. } => "shape_mismatch",
1280            Self::DtypeMismatch { .. } => "dtype_mismatch",
1281            Self::WrongAccessClass { .. } => "wrong_access_class",
1282            Self::DanglingSection { .. } => "dangling_section",
1283        }
1284    }
1285}
1286
1287impl fmt::Display for ArtifactFinding {
1288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1289        match self {
1290            Self::Missing { name } => write!(f, "MISSING       {name}"),
1291            Self::Extra { name } => write!(f, "EXTRA         {name}"),
1292            Self::ShapeMismatch {
1293                name,
1294                expected,
1295                found,
1296            } => write!(
1297                f,
1298                "SHAPE         {name}: expected {expected:?}, found {found:?}"
1299            ),
1300            Self::DtypeMismatch {
1301                name,
1302                expected,
1303                found,
1304            } => write!(
1305                f,
1306                "DTYPE         {name}: expected {expected}, found {found}"
1307            ),
1308            Self::WrongAccessClass {
1309                name,
1310                expected,
1311                found,
1312            } => write!(
1313                f,
1314                "ACCESS_CLASS  {name}: expected {expected}, found {found}"
1315            ),
1316            Self::DanglingSection { name, section } => {
1317                write!(
1318                    f,
1319                    "DANGLING      {name}: names undeclared section `{section}`"
1320                )
1321            }
1322        }
1323    }
1324}
1325
1326/// The expected tensor inventory for one artifact.
1327///
1328/// The **mechanism**, not the data: which tensors a `.fttsq` must carry, at which precision, comes
1329/// from the OQ-2 inventory crossed with the converter's per-tensor quantization policy. This module
1330/// does not hard-code that recipe, because it is per-artifact and measured.
1331#[derive(Clone, Debug, Default)]
1332pub struct ArtifactManifest {
1333    label: String,
1334    expected: Vec<ExpectedArtifactTensor>,
1335}
1336
1337impl ArtifactManifest {
1338    /// Starts a manifest with a human label used in the report header.
1339    #[must_use]
1340    pub fn new(label: impl Into<String>) -> Self {
1341        Self {
1342            label: label.into(),
1343            expected: Vec::new(),
1344        }
1345    }
1346
1347    /// Adds one expectation.
1348    #[must_use]
1349    pub fn expect(mut self, tensor: ExpectedArtifactTensor) -> Self {
1350        self.expected.push(tensor);
1351        self
1352    }
1353
1354    /// The label.
1355    #[must_use]
1356    pub fn label(&self) -> &str {
1357        &self.label
1358    }
1359
1360    /// How many tensors are expected.
1361    #[must_use]
1362    pub fn len(&self) -> usize {
1363        self.expected.len()
1364    }
1365
1366    /// Whether the manifest expects nothing.
1367    #[must_use]
1368    pub fn is_empty(&self) -> bool {
1369        self.expected.is_empty()
1370    }
1371
1372    /// Audits a reader against this manifest, reporting **every** divergence.
1373    ///
1374    /// Deliberately does not stop at the first finding: a converter bug usually produces a family
1375    /// of related divergences, and seeing all of them at once is the difference between one fix and
1376    /// twenty rounds of rerunning a multi-gigabyte conversion.
1377    #[must_use]
1378    pub fn audit(&self, reader: &FttsqReader) -> ArtifactCensus {
1379        let mut findings = Vec::new();
1380        let expected_names: BTreeMap<&str, &ExpectedArtifactTensor> = self
1381            .expected
1382            .iter()
1383            .map(|tensor| (tensor.name.as_str(), tensor))
1384            .collect();
1385
1386        for expectation in &self.expected {
1387            let Some(found) = reader.tensor(&expectation.name) else {
1388                findings.push(ArtifactFinding::Missing {
1389                    name: expectation.name.clone(),
1390                });
1391                continue;
1392            };
1393            if found.shape != expectation.shape {
1394                findings.push(ArtifactFinding::ShapeMismatch {
1395                    name: expectation.name.clone(),
1396                    expected: expectation.shape.clone(),
1397                    found: found.shape.clone(),
1398                });
1399            }
1400            if found.dtype != expectation.dtype {
1401                findings.push(ArtifactFinding::DtypeMismatch {
1402                    name: expectation.name.clone(),
1403                    expected: expectation.dtype,
1404                    found: found.dtype,
1405                });
1406            }
1407            match reader.section(&found.section) {
1408                Some(section) if section.access_class != expectation.access_class => {
1409                    findings.push(ArtifactFinding::WrongAccessClass {
1410                        name: expectation.name.clone(),
1411                        expected: expectation.access_class,
1412                        found: section.access_class,
1413                    });
1414                }
1415                Some(_) => {}
1416                None => findings.push(ArtifactFinding::DanglingSection {
1417                    name: expectation.name.clone(),
1418                    section: found.section.clone(),
1419                }),
1420            }
1421        }
1422
1423        for tensor in reader.tensors() {
1424            if !expected_names.contains_key(tensor.name.as_str()) {
1425                findings.push(ArtifactFinding::Extra {
1426                    name: tensor.name.clone(),
1427                });
1428            }
1429        }
1430
1431        ArtifactCensus {
1432            label: self.label.clone(),
1433            expected: self.expected.len(),
1434            found: reader.tensors().len(),
1435            findings,
1436        }
1437    }
1438}
1439
1440/// The itemized result of an artifact census.
1441#[derive(Clone, Debug)]
1442pub struct ArtifactCensus {
1443    label: String,
1444    expected: usize,
1445    found: usize,
1446    findings: Vec<ArtifactFinding>,
1447}
1448
1449impl ArtifactCensus {
1450    /// Whether the artifact matched its manifest exactly.
1451    #[must_use]
1452    pub fn is_green(&self) -> bool {
1453        self.findings.is_empty()
1454    }
1455
1456    /// Every divergence found.
1457    #[must_use]
1458    pub fn findings(&self) -> &[ArtifactFinding] {
1459        &self.findings
1460    }
1461
1462    /// How many findings of one class.
1463    #[must_use]
1464    pub fn count_of(&self, class: &str) -> usize {
1465        self.findings
1466            .iter()
1467            .filter(|finding| finding.class() == class)
1468            .count()
1469    }
1470
1471    /// A readable, itemized report.
1472    #[must_use]
1473    pub fn render(&self) -> String {
1474        let mut out = format!(
1475            "artifact census `{}`: expected {} tensors, artifact declares {} — {}\n",
1476            self.label,
1477            self.expected,
1478            self.found,
1479            if self.is_green() {
1480                "GREEN".to_owned()
1481            } else {
1482                format!("{} FINDINGS", self.findings.len())
1483            }
1484        );
1485        for finding in &self.findings {
1486            out.push_str(&format!("  {finding}\n"));
1487        }
1488        out
1489    }
1490}
1491
1492impl fmt::Display for ArtifactCensus {
1493    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1494        f.write_str(&self.render())
1495    }
1496}
1497
1498impl std::error::Error for ArtifactCensus {}
1499
1500fn required_str<'a>(value: Option<&'a Value>, path: &str) -> Result<&'a str, FttsqError> {
1501    value
1502        .and_then(Value::as_str)
1503        .filter(|text| !text.is_empty())
1504        .ok_or_else(|| FttsqError::Field {
1505            path: path.to_owned(),
1506            expected: "a non-empty string".to_owned(),
1507        })
1508}
1509
1510fn required_u64(value: Option<&Value>, path: &str) -> Result<u64, FttsqError> {
1511    value
1512        .and_then(Value::as_u64)
1513        .ok_or_else(|| FttsqError::Field {
1514            path: path.to_owned(),
1515            expected: "a non-negative integer".to_owned(),
1516        })
1517}
1518
1519fn parse_sections(value: Option<&Value>, file_len: u64) -> Result<Vec<SectionEntry>, FttsqError> {
1520    let array = value
1521        .and_then(Value::as_array)
1522        .ok_or_else(|| FttsqError::Field {
1523            path: "sections".to_owned(),
1524            expected: "an array".to_owned(),
1525        })?;
1526    if array.len() > MAX_SECTIONS {
1527        return Err(FttsqError::LimitExceeded {
1528            what: "section".to_owned(),
1529            found: array.len() as u64,
1530            limit: MAX_SECTIONS as u64,
1531        });
1532    }
1533
1534    let mut sections = Vec::with_capacity(array.len());
1535    let mut seen: BTreeMap<String, ()> = BTreeMap::new();
1536    for (index, entry) in array.iter().enumerate() {
1537        let path = |field: &str| format!("sections[{index}].{field}");
1538        let name = required_str(entry.get("name"), &path("name"))?.to_owned();
1539        if seen.insert(name.clone(), ()).is_some() {
1540            return Err(FttsqError::DuplicateName {
1541                what: "section".to_owned(),
1542                name,
1543            });
1544        }
1545        let class_text = required_str(entry.get("access_class"), &path("access_class"))?;
1546        let access_class =
1547            AccessClass::parse(class_text).ok_or_else(|| FttsqError::UnknownValue {
1548                path: path("access_class"),
1549                found: class_text.to_owned(),
1550            })?;
1551        let offset = required_u64(entry.get("offset"), &path("offset"))?;
1552        let length = required_u64(entry.get("length"), &path("length"))?;
1553        let sha256 = required_str(entry.get("sha256"), &path("sha256"))?.to_owned();
1554
1555        let end = offset
1556            .checked_add(length)
1557            .ok_or_else(|| FttsqError::RangeOutOfBounds {
1558                what: format!("section `{name}`"),
1559                offset,
1560                length,
1561                bound: file_len,
1562            })?;
1563        if end > file_len {
1564            return Err(FttsqError::RangeOutOfBounds {
1565                what: format!("section `{name}`"),
1566                offset,
1567                length,
1568                bound: file_len,
1569            });
1570        }
1571
1572        sections.push(SectionEntry {
1573            name,
1574            access_class,
1575            offset,
1576            length,
1577            sha256,
1578        });
1579    }
1580
1581    // Overlap is checked on a sorted copy so declaration order stays free.
1582    let mut ordered: Vec<&SectionEntry> = sections.iter().collect();
1583    ordered.sort_by_key(|section| section.offset);
1584    for pair in ordered.windows(2) {
1585        let (first, second) = (pair[0], pair[1]);
1586        let first_end = first.end().unwrap_or(u64::MAX);
1587        if first_end > second.offset {
1588            return Err(FttsqError::SectionOverlap {
1589                first: first.name.clone(),
1590                second: second.name.clone(),
1591            });
1592        }
1593    }
1594
1595    Ok(sections)
1596}
1597
1598fn parse_tensors(
1599    value: Option<&Value>,
1600    sections: &[SectionEntry],
1601    section_index: &BTreeMap<String, usize>,
1602) -> Result<Vec<TensorEntry>, FttsqError> {
1603    let array = value
1604        .and_then(Value::as_array)
1605        .ok_or_else(|| FttsqError::Field {
1606            path: "tensors".to_owned(),
1607            expected: "an array".to_owned(),
1608        })?;
1609    if array.len() > MAX_TENSORS {
1610        return Err(FttsqError::LimitExceeded {
1611            what: "tensor".to_owned(),
1612            found: array.len() as u64,
1613            limit: MAX_TENSORS as u64,
1614        });
1615    }
1616
1617    let mut tensors = Vec::with_capacity(array.len());
1618    let mut seen: BTreeMap<String, ()> = BTreeMap::new();
1619    for (index, entry) in array.iter().enumerate() {
1620        let path = |field: &str| format!("tensors[{index}].{field}");
1621        let name = required_str(entry.get("name"), &path("name"))?.to_owned();
1622        if seen.insert(name.clone(), ()).is_some() {
1623            return Err(FttsqError::DuplicateName {
1624                what: "tensor".to_owned(),
1625                name,
1626            });
1627        }
1628        let section = required_str(entry.get("section"), &path("section"))?.to_owned();
1629        let dtype_text = required_str(entry.get("dtype"), &path("dtype"))?;
1630        let dtype = StoredDtype::parse(dtype_text).ok_or_else(|| FttsqError::UnknownValue {
1631            path: path("dtype"),
1632            found: dtype_text.to_owned(),
1633        })?;
1634
1635        let shape_array = entry
1636            .get("shape")
1637            .and_then(Value::as_array)
1638            .ok_or_else(|| FttsqError::Field {
1639                path: path("shape"),
1640                expected: "an array".to_owned(),
1641            })?;
1642        if shape_array.len() > MAX_RANK {
1643            return Err(FttsqError::LimitExceeded {
1644                what: format!("tensor `{name}` rank"),
1645                found: shape_array.len() as u64,
1646                limit: MAX_RANK as u64,
1647            });
1648        }
1649        let mut shape = Vec::with_capacity(shape_array.len());
1650        for (axis, dim) in shape_array.iter().enumerate() {
1651            let dim = dim.as_u64().ok_or_else(|| FttsqError::Field {
1652                path: format!("{}[{axis}]", path("shape")),
1653                expected: "a non-negative integer".to_owned(),
1654            })?;
1655            if dim > MAX_DIM {
1656                return Err(FttsqError::LimitExceeded {
1657                    what: format!("tensor `{name}` dimension {axis}"),
1658                    found: dim,
1659                    limit: MAX_DIM,
1660                });
1661            }
1662            shape.push(dim);
1663        }
1664
1665        let offset = required_u64(entry.get("offset"), &path("offset"))?;
1666        let length = required_u64(entry.get("length"), &path("length"))?;
1667        let scales = entry
1668            .get("scales")
1669            .and_then(Value::as_str)
1670            .map(str::to_owned);
1671
1672        let tensor = TensorEntry {
1673            name,
1674            section,
1675            dtype,
1676            shape,
1677            offset,
1678            length,
1679            scales,
1680        };
1681
1682        // The declared length must equal what shape and dtype imply. Trusting the declared length
1683        // alone would let a directory hand out a window that does not match the tensor a kernel
1684        // then indexes by shape.
1685        let elements = tensor.elements().ok_or_else(|| FttsqError::LimitExceeded {
1686            what: format!("tensor `{}` element count", tensor.name),
1687            found: u64::MAX,
1688            limit: MAX_DIM,
1689        })?;
1690        let implied = dtype
1691            .storage_bytes(elements)
1692            .ok_or_else(|| FttsqError::LimitExceeded {
1693                what: format!("tensor `{}` storage size", tensor.name),
1694                found: u64::MAX,
1695                limit: MAX_DIM,
1696            })?;
1697        if implied != tensor.length {
1698            return Err(FttsqError::LengthMismatch {
1699                tensor: tensor.name.clone(),
1700                declared: tensor.length,
1701                implied,
1702            });
1703        }
1704
1705        let owner = section_index
1706            .get(&tensor.section)
1707            .and_then(|&index| sections.get(index))
1708            .ok_or_else(|| FttsqError::UnknownSection {
1709                tensor: tensor.name.clone(),
1710                section: tensor.section.clone(),
1711            })?;
1712        let end = tensor.offset.checked_add(tensor.length).ok_or_else(|| {
1713            FttsqError::RangeOutOfBounds {
1714                what: format!("tensor `{}`", tensor.name),
1715                offset: tensor.offset,
1716                length: tensor.length,
1717                bound: owner.length,
1718            }
1719        })?;
1720        if end > owner.length {
1721            return Err(FttsqError::RangeOutOfBounds {
1722                what: format!("tensor `{}`", tensor.name),
1723                offset: tensor.offset,
1724                length: tensor.length,
1725                bound: owner.length,
1726            });
1727        }
1728
1729        tensors.push(tensor);
1730    }
1731
1732    // Overlap within each section. Two tensors sharing bytes means one of them is wrong, and which
1733    // one is not knowable at read time — so both are refused.
1734    let mut by_section: BTreeMap<&str, Vec<&TensorEntry>> = BTreeMap::new();
1735    for tensor in &tensors {
1736        by_section
1737            .entry(tensor.section.as_str())
1738            .or_default()
1739            .push(tensor);
1740    }
1741    for group in by_section.values_mut() {
1742        group.sort_by_key(|tensor| tensor.offset);
1743        for pair in group.windows(2) {
1744            let (first, second) = (pair[0], pair[1]);
1745            let first_end = first.offset.saturating_add(first.length);
1746            if first_end > second.offset {
1747                return Err(FttsqError::TensorOverlap {
1748                    first: first.name.clone(),
1749                    second: second.name.clone(),
1750                });
1751            }
1752        }
1753    }
1754
1755    Ok(tensors)
1756}
1757
1758/// Metadata and fixed section lengths for a bounded `.fttsq` conversion.
1759///
1760/// A converter learns every tensor's shape, storage policy, section, and final byte length from
1761/// the validated input manifest before it starts payload conversion. That lets this plan reserve
1762/// the directory first, then stream one section at a time into a caller-owned seekable temporary
1763/// file. The writer never retains a payload section, and does not create, rename, or remove files:
1764/// the caller owns the atomic-temp-file policy around it.
1765#[derive(Debug)]
1766pub struct FttsqStreamPlan {
1767    model_family: String,
1768    source_sha256: String,
1769    license_notice: String,
1770    model_config: Value,
1771    quantization_manifest: Value,
1772    sections: Vec<(String, AccessClass, u64)>,
1773    tensors: Vec<TensorEntry>,
1774}
1775
1776impl FttsqStreamPlan {
1777    /// Starts a bounded conversion plan for one model family and source checkpoint.
1778    #[must_use]
1779    pub fn new(model_family: impl Into<String>, source_sha256: impl Into<String>) -> Self {
1780        Self {
1781            model_family: model_family.into(),
1782            source_sha256: source_sha256.into(),
1783            license_notice: String::new(),
1784            model_config: Value::Null,
1785            quantization_manifest: Value::Null,
1786            sections: Vec::new(),
1787            tensors: Vec::new(),
1788        }
1789    }
1790
1791    /// Sets the required Apache-2.0 §4 attribution notice.
1792    #[must_use]
1793    pub fn license_notice(mut self, notice: impl Into<String>) -> Self {
1794        self.license_notice = notice.into();
1795        self
1796    }
1797
1798    /// Attaches the frozen upstream model configuration.
1799    #[must_use]
1800    pub fn model_config(mut self, config: Value) -> Self {
1801        self.model_config = config;
1802        self
1803    }
1804
1805    /// Attaches the policy used to quantize each tensor.
1806    #[must_use]
1807    pub fn quantization_manifest(mut self, manifest: Value) -> Self {
1808        self.quantization_manifest = manifest;
1809        self
1810    }
1811
1812    /// Declares a section's final byte length before its bytes are streamed.
1813    #[must_use]
1814    pub fn section(
1815        mut self,
1816        name: impl Into<String>,
1817        access_class: AccessClass,
1818        length: u64,
1819    ) -> Self {
1820        self.sections.push((name.into(), access_class, length));
1821        self
1822    }
1823
1824    /// Declares a tensor located in one of the planned sections.
1825    #[must_use]
1826    pub fn tensor(mut self, tensor: TensorEntry) -> Self {
1827        self.tensors.push(tensor);
1828        self
1829    }
1830
1831    /// Writes the header and reserved directory into a caller-owned seekable stream.
1832    ///
1833    /// Payload sections must subsequently be supplied in declaration order through
1834    /// [`FttsqStreamingWriter::write_section`]. A caller that needs an atomic artifact should
1835    /// provide its own same-filesystem temporary file, call [`FttsqStreamingWriter::finish`],
1836    /// sync it, and rename it only after this method has finalized the directory.
1837    ///
1838    /// # Errors
1839    ///
1840    /// Refuses malformed planned metadata before writing any bytes, and names I/O failures from
1841    /// the caller's stream without assuming a path or filesystem policy.
1842    pub fn begin<W: std::io::Write + std::io::Seek>(
1843        self,
1844        mut writer: W,
1845    ) -> Result<FttsqStreamingWriter<W>, FttsqError> {
1846        if self.license_notice.trim().is_empty() {
1847            return Err(FttsqError::LicenseNoticeMissing);
1848        }
1849
1850        // The final SHA-256 strings are unknown until each section has streamed, but their exact
1851        // wire width is known. Filling that width now keeps the reserved directory large enough
1852        // for the finalized digests without retaining a single payload byte.
1853        let mut sections: Vec<SectionEntry> = self
1854            .sections
1855            .into_iter()
1856            .map(|(name, access_class, length)| SectionEntry {
1857                name,
1858                access_class,
1859                offset: 0,
1860                length,
1861                sha256: "0".repeat(64),
1862            })
1863            .collect();
1864        let mut probe_sections = sections.clone();
1865        for section in &mut probe_sections {
1866            // Twenty decimal digits are the widest valid offset. No section layout is required
1867            // for this pass, avoiding arithmetic at a fake near-`u64::MAX` payload start.
1868            section.offset = u64::MAX;
1869        }
1870        let probe = stream_directory_json(
1871            &self.model_family,
1872            &self.source_sha256,
1873            &self.license_notice,
1874            &self.model_config,
1875            &self.quantization_manifest,
1876            &probe_sections,
1877            &self.tensors,
1878        );
1879        let directory_len = serde_json::to_vec(&probe)
1880            .map_err(|error| FttsqError::DirectoryMalformed {
1881                detail: error.to_string(),
1882            })?
1883            .len() as u64;
1884        if directory_len > MAX_DIRECTORY_BYTES {
1885            return Err(FttsqError::DirectoryLength {
1886                declared: directory_len,
1887                limit: MAX_DIRECTORY_BYTES,
1888            });
1889        }
1890        let payload_start =
1891            HEADER_PREFIX_BYTES
1892                .checked_add(directory_len)
1893                .ok_or(FttsqError::DirectoryLength {
1894                    declared: directory_len,
1895                    limit: u64::MAX,
1896                })?;
1897        let final_file_len = layout_stream_sections(&mut sections, payload_start)?;
1898
1899        let directory = stream_directory_json(
1900            &self.model_family,
1901            &self.source_sha256,
1902            &self.license_notice,
1903            &self.model_config,
1904            &self.quantization_manifest,
1905            &sections,
1906            &self.tensors,
1907        );
1908        let mut directory_bytes =
1909            serde_json::to_vec(&directory).map_err(|error| FttsqError::DirectoryMalformed {
1910                detail: error.to_string(),
1911            })?;
1912        if directory_bytes.len() as u64 > directory_len {
1913            return Err(FttsqError::DirectoryLength {
1914                declared: directory_bytes.len() as u64,
1915                limit: directory_len,
1916            });
1917        }
1918        directory_bytes.resize(directory_len as usize, b' ');
1919
1920        let mut header_and_directory = Vec::with_capacity(
1921            (HEADER_PREFIX_BYTES as usize).saturating_add(directory_bytes.len()),
1922        );
1923        header_and_directory.extend_from_slice(MAGIC);
1924        header_and_directory.extend_from_slice(&FORMAT_VERSION.to_le_bytes());
1925        header_and_directory.extend_from_slice(&directory_len.to_le_bytes());
1926        header_and_directory.extend_from_slice(&directory_bytes);
1927
1928        // Validate the exact finalized offsets and tensor spans while their metadata is still
1929        // cheap. Digest verification deliberately waits for all streamed payload bytes.
1930        FttsqReader::parse_directory_for_file_len(&header_and_directory, final_file_len)?;
1931        writer
1932            .write_all(&header_and_directory)
1933            .map_err(|error| stream_io_error("write header and directory", &error))?;
1934
1935        let mut streaming = FttsqStreamingWriter {
1936            writer,
1937            model_family: self.model_family,
1938            source_sha256: self.source_sha256,
1939            license_notice: self.license_notice,
1940            model_config: self.model_config,
1941            quantization_manifest: self.quantization_manifest,
1942            sections,
1943            tensors: self.tensors,
1944            directory_len,
1945            current_section: 0,
1946            section_written: 0,
1947            section_hasher: Sha256::new(),
1948        };
1949        streaming.finalize_empty_sections();
1950        Ok(streaming)
1951    }
1952}
1953
1954/// A bounded writer for `.fttsq` section payloads.
1955///
1956/// The stream owns metadata plus one incremental SHA-256 state; it never owns a section payload.
1957/// This is intentionally lower-level than [`FttsqWriter`]: callers retain responsibility for the
1958/// surrounding atomic temporary-file creation, sync, and rename, while this type finalizes the
1959/// directory only after every declared byte and digest is complete.
1960#[derive(Debug)]
1961pub struct FttsqStreamingWriter<W> {
1962    writer: W,
1963    model_family: String,
1964    source_sha256: String,
1965    license_notice: String,
1966    model_config: Value,
1967    quantization_manifest: Value,
1968    sections: Vec<SectionEntry>,
1969    tensors: Vec<TensorEntry>,
1970    directory_len: u64,
1971    current_section: usize,
1972    section_written: u64,
1973    section_hasher: Sha256,
1974}
1975
1976impl<W: std::io::Write + std::io::Seek> FttsqStreamingWriter<W> {
1977    /// Appends bytes to the next declared section.
1978    ///
1979    /// Calls for a later section are refused rather than buffered. That ordering makes the
1980    /// converter's one-tensor-at-a-time memory bound mechanical: once a section is complete, its
1981    /// source mapping and tile buffers can be released before conversion continues.
1982    ///
1983    /// # Errors
1984    ///
1985    /// Returns a named refusal for out-of-order or overlong sections, or [`FttsqError::Io`] when
1986    /// the caller-owned stream cannot accept the bytes.
1987    pub fn write_section(&mut self, section: &str, bytes: &[u8]) -> Result<(), FttsqError> {
1988        let Some(entry) = self.sections.get(self.current_section) else {
1989            return Err(FttsqError::SectionWriteOutOfOrder {
1990                expected: None,
1991                actual: section.to_owned(),
1992            });
1993        };
1994        let expected = entry.name.clone();
1995        let declared = entry.length;
1996        if expected != section {
1997            return Err(FttsqError::SectionWriteOutOfOrder {
1998                expected: Some(expected),
1999                actual: section.to_owned(),
2000            });
2001        }
2002        let bytes_len = bytes.len() as u64;
2003        let attempted = self.section_written.checked_add(bytes_len).ok_or_else(|| {
2004            FttsqError::SectionLengthExceeded {
2005                section: expected.clone(),
2006                declared,
2007                attempted: u64::MAX,
2008            }
2009        })?;
2010        if attempted > declared {
2011            return Err(FttsqError::SectionLengthExceeded {
2012                section: expected,
2013                declared,
2014                attempted,
2015            });
2016        }
2017
2018        self.writer
2019            .write_all(bytes)
2020            .map_err(|error| stream_io_error("write section", &error))?;
2021        self.section_hasher.update(bytes);
2022        self.section_written = attempted;
2023        self.finalize_empty_sections();
2024        Ok(())
2025    }
2026
2027    /// Finalizes all completed section digests and rewrites the reserved directory in place.
2028    ///
2029    /// The returned stream is positioned at its end and flushed, ready for a file-owning caller to
2030    /// perform its durability and atomic-rename steps. A successful return guarantees that a
2031    /// complete byte buffer collected from the stream passes [`FttsqReader::open`].
2032    ///
2033    /// # Errors
2034    ///
2035    /// Refuses an incomplete declared section and names failures while seeking, finalizing, or
2036    /// flushing the caller-owned stream.
2037    pub fn finish(mut self) -> Result<W, FttsqError> {
2038        if let Some(section) = self.sections.get(self.current_section) {
2039            return Err(FttsqError::SectionIncomplete {
2040                section: section.name.clone(),
2041                declared: section.length,
2042                written: self.section_written,
2043            });
2044        }
2045
2046        let directory = stream_directory_json(
2047            &self.model_family,
2048            &self.source_sha256,
2049            &self.license_notice,
2050            &self.model_config,
2051            &self.quantization_manifest,
2052            &self.sections,
2053            &self.tensors,
2054        );
2055        let directory_bytes =
2056            serde_json::to_vec(&directory).map_err(|error| FttsqError::DirectoryMalformed {
2057                detail: error.to_string(),
2058            })?;
2059        if directory_bytes.len() as u64 > self.directory_len {
2060            return Err(FttsqError::DirectoryLength {
2061                declared: directory_bytes.len() as u64,
2062                limit: self.directory_len,
2063            });
2064        }
2065
2066        self.writer
2067            .seek(std::io::SeekFrom::Start(HEADER_PREFIX_BYTES))
2068            .map_err(|error| stream_io_error("seek to directory", &error))?;
2069        self.writer
2070            .write_all(&directory_bytes)
2071            .map_err(|error| stream_io_error("finalize directory", &error))?;
2072        write_space_padding(
2073            &mut self.writer,
2074            self.directory_len - directory_bytes.len() as u64,
2075        )?;
2076        self.writer
2077            .seek(std::io::SeekFrom::End(0))
2078            .map_err(|error| stream_io_error("seek to artifact end", &error))?;
2079        self.writer
2080            .flush()
2081            .map_err(|error| stream_io_error("flush finalized artifact", &error))?;
2082        Ok(self.writer)
2083    }
2084
2085    fn finalize_empty_sections(&mut self) {
2086        while let Some(section) = self.sections.get_mut(self.current_section) {
2087            if self.section_written != section.length {
2088                break;
2089            }
2090            section.sha256 = to_hex(&std::mem::take(&mut self.section_hasher).finish());
2091            self.current_section += 1;
2092            self.section_written = 0;
2093        }
2094    }
2095}
2096
2097fn layout_stream_sections(
2098    sections: &mut [SectionEntry],
2099    payload_start: u64,
2100) -> Result<u64, FttsqError> {
2101    let mut cursor = payload_start;
2102    for section in sections {
2103        section.offset = cursor;
2104        cursor =
2105            cursor
2106                .checked_add(section.length)
2107                .ok_or_else(|| FttsqError::RangeOutOfBounds {
2108                    what: format!("section `{}`", section.name),
2109                    offset: section.offset,
2110                    length: section.length,
2111                    bound: u64::MAX,
2112                })?;
2113    }
2114    Ok(cursor)
2115}
2116
2117fn stream_directory_json(
2118    model_family: &str,
2119    source_sha256: &str,
2120    license_notice: &str,
2121    model_config: &Value,
2122    quantization_manifest: &Value,
2123    sections: &[SectionEntry],
2124    tensors: &[TensorEntry],
2125) -> Value {
2126    let sections: Vec<Value> = sections
2127        .iter()
2128        .map(|section| {
2129            json!({
2130                "name": section.name,
2131                "access_class": section.access_class.as_str(),
2132                "offset": section.offset,
2133                "length": section.length,
2134                "sha256": section.sha256,
2135            })
2136        })
2137        .collect();
2138    let tensors: Vec<Value> = tensors
2139        .iter()
2140        .map(|tensor| {
2141            json!({
2142                "name": tensor.name,
2143                "section": tensor.section,
2144                "dtype": tensor.dtype.as_str(),
2145                "shape": tensor.shape,
2146                "offset": tensor.offset,
2147                "length": tensor.length,
2148                "scales": tensor.scales,
2149            })
2150        })
2151        .collect();
2152    json!({
2153        "format_version": FORMAT_VERSION,
2154        "model_family": model_family,
2155        "source_sha256": source_sha256,
2156        "license_notice": license_notice,
2157        "model_config": model_config,
2158        "quantization_manifest": quantization_manifest,
2159        "sections": sections,
2160        "tensors": tensors,
2161    })
2162}
2163
2164fn stream_io_error(operation: &str, error: &std::io::Error) -> FttsqError {
2165    FttsqError::Io {
2166        operation: operation.to_owned(),
2167        path: "<fttsq stream>".to_owned(),
2168        detail: error.to_string(),
2169    }
2170}
2171
2172fn write_space_padding<W: std::io::Write>(
2173    writer: &mut W,
2174    mut remaining: u64,
2175) -> Result<(), FttsqError> {
2176    const SPACES: [u8; 4096] = [b' '; 4096];
2177    while remaining > 0 {
2178        let count = remaining.min(SPACES.len() as u64) as usize;
2179        writer
2180            .write_all(&SPACES[..count])
2181            .map_err(|error| stream_io_error("pad finalized directory", &error))?;
2182        remaining -= count as u64;
2183    }
2184    Ok(())
2185}
2186
2187/// Builds a `.fttsq` artifact.
2188///
2189/// Sections are appended in the order given; the writer computes each digest and lays out absolute
2190/// offsets, so a caller cannot produce an artifact whose directory disagrees with its payload.
2191#[derive(Debug, Default)]
2192pub struct FttsqWriter {
2193    model_family: String,
2194    source_sha256: String,
2195    license_notice: String,
2196    model_config: Value,
2197    quantization_manifest: Value,
2198    sections: Vec<(SectionEntry, Vec<u8>)>,
2199    tensors: Vec<TensorEntry>,
2200}
2201
2202impl FttsqWriter {
2203    /// Starts an artifact for one model family, converted from a checkpoint with `source_sha256`.
2204    #[must_use]
2205    pub fn new(model_family: impl Into<String>, source_sha256: impl Into<String>) -> Self {
2206        Self {
2207            model_family: model_family.into(),
2208            source_sha256: source_sha256.into(),
2209            license_notice: String::new(),
2210            model_config: Value::Null,
2211            quantization_manifest: Value::Null,
2212            sections: Vec::new(),
2213            tensors: Vec::new(),
2214        }
2215    }
2216
2217    /// Sets the Apache-2.0 §4 attribution notice. Required — [`FttsqWriter::finish`] refuses without it.
2218    #[must_use]
2219    pub fn license_notice(mut self, notice: impl Into<String>) -> Self {
2220        self.license_notice = notice.into();
2221        self
2222    }
2223
2224    /// Attaches the frozen upstream model config.
2225    #[must_use]
2226    pub fn model_config(mut self, config: Value) -> Self {
2227        self.model_config = config;
2228        self
2229    }
2230
2231    /// Attaches the per-tensor quantization policy the license notice refers to.
2232    #[must_use]
2233    pub fn quantization_manifest(mut self, manifest: Value) -> Self {
2234        self.quantization_manifest = manifest;
2235        self
2236    }
2237
2238    /// Appends a section with its payload. Offset and digest are computed at [`FttsqWriter::finish`].
2239    #[must_use]
2240    pub fn section(
2241        mut self,
2242        name: impl Into<String>,
2243        access_class: AccessClass,
2244        payload: Vec<u8>,
2245    ) -> Self {
2246        let entry = SectionEntry {
2247            name: name.into(),
2248            access_class,
2249            offset: 0,
2250            length: payload.len() as u64,
2251            sha256: String::new(),
2252        };
2253        self.sections.push((entry, payload));
2254        self
2255    }
2256
2257    /// Declares a tensor located inside an already-added section.
2258    #[must_use]
2259    pub fn tensor(mut self, tensor: TensorEntry) -> Self {
2260        self.tensors.push(tensor);
2261        self
2262    }
2263
2264    /// Serializes the artifact.
2265    ///
2266    /// The result is re-parsed before being returned, so a writer bug surfaces here rather than as
2267    /// an unreadable multi-gigabyte file discovered hours later.
2268    ///
2269    /// # Errors
2270    ///
2271    /// Returns [`FttsqError::LicenseNoticeMissing`] without a notice, or whatever the validating
2272    /// re-parse rejects.
2273    pub fn finish(mut self) -> Result<Vec<u8>, FttsqError> {
2274        if self.license_notice.trim().is_empty() {
2275            return Err(FttsqError::LicenseNoticeMissing);
2276        }
2277
2278        for (entry, payload) in &mut self.sections {
2279            entry.length = payload.len() as u64;
2280            entry.sha256 = hex_digest(payload);
2281        }
2282
2283        // Two passes: the directory's size depends on the offsets, and the offsets depend on the
2284        // directory's size. Serialize once with placeholder offsets to learn the exact directory
2285        // length, then again with the real ones. The placeholder pass uses u64::MAX-width numbers
2286        // so the second directory can only be the same size or smaller — and we pad to match.
2287        let probe = self.directory_json(u64::MAX);
2288        let probe_len = serde_json::to_vec(&probe)
2289            .map_err(|error| FttsqError::DirectoryMalformed {
2290                detail: error.to_string(),
2291            })?
2292            .len() as u64;
2293
2294        let payload_start = HEADER_PREFIX_BYTES + probe_len;
2295        let directory = self.directory_json(payload_start);
2296        let mut directory_bytes =
2297            serde_json::to_vec(&directory).map_err(|error| FttsqError::DirectoryMalformed {
2298                detail: error.to_string(),
2299            })?;
2300        // Pad with trailing spaces so the directory occupies exactly `probe_len` bytes and the
2301        // offsets computed above stay correct. JSON tolerates trailing whitespace.
2302        while (directory_bytes.len() as u64) < probe_len {
2303            directory_bytes.push(b' ');
2304        }
2305
2306        let mut out = Vec::with_capacity(payload_start as usize);
2307        out.extend_from_slice(MAGIC);
2308        out.extend_from_slice(&FORMAT_VERSION.to_le_bytes());
2309        out.extend_from_slice(&(directory_bytes.len() as u64).to_le_bytes());
2310        out.extend_from_slice(&directory_bytes);
2311        for (_, payload) in &self.sections {
2312            out.extend_from_slice(payload);
2313        }
2314
2315        // Prove what we just wrote is readable, digests and all.
2316        FttsqReader::open(&out)?;
2317        Ok(out)
2318    }
2319
2320    /// Serializes and writes the artifact to `path` **atomically**.
2321    ///
2322    /// Writes to a temporary file beside the destination, fsyncs it, then renames over the target.
2323    /// A reader therefore observes either the previous artifact or the complete new one, never a
2324    /// half-written prefix — which matters because a truncated `.fttsq` is exactly the shape that
2325    /// digest verification would report as *corruption* rather than as an interrupted write.
2326    ///
2327    /// The temporary lives in the destination's own directory so the rename stays within one
2328    /// filesystem; `rename` across mount points is not atomic and would silently degrade to a copy.
2329    /// On failure the temporary is removed.
2330    ///
2331    /// # Errors
2332    ///
2333    /// Returns [`FttsqError::Io`] naming the operation and path, or whatever
2334    /// [`FttsqWriter::finish`] rejects.
2335    pub fn write_to_path(self, path: &std::path::Path) -> Result<(), FttsqError> {
2336        use std::io::Write as _;
2337
2338        let bytes = self.finish()?;
2339
2340        let parent = path.parent().unwrap_or_else(|| std::path::Path::new("."));
2341        // Unique per process so two concurrent converters cannot collide on the temporary.
2342        let file_name = path.file_name().map_or_else(
2343            || std::ffi::OsString::from("artifact.fttsq"),
2344            std::ffi::OsStr::to_os_string,
2345        );
2346        let mut temp_name = file_name;
2347        temp_name.push(format!(".tmp.{}", std::process::id()));
2348        let temp_path = parent.join(temp_name);
2349
2350        let io =
2351            |operation: &str, target: &std::path::Path, error: &std::io::Error| FttsqError::Io {
2352                operation: operation.to_owned(),
2353                path: target.display().to_string(),
2354                detail: error.to_string(),
2355            };
2356
2357        // Any failure past this point must not leave the temporary behind.
2358        let result = (|| -> Result<(), FttsqError> {
2359            let mut file = std::fs::File::create(&temp_path)
2360                .map_err(|error| io("create", &temp_path, &error))?;
2361            file.write_all(&bytes)
2362                .map_err(|error| io("write", &temp_path, &error))?;
2363            // fsync before rename: without it the rename can land while the data is still in the
2364            // page cache, so a crash leaves a correctly-named file full of zeros.
2365            file.sync_all()
2366                .map_err(|error| io("fsync", &temp_path, &error))?;
2367            drop(file);
2368            std::fs::rename(&temp_path, path).map_err(|error| io("rename", path, &error))
2369        })();
2370
2371        if result.is_err() {
2372            let _ = std::fs::remove_file(&temp_path);
2373        }
2374        result
2375    }
2376
2377    fn directory_json(&self, payload_start: u64) -> Value {
2378        let mut cursor = payload_start;
2379        let sections: Vec<Value> = self
2380            .sections
2381            .iter()
2382            .map(|(entry, _)| {
2383                let offset = cursor;
2384                // The probe pass begins at `u64::MAX` to reserve the widest possible decimal
2385                // offset. Saturation keeps that metadata-only calculation defined even for an
2386                // adversarially large in-memory construction; real artifact offsets below are
2387                // still computed from the actual payload start.
2388                cursor = cursor.saturating_add(entry.length);
2389                json!({
2390                    "name": entry.name,
2391                    "access_class": entry.access_class.as_str(),
2392                    "offset": offset,
2393                    "length": entry.length,
2394                    "sha256": entry.sha256,
2395                })
2396            })
2397            .collect();
2398
2399        let tensors: Vec<Value> = self
2400            .tensors
2401            .iter()
2402            .map(|tensor| {
2403                json!({
2404                    "name": tensor.name,
2405                    "section": tensor.section,
2406                    "dtype": tensor.dtype.as_str(),
2407                    "shape": tensor.shape,
2408                    "offset": tensor.offset,
2409                    "length": tensor.length,
2410                    "scales": tensor.scales,
2411                })
2412            })
2413            .collect();
2414
2415        json!({
2416            "format_version": FORMAT_VERSION,
2417            "model_family": self.model_family,
2418            "source_sha256": self.source_sha256,
2419            "license_notice": self.license_notice,
2420            "model_config": self.model_config,
2421            "quantization_manifest": self.quantization_manifest,
2422            "sections": sections,
2423            "tensors": tensors,
2424        })
2425    }
2426}
2427
2428#[cfg(test)]
2429mod tests {
2430    use super::*;
2431    use std::io::Cursor;
2432
2433    /// The §3 notice from `docs/LICENSE_AND_ATTRIBUTION.md`, abbreviated for tests.
2434    const NOTICE: &str = "Copyright 2026 Alibaba Cloud\nApache-2.0\nCHANGES: requantized to .fttsq";
2435
2436    fn artifact() -> Vec<u8> {
2437        FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "a".repeat(64))
2438            .license_notice(NOTICE)
2439            .model_config(json!({ "hidden_size": 1024 }))
2440            .quantization_manifest(json!({ "talker": "q8" }))
2441            .section(
2442                "microdecoder",
2443                AccessClass::HotRecurrentMicrodecoder,
2444                vec![7_u8; 64],
2445            )
2446            .section(
2447                "text_embedding",
2448                AccessClass::ColdTextEmbedding,
2449                vec![9_u8; 32],
2450            )
2451            .tensor(TensorEntry {
2452                name: "microdecoder.body".to_owned(),
2453                section: "microdecoder".to_owned(),
2454                dtype: StoredDtype::Q8,
2455                shape: vec![8, 8],
2456                offset: 0,
2457                length: 64,
2458                scales: Some("microdecoder.body.scales".to_owned()),
2459            })
2460            .tensor(TensorEntry {
2461                name: "text_embedding.weight".to_owned(),
2462                section: "text_embedding".to_owned(),
2463                dtype: StoredDtype::Bf16,
2464                shape: vec![4, 4],
2465                offset: 0,
2466                length: 32,
2467                scales: None,
2468            })
2469            .finish()
2470            .expect("the fixture artifact is writable")
2471    }
2472
2473    fn stream_plan() -> FttsqStreamPlan {
2474        FttsqStreamPlan::new("qwen3-tts-12hz-0.6b-base", "a".repeat(64))
2475            .license_notice(NOTICE)
2476            .model_config(json!({ "hidden_size": 1024 }))
2477            .quantization_manifest(json!({ "talker": "q8" }))
2478            .section("microdecoder", AccessClass::HotRecurrentMicrodecoder, 64)
2479            .section("text_embedding", AccessClass::ColdTextEmbedding, 32)
2480            .tensor(TensorEntry {
2481                name: "microdecoder.body".to_owned(),
2482                section: "microdecoder".to_owned(),
2483                dtype: StoredDtype::Q8,
2484                shape: vec![8, 8],
2485                offset: 0,
2486                length: 64,
2487                scales: Some("microdecoder.body.scales".to_owned()),
2488            })
2489            .tensor(TensorEntry {
2490                name: "text_embedding.weight".to_owned(),
2491                section: "text_embedding".to_owned(),
2492                dtype: StoredDtype::Bf16,
2493                shape: vec![4, 4],
2494                offset: 0,
2495                length: 32,
2496                scales: None,
2497            })
2498    }
2499
2500    fn streamed_artifact() -> Vec<u8> {
2501        let mut writer = stream_plan()
2502            .begin(Cursor::new(Vec::new()))
2503            .expect("the stream plan is structurally valid");
2504        writer
2505            .write_section("microdecoder", &[7_u8; 64])
2506            .expect("first section streams");
2507        writer
2508            .write_section("text_embedding", &[9_u8; 32])
2509            .expect("second section streams");
2510        writer
2511            .finish()
2512            .expect("complete stream finalizes")
2513            .into_inner()
2514    }
2515
2516    #[test]
2517    fn streaming_writer_is_canonical_and_never_retains_section_payloads() {
2518        // The buffered writer is only a small-fixture oracle here. The stream receives two
2519        // independent borrowed sections and must produce identical canonical bytes, including
2520        // directory offsets and digests, without taking ownership of either payload.
2521        let bytes = streamed_artifact();
2522        assert_eq!(bytes, artifact());
2523        let reader = FttsqReader::open(&bytes).expect("finalized stream verifies");
2524        assert_eq!(
2525            reader
2526                .tensor_bytes("microdecoder.body", &bytes)
2527                .expect("streamed tensor resolves"),
2528            &[7_u8; 64]
2529        );
2530    }
2531
2532    #[test]
2533    fn streaming_writer_refuses_out_of_order_or_incomplete_sections() {
2534        let mut writer = stream_plan()
2535            .begin(Cursor::new(Vec::new()))
2536            .expect("the stream plan is structurally valid");
2537        assert_eq!(
2538            writer
2539                .write_section("text_embedding", &[9_u8; 32])
2540                .expect_err("later sections cannot be buffered"),
2541            FttsqError::SectionWriteOutOfOrder {
2542                expected: Some("microdecoder".to_owned()),
2543                actual: "text_embedding".to_owned(),
2544            }
2545        );
2546        writer
2547            .write_section("microdecoder", &[7_u8; 63])
2548            .expect("a bounded partial chunk is accepted");
2549        assert_eq!(
2550            writer
2551                .finish()
2552                .expect_err("a partial section cannot acquire a digest"),
2553            FttsqError::SectionIncomplete {
2554                section: "microdecoder".to_owned(),
2555                declared: 64,
2556                written: 63,
2557            }
2558        );
2559    }
2560
2561    #[test]
2562    fn round_trips_through_write_and_read() {
2563        let bytes = artifact();
2564        let reader =
2565            FttsqReader::open(&bytes).expect("the artifact we just wrote must be readable");
2566
2567        assert_eq!(reader.format_version(), FORMAT_VERSION);
2568        assert_eq!(reader.model_family(), "qwen3-tts-12hz-0.6b-base");
2569        assert!(reader.license_notice().contains("Alibaba Cloud"));
2570        assert_eq!(reader.model_config()["hidden_size"], 1024);
2571        assert_eq!(reader.sections().len(), 2);
2572        assert_eq!(reader.tensors().len(), 2);
2573
2574        // Tensor payloads must come back byte-identical, through the section indirection.
2575        assert_eq!(
2576            reader
2577                .tensor_bytes("microdecoder.body", &bytes)
2578                .expect("tensor resolves"),
2579            &vec![7_u8; 64][..]
2580        );
2581        assert_eq!(
2582            reader
2583                .tensor_bytes("text_embedding.weight", &bytes)
2584                .expect("tensor resolves"),
2585            &vec![9_u8; 32][..]
2586        );
2587    }
2588
2589    #[test]
2590    fn bf16_payload_is_byte_identical_across_the_round_trip() {
2591        // Verbatim BF16 carriage is the property the converter's parity argument rests on: if the
2592        // container perturbs a single byte, every downstream parity claim is about the wrong bytes.
2593        let payload: Vec<u8> = (0..=255_u8).cycle().take(4096).collect();
2594        let bytes = FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "b".repeat(64))
2595            .license_notice(NOTICE)
2596            .section("talker", AccessClass::HotRecurrentTalker, payload.clone())
2597            .tensor(TensorEntry {
2598                name: "talker.weight".to_owned(),
2599                section: "talker".to_owned(),
2600                dtype: StoredDtype::Bf16,
2601                shape: vec![64, 32],
2602                offset: 0,
2603                length: 4096,
2604                scales: None,
2605            })
2606            .finish()
2607            .expect("writable");
2608        let reader = FttsqReader::open(&bytes).expect("readable");
2609        assert_eq!(
2610            reader
2611                .tensor_bytes("talker.weight", &bytes)
2612                .expect("resolves"),
2613            &payload[..]
2614        );
2615    }
2616
2617    #[test]
2618    fn access_classes_drive_the_page_in_policy() {
2619        let bytes = artifact();
2620        let reader = FttsqReader::open(&bytes).expect("readable");
2621
2622        let hot = reader.sections_in_class(AccessClass::HotRecurrentMicrodecoder);
2623        assert_eq!(hot.len(), 1);
2624        assert!(hot[0].access_class.is_hot());
2625        assert!(!hot[0].access_class.is_row_granular());
2626
2627        let cold = reader.sections_in_class(AccessClass::ColdTextEmbedding);
2628        assert_eq!(cold.len(), 1);
2629        assert!(
2630            !cold[0].access_class.is_hot(),
2631            "the 622 MB embedding must never be advised resident"
2632        );
2633        assert!(
2634            cold[0].access_class.is_row_granular(),
2635            "the cold embedding is accessed a row at a time, never as a unit"
2636        );
2637    }
2638
2639    #[test]
2640    fn a_newer_format_version_is_refused_rather_than_guessed_at() {
2641        let mut bytes = artifact();
2642        bytes[8..12].copy_from_slice(&(FORMAT_VERSION + 1).to_le_bytes());
2643        let error = FttsqReader::parse_directory(&bytes).expect_err("must refuse");
2644        assert_eq!(
2645            error,
2646            FttsqError::UnsupportedVersion {
2647                found: FORMAT_VERSION + 1,
2648                supported: FORMAT_VERSION,
2649            }
2650        );
2651    }
2652
2653    #[test]
2654    fn bad_magic_and_truncation_are_named_refusals() {
2655        assert!(matches!(
2656            FttsqReader::parse_directory(&[]),
2657            Err(FttsqError::TooShort { .. })
2658        ));
2659        let mut bytes = artifact();
2660        bytes[0] = b'X';
2661        assert!(matches!(
2662            FttsqReader::parse_directory(&bytes),
2663            Err(FttsqError::BadMagic { .. })
2664        ));
2665    }
2666
2667    #[test]
2668    fn a_truncated_file_never_yields_a_partial_load() {
2669        let full = artifact();
2670        // Cut into the payload: the directory still parses, but a section runs past the end.
2671        for cut in [full.len() - 1, full.len() - 40, full.len() - 90] {
2672            let error = FttsqReader::open(&full[..cut]).expect_err("truncation must be refused");
2673            assert!(
2674                matches!(
2675                    error,
2676                    FttsqError::RangeOutOfBounds { .. } | FttsqError::DirectoryLength { .. }
2677                ),
2678                "unexpected error for cut at {cut}: {error}"
2679            );
2680        }
2681    }
2682
2683    #[test]
2684    fn a_single_flipped_payload_bit_fails_digest_verification() {
2685        let mut bytes = artifact();
2686        let last = bytes.len() - 1;
2687        bytes[last] ^= 0x01;
2688        let error = FttsqReader::open(&bytes).expect_err("a bit flip must be caught");
2689        assert!(
2690            matches!(
2691                &error,
2692                FttsqError::DigestMismatch { section, .. } if section == "text_embedding"
2693            ),
2694            "expected a digest mismatch for text_embedding, got {error}"
2695        );
2696        // Structure alone still parses — which is exactly why the digest gate has to exist.
2697        assert!(FttsqReader::parse_directory(&bytes).is_ok());
2698    }
2699
2700    #[test]
2701    fn a_hostile_directory_length_cannot_provoke_a_huge_read() {
2702        let mut bytes = artifact();
2703        bytes[12..20].copy_from_slice(&u64::MAX.to_le_bytes());
2704        let error = FttsqReader::parse_directory(&bytes).expect_err("must refuse");
2705        assert!(matches!(error, FttsqError::DirectoryLength { .. }));
2706    }
2707
2708    /// Directory-level defects, each of which would otherwise become a bad read at runtime.
2709    #[test]
2710    fn structural_violations_are_each_refused_by_name() {
2711        type StructuralCase = (&'static str, Value, fn(&FttsqError) -> bool);
2712        let cases: Vec<StructuralCase> = vec![
2713            (
2714                "overlapping sections",
2715                json!([
2716                    {"name": "a", "access_class": "METADATA", "offset": 100, "length": 50, "sha256": "x"},
2717                    {"name": "b", "access_class": "METADATA", "offset": 120, "length": 10, "sha256": "x"},
2718                ]),
2719                |e| matches!(e, FttsqError::SectionOverlap { .. }),
2720            ),
2721            (
2722                "a section running past the file",
2723                json!([
2724                    {"name": "a", "access_class": "METADATA", "offset": 100, "length": u64::MAX, "sha256": "x"},
2725                ]),
2726                |e| matches!(e, FttsqError::RangeOutOfBounds { .. }),
2727            ),
2728            (
2729                "a duplicate section name",
2730                json!([
2731                    {"name": "a", "access_class": "METADATA", "offset": 100, "length": 10, "sha256": "x"},
2732                    {"name": "a", "access_class": "METADATA", "offset": 200, "length": 10, "sha256": "x"},
2733                ]),
2734                |e| matches!(e, FttsqError::DuplicateName { .. }),
2735            ),
2736            (
2737                "an unknown access class",
2738                json!([
2739                    {"name": "a", "access_class": "PROBABLY_HOT", "offset": 100, "length": 10, "sha256": "x"},
2740                ]),
2741                |e| matches!(e, FttsqError::UnknownValue { .. }),
2742            ),
2743        ];
2744
2745        for (description, sections, matches_expected) in cases {
2746            let error = parse_sections(Some(&sections), 4096)
2747                .expect_err(&format!("`{description}` must be refused"));
2748            assert!(
2749                matches_expected(&error),
2750                "`{description}` produced the wrong error: {error}"
2751            );
2752        }
2753    }
2754
2755    #[test]
2756    fn a_tensor_whose_length_disagrees_with_its_shape_is_refused() {
2757        let sections = vec![SectionEntry {
2758            name: "s".to_owned(),
2759            access_class: AccessClass::Metadata,
2760            offset: 0,
2761            length: 4096,
2762            sha256: String::new(),
2763        }];
2764        let index: BTreeMap<String, usize> = [("s".to_owned(), 0)].into_iter().collect();
2765
2766        // 8x8 bf16 is 128 bytes, not 64.
2767        let tensors = json!([
2768            {"name": "t", "section": "s", "dtype": "bf16", "shape": [8, 8], "offset": 0, "length": 64},
2769        ]);
2770        let error = parse_tensors(Some(&tensors), &sections, &index).expect_err("must refuse");
2771        assert_eq!(
2772            error,
2773            FttsqError::LengthMismatch {
2774                tensor: "t".to_owned(),
2775                declared: 64,
2776                implied: 128,
2777            }
2778        );
2779    }
2780
2781    #[test]
2782    fn tensors_may_not_overlap_within_a_section() {
2783        let sections = vec![SectionEntry {
2784            name: "s".to_owned(),
2785            access_class: AccessClass::Metadata,
2786            offset: 0,
2787            length: 4096,
2788            sha256: String::new(),
2789        }];
2790        let index: BTreeMap<String, usize> = [("s".to_owned(), 0)].into_iter().collect();
2791        let tensors = json!([
2792            {"name": "a", "section": "s", "dtype": "q8", "shape": [64], "offset": 0, "length": 64},
2793            {"name": "b", "section": "s", "dtype": "q8", "shape": [64], "offset": 32, "length": 64},
2794        ]);
2795        let error = parse_tensors(Some(&tensors), &sections, &index).expect_err("must refuse");
2796        assert!(matches!(error, FttsqError::TensorOverlap { .. }), "{error}");
2797    }
2798
2799    #[test]
2800    fn a_tensor_leaving_its_section_is_refused() {
2801        let sections = vec![SectionEntry {
2802            name: "s".to_owned(),
2803            access_class: AccessClass::Metadata,
2804            offset: 0,
2805            length: 64,
2806            sha256: String::new(),
2807        }];
2808        let index: BTreeMap<String, usize> = [("s".to_owned(), 0)].into_iter().collect();
2809        let tensors = json!([
2810            {"name": "a", "section": "s", "dtype": "q8", "shape": [64], "offset": 32, "length": 64},
2811        ]);
2812        let error = parse_tensors(Some(&tensors), &sections, &index).expect_err("must refuse");
2813        assert!(
2814            matches!(error, FttsqError::RangeOutOfBounds { .. }),
2815            "{error}"
2816        );
2817    }
2818
2819    #[test]
2820    fn an_artifact_without_a_license_notice_cannot_be_written_or_read() {
2821        // Writer side.
2822        let error = FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "c".repeat(64))
2823            .section("m", AccessClass::Metadata, vec![1, 2, 3])
2824            .finish()
2825            .expect_err("Apache-2.0 §4 makes the notice mandatory");
2826        assert_eq!(error, FttsqError::LicenseNoticeMissing);
2827
2828        // Reader side: an artifact whose notice was stripped after the fact is still refused.
2829        let mut bytes = artifact();
2830        let directory_len = u64::from_le_bytes(bytes[12..20].try_into().expect("header length"));
2831        let directory_start = HEADER_PREFIX_BYTES as usize;
2832        let directory_end = directory_start + directory_len as usize;
2833        let mut directory: Value = serde_json::from_slice(&bytes[directory_start..directory_end])
2834            .expect("fixture directory");
2835        directory["license_notice"] = Value::String(String::new());
2836        let mut replacement = serde_json::to_vec(&directory).expect("serializes directory");
2837        assert!(
2838            replacement.len() <= directory_len as usize,
2839            "removing a notice cannot grow it"
2840        );
2841        replacement.resize(directory_len as usize, b' ');
2842        bytes[directory_start..directory_end].copy_from_slice(&replacement);
2843        assert_eq!(
2844            FttsqReader::open(&bytes).expect_err("must refuse a missing notice"),
2845            FttsqError::LicenseNoticeMissing
2846        );
2847    }
2848
2849    #[test]
2850    fn write_to_path_lands_a_complete_readable_artifact_and_leaves_no_temporary() {
2851        let dir = std::env::temp_dir().join(format!("ftts-fttsq-write-{}", std::process::id()));
2852        std::fs::create_dir_all(&dir).expect("scratch dir");
2853        let path = dir.join("model.fttsq");
2854
2855        FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "d".repeat(64))
2856            .license_notice(NOTICE)
2857            .section("m", AccessClass::HotRecurrentMicrodecoder, vec![3_u8; 128])
2858            .section(
2859                "embedding",
2860                AccessClass::ColdTextEmbedding,
2861                vec![9_u8; 8192],
2862            )
2863            .tensor(TensorEntry {
2864                name: "m.w".to_owned(),
2865                section: "m".to_owned(),
2866                dtype: StoredDtype::Q8,
2867                shape: vec![128],
2868                offset: 0,
2869                length: 128,
2870                scales: None,
2871            })
2872            .tensor(TensorEntry {
2873                name: "embedding.one_row".to_owned(),
2874                section: "embedding".to_owned(),
2875                dtype: StoredDtype::Q8,
2876                shape: vec![32],
2877                offset: 4096,
2878                length: 32,
2879                scales: None,
2880            })
2881            .write_to_path(&path)
2882            .expect("artifact is writable");
2883
2884        let bytes = std::fs::read(&path).expect("artifact is readable");
2885        let reader = FttsqReader::open(&bytes).expect("what landed on disk must verify");
2886        assert_eq!(
2887            reader.tensor_bytes("m.w", &bytes).expect("resolves"),
2888            &vec![3_u8; 128][..]
2889        );
2890
2891        let mapped = MappedFttsq::open(&path).expect("mapped artifact validates");
2892        assert_eq!(mapped.len(), bytes.len());
2893        assert_eq!(
2894            mapped
2895                .tensor_bytes("embedding.one_row")
2896                .expect("row range resolves without copying the section"),
2897            &vec![9_u8; 32][..]
2898        );
2899
2900        let micro = mapped
2901            .page_advice()
2902            .iter()
2903            .find(|application| application.section == "m")
2904            .expect("microdecoder application is recorded");
2905        assert_eq!(micro.policy, PagePolicy::Resident);
2906        assert_eq!(micro.requested, Some(MemoryAdvice::WillNeed));
2907        assert!(
2908            !matches!(micro.outcome, PageAdviceOutcome::Failed(_)),
2909            "a valid mapped microdecoder section must receive a usable advice result: {micro:?}"
2910        );
2911
2912        let embedding = mapped
2913            .page_advice()
2914            .iter()
2915            .find(|application| application.section == "embedding")
2916            .expect("embedding application is recorded");
2917        assert_eq!(embedding.policy, PagePolicy::LazyRowGranular);
2918        assert_eq!(embedding.requested, Some(MemoryAdvice::Random));
2919        assert!(
2920            !embedding.policy.may_prefetch(),
2921            "the cold embedding policy must make wholesale prefetch impossible"
2922        );
2923        for observation in [&embedding.residency_before, &embedding.residency_after] {
2924            match observation {
2925                PageResidencyOutcome::Measured {
2926                    resident_pages,
2927                    total_pages,
2928                } => assert!(
2929                    resident_pages <= total_pages,
2930                    "the OQ-18 residency measurement exceeded the section's page span"
2931                ),
2932                PageResidencyOutcome::Unsupported => {}
2933                PageResidencyOutcome::Failed(detail) => {
2934                    panic!("the cold embedding residency measurement failed: {detail}");
2935                }
2936            }
2937        }
2938        assert!(
2939            mapped.page_advice().iter().all(|application| {
2940                application.policy.may_prefetch()
2941                    || application.requested != Some(MemoryAdvice::WillNeed)
2942            }),
2943            "a non-prefetch section was routed to MADV_WILLNEED"
2944        );
2945
2946        // The temporary must not survive a successful write.
2947        let strays: Vec<_> = std::fs::read_dir(&dir)
2948            .expect("dir is listable")
2949            .filter_map(Result::ok)
2950            .map(|entry| entry.file_name().to_string_lossy().into_owned())
2951            .filter(|name| name.contains(".tmp."))
2952            .collect();
2953        assert!(strays.is_empty(), "temporary files left behind: {strays:?}");
2954
2955        std::fs::remove_file(&path).expect("cleanup");
2956    }
2957
2958    #[test]
2959    fn write_to_path_refuses_before_touching_the_filesystem_when_the_notice_is_missing() {
2960        let dir = std::env::temp_dir().join(format!("ftts-fttsq-refuse-{}", std::process::id()));
2961        std::fs::create_dir_all(&dir).expect("scratch dir");
2962        let path = dir.join("model.fttsq");
2963
2964        let error = FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "e".repeat(64))
2965            .section("m", AccessClass::Metadata, vec![1, 2, 3])
2966            .write_to_path(&path)
2967            .expect_err("a notice-less artifact must never reach disk");
2968        assert_eq!(error, FttsqError::LicenseNoticeMissing);
2969        assert!(
2970            !path.exists(),
2971            "a refused artifact must not leave a file behind"
2972        );
2973    }
2974
2975    /// The invariant the whole page-in policy exists to enforce.
2976    #[test]
2977    fn the_cold_text_embedding_is_never_prefetched_and_hot_classes_always_are() {
2978        assert_eq!(
2979            AccessClass::ColdTextEmbedding.page_policy(),
2980            PagePolicy::LazyRowGranular
2981        );
2982        assert!(
2983            !AccessClass::ColdTextEmbedding.page_policy().may_prefetch(),
2984            "MADV_WILLNEED over the ~622 MB embedding would evict the microdecoder pack"
2985        );
2986
2987        for hot in [
2988            AccessClass::HotRecurrentMicrodecoder,
2989            AccessClass::HotRecurrentTalker,
2990            AccessClass::HotCodecDecoder,
2991        ] {
2992            assert_eq!(hot.page_policy(), PagePolicy::Resident);
2993            assert!(hot.page_policy().may_prefetch());
2994        }
2995        for cold in [
2996            AccessClass::EnrollmentSpeakerEncoder,
2997            AccessClass::EnrollmentCodecEncoder,
2998            AccessClass::Metadata,
2999        ] {
3000            assert_eq!(cold.page_policy(), PagePolicy::OnDemand);
3001            assert!(!cold.page_policy().may_prefetch());
3002        }
3003
3004        // is_hot() and the policy must not be able to disagree — two encodings of one fact.
3005        for class in [
3006            AccessClass::HotRecurrentMicrodecoder,
3007            AccessClass::HotRecurrentTalker,
3008            AccessClass::HotCodecDecoder,
3009            AccessClass::ColdTextEmbedding,
3010            AccessClass::EnrollmentSpeakerEncoder,
3011            AccessClass::EnrollmentCodecEncoder,
3012            AccessClass::Metadata,
3013        ] {
3014            assert_eq!(
3015                class.is_hot(),
3016                class.page_policy().may_prefetch(),
3017                "is_hot() and page_policy() disagree for {class}"
3018            );
3019            assert_eq!(
3020                class.is_row_granular(),
3021                class.page_policy() == PagePolicy::LazyRowGranular,
3022                "is_row_granular() and page_policy() disagree for {class}"
3023            );
3024        }
3025    }
3026
3027    #[test]
3028    fn the_page_in_plan_prefetches_the_microdecoder_before_the_larger_talker() {
3029        // Sizes stand in for the real ~110 MB pack and ~440 MB talker.
3030        let bytes = FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "f".repeat(64))
3031            .license_notice(NOTICE)
3032            .section("talker", AccessClass::HotRecurrentTalker, vec![1_u8; 400])
3033            .section("embedding", AccessClass::ColdTextEmbedding, vec![2_u8; 900])
3034            .section(
3035                "micro",
3036                AccessClass::HotRecurrentMicrodecoder,
3037                vec![3_u8; 100],
3038            )
3039            .section("meta", AccessClass::Metadata, vec![4_u8; 8])
3040            .finish()
3041            .expect("writable");
3042        let reader = FttsqReader::open(&bytes).expect("readable");
3043
3044        let plan = reader.page_in_plan();
3045        let order: Vec<&str> = plan
3046            .iter()
3047            .map(|(section, _)| section.name.as_str())
3048            .collect();
3049        assert_eq!(
3050            order,
3051            vec!["micro", "talker", "embedding", "meta"],
3052            "resident sections first, smallest first, so the 15x-reread pack wins the cache race"
3053        );
3054        assert_eq!(plan[0].1, PagePolicy::Resident);
3055        assert_eq!(plan[2].1, PagePolicy::LazyRowGranular);
3056        assert_eq!(plan[3].1, PagePolicy::OnDemand);
3057
3058        // Nothing outside the resident group may ever be prefetched.
3059        for (section, policy) in &plan {
3060            assert_eq!(
3061                policy.may_prefetch(),
3062                section.access_class.is_hot(),
3063                "section `{}` would be prefetched against policy",
3064                section.name
3065            );
3066        }
3067    }
3068
3069    fn census_fixture() -> (Vec<u8>, ArtifactManifest) {
3070        let bytes = FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "g".repeat(64))
3071            .license_notice(NOTICE)
3072            .section(
3073                "micro",
3074                AccessClass::HotRecurrentMicrodecoder,
3075                vec![1_u8; 64],
3076            )
3077            .section("embedding", AccessClass::ColdTextEmbedding, vec![2_u8; 32])
3078            .tensor(TensorEntry {
3079                name: "micro.body".to_owned(),
3080                section: "micro".to_owned(),
3081                dtype: StoredDtype::Q8,
3082                shape: vec![8, 8],
3083                offset: 0,
3084                length: 64,
3085                scales: None,
3086            })
3087            .tensor(TensorEntry {
3088                name: "text_embedding.weight".to_owned(),
3089                section: "embedding".to_owned(),
3090                dtype: StoredDtype::Bf16,
3091                shape: vec![4, 4],
3092                offset: 0,
3093                length: 32,
3094                scales: None,
3095            })
3096            .finish()
3097            .expect("writable");
3098
3099        let manifest = ArtifactManifest::new("qwen3-tts pinned")
3100            .expect(ExpectedArtifactTensor {
3101                name: "micro.body".to_owned(),
3102                shape: vec![8, 8],
3103                dtype: StoredDtype::Q8,
3104                access_class: AccessClass::HotRecurrentMicrodecoder,
3105            })
3106            .expect(ExpectedArtifactTensor {
3107                name: "text_embedding.weight".to_owned(),
3108                shape: vec![4, 4],
3109                dtype: StoredDtype::Bf16,
3110                access_class: AccessClass::ColdTextEmbedding,
3111            });
3112        (bytes, manifest)
3113    }
3114
3115    #[test]
3116    fn a_matching_artifact_passes_its_census() {
3117        let (bytes, manifest) = census_fixture();
3118        let reader = FttsqReader::open(&bytes).expect("readable");
3119        let report = manifest.audit(&reader);
3120        assert!(report.is_green(), "{}", report.render());
3121        assert!(reader.verify_census(&manifest).is_ok());
3122    }
3123
3124    /// Each divergence class must be caught, and all of them reported in one pass.
3125    #[test]
3126    fn the_census_names_every_divergence_class_in_one_pass() {
3127        let (bytes, _) = census_fixture();
3128        let reader = FttsqReader::open(&bytes).expect("readable");
3129
3130        let manifest = ArtifactManifest::new("deliberately wrong")
3131            // Right name, wrong shape AND wrong dtype: both must be reported, not just the first.
3132            .expect(ExpectedArtifactTensor {
3133                name: "micro.body".to_owned(),
3134                shape: vec![16, 4],
3135                dtype: StoredDtype::Q4,
3136                access_class: AccessClass::HotRecurrentMicrodecoder,
3137            })
3138            // Correct in every respect except where it lives — the silent one.
3139            .expect(ExpectedArtifactTensor {
3140                name: "text_embedding.weight".to_owned(),
3141                shape: vec![4, 4],
3142                dtype: StoredDtype::Bf16,
3143                access_class: AccessClass::HotRecurrentTalker,
3144            })
3145            // Required but absent.
3146            .expect(ExpectedArtifactTensor {
3147                name: "codec.decoder.weight".to_owned(),
3148                shape: vec![2],
3149                dtype: StoredDtype::Q8,
3150                access_class: AccessClass::HotCodecDecoder,
3151            });
3152
3153        let report = manifest.audit(&reader);
3154        assert!(!report.is_green());
3155        assert_eq!(report.count_of("shape_mismatch"), 1, "{}", report.render());
3156        assert_eq!(report.count_of("dtype_mismatch"), 1, "{}", report.render());
3157        assert_eq!(
3158            report.count_of("wrong_access_class"),
3159            1,
3160            "a tensor in the wrong access class still produces correct audio while destroying \
3161             residency — the census is the only thing that catches it:\n{}",
3162            report.render()
3163        );
3164        assert_eq!(report.count_of("missing"), 1, "{}", report.render());
3165
3166        let rendered = report.render();
3167        for expected in [
3168            "micro.body",
3169            "text_embedding.weight",
3170            "codec.decoder.weight",
3171            "ACCESS_CLASS",
3172            "SHAPE",
3173            "DTYPE",
3174            "MISSING",
3175        ] {
3176            assert!(
3177                rendered.contains(expected),
3178                "census report is missing `{expected}`:\n{rendered}"
3179            );
3180        }
3181
3182        assert!(reader.verify_census(&manifest).is_err());
3183    }
3184
3185    /// An artifact carrying tensors nobody expected is a *different checkpoint*.
3186    #[test]
3187    fn unexpected_tensors_are_reported_as_extra() {
3188        let (bytes, _) = census_fixture();
3189        let reader = FttsqReader::open(&bytes).expect("readable");
3190        let manifest = ArtifactManifest::new("partial").expect(ExpectedArtifactTensor {
3191            name: "micro.body".to_owned(),
3192            shape: vec![8, 8],
3193            dtype: StoredDtype::Q8,
3194            access_class: AccessClass::HotRecurrentMicrodecoder,
3195        });
3196        let report = manifest.audit(&reader);
3197        assert_eq!(report.count_of("extra"), 1, "{}", report.render());
3198        assert!(report.render().contains("text_embedding.weight"));
3199    }
3200
3201    #[test]
3202    fn quantized_dtype_sizes_are_exact_including_the_odd_q4_tail() {
3203        assert_eq!(StoredDtype::Bf16.storage_bytes(10), Some(20));
3204        assert_eq!(StoredDtype::F32.storage_bytes(10), Some(40));
3205        assert_eq!(StoredDtype::Q8.storage_bytes(10), Some(10));
3206        // Two elements per byte, rounding up: an odd count still occupies a whole trailing byte.
3207        assert_eq!(StoredDtype::Q4.storage_bytes(10), Some(5));
3208        assert_eq!(StoredDtype::Q4.storage_bytes(11), Some(6));
3209        // Overflow is reported, never wrapped into a small, plausible-looking size.
3210        assert_eq!(StoredDtype::F32.storage_bytes(u64::MAX), None);
3211    }
3212
3213    #[test]
3214    fn wire_strings_round_trip_for_every_enum_value() {
3215        for class in [
3216            AccessClass::HotRecurrentMicrodecoder,
3217            AccessClass::HotRecurrentTalker,
3218            AccessClass::HotCodecDecoder,
3219            AccessClass::ColdTextEmbedding,
3220            AccessClass::EnrollmentSpeakerEncoder,
3221            AccessClass::EnrollmentCodecEncoder,
3222            AccessClass::Metadata,
3223        ] {
3224            assert_eq!(AccessClass::parse(class.as_str()), Some(class));
3225        }
3226        for dtype in [
3227            StoredDtype::Bf16,
3228            StoredDtype::F32,
3229            StoredDtype::Q8,
3230            StoredDtype::Q4,
3231        ] {
3232            assert_eq!(StoredDtype::parse(dtype.as_str()), Some(dtype));
3233        }
3234        assert_eq!(AccessClass::parse("HOT_SOMETHING"), None);
3235        assert_eq!(StoredDtype::parse("f16"), None);
3236    }
3237}