Files
punktfunk/crates
enricobuehler 9d58f4c170 fix(pf-vdisplay): one unreadable byte reverted the host to built-in display defaults, and one bad preset dropped the whole catalog
The policy layer folded every failure into "unconfigured", then wrote that emptiness back.

* **Any parse error reverted the WHOLE policy.** An unknown enum variant, a mistyped scalar, an
  EACCES or EIO — all became `Err(_) => None`, i.e. the host silently ran on built-in defaults with
  the operator's `display-settings.json` still sitting on disk. Parsing is now layered: strict
  first, then per-axis salvage so one unreadable axis costs only that axis, and only `NotFound` is
  quiet — EACCES/EIO warn loudly that the host is on defaults. `version` is read instead of being
  blindly rewritten to 1.

* **One malformed entry dropped the entire custom-preset catalog**, and the next CRUD atomically
  renamed the empty vector over the file. Entries are parsed one at a time now; a lossy load is
  flagged and refuses to overwrite.

* `sanitized()` clamped `max_displays` but never `KeepAlive::Duration.seconds`, so a PUT could pin a
  display for ~136 years — a deadline the reaper never reaches and a nonsense `expires_in_ms` in
  `/display/state`. Clamped to a day, in both `sanitized()` and `sanitize_preset_fields`, and
  sanitization now runs on LOAD as well as on write.

* The two stores' temp files had fixed names and no write lock, so concurrent saves could interleave
  serialize -> rename -> in-memory update. Unique suffixes, a lock, and the in-memory update ordered
  after the rename.

* `new_preset_id` never consulted the loaded entries for collisions.

* **Manual layout could place an unpinned display exactly on top of a pinned one**: the fallback was
  the unconditional auto-row prefix sum, blind to where prior members were pinned. Unpinned members
  now pack clear of the pins. Layout keys are canonicalized and unusable ones dropped at write time
  rather than persisted-and-ignored.

Adds 20 tests, all running on macOS: a 20k-round randomized property test asserting no unpinned
member ever overlaps a sibling (verified to fail against the pre-fix `arrange_manual`), the salvage
and quarantine paths, the clamps, and a field-count guard that fails the moment a 13th policy axis
appears without being wired into the merge path.

Note: `partial_json_fills_defaults` was renamed to `serde_defaults_fill_a_partial_document` with no
assertion weakened — it pins the FILE contract (an old settings file must still load), which is not
the mgmt PUT contract that sweep item 11.1 is about.
2026-08-11 08:48:47 +02:00
..