Files
FreeTLab c65497e58a
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m49s
docs(openspec): archive persistence-corruption-guard and promote its spec
Close the SDD cycle for the data-loss shielding change: verified pass
with warnings (0 critical, 10/10 scenarios with named tests, W1 fixed
post-verify), three stacked work units shipped plus the path-resolution
fix. The persistence-resilience capability spec is promoted to
openspec/specs/.
2026-07-11 17:34:36 +02:00

6.0 KiB

Spec: Persistence Corruption Guard

New Capability: persistence-resilience

Purpose

Cross-cutting invariants so a corrupt or schema-incompatible read of persisted user data (alarms, custom stations, EQ presets/device names) never silently destroys prior valid data: per-entry tolerant parsing, never-fabricate identity, and a degraded-read guard that suppresses automatic writes until a good read or an explicit user mutation restores write authority.

Requirements

Requirement: Per-entry tolerant parsing preserves valid siblings

When a persisted collection decodes at the top level but one entry is malformed, the system MUST skip and log only that entry (index/key/id + reason), retaining every other valid entry. A skipped entry MUST NOT cause any sibling to be lost, regardless of whether a normalizing rewrite of the survivors later occurs — that mechanism is a design-time decision; this requirement constrains only the observable outcome.

Scenario: alarms — one corrupt alarm among N — Dart-testable via SharedPreferences mock seeding
  • GIVEN alarmas_musicales_v1 has 3 valid alarms and 1 malformed entry
  • WHEN the config loads
  • THEN alarmas MUST contain exactly the 3 valid entries; corrupt one skipped + logged
Scenario: custom stations — one corrupt station among N — Dart-testable via injected temp-file fixture
  • GIVEN the custom-stations file has 2 valid stations and 1 entry missing required fields
  • WHEN custom stations load
  • THEN _emisorasCustom MUST contain exactly the 2 valid stations; corrupt one skipped + logged
Scenario: EQ maps — one corrupt value among N — Dart-testable via SharedPreferences mock seeding
  • GIVEN a persisted EQ map (per-station/per-device/matrix presets, device names) has 3 valid entries and 1 entry whose value fails to parse
  • WHEN that map loads
  • THEN it MUST contain exactly the 3 valid entries under their original keys; corrupt one skipped + logged

Requirement: Entry identity is never fabricated

An entry whose identity field (alarm id, station uuid) is missing, empty, or the wrong type MUST be treated as corrupt and skipped per the requirement above. The system MUST NOT synthesize a substitute identity. (EQ entries are keyed by their persisted map key, always present by construction — no scenario needed here.)

Scenario: alarms — entry with no id is skipped, never assigned one — Dart-testable via SharedPreferences mock seeding
  • GIVEN one persisted alarm entry has no id field
  • WHEN the config loads
  • THEN that entry MUST be absent, and no alarm with a freshly generated id MUST appear in its place
Scenario: custom stations — entry with no uuid is skipped, never assigned one — Dart-testable via injected temp-file fixture
  • GIVEN one persisted station entry has no uuid field
  • WHEN custom stations load
  • THEN that entry MUST be absent, and no station with a freshly generated uuid MUST appear in its place

Requirement: Degraded top-level read suppresses automatic writes until authority is restored

When a persisted payload's top-level decode fails entirely, in-memory state MAY be empty for the session, but the system MUST NOT let any non-explicit write reach disk while degraded. Suppression MUST lift on the next successful read or an explicit user mutation, whichever comes first (user intent wins).

Scenario: alarms — recalcularTodas writes nothing after a degraded read; explicit save restores authority — Dart-testable via SharedPreferences mock seeding
  • GIVEN alarmas_musicales_v1 is unparseable
  • WHEN the config loads and recalcularTodas() runs
  • THEN no write occurs; the original string stays on disk
  • AND WHEN the user calls guardarAlarma afterward
  • THEN the write reaches disk and later automatic writes resume
Scenario: custom stations — no automatic write after a degraded file; explicit add/remove restores authority — Dart-testable via injected temp-file fixture
  • GIVEN the custom-stations file is unparseable
  • WHEN custom stations load
  • THEN no write to the file occurs
  • AND WHEN the user calls agregarEmisoraCustom or eliminarEmisoraCustom
  • THEN that write reaches the file and normal writes resume
Scenario: EQ — no automatic write after a degraded key; explicit tweak restores authority — Dart-testable via SharedPreferences mock seeding
  • GIVEN one of the 4 EQ persistence keys is unparseable
  • WHEN the EQ config loads
  • THEN no guardarXxx write occurs
  • AND WHEN the user performs an explicit EQ tweak (e.g. guardarPresetDispositivo)
  • THEN that write reaches disk and normal writes resume for that key

Requirement: Healthy reads are behaviorally unchanged

For a payload that decodes fully and validly at container and entry level, this capability MUST NOT alter resulting state, timing, or persisted bytes versus pre-change behavior.

Scenario: existing suites lock byte-for-byte parity — Dart-testable (regression)
  • GIVEN the existing green suites (test/servicios/servicio_alarmas_*, test/estado/estado_radio_*, test/servicios/servicio_ecualizador_test.dart) seeded with well-formed data
  • WHEN they run unmodified against the changed code
  • THEN every assertion MUST still pass with no behavioral difference

Requirement: Diagnostics are developer-facing only

Every skipped entry and every degraded read MUST log with context (subsystem, key/id/index, reason) via the existing [PluriWave]-prefixed convention. This capability MUST NOT introduce any user-visible string or l10n key.

Scenario: skip and degraded-read events are logged, no l10n added — Dart-testable (log/hook assertion)
  • GIVEN a corrupt entry is skipped or a top-level decode fails
  • WHEN that event occurs
  • THEN a [PluriWave]-prefixed log line MUST identify the subsystem and reason, and no string MUST be added to lib/l10n/app_en.arb or any locale file

Localization

Not applicable — no user-visible copy is introduced by this capability; all diagnostics are developer-facing log lines only.