Skip to content

fix(audit): extract/zipper/group parity, validator guard, memory accounting (3/3) - #536

Merged
nh13 merged 5 commits into
feat-runallfrom
nh/audit-3-parity
Jul 13, 2026
Merged

nh13 merged 5 commits into
feat-runallfrom
nh/audit-3-parity

Conversation

@nh13

@nh13 nh13 commented Jul 10, 2026 •

Copy link
Copy Markdown
Member

Part 3 of a 3-PR audit-fix stack. Base is nh/audit-2-sort-data (PR 2); merge PRs 1 and 2 first, then this.

Fixes in this PR (command parity + validator guard + memory accounting)

  • V6b (extract): the 1-2 template-read guard lived only in the standalone command; the chain/runall path skipped it and would silently emit malformed BAM templates. Share the check between both paths.
  • V6c (zipper): a natural_compare mismatch guard rejected valid query-grouped (non-sorted) input. fgbio's ZipperBams matches by name equality only; a genuinely orphaned mapped read is still caught by the existing end-of-stream check.
  • validator gap (chains): an ordering-valid but type-incompatible chain (e.g. Group -> Clip, where Clip consumes a record stream but Group emits templates) built a mis-typed pipeline that panicked at dispatch. Guard the tail kind in add_clip/add_dedup. Latent today.
  • M1 (memory): Template::heap_size (the pipeline byte budget) summed logical len(), while MemoryEstimate summed capacity() + container overhead — so --max-memory under-counted RSS and could OOM. Unify on the capacity-based footprint.
  • V3/V4 (group): the single-threaded path over-counted accepted_records on UMI-assignment failure (the chain path zeroes it), and the help claimed "any order (including unsorted)" while the code requires template-coordinate/queryname sorted input. Fix both.

Each fix has a regression test (V4 is a help/doc correction).

Summary by CodeRabbit

  • Bug Fixes
    • Enforced template-read count validation consistently across extraction and chain building (only 1–2 template reads allowed).
    • Fixed UMI grouping metrics to reflect that failed assignment produces no output.
    • Updated zipper merge behavior to accept missing mapped reads in grouped, out-of-order inputs while preserving unmapped-only output.
    • Prevented incompatible pipeline stage combinations from failing unexpectedly.
    • Improved memory/heap size estimates to account for allocated capacity.
  • Documentation
    • Clarified required input sorting and output ordering for UMI grouping in CLI help.
  • Tests
    • Added coverage for validation, metric rollback, and grouped zipper-merge acceptance.

@nh13
nh13 temporarily deployed to github-actions July 10, 2026 05:56 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 190ab006-20a1-4b0d-9bb4-2485d214f519

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe2c7b and a1323bd.

📒 Files selected for processing (6)
  • src/lib/commands/extract.rs
  • src/lib/commands/group.rs
  • src/lib/commands/zipper.rs
  • src/lib/pipeline/chains/builder.rs
  • src/lib/template.rs
  • tests/integration/test_chain_build.rs

Walkthrough

The changes centralize template-count validation, reject incompatible fused pipeline stages earlier, correct failed-group metrics, accept missing mapped reads in query-grouped zipper input, and calculate template memory from allocated capacities.

Changes

Pipeline behavior updates

Layer / File(s) Summary
Shared template-count validation
src/lib/commands/extract.rs, src/lib/pipeline/chains/builder.rs
A shared helper enforces one or two template reads for both Extract validation and chain construction, with unit coverage.
Chain tail compatibility checks
src/lib/pipeline/chains/builder.rs, tests/integration/test_chain_build.rs
Clip, Filter, and Dedup require decoded record-batch tails; integration tests reject invalid Group -> Clip and Group -> Filter chains.
Grouping and zipper processing behavior
src/lib/commands/group.rs, src/lib/commands/zipper.rs
Group help documents ordering constraints, failed UMI groups roll back accepted-template metrics, and zipper merging accepts missing mapped templates in query-grouped input.
Capacity-based template memory estimation
src/lib/template.rs
Template heap sizing uses allocation capacities, and estimate_heap_size delegates to the shared calculation with matching tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: fgumi extract, fgumi group

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main audit-fix themes: extract/zipper/group behavior, validator guarding, and memory accounting.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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/audit-3-parity

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

@codecov

codecov Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.35036% with 5 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (feat-runall@a64fbbe). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/lib/template.rs 87.50% 4 Missing ⚠️
src/lib/pipeline/chains/builder.rs 90.00% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff               @@
##             feat-runall     #536   +/-   ##
==============================================
  Coverage               ?   94.23%           
==============================================
  Files                  ?      111           
  Lines                  ?    50988           
  Branches               ?        0           
==============================================
  Hits                   ?    48047           
  Misses                 ?     2941           
  Partials               ?        0           

☔ View full report in Codecov by Harness.
📢 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 force-pushed the nh/audit-2-sort-data branch from 8102324 to 83de353 Compare July 11, 2026 05:21
@nh13
nh13 force-pushed the nh/audit-3-parity branch from 28856c6 to f697a33 Compare July 11, 2026 05:26
@nh13
nh13 temporarily deployed to github-actions July 11, 2026 05:26 — with GitHub Actions Inactive
@nh13
nh13 force-pushed the nh/audit-2-sort-data branch from 83de353 to 3caea5f Compare July 11, 2026 23:20
@nh13
nh13 force-pushed the nh/audit-3-parity branch from f697a33 to 30af4db Compare July 11, 2026 23:21
@nh13
nh13 temporarily deployed to github-actions July 11, 2026 23:21 — with GitHub Actions Inactive
@nh13
nh13 force-pushed the nh/audit-2-sort-data branch from 3caea5f to 1ff6787 Compare July 12, 2026 00:43
@nh13
nh13 force-pushed the nh/audit-3-parity branch from 30af4db to a0c216b Compare July 12, 2026 00:43
@nh13
nh13 temporarily deployed to github-actions July 12, 2026 00:43 — with GitHub Actions Inactive
@nh13
nh13 force-pushed the nh/audit-2-sort-data branch from 1ff6787 to 1e2abc2 Compare July 12, 2026 05:39
@nh13
nh13 force-pushed the nh/audit-3-parity branch from a0c216b to 4fe2c7b Compare July 12, 2026 05:39
@nh13
nh13 temporarily deployed to github-actions July 12, 2026 05:39 — with GitHub Actions Inactive
@nh13

nh13 commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

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 Jul 12, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

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: 4

Caution

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

⚠️ Outside diff range comments (1)
src/lib/commands/group.rs (1)

1310-1328: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a regression test for the UMI-assignment failure path. src/lib/commands/group.rs:1310-1328 needs coverage that forces assign_umi_groups_impl to return Err and asserts accepted_templates is rolled back while discard counters stay unchanged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/commands/group.rs` around lines 1310 - 1328, Add a regression test
covering the error branch around assign_umi_groups_impl: arrange inputs or a
test double that makes it return Err, then assert the group’s accepted_templates
contribution is subtracted from total_filter_metrics while discard counters
remain unchanged. Keep the existing successful assignment behavior and failure
return path intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/commands/zipper.rs`:
- Around line 2782-2813: Strengthen
grouped_out_of_natural_order_missing_mapped_read_is_accepted by asserting the
expected total record count and validating each read’s mapping status,
specifically that q10 is emitted unmapped-only while q2 is merged with its
mapped records. Follow the flags().is_unmapped() assertion pattern used by
test_unmapped_only_reads, replacing the name-only any checks with assertions
that detect duplication or accidental merging.

In `@src/lib/pipeline/chains/builder.rs`:
- Around line 3773-3776: Update the comments around the guards near add_group
and the corresponding locations to document that the intermediate Group stage
uses ChainTailKind::BatchedProcessedPositionGroups, replacing the incorrect
BamTemplateBatch reference. Keep the existing validation logic unchanged.

In `@src/lib/template.rs`:
- Around line 1030-1053: Update
heap_size_counts_capacity_and_matches_memory_estimate to include a RawRecord
with deliberately reserved spare capacity in the template’s Vec<RawRecord>.
Assert heap_size() against an independent lower bound that includes
RawRecord::capacity() and size_of::<RawRecord>(), rather than relying only on
the tautological estimate_heap_size comparison; retain the queryname capacity
coverage.

In `@tests/integration/test_chain_build.rs`:
- Around line 299-301: Strengthen the error assertions in the integration test
cases around the existing err.to_string() checks so they verify the Clip/Filter
tail-kind guard specifically, rather than merely matching “record-stream”.
Assert the stage-specific error text or the expected tail kind for both affected
cases, while preserving the existing failure context.

---

Outside diff comments:
In `@src/lib/commands/group.rs`:
- Around line 1310-1328: Add a regression test covering the error branch around
assign_umi_groups_impl: arrange inputs or a test double that makes it return
Err, then assert the group’s accepted_templates contribution is subtracted from
total_filter_metrics while discard counters remain unchanged. Keep the existing
successful assignment behavior and failure return path intact.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: da8c0dfe-dfe4-4eec-918d-835108ad2749

📥 Commits

Reviewing files that changed from the base of the PR and between 1e2abc2 and 4fe2c7b.

📒 Files selected for processing (6)
  • src/lib/commands/extract.rs
  • src/lib/commands/group.rs
  • src/lib/commands/zipper.rs
  • src/lib/pipeline/chains/builder.rs
  • src/lib/template.rs
  • tests/integration/test_chain_build.rs

Comment thread src/lib/commands/zipper.rs
Comment thread src/lib/pipeline/chains/builder.rs Outdated
Comment thread src/lib/template.rs
Comment thread tests/integration/test_chain_build.rs Outdated
@nh13
nh13 force-pushed the nh/audit-2-sort-data branch from 1e2abc2 to 8364590 Compare July 12, 2026 15:25
@nh13
nh13 force-pushed the nh/audit-3-parity branch from 4fe2c7b to 8f5b2ae Compare July 12, 2026 15:40
@nh13
nh13 temporarily deployed to github-actions July 12, 2026 15:40 — with GitHub Actions Inactive
@nh13

nh13 commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai Addressed the outside-diff-range comment on src/lib/commands/group.rs: added assignment_failure_rolls_back_accepted_templates_but_not_discards, which forces assign_umi_groups_impl to Err (a filter-passing template whose RX value has a trailing non-UTF-8 byte) and asserts accepted_templates is rolled back while the discard counters stay unchanged.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd group.rs --full-path src/lib/commands
rg -n "assignment_failure_rolls_back_accepted_templates_but_not_discards" -A 40 src/lib/commands/group.rs | head -80

Length of output: 2187


Confirmed — the test is in src/lib/commands/group.rs at Line 5208 and matches the described scenario: a filter-passing template whose RX value has a trailing non-UTF-8 byte drives assign_umi_groups_impl into the Err branch, and the assertions verify accepted_templates rollback while discard counters are preserved. Marking as addressed.

@nh13

nh13 commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

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 force-pushed the nh/audit-2-sort-data branch from 8364590 to 1ad6381 Compare July 12, 2026 17:14
Base automatically changed from nh/audit-2-sort-data to feat-runall July 12, 2026 17:29
nh13 added 5 commits July 12, 2026 13:32
… path too

The read-structure guard (total template reads must be 1 or 2, i.e. a valid SAM
template) lived only in the standalone Extract command's validate; the
chain/runall path (ChainBuilder::add_extract) skipped it and would silently emit
malformed BAM templates (0 template reads -> no sequence; 3+ -> more segments
than R1/R2 can hold). Extract the check into a shared
extract::validate_template_count and call it from both the standalone command and
add_extract. Adds an rstest case table (1/2 accepted, 0/3 rejected).
…uped input

On a name mismatch the zipper compared the mapped and unmapped head names with
natural_compare and errored if the mapped head sorted first. That assumes
queryname-SORTED input, but the tool documents (and fgbio ZipperBams supports)
query-GROUPED input too — records with the same name consecutive, but groups in
any order. For grouped input a later group can natural-sort before the current
one, so the guard spuriously rejected valid input when the mapped BAM was merely
missing a read. Match fgbio: compare by name equality only; on mismatch emit the
unmapped read as unmapped-only and advance. A genuinely orphaned mapped read is
still caught by the existing end-of-stream 'mapped reads remaining' check. Adds a
regression test with grouped, non-natural-order input and a missing mapped read.
… of dispatch-panicking

Clip and Dedup group the record stream internally (GroupBam / GroupByPosition),
so they consume a DecodedRecordBatch tail. An ordering-valid but type-incompatible
chain (e.g. Group -> Clip, where Group emits BamTemplateBatch) passed the
stage-ordering validators but built a mis-typed, type-erased pipeline that
panicked at dispatch. Guard chain_tail_kind == DecodedRecordBatch at the top of
add_clip and add_dedup and bail with a clear message otherwise. Latent today (no
CLI/runall constructs these specs) but defends the chain builder. Adds a
build-time Group -> Clip rejection test.
…o avoid under-counting RSS

Template had two divergent size methods: heap_size() (used by the typed-step
pipeline's byte-bounded queues via BamTemplateBatch) summed logical record
len() + name.len(), while MemoryEstimate::estimate_heap_size() summed record
capacity() + Vec<RawRecord> overhead + name capacity(). The len-based heap_size
under-counts over-allocated buffers and ignores container overhead, so
--max-memory under-estimates real RSS and the pipeline can buffer past its budget
and risk OOM — and it was inconsistent with DecodedRecordBatch, which already
sizes by capacity. Make heap_size compute the true allocated footprint and have
estimate_heap_size delegate to it (single source of truth). Adds a regression
test (spare-capacity name -> heap_size counts capacity and equals
estimate_heap_size).
…unt + sort-order docs)

Two divergences between group's single-threaded fast path and the chain path:

- Metrics (V3): on UMI-assignment failure the chain path drops the whole group
  and zeroes its accepted_templates, but the single-threaded path merged this
  group's filter metrics (including accepted_templates) before assignment and
  only logged + returned on failure, over-counting accepted_records. Roll back
  this group's accepted_templates on failure to match the (empty) output and the
  chain path; the genuine discard counters are kept.

- Help (V4): the long-about claimed 'Accepts reads in any order (including
  unsorted)', but both paths hard-reject input that is not template-coordinate
  sorted (or queryname sorted with --allow-unmapped). Unlike fgbio, fgumi does
  not re-sort internally. Correct the help to state the actual requirement.
@nh13
nh13 force-pushed the nh/audit-3-parity branch from 8f5b2ae to a1323bd Compare July 12, 2026 17:36
@nh13
nh13 temporarily deployed to github-actions July 12, 2026 17:36 — with GitHub Actions Inactive
@nh13

nh13 commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

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 merged commit 6362996 into feat-runall Jul 13, 2026
11 checks passed
@nh13
nh13 deleted the nh/audit-3-parity branch July 13, 2026 02:38

This branch was previously deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant