Drop only the field when compressed DATA cannot be decoded

KT
19h ago
+684-28

Stacked on #27 (SOW-0176). Merge after it.

A compressed DATA object that cannot be decoded now costs only its field, not the whole entry, in Go and Rust:

  • Entry reads, payload visitors, entry enumeration, facade data getters, Explorer/Netdata facets and the Rust log-query row projection skip the field.
  • Unique-value enumeration and --verify still report the damage, as systemd does.
  • Rust: the "bad DATA behind one entry item" rule moves to JournalError::is_recoverable_entry_data in journal-core, so the SDK and journal-engine share one owner.

Behavior against a systemd 262 oracle on files with one corrupted compressed field:

Codecsystemd 262Go and Rust
zstd, lz4rows kept, field droppedsame
xzread fails (Cannot allocate memory)rows kept, field dropped (intended difference, documented)

run_reader_hardening.py now expects stock's rows for the corrupt and oversized zstd streams and checks them against stock -o export; strict verification still rejects those files.

Validation: Go and Rust unit tests, reader hardening, compression, verify, mixed directory, live and query matrices, docs examples and the duplication gate.


Summary by cubic

A compressed DATA object that cannot be decoded now costs only its field, not the whole entry, matching systemd 262 for zstd and lz4. Entry reads, payload visitors, entry enumeration, facade data getters, Explorer/Netdata facets, and the Rust log-query row projection skip the damaged field, while unique-value enumeration and --verify still report it; corrupt xz drops the field where systemd fails the whole read (documented difference).

Refactors

  • The Rust bad-DATA rule moved to JournalError::is_recoverable_entry_data in journal-core, giving the SDK and journal-engine a single owner.
  • The Rust tests share the damaged compressed entry setup between the field-drop and Explorer tests.

Bug Fixes

  • The hardening oracle now parses stock -o export output and compares per-row realtime and payloads instead of row counts, asserting the damaged MESSAGE field is dropped.