Skip to content

feat(raw-bam): additive API surface - #288

Merged
nh13 merged 1 commit into
mainfrom
nh/raw-bam-api-7-additive-api
Apr 18, 2026
Merged

nh13 merged 1 commit into
mainfrom
nh/raw-bam-api-7-additive-api

Conversation

@nh13

@nh13 nh13 commented Apr 17, 2026

Copy link
Copy Markdown
Member

Summary

Additive additions to `fgumi-raw-bam` that later stack-series PRs consume:

  • `RawBamWriter`, `write_raw_record`, `write_raw_records`
  • `CigarOp` / `CigarKind` typed CIGAR iteration
  • `TagValue` zero-copy typed tag dispatch
  • Symmetric `RawRecord` ↔ `RecordBuf` converters + `RecordBufEncoder`
  • `SamBuilder` (general-purpose record construction, renamed from `MappedBamRecordBuilder`)
  • `UnmappedSamBuilder` (renamed from `UnmappedBamRecordBuilder`)
  • `IndexedRawBamReader`, `RawQueryIter`, `UnmappedIter` (new `indexed_reader.rs`)
  • Matching re-exports in `lib.rs`

Also updates three consensus callers (`codec_caller`, `duplex_caller`, `vanilla_caller`) and `commands/extract.rs` to use the renamed `UnmappedSamBuilder`.

Adds comprehensive `noodles` round-trip tests and widens the tag-editor invariant proptests to match the expanded API.

Part 2 of 7 in the follow-up series closing #272. Stacks on #286.

Test plan

  • `cargo ci-test` — 2541/2541 pass (incl. expanded proptest + round-trip coverage)
  • `cargo ci-fmt` clean
  • `cargo ci-lint` clean

Stack

  • PR 1: zipper `restore_unconverted_bases_in_raw_record` → RawRecord methods (refactor(zipper): use RawRecord methods in restore_unconverted_bases_in_raw_record #286)
  • PR 2 (this): fgumi-raw-bam additive API
  • PR 3: fgumi-sam raw helpers + RawRecordClipper
  • PR 4: consensus internals → RawRecord + drop RecordBuf variants
  • PR 5: commands + storage + pipeline collapses
  • PR 6: workspace test migrations to SamBuilder
  • PR 7: delete vendored bam_codec + simulate → SamBuilder

@nh13 nh13 added the raw-bam label Apr 17, 2026
@nh13
nh13 temporarily deployed to github-actions April 17, 2026 20:16 — with GitHub Actions Inactive
@codecov

codecov Bot commented Apr 17, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.07%. Comparing base (48c63e3) to head (1d47104).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #288   +/-   ##
=======================================
  Coverage   90.07%   90.07%           
=======================================
  Files         124      124           
  Lines       62247    62247           
=======================================
  Hits        56071    56071           
  Misses       6176     6176           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nh13
nh13 marked this pull request as ready for review April 17, 2026 22:28
@nh13

nh13 commented Apr 18, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Apr 18, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bb5851af-ab73-4eca-90fc-33ae8006f348

📥 Commits

Reviewing files that changed from the base of the PR and between 6a93923 and 1d47104.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • crates/fgumi-consensus/src/codec_caller.rs
  • crates/fgumi-consensus/src/duplex_caller.rs
  • crates/fgumi-consensus/src/vanilla_caller.rs
  • crates/fgumi-raw-bam/Cargo.toml
  • crates/fgumi-raw-bam/src/builder.rs
  • crates/fgumi-raw-bam/src/cigar.rs
  • crates/fgumi-raw-bam/src/indexed_reader.rs
  • crates/fgumi-raw-bam/src/lib.rs
  • crates/fgumi-raw-bam/src/noodles_compat.rs
  • crates/fgumi-raw-bam/src/raw_bam_record.rs
  • crates/fgumi-raw-bam/src/sequence.rs
  • crates/fgumi-raw-bam/src/tags.rs
  • crates/fgumi-raw-bam/src/testutil.rs
  • crates/fgumi-raw-bam/tests/noodles_roundtrip.rs
  • crates/fgumi-raw-bam/tests/noodles_roundtrip_comprehensive.rs
  • crates/fgumi-raw-bam/tests/tags_editor_invariants.proptest-regressions
  • crates/fgumi-raw-bam/tests/tags_editor_invariants.rs
  • src/lib/commands/extract.rs
✅ Files skipped from review due to trivial changes (5)
  • crates/fgumi-raw-bam/src/testutil.rs
  • crates/fgumi-raw-bam/tests/tags_editor_invariants.proptest-regressions
  • crates/fgumi-raw-bam/tests/noodles_roundtrip.rs
  • crates/fgumi-raw-bam/Cargo.toml
  • crates/fgumi-consensus/src/vanilla_caller.rs
🚧 Files skipped from review as they are similar to previous changes (9)
  • crates/fgumi-consensus/src/codec_caller.rs
  • crates/fgumi-raw-bam/src/sequence.rs
  • src/lib/commands/extract.rs
  • crates/fgumi-raw-bam/tests/tags_editor_invariants.rs
  • crates/fgumi-consensus/src/duplex_caller.rs
  • crates/fgumi-raw-bam/src/raw_bam_record.rs
  • crates/fgumi-raw-bam/src/builder.rs
  • crates/fgumi-raw-bam/src/cigar.rs
  • crates/fgumi-raw-bam/src/noodles_compat.rs

📝 Walkthrough

Walkthrough

Swapped UnmappedBamRecordBuilder for UnmappedSamBuilder across consensus, CLI, and tests and added a new SamBuilder for mapped-record construction. Introduced typed CIGAR APIs (CigarKind, CigarOp) and typed CIGAR iteration. Added zero-copy aux tag decoding (TagValue, RawTagsView::get, iter_typed). Added raw-BAM writing (write_raw_record(s), RawBamWriter), indexed raw-BAM reader (IndexedRawBamReader with region and unmapped iterators), noodles compatibility encoders/decoders, many tests, and Cargo feature/dev-dependency updates.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main change: additive API surface for fgumi-raw-bam crate.
Description check ✅ Passed Description is directly related to the changeset, detailing additive APIs, builder renames, new modules, and affected callers.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nh/raw-bam-api-7-additive-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nh13

nh13 commented Apr 18, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/fgumi-raw-bam/tests/tags_editor_invariants.rs (1)

1-1: ⚠️ Potential issue | 🟠 Major

Restore the crate-level unsafe denial.

This integration test file is its own crate root, so src/lib.rs’s crate attribute does not cover it.

Proposed fix
 #![cfg(feature = "test-utils")]
+#![deny(unsafe_code)]

As per coding guidelines, **/*.rs: Enforce #![deny(unsafe_code)] - only standard library unsafe code is permitted in Rust files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fgumi-raw-bam/tests/tags_editor_invariants.rs` at line 1, This test
crate is missing the crate-level safety denial: add the crate attribute
preventing unsafe code at the top of the file by inserting the
`#![deny(unsafe_code)]` crate attribute (in addition to the existing
`#![cfg(feature = "test-utils")]`) so the test file enforces the same
unsafe-code prohibition as src/lib.rs; ensure the attribute appears before any
items in the file to apply crate-wide.
crates/fgumi-raw-bam/src/tags.rs (1)

1256-1307: ⚠️ Potential issue | 🟠 Major

Keep in-place setters non-panicking on truncated aux bytes.

find_tag_position can return a matching target before its payload is validated. The writes at lines 1263, 1273, 1283, and 1306 can panic on malformed records; return false instead.

Proposed fix
     pub fn set_int_in_place(&mut self, tag: &[u8; 2], value: i64) -> bool {
         let Some((p, val_type)) = find_tag_position(self.0, *tag) else {
             return false;
         };
+        let Some(size) = tag_value_size(val_type, &self.0[p + 3..]) else {
+            return false;
+        };
+        if p + 3 + size > self.0.len() {
+            return false;
+        }
         match val_type {
             b'c' => i8::try_from(value)
                 .map(|v| {
@@
     pub fn set_float_in_place(&mut self, tag: &[u8; 2], value: f32) -> bool {
         let Some((p, val_type)) = find_tag_position(self.0, *tag) else {
             return false;
         };
-        if val_type != b'f' {
+        if val_type != b'f' || p + 7 > self.0.len() {
             return false;
         }
         self.0[p + 3..p + 7].copy_from_slice(&value.to_le_bytes());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fgumi-raw-bam/src/tags.rs` around lines 1256 - 1307, The in-place
setters (set_int_in_place and set_float_in_place) currently assume the payload
bytes exist and can panic on malformed/truncated aux data; before writing at
offsets like self.0[p+3], self.0[p+3..p+5], self.0[p+3..p+7] (set_int_in_place)
and self.0[p+3..p+7] (set_float_in_place), validate the buffer has the required
length and return false if not; use checked indexing or slice length checks
(e.g., self.0.get_mut(p+3) / self.0.get_mut(p+3..p+5) / self.0.get_mut(p+3..p+7)
or compare self.0.len() with p + needed_bytes) to avoid panics while keeping the
existing type checks from find_tag_position. Ensure each match arm in
set_int_in_place only performs its write after verifying the target slice exists
and otherwise yields false.
🧹 Nitpick comments (5)
crates/fgumi-raw-bam/tests/noodles_roundtrip.rs (1)

44-64: Strengthen decoded-value assertions in these focused proptests.

These tests check only parseability and counts. While noodles_roundtrip_comprehensive.rs provides broader coverage, these focused tests should assert decoded CIGAR op kinds/lengths (not just count) and decoded sequence/quality values to clearly validate the round-trip semantics of set_cigar_ops and set_sequence_and_qualities.

For example: decode and assert buf.cigar() contains the expected ops with correct kinds, and buf.sequence() / buf.quality_scores() match the inputs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fgumi-raw-bam/tests/noodles_roundtrip.rs` around lines 44 - 64, The
test only asserts the decoded CIGAR count — update set_cigar_ops_roundtrips to
also assert that the decoded CIGAR op kinds and lengths match the expected
values derived from cigar_ops (map each u32 word into length and op kind) by
iterating buf.cigar() and comparing each op's kind/len; additionally, after
calling set_sequence_and_qualities (or using build_record input), decode
buf.sequence() and buf.quality_scores() from decode_with_noodles and assert they
equal the original sequence bytes and quality bytes used to construct rec; keep
existing use of cigar_ops_vec(), decode_with_noodles(), build_record(),
set_cigar_ops(), and set_sequence_and_qualities() to locate insertion points.
crates/fgumi-raw-bam/src/noodles_compat.rs (1)

106-111: Use or document the ignored header.

raw_record_to_record_buf accepts header but decodes through raw_records_to_record_bufs, which constructs a default header internally. Please either thread the supplied header into decoding or document/remove the parameter before this becomes public API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fgumi-raw-bam/src/noodles_compat.rs` around lines 106 - 111,
raw_record_to_record_buf currently takes a Header but ignores it
(raw_records_to_record_bufs builds a default header), so either thread the
provided Header through or remove/document the parameter: update
raw_records_to_record_bufs to accept a &noodles::sam::Header and use it for
decoding (and change its callers), or remove the unused _header parameter from
raw_record_to_record_buf (and its signature) if you don't need it, or add a
clear doc comment on raw_record_to_record_buf stating the header is
intentionally ignored; reference the functions raw_record_to_record_buf and
raw_records_to_record_bufs when making the change.
crates/fgumi-raw-bam/src/cigar.rs (1)

56-65: Public consumes_* can overshift for op_type ≥ 16.

Guard removal is fine for spec-compliant 4-bit inputs, and all in-crate callers mask & 0xF. But these remain public const fn taking u32; passing e.g. 16 yields BAM_CIGAR_TYPE >> 32 → debug panic / release wrap (incorrect true). The narrowed test (9..=15) no longer catches this. Cheap guard: mask inside the function.

Optional defensive tweak
 pub const fn consumes_ref(op_type: u32) -> bool {
-    (BAM_CIGAR_TYPE >> (op_type << 1)) & 2 != 0
+    (BAM_CIGAR_TYPE >> ((op_type & 0xF) << 1)) & 2 != 0
 }
@@
 pub const fn consumes_query(op_type: u32) -> bool {
-    (BAM_CIGAR_TYPE >> (op_type << 1)) & 1 != 0
+    (BAM_CIGAR_TYPE >> ((op_type & 0xF) << 1)) & 1 != 0
 }

Alternatively, document the 0..=15 precondition.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fgumi-raw-bam/src/cigar.rs` around lines 56 - 65, Public const fns
consumes_ref and consumes_query accept u32 and can overshift when op_type ≥ 16;
mask the input inside each function (e.g., use op_type & 0xF) before shifting so
the shift amount is bounded to 0..=15 and avoids panic/wrapping, keeping them
const and preserving existing logic that checks BAM_CIGAR_TYPE >> (masked_op <<
1).
crates/fgumi-raw-bam/src/raw_bam_record.rs (1)

123-128: DerefMut<[u8]> + as_mut_vec bypass all record invariants.

Intentional per the PR (needed for splice/resize), but worth a one-line safety note in the rustdoc: callers are on the hook for keeping l_read_name/n_cigar_op/l_seq consistent with the buffer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fgumi-raw-bam/src/raw_bam_record.rs` around lines 123 - 128, Add a
one-line safety note to the Rustdoc for RawRecord explaining that the impl of
std::ops::DerefMut for RawRecord (and use of as_mut_vec) allows callers to
mutate the inner buffer directly and therefore can bypass record invariants;
explicitly state callers must ensure l_read_name, n_cigar_op, and l_seq remain
consistent with the underlying buffer when using DerefMut/as_mut_vec (mention
RawRecord::deref_mut and any public as_mut_vec helper by name) so future readers
know they are responsible for preserving invariants.
crates/fgumi-raw-bam/tests/noodles_roundtrip_comprehensive.rs (1)

467-533: Proptest float-NaN branch is dead.

-1000.0f32..=1000.0f32 never produces NaN, so the || decoded_f.is_nan() && as_score.is_nan() tail is unreachable. Harmless — drop it or widen the strategy with prop_oneof![Just(f32::NAN), ...] if NaN handling matters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fgumi-raw-bam/tests/noodles_roundtrip_comprehensive.rs` around lines
467 - 533, The proptest strategy for as_score in
proptest_random_complete_record_roundtrip uses -1000.0f32..=1000.0f32 which
cannot generate NaN, so the NaN branch in the AS float assertion is unreachable;
either remove the decoded_f.is_nan() && as_score.is_nan() check from the
assertion (simplify to (decoded_f - as_score).abs() < 1e-3_f32) or change the
strategy for as_score to include NaN (e.g., replace the range with a
prop_oneof![Just(f32::NAN), (-1000.0f32..=1000.0f32)] so as_score can be NaN);
update the proptest declaration for as_score in the
proptest_random_complete_record_roundtrip test accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/fgumi-raw-bam/src/builder.rs`:
- Around line 521-527: The code writes self.bin for all records but doesn't emit
the special UNMAPPED_BIN value for unmapped SamBuilder records; update the
fixed-header bin write in the builder (where
buf.extend_from_slice(&self.bin.to_le_bytes()) is called) to use UNMAPPED_BIN
(4680) when the record is unmapped (e.g., when ref_id == -1 and/or pos == -1)
otherwise keep self.bin; you can compute let bin_to_write = if self.ref_id == -1
&& self.pos == -1 { UNMAPPED_BIN } else { self.bin } and then write
buf.extend_from_slice(&bin_to_write.to_le_bytes()) so SamBuilder matches
UnmappedSamBuilder behavior.

In `@crates/fgumi-raw-bam/src/indexed_reader.rs`:
- Around line 107-114: query_unmapped currently only seeks when
index.last_first_record_start_position() returns Some, so if it returns None the
reader is left at the prior BGZF position; change query_unmapped to seek to the
beginning fallback when last_first_record_start_position() is None by calling
self.inner.get_mut().seek_to_virtual_position(0).context("seeking to unmapped
position")? so UnmappedIter always starts at the documented first-record
fallback; update the branch in query_unmapped to call
seek_to_virtual_position(0) on self.inner when the index returns None.

In `@crates/fgumi-raw-bam/src/tags.rs`:
- Around line 1042-1047: The iterator iter_typed currently rescans RawTagsView
for each entry which causes O(n²) behavior and duplicates; instead, for each
iterated entry use the entry's own type and value bytes to produce the TagValue
directly (use the entry.type_byte and entry.value_bytes to decode/construct a
TagValue for that entry) and yield (entry.tag, decoded_value) without calling
RawTagsView::get again; update iter_typed to map each entry -> (entry.tag,
TagValue::from_bytes / decode using entry.type_byte and entry.value_bytes) so
duplicate keys return their respective values and performance becomes linear.
- Around line 1006-1012: The get() method can panic when handling 'A' tags
because it accesses aux[start] without checking bounds; change the branch
handling val_type == b'A' in function get (and the TagValue::Char construction)
to first ensure aux has at least start+1 bytes (e.g., by checking
aux.get(start)) and return None on truncated data; keep using
find_tag_position(...) to locate p and val_type, but guard the aux[start] access
to avoid panics for truncated "XA:A" entries.
- Around line 437-451: The type_byte() match currently returns the array's
element subtype for Self::Array(a) which is wrong; change the Self::Array(a) arm
in TagValue::type_byte to return the BAM array type byte b'B' (instead of
a.elem_type) so that Array variants report the canonical aux type 'B' while the
element subtype remains available on the Array value itself.

---

Outside diff comments:
In `@crates/fgumi-raw-bam/src/tags.rs`:
- Around line 1256-1307: The in-place setters (set_int_in_place and
set_float_in_place) currently assume the payload bytes exist and can panic on
malformed/truncated aux data; before writing at offsets like self.0[p+3],
self.0[p+3..p+5], self.0[p+3..p+7] (set_int_in_place) and self.0[p+3..p+7]
(set_float_in_place), validate the buffer has the required length and return
false if not; use checked indexing or slice length checks (e.g.,
self.0.get_mut(p+3) / self.0.get_mut(p+3..p+5) / self.0.get_mut(p+3..p+7) or
compare self.0.len() with p + needed_bytes) to avoid panics while keeping the
existing type checks from find_tag_position. Ensure each match arm in
set_int_in_place only performs its write after verifying the target slice exists
and otherwise yields false.

In `@crates/fgumi-raw-bam/tests/tags_editor_invariants.rs`:
- Line 1: This test crate is missing the crate-level safety denial: add the
crate attribute preventing unsafe code at the top of the file by inserting the
`#![deny(unsafe_code)]` crate attribute (in addition to the existing
`#![cfg(feature = "test-utils")]`) so the test file enforces the same
unsafe-code prohibition as src/lib.rs; ensure the attribute appears before any
items in the file to apply crate-wide.

---

Nitpick comments:
In `@crates/fgumi-raw-bam/src/cigar.rs`:
- Around line 56-65: Public const fns consumes_ref and consumes_query accept u32
and can overshift when op_type ≥ 16; mask the input inside each function (e.g.,
use op_type & 0xF) before shifting so the shift amount is bounded to 0..=15 and
avoids panic/wrapping, keeping them const and preserving existing logic that
checks BAM_CIGAR_TYPE >> (masked_op << 1).

In `@crates/fgumi-raw-bam/src/noodles_compat.rs`:
- Around line 106-111: raw_record_to_record_buf currently takes a Header but
ignores it (raw_records_to_record_bufs builds a default header), so either
thread the provided Header through or remove/document the parameter: update
raw_records_to_record_bufs to accept a &noodles::sam::Header and use it for
decoding (and change its callers), or remove the unused _header parameter from
raw_record_to_record_buf (and its signature) if you don't need it, or add a
clear doc comment on raw_record_to_record_buf stating the header is
intentionally ignored; reference the functions raw_record_to_record_buf and
raw_records_to_record_bufs when making the change.

In `@crates/fgumi-raw-bam/src/raw_bam_record.rs`:
- Around line 123-128: Add a one-line safety note to the Rustdoc for RawRecord
explaining that the impl of std::ops::DerefMut for RawRecord (and use of
as_mut_vec) allows callers to mutate the inner buffer directly and therefore can
bypass record invariants; explicitly state callers must ensure l_read_name,
n_cigar_op, and l_seq remain consistent with the underlying buffer when using
DerefMut/as_mut_vec (mention RawRecord::deref_mut and any public as_mut_vec
helper by name) so future readers know they are responsible for preserving
invariants.

In `@crates/fgumi-raw-bam/tests/noodles_roundtrip_comprehensive.rs`:
- Around line 467-533: The proptest strategy for as_score in
proptest_random_complete_record_roundtrip uses -1000.0f32..=1000.0f32 which
cannot generate NaN, so the NaN branch in the AS float assertion is unreachable;
either remove the decoded_f.is_nan() && as_score.is_nan() check from the
assertion (simplify to (decoded_f - as_score).abs() < 1e-3_f32) or change the
strategy for as_score to include NaN (e.g., replace the range with a
prop_oneof![Just(f32::NAN), (-1000.0f32..=1000.0f32)] so as_score can be NaN);
update the proptest declaration for as_score in the
proptest_random_complete_record_roundtrip test accordingly.

In `@crates/fgumi-raw-bam/tests/noodles_roundtrip.rs`:
- Around line 44-64: The test only asserts the decoded CIGAR count — update
set_cigar_ops_roundtrips to also assert that the decoded CIGAR op kinds and
lengths match the expected values derived from cigar_ops (map each u32 word into
length and op kind) by iterating buf.cigar() and comparing each op's kind/len;
additionally, after calling set_sequence_and_qualities (or using build_record
input), decode buf.sequence() and buf.quality_scores() from decode_with_noodles
and assert they equal the original sequence bytes and quality bytes used to
construct rec; keep existing use of cigar_ops_vec(), decode_with_noodles(),
build_record(), set_cigar_ops(), and set_sequence_and_qualities() to locate
insertion points.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8807b8b3-4354-43cf-8ba5-28c39fe2f5af

📥 Commits

Reviewing files that changed from the base of the PR and between 7a3a042 and 3ff61b1.

📒 Files selected for processing (18)
  • crates/fgumi-consensus/src/codec_caller.rs
  • crates/fgumi-consensus/src/duplex_caller.rs
  • crates/fgumi-consensus/src/vanilla_caller.rs
  • crates/fgumi-raw-bam/Cargo.toml
  • crates/fgumi-raw-bam/src/builder.rs
  • crates/fgumi-raw-bam/src/cigar.rs
  • crates/fgumi-raw-bam/src/indexed_reader.rs
  • crates/fgumi-raw-bam/src/lib.rs
  • crates/fgumi-raw-bam/src/noodles_compat.rs
  • crates/fgumi-raw-bam/src/raw_bam_record.rs
  • crates/fgumi-raw-bam/src/sequence.rs
  • crates/fgumi-raw-bam/src/tags.rs
  • crates/fgumi-raw-bam/src/testutil.rs
  • crates/fgumi-raw-bam/tests/noodles_roundtrip.rs
  • crates/fgumi-raw-bam/tests/noodles_roundtrip_comprehensive.rs
  • crates/fgumi-raw-bam/tests/tags_editor_invariants.proptest-regressions
  • crates/fgumi-raw-bam/tests/tags_editor_invariants.rs
  • src/lib/commands/extract.rs

Comment thread crates/fgumi-raw-bam/src/builder.rs
Comment thread crates/fgumi-raw-bam/src/indexed_reader.rs
Comment thread crates/fgumi-raw-bam/src/tags.rs
Comment thread crates/fgumi-raw-bam/src/tags.rs
Comment thread crates/fgumi-raw-bam/src/tags.rs
@nh13
nh13 force-pushed the nh/raw-bam-api-6-zipper-raw-record-methods branch from 7a3a042 to f820ecd Compare April 18, 2026 07:36
Base automatically changed from nh/raw-bam-api-6-zipper-raw-record-methods to main April 18, 2026 07:39
@nh13
nh13 force-pushed the nh/raw-bam-api-7-additive-api branch from 3ff61b1 to 6a93923 Compare April 18, 2026 08:32
@nh13
nh13 temporarily deployed to github-actions April 18, 2026 08:32 — with GitHub Actions Inactive
@nh13

nh13 commented Apr 18, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Apr 18, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

1 similar comment
@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/fgumi-raw-bam/src/sequence.rs (1)

223-250: ⚠️ Potential issue | 🟠 Major

Keep the destination-size check in release builds.

Line 228 is a public precondition. With debug_assert!, release builds can partially overwrite dst before a later index panic. Keep the eager assert! or return a Result.

🐛 Proposed fix
-    debug_assert!(dst.len() >= packed_len);
+    assert!(
+        dst.len() >= packed_len,
+        "dst too short: got {} bytes, need {packed_len}",
+        dst.len()
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fgumi-raw-bam/src/sequence.rs` around lines 223 - 250, The public
function pack_sequence_into_slice currently uses debug_assert! for the dst size
check which is removed in release builds; change this to a real runtime check:
either replace debug_assert!(dst.len() >= packed_len) with assert!(dst.len() >=
packed_len) to keep the eager panic in all builds, or convert
pack_sequence_into_slice to return a Result and return an Err if dst.len() <
packed_len (use the packed_len and dst variables to perform the check before
writing to dst). Ensure the chosen approach is applied at the start of the
function before any writes to dst.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/fgumi-raw-bam/src/cigar.rs`:
- Around line 955-957: The constructor CigarOp::new allows zero-length ops but
spec/docs mark those as invalid; update CigarOp::new to reject len == 0 (e.g.,
assert!(len > 0, "CIGAR op length must be non-zero") or otherwise return an Err)
and mirror the same guard for any other typed constructors that currently accept
a length (refer to CigarOp::new and similar constructors in the file), leaving
from_raw unchanged so it can still represent malformed wire data; add/update
tests to cover rejection of zero-length CIGAR ops.

In `@crates/fgumi-raw-bam/src/indexed_reader.rs`:
- Around line 120-128: query_unmapped currently falls back to BGZF offset 0 when
both index.last_first_record_start_position() and first_record_position are
None, which can make UnmappedIter try to parse the BAM header as a record;
change query_unmapped to detect this missing-state and return an explicit error
instead of VirtualPosition::from(0). Specifically, in the query_unmapped
function (around the virtual_pos computation that uses
index.last_first_record_start_position() and self.first_record_position) replace
the final unwrap_or(bgzf::VirtualPosition::from(0)) path with an early check
that returns an anyhow error (e.g., using anyhow::bail! or
Err(anyhow::anyhow!(...))) when neither marker is present and read_header has
not been called, so callers get a clear failure rather than attempting to read
the header via UnmappedIter.

In `@crates/fgumi-raw-bam/src/noodles_compat.rs`:
- Around line 113-118: raw_record_to_record_buf currently ignores the supplied
header which breaks decoding for mapped records; update the decode path to
thread the Header through: change raw_records_to_record_bufs to accept a
&noodles::sam::Header (or add a new raw_records_to_record_bufs_with_header that
does) and have raw_record_to_record_buf call that variant passing the header
instead of constructing a synthetic empty header, ensuring any header-dependent
decoding in noodles::sam::alignment::RecordBuf is used; then add a round-trip
test that constructs a non-empty Header with a reference dictionary and verifies
a mapped RawRecord round-trips via raw_record_to_record_buf/ back to bytes to
validate the fix.

---

Outside diff comments:
In `@crates/fgumi-raw-bam/src/sequence.rs`:
- Around line 223-250: The public function pack_sequence_into_slice currently
uses debug_assert! for the dst size check which is removed in release builds;
change this to a real runtime check: either replace debug_assert!(dst.len() >=
packed_len) with assert!(dst.len() >= packed_len) to keep the eager panic in all
builds, or convert pack_sequence_into_slice to return a Result and return an Err
if dst.len() < packed_len (use the packed_len and dst variables to perform the
check before writing to dst). Ensure the chosen approach is applied at the start
of the function before any writes to dst.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8097bee9-a192-464f-8540-b5de9494d491

📥 Commits

Reviewing files that changed from the base of the PR and between 3ff61b1 and 6a93923.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • crates/fgumi-consensus/src/codec_caller.rs
  • crates/fgumi-consensus/src/duplex_caller.rs
  • crates/fgumi-consensus/src/vanilla_caller.rs
  • crates/fgumi-raw-bam/Cargo.toml
  • crates/fgumi-raw-bam/src/builder.rs
  • crates/fgumi-raw-bam/src/cigar.rs
  • crates/fgumi-raw-bam/src/indexed_reader.rs
  • crates/fgumi-raw-bam/src/lib.rs
  • crates/fgumi-raw-bam/src/noodles_compat.rs
  • crates/fgumi-raw-bam/src/raw_bam_record.rs
  • crates/fgumi-raw-bam/src/sequence.rs
  • crates/fgumi-raw-bam/src/tags.rs
  • crates/fgumi-raw-bam/src/testutil.rs
  • crates/fgumi-raw-bam/tests/noodles_roundtrip.rs
  • crates/fgumi-raw-bam/tests/noodles_roundtrip_comprehensive.rs
  • crates/fgumi-raw-bam/tests/tags_editor_invariants.proptest-regressions
  • crates/fgumi-raw-bam/tests/tags_editor_invariants.rs
  • src/lib/commands/extract.rs
✅ Files skipped from review due to trivial changes (4)
  • crates/fgumi-raw-bam/Cargo.toml
  • crates/fgumi-raw-bam/tests/tags_editor_invariants.proptest-regressions
  • crates/fgumi-raw-bam/src/testutil.rs
  • crates/fgumi-raw-bam/tests/noodles_roundtrip_comprehensive.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/fgumi-raw-bam/tests/noodles_roundtrip.rs
  • crates/fgumi-consensus/src/codec_caller.rs
  • src/lib/commands/extract.rs
  • crates/fgumi-raw-bam/src/raw_bam_record.rs

Comment thread crates/fgumi-raw-bam/src/cigar.rs
Comment thread crates/fgumi-raw-bam/src/indexed_reader.rs Outdated
Comment thread crates/fgumi-raw-bam/src/noodles_compat.rs
Additive additions to fgumi-raw-bam that later stack-series PRs
consume:

- RawBamWriter, write_raw_record, write_raw_records (raw_bam_record.rs)
- CigarOp, CigarKind typed CIGAR iteration (cigar.rs)
- TagValue zero-copy typed tag dispatch (tags.rs)
- Symmetric RawRecord <-> RecordBuf converters + RecordBufEncoder
  (noodles_compat.rs)
- SamBuilder (general-purpose record construction) renamed from
  MappedBamRecordBuilder; UnmappedSamBuilder renamed from
  UnmappedBamRecordBuilder (builder.rs)
- IndexedRawBamReader, RawQueryIter, UnmappedIter (new
  indexed_reader.rs) for raw-byte indexed BAM iteration
- Matching re-exports in lib.rs

Also updates three consensus callers (codec/duplex/vanilla) and
commands/extract.rs to use the renamed UnmappedSamBuilder.

Adds comprehensive noodles round-trip tests and widens the tag-editor
invariant proptests to match the expanded API.

Part 2 of 7 in the follow-up series closing #272.
@nh13
nh13 force-pushed the nh/raw-bam-api-7-additive-api branch from 6a93923 to 1d47104 Compare April 18, 2026 16:55
@nh13
nh13 temporarily deployed to github-actions April 18, 2026 16:55 — with GitHub Actions Inactive
@nh13
nh13 merged commit c69e532 into main Apr 18, 2026
9 checks passed
@nh13
nh13 deleted the nh/raw-bam-api-7-additive-api branch April 18, 2026 18:27
This was referenced Apr 18, 2026

This branch was previously deployed

1 inactive deployment
github-actions — 1d471041 Deployed Apr 18, 2026 by nh13 via coverage #1206
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant