mzdata 0.67.0

A library to read mass spectrometry data formats and a data model for mass spectra
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
# Documentation Review: mzdata (root crate, src/)

Scope: `src/` in the root `mzdata` crate -- the facade crate that re-exports
`mzdata-spectrum`, `mzdata-bindata`, `mzdata-param`, `mzdata-meta`, and adds the `io/`
subsystem (format readers/writers), grouping/averaging iterators, and tutorials. No code
was changed; this is a review only. Companion reports already cover the sub-crates in
depth: `crates/mzdata-spectrum/spectrum-docs.md` and `crates/mzdata-bindata/bindata-docs.md`.

This review was produced by four parallel passes (root data-model files and tutorials;
mzML/MGF/imzML plus the core I/O traits; the vendor formats Thermo/TDF/mzMLb; and the
format-inference/index/USI/PROXI infrastructure), then consolidated here.

## How to read this report

- "Missing" means a public item has no `///` doc comment at all.
- "Thin" means a doc comment exists but omits information a caller needs (units, panics,
  errors, preconditions, side effects, algorithm behavior).
- A number of findings below describe places where the observable behavior looks like it
  does not match what a reasonable reader would expect from the name or the (thin/absent)
  doc comment, including several likely-genuine bugs and cross-file inconsistencies. These
  are flagged as "worth verifying," not asserted as confirmed, since no code was run or
  changed. They are included because writing accurate documentation is exactly the
  exercise that surfaced them.
- Given the size of this crate, per-file sections summarize repetitive small gaps (e.g. a
  run of undocumented enum variants, or a family of undocumented one-line getters) as a
  pattern with a representative line range, rather than listing every instance.

---

## 1. Crate root

### src/lib.rs
- The crate-level docs are a genuine strength here (overview, a working example, a feature
  table) -- better than either sub-crate's root. The one clear gap: it never names the
  constituent sub-crates (`mzdata-spectrum`, `mzdata-bindata`, `mzdata-param`,
  `mzdata-meta`) or explains that this crate is a workspace facade gluing them together.
  A docs.rs reader has no way to know, from this crate alone, that e.g. `SpectrumLike` and
  `DataArray` actually live in and are documented by different crates. The re-exports at
  the bottom of the file (`mzdata_param::{curie, impl_param_described, ...}`,
  `mzdata_meta::{impl_metadata_trait, ...}`) appear with no explanation of why they sit at
  crate root rather than under `params`/`meta`.
- Typo: "funcitonality" (appears twice).
- `src/spectrum.rs` line ~10-11 states "[bindata] ... does not include signal processing
  as that is outside the scope of this crate" -- worth verifying/clarifying: under the
  `mzsignal` feature this root crate *does* perform signal processing directly
  (`pick_peaks`, `reprofile_with_shape`, `denoise` re-exported from `mzdata-spectrum`;
  `average_spectra`/`SpectrumAveragingIterator` defined directly in `src/spectrum/
  group.rs`). The sentence is presumably scoped to the `bindata` sub-crate specifically,
  but as written it reads as a blanket claim about the whole crate, which is false once
  `mzsignal` is enabled.

### src/main.rs
No issues; a trivial stub pointing at `examples/mzinfo.rs`.

### src/meta.rs
One-sentence module doc ("Metadata describing mass spectrometry data files and their
contents.") with no listing of the module's actual key types (`InstrumentConfiguration`,
`DataProcessing`, `Software`, `FileDescription`, `Sample`, `ScanSettings`, per this
project's own architecture notes). This is noticeably thinner than its siblings
`params.rs` and `spectrum.rs`, both of which enumerate their key types and give runnable
examples.

### src/params.rs
No gaps found. Excellent module doc: lists core types and includes multiple runnable
examples (building params, generic value reads, `ParamDescribed`, CURIE matching, PSI-MS
vocabulary lookups). Flagged here only as the crate's best example, worth using as a
template for `meta.rs` and `prelude.rs`.

### src/prelude.rs
One-line module doc ("A set of foundational traits used throughout the library.") with no
grouping or rationale for what's included, despite being the crate's primary "import
everything" entry point. A short paragraph explaining the trait families (spectrum access,
ion-mobility-frame access, metadata, parameter access) and why each belongs in the prelude
would help a newcomer who does `use mzdata::prelude::*;` and wants to know what just came
into scope.

### src/utils.rs
`PROTON`, `mass_charge_ratio`, and `neutral_mass` have **zero** documentation -- not even a
one-line comment -- despite being the crate's core mass-spec unit-conversion functions
(m/z <-> neutral mass). This is the same finding as `crates/mzdata-bindata/src/utils.rs`
(a near-identical, separately-defined pair of functions with the same gap); see that
report's section 5 for the full analysis (single-proton-adduct assumption, sign convention
for negative charge, behavior at `z == 0`). Worth noting explicitly: **this crate has two
independent copies of essentially the same physics formula** (root `src/utils.rs` and
`mzdata-bindata`'s `src/utils.rs`), neither documented, and nothing cross-references the
other or explains why both exist.

---

## 2. src/spectrum/ (grouping, averaging, MS^e handling)

Several files here (`bindata.rs`, `frame.rs`, `spectrum_types.rs`) are thin
`pub use mzdata_spectrum::*`/`pub use mzdata_bindata::*` re-export shims with no new public
items; they add nothing beyond what the sub-crate reports already cover. The substantive,
root-crate-only logic lives in `group.rs`, `group/flat.rs`, `group/mse_iter.rs`,
`group/util.rs`, and `utils.rs` (the `Collator`), and this is where the real documentation
gaps are -- and where the crate's most algorithmically complex code lives.

### src/spectrum/group.rs (largest file in this area, ~1550 lines)
- `SpectrumGroupingIterator`/`IonMobilityFrameGroupingIterator`: the struct docs describe
  the *purpose* ("batch together MSn spectra with their MS1 precursor") but not the
  *mechanism*: the generation-tracking flush logic (`generation_tracker`,
  `flush_generations`), the `buffering` field (only ever mentioned as "default buffering
  level of 3" inside `new()`'s doc, with no explanation of what it controls, no accessor,
  and no way to change it after construction), or that emitted groups can legitimately
  have **no precursor at all** (`deque_group_without_precursor`) -- a caller reading only
  the struct doc would not expect `group.precursor()` to ever be `None`.
  - `clear()`'s doc ("Reset the internal storage as though this iterator hadn't ever been
    used") does not mention that the underlying `source` iterator is *not* rewound --
    only internal buffers are cleared. That is a materially different guarantee than "as
    though never used" and could mislead a caller trying to restart iteration.
  - Worth verifying: `max_ms1_seeking_depth`'s doc reads like a scoped fallback ("maximum
    number of entries to pass without seeing an MS1 entry before emitting a group without
    a precursor"), but `deque_group_without_precursor` calls `flush_all_products`, which
    drains the **entire** pending-product buffer, not just the entries responsible for
    exceeding the depth limit. Unrelated, still-pending MSn spectra belonging to other,
    future precursors can end up dumped into one orphan group. This is not documented and
    would surprise a caller relying on the "maximum number of entries" framing.
- `mzsignal_impl` module (feature `mzsignal`), the spectrum-averaging/reprofiling engine:
  - `average_spectra`'s doc omits the hard-coded `FWHM_DEFAULT = 0.01` used when
    reprofiling centroid input (not configurable, not mentioned) and doesn't say what
    happens on an empty or all-non-profile `spectra` input.
  - `ArcArrays` and `SpectrumAveragingContext`: struct docs are one line each, and every
    field and nearly every inherent method (`new`, `iter`, `reprofile_with`,
    `reprofile_with_average_with`, `average_with`, etc.) has **zero** documentation,
    despite being the actual averaging API surface.
  - **Undocumented `unsafe`**: `reprofile_with_average_with` and `average_with` both
    contain `unsafe { std::slice::from_raw_parts(mp, ...) }`, reinterpreting an
    `Arc<Vec<f64>>`/`Arc<Vec<f32>>` pointer as a borrowed slice, with no `// SAFETY:`
    comment anywhere and no rustdoc explanation of the invariant that makes this sound
    (e.g. that the `Arc` must outlive the returned slice, or that no other thread mutates
    it). Not asserting unsoundness, but unexplained `unsafe` in signal-averaging code that
    processes every spectrum passing through this path deserves, at minimum, a documented
    safety argument.
  - `DeferredSpectrumAveragingIterator`/`SpectrumAveragingIterator`: struct docs exist but
    fields, `into_inner`, `get_ref`, and `new` are undocumented on both; `initial_feed`'s
    one-line doc doesn't explain the sliding-window pre-fill algorithm it implements.
  - By contrast, `SpectrumGroupAveraging` (the public trait tying this together, with
    `averaging`/`averaging_deferred` and proper `# Arguments` sections) is genuinely well
    documented -- a good template for the rest of this module.

### src/spectrum/group/flat.rs
Entirely undocumented: `FlatSpectrumGroupingIterator`/`FlatIonMobilityGroupingIterator`
and every method have zero `///` comments. More importantly, nothing explains that this is
a **materially simpler, different grouping strategy** than `SpectrumGroupingIterator` in
`group.rs`: "flat" grouping starts a new group at every MS1 and appends everything until
the next MS1, with no buffering, no precursor-ID matching, and no orphan handling. Since
both strategies are public and reachable, a reader choosing between them has no doc
anywhere explaining the tradeoff (simpler/faster but less correct for interleaved/
out-of-order acquisitions, vs. the buffered, ID-matching, more correct but more complex
`SpectrumGroupingIterator`).

### src/spectrum/group/mse_iter.rs (Waters MS^e alternating-scan grouping)
- `HasScanConfiguration` (trait and its `get_configuration` method) has no doc comment.
  Also worth flagging to a maintainer as a possible dead-code/visibility issue: the
  containing module isn't `pub`, and the trait isn't re-exported, so it appears
  unreachable from outside the crate despite being marked `pub`.
- `MSEIterator`'s core algorithm (`next_group`, `feed_item`) has no explanation of what
  "low configuration" physically means (a scan-configuration CV/user-param value
  identifying the low-collision-energy function in an MS^e acquisition), why a match on it
  triggers a group boundary, or what happens to configurations that are neither the "low"
  one nor in `ignored_configurations` (they silently accumulate as "high energy" scans in
  the same group).
- Asymmetric doc coverage: `IonMobilityFrameMSEIteratorExt` and `IonMobilityFrameMSEIterator`
  have doc comments; their structurally-identical siblings `SpectrumMSEIteratorExt` and
  `SpectrumMSEIterator` do not.

### src/spectrum/group/util.rs
`GenerationTracker` (`pub(crate)`) and all of its methods, including `older_than` (which
implements the generation-window eviction algorithm underpinning `group.rs`'s
`flush_generations`), have zero doc comments. Since this is the one piece of code that
would actually explain the grouping iterator's stale-buffer/flush semantics, its complete
lack of documentation compounds the gaps in `group.rs` above -- a reader trying to
understand "when does a group get flushed" has nowhere to go.

### src/spectrum/scan_properties.rs
Mostly a re-export shim plus one new trait, `PrecursorRetrieval`. The trait itself has no
top-level doc (only its two methods do); neither method documents what happens when
`precursor_id`/`product_id` is present but not found in the given source (falls through to
`None`, unstated here).

### src/spectrum/utils.rs (the `Collator<T>` type)
- `Collator`'s struct doc is good, including an explicit "Warning" about unbounded memory
  growth -- a positive example. But its four public fields (`waiting`, `next_key`, `ticks`,
  `done`) have no field-level docs.
- `receive_from_map_timeout`, `receive_from_map_iter_timeout`, `receive_map`,
  `receive_map_iter` (four consecutive public methods) have **zero** documentation despite
  taking a nontrivial remapping callback `F: Fn(usize, U) -> ...` as a parameter. This
  stands out because the neighboring, simpler `receive_from`/`receive_from_timeout`
  methods just above them *are* well documented (batch size, timeout semantics) -- an
  inconsistency within the same short file.
- `collate` (the unbounded-channel sibling of the well-documented `collate_sync`, which
  has an explicit "blocks the calling thread" note) doesn't restate that caveat, forcing a
  cross-reference.

---

## 3. Tutorials (src/tutorial/*.rs and the docs/*.md they include)

These are thin Rust shims (`#![doc = include_str!(...)]`) pulling in markdown from `docs/`.
The markdown itself was checked against the code it describes.

- **docs/reader_tutorial.md** and **docs/writer_tutorial.md**: accurate; spot-checked
  claims (e.g. `StreamingSpectrumIterator` panic behavior, `MZReader` enum dispatch,
  `SpectrumWriter`/`MSDataFileMetadata` usage) match the code. No discrepancies found.
- **docs/spectrum_tutorial.md**: accurate where checked, including the specific claim that
  `pick_peaks` does not update `signal_continuity` automatically (confirmed against
  `mzdata-spectrum`) and the `MultiLayerSpectrum` field-by-field construction example
  (confirmed against the struct definition). One systemic issue: **every code example
  touching signal processing (peak picking, reprofiling, denoising, the full
  "putting it together" example) is fenced as ` ```ignore ` rather than ` ```rust `**,
  meaning none of them are compiled or checked by the doc build, unlike the earlier,
  simpler metadata/peaks examples in the same file which do use ` ```rust `. The most
  complex and most failure-prone examples are exactly the ones never verified.
- **docs/ion_mobility_tutorial.md**: mostly accurate (spot-checked the
  `VoltSecondPerSquareCentimeter`/`Millisecond` unit claims against `mzdata-param`), but:
  - Line ~64 has a broken/truncated sentence: "Ion mobility data can come from many
    places, but the common :" (missing words, stray colon).
  - The same ` ```ignore `-for-signal-processing-examples pattern recurs here: the feature
    extraction and querying examples (the parts doing the actual "signal processing" work)
    are never compiled, only the simpler metadata-access examples are.
  - `extract_features_simple`'s `maximum_gap_size` parameter is described as "maximum
    allowed gap (in ion mobility units)" without clarifying whether that means the frame's
    *native* unit (1/K0 vs. milliseconds, which the tutorial elsewhere stresses varies by
    instrument) or a normalized unit -- worth clarifying given the tutorial's own emphasis
    on unit variability.

Recommendation: fix the broken sentence, and change the signal-processing example fences
from ` ```ignore ` to ` ```rust ` (or `no_run` if they require external data files) so the
doc build actually exercises them.

---

## 4. src/io/ -- core traits and dispatch

### src/io/mod.rs, src/io/traits.rs
Both have only a stub or absent module doc. Neither explains the overall shape of the I/O
subsystem: format dispatch (`MZReaderType`/`MZReader`), the `mz_read!`/`mz_write!` macros,
or that `io::traits` is the hub defining `SpectrumSource`, `RandomAccessSpectrumIterator`,
`SpectrumWriter`, `MZFileReader`, etc. A newcomer landing on `io::mod`'s docs.rs page gets
no orientation.

### src/io/shorthand.rs (`mz_read!`/`mz_write!`)
This project's own CLAUDE.md states these macros are "zero-overhead alternatives... usage
is discouraged outside of throwaway code," but **that caveat exists only in CLAUDE.md, not
in the macros' own doc comments**. A reader who only sees rustdoc/docs.rs (which is
everyone except contributors reading this specific repo file) has no warning against
reaching for these pervasively. Additionally, no file anywhere compares the three ways to
open a reader (`MZReaderType` enum dispatch vs. `mz_read!` vs. constructing a concrete
reader type directly) or explains what tradeoff each makes -- the macro's own doc only
says it avoids "penalty" without saying what the alternatives' penalty actually is, and
doesn't contrast its own real limitation (the reader is scoped to the macro's closure and
"cannot be extracted without substantial type system torture") against `MZReaderType`,
which *can* be stored and returned freely.

### src/io/traits/util.rs, chromatogram.rs, spectrum.rs, frame.rs
- `SeekRead` (a widely-used bound throughout every reader implementation) has no doc
  comment at all.
- `SpectrumSource`'s trait-level doc doesn't surface `DetailLevel` semantics or the
  chronological-ordering assumption behind `get_spectrum_by_time` (that assumption is
  documented on the method, but not promoted to the trait level where a reader would look
  first); nothing states what happens with out-of-order scan times (undefined --
  binary-search-based lookup will simply misbehave).
- `MZFileReader::open_path`'s doc says index building is "usually negligible on 'regular'
  IO file systems" but says nothing about failure/recovery. This directly undersells the
  actual complexity: concrete readers (see mzML below) implement multi-tier fallback and
  validation logic that this trait-level doc gives no hint of.
- `StreamingSpectrumIterator`'s panic behavior is documented once at the type level but
  not repeated as a `# Panics` section on each individual panicking method (`reset`,
  `iter`).
- `RandomAccessSpectrumIterator for SpectrumIterator::start_from_time` calls `.expect(...)`
  on a boundary-testing spectrum fetch -- undocumented panic path, including a possible
  underflow-adjacent edge case at `len() == 0`.
- `SpectrumWriter::write`/`write_owned`'s returned `usize` is not documented at all, and
  **its meaning differs by implementation**: `MzMLWriterType::write` returns the post-write
  stream byte position, while `MGFWriterType::write` always returns `Ok(0)` regardless of
  what was written. A caller using the trait-level `write_all`'s accumulated byte count
  gets a meaningful number for mzML and a meaningless `0` for MGF, and neither the trait
  nor either impl documents this.
- `IonMobilityFrameSource::has_ion_mobility`'s doc ("reads a sparse 1% of the entries")
  doesn't mention that it temporarily mutates `detail_level` mid-call (restored
  afterward) -- a side effect a caller concurrently reading `detail_level` could observe.
- `BorrowedGeneric3DIonMobilityFrameSource`'s `start_from_id/index/time` have `_ =>
  todo!()` arms for unmapped `SpectrumAccessError` variants (e.g.
  `SpectrumIndexNotFound` reaching `start_from_id`) -- an undocumented, apparently
  reachable panic trap.

---

## 5. src/io/mzml/ (mzML and indexedmzML)

- `mod.rs` has a genuinely good module doc; no significant gaps there.
- `reading_shared.rs`: `MzMLParserState`/`MzMLParserError` enums have block-level docs but
  zero per-variant docs across roughly 50 and 9 variants respectively (pattern, not
  itemized). More substantively:
  - `CVParamParse::handle_param_borrowed`/`handle_param` decode XML attributes with an
    **undocumented, inconsistent** malformed-input policy: `name`/`value`/`unitName`
    gracefully fall back to Latin-1 decoding when not valid UTF-8, but `cvRef`,
    `accession`, and `unitAccession` panic (`.unwrap_or_else(|e| panic!(...))`) on the
    same class of failure. A non-UTF8 CV accession attribute crashes the whole parse while
    a non-UTF8 name attribute silently degrades -- worth documenting as a real asymmetry,
    if not reconsidering.
  - `FileMetadataBuilder::start_element` has dozens of `.expect("Error decoding X")` calls
    on attribute text (id, name, location, version, order, etc.); every one panics on
    malformed/non-UTF8 input, none of it documented as a precondition anywhere.
  - `build_spectrum_index` is the best-documented function in the file (has
    `# Arguments`/`# Returns`), but doesn't mention that it silently ignores unparsable
    spectrum-id attributes (no warning logged) or what happens on duplicate spectrum IDs.
    It also contains an unresolved mystery left as a plain code comment rather than
    addressed: "This count is off by 2 because somehow the < and > bytes are removed?" --
    an admission that the byte-offset arithmetic here isn't fully understood by its
    author. Worth flagging verbatim as an unverified correctness note in load-bearing
    offset-index-building code.
- `reader.rs`:
  - Extensive `.expect(...)`-based panics on malformed numeric CV values throughout
    `SpectrumBuilding` (`fill_binary_data_array`, `fill_selected_ion`,
    `populate_isolation_window`, `fill_scan_window`) and `fill_param_into` (scan start
    time, injection time, collision energy) -- none documented with `# Panics`.
  - `MzMLReaderType::from_buffered_and_detail_level` silently swallows any error from
    `parse_metadata()` (`Ok(())=>{}, Err(_err)=>{}`) -- a malformed mzML header/metadata
    section leaves the reader with partial/default metadata and gives the caller no way to
    know parsing failed. Not documented anywhere.
  - **Index recovery is a substantial, entirely undocumented three-tier fallback**: (1)
    try reading the trailing `indexedmzML` index; (2) on failure, rebuild via a full
    linear scan; (3) even on a successful index read, *validate* it by probing a spectrum
    near the middle of the file and checking for CRLF/LF byte-offset drift, rebuilding via
    full scan with a `warn!` if drift is detected. None of `MzMLReaderType`'s docs,
    `new_indexed`'s one-line doc, or the module doc mention any of this. `_read_index`
    also panics outright if seeking back to file start fails during recovery.
  - `get_chromatogram_by_id/_by_index`, `get_spectrum_by_id/_by_index`, and `reset` all use
    `.expect(...)` around seek/checkpoint operations -- every random-access call can panic
    on I/O failure mid-seek; undocumented, and the shared trait in `traits/spectrum.rs`
    gives no indication either.
  - **Worth verifying, high confidence, cross-file**: the sync reader's graceful
    index-recovery fallback described above has **no counterpart in the async reader**.
    `AsyncMzMLReaderType::new_indexed` calls `.expect("Failed to read mzML index")` (i.e.
    panics) if the trailing index is missing or invalid, and
    `AsyncMZFileReader::construct_index_from_stream` explicitly `panic!`s with "Building
    an index from byte stream not yet implemented." **Net effect: a plain, non-indexed
    mzML file that the synchronous reader opens successfully by falling back to a full
    scan will cause the async reader to panic outright.** This is a major, load-bearing
    capability gap between the sync and async mzML readers, and it is not documented in
    either `async_reader.rs` or the shared trait docs.
- `writer.rs`: struct doc is reasonable but brief; a large number of public methods
  (`write_scan_list`, `write_isolation_window`, `write_precursor`,
  `write_binary_data_arrays`, `start_spectrum`, `start_chromatogram`, `write_chromatogram`,
  `write_index`, `write_index_list`, compression getters/setters) have zero doc comments
  (pattern, not itemized). Notably, the inherent `write_spectrum` *does* have good
  `## Side-Effects`/`# Errors` sections (documenting that it implicitly opens
  `<spectrumList>` and freezes `spectrum_count`), but the `SpectrumWriter` trait impl's
  `write`/`flush`/`close` -- the primary interface most callers actually use -- has none
  of that, so the documented behavior is effectively invisible through the generic path.
  `write_binary_data_array_pre_encoded` has a real `# Panics` section (a good exemplar,
  panics on `BinaryDataArrayType::Unknown`); its siblings `write_binary_data_array`/
  `write_binary_data_arrays`, which call into the same panic path, do not repeat it.
  `ChromatogramCollector::to_chromatogram` panics for any `ChromatogramType` other than
  TIC/BPC, undocumented. `write_spectrum` also `.unwrap()`s when reading back a
  caller-supplied TIC/BPI param as `f32`, panicking if a caller manually inserted a
  non-numeric value there.
- `async_reader.rs`: struct doc is a one-line pointer to the sync type and does not
  mention the capability gap above, nor which sync-side features (e.g. EOL-mismatch
  detection) are simply absent here (confirmed absent). Otherwise mirrors the sync
  reader's general documentation thinness.

---

## 6. src/io/mgf/

- `mod.rs`: fine, trivial.
- `reader.rs`: `MGFParserState`/`MGFError` enums undocumented per-variant (pattern).
  `handle_scan_header` has the same kind of **inconsistent malformed-input handling** seen
  in mzML: `PEPMASS`/`CHARGE` degrade gracefully to a proper `MGFError::
  MalformedHeaderLine`, but `RTINSECONDS` calls `value.parse::<f64>().unwrap()` directly
  and will panic the entire reader on one malformed retention-time value. None of this is
  documented, and the inconsistency itself (some fields recoverable, one is not) is worth
  calling out explicitly.
  - `read_next` swallows every `MGFError` via `.ok()`, meaning a parse error mid-file (a
    genuinely malformed peak line, say) is indistinguishable from a cleanly-exhausted
    file when consumed through the `Iterator`/`SpectrumSource` API -- callers cannot tell
    "no more spectra" from "the file is truncated or corrupt." This is not documented on
    `read_next`, the trait, or the module doc. `read_into`, by contrast, does propagate the
    error -- so the same underlying failure surfaces differently depending on which method
    is called, and that inconsistency is also undocumented.
  - `build_index` panics via `.expect(...)`/`panic!` on any I/O error during its pre-scan;
    its own doc describes only the happy path.
  - Random-access methods (`get_spectrum_by_id/_by_index`) again use undocumented
    `.expect(...)` around seek/checkpoint operations, matching the mzML pattern.
- `writer.rs`: struct docs are reasonable. `write` always returns `Ok(0)` (see the
  cross-file discrepancy in section 4 above). `write_arrays` rejects profile-mode spectra
  (MGF is a centroid-only format) but that constraint is not mentioned on `write_peaks`,
  the actual public entry point most callers would reach for.
- `async_reader.rs`: same general thinness as the sync reader; unlike mzML, no sync/async
  divergence was found here (both panic identically on I/O errors during index building).

---

## 7. src/io/imzml/ (imaging mzML)

- `mod.rs` has one of the best module docs in the whole crate: explains the `.ibd`
  external-file model and the continuous/processed storage modes clearly.
- `reader.rs`, however, has real gaps that matter for a format whose entire value
  proposition is correctly pairing two separate files:
  - `ImzMLReaderType` itself (the primary public reader type) has **no doc comment at
    all**, unlike its mzML sibling which has a solid multi-paragraph type-level doc.
  - `ImzMLFileMetadata` and `DataRangeQuery` have zero documentation, despite
    `ImzMLFileMetadata` carrying the UUID/checksum fields central to the format's
    data-integrity story that the module doc itself describes.
  - `check_ibd_file` validates the `.ibd` file's UUID against the value declared in the
    `.imzML` file, but **only logs a warning on mismatch and does not fail** -- a
    wrongly-paired `.ibd`/`.imzML` pair will silently proceed to read binary data from the
    wrong file. Worse, the **checksum is never validated at all**: there is a literal
    `// TODO check that the checksum matches if available` in the code, despite
    `ImzMLFileMetadata` already storing `ibd_checksum`/`ibd_checksum_type` for exactly
    this purpose. None of this (partial validation, silent-on-mismatch behavior, checksum
    never checked) is documented as a caveat anywhere, despite being a real, meaningful
    data-integrity gap for an imaging format where silent misalignment between two files
    would quietly corrupt every spectrum read.
  - The same silent-error-swallowing pattern as the mzML reader recurs here:
    `parse_metadata()` and `check_ibd_file()` errors are both discarded during
    construction, undocumented.
  - `load_ibd_arrays` (the actual binary-array decoding hook for this format) only
    supports `BinaryCompressionType::NoCompression`/`Decoded`; any other compression type
    returns an error, with a plain code comment (not a doc comment) noting that imzML has
    its own external-data-compression CV terms that simply aren't implemented yet. This
    real format-coverage limitation (compressed `.ibd` data is unsupported) appears
    nowhere in the module or struct docs.
  - Unlike the mzML reader, which tries a trailing index before falling back to a full
    scan, `ImzMLReaderType`'s constructor **unconditionally does a full linear XML
    pre-scan** on every open (`build_index()` with no fast path). Given that imzML's
    entire use case is large imaging datasets (per the module doc's own framing), this
    undocumented performance characteristic is a meaningful, real difference from its
    mzML sibling that a user would want to know about before opening a large file.
  - `open_file`'s dependency on the `filename` feature to auto-pair the `.ibd` file is
    documented only in an inline comment and the runtime error message, not on the method
    or type itself, and the `filename` feature is not listed among this crate's documented
    features anywhere else either.

---

## 8. Vendor-format readers: Thermo, Bruker TDF, mzMLb

These are feature-gated (`thermo`, `bruker_tdf`, `mzmlb`) and, on the whole, the weakest
documentation coverage in the crate -- several files (tdf/sql.rs, tdf/arrays.rs,
tdf/constants.rs, thermo/instruments.rs) have essentially zero `///` documentation on any
public item.

### Thermo (`src/io/thermo/`)
- `mod.rs`'s module doc is a genuine strength: states the feature gate, the .NET 8
  runtime requirement, licensing, and the `MZDATA_IGNORE_UNKNOWN_INSTRUMENT` escape hatch.
  It does not state supported OS/platforms or the per-call marshaling cost of the .NET
  interop layer.
- `reader.rs`: the main struct and most methods (including `get_tic`/`get_bpc`, which sit
  right next to a properly-documented sibling method) have no docs. `get_centroiding`/
  `set_centroiding` don't name the underlying vendor algorithm, its performance cost, or
  that toggling it changes `signal_continuity` on subsequently-read spectra.
  `get_load_extended_spectrum_data`/`set_load_extended_spectrum_data` don't list which
  array types get populated (ChargeArray, BaselineArray, SignalToNoiseArray,
  ResolutionArray), nor a plain-code-comment caveat that the resolution array is only
  present on FT mass analyzers, which never made it into rustdoc.
  - Undocumented panic: precursor population calls `todo!()` for the `MPD`/`PTD`
    dissociation methods -- a file using either will panic mid-read, with no `# Panics`
    anywhere.
  - There is a `#[cfg(feature = "doc-only")]` stub module providing a fake
    `ThermoRawReaderType` purely so docs.rs can build without the .NET runtime; that it is
    a stub, and that the "real" type differs, is only recorded in a plain code comment,
    never surfaced to a rustdoc reader.
- `async_reader.rs`: no module doc explaining this requires the `async`/`async_partial`
  features or that every call is dispatched through `tokio::task::spawn_blocking` (a real,
  undocumented per-call cost). Worth verifying: every async method temporarily takes
  `self.inner` out via `.unwrap()` and restores it after the blocking call completes; if
  the blocking closure panics, `self.inner` is left `None` permanently and every
  subsequent call on that reader will then panic. Not documented anywhere.
- `instruments.rs`: essentially zero documentation on ~100 enum variants and several
  matching/mapping functions. Worth verifying: the model-name matcher list is
  order-sensitive for substring (`Contains`) matches (first match wins, undocumented as a
  convention), and `"ASTRAL"` is listed before the more specific `"ASTRAL ZOOM"`, which
  would make the dedicated `Orbitrap_Astral_Zoom` variant unreachable from real-world
  instrument-name strings containing "ASTRAL ZOOM." Also worth verifying: a display-name
  literal, `"Orbitrap Atral"`, looks like a typo for "Astral" with no citation to confirm
  or deny against the PSI-MS term text.

### Bruker TDF (`src/io/tdf/`)
- `mod.rs` states the feature gate and dependencies but not which acquisition modes are
  supported (DDA-PASEF, DIA-PASEF) versus which cause a panic (see below), nor the
  required on-disk `.d` directory layout.
- `calibration.rs` is the single most important signal-processing file in this batch (see
  Section 9), and its documentation is thin-to-absent on exactly the parts that matter:
  field units are unstated throughout `MzCalibration`/`TimsCalibration`/
  `MzCalibrationModel2` (is `t1`/`t2` a temperature? what are `c0..c7`?), and the actual
  TOF-to-m/z conversion formula (`convert_f64`/`invert_f64`: a cubic polynomial plus up to
  8 Newton-Raphson iterations, falling back to quadratic/linear solves) has no explanation
  of the physical model, why 8 iterations, or what happens when the derivative hits zero
  (breaks early, silently returning a possibly-inaccurate value).
  - Worth verifying: three separate impls expose a method literally named `convert_f6`
    (not `convert_f64`) alongside a correctly-named `invert_f64` -- looks like a typo, and
    because it's undocumented there's no way to confirm intent.
  - Worth verifying: an unconditional `eprintln!("Converting {value:?} to m/z
    calibration");` fires on every m/z-calibration-model resolution (potentially per
    frame). This looks like leftover debug output, inconsistent with the `log::` macros
    used everywhere else in the codebase, and is undocumented either way.
  - Worth verifying, and directly relevant to result accuracy: `reader.rs` contains a
    commented-out line disabling m/z recalibration, with the comment "The m/z models
    aren't consistently good enough, or at least my translations aren't" -- i.e. an
    admission from the author that the ported Bruker m/z-recalibration formula may not
    match Bruker's own values closely. This caveat about accuracy is not surfaced in any
    public doc on `CalibrationParameters`, `MzCalibrationModel`, or `TDFFrameReaderType`,
    even though it directly bears on the trustworthiness of every m/z value the reader
    produces.
- `sql.rs`: almost entirely undocumented (the `FromSQL` trait, `SQLFrame`'s 17 fields,
  `RawTDFSQLReader`, etc.). `SQLFrame.time`'s unit is never stated in this file (only
  inferable from a division-by-60 at a call site elsewhere). `SQLTrace::load_trace_data`
  implements a nontrivial in-place reordering of chromatogram trace chunks that may arrive
  out of time order, entirely undocumented, with no explanation of why they'd be unsorted
  in the first place.
- `arrays.rs` (the file that actually assembles spectra/frames from raw TDF binary blobs,
  called out specifically by this review's scope): **no doc comments at all** on
  `FrameToArraysMapper` or `consolidate_peaks`. The prefix-sum offset-array explanation
  that does exist is a plain code comment, not rustdoc, so it's invisible to API
  consumers. `consolidate_peaks`'s ion-mobility feature merging (via `mzsignal`) uses
  unexplained magic numbers for a minimum point count and an ion-mobility gap tolerance,
  with no stated units or rationale.
- `constants.rs`: no documentation on `InstrumentSource`/`MsMsType` or their numeric
  codes, and no citation back to the Bruker schema fields they represent.
- `reader.rs`: a struct apparently misnamed `IndexExtry` (likely meant "IndexEntry") is
  entirely undocumented. **Undocumented panics on real acquisition modes**: `scan_range`,
  `guess_number_of_entries`, and `build_index` all call `todo!()` for
  `MsMsType::MRM`/`PRMPASEF`/`Unknown` -- meaning PRM-PASEF and MRM Bruker files will
  panic when read, a constraint not documented anywhere (not in `tdf/mod.rs`, not on the
  reader type, no `# Panics` section). `index_to_precursor` assumes isolation windows are
  symmetric around the target without documenting or citing that assumption against real
  quadrupole isolation behavior.

### mzMLb (`src/io/mzmlb/`)
- `mod.rs`'s module doc, unlike Thermo's, never mentions the native **libhdf5** system
  library dependency required at build/runtime -- a real, undocumented barrier for a
  docs.rs reader trying to use this feature.
- `common.rs`: the HDF5-integer-type-to-`BinaryDataArrayType` mapping maps 8-bit and
  16-bit integer HDF5 types to `Unknown` (i.e., unrepresentable). Worth verifying,
  cross-file: this feeds directly into `reader.rs`'s array-reading match, which has a
  `todo!()` arm for exactly `BinaryDataArrayType::Unknown` -- meaning an mzMLb file
  storing an array as an 8-bit or 16-bit HDF5 integer will panic on read, undocumented
  anywhere.
- `reader.rs`: the chunk-caching machinery for external HDF5 datasets (`
  ExternalDataRegistry`, `CacheInterval`, `ByteReader` -- exactly the "how chunked
  datasets map onto arrays" mechanism this review's scope asked about) is undocumented
  except for its two public constructors, which do reasonably explain chunk-size/memory
  tradeoffs (a good, if isolated, example). Worth verifying: a `handle_encoding` function
  marked `#[allow(unused)]` (suggesting it isn't wired into the live read path)
  additionally has a `NumpressLinear` branch that, even on successful decompression,
  falls through to an unconditional `Err(...)` return -- if this code is in fact live
  anywhere, a successful decompression would be reported as a failure. Undocumented
  either way, and unclear whether it's dead code or a live bug.
- `writer.rs`: no module doc. Chunk size and compression-scheme constants/methods
  (`DEFAULT_CHUNK_SIZE`, `zlib_compression`, `blosc_zstd`) have zero documentation -- no
  units, no compression-level meaning, no size/speed tradeoff explanation -- and the
  builder silently defaults to zlib level 9 (maximum compression, slowest) when a caller
  doesn't explicitly choose a scheme, a default that is not surfaced anywhere a caller
  would see it before writing a large file. `write_binary_data_array` panics on any
  `ArrayType` not in its explicit match list. Worth verifying, cross-file: that list
  excludes `BaselineArray`/`ResolutionArray`/`WavelengthArray`, yet the Thermo reader
  actively produces `BaselineArray`/`ResolutionArray` when extended-data loading is
  enabled -- round-tripping a Thermo spectrum with extended data through the mzMLb writer
  would panic, and this incompatibility between two vendor-format modules is undocumented
  anywhere. Also worth verifying: `Drop for MzMLbWriterType` calls `.close().unwrap()`,
  meaning an I/O failure during an implicit drop (rather than an explicit `.close()` call)
  panics inside a destructor -- undocumented, and no guidance anywhere to always close
  explicitly and check the `Result`.

---

## 9. src/io/ infrastructure: format inference, offset index, USI, PROXI, compression

### Format inference and the read/write pipeline (`infer_format/*.rs`)
This is where "which format did the user hand us, and how do we know" is decided, and it
is one of the least-documented areas relative to how much a caller's correctness depends
on it.
- No file explains the overall precedence when multiple detection strategies could apply
  (the specific thing this review's scope asked about). Reconstructed from the code:
  **extension-based inference wins outright and is never cross-checked against file
  content** once it succeeds; content-sniffing (reading the first 500 bytes, decompressing
  that same budget for gzip input) is used only as a fallback when the extension is
  unrecognized. Within that fallback, sniffers run in a fixed, undocumented order (imzML
  is checked before mzML, since imzML content is a superset-like profile of mzML). None of
  this is written down anywhere; a mislabeled file (e.g. an MGF renamed `.mzml`) would be
  trusted on its extension alone with no content verification.
  - "Indexed" vs. "raw" mzML is not a distinct format-inference outcome at all; both map
    to the same `MassSpectrometryFormat::MzML`, and indexing is handled transparently
    inside the mzML reader. This isn't stated anywhere and could confuse a reader
    expecting indexing to be an inference-time concern.
- `pipeline.rs`'s `MassSpectrometryReadWriteProcess` trait doc claims to "handle all the
  gory details of file format inference" but never says what those details are, and in
  practice layers its *own* undocumented behavior on top of the inference module: a
  gzip-compressed MGF/mzML is silently downgraded to a non-random-access streaming reader
  (losing seek support) while an uncompressed file gets a fully indexed, random-access
  reader through the same API; gzip is silently ignored altogether (neither decompressed
  nor rejected) for mzMLb/imzML/Thermo/TDF inputs opened through this same entry point.
  `Source::Stdin` always content-sniffs regardless of any hint and only supports MGF/mzML,
  an undocumented restriction relative to the path-based branch. Writing supports far
  fewer formats than reading (no Thermo/TDF/imzML writers at all), an asymmetry not
  flagged anywhere near `open_writer`'s doc.
- `dispatch.rs`: `MZReaderType` explains its fast-dispatch design reasonably but never
  states *why* it's `#[non_exhaustive]` (forward-compatibility for adding formats without
  a semver break -- present only in project memory/CLAUDE.md, not in source).
  `IMMZReaderType` (the ion-mobility-frame dispatch enum) has **zero** documentation of
  any kind, including no mention that it lacks an `open_path` and must be reached via
  `MZReaderType::try_into_frame_source` -- a fact otherwise recorded only outside the
  codebase.

### src/io/offset_index.rs
`OffsetIndex`'s struct doc describes it as "an ordered mapping from entity ID to byte
offset" but never explains the mechanism it actually enables (a reader seeks the
underlying stream to the stored offset and parses the record starting there), nor what the
offset is relative to when the stream is wrapped by a decompressor (ambiguous as
documented). A representative run of public methods (`new`, `len`, `is_empty`, `keys`,
`clear`) have no doc comments at all.

### src/io/usi.rs (Universal Spectrum Identifier)
No module doc, no reference to the PSI USI specification, and -- the most concrete gap --
**no grammar description or worked valid/invalid examples anywhere in a doc comment**
(examples exist only inside `#[cfg(test)]`, invisible in rendered docs). The `USI` struct
and all six of its fields are entirely undocumented, as is the `Identifier` enum
(`Scan`/`Index`/`NativeID`). This matters concretely: `proxi.rs` treats `Scan` as 1-based
and `Index` as already 0-based when mapping to the internal spectrum index, and that
numbering-convention difference is load-bearing but appears nowhere in `usi.rs` itself --
a reader has to reverse-engineer it from an unrelated file. The actual `FromStr` parsing
algorithm (splitting on `:`, then re-associating trailing tokens by a repository-code
marker to separate interpretation from provenance, even though interpretation strings can
themselves contain colons) is exactly the kind of nontrivial parsing logic this review was
asked to check, and it has no explanation anywhere.

### src/io/proxi.rs
No module doc; nowhere does it state which parts of the PROXI spec are implemented (in
practice: only the `spectra?usi=...&resultType=full` GET endpoint against a fixed list of
known servers) or what's explicitly out of scope. Worth verifying: the blocking and async
"try all known backends" entry points have near-identical doc wording ("try all known
backends... return the first successful spectrum"), but they do not behave the same way --
the blocking version tries backends **sequentially, deterministically**, while the async
version **races them concurrently** and returns whichever completes first, which is
network-timing-dependent and non-deterministic. This meaningful divergence between two
"equivalent" documented methods is not called out anywhere. Separately, the conversion
from a parsed PROXI JSON response into `SpectrumDescription`/`MultiLayerSpectrum` contains
a long list of `.expect(...)`/`.unwrap()` calls on fields like ms level, scan start time,
selected ion m/z, charge state, and isolation window bounds -- all parsing **untrusted
data from a live HTTP response** -- with no `# Panics` documentation anywhere, meaning an
unexpected value from any queried PROXI server can crash the calling process.

### src/io/compression.rs
`is_gzipped` only checks the 2-byte magic prefix and says nothing about corrupt-but-valid-
prefix streams (which surface later, elsewhere, as an opaque I/O error). `RestartableGzDecoder`'s
doc mentions it's "not intended to be efficient" but doesn't state the actual cost that
matters to a caller: every seek fully rewinds and re-decompresses from byte zero up to the
target offset, i.e. seeking is O(offset), not O(1) or O(delta). The only place this cost is
acknowledged in prose is an unrelated doc comment on `dispatch.rs`'s
`MZReaderType::open_gzipped_read_seek`, with no cross-reference back to where the mechanism
actually lives.

### src/io/utils.rs
`DetailLevel`'s variants are reasonably documented (a positive example), but there is no
central table anywhere listing which concrete readers actually support which detail
levels, despite each reader having different, undocumented-in-aggregate support.
`PreBufferedStream`'s `Seek`/`Read` trait impls have no doc comments on the impl methods
themselves, despite genuinely non-standard behavior (erroring when seeking outside the
pre-buffered window). `ConcurrentLoader::load`'s one-line doc omits that it panics via
`.unwrap()` if any spawned chunk's file-open call fails, and doesn't state the (useful,
worth advertising) guarantee that results are re-sorted back into original spectrum-index
order despite concurrent loading.

---

## 10. Signal processing operations that need more detail

Pulling together the operations, across this whole crate, that actually transform or
derive numeric content from spectral data (as opposed to just moving bytes/records
around), since these encode algorithmic choices a caller cannot infer from a type
signature alone:

1. **Spectrum averaging and reprofiling** (`src/spectrum/group.rs`, `mzsignal_impl`
   module). The public entry point (`SpectrumGroupAveraging`) is well documented, but
   everything underneath it (`ArcArrays`, `SpectrumAveragingContext`,
   `DeferredSpectrumAveragingIterator`/`SpectrumAveragingIterator`) is essentially
   undocumented, including the hard-coded default FWHM used for reprofiling centroid
   input before averaging, and two uses of `unsafe` raw-pointer-to-slice reinterpretation
   with no safety rationale written down anywhere. See Section 2.
2. **MS^e alternating-scan grouping** (`src/spectrum/group/mse_iter.rs`). The grouping
   rule itself (group boundary triggered by returning to the "low energy" configuration)
   is a real domain-specific algorithm with no explanation of the underlying acquisition
   concept anywhere in the doc comments. See Section 2.
3. **Bruker TDF calibration math** (`src/io/tdf/calibration.rs`). The single most
   consequential undocumented algorithm in this entire review: the TOF-index-to-m/z
   conversion (cubic polynomial plus iterative Newton-Raphson refinement) and the
   scan-number-to-1/K0 conversion have no explanation of the physical model, units, or
   accuracy, and there is a code-level admission (a disabled recalibration path with a
   comment about the formula not being "consistently good enough") that the m/z values
   this crate produces from TDF files may not match Bruker's own reference values -- a
   caveat with obvious real-world consequences that is invisible to anyone reading only
   the public API docs. See Section 8.
4. **TDF ion-mobility feature consolidation** (`src/io/tdf/arrays.rs`,
   `consolidate_peaks`). Merges points across the ion-mobility dimension via `mzsignal`
   with unexplained magic-number parameters (minimum point count, gap tolerance) and no
   stated units. See Section 8.
5. **mzML/mzMLb/imzML binary array decoding hooks**. Multiple, separately-implemented
   "read the raw bytes into typed arrays" code paths (`mzml/reader.rs`'s
   `fill_binary_data_array`, `mzmlb/reader.rs`'s HDF5 chunk assembly, `imzml/reader.rs`'s
   `load_ibd_arrays`) each have their own undocumented failure modes (numeric-parse
   panics; unrepresentable HDF5 integer widths; unsupported IBD compression schemes), and
   none of them are documented as a coherent, comparable set anywhere -- a caller who
   understands one format's array-decoding limitations has no reason to expect the
   others' limitations are different.
6. **mzML random-access index construction and validation** (`src/io/mzml/reader.rs`).
   A genuinely sophisticated three-tier algorithm (read trailing index, fall back to full
   scan, validate the trailing index against a mid-file probe to detect CRLF/LF drift)
   that is completely absent from any public-facing doc, and whose sync/async
   implementations diverge sharply in robustness (graceful fallback vs. hard panic). See
   Section 5.
7. **Format inference / content sniffing** (`src/io/infer_format/inference.rs`). The
   precedence between extension-based and content-based format detection, and the fixed
   ordering of content sniffers, is real decision logic that determines which parser a
   file is handed to, and it is not documented as a policy anywhere a user could find it
   before hitting a surprising misdetection. See Section 9.
8. **USI parsing** (`src/io/usi.rs`). A real grammar with nontrivial tokenization rules
   (colon-delimited fields, with trailing fields re-split by a repository-code marker to
   handle embedded colons in the interpretation field) and a load-bearing, cross-file
   1-based-vs-0-based indexing convention, none of which is written down in prose
   anywhere in the file that owns the type. See Section 9.
9. **PROXI spectrum resolution** (`src/io/proxi.rs`). Both the backend-selection strategy
   difference between the blocking and async APIs (deterministic-sequential vs.
   racing-concurrent) and the extensive unguarded parsing of untrusted network response
   data into typed spectra deserve documentation, the latter especially given it is a
   direct route from external, unauthenticated network input into process-crashing
   panics. See Section 9.

---

## 11. Cross-cutting patterns

A few issues recur so often across independently-written files that they are worth
naming once as crate-wide patterns rather than as N separate findings:

1. **Panic-on-malformed-input via `.expect()`/`.unwrap()` is pervasive** across mzML, MGF,
   Thermo, TDF, mzMLb, and PROXI parsing code, almost always undocumented with a
   `# Panics` section, despite most of these code paths processing external,
   not-necessarily-well-formed input (files from third-party instruments/software, or
   live network responses). Where the same crate handles conceptually similar malformed
   fields differently (some recover to a typed `Err`, others panic) -- seen concretely in
   both mzML's CV-attribute decoding and MGF's header-line parsing -- the inconsistency
   itself is undocumented and looks unintentional.
2. **Silent error-swallowing during construction**: both `MzMLReaderType` and
   `ImzMLReaderType` discard any error from their metadata-parsing step
   (`Ok(())=>{}, Err(_)=>{}`), leaving the caller with a reader that silently has
   partial/default metadata and no signal that anything went wrong. Undocumented in both
   places.
3. **Enum variants without individual doc comments** is systemic:
   `MzMLParserState`/`MzMLParserError`/`MzMLWriterState`, `MGFParserState`/`MGFError`,
   `ArraysAvailable`-style outcome enums, and many more all have a block-level summary but
   zero per-variant documentation.
4. **Sync/async divergence**: the mzML index-recovery fallback (graceful vs. panicking)
   is the clearest, highest-impact example, but the pattern of "the async reader exists
   but is documented and tested less thoroughly than its sync counterpart" recurs across
   Thermo and mzML.
5. **Caveats left in plain code comments instead of doc comments**: several
   correctness-relevant notes (the mzML byte-offset-off-by-2 mystery, the TDF
   m/z-model-accuracy admission, the imzML checksum-not-implemented TODO, the FT-only
   resolution-array note in Thermo) exist only as `//` comments next to the code, which
   means they never reach rendered documentation even though they are exactly the kind of
   caller-relevant caveat a reader would want to see before trusting the output.

---

## Suggested priority order

1. **Verify and, if confirmed, fix or clearly document** the highest-impact discrepancies
   found in this review: the sync-vs-async mzML index-recovery gap (panics instead of
   falling back), the TDF `eprintln!`/`convert_f6` oddities and the disabled-recalibration
   accuracy admission, the mzMLb `Float32`-stored/`f64`-returned-style mismatches noted in
   the bindata report that this crate's readers ultimately depend on, and the PROXI
   blocking-vs-async backend-selection divergence.
2. Add `# Panics` sections to the parsing hot paths most likely to be hit by real
   third-party files: mzML/MGF CV-attribute and numeric-field decoding, TDF's
   `MsMsType::MRM`/`PRMPASEF` handling, and PROXI's response-to-spectrum conversion (the
   last of these is the only one processing live network input).
3. Document the TDF calibration math (Section 8/10, item 3) -- this is the single most
   consequential piece of undocumented signal-processing logic in the crate, since it
   determines the accuracy of every m/z value read from Bruker files.
4. Document the mzML index-recovery algorithm and the format-inference precedence rules
   (Sections 5 and 9) -- both are "how does this crate decide what my data actually is"
   logic that a user has no way to audit today.
5. Document `usi.rs`'s grammar and the imzML `.ibd` pairing/checksum validation gap
   (Section 7 and 9) -- both are integrity-relevant and currently invisible.
6. Fill in the crate-root gaps that are cheap to fix and high-visibility: name the
   sub-crates in `src/lib.rs`'s crate doc, document `mass_charge_ratio`/`neutral_mass` in
   `src/utils.rs` (and note its duplication with `mzdata-bindata`'s copy), bring
   `src/meta.rs`/`src/prelude.rs` up to `src/params.rs`'s standard, and fix the broken
   sentence in `docs/ion_mobility_tutorial.md`.
7. Change the `mzsignal`-feature tutorial examples from ` ```ignore ` to a compiled fence
   so the doc build actually verifies the crate's most complex examples.
8. Work through the remaining missing-doc patterns (enum variants, one-line getters,
   `SpectrumWriter`'s trait-level `write` return-value contract, `mz_read!`/`mz_write!`'s
   missing "discouraged in production" caveat) as lower-urgency cleanup.