Skip to content

fix(stream): stop PriorityStreamer::analyze() from duplicating object-nested arrays on reconstruction - #397

Merged
bug-ops merged 1 commit into
mainfrom
fix/394-array-duplication-reconstruction
Aug 17, 2026
Merged

fix(stream): stop PriorityStreamer::analyze() from duplicating object-nested arrays on reconstruction#397
bug-ops merged 1 commit into
mainfrom
fix/394-array-duplication-reconstruction

Conversation

@bug-ops

@bug-ops bug-ops commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • PriorityStreamer::analyze()'s extract_patches emitted a Set patch carrying an object field's full cloned value, then unconditionally recursed into that same value. For a non-empty array field this produced a redundant Append patch for the same elements, so JsonReconstructor applied both and duplicated every object-nested array on reconstruction (e.g. {"items":[1,2,3]} round-tripped to {"items":[1,2,3,1,2,3]}).
  • Set patches now carry an array-emptied skeleton of the value (skeletonize_arrays) instead of a full clone, with the array's own Append patch supplying the data.
  • Each field's Set priority is hoisted to max(own priority, highest Append priority anywhere in its subtree) so a Set can never be applied after an Append it must precede — this closes three data-loss regressions an adversarial review round found in an earlier skeletonize-only attempt: same-path priority inversion, parent-object priority inversion (including a chunked-array tail variant), and permanent loss of arrays nested under JsonPath-unencodable keys.
  • Array priority for chunked arrays (arr.chunks(max_patch_size)) is now computed once for the whole array before chunking, keeping chunk order stable regardless of per-chunk priority differences.
  • Wire format is unchanged (PatchOperation/JsonPatch/PriorityStreamFrame shapes untouched) — fix-scoped change, not breaking.

Closes #394

Test plan

  • cargo +nightly fmt --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo nextest run --workspace --all-features --lib --bins (1019/1019 pass)
  • 19 round-trip regression tests added in crates/pjs-core/src/stream/priority.rs (analyze() -> JsonReconstructor -> assert structural equality), covering: exact issue repro, multi-entity payload, chunked arrays (including a divergent-chunk-priority case), nesting at depth, array-of-arrays, arrays inside array elements, empty arrays, top-level bare arrays, mixed payloads, and the three priority-inversion/unencodable-key regressions found during review
  • Independently fuzz-verified (~1000 adversarial + 10,211 randomized documents, 0 mismatches) during adversarial review
  • CHANGELOG.md updated under [Unreleased]/### Fixed

…-nested arrays on reconstruction

extract_patches's Object branch pushed a Set patch carrying the full
cloned value of a field, then unconditionally recursed into it. For a
non-empty array field this produced a redundant Append patch for the
same elements, so JsonReconstructor applied both and doubled every
object-nested array on reconstruction (e.g. {"items":[1,2,3]} became
{"items":[1,2,3,1,2,3]}).

Set patches now carry an array-emptied skeleton of the value
(skeletonize_arrays) instead of a full clone, with the array's own
Append patch supplying the data, and are priority-hoisted to
max(own priority, highest Append priority in their subtree) so a Set
can never be applied after an Append it must precede -- closing three
data-loss regressions an adversarial review round found in an earlier
skeletonize-only attempt (same-path and parent-object priority
inversion wiping already-appended data, and permanent loss of arrays
under JsonPath-unencodable keys). Array priority for chunked arrays is
now computed once for the whole array before chunking, keeping chunk
order stable regardless of per-chunk priority differences.

Wire format is unchanged; this is a fix-scoped change.

Closes #394
@bug-ops
bug-ops enabled auto-merge (squash) August 17, 2026 21:53
@github-actions github-actions Bot added documentation Documentation updates, README, guides core Changes to pjs-core crate performance Performance improvements and optimizations rust Rust language specific changes labels Aug 17, 2026
@github-actions

Copy link
Copy Markdown

WASM Bundle Size Report

Target Raw (KB) Gzipped (KB) Status
web 158 70 PASS
nodejs 158 70 PASS
bundler 158 70 PASS

@bug-ops
bug-ops merged commit e2e40f3 into main Aug 17, 2026
54 checks passed
@bug-ops
bug-ops deleted the fix/394-array-duplication-reconstruction branch August 17, 2026 21:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Changes to pjs-core crate documentation Documentation updates, README, guides performance Performance improvements and optimizations rust Rust language specific changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PriorityStreamer::analyze() + JsonReconstructor duplicates every non-empty array nested inside an object

1 participant