Skip to content

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 answers 200 duplicate and 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 401 the 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 in deploy.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-backups bucket (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.