Skip to main content

ftts_artifacts/
fttsq.rs

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