6. Runtime view¶
Last reviewed: 2026-11-03
How the blocks work together in the important scenarios. Each scenario states the flow and links to the spec that holds the details.
1. Pairing a watch, with consent¶
The consent question comes first. Without Yes the watch sends nothing, so none of the steps below start (privacy § 1).
sequenceDiagram
actor U as Skipper
participant W as Watch app
participant S as Server
participant B as Browser (/pair)
U->>W: opens Pair device
W->>U: "Send your trips to sailever.com?"
U->>W: Yes (stored on the watch only)
W->>S: POST /api/v1/devices/pairing_requests
S-->>W: 201 six-digit code + poll token, valid 10 min
W->>U: shows the code
loop every 5 s
W->>S: GET pairing_requests/poll
S-->>W: 202 pending
end
U->>B: enters the code (signed in)
B->>S: POST /pair
S->>S: device linked to the user, code consumed, old token cleared
W->>S: GET pairing_requests/poll
S-->>W: 200 paired + device token (delivered once)
W->>W: stores the token
Details: device-api, sync-and-pairing, ADR 003.
2. From a recording to a trip¶
sequenceDiagram
participant W as Watch app
participant A as API controller
participant Q as Solid Queue
participant I as TrackSegmentIngestJob
participant R as TrackRecomputeJob
participant D as DB and track file
participant B as Browser
W->>W: segment closes, written to the local queue
W->>A: POST /api/v1/tracks/segments (Bearer token)
A->>A: check envelope, header, CRC
A->>D: store raw segment
A-->>W: 201 (tides for a paired watch)
W->>W: delete the file from the queue
A->>Q: queue ingest job
Q->>I: run (one per track at a time)
I->>D: decode, replace this segment's points on the 5 s grid, find or create track and trip
I->>Q: queue recompute
Q->>R: run
R->>D: metrics, events, map line, names, trip aggregates
R-->>B: Turbo refresh of open trip pages
Details: track-sync, ingestion. Reprocessing is safe: a rerun replaces exactly its own points.
3. When something goes wrong with an upload¶
flowchart TB
up["Segment upload"] --> chk{"Server: header and CRC ok?"}
chk -->|"no"| qz["202, segment quarantined"]
chk -->|"yes"| st["Stored, 201, job queued"]
st --> dec{"Job: decodes? no FIT collision?"}
dec -->|"no"| qz
dec -->|"yes"| ok["Points stored, track recomputed"]
qz --> adm["Admin: Quarantined segments<br/>payload kept, Reprocess"]
adm --> st
st -.->|"job lost or failed: sweep every 15 min"| st
up --> net{"Watch: 2xx?"}
net -->|"yes"| del["File deleted from the queue"]
net -->|"other, or offline"| keep["File stays queued until the next save point"]
net -->|"401"| tok["Token deleted, files kept"]
Details: ingestion § Quarantine, track-sync § Upload rules.
4. Sharing a trip and opening it¶
sequenceDiagram
actor O as Owner or skipper
actor V as Visitor
participant S as Server
O->>S: opens Share (click)
S->>S: creates a public link, token of 22 characters, 30 days
O-->>V: sends /t/token
V->>S: GET /t/token
alt link works
S-->>V: 200 trip page, no ids, noindex
V->>S: opens the map: /t/token/replay
S-->>V: replay page, then the points endpoint by token
else ended or expired
S-->>V: 410 "This link has expired"
end
Details: sharing, trip-replay, ADR 010, ADR 016.
5. Background jobs and the nightly backup¶
flowchart LR
subgraph web["App VM"]
fb["Fallback worker<br/>realtime only"]
end
subgraph worker["Worker VM"]
rt["realtime<br/>ingest, recompute,<br/>FIT import, erasure"]
df["default<br/>wind, tides, sweep"]
bg["background<br/>OSM places, backup,<br/>cleanups"]
rec["Recurring schedule<br/>jobs role only"]
tb["Tile and depth builds<br/>128 CPU shares"]
end
rec -->|"every 15 min"| df
rec -->|"nightly 03:00 UTC"| bg
bg -->|"age-encrypted dump"| bk[("sailever-backups")]
fb -.->|"takes over realtime while the worker VM is down"| rt
rt --- df --- bg
Priority: realtime before default before background; the tile builds always yield to the Rails jobs (DEPLOYMENT § Jobs, ADR 011). If the worker VM is down for long, only the realtime queue moves; the others catch up afterwards.