8. Cross-cutting concepts¶
Last reviewed: 2026-11-03
Rules and solutions that apply in many blocks. Each concept states the rule in a few lines and links to where it is specified or built.
Offline first, accept first, retry safely¶
- Watch: a segment is written to the local queue before the upload request, so a crash or no connection loses nothing. There is one attempt per trigger and no retry loop; a failed file stays queued until the next save point. The queue is 64 KB on Garmin and 16 MiB on Wear OS (track-sync).
- Server: the upload endpoint checks the envelope, header and CRC, stores the raw bytes and answers; decoding happens later in a job. Every segment has the identity
(device_id, track_started_at, seq), so a repeat answers200 duplicateand a rerun replaces exactly its own points (ingestion). - Jobs: track processing retries 5 times with growing waits; a sweep every 15 minutes queues segments still
received(ingestion § Sweep).
Errors and failure handling¶
- Bad input is parked, not dropped. A segment that fails its CRC, does not decode or collides with a FIT import is quarantined with its error and its payload; an admin reprocesses it (ingestion § Quarantine).
- Non-critical helpers fail quietly. For example a failed tide warm-up job is discarded; the next request asks again (
TideWarmJob). - Watch errors are never shown as data loss: on
401the app deletes its token and keeps the queued files (device-api). - Unexpected server errors go to Sentry (
sentry-rails, DSN from the environment) and to the log.
Logging and monitoring¶
- Logs go to STDOUT, tagged with the request id, at level
info(RAILS_LOG_LEVEL); sensitive parameters are filtered (filter_parameter_logging.rb). The owner reads them with Kamal:bin/kamal logs,bin/kamal jobs-logs(PRODUCTION_ACCESS). - Failed jobs show in the admin area, in Sentry and with the Server workflow's mode
jobs-status(admin). - Health: every host answers
/up(the Rails health check); Kamal's healthcheck is configured indeploy.yml. - Not in place: no uptime monitor or alerting is documented (section 11, K11).
Security¶
| Concern | Rule | Where |
|---|---|---|
| Sign-in | Passkeys and magic links, no passwords | auth |
| Watch access | Device token dt_…, only its SHA-256 hash is stored; revoked on re-pairing, unpairing or removal |
device-api |
| Authorisation | Every record id from a request goes through an access scope; a matrix test covers it | ADR 015 |
| Sharing | Tokens of 22 base58 characters, links end after 30 days, noindex, no ids on public pages |
sharing |
| Abuse | Per-IP rate limits on public pages and sign-in. Web pairing: 5 per 5 minutes. Device API: register 30 per hour, pairing request 10 and poll 120 per 10 minutes, segment upload 120 per hour (per device, else per IP) | RateLimitStore, api/v1 |
| Transport | HTTPS forced; PostgreSQL over TLS 1.3 with verify-full |
DEPLOYMENT § Postgres |
| Secrets | In the password manager, never in the repo; read through mise secret; deploys only from the GitHub job |
credentials, AGENTS.md §4 |
| Backups | Encrypted with age; the app's bucket key can write but not read or delete |
DEPLOYMENT § Backups |
Data lifecycle and privacy¶
- Consent and erasure: nothing leaves the watch before consent; deleting an account removes everything at once; a test fails when a table holding user data is not covered (privacy).
- Retention: cleanup jobs enforce the limits the privacy page states (never-paired devices 30 days, support requests and applications 12 months, ended links 30 days) (data-model § Cleanup jobs).
- Backup: every night at 03:00 UTC the main database and all track files are written encrypted to the
sailever-backupsbucket (kept 30 days); the files bucket keeps no versions, so the backup is the only second copy (DEPLOYMENT § Backups, ADR 017). A restore test script exists; the owner runs it.
Derived data is recomputed, never patched¶
All derived values (metrics, events, names, trip totals, map lines) are rebuilt from the stored points. Every path that changes points or trims a track must store the map line and run the trip aggregates again (ingestion § Purpose, ADR 007).
Vessel states and sailing calculations¶
The eight vessel states and the formulas for SOG, tides, barometer trend and night miles are defined once and used by the watch, the DataField and the server (vessel-state, calculations).
Testing and quality gates¶
| Gate | What | Command |
|---|---|---|
| Server | Tests and the quality checks; includes the account erasure, access matrix and job queue tests | mise server:check |
| Garmin | Builds with warnings on, unit tests | mise garmin:check |
| Wear OS | Build, unit tests, lint | mise android:check |
| Docs | Banned terms, links, arc42 shape, strict viewer build | mise check |
| Git | Secrets, forbidden files, commit messages, tags | mise guard |
All of them are the Definition of Done.
Internationalisation and domain language¶
The website is in English and German (i18n). Terms follow the glossary; mise doccheck flags banned words.
Watch UI conventions¶
visual-system for the watch, mobile conventions for the planned phone apps.