Bug b003 — rust-yaml fork `RoundTripDocument::parse_all` errors on a trailing `...` after a block collection
Status: Resolved — moot (2026-07-10). The rust-yaml fork RoundTripDocument backend was removed when yqr consolidated on noyalib (yqr-m005), so this fork bug no longer affects yqr. (The upstream fork issue itself is unchanged.)
Severity: Medium — breaks the a001 byte-for-byte identity of --engine rust-yaml for any stream ending in a ... document-end marker after a top-level block collection; the default pipeline and other engine inputs are unaffected
Owner: yqr maintainers
Last updated: 2026-07-04
Affects: the --engine rust-yaml fidelity read path (yqr-f003); irrelevant to the default pipeline and to --engine noyalib
Component: the rust-yaml fork’s roundtrip::document_boundaries / RoundTripDocument::parse_all (zoosky/rust-yaml branch feat/roundtrip-document; the rust-yaml#73 substrate)
Related: yqr-f003 (backend A), yqr-b001 §8 (#73), yqr-m002 §6.1 (document-boundary policy)
1. Summary
Adversarially reviewing backend A surfaced a document-segmentation bug in the
fork’s RoundTripDocument::parse_all: a stream that ends with a ...
document-end marker after a top-level block collection is rejected with a
leaked internal error instead of parsing. Because it fails in parse_all, the
whole stream is rejected before evaluation, so every filter (identity and
projections) aborts — the a001 north-star (yqr --engine rust-yaml '.' f ==
cat f) is impossible for such a file.
$ printf 'a: 1\n...\n' | yqr --engine rust-yaml '.'
yqr: io error: failed to parse YAML input: Error at line 1, column 1: \
internal document accounting mismatch: 2 byte ranges, 1 span trees, 1 values
$ printf 'a: 1\n...\n' | yqr '.' # default pipeline: fine
a: 1
The input is textbook-valid YAML (one block mapping terminated by an explicit
... marker), which the default pipeline handles.
2. Root cause
parse_all cross-checks three per-document counts and refuses on any mismatch:
if boundaries.len() != trees.len() || trees.len() != values.len() { return Err(Error::parse(/* "internal document accounting mismatch: N byte ranges, M span trees, K values" */)); }
For a: 1\n...\n the composer yields 1 span tree and 1 value, but
document_boundaries returns 2 byte ranges: [0, input_len]. The trailing
... after block content leaves a phantom boundary at EOF (an empty
[input_len, input_len] range) with no matching tree or value, tripping the
guard.
Confirmed specificity (from the review):
- Fails (exit 5):
a: 1\n...\n(block map),- a\n- b\n...\n(block seq),a: {b: 1}\n...\n(block map with nested value). - Works (exit 0):
42\n...\n,"hi"\n...\n(scalars);{}\n...\n,{a: 1}\n...\n(flow collections);a: 1\n...\n---\nb: 2\n(content follows the marker);a: 1\n(no marker).
The block-vs-flow/scalar asymmetry points at how the block-collection close
tokens interact with the boundary counter around the ... DocumentEnd.
3. Upstream ask (fork)
In roundtrip::document_boundaries (or the reconciliation in parse_all): do
not emit a boundary for an empty trailing region after a ... marker — an
explicit --- starts a new (possibly null) document, but a trailing ... only
closes the current one. The boundary count must agree with the composer’s
document count for a stream ending in ... after a block collection. Add
a: 1\n...\n and - a\n- b\n...\n to parse_all’s round-trip cases.
Per yqr-m002 §6.1, the last document extends to EOF including trailing trivia,
so a: 1\n...\n is a single document whose slice is the whole input.
4. yqr-side handling (shipped in f003)
- Documented limitation.
yqr-f003and the README--enginenotes record that backend A currently rejects a trailing...after a block collection. - Pinned as a test.
tests/fidelity_engine_rustyaml.rs(document_end_marker_after_block_is_a_known_limitation) asserts the current error, so a future fork bump that fixes this flips the test and prompts turning it into a byte-for-byte identity assertion. - No adapter work-around is attempted:
parse_allfails wholesale, so there is no faithful span to recover; consistent with how--engine noyaliberrors on inputs its parser rejects (yqr-f002§4a).
5. Acceptance criteria
Resolved when the fork’s parse_all accepts a trailing ... after a block
collection (byte-for-byte), the yqr git dependency is bumped to include the fix,
and the pinning test is converted to assert yqr --engine rust-yaml '.' f == cat f for a: 1\n...\n and - a\n- b\n...\n.