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    /// Sections the backing bytes stop short of, in declaration order.
703    ///
704    /// Empty for every whole-file load, which is every native load. Non-empty only via
705    /// [`MappedFttsq::from_prefix_bytes`], where a caller has deliberately declined to hold a
706    /// trailing section. Reads into an absent section are already refused by the range checks in
707    /// [`FttsqReader::tensor_bytes`]; this list exists so callers can ask BEFORE reading, and so
708    /// the refusal can name the cause rather than looking like a corrupt artifact.
709    absent_sections: Vec<String>,
710}
711
712impl MappedFttsq {
713    /// Map, fully validate, and apply the access-class page-in plan to an artifact.
714    ///
715    /// The integrity check remains eager because the v1 format carries a digest per section rather
716    /// than a per-page Merkle tree. Consequently, this loader establishes a *policy* guarantee —
717    /// the cold embedding is never sent `MADV_WILLNEED` — and records the pre-digest residency
718    /// measurement, not a claim that opening v1 avoids every cold-section page fault. A
719    /// page-granular integrity format is required before that stronger claim can be made honestly.
720    ///
721    /// # Errors
722    ///
723    /// Returns a named [`FttsqError`] for mapping, structural, or digest-validation failures.
724    pub fn open(path: impl AsRef<std::path::Path>) -> Result<Self, FttsqError> {
725        let path = path.as_ref();
726        let mapping = MappedFile::open(path).map_err(|error| FttsqError::Io {
727            operation: "memory-map artifact".to_owned(),
728            path: path.display().to_string(),
729            detail: error.to_string(),
730        })?;
731        // Structural validation bounds each requested range before an OS call. The subsequent
732        // digest pass is still mandatory before this mapping can be returned to a caller.
733        let reader = FttsqReader::parse_directory(mapping.as_slice())?;
734        let page_advice = apply_page_in_plan(&mapping, &reader);
735        reader.verify_digests(mapping.as_slice())?;
736        Ok(Self {
737            mapping,
738            reader,
739            page_advice,
740            absent_sections: Vec::new(),
741        })
742    }
743
744    /// Validate an artifact held wholly in memory, for targets with no filesystem (wasm32).
745    ///
746    /// Runs the identical structural-then-digest pipeline as [`MappedFttsq::open`]; only the
747    /// byte source differs, so a buffer that verifies here is exactly a file that would have
748    /// verified there.
749    ///
750    /// # Errors
751    ///
752    /// Returns the same named [`FttsqError`] classes as [`MappedFttsq::open`].
753    #[cfg(not(unix))] // the owned-bytes MappedFile backing (and its from_bytes) exists off-unix
754    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, FttsqError> {
755        let mapping = MappedFile::from_bytes(bytes);
756        let reader = FttsqReader::parse_directory(mapping.as_slice())?;
757        let page_advice = apply_page_in_plan(&mapping, &reader);
758        reader.verify_digests(mapping.as_slice())?;
759        Ok(Self {
760            mapping,
761            reader,
762            page_advice,
763            absent_sections: Vec::new(),
764        })
765    }
766
767    /// Load a PREFIX of an artifact: every section it fully contains is verified, the rest are
768    /// recorded as absent.
769    ///
770    /// # Why a partial artifact is a legitimate thing to want
771    ///
772    /// [`AccessClass::ColdTextEmbedding`] is 622 MB — 47% of the artifact — and the format already
773    /// says what it is for: lazy, row-granular, never paged in wholesale. A native host gets that
774    /// for free, because the mapping only faults in the rows an utterance actually touches.
775    /// wasm has no mapping. Every byte staged into linear memory is a byte permanently resident,
776    /// and the wasm heap is grow-only — a tab can never hand memory back — so on a phone the cold
777    /// table is 622 MB of ballast held for the lifetime of the page to serve a few hundred 4 KB
778    /// rows per utterance.
779    ///
780    /// So the browser stops the buffer where the cold section starts and reads those rows from
781    /// OPFS instead. That works ONLY because the cold section is last in the file: every other
782    /// section keeps its true offset, so nothing needs remapping and no offset arithmetic changes.
783    /// A future layout that put a hot section after a cold one would break this, which is why the
784    /// prefix length is derived from the directory rather than assumed
785    /// ([`FttsqReader::prefix_len_omitting`]).
786    ///
787    /// # Integrity
788    ///
789    /// Undiminished for what is present. The v1 format carries a digest per section, so a prefix
790    /// verifies exactly the sections it holds, in full, with the same SHA-256 comparison as a
791    /// whole-file load. What is NOT present is not vouched for here — the caller holding the rest
792    /// out of band is responsible for it (the browser verifies the whole file on download, before
793    /// it ever reaches this path).
794    ///
795    /// # Errors
796    ///
797    /// As [`MappedFttsq::from_bytes`], plus a refusal when the prefix is too short to contain the
798    /// directory itself, or when it splits a section rather than stopping cleanly before one — a
799    /// partial section cannot be verified, so it is rejected rather than silently trusted.
800    #[cfg(not(unix))] // same owned-bytes backing as `from_bytes`; a mapping never needs this
801    pub fn from_prefix_bytes(bytes: Vec<u8>, file_len: u64) -> Result<Self, FttsqError> {
802        let mapping = MappedFile::from_bytes(bytes);
803        // Against the artifact's REAL length, not the bytes in hand: the elided section runs past
804        // the prefix by construction, so bounding by what is present rejects every prefix outright.
805        let reader = FttsqReader::parse_directory_of_prefix(mapping.as_slice(), file_len)?;
806        let available = mapping.as_slice().len() as u64;
807
808        let absent_sections = reader.absent_sections_in_prefix(available)?;
809        let page_advice = apply_page_in_plan(&mapping, &reader);
810        reader.verify_digests_of_present(mapping.as_slice())?;
811        Ok(Self {
812            mapping,
813            reader,
814            page_advice,
815            absent_sections,
816        })
817    }
818
819    /// Sections this artifact's bytes stop short of; empty for a whole-file load.
820    #[must_use]
821    pub fn absent_sections(&self) -> &[String] {
822        &self.absent_sections
823    }
824
825    /// Whether a named section's bytes are actually held.
826    #[must_use]
827    pub fn has_section(&self, name: &str) -> bool {
828        self.reader.section(name).is_some() && !self.absent_sections.iter().any(|s| s == name)
829    }
830
831    /// The fully validated directory over this artifact.
832    #[must_use]
833    pub const fn reader(&self) -> &FttsqReader {
834        &self.reader
835    }
836
837    /// The policy application record, in the same priority order as `page_in_plan`.
838    #[must_use]
839    pub fn page_advice(&self) -> &[PageAdviceApplication] {
840        &self.page_advice
841    }
842
843    /// Borrow one tensor's logical bytes without copying the mapped payload.
844    ///
845    /// # Errors
846    ///
847    /// Returns the same named lookup/range errors as [`FttsqReader::tensor_bytes`].
848    pub fn tensor_bytes(&self, name: &str) -> Result<&[u8], FttsqError> {
849        self.reader.tensor_bytes(name, self.mapping.as_slice())
850    }
851
852    /// The artifact's mapped (or fallback-owned) byte length.
853    #[must_use]
854    pub fn len(&self) -> usize {
855        self.mapping.len()
856    }
857
858    /// Whether this artifact is empty.
859    #[must_use]
860    pub fn is_empty(&self) -> bool {
861        self.mapping.is_empty()
862    }
863}
864
865fn apply_page_in_plan(mapping: &MappedFile, reader: &FttsqReader) -> Vec<PageAdviceApplication> {
866    reader
867        .page_in_plan()
868        .into_iter()
869        .map(|(section, policy)| {
870            let requested = match policy {
871                PagePolicy::Resident => Some(MemoryAdvice::WillNeed),
872                PagePolicy::LazyRowGranular => Some(MemoryAdvice::Random),
873                PagePolicy::OnDemand => None,
874            };
875
876            // This is intentionally a runtime assertion rather than a documentation promise. A
877            // future policy refactor must fail immediately if it ever routes the 622 MB cold text
878            // embedding through `MADV_WILLNEED`.
879            assert!(
880                policy.may_prefetch() || requested != Some(MemoryAdvice::WillNeed),
881                "a non-prefetch policy must never issue MADV_WILLNEED"
882            );
883
884            let residency_before = observe_residency(mapping, section.offset, section.length);
885            let outcome = match requested {
886                Some(advice) => match mapping.advise(section.offset, section.length, advice) {
887                    Ok(MemoryAdviceOutcome::Applied) => PageAdviceOutcome::Applied,
888                    Ok(MemoryAdviceOutcome::SkippedEmpty) => PageAdviceOutcome::SkippedEmpty,
889                    Ok(MemoryAdviceOutcome::Unsupported) => PageAdviceOutcome::Unsupported,
890                    Err(error) => PageAdviceOutcome::Failed(error.to_string()),
891                },
892                None => PageAdviceOutcome::NotRequested,
893            };
894            let residency_after = observe_residency(mapping, section.offset, section.length);
895
896            PageAdviceApplication {
897                section: section.name.clone(),
898                policy,
899                requested,
900                residency_before,
901                outcome,
902                residency_after,
903            }
904        })
905        .collect()
906}
907
908fn observe_residency(mapping: &MappedFile, offset: u64, length: u64) -> PageResidencyOutcome {
909    match mapping.resident_pages(offset, length) {
910        Ok(MemoryResidency::Measured {
911            resident_pages,
912            total_pages,
913        }) => PageResidencyOutcome::Measured {
914            resident_pages,
915            total_pages,
916        },
917        Ok(MemoryResidency::Unsupported) => PageResidencyOutcome::Unsupported,
918        Err(error) => PageResidencyOutcome::Failed(error.to_string()),
919    }
920}
921
922impl FttsqReader {
923    /// Parses and fully validates an artifact, **including** every section digest.
924    ///
925    /// Digest verification is not optional here. A reader that can be asked to skip it grows a
926    /// caller that always skips it, and then corruption surfaces as audio rather than as an error.
927    ///
928    /// # Errors
929    ///
930    /// Returns a named [`FttsqError`] for any structural, range, or integrity violation.
931    pub fn open(bytes: &[u8]) -> Result<Self, FttsqError> {
932        let reader = Self::parse_directory(bytes)?;
933        reader.verify_digests(bytes)?;
934        Ok(reader)
935    }
936
937    /// Parses and structurally validates without computing digests.
938    ///
939    /// For inspection tooling (`ftts inspect`) over an artifact whose bytes are not all present —
940    /// listing a remote artifact's tensors, say. Never use this to load weights: it does not prove
941    /// the payload is intact.
942    ///
943    /// # Errors
944    ///
945    /// Returns a named [`FttsqError`] for any structural or range violation.
946    pub fn parse_directory(bytes: &[u8]) -> Result<Self, FttsqError> {
947        Self::parse_directory_for_file_len(bytes, bytes.len() as u64)
948    }
949
950    /// Parses the directory of a PREFIX, validating ranges against the artifact's full length.
951    ///
952    /// [`FttsqReader::parse_directory`] bounds every declared range by the bytes in hand, which is
953    /// right for a whole file and wrong for a deliberate prefix: the section being left behind
954    /// necessarily runs past the end, so parsing refuses before absence can even be considered.
955    /// Here the structural check keeps its full strength against `file_len` — the artifact's real
956    /// size, known independently — while only the header and directory need to be present.
957    ///
958    /// This proves structure, never integrity. Digests still decide what may be read, via
959    /// [`FttsqReader::verify_digests_of_present`].
960    ///
961    /// # Errors
962    ///
963    /// Returns a named [`FttsqError`] for any structural or range violation.
964    pub fn parse_directory_of_prefix(bytes: &[u8], file_len: u64) -> Result<Self, FttsqError> {
965        Self::parse_directory_for_file_len(bytes, file_len)
966    }
967
968    /// Parses a present header and directory against a declared final file length.
969    ///
970    /// The streaming writer uses this before accepting payload bytes: only the header and
971    /// directory are in memory at that point, but all declared section and tensor ranges must
972    /// already be valid for the eventual artifact length. It stays private so callers cannot
973    /// mistake a structural preflight for a verified artifact load.
974    fn parse_directory_for_file_len(bytes: &[u8], file_len: u64) -> Result<Self, FttsqError> {
975        let present_len = bytes.len() as u64;
976        if present_len < HEADER_PREFIX_BYTES {
977            return Err(FttsqError::TooShort {
978                length: present_len,
979            });
980        }
981
982        let mut magic = [0_u8; 8];
983        magic.copy_from_slice(&bytes[..8]);
984        if &magic != MAGIC {
985            return Err(FttsqError::BadMagic { found: magic });
986        }
987
988        let format_version = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
989        // Version 0 was never issued: accepting it would silently read an unversioned or
990        // zero-filled header as v1. Valid artifacts carry 1..=FORMAT_VERSION.
991        if format_version == 0 || format_version > FORMAT_VERSION {
992            return Err(FttsqError::UnsupportedVersion {
993                found: format_version,
994                supported: FORMAT_VERSION,
995            });
996        }
997
998        let mut length_bytes = [0_u8; 8];
999        length_bytes.copy_from_slice(&bytes[12..20]);
1000        let directory_len = u64::from_le_bytes(length_bytes);
1001        if directory_len > MAX_DIRECTORY_BYTES {
1002            return Err(FttsqError::DirectoryLength {
1003                declared: directory_len,
1004                limit: MAX_DIRECTORY_BYTES,
1005            });
1006        }
1007        let directory_end =
1008            HEADER_PREFIX_BYTES
1009                .checked_add(directory_len)
1010                .ok_or(FttsqError::DirectoryLength {
1011                    declared: directory_len,
1012                    limit: u64::MAX,
1013                })?;
1014        if directory_end > present_len || directory_end > file_len {
1015            return Err(FttsqError::DirectoryLength {
1016                declared: directory_len,
1017                limit: present_len.min(file_len),
1018            });
1019        }
1020
1021        // `directory_end` is bounded by `file_len` above, so both casts are in range.
1022        let directory_bytes = &bytes[HEADER_PREFIX_BYTES as usize..directory_end as usize];
1023        let directory: Value = serde_json::from_slice(directory_bytes).map_err(|error| {
1024            FttsqError::DirectoryMalformed {
1025                detail: error.to_string(),
1026            }
1027        })?;
1028        let object = directory
1029            .as_object()
1030            .ok_or_else(|| FttsqError::DirectoryMalformed {
1031                detail: "top level is not a JSON object".to_owned(),
1032            })?;
1033
1034        let model_family = required_str(object.get("model_family"), "model_family")?.to_owned();
1035        let source_sha256 = required_str(object.get("source_sha256"), "source_sha256")?.to_owned();
1036
1037        // Apache-2.0 §4 compliance is a read-time gate, not a writer-side courtesy.
1038        let license_notice = object
1039            .get("license_notice")
1040            .and_then(Value::as_str)
1041            .unwrap_or_default()
1042            .to_owned();
1043        if license_notice.trim().is_empty() {
1044            return Err(FttsqError::LicenseNoticeMissing);
1045        }
1046
1047        let model_config = object.get("model_config").cloned().unwrap_or(Value::Null);
1048        let quantization_manifest = object
1049            .get("quantization_manifest")
1050            .cloned()
1051            .unwrap_or(Value::Null);
1052
1053        let sections = parse_sections(object.get("sections"), file_len)?;
1054        let section_index: BTreeMap<String, usize> = sections
1055            .iter()
1056            .enumerate()
1057            .map(|(index, section)| (section.name.clone(), index))
1058            .collect();
1059        let tensors = parse_tensors(object.get("tensors"), &sections, &section_index)?;
1060        let tensor_index: BTreeMap<String, usize> = tensors
1061            .iter()
1062            .enumerate()
1063            .map(|(index, tensor)| (tensor.name.clone(), index))
1064            .collect();
1065
1066        Ok(Self {
1067            format_version,
1068            model_family,
1069            source_sha256,
1070            license_notice,
1071            model_config,
1072            quantization_manifest,
1073            sections,
1074            tensors,
1075            section_index,
1076            tensor_index,
1077        })
1078    }
1079
1080    /// Recomputes and checks every section digest against `bytes`.
1081    ///
1082    /// # Errors
1083    ///
1084    /// Returns [`FttsqError::DigestMismatch`] naming the first corrupt section.
1085    pub fn verify_digests(&self, bytes: &[u8]) -> Result<(), FttsqError> {
1086        for section in &self.sections {
1087            let payload = self.section_bytes(section, bytes)?;
1088            let mut hasher = Sha256::new();
1089            hasher.update(payload);
1090            let actual = to_hex(&hasher.finish());
1091            if actual != section.sha256 {
1092                return Err(FttsqError::DigestMismatch {
1093                    section: section.name.clone(),
1094                    expected: section.sha256.clone(),
1095                    actual,
1096                });
1097            }
1098        }
1099        Ok(())
1100    }
1101
1102    /// Verify every section the bytes fully contain, skipping those they stop short of.
1103    ///
1104    /// The skip is deliberately silent because the caller has already classified what is absent
1105    /// and refused any section that was merely truncated — see [`MappedFttsq::from_prefix_bytes`].
1106    /// What this verifies, it verifies exactly as [`FttsqReader::verify_digests`] does.
1107    ///
1108    /// # Errors
1109    ///
1110    /// On any present section whose payload does not match its declared digest.
1111    pub fn verify_digests_of_present(&self, bytes: &[u8]) -> Result<(), FttsqError> {
1112        for section in &self.sections {
1113            let Some(end) = section.end() else { continue };
1114            if end > bytes.len() as u64 {
1115                continue;
1116            }
1117            let payload = self.section_bytes(section, bytes)?;
1118            let mut hasher = Sha256::new();
1119            hasher.update(payload);
1120            let actual = to_hex(&hasher.finish());
1121            if actual != section.sha256 {
1122                return Err(FttsqError::DigestMismatch {
1123                    section: section.name.clone(),
1124                    expected: section.sha256.clone(),
1125                    actual,
1126                });
1127            }
1128        }
1129        Ok(())
1130    }
1131
1132    /// Which sections a prefix of `available` bytes stops short of entirely.
1133    ///
1134    /// A section is absent when it starts at or after the cut, and present when it ends at or
1135    /// before it. A section straddling the cut is neither: it cannot be digest-checked and must
1136    /// not be read, so it is an error rather than a third category. That distinction is what
1137    /// separates "the caller deliberately declined a trailing section" from "the download stopped
1138    /// early", which would otherwise both arrive here looking identical.
1139    ///
1140    /// # Errors
1141    ///
1142    /// When the prefix splits a section, or a declared section's range overflows.
1143    pub fn absent_sections_in_prefix(&self, available: u64) -> Result<Vec<String>, FttsqError> {
1144        let mut absent = Vec::new();
1145        for section in &self.sections {
1146            let end = section
1147                .end()
1148                .ok_or_else(|| FttsqError::RangeOutOfBounds {
1149                    what: format!("section `{}`", section.name),
1150                    offset: section.offset,
1151                    length: section.length,
1152                    bound: available,
1153                })?;
1154            if end <= available {
1155                continue;
1156            }
1157            if section.offset < available {
1158                return Err(FttsqError::RangeOutOfBounds {
1159                    what: format!("prefix splits section `{}`", section.name),
1160                    offset: section.offset,
1161                    length: section.length,
1162                    bound: available,
1163                });
1164            }
1165            absent.push(section.name.clone());
1166        }
1167        Ok(absent)
1168    }
1169
1170    /// The shortest prefix that holds every section EXCEPT those in `omit`.
1171    ///
1172    /// Returns `None` when omitting those classes would not leave a contiguous prefix — i.e. when
1173    /// a retained section sits after an omitted one. That is the guard that keeps the browser's
1174    /// truncation honest against a future layout change: the saving depends on the omitted
1175    /// sections being last, and this is what refuses to assume it.
1176    #[must_use]
1177    pub fn prefix_len_omitting(&self, omit: &[AccessClass]) -> Option<u64> {
1178        let omitted = |section: &SectionEntry| omit.contains(&section.access_class);
1179        let first_omitted = self
1180            .sections
1181            .iter()
1182            .filter(|section| omitted(section))
1183            .map(|section| section.offset)
1184            .min()?;
1185        // Nothing retained may live at or beyond that point, or the prefix would drop it too.
1186        if self
1187            .sections
1188            .iter()
1189            .any(|section| !omitted(section) && section.offset >= first_omitted)
1190        {
1191            return None;
1192        }
1193        Some(first_omitted)
1194    }
1195
1196    fn section_bytes<'a>(
1197        &self,
1198        section: &SectionEntry,
1199        bytes: &'a [u8],
1200    ) -> Result<&'a [u8], FttsqError> {
1201        let end = section.end().ok_or_else(|| FttsqError::RangeOutOfBounds {
1202            what: format!("section `{}`", section.name),
1203            offset: section.offset,
1204            length: section.length,
1205            bound: bytes.len() as u64,
1206        })?;
1207        if end > bytes.len() as u64 {
1208            return Err(FttsqError::RangeOutOfBounds {
1209                what: format!("section `{}`", section.name),
1210                offset: section.offset,
1211                length: section.length,
1212                bound: bytes.len() as u64,
1213            });
1214        }
1215        Ok(&bytes[section.offset as usize..end as usize])
1216    }
1217
1218    /// The format version the artifact declares.
1219    #[must_use]
1220    pub const fn format_version(&self) -> u32 {
1221        self.format_version
1222    }
1223
1224    /// The model family the artifact was built from.
1225    #[must_use]
1226    pub fn model_family(&self) -> &str {
1227        &self.model_family
1228    }
1229
1230    /// SHA-256 of the upstream checkpoint this artifact was converted from.
1231    #[must_use]
1232    pub fn source_sha256(&self) -> &str {
1233        &self.source_sha256
1234    }
1235
1236    /// The Apache-2.0 §4 attribution notice. Guaranteed non-empty.
1237    #[must_use]
1238    pub fn license_notice(&self) -> &str {
1239        &self.license_notice
1240    }
1241
1242    /// The frozen copy of the upstream model config.
1243    #[must_use]
1244    pub const fn model_config(&self) -> &Value {
1245        &self.model_config
1246    }
1247
1248    /// The per-tensor quantization policy actually applied, which the license notice references.
1249    #[must_use]
1250    pub const fn quantization_manifest(&self) -> &Value {
1251        &self.quantization_manifest
1252    }
1253
1254    /// Every declared section, in declaration order.
1255    #[must_use]
1256    pub fn sections(&self) -> &[SectionEntry] {
1257        &self.sections
1258    }
1259
1260    /// Every declared tensor, in declaration order.
1261    #[must_use]
1262    pub fn tensors(&self) -> &[TensorEntry] {
1263        &self.tensors
1264    }
1265
1266    /// Looks a section up by name.
1267    #[must_use]
1268    pub fn section(&self, name: &str) -> Option<&SectionEntry> {
1269        self.section_index
1270            .get(name)
1271            .and_then(|&index| self.sections.get(index))
1272    }
1273
1274    /// Looks a tensor up by name.
1275    #[must_use]
1276    pub fn tensor(&self, name: &str) -> Option<&TensorEntry> {
1277        self.tensor_index
1278            .get(name)
1279            .and_then(|&index| self.tensors.get(index))
1280    }
1281
1282    /// Sections belonging to one access class.
1283    #[must_use]
1284    pub fn sections_in_class(&self, class: AccessClass) -> Vec<&SectionEntry> {
1285        self.sections
1286            .iter()
1287            .filter(|section| section.access_class == class)
1288            .collect()
1289    }
1290
1291    /// The byte span of one tensor within `bytes`.
1292    ///
1293    /// # Errors
1294    ///
1295    /// Returns [`FttsqError::UnknownSection`] when the tensor's section is missing, or
1296    /// [`FttsqError::RangeOutOfBounds`] when the resolved span leaves the buffer.
1297    pub fn tensor_bytes<'a>(&self, name: &str, bytes: &'a [u8]) -> Result<&'a [u8], FttsqError> {
1298        let tensor = self
1299            .tensor(name)
1300            .ok_or_else(|| FttsqError::UnknownSection {
1301                tensor: name.to_owned(),
1302                section: "<unknown tensor>".to_owned(),
1303            })?;
1304        let section = self
1305            .section(&tensor.section)
1306            .ok_or_else(|| FttsqError::UnknownSection {
1307                tensor: tensor.name.clone(),
1308                section: tensor.section.clone(),
1309            })?;
1310        let payload = self.section_bytes(section, bytes)?;
1311        let end = tensor.offset.checked_add(tensor.length).ok_or_else(|| {
1312            FttsqError::RangeOutOfBounds {
1313                what: format!("tensor `{}`", tensor.name),
1314                offset: tensor.offset,
1315                length: tensor.length,
1316                bound: payload.len() as u64,
1317            }
1318        })?;
1319        if end > payload.len() as u64 {
1320            return Err(FttsqError::RangeOutOfBounds {
1321                what: format!("tensor `{}`", tensor.name),
1322                offset: tensor.offset,
1323                length: tensor.length,
1324                bound: payload.len() as u64,
1325            });
1326        }
1327        Ok(&payload[tensor.offset as usize..end as usize])
1328    }
1329
1330    /// The page-in plan: every section paired with its policy, in the order to apply them.
1331    ///
1332    /// Ordered [`PagePolicy::Resident`] first. Prefetch order matters under memory pressure — the
1333    /// section that gets its `MADV_WILLNEED` in last is the one most likely to find the page cache
1334    /// already full, and that must never be the microdecoder pack.
1335    ///
1336    /// Within the resident group, smaller sections come first: the ~110 MB microdecoder pack lands
1337    /// ahead of the ~440 MB talker, so the reread-15×-per-frame working set wins the race for
1338    /// cache it would otherwise lose to a body read once per frame.
1339    #[must_use]
1340    pub fn page_in_plan(&self) -> Vec<(&SectionEntry, PagePolicy)> {
1341        let mut plan: Vec<(&SectionEntry, PagePolicy)> = self
1342            .sections
1343            .iter()
1344            .map(|section| (section, section.access_class.page_policy()))
1345            .collect();
1346        plan.sort_by_key(|(section, policy)| {
1347            let rank = match policy {
1348                PagePolicy::Resident => 0_u8,
1349                PagePolicy::LazyRowGranular => 1,
1350                PagePolicy::OnDemand => 2,
1351            };
1352            (rank, section.length)
1353        });
1354        plan
1355    }
1356
1357    /// Audits the artifact's tensor directory against an expected inventory.
1358    ///
1359    /// # Errors
1360    ///
1361    /// Returns the itemized [`ArtifactCensus`] when anything is missing, extra, or mis-declared.
1362    pub fn verify_census(&self, manifest: &ArtifactManifest) -> Result<(), Box<ArtifactCensus>> {
1363        let report = manifest.audit(self);
1364        if report.is_green() {
1365            Ok(())
1366        } else {
1367            Err(Box::new(report))
1368        }
1369    }
1370}
1371
1372/// One tensor the artifact is required to carry, and where.
1373///
1374/// Distinct from [`crate::census::ExpectedTensor`] on purpose, and the difference is not
1375/// incidental: that one audits an *upstream* checkpoint, so it speaks
1376/// [`crate::safetensors::Dtype`] (bf16/f32) and has no notion of sections. A `.fttsq` is a
1377/// **quantized** artifact, so its expectations are keyed on [`StoredDtype`] and additionally pin
1378/// the [`AccessClass`] — a tensor that lands in the wrong section still loads and still produces
1379/// correct audio, while quietly destroying the residency the whole optimization program depends on.
1380/// That failure is invisible to a dtype-and-shape census, which is exactly why this one exists.
1381#[derive(Clone, Debug, PartialEq, Eq)]
1382pub struct ExpectedArtifactTensor {
1383    /// Tensor name, exactly as the directory records it.
1384    pub name: String,
1385    /// Required logical shape.
1386    pub shape: Vec<u64>,
1387    /// Required storage dtype after quantization.
1388    pub dtype: StoredDtype,
1389    /// The access class whose section must hold it.
1390    pub access_class: AccessClass,
1391}
1392
1393/// One way an artifact diverged from its expected inventory.
1394#[derive(Clone, Debug, PartialEq, Eq)]
1395pub enum ArtifactFinding {
1396    /// A required tensor is absent. Certain failure downstream.
1397    Missing {
1398        /// The tensor.
1399        name: String,
1400    },
1401    /// The artifact carries a tensor the manifest does not list — the signature of a different
1402    /// checkpoint or a converter that changed its naming.
1403    Extra {
1404        /// The tensor.
1405        name: String,
1406    },
1407    /// Present, but not the shape we compiled kernels for.
1408    ShapeMismatch {
1409        /// The tensor.
1410        name: String,
1411        /// Expected shape.
1412        expected: Vec<u64>,
1413        /// Shape found.
1414        found: Vec<u64>,
1415    },
1416    /// Present, but quantized differently than the recipe says.
1417    DtypeMismatch {
1418        /// The tensor.
1419        name: String,
1420        /// Expected dtype.
1421        expected: StoredDtype,
1422        /// Dtype found.
1423        found: StoredDtype,
1424    },
1425    /// Present and correct, but filed under the wrong access class.
1426    ///
1427    /// The quiet one: audio stays correct while the page-in policy silently becomes wrong.
1428    WrongAccessClass {
1429        /// The tensor.
1430        name: String,
1431        /// Expected class.
1432        expected: AccessClass,
1433        /// Class found.
1434        found: AccessClass,
1435    },
1436    /// A tensor names a section the artifact does not declare.
1437    DanglingSection {
1438        /// The tensor.
1439        name: String,
1440        /// The section it named.
1441        section: String,
1442    },
1443}
1444
1445impl ArtifactFinding {
1446    /// The tensor this finding is about.
1447    #[must_use]
1448    pub fn tensor(&self) -> &str {
1449        match self {
1450            Self::Missing { name }
1451            | Self::Extra { name }
1452            | Self::ShapeMismatch { name, .. }
1453            | Self::DtypeMismatch { name, .. }
1454            | Self::WrongAccessClass { name, .. }
1455            | Self::DanglingSection { name, .. } => name,
1456        }
1457    }
1458
1459    /// Short class label, for counting findings by kind.
1460    #[must_use]
1461    pub const fn class(&self) -> &'static str {
1462        match self {
1463            Self::Missing { .. } => "missing",
1464            Self::Extra { .. } => "extra",
1465            Self::ShapeMismatch { .. } => "shape_mismatch",
1466            Self::DtypeMismatch { .. } => "dtype_mismatch",
1467            Self::WrongAccessClass { .. } => "wrong_access_class",
1468            Self::DanglingSection { .. } => "dangling_section",
1469        }
1470    }
1471}
1472
1473impl fmt::Display for ArtifactFinding {
1474    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1475        match self {
1476            Self::Missing { name } => write!(f, "MISSING       {name}"),
1477            Self::Extra { name } => write!(f, "EXTRA         {name}"),
1478            Self::ShapeMismatch {
1479                name,
1480                expected,
1481                found,
1482            } => write!(
1483                f,
1484                "SHAPE         {name}: expected {expected:?}, found {found:?}"
1485            ),
1486            Self::DtypeMismatch {
1487                name,
1488                expected,
1489                found,
1490            } => write!(
1491                f,
1492                "DTYPE         {name}: expected {expected}, found {found}"
1493            ),
1494            Self::WrongAccessClass {
1495                name,
1496                expected,
1497                found,
1498            } => write!(
1499                f,
1500                "ACCESS_CLASS  {name}: expected {expected}, found {found}"
1501            ),
1502            Self::DanglingSection { name, section } => {
1503                write!(
1504                    f,
1505                    "DANGLING      {name}: names undeclared section `{section}`"
1506                )
1507            }
1508        }
1509    }
1510}
1511
1512/// The expected tensor inventory for one artifact.
1513///
1514/// The **mechanism**, not the data: which tensors a `.fttsq` must carry, at which precision, comes
1515/// from the OQ-2 inventory crossed with the converter's per-tensor quantization policy. This module
1516/// does not hard-code that recipe, because it is per-artifact and measured.
1517#[derive(Clone, Debug, Default)]
1518pub struct ArtifactManifest {
1519    label: String,
1520    expected: Vec<ExpectedArtifactTensor>,
1521}
1522
1523impl ArtifactManifest {
1524    /// Starts a manifest with a human label used in the report header.
1525    #[must_use]
1526    pub fn new(label: impl Into<String>) -> Self {
1527        Self {
1528            label: label.into(),
1529            expected: Vec::new(),
1530        }
1531    }
1532
1533    /// Adds one expectation.
1534    #[must_use]
1535    pub fn expect(mut self, tensor: ExpectedArtifactTensor) -> Self {
1536        self.expected.push(tensor);
1537        self
1538    }
1539
1540    /// The label.
1541    #[must_use]
1542    pub fn label(&self) -> &str {
1543        &self.label
1544    }
1545
1546    /// How many tensors are expected.
1547    #[must_use]
1548    pub fn len(&self) -> usize {
1549        self.expected.len()
1550    }
1551
1552    /// Whether the manifest expects nothing.
1553    #[must_use]
1554    pub fn is_empty(&self) -> bool {
1555        self.expected.is_empty()
1556    }
1557
1558    /// Audits a reader against this manifest, reporting **every** divergence.
1559    ///
1560    /// Deliberately does not stop at the first finding: a converter bug usually produces a family
1561    /// of related divergences, and seeing all of them at once is the difference between one fix and
1562    /// twenty rounds of rerunning a multi-gigabyte conversion.
1563    #[must_use]
1564    pub fn audit(&self, reader: &FttsqReader) -> ArtifactCensus {
1565        let mut findings = Vec::new();
1566        let expected_names: BTreeMap<&str, &ExpectedArtifactTensor> = self
1567            .expected
1568            .iter()
1569            .map(|tensor| (tensor.name.as_str(), tensor))
1570            .collect();
1571
1572        for expectation in &self.expected {
1573            let Some(found) = reader.tensor(&expectation.name) else {
1574                findings.push(ArtifactFinding::Missing {
1575                    name: expectation.name.clone(),
1576                });
1577                continue;
1578            };
1579            if found.shape != expectation.shape {
1580                findings.push(ArtifactFinding::ShapeMismatch {
1581                    name: expectation.name.clone(),
1582                    expected: expectation.shape.clone(),
1583                    found: found.shape.clone(),
1584                });
1585            }
1586            if found.dtype != expectation.dtype {
1587                findings.push(ArtifactFinding::DtypeMismatch {
1588                    name: expectation.name.clone(),
1589                    expected: expectation.dtype,
1590                    found: found.dtype,
1591                });
1592            }
1593            match reader.section(&found.section) {
1594                Some(section) if section.access_class != expectation.access_class => {
1595                    findings.push(ArtifactFinding::WrongAccessClass {
1596                        name: expectation.name.clone(),
1597                        expected: expectation.access_class,
1598                        found: section.access_class,
1599                    });
1600                }
1601                Some(_) => {}
1602                None => findings.push(ArtifactFinding::DanglingSection {
1603                    name: expectation.name.clone(),
1604                    section: found.section.clone(),
1605                }),
1606            }
1607        }
1608
1609        for tensor in reader.tensors() {
1610            if !expected_names.contains_key(tensor.name.as_str()) {
1611                findings.push(ArtifactFinding::Extra {
1612                    name: tensor.name.clone(),
1613                });
1614            }
1615        }
1616
1617        ArtifactCensus {
1618            label: self.label.clone(),
1619            expected: self.expected.len(),
1620            found: reader.tensors().len(),
1621            findings,
1622        }
1623    }
1624}
1625
1626/// The itemized result of an artifact census.
1627#[derive(Clone, Debug)]
1628pub struct ArtifactCensus {
1629    label: String,
1630    expected: usize,
1631    found: usize,
1632    findings: Vec<ArtifactFinding>,
1633}
1634
1635impl ArtifactCensus {
1636    /// Whether the artifact matched its manifest exactly.
1637    #[must_use]
1638    pub fn is_green(&self) -> bool {
1639        self.findings.is_empty()
1640    }
1641
1642    /// Every divergence found.
1643    #[must_use]
1644    pub fn findings(&self) -> &[ArtifactFinding] {
1645        &self.findings
1646    }
1647
1648    /// How many findings of one class.
1649    #[must_use]
1650    pub fn count_of(&self, class: &str) -> usize {
1651        self.findings
1652            .iter()
1653            .filter(|finding| finding.class() == class)
1654            .count()
1655    }
1656
1657    /// A readable, itemized report.
1658    #[must_use]
1659    pub fn render(&self) -> String {
1660        let mut out = format!(
1661            "artifact census `{}`: expected {} tensors, artifact declares {} — {}\n",
1662            self.label,
1663            self.expected,
1664            self.found,
1665            if self.is_green() {
1666                "GREEN".to_owned()
1667            } else {
1668                format!("{} FINDINGS", self.findings.len())
1669            }
1670        );
1671        for finding in &self.findings {
1672            out.push_str(&format!("  {finding}\n"));
1673        }
1674        out
1675    }
1676}
1677
1678impl fmt::Display for ArtifactCensus {
1679    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1680        f.write_str(&self.render())
1681    }
1682}
1683
1684impl std::error::Error for ArtifactCensus {}
1685
1686fn required_str<'a>(value: Option<&'a Value>, path: &str) -> Result<&'a str, FttsqError> {
1687    value
1688        .and_then(Value::as_str)
1689        .filter(|text| !text.is_empty())
1690        .ok_or_else(|| FttsqError::Field {
1691            path: path.to_owned(),
1692            expected: "a non-empty string".to_owned(),
1693        })
1694}
1695
1696fn required_u64(value: Option<&Value>, path: &str) -> Result<u64, FttsqError> {
1697    value
1698        .and_then(Value::as_u64)
1699        .ok_or_else(|| FttsqError::Field {
1700            path: path.to_owned(),
1701            expected: "a non-negative integer".to_owned(),
1702        })
1703}
1704
1705fn parse_sections(value: Option<&Value>, file_len: u64) -> Result<Vec<SectionEntry>, FttsqError> {
1706    let array = value
1707        .and_then(Value::as_array)
1708        .ok_or_else(|| FttsqError::Field {
1709            path: "sections".to_owned(),
1710            expected: "an array".to_owned(),
1711        })?;
1712    if array.len() > MAX_SECTIONS {
1713        return Err(FttsqError::LimitExceeded {
1714            what: "section".to_owned(),
1715            found: array.len() as u64,
1716            limit: MAX_SECTIONS as u64,
1717        });
1718    }
1719
1720    let mut sections = Vec::with_capacity(array.len());
1721    let mut seen: BTreeMap<String, ()> = BTreeMap::new();
1722    for (index, entry) in array.iter().enumerate() {
1723        let path = |field: &str| format!("sections[{index}].{field}");
1724        let name = required_str(entry.get("name"), &path("name"))?.to_owned();
1725        if seen.insert(name.clone(), ()).is_some() {
1726            return Err(FttsqError::DuplicateName {
1727                what: "section".to_owned(),
1728                name,
1729            });
1730        }
1731        let class_text = required_str(entry.get("access_class"), &path("access_class"))?;
1732        let access_class =
1733            AccessClass::parse(class_text).ok_or_else(|| FttsqError::UnknownValue {
1734                path: path("access_class"),
1735                found: class_text.to_owned(),
1736            })?;
1737        let offset = required_u64(entry.get("offset"), &path("offset"))?;
1738        let length = required_u64(entry.get("length"), &path("length"))?;
1739        let sha256 = required_str(entry.get("sha256"), &path("sha256"))?.to_owned();
1740
1741        let end = offset
1742            .checked_add(length)
1743            .ok_or_else(|| FttsqError::RangeOutOfBounds {
1744                what: format!("section `{name}`"),
1745                offset,
1746                length,
1747                bound: file_len,
1748            })?;
1749        if end > file_len {
1750            return Err(FttsqError::RangeOutOfBounds {
1751                what: format!("section `{name}`"),
1752                offset,
1753                length,
1754                bound: file_len,
1755            });
1756        }
1757
1758        sections.push(SectionEntry {
1759            name,
1760            access_class,
1761            offset,
1762            length,
1763            sha256,
1764        });
1765    }
1766
1767    // Overlap is checked on a sorted copy so declaration order stays free.
1768    let mut ordered: Vec<&SectionEntry> = sections.iter().collect();
1769    ordered.sort_by_key(|section| section.offset);
1770    for pair in ordered.windows(2) {
1771        let (first, second) = (pair[0], pair[1]);
1772        let first_end = first.end().unwrap_or(u64::MAX);
1773        if first_end > second.offset {
1774            return Err(FttsqError::SectionOverlap {
1775                first: first.name.clone(),
1776                second: second.name.clone(),
1777            });
1778        }
1779    }
1780
1781    Ok(sections)
1782}
1783
1784fn parse_tensors(
1785    value: Option<&Value>,
1786    sections: &[SectionEntry],
1787    section_index: &BTreeMap<String, usize>,
1788) -> Result<Vec<TensorEntry>, FttsqError> {
1789    let array = value
1790        .and_then(Value::as_array)
1791        .ok_or_else(|| FttsqError::Field {
1792            path: "tensors".to_owned(),
1793            expected: "an array".to_owned(),
1794        })?;
1795    if array.len() > MAX_TENSORS {
1796        return Err(FttsqError::LimitExceeded {
1797            what: "tensor".to_owned(),
1798            found: array.len() as u64,
1799            limit: MAX_TENSORS as u64,
1800        });
1801    }
1802
1803    let mut tensors = Vec::with_capacity(array.len());
1804    let mut seen: BTreeMap<String, ()> = BTreeMap::new();
1805    for (index, entry) in array.iter().enumerate() {
1806        let path = |field: &str| format!("tensors[{index}].{field}");
1807        let name = required_str(entry.get("name"), &path("name"))?.to_owned();
1808        if seen.insert(name.clone(), ()).is_some() {
1809            return Err(FttsqError::DuplicateName {
1810                what: "tensor".to_owned(),
1811                name,
1812            });
1813        }
1814        let section = required_str(entry.get("section"), &path("section"))?.to_owned();
1815        let dtype_text = required_str(entry.get("dtype"), &path("dtype"))?;
1816        let dtype = StoredDtype::parse(dtype_text).ok_or_else(|| FttsqError::UnknownValue {
1817            path: path("dtype"),
1818            found: dtype_text.to_owned(),
1819        })?;
1820
1821        let shape_array = entry
1822            .get("shape")
1823            .and_then(Value::as_array)
1824            .ok_or_else(|| FttsqError::Field {
1825                path: path("shape"),
1826                expected: "an array".to_owned(),
1827            })?;
1828        if shape_array.len() > MAX_RANK {
1829            return Err(FttsqError::LimitExceeded {
1830                what: format!("tensor `{name}` rank"),
1831                found: shape_array.len() as u64,
1832                limit: MAX_RANK as u64,
1833            });
1834        }
1835        let mut shape = Vec::with_capacity(shape_array.len());
1836        for (axis, dim) in shape_array.iter().enumerate() {
1837            let dim = dim.as_u64().ok_or_else(|| FttsqError::Field {
1838                path: format!("{}[{axis}]", path("shape")),
1839                expected: "a non-negative integer".to_owned(),
1840            })?;
1841            if dim > MAX_DIM {
1842                return Err(FttsqError::LimitExceeded {
1843                    what: format!("tensor `{name}` dimension {axis}"),
1844                    found: dim,
1845                    limit: MAX_DIM,
1846                });
1847            }
1848            shape.push(dim);
1849        }
1850
1851        let offset = required_u64(entry.get("offset"), &path("offset"))?;
1852        let length = required_u64(entry.get("length"), &path("length"))?;
1853        let scales = entry
1854            .get("scales")
1855            .and_then(Value::as_str)
1856            .map(str::to_owned);
1857
1858        let tensor = TensorEntry {
1859            name,
1860            section,
1861            dtype,
1862            shape,
1863            offset,
1864            length,
1865            scales,
1866        };
1867
1868        // The declared length must equal what shape and dtype imply. Trusting the declared length
1869        // alone would let a directory hand out a window that does not match the tensor a kernel
1870        // then indexes by shape.
1871        let elements = tensor.elements().ok_or_else(|| FttsqError::LimitExceeded {
1872            what: format!("tensor `{}` element count", tensor.name),
1873            found: u64::MAX,
1874            limit: MAX_DIM,
1875        })?;
1876        let implied = dtype
1877            .storage_bytes(elements)
1878            .ok_or_else(|| FttsqError::LimitExceeded {
1879                what: format!("tensor `{}` storage size", tensor.name),
1880                found: u64::MAX,
1881                limit: MAX_DIM,
1882            })?;
1883        if implied != tensor.length {
1884            return Err(FttsqError::LengthMismatch {
1885                tensor: tensor.name.clone(),
1886                declared: tensor.length,
1887                implied,
1888            });
1889        }
1890
1891        let owner = section_index
1892            .get(&tensor.section)
1893            .and_then(|&index| sections.get(index))
1894            .ok_or_else(|| FttsqError::UnknownSection {
1895                tensor: tensor.name.clone(),
1896                section: tensor.section.clone(),
1897            })?;
1898        let end = tensor.offset.checked_add(tensor.length).ok_or_else(|| {
1899            FttsqError::RangeOutOfBounds {
1900                what: format!("tensor `{}`", tensor.name),
1901                offset: tensor.offset,
1902                length: tensor.length,
1903                bound: owner.length,
1904            }
1905        })?;
1906        if end > owner.length {
1907            return Err(FttsqError::RangeOutOfBounds {
1908                what: format!("tensor `{}`", tensor.name),
1909                offset: tensor.offset,
1910                length: tensor.length,
1911                bound: owner.length,
1912            });
1913        }
1914
1915        tensors.push(tensor);
1916    }
1917
1918    // Overlap within each section. Two tensors sharing bytes means one of them is wrong, and which
1919    // one is not knowable at read time — so both are refused.
1920    let mut by_section: BTreeMap<&str, Vec<&TensorEntry>> = BTreeMap::new();
1921    for tensor in &tensors {
1922        by_section
1923            .entry(tensor.section.as_str())
1924            .or_default()
1925            .push(tensor);
1926    }
1927    for group in by_section.values_mut() {
1928        group.sort_by_key(|tensor| tensor.offset);
1929        for pair in group.windows(2) {
1930            let (first, second) = (pair[0], pair[1]);
1931            let first_end = first.offset.saturating_add(first.length);
1932            if first_end > second.offset {
1933                return Err(FttsqError::TensorOverlap {
1934                    first: first.name.clone(),
1935                    second: second.name.clone(),
1936                });
1937            }
1938        }
1939    }
1940
1941    Ok(tensors)
1942}
1943
1944/// Metadata and fixed section lengths for a bounded `.fttsq` conversion.
1945///
1946/// A converter learns every tensor's shape, storage policy, section, and final byte length from
1947/// the validated input manifest before it starts payload conversion. That lets this plan reserve
1948/// the directory first, then stream one section at a time into a caller-owned seekable temporary
1949/// file. The writer never retains a payload section, and does not create, rename, or remove files:
1950/// the caller owns the atomic-temp-file policy around it.
1951#[derive(Debug)]
1952pub struct FttsqStreamPlan {
1953    model_family: String,
1954    source_sha256: String,
1955    license_notice: String,
1956    model_config: Value,
1957    quantization_manifest: Value,
1958    sections: Vec<(String, AccessClass, u64)>,
1959    tensors: Vec<TensorEntry>,
1960}
1961
1962impl FttsqStreamPlan {
1963    /// Starts a bounded conversion plan for one model family and source checkpoint.
1964    #[must_use]
1965    pub fn new(model_family: impl Into<String>, source_sha256: impl Into<String>) -> Self {
1966        Self {
1967            model_family: model_family.into(),
1968            source_sha256: source_sha256.into(),
1969            license_notice: String::new(),
1970            model_config: Value::Null,
1971            quantization_manifest: Value::Null,
1972            sections: Vec::new(),
1973            tensors: Vec::new(),
1974        }
1975    }
1976
1977    /// Sets the required Apache-2.0 §4 attribution notice.
1978    #[must_use]
1979    pub fn license_notice(mut self, notice: impl Into<String>) -> Self {
1980        self.license_notice = notice.into();
1981        self
1982    }
1983
1984    /// Attaches the frozen upstream model configuration.
1985    #[must_use]
1986    pub fn model_config(mut self, config: Value) -> Self {
1987        self.model_config = config;
1988        self
1989    }
1990
1991    /// Attaches the policy used to quantize each tensor.
1992    #[must_use]
1993    pub fn quantization_manifest(mut self, manifest: Value) -> Self {
1994        self.quantization_manifest = manifest;
1995        self
1996    }
1997
1998    /// Declares a section's final byte length before its bytes are streamed.
1999    #[must_use]
2000    pub fn section(
2001        mut self,
2002        name: impl Into<String>,
2003        access_class: AccessClass,
2004        length: u64,
2005    ) -> Self {
2006        self.sections.push((name.into(), access_class, length));
2007        self
2008    }
2009
2010    /// Declares a tensor located in one of the planned sections.
2011    #[must_use]
2012    pub fn tensor(mut self, tensor: TensorEntry) -> Self {
2013        self.tensors.push(tensor);
2014        self
2015    }
2016
2017    /// Writes the header and reserved directory into a caller-owned seekable stream.
2018    ///
2019    /// Payload sections must subsequently be supplied in declaration order through
2020    /// [`FttsqStreamingWriter::write_section`]. A caller that needs an atomic artifact should
2021    /// provide its own same-filesystem temporary file, call [`FttsqStreamingWriter::finish`],
2022    /// sync it, and rename it only after this method has finalized the directory.
2023    ///
2024    /// # Errors
2025    ///
2026    /// Refuses malformed planned metadata before writing any bytes, and names I/O failures from
2027    /// the caller's stream without assuming a path or filesystem policy.
2028    pub fn begin<W: std::io::Write + std::io::Seek>(
2029        self,
2030        mut writer: W,
2031    ) -> Result<FttsqStreamingWriter<W>, FttsqError> {
2032        if self.license_notice.trim().is_empty() {
2033            return Err(FttsqError::LicenseNoticeMissing);
2034        }
2035
2036        // The final SHA-256 strings are unknown until each section has streamed, but their exact
2037        // wire width is known. Filling that width now keeps the reserved directory large enough
2038        // for the finalized digests without retaining a single payload byte.
2039        let mut sections: Vec<SectionEntry> = self
2040            .sections
2041            .into_iter()
2042            .map(|(name, access_class, length)| SectionEntry {
2043                name,
2044                access_class,
2045                offset: 0,
2046                length,
2047                sha256: "0".repeat(64),
2048            })
2049            .collect();
2050        let mut probe_sections = sections.clone();
2051        for section in &mut probe_sections {
2052            // Twenty decimal digits are the widest valid offset. No section layout is required
2053            // for this pass, avoiding arithmetic at a fake near-`u64::MAX` payload start.
2054            section.offset = u64::MAX;
2055        }
2056        let probe = stream_directory_json(
2057            &self.model_family,
2058            &self.source_sha256,
2059            &self.license_notice,
2060            &self.model_config,
2061            &self.quantization_manifest,
2062            &probe_sections,
2063            &self.tensors,
2064        );
2065        let directory_len = serde_json::to_vec(&probe)
2066            .map_err(|error| FttsqError::DirectoryMalformed {
2067                detail: error.to_string(),
2068            })?
2069            .len() as u64;
2070        if directory_len > MAX_DIRECTORY_BYTES {
2071            return Err(FttsqError::DirectoryLength {
2072                declared: directory_len,
2073                limit: MAX_DIRECTORY_BYTES,
2074            });
2075        }
2076        let payload_start =
2077            HEADER_PREFIX_BYTES
2078                .checked_add(directory_len)
2079                .ok_or(FttsqError::DirectoryLength {
2080                    declared: directory_len,
2081                    limit: u64::MAX,
2082                })?;
2083        let final_file_len = layout_stream_sections(&mut sections, payload_start)?;
2084
2085        let directory = stream_directory_json(
2086            &self.model_family,
2087            &self.source_sha256,
2088            &self.license_notice,
2089            &self.model_config,
2090            &self.quantization_manifest,
2091            &sections,
2092            &self.tensors,
2093        );
2094        let mut directory_bytes =
2095            serde_json::to_vec(&directory).map_err(|error| FttsqError::DirectoryMalformed {
2096                detail: error.to_string(),
2097            })?;
2098        if directory_bytes.len() as u64 > directory_len {
2099            return Err(FttsqError::DirectoryLength {
2100                declared: directory_bytes.len() as u64,
2101                limit: directory_len,
2102            });
2103        }
2104        directory_bytes.resize(directory_len as usize, b' ');
2105
2106        let mut header_and_directory = Vec::with_capacity(
2107            (HEADER_PREFIX_BYTES as usize).saturating_add(directory_bytes.len()),
2108        );
2109        header_and_directory.extend_from_slice(MAGIC);
2110        header_and_directory.extend_from_slice(&FORMAT_VERSION.to_le_bytes());
2111        header_and_directory.extend_from_slice(&directory_len.to_le_bytes());
2112        header_and_directory.extend_from_slice(&directory_bytes);
2113
2114        // Validate the exact finalized offsets and tensor spans while their metadata is still
2115        // cheap. Digest verification deliberately waits for all streamed payload bytes.
2116        FttsqReader::parse_directory_for_file_len(&header_and_directory, final_file_len)?;
2117        writer
2118            .write_all(&header_and_directory)
2119            .map_err(|error| stream_io_error("write header and directory", &error))?;
2120
2121        let mut streaming = FttsqStreamingWriter {
2122            writer,
2123            model_family: self.model_family,
2124            source_sha256: self.source_sha256,
2125            license_notice: self.license_notice,
2126            model_config: self.model_config,
2127            quantization_manifest: self.quantization_manifest,
2128            sections,
2129            tensors: self.tensors,
2130            directory_len,
2131            current_section: 0,
2132            section_written: 0,
2133            section_hasher: Sha256::new(),
2134        };
2135        streaming.finalize_empty_sections();
2136        Ok(streaming)
2137    }
2138}
2139
2140/// A bounded writer for `.fttsq` section payloads.
2141///
2142/// The stream owns metadata plus one incremental SHA-256 state; it never owns a section payload.
2143/// This is intentionally lower-level than [`FttsqWriter`]: callers retain responsibility for the
2144/// surrounding atomic temporary-file creation, sync, and rename, while this type finalizes the
2145/// directory only after every declared byte and digest is complete.
2146#[derive(Debug)]
2147pub struct FttsqStreamingWriter<W> {
2148    writer: W,
2149    model_family: String,
2150    source_sha256: String,
2151    license_notice: String,
2152    model_config: Value,
2153    quantization_manifest: Value,
2154    sections: Vec<SectionEntry>,
2155    tensors: Vec<TensorEntry>,
2156    directory_len: u64,
2157    current_section: usize,
2158    section_written: u64,
2159    section_hasher: Sha256,
2160}
2161
2162impl<W: std::io::Write + std::io::Seek> FttsqStreamingWriter<W> {
2163    /// Appends bytes to the next declared section.
2164    ///
2165    /// Calls for a later section are refused rather than buffered. That ordering makes the
2166    /// converter's one-tensor-at-a-time memory bound mechanical: once a section is complete, its
2167    /// source mapping and tile buffers can be released before conversion continues.
2168    ///
2169    /// # Errors
2170    ///
2171    /// Returns a named refusal for out-of-order or overlong sections, or [`FttsqError::Io`] when
2172    /// the caller-owned stream cannot accept the bytes.
2173    pub fn write_section(&mut self, section: &str, bytes: &[u8]) -> Result<(), FttsqError> {
2174        let Some(entry) = self.sections.get(self.current_section) else {
2175            return Err(FttsqError::SectionWriteOutOfOrder {
2176                expected: None,
2177                actual: section.to_owned(),
2178            });
2179        };
2180        let expected = entry.name.clone();
2181        let declared = entry.length;
2182        if expected != section {
2183            return Err(FttsqError::SectionWriteOutOfOrder {
2184                expected: Some(expected),
2185                actual: section.to_owned(),
2186            });
2187        }
2188        let bytes_len = bytes.len() as u64;
2189        let attempted = self.section_written.checked_add(bytes_len).ok_or_else(|| {
2190            FttsqError::SectionLengthExceeded {
2191                section: expected.clone(),
2192                declared,
2193                attempted: u64::MAX,
2194            }
2195        })?;
2196        if attempted > declared {
2197            return Err(FttsqError::SectionLengthExceeded {
2198                section: expected,
2199                declared,
2200                attempted,
2201            });
2202        }
2203
2204        self.writer
2205            .write_all(bytes)
2206            .map_err(|error| stream_io_error("write section", &error))?;
2207        self.section_hasher.update(bytes);
2208        self.section_written = attempted;
2209        self.finalize_empty_sections();
2210        Ok(())
2211    }
2212
2213    /// Finalizes all completed section digests and rewrites the reserved directory in place.
2214    ///
2215    /// The returned stream is positioned at its end and flushed, ready for a file-owning caller to
2216    /// perform its durability and atomic-rename steps. A successful return guarantees that a
2217    /// complete byte buffer collected from the stream passes [`FttsqReader::open`].
2218    ///
2219    /// # Errors
2220    ///
2221    /// Refuses an incomplete declared section and names failures while seeking, finalizing, or
2222    /// flushing the caller-owned stream.
2223    pub fn finish(mut self) -> Result<W, FttsqError> {
2224        if let Some(section) = self.sections.get(self.current_section) {
2225            return Err(FttsqError::SectionIncomplete {
2226                section: section.name.clone(),
2227                declared: section.length,
2228                written: self.section_written,
2229            });
2230        }
2231
2232        let directory = stream_directory_json(
2233            &self.model_family,
2234            &self.source_sha256,
2235            &self.license_notice,
2236            &self.model_config,
2237            &self.quantization_manifest,
2238            &self.sections,
2239            &self.tensors,
2240        );
2241        let directory_bytes =
2242            serde_json::to_vec(&directory).map_err(|error| FttsqError::DirectoryMalformed {
2243                detail: error.to_string(),
2244            })?;
2245        if directory_bytes.len() as u64 > self.directory_len {
2246            return Err(FttsqError::DirectoryLength {
2247                declared: directory_bytes.len() as u64,
2248                limit: self.directory_len,
2249            });
2250        }
2251
2252        self.writer
2253            .seek(std::io::SeekFrom::Start(HEADER_PREFIX_BYTES))
2254            .map_err(|error| stream_io_error("seek to directory", &error))?;
2255        self.writer
2256            .write_all(&directory_bytes)
2257            .map_err(|error| stream_io_error("finalize directory", &error))?;
2258        write_space_padding(
2259            &mut self.writer,
2260            self.directory_len - directory_bytes.len() as u64,
2261        )?;
2262        self.writer
2263            .seek(std::io::SeekFrom::End(0))
2264            .map_err(|error| stream_io_error("seek to artifact end", &error))?;
2265        self.writer
2266            .flush()
2267            .map_err(|error| stream_io_error("flush finalized artifact", &error))?;
2268        Ok(self.writer)
2269    }
2270
2271    fn finalize_empty_sections(&mut self) {
2272        while let Some(section) = self.sections.get_mut(self.current_section) {
2273            if self.section_written != section.length {
2274                break;
2275            }
2276            section.sha256 = to_hex(&std::mem::take(&mut self.section_hasher).finish());
2277            self.current_section += 1;
2278            self.section_written = 0;
2279        }
2280    }
2281}
2282
2283fn layout_stream_sections(
2284    sections: &mut [SectionEntry],
2285    payload_start: u64,
2286) -> Result<u64, FttsqError> {
2287    let mut cursor = payload_start;
2288    for section in sections {
2289        section.offset = cursor;
2290        cursor =
2291            cursor
2292                .checked_add(section.length)
2293                .ok_or_else(|| FttsqError::RangeOutOfBounds {
2294                    what: format!("section `{}`", section.name),
2295                    offset: section.offset,
2296                    length: section.length,
2297                    bound: u64::MAX,
2298                })?;
2299    }
2300    Ok(cursor)
2301}
2302
2303fn stream_directory_json(
2304    model_family: &str,
2305    source_sha256: &str,
2306    license_notice: &str,
2307    model_config: &Value,
2308    quantization_manifest: &Value,
2309    sections: &[SectionEntry],
2310    tensors: &[TensorEntry],
2311) -> Value {
2312    let sections: Vec<Value> = sections
2313        .iter()
2314        .map(|section| {
2315            json!({
2316                "name": section.name,
2317                "access_class": section.access_class.as_str(),
2318                "offset": section.offset,
2319                "length": section.length,
2320                "sha256": section.sha256,
2321            })
2322        })
2323        .collect();
2324    let tensors: Vec<Value> = tensors
2325        .iter()
2326        .map(|tensor| {
2327            json!({
2328                "name": tensor.name,
2329                "section": tensor.section,
2330                "dtype": tensor.dtype.as_str(),
2331                "shape": tensor.shape,
2332                "offset": tensor.offset,
2333                "length": tensor.length,
2334                "scales": tensor.scales,
2335            })
2336        })
2337        .collect();
2338    json!({
2339        "format_version": FORMAT_VERSION,
2340        "model_family": model_family,
2341        "source_sha256": source_sha256,
2342        "license_notice": license_notice,
2343        "model_config": model_config,
2344        "quantization_manifest": quantization_manifest,
2345        "sections": sections,
2346        "tensors": tensors,
2347    })
2348}
2349
2350fn stream_io_error(operation: &str, error: &std::io::Error) -> FttsqError {
2351    FttsqError::Io {
2352        operation: operation.to_owned(),
2353        path: "<fttsq stream>".to_owned(),
2354        detail: error.to_string(),
2355    }
2356}
2357
2358fn write_space_padding<W: std::io::Write>(
2359    writer: &mut W,
2360    mut remaining: u64,
2361) -> Result<(), FttsqError> {
2362    const SPACES: [u8; 4096] = [b' '; 4096];
2363    while remaining > 0 {
2364        let count = remaining.min(SPACES.len() as u64) as usize;
2365        writer
2366            .write_all(&SPACES[..count])
2367            .map_err(|error| stream_io_error("pad finalized directory", &error))?;
2368        remaining -= count as u64;
2369    }
2370    Ok(())
2371}
2372
2373/// Builds a `.fttsq` artifact.
2374///
2375/// Sections are appended in the order given; the writer computes each digest and lays out absolute
2376/// offsets, so a caller cannot produce an artifact whose directory disagrees with its payload.
2377#[derive(Debug, Default)]
2378pub struct FttsqWriter {
2379    model_family: String,
2380    source_sha256: String,
2381    license_notice: String,
2382    model_config: Value,
2383    quantization_manifest: Value,
2384    sections: Vec<(SectionEntry, Vec<u8>)>,
2385    tensors: Vec<TensorEntry>,
2386}
2387
2388impl FttsqWriter {
2389    /// Starts an artifact for one model family, converted from a checkpoint with `source_sha256`.
2390    #[must_use]
2391    pub fn new(model_family: impl Into<String>, source_sha256: impl Into<String>) -> Self {
2392        Self {
2393            model_family: model_family.into(),
2394            source_sha256: source_sha256.into(),
2395            license_notice: String::new(),
2396            model_config: Value::Null,
2397            quantization_manifest: Value::Null,
2398            sections: Vec::new(),
2399            tensors: Vec::new(),
2400        }
2401    }
2402
2403    /// Sets the Apache-2.0 §4 attribution notice. Required — [`FttsqWriter::finish`] refuses without it.
2404    #[must_use]
2405    pub fn license_notice(mut self, notice: impl Into<String>) -> Self {
2406        self.license_notice = notice.into();
2407        self
2408    }
2409
2410    /// Attaches the frozen upstream model config.
2411    #[must_use]
2412    pub fn model_config(mut self, config: Value) -> Self {
2413        self.model_config = config;
2414        self
2415    }
2416
2417    /// Attaches the per-tensor quantization policy the license notice refers to.
2418    #[must_use]
2419    pub fn quantization_manifest(mut self, manifest: Value) -> Self {
2420        self.quantization_manifest = manifest;
2421        self
2422    }
2423
2424    /// Appends a section with its payload. Offset and digest are computed at [`FttsqWriter::finish`].
2425    #[must_use]
2426    pub fn section(
2427        mut self,
2428        name: impl Into<String>,
2429        access_class: AccessClass,
2430        payload: Vec<u8>,
2431    ) -> Self {
2432        let entry = SectionEntry {
2433            name: name.into(),
2434            access_class,
2435            offset: 0,
2436            length: payload.len() as u64,
2437            sha256: String::new(),
2438        };
2439        self.sections.push((entry, payload));
2440        self
2441    }
2442
2443    /// Declares a tensor located inside an already-added section.
2444    #[must_use]
2445    pub fn tensor(mut self, tensor: TensorEntry) -> Self {
2446        self.tensors.push(tensor);
2447        self
2448    }
2449
2450    /// Serializes the artifact.
2451    ///
2452    /// The result is re-parsed before being returned, so a writer bug surfaces here rather than as
2453    /// an unreadable multi-gigabyte file discovered hours later.
2454    ///
2455    /// # Errors
2456    ///
2457    /// Returns [`FttsqError::LicenseNoticeMissing`] without a notice, or whatever the validating
2458    /// re-parse rejects.
2459    pub fn finish(mut self) -> Result<Vec<u8>, FttsqError> {
2460        if self.license_notice.trim().is_empty() {
2461            return Err(FttsqError::LicenseNoticeMissing);
2462        }
2463
2464        for (entry, payload) in &mut self.sections {
2465            entry.length = payload.len() as u64;
2466            entry.sha256 = hex_digest(payload);
2467        }
2468
2469        // Two passes: the directory's size depends on the offsets, and the offsets depend on the
2470        // directory's size. Serialize once with placeholder offsets to learn the exact directory
2471        // length, then again with the real ones. The placeholder pass uses u64::MAX-width numbers
2472        // so the second directory can only be the same size or smaller — and we pad to match.
2473        let probe = self.directory_json(u64::MAX);
2474        let probe_len = serde_json::to_vec(&probe)
2475            .map_err(|error| FttsqError::DirectoryMalformed {
2476                detail: error.to_string(),
2477            })?
2478            .len() as u64;
2479
2480        let payload_start = HEADER_PREFIX_BYTES + probe_len;
2481        let directory = self.directory_json(payload_start);
2482        let mut directory_bytes =
2483            serde_json::to_vec(&directory).map_err(|error| FttsqError::DirectoryMalformed {
2484                detail: error.to_string(),
2485            })?;
2486        // Pad with trailing spaces so the directory occupies exactly `probe_len` bytes and the
2487        // offsets computed above stay correct. JSON tolerates trailing whitespace.
2488        while (directory_bytes.len() as u64) < probe_len {
2489            directory_bytes.push(b' ');
2490        }
2491
2492        let mut out = Vec::with_capacity(payload_start as usize);
2493        out.extend_from_slice(MAGIC);
2494        out.extend_from_slice(&FORMAT_VERSION.to_le_bytes());
2495        out.extend_from_slice(&(directory_bytes.len() as u64).to_le_bytes());
2496        out.extend_from_slice(&directory_bytes);
2497        for (_, payload) in &self.sections {
2498            out.extend_from_slice(payload);
2499        }
2500
2501        // Prove what we just wrote is readable, digests and all.
2502        FttsqReader::open(&out)?;
2503        Ok(out)
2504    }
2505
2506    /// Serializes and writes the artifact to `path` **atomically**.
2507    ///
2508    /// Writes to a temporary file beside the destination, fsyncs it, then renames over the target.
2509    /// A reader therefore observes either the previous artifact or the complete new one, never a
2510    /// half-written prefix — which matters because a truncated `.fttsq` is exactly the shape that
2511    /// digest verification would report as *corruption* rather than as an interrupted write.
2512    ///
2513    /// The temporary lives in the destination's own directory so the rename stays within one
2514    /// filesystem; `rename` across mount points is not atomic and would silently degrade to a copy.
2515    /// On failure the temporary is removed.
2516    ///
2517    /// # Errors
2518    ///
2519    /// Returns [`FttsqError::Io`] naming the operation and path, or whatever
2520    /// [`FttsqWriter::finish`] rejects.
2521    pub fn write_to_path(self, path: &std::path::Path) -> Result<(), FttsqError> {
2522        use std::io::Write as _;
2523
2524        let bytes = self.finish()?;
2525
2526        let parent = path.parent().unwrap_or_else(|| std::path::Path::new("."));
2527        // Unique per process so two concurrent converters cannot collide on the temporary.
2528        let file_name = path.file_name().map_or_else(
2529            || std::ffi::OsString::from("artifact.fttsq"),
2530            std::ffi::OsStr::to_os_string,
2531        );
2532        let mut temp_name = file_name;
2533        temp_name.push(format!(".tmp.{}", std::process::id()));
2534        let temp_path = parent.join(temp_name);
2535
2536        let io =
2537            |operation: &str, target: &std::path::Path, error: &std::io::Error| FttsqError::Io {
2538                operation: operation.to_owned(),
2539                path: target.display().to_string(),
2540                detail: error.to_string(),
2541            };
2542
2543        // Any failure past this point must not leave the temporary behind.
2544        let result = (|| -> Result<(), FttsqError> {
2545            let mut file = std::fs::File::create(&temp_path)
2546                .map_err(|error| io("create", &temp_path, &error))?;
2547            file.write_all(&bytes)
2548                .map_err(|error| io("write", &temp_path, &error))?;
2549            // fsync before rename: without it the rename can land while the data is still in the
2550            // page cache, so a crash leaves a correctly-named file full of zeros.
2551            file.sync_all()
2552                .map_err(|error| io("fsync", &temp_path, &error))?;
2553            drop(file);
2554            std::fs::rename(&temp_path, path).map_err(|error| io("rename", path, &error))
2555        })();
2556
2557        if result.is_err() {
2558            let _ = std::fs::remove_file(&temp_path);
2559        }
2560        result
2561    }
2562
2563    fn directory_json(&self, payload_start: u64) -> Value {
2564        let mut cursor = payload_start;
2565        let sections: Vec<Value> = self
2566            .sections
2567            .iter()
2568            .map(|(entry, _)| {
2569                let offset = cursor;
2570                // The probe pass begins at `u64::MAX` to reserve the widest possible decimal
2571                // offset. Saturation keeps that metadata-only calculation defined even for an
2572                // adversarially large in-memory construction; real artifact offsets below are
2573                // still computed from the actual payload start.
2574                cursor = cursor.saturating_add(entry.length);
2575                json!({
2576                    "name": entry.name,
2577                    "access_class": entry.access_class.as_str(),
2578                    "offset": offset,
2579                    "length": entry.length,
2580                    "sha256": entry.sha256,
2581                })
2582            })
2583            .collect();
2584
2585        let tensors: Vec<Value> = self
2586            .tensors
2587            .iter()
2588            .map(|tensor| {
2589                json!({
2590                    "name": tensor.name,
2591                    "section": tensor.section,
2592                    "dtype": tensor.dtype.as_str(),
2593                    "shape": tensor.shape,
2594                    "offset": tensor.offset,
2595                    "length": tensor.length,
2596                    "scales": tensor.scales,
2597                })
2598            })
2599            .collect();
2600
2601        json!({
2602            "format_version": FORMAT_VERSION,
2603            "model_family": self.model_family,
2604            "source_sha256": self.source_sha256,
2605            "license_notice": self.license_notice,
2606            "model_config": self.model_config,
2607            "quantization_manifest": self.quantization_manifest,
2608            "sections": sections,
2609            "tensors": tensors,
2610        })
2611    }
2612}
2613
2614#[cfg(test)]
2615mod tests {
2616    use super::*;
2617    use std::io::Cursor;
2618
2619    /// The §3 notice from `docs/LICENSE_AND_ATTRIBUTION.md`, abbreviated for tests.
2620    const NOTICE: &str = "Copyright 2026 Alibaba Cloud\nApache-2.0\nCHANGES: requantized to .fttsq";
2621
2622    fn artifact() -> Vec<u8> {
2623        FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "a".repeat(64))
2624            .license_notice(NOTICE)
2625            .model_config(json!({ "hidden_size": 1024 }))
2626            .quantization_manifest(json!({ "talker": "q8" }))
2627            .section(
2628                "microdecoder",
2629                AccessClass::HotRecurrentMicrodecoder,
2630                vec![7_u8; 64],
2631            )
2632            .section(
2633                "text_embedding",
2634                AccessClass::ColdTextEmbedding,
2635                vec![9_u8; 32],
2636            )
2637            .tensor(TensorEntry {
2638                name: "microdecoder.body".to_owned(),
2639                section: "microdecoder".to_owned(),
2640                dtype: StoredDtype::Q8,
2641                shape: vec![8, 8],
2642                offset: 0,
2643                length: 64,
2644                scales: Some("microdecoder.body.scales".to_owned()),
2645            })
2646            .tensor(TensorEntry {
2647                name: "text_embedding.weight".to_owned(),
2648                section: "text_embedding".to_owned(),
2649                dtype: StoredDtype::Bf16,
2650                shape: vec![4, 4],
2651                offset: 0,
2652                length: 32,
2653                scales: None,
2654            })
2655            .finish()
2656            .expect("the fixture artifact is writable")
2657    }
2658
2659    fn stream_plan() -> FttsqStreamPlan {
2660        FttsqStreamPlan::new("qwen3-tts-12hz-0.6b-base", "a".repeat(64))
2661            .license_notice(NOTICE)
2662            .model_config(json!({ "hidden_size": 1024 }))
2663            .quantization_manifest(json!({ "talker": "q8" }))
2664            .section("microdecoder", AccessClass::HotRecurrentMicrodecoder, 64)
2665            .section("text_embedding", AccessClass::ColdTextEmbedding, 32)
2666            .tensor(TensorEntry {
2667                name: "microdecoder.body".to_owned(),
2668                section: "microdecoder".to_owned(),
2669                dtype: StoredDtype::Q8,
2670                shape: vec![8, 8],
2671                offset: 0,
2672                length: 64,
2673                scales: Some("microdecoder.body.scales".to_owned()),
2674            })
2675            .tensor(TensorEntry {
2676                name: "text_embedding.weight".to_owned(),
2677                section: "text_embedding".to_owned(),
2678                dtype: StoredDtype::Bf16,
2679                shape: vec![4, 4],
2680                offset: 0,
2681                length: 32,
2682                scales: None,
2683            })
2684    }
2685
2686    fn streamed_artifact() -> Vec<u8> {
2687        let mut writer = stream_plan()
2688            .begin(Cursor::new(Vec::new()))
2689            .expect("the stream plan is structurally valid");
2690        writer
2691            .write_section("microdecoder", &[7_u8; 64])
2692            .expect("first section streams");
2693        writer
2694            .write_section("text_embedding", &[9_u8; 32])
2695            .expect("second section streams");
2696        writer
2697            .finish()
2698            .expect("complete stream finalizes")
2699            .into_inner()
2700    }
2701
2702    #[test]
2703    fn streaming_writer_is_canonical_and_never_retains_section_payloads() {
2704        // The buffered writer is only a small-fixture oracle here. The stream receives two
2705        // independent borrowed sections and must produce identical canonical bytes, including
2706        // directory offsets and digests, without taking ownership of either payload.
2707        let bytes = streamed_artifact();
2708        assert_eq!(bytes, artifact());
2709        let reader = FttsqReader::open(&bytes).expect("finalized stream verifies");
2710        assert_eq!(
2711            reader
2712                .tensor_bytes("microdecoder.body", &bytes)
2713                .expect("streamed tensor resolves"),
2714            &[7_u8; 64]
2715        );
2716    }
2717
2718    #[test]
2719    fn streaming_writer_refuses_out_of_order_or_incomplete_sections() {
2720        let mut writer = stream_plan()
2721            .begin(Cursor::new(Vec::new()))
2722            .expect("the stream plan is structurally valid");
2723        assert_eq!(
2724            writer
2725                .write_section("text_embedding", &[9_u8; 32])
2726                .expect_err("later sections cannot be buffered"),
2727            FttsqError::SectionWriteOutOfOrder {
2728                expected: Some("microdecoder".to_owned()),
2729                actual: "text_embedding".to_owned(),
2730            }
2731        );
2732        writer
2733            .write_section("microdecoder", &[7_u8; 63])
2734            .expect("a bounded partial chunk is accepted");
2735        assert_eq!(
2736            writer
2737                .finish()
2738                .expect_err("a partial section cannot acquire a digest"),
2739            FttsqError::SectionIncomplete {
2740                section: "microdecoder".to_owned(),
2741                declared: 64,
2742                written: 63,
2743            }
2744        );
2745    }
2746
2747    #[test]
2748    fn round_trips_through_write_and_read() {
2749        let bytes = artifact();
2750        let reader =
2751            FttsqReader::open(&bytes).expect("the artifact we just wrote must be readable");
2752
2753        assert_eq!(reader.format_version(), FORMAT_VERSION);
2754        assert_eq!(reader.model_family(), "qwen3-tts-12hz-0.6b-base");
2755        assert!(reader.license_notice().contains("Alibaba Cloud"));
2756        assert_eq!(reader.model_config()["hidden_size"], 1024);
2757        assert_eq!(reader.sections().len(), 2);
2758        assert_eq!(reader.tensors().len(), 2);
2759
2760        // Tensor payloads must come back byte-identical, through the section indirection.
2761        assert_eq!(
2762            reader
2763                .tensor_bytes("microdecoder.body", &bytes)
2764                .expect("tensor resolves"),
2765            &vec![7_u8; 64][..]
2766        );
2767        assert_eq!(
2768            reader
2769                .tensor_bytes("text_embedding.weight", &bytes)
2770                .expect("tensor resolves"),
2771            &vec![9_u8; 32][..]
2772        );
2773    }
2774
2775    #[test]
2776    fn bf16_payload_is_byte_identical_across_the_round_trip() {
2777        // Verbatim BF16 carriage is the property the converter's parity argument rests on: if the
2778        // container perturbs a single byte, every downstream parity claim is about the wrong bytes.
2779        let payload: Vec<u8> = (0..=255_u8).cycle().take(4096).collect();
2780        let bytes = FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "b".repeat(64))
2781            .license_notice(NOTICE)
2782            .section("talker", AccessClass::HotRecurrentTalker, payload.clone())
2783            .tensor(TensorEntry {
2784                name: "talker.weight".to_owned(),
2785                section: "talker".to_owned(),
2786                dtype: StoredDtype::Bf16,
2787                shape: vec![64, 32],
2788                offset: 0,
2789                length: 4096,
2790                scales: None,
2791            })
2792            .finish()
2793            .expect("writable");
2794        let reader = FttsqReader::open(&bytes).expect("readable");
2795        assert_eq!(
2796            reader
2797                .tensor_bytes("talker.weight", &bytes)
2798                .expect("resolves"),
2799            &payload[..]
2800        );
2801    }
2802
2803    #[test]
2804    fn access_classes_drive_the_page_in_policy() {
2805        let bytes = artifact();
2806        let reader = FttsqReader::open(&bytes).expect("readable");
2807
2808        let hot = reader.sections_in_class(AccessClass::HotRecurrentMicrodecoder);
2809        assert_eq!(hot.len(), 1);
2810        assert!(hot[0].access_class.is_hot());
2811        assert!(!hot[0].access_class.is_row_granular());
2812
2813        let cold = reader.sections_in_class(AccessClass::ColdTextEmbedding);
2814        assert_eq!(cold.len(), 1);
2815        assert!(
2816            !cold[0].access_class.is_hot(),
2817            "the 622 MB embedding must never be advised resident"
2818        );
2819        assert!(
2820            cold[0].access_class.is_row_granular(),
2821            "the cold embedding is accessed a row at a time, never as a unit"
2822        );
2823    }
2824
2825    #[test]
2826    fn a_newer_format_version_is_refused_rather_than_guessed_at() {
2827        let mut bytes = artifact();
2828        bytes[8..12].copy_from_slice(&(FORMAT_VERSION + 1).to_le_bytes());
2829        let error = FttsqReader::parse_directory(&bytes).expect_err("must refuse");
2830        assert_eq!(
2831            error,
2832            FttsqError::UnsupportedVersion {
2833                found: FORMAT_VERSION + 1,
2834                supported: FORMAT_VERSION,
2835            }
2836        );
2837    }
2838
2839    #[test]
2840    fn bad_magic_and_truncation_are_named_refusals() {
2841        assert!(matches!(
2842            FttsqReader::parse_directory(&[]),
2843            Err(FttsqError::TooShort { .. })
2844        ));
2845        let mut bytes = artifact();
2846        bytes[0] = b'X';
2847        assert!(matches!(
2848            FttsqReader::parse_directory(&bytes),
2849            Err(FttsqError::BadMagic { .. })
2850        ));
2851    }
2852
2853    #[test]
2854    fn a_truncated_file_never_yields_a_partial_load() {
2855        let full = artifact();
2856        // Cut into the payload: the directory still parses, but a section runs past the end.
2857        for cut in [full.len() - 1, full.len() - 40, full.len() - 90] {
2858            let error = FttsqReader::open(&full[..cut]).expect_err("truncation must be refused");
2859            assert!(
2860                matches!(
2861                    error,
2862                    FttsqError::RangeOutOfBounds { .. } | FttsqError::DirectoryLength { .. }
2863                ),
2864                "unexpected error for cut at {cut}: {error}"
2865            );
2866        }
2867    }
2868
2869    #[test]
2870    fn a_single_flipped_payload_bit_fails_digest_verification() {
2871        let mut bytes = artifact();
2872        let last = bytes.len() - 1;
2873        bytes[last] ^= 0x01;
2874        let error = FttsqReader::open(&bytes).expect_err("a bit flip must be caught");
2875        assert!(
2876            matches!(
2877                &error,
2878                FttsqError::DigestMismatch { section, .. } if section == "text_embedding"
2879            ),
2880            "expected a digest mismatch for text_embedding, got {error}"
2881        );
2882        // Structure alone still parses — which is exactly why the digest gate has to exist.
2883        assert!(FttsqReader::parse_directory(&bytes).is_ok());
2884    }
2885
2886    #[test]
2887    fn a_hostile_directory_length_cannot_provoke_a_huge_read() {
2888        let mut bytes = artifact();
2889        bytes[12..20].copy_from_slice(&u64::MAX.to_le_bytes());
2890        let error = FttsqReader::parse_directory(&bytes).expect_err("must refuse");
2891        assert!(matches!(error, FttsqError::DirectoryLength { .. }));
2892    }
2893
2894    /// Directory-level defects, each of which would otherwise become a bad read at runtime.
2895    #[test]
2896    fn structural_violations_are_each_refused_by_name() {
2897        type StructuralCase = (&'static str, Value, fn(&FttsqError) -> bool);
2898        let cases: Vec<StructuralCase> = vec![
2899            (
2900                "overlapping sections",
2901                json!([
2902                    {"name": "a", "access_class": "METADATA", "offset": 100, "length": 50, "sha256": "x"},
2903                    {"name": "b", "access_class": "METADATA", "offset": 120, "length": 10, "sha256": "x"},
2904                ]),
2905                |e| matches!(e, FttsqError::SectionOverlap { .. }),
2906            ),
2907            (
2908                "a section running past the file",
2909                json!([
2910                    {"name": "a", "access_class": "METADATA", "offset": 100, "length": u64::MAX, "sha256": "x"},
2911                ]),
2912                |e| matches!(e, FttsqError::RangeOutOfBounds { .. }),
2913            ),
2914            (
2915                "a duplicate section name",
2916                json!([
2917                    {"name": "a", "access_class": "METADATA", "offset": 100, "length": 10, "sha256": "x"},
2918                    {"name": "a", "access_class": "METADATA", "offset": 200, "length": 10, "sha256": "x"},
2919                ]),
2920                |e| matches!(e, FttsqError::DuplicateName { .. }),
2921            ),
2922            (
2923                "an unknown access class",
2924                json!([
2925                    {"name": "a", "access_class": "PROBABLY_HOT", "offset": 100, "length": 10, "sha256": "x"},
2926                ]),
2927                |e| matches!(e, FttsqError::UnknownValue { .. }),
2928            ),
2929        ];
2930
2931        for (description, sections, matches_expected) in cases {
2932            let error = parse_sections(Some(&sections), 4096)
2933                .expect_err(&format!("`{description}` must be refused"));
2934            assert!(
2935                matches_expected(&error),
2936                "`{description}` produced the wrong error: {error}"
2937            );
2938        }
2939    }
2940
2941    #[test]
2942    fn a_tensor_whose_length_disagrees_with_its_shape_is_refused() {
2943        let sections = vec![SectionEntry {
2944            name: "s".to_owned(),
2945            access_class: AccessClass::Metadata,
2946            offset: 0,
2947            length: 4096,
2948            sha256: String::new(),
2949        }];
2950        let index: BTreeMap<String, usize> = [("s".to_owned(), 0)].into_iter().collect();
2951
2952        // 8x8 bf16 is 128 bytes, not 64.
2953        let tensors = json!([
2954            {"name": "t", "section": "s", "dtype": "bf16", "shape": [8, 8], "offset": 0, "length": 64},
2955        ]);
2956        let error = parse_tensors(Some(&tensors), &sections, &index).expect_err("must refuse");
2957        assert_eq!(
2958            error,
2959            FttsqError::LengthMismatch {
2960                tensor: "t".to_owned(),
2961                declared: 64,
2962                implied: 128,
2963            }
2964        );
2965    }
2966
2967    #[test]
2968    fn tensors_may_not_overlap_within_a_section() {
2969        let sections = vec![SectionEntry {
2970            name: "s".to_owned(),
2971            access_class: AccessClass::Metadata,
2972            offset: 0,
2973            length: 4096,
2974            sha256: String::new(),
2975        }];
2976        let index: BTreeMap<String, usize> = [("s".to_owned(), 0)].into_iter().collect();
2977        let tensors = json!([
2978            {"name": "a", "section": "s", "dtype": "q8", "shape": [64], "offset": 0, "length": 64},
2979            {"name": "b", "section": "s", "dtype": "q8", "shape": [64], "offset": 32, "length": 64},
2980        ]);
2981        let error = parse_tensors(Some(&tensors), &sections, &index).expect_err("must refuse");
2982        assert!(matches!(error, FttsqError::TensorOverlap { .. }), "{error}");
2983    }
2984
2985    #[test]
2986    fn a_tensor_leaving_its_section_is_refused() {
2987        let sections = vec![SectionEntry {
2988            name: "s".to_owned(),
2989            access_class: AccessClass::Metadata,
2990            offset: 0,
2991            length: 64,
2992            sha256: String::new(),
2993        }];
2994        let index: BTreeMap<String, usize> = [("s".to_owned(), 0)].into_iter().collect();
2995        let tensors = json!([
2996            {"name": "a", "section": "s", "dtype": "q8", "shape": [64], "offset": 32, "length": 64},
2997        ]);
2998        let error = parse_tensors(Some(&tensors), &sections, &index).expect_err("must refuse");
2999        assert!(
3000            matches!(error, FttsqError::RangeOutOfBounds { .. }),
3001            "{error}"
3002        );
3003    }
3004
3005    #[test]
3006    fn an_artifact_without_a_license_notice_cannot_be_written_or_read() {
3007        // Writer side.
3008        let error = FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "c".repeat(64))
3009            .section("m", AccessClass::Metadata, vec![1, 2, 3])
3010            .finish()
3011            .expect_err("Apache-2.0 §4 makes the notice mandatory");
3012        assert_eq!(error, FttsqError::LicenseNoticeMissing);
3013
3014        // Reader side: an artifact whose notice was stripped after the fact is still refused.
3015        let mut bytes = artifact();
3016        let directory_len = u64::from_le_bytes(bytes[12..20].try_into().expect("header length"));
3017        let directory_start = HEADER_PREFIX_BYTES as usize;
3018        let directory_end = directory_start + directory_len as usize;
3019        let mut directory: Value = serde_json::from_slice(&bytes[directory_start..directory_end])
3020            .expect("fixture directory");
3021        directory["license_notice"] = Value::String(String::new());
3022        let mut replacement = serde_json::to_vec(&directory).expect("serializes directory");
3023        assert!(
3024            replacement.len() <= directory_len as usize,
3025            "removing a notice cannot grow it"
3026        );
3027        replacement.resize(directory_len as usize, b' ');
3028        bytes[directory_start..directory_end].copy_from_slice(&replacement);
3029        assert_eq!(
3030            FttsqReader::open(&bytes).expect_err("must refuse a missing notice"),
3031            FttsqError::LicenseNoticeMissing
3032        );
3033    }
3034
3035    #[test]
3036    fn write_to_path_lands_a_complete_readable_artifact_and_leaves_no_temporary() {
3037        let dir = std::env::temp_dir().join(format!("ftts-fttsq-write-{}", std::process::id()));
3038        std::fs::create_dir_all(&dir).expect("scratch dir");
3039        let path = dir.join("model.fttsq");
3040
3041        FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "d".repeat(64))
3042            .license_notice(NOTICE)
3043            .section("m", AccessClass::HotRecurrentMicrodecoder, vec![3_u8; 128])
3044            .section(
3045                "embedding",
3046                AccessClass::ColdTextEmbedding,
3047                vec![9_u8; 8192],
3048            )
3049            .tensor(TensorEntry {
3050                name: "m.w".to_owned(),
3051                section: "m".to_owned(),
3052                dtype: StoredDtype::Q8,
3053                shape: vec![128],
3054                offset: 0,
3055                length: 128,
3056                scales: None,
3057            })
3058            .tensor(TensorEntry {
3059                name: "embedding.one_row".to_owned(),
3060                section: "embedding".to_owned(),
3061                dtype: StoredDtype::Q8,
3062                shape: vec![32],
3063                offset: 4096,
3064                length: 32,
3065                scales: None,
3066            })
3067            .write_to_path(&path)
3068            .expect("artifact is writable");
3069
3070        let bytes = std::fs::read(&path).expect("artifact is readable");
3071        let reader = FttsqReader::open(&bytes).expect("what landed on disk must verify");
3072        assert_eq!(
3073            reader.tensor_bytes("m.w", &bytes).expect("resolves"),
3074            &vec![3_u8; 128][..]
3075        );
3076
3077        let mapped = MappedFttsq::open(&path).expect("mapped artifact validates");
3078        assert_eq!(mapped.len(), bytes.len());
3079        assert_eq!(
3080            mapped
3081                .tensor_bytes("embedding.one_row")
3082                .expect("row range resolves without copying the section"),
3083            &vec![9_u8; 32][..]
3084        );
3085
3086        let micro = mapped
3087            .page_advice()
3088            .iter()
3089            .find(|application| application.section == "m")
3090            .expect("microdecoder application is recorded");
3091        assert_eq!(micro.policy, PagePolicy::Resident);
3092        assert_eq!(micro.requested, Some(MemoryAdvice::WillNeed));
3093        assert!(
3094            !matches!(micro.outcome, PageAdviceOutcome::Failed(_)),
3095            "a valid mapped microdecoder section must receive a usable advice result: {micro:?}"
3096        );
3097
3098        let embedding = mapped
3099            .page_advice()
3100            .iter()
3101            .find(|application| application.section == "embedding")
3102            .expect("embedding application is recorded");
3103        assert_eq!(embedding.policy, PagePolicy::LazyRowGranular);
3104        assert_eq!(embedding.requested, Some(MemoryAdvice::Random));
3105        assert!(
3106            !embedding.policy.may_prefetch(),
3107            "the cold embedding policy must make wholesale prefetch impossible"
3108        );
3109        for observation in [&embedding.residency_before, &embedding.residency_after] {
3110            match observation {
3111                PageResidencyOutcome::Measured {
3112                    resident_pages,
3113                    total_pages,
3114                } => assert!(
3115                    resident_pages <= total_pages,
3116                    "the OQ-18 residency measurement exceeded the section's page span"
3117                ),
3118                PageResidencyOutcome::Unsupported => {}
3119                PageResidencyOutcome::Failed(detail) => {
3120                    panic!("the cold embedding residency measurement failed: {detail}");
3121                }
3122            }
3123        }
3124        assert!(
3125            mapped.page_advice().iter().all(|application| {
3126                application.policy.may_prefetch()
3127                    || application.requested != Some(MemoryAdvice::WillNeed)
3128            }),
3129            "a non-prefetch section was routed to MADV_WILLNEED"
3130        );
3131
3132        // The temporary must not survive a successful write.
3133        let strays: Vec<_> = std::fs::read_dir(&dir)
3134            .expect("dir is listable")
3135            .filter_map(Result::ok)
3136            .map(|entry| entry.file_name().to_string_lossy().into_owned())
3137            .filter(|name| name.contains(".tmp."))
3138            .collect();
3139        assert!(strays.is_empty(), "temporary files left behind: {strays:?}");
3140
3141        std::fs::remove_file(&path).expect("cleanup");
3142    }
3143
3144    #[test]
3145    fn write_to_path_refuses_before_touching_the_filesystem_when_the_notice_is_missing() {
3146        let dir = std::env::temp_dir().join(format!("ftts-fttsq-refuse-{}", std::process::id()));
3147        std::fs::create_dir_all(&dir).expect("scratch dir");
3148        let path = dir.join("model.fttsq");
3149
3150        let error = FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "e".repeat(64))
3151            .section("m", AccessClass::Metadata, vec![1, 2, 3])
3152            .write_to_path(&path)
3153            .expect_err("a notice-less artifact must never reach disk");
3154        assert_eq!(error, FttsqError::LicenseNoticeMissing);
3155        assert!(
3156            !path.exists(),
3157            "a refused artifact must not leave a file behind"
3158        );
3159    }
3160
3161    /// The invariant the whole page-in policy exists to enforce.
3162    #[test]
3163    fn the_cold_text_embedding_is_never_prefetched_and_hot_classes_always_are() {
3164        assert_eq!(
3165            AccessClass::ColdTextEmbedding.page_policy(),
3166            PagePolicy::LazyRowGranular
3167        );
3168        assert!(
3169            !AccessClass::ColdTextEmbedding.page_policy().may_prefetch(),
3170            "MADV_WILLNEED over the ~622 MB embedding would evict the microdecoder pack"
3171        );
3172
3173        for hot in [
3174            AccessClass::HotRecurrentMicrodecoder,
3175            AccessClass::HotRecurrentTalker,
3176            AccessClass::HotCodecDecoder,
3177        ] {
3178            assert_eq!(hot.page_policy(), PagePolicy::Resident);
3179            assert!(hot.page_policy().may_prefetch());
3180        }
3181        for cold in [
3182            AccessClass::EnrollmentSpeakerEncoder,
3183            AccessClass::EnrollmentCodecEncoder,
3184            AccessClass::Metadata,
3185        ] {
3186            assert_eq!(cold.page_policy(), PagePolicy::OnDemand);
3187            assert!(!cold.page_policy().may_prefetch());
3188        }
3189
3190        // is_hot() and the policy must not be able to disagree — two encodings of one fact.
3191        for class in [
3192            AccessClass::HotRecurrentMicrodecoder,
3193            AccessClass::HotRecurrentTalker,
3194            AccessClass::HotCodecDecoder,
3195            AccessClass::ColdTextEmbedding,
3196            AccessClass::EnrollmentSpeakerEncoder,
3197            AccessClass::EnrollmentCodecEncoder,
3198            AccessClass::Metadata,
3199        ] {
3200            assert_eq!(
3201                class.is_hot(),
3202                class.page_policy().may_prefetch(),
3203                "is_hot() and page_policy() disagree for {class}"
3204            );
3205            assert_eq!(
3206                class.is_row_granular(),
3207                class.page_policy() == PagePolicy::LazyRowGranular,
3208                "is_row_granular() and page_policy() disagree for {class}"
3209            );
3210        }
3211    }
3212
3213    #[test]
3214    fn the_page_in_plan_prefetches_the_microdecoder_before_the_larger_talker() {
3215        // Sizes stand in for the real ~110 MB pack and ~440 MB talker.
3216        let bytes = FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "f".repeat(64))
3217            .license_notice(NOTICE)
3218            .section("talker", AccessClass::HotRecurrentTalker, vec![1_u8; 400])
3219            .section("embedding", AccessClass::ColdTextEmbedding, vec![2_u8; 900])
3220            .section(
3221                "micro",
3222                AccessClass::HotRecurrentMicrodecoder,
3223                vec![3_u8; 100],
3224            )
3225            .section("meta", AccessClass::Metadata, vec![4_u8; 8])
3226            .finish()
3227            .expect("writable");
3228        let reader = FttsqReader::open(&bytes).expect("readable");
3229
3230        let plan = reader.page_in_plan();
3231        let order: Vec<&str> = plan
3232            .iter()
3233            .map(|(section, _)| section.name.as_str())
3234            .collect();
3235        assert_eq!(
3236            order,
3237            vec!["micro", "talker", "embedding", "meta"],
3238            "resident sections first, smallest first, so the 15x-reread pack wins the cache race"
3239        );
3240        assert_eq!(plan[0].1, PagePolicy::Resident);
3241        assert_eq!(plan[2].1, PagePolicy::LazyRowGranular);
3242        assert_eq!(plan[3].1, PagePolicy::OnDemand);
3243
3244        // Nothing outside the resident group may ever be prefetched.
3245        for (section, policy) in &plan {
3246            assert_eq!(
3247                policy.may_prefetch(),
3248                section.access_class.is_hot(),
3249                "section `{}` would be prefetched against policy",
3250                section.name
3251            );
3252        }
3253    }
3254
3255    fn census_fixture() -> (Vec<u8>, ArtifactManifest) {
3256        let bytes = FttsqWriter::new("qwen3-tts-12hz-0.6b-base", "g".repeat(64))
3257            .license_notice(NOTICE)
3258            .section(
3259                "micro",
3260                AccessClass::HotRecurrentMicrodecoder,
3261                vec![1_u8; 64],
3262            )
3263            .section("embedding", AccessClass::ColdTextEmbedding, vec![2_u8; 32])
3264            .tensor(TensorEntry {
3265                name: "micro.body".to_owned(),
3266                section: "micro".to_owned(),
3267                dtype: StoredDtype::Q8,
3268                shape: vec![8, 8],
3269                offset: 0,
3270                length: 64,
3271                scales: None,
3272            })
3273            .tensor(TensorEntry {
3274                name: "text_embedding.weight".to_owned(),
3275                section: "embedding".to_owned(),
3276                dtype: StoredDtype::Bf16,
3277                shape: vec![4, 4],
3278                offset: 0,
3279                length: 32,
3280                scales: None,
3281            })
3282            .finish()
3283            .expect("writable");
3284
3285        let manifest = ArtifactManifest::new("qwen3-tts pinned")
3286            .expect(ExpectedArtifactTensor {
3287                name: "micro.body".to_owned(),
3288                shape: vec![8, 8],
3289                dtype: StoredDtype::Q8,
3290                access_class: AccessClass::HotRecurrentMicrodecoder,
3291            })
3292            .expect(ExpectedArtifactTensor {
3293                name: "text_embedding.weight".to_owned(),
3294                shape: vec![4, 4],
3295                dtype: StoredDtype::Bf16,
3296                access_class: AccessClass::ColdTextEmbedding,
3297            });
3298        (bytes, manifest)
3299    }
3300
3301    #[test]
3302    fn a_matching_artifact_passes_its_census() {
3303        let (bytes, manifest) = census_fixture();
3304        let reader = FttsqReader::open(&bytes).expect("readable");
3305        let report = manifest.audit(&reader);
3306        assert!(report.is_green(), "{}", report.render());
3307        assert!(reader.verify_census(&manifest).is_ok());
3308    }
3309
3310    /// Each divergence class must be caught, and all of them reported in one pass.
3311    #[test]
3312    fn the_census_names_every_divergence_class_in_one_pass() {
3313        let (bytes, _) = census_fixture();
3314        let reader = FttsqReader::open(&bytes).expect("readable");
3315
3316        let manifest = ArtifactManifest::new("deliberately wrong")
3317            // Right name, wrong shape AND wrong dtype: both must be reported, not just the first.
3318            .expect(ExpectedArtifactTensor {
3319                name: "micro.body".to_owned(),
3320                shape: vec![16, 4],
3321                dtype: StoredDtype::Q4,
3322                access_class: AccessClass::HotRecurrentMicrodecoder,
3323            })
3324            // Correct in every respect except where it lives — the silent one.
3325            .expect(ExpectedArtifactTensor {
3326                name: "text_embedding.weight".to_owned(),
3327                shape: vec![4, 4],
3328                dtype: StoredDtype::Bf16,
3329                access_class: AccessClass::HotRecurrentTalker,
3330            })
3331            // Required but absent.
3332            .expect(ExpectedArtifactTensor {
3333                name: "codec.decoder.weight".to_owned(),
3334                shape: vec![2],
3335                dtype: StoredDtype::Q8,
3336                access_class: AccessClass::HotCodecDecoder,
3337            });
3338
3339        let report = manifest.audit(&reader);
3340        assert!(!report.is_green());
3341        assert_eq!(report.count_of("shape_mismatch"), 1, "{}", report.render());
3342        assert_eq!(report.count_of("dtype_mismatch"), 1, "{}", report.render());
3343        assert_eq!(
3344            report.count_of("wrong_access_class"),
3345            1,
3346            "a tensor in the wrong access class still produces correct audio while destroying \
3347             residency — the census is the only thing that catches it:\n{}",
3348            report.render()
3349        );
3350        assert_eq!(report.count_of("missing"), 1, "{}", report.render());
3351
3352        let rendered = report.render();
3353        for expected in [
3354            "micro.body",
3355            "text_embedding.weight",
3356            "codec.decoder.weight",
3357            "ACCESS_CLASS",
3358            "SHAPE",
3359            "DTYPE",
3360            "MISSING",
3361        ] {
3362            assert!(
3363                rendered.contains(expected),
3364                "census report is missing `{expected}`:\n{rendered}"
3365            );
3366        }
3367
3368        assert!(reader.verify_census(&manifest).is_err());
3369    }
3370
3371    /// An artifact carrying tensors nobody expected is a *different checkpoint*.
3372    #[test]
3373    fn unexpected_tensors_are_reported_as_extra() {
3374        let (bytes, _) = census_fixture();
3375        let reader = FttsqReader::open(&bytes).expect("readable");
3376        let manifest = ArtifactManifest::new("partial").expect(ExpectedArtifactTensor {
3377            name: "micro.body".to_owned(),
3378            shape: vec![8, 8],
3379            dtype: StoredDtype::Q8,
3380            access_class: AccessClass::HotRecurrentMicrodecoder,
3381        });
3382        let report = manifest.audit(&reader);
3383        assert_eq!(report.count_of("extra"), 1, "{}", report.render());
3384        assert!(report.render().contains("text_embedding.weight"));
3385    }
3386
3387    #[test]
3388    fn quantized_dtype_sizes_are_exact_including_the_odd_q4_tail() {
3389        assert_eq!(StoredDtype::Bf16.storage_bytes(10), Some(20));
3390        assert_eq!(StoredDtype::F32.storage_bytes(10), Some(40));
3391        assert_eq!(StoredDtype::Q8.storage_bytes(10), Some(10));
3392        // Two elements per byte, rounding up: an odd count still occupies a whole trailing byte.
3393        assert_eq!(StoredDtype::Q4.storage_bytes(10), Some(5));
3394        assert_eq!(StoredDtype::Q4.storage_bytes(11), Some(6));
3395        // Overflow is reported, never wrapped into a small, plausible-looking size.
3396        assert_eq!(StoredDtype::F32.storage_bytes(u64::MAX), None);
3397    }
3398
3399    #[test]
3400    fn wire_strings_round_trip_for_every_enum_value() {
3401        for class in [
3402            AccessClass::HotRecurrentMicrodecoder,
3403            AccessClass::HotRecurrentTalker,
3404            AccessClass::HotCodecDecoder,
3405            AccessClass::ColdTextEmbedding,
3406            AccessClass::EnrollmentSpeakerEncoder,
3407            AccessClass::EnrollmentCodecEncoder,
3408            AccessClass::Metadata,
3409        ] {
3410            assert_eq!(AccessClass::parse(class.as_str()), Some(class));
3411        }
3412        for dtype in [
3413            StoredDtype::Bf16,
3414            StoredDtype::F32,
3415            StoredDtype::Q8,
3416            StoredDtype::Q4,
3417        ] {
3418            assert_eq!(StoredDtype::parse(dtype.as_str()), Some(dtype));
3419        }
3420        assert_eq!(AccessClass::parse("HOT_SOMETHING"), None);
3421        assert_eq!(StoredDtype::parse("f16"), None);
3422    }
3423}