Close single-ownership follow-ups against systemd

KT
2d ago
+1868-541

Stacked on #26 (SOW-0097). Merge after it.

Closes the follow-ups from the single-ownership work, each checked against systemd and delivered in Go and Rust:

  • --list-boots and -b match v262 on journals without _BOOT_ID DATA; callers opt into the old header fallback with BootListOptions, and listing errors are returned.
  • Verification keys follow the v262 grammar through one public SDK parser per language; the CLI copies are gone. Keys are judged per file, and a rejected key stops the run with v262's message.
  • An unreadable DATA object or a payload without = costs one field, not the entry, in both languages.
  • Go retention counts *.journal~ and deletes them first; Rust covers chain-named disposed files too.
  • Rust: open_files deduplicates, single-value maps keep the first repeated value, a missing main entry array is detected at open, and boot ranges stop at the visible tail.
  • Non-UTF-8 source names convert the same way in both languages.

Validation against a systemd 262 oracle built with sealing: Go and Rust unit tests, byte identity, verify, directory, mixed directory, live, query, reader hardening and parser parity matrices. A differential of 29 verification keys on sealed and plain files shows no exit-code differences.

Known remaining difference: v262 prints a => Validated from ... summary after a sealed PASS; that needs a new SDK result type and is tracked by SOW-0177.


Summary by cubic

Closes the single-ownership follow-ups by aligning Go and Rust journal behavior with systemd v262 across boot listing, verification keys, damaged entries, retention, and source-name handling.

  • --list-boots and -b now match v262 on journals without _BOOT_ID DATA: no boots are listed by default, and callers opt into the old header fallback with BootListOptions; listing errors are returned.
  • Verification keys follow the v262 grammar through one public SDK parser per language, replacing the CLI copies; keys are judged per file and a rejected key stops the run with v262's message.
  • An unreadable DATA object or a payload without = costs one field, not the entry, in both languages.
  • Go retention counts *.journal~ files and deletes them before archived history; Rust covers chain-named disposed files too.
  • Rust open_files deduplicates paths, single-value maps keep the first repeated value, a missing main entry array is detected at open, and boot ranges stop at the visible tail.
  • Non-UTF-8 source names convert the same way in both languages.

The one remaining difference is the => Validated from ... summary v262 prints after a sealed PASS; that needs a new SDK result type and is tracked in SOW-0177.