10. Quality requirements¶
Last reviewed: 2026-11-03
The quality goals, as a tree, with concrete scenarios. The seven goals below were derived from the ADRs and specs and accepted by the owner on 2026-11-03. Every number in a scenario is taken from the linked spec or ADR; where no target exists yet, the scenario says so.
Quality goals¶
In the order they are defended when goals collide. The first five are the top goals of section 1.
| # | Goal | In one sentence | Rooted in |
|---|---|---|---|
| Q1 | Privacy by consent | Nothing leaves the watch before Yes on the watch, and every user can erase everything. | privacy, ADR 006 |
| Q2 | Never lose a track | A recording survives offline use, crashes, restarts, repeated and out-of-order uploads. | track-sync, ingestion, ADR 017 |
| Q3 | Access safety | A user sees only their own and shared data; links end; public pages carry no ids. | ADR 015, sharing |
| Q4 | Trustworthy numbers | Derived values are recomputed from stored points; guesses are suggestions the skipper confirms. | calculations, ADR 009 |
| Q5 | Parity across watches | Garmin, Wear OS (and later Apple Watch) behave the same, or the gap is written down. | parity, specs/README |
| Q6 | Fast maps and trip pages | Long tracks open quickly because the page reads stored, simplified lines. | ADR 007 |
| Q7 | Low running cost | The beta runs on a small budget: own tiles, depth and places, heavy jobs at low priority. | ADR 004, ADR 011 |
Quality tree¶
flowchart LR
root(["SailEver quality"])
root --> trust["Trust"]
root --> rel["Reliability"]
root --> perf["Efficiency"]
root --> maint["Maintainability"]
trust --> Q1["Q1 Privacy by consent"]
trust --> Q3["Q3 Access safety"]
trust --> Q4["Q4 Trustworthy numbers"]
rel --> Q2["Q2 Never lose a track"]
perf --> Q6["Q6 Fast maps and trip pages"]
perf --> Q7["Q7 Low running cost"]
maint --> Q5["Q5 Parity across watches"]
Quality scenarios¶
| ID | Goal | Stimulus | Response | Measure |
|---|---|---|---|---|
| S1 | Q1 | The user records a track but never says Yes to "Send your trips to sailever.com?". | The watch records on the watch only and sends no request: no registration, pairing or upload. | Zero requests to sailever.com; one consent gate in front of every request (privacy § 1). |
| S2 | Q1 | A user deletes their account. | Everything tied to it is removed at once: devices, trips, tracks and track files, segments, boats, passkeys. No grace period. | account_erasure_test.rb fails when a table with a user_id or email column is not covered (privacy § 3). |
| S3 | Q2 | The watch has no connection when a segment closes, or the app crashes during the upload request. | The closed segment is written to the local queue before the request, so nothing is lost. It is sent at the next save point. | Queue 64 KB on Garmin, 16 MiB on Wear OS; a warning over 75 %; queued files are never deleted automatically (track-sync § Local queue). |
| S4 | Q2 | The same segment arrives twice, or segments arrive out of order. | Identity is (device_id, track_started_at, seq); a repeat answers 200 duplicate; a rerun replaces exactly its own points. |
Same stored points and totals after any order (ingestion). |
| S5 | Q2 | A segment fails to decode or collides with a FIT import. | It is quarantined with its error and its payload kept; an admin reprocesses it. | No segment is dropped silently; a sweep every 15 min re-queues segments still received (ingestion § Quarantine). |
| S6 | Q3 | A request names a record id that belongs to someone else. | The lookup goes through an access scope and answers 404. | A matrix test covers every record id from a request (ADR 015). |
| S7 | Q3 | A public trip link is opened after its owner stopped sharing, or after 30 days. | The page answers 410 with no trip details, for everybody including the owner. | Link lifetime 30 days; public page has no record ids in HTML, URLs or JSON (sharing § Rules). |
| S8 | Q4 | A late segment arrives, or the skipper trims a track. | All derived values (metrics, events, names, trip totals) are rebuilt from the stored points. | Reprocessing lands on the same numbers (ingestion § Purpose). |
| S9 | Q4 | The skipper opens "Analyze track" in the editor. | Engine and sail changes are suggested with their reliability; nothing is saved until the skipper saves. | 85 % balanced accuracy on one skipper's six days, about 70 % on 13 other trips; shown with the suggestion (ADR 009). |
| S10 | Q5 | A WatchApp change that is not platform-specific is made in one port. | The other port gets it in the same piece of work, or parity.md records the gap with its reason. |
mise doccheck gate reports a change in only one port (AGENTS.md §0). |
| S11 | Q6 | A skipper opens a trip with many long tracks. | The page reads stored map lines, not the points. | At most 10 000 points per trip map, 600 per trip card, rebuilt in milliseconds (ADR 007). No response-time target is set yet. |
| S12 | Q7 | A depth or tile build runs on the worker VM while uploads arrive. | Rails jobs always come first; the build gets 128 CPU shares against 1024. | Tiles cost about US$0.55 a month, budget under €2 (ADR 004, DEPLOYMENT § Jobs). |
Process gates that protect all goals (tests, quality gate, doccheck) are in AGENTS.md §0.