Skip to content

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.