- authentication-methods: document completeAuth mode param ("redirect"|"json");
clarify add-passkey nudge (no dismiss mechanism, disappears on passkey add)
- journal-auth: session maxAge is 30 days; terms allow-list uses /legal/ prefix
matching (broader than fixed list of paths)
- session-notes: mark awareness isolation and UndoManager isolation as not yet
implemented (shared instances in current code)
- activity-feed: add fan-out scenario for visibility change to public
- explore: note that ?perPage is not yet implemented (hardcoded page size)
- multi-day-routes: add per-day GPX track split scenario (splitByDays option);
document overnight vs isDayBreak naming gap
- osm-poi-overlays: debounce is 800ms + 2000ms min interval (not 500ms);
retry is not automatic (fires on next viewport change)
- brouter-integration: rate limit corrected to 300/hour; add segment-cache
requirement (client caches per-pair segments)
- wahoo-route-push: OAuth state shape uses camelCase (returnTo, pushAfter
object) not snake_case with push_after boolean
- komoot-import: document noop adapter / ConnectedServiceManager bypass;
note four Komoot-specific routes that bypass the generic OAuth framework
- background-jobs: exponential backoff not wired (retryLimit only); add SIGINT
- connected-services: add revoked status; name ConnectionNotActiveError
- infrastructure: add INTEGRATION_SECRET and SENTRY_DSN to env var lists;
split secret decryption scenario by workflow (cd-apps vs cd-infra)
- secret-management: correct CD decryption — cd-apps only decrypts app.env;
cd-infra decrypts both
- transactional-emails: welcome email is async (pg-boss job); magic-link email
includes 6-digit numeric code
- journal-route-detail: websites are https: links (not mailto:); opening_hours
is also displayed
- local-dev-environment: add mobile app (Expo) dev commands
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
119 lines
6.7 KiB
Markdown
119 lines
6.7 KiB
Markdown
## Purpose
|
|
|
|
Route computation between waypoints via the BRouter HTTP API, including routing host election, result broadcasting via Yjs, profile selection, and rate-limited proxying.
|
|
## Requirements
|
|
### Requirement: Route computation from waypoints
|
|
The Planner SHALL compute a route between ordered waypoints by calling the BRouter HTTP API with tiledesc enabled and returning the result as an EnrichedRoute, preserving per-point elevation, surface data, and segment boundary indices.
|
|
|
|
#### Scenario: Compute route with two waypoints
|
|
- **WHEN** the routing host submits two waypoints (start, end) with profile "trekking"
|
|
- **THEN** the BRouter API returns a route within 2 seconds
|
|
|
|
#### Scenario: Compute route with via points
|
|
- **WHEN** the routing host submits three or more waypoints
|
|
- **THEN** the BRouter API returns a route passing through all waypoints in order
|
|
|
|
#### Scenario: Per-point elevation preserved
|
|
- **WHEN** BRouter returns GeoJSON with 3D coordinates [lon, lat, ele]
|
|
- **THEN** the merged route response SHALL preserve elevation values for every coordinate point
|
|
|
|
#### Scenario: Segment boundaries tracked
|
|
- **WHEN** a route with N waypoints is computed (N-1 segments)
|
|
- **THEN** the response SHALL include an array of coordinate indices marking where each waypoint-to-waypoint segment begins
|
|
|
|
#### Scenario: Surface data extracted
|
|
- **WHEN** BRouter returns tiledesc messages with WayTags containing surface information
|
|
- **THEN** the response SHALL include a surface type string per coordinate point extracted from the WayTags (e.g., "asphalt", "gravel", "path")
|
|
|
|
### Requirement: Routing host election
|
|
The Planner SHALL elect one participant per session as the "routing host" who is responsible for sending waypoint changes to BRouter. Only the host SHALL make BRouter API calls.
|
|
|
|
#### Scenario: Initial host assignment
|
|
- **WHEN** a session is created
|
|
- **THEN** the session creator is assigned as the routing host via Yjs awareness state
|
|
|
|
#### Scenario: Host failover
|
|
- **WHEN** the current routing host disconnects
|
|
- **THEN** failover is immediate via deterministic Yjs clientID election: the client with the lowest remaining ID becomes host instantly
|
|
|
|
### Requirement: Route broadcast
|
|
The routing host SHALL store computed route results in the Yjs document so that all participants receive route updates automatically.
|
|
|
|
#### Scenario: Route update propagation
|
|
- **WHEN** the routing host receives a new route from BRouter
|
|
- **THEN** the route GeoJSON is stored in a Y.Map field and all participants see the updated route on their maps
|
|
|
|
### Requirement: Profile selection
|
|
The Planner SHALL support selecting a routing profile that determines how BRouter computes the route.
|
|
|
|
#### Scenario: Switch routing profile
|
|
- **WHEN** a user changes the routing profile (available profiles: `trekking`, `fastbike`, `safety`, `shortest`, `car`)
|
|
- **THEN** the profile change syncs via Yjs and the routing host recomputes the route
|
|
|
|
### Requirement: BRouter API proxy
|
|
The Planner backend SHALL proxy all BRouter API calls. Clients SHALL NOT communicate with BRouter directly. The proxy SHALL attach a `X-BRouter-Auth: <token>` header to every upstream request using the `BROUTER_AUTH_TOKEN` environment variable.
|
|
|
|
#### Scenario: Proxied route request
|
|
- **WHEN** the routing host client requests a route computation
|
|
- **THEN** the Planner backend forwards the request to BRouter over the configured upstream URL with the `X-BRouter-Auth` header set, applies rate limiting, and returns the response
|
|
|
|
#### Scenario: Missing auth token at startup
|
|
- **WHEN** the Planner starts in production without `BROUTER_AUTH_TOKEN` set
|
|
- **THEN** the Planner logs a fatal error and refuses to start
|
|
|
|
### Requirement: Rate limiting
|
|
The Planner backend SHALL rate limit BRouter API calls to prevent abuse. See `rate-limiting` spec for the authoritative limit values.
|
|
|
|
#### Scenario: Rate limit exceeded
|
|
- **WHEN** a session exceeds 300 route computations per hour (the `DEFAULT_MAX_REQUESTS` value)
|
|
- **THEN** subsequent requests receive a 429 response with a Retry-After header
|
|
|
|
### Requirement: Client-side segment cache
|
|
The Planner client SHALL cache BRouter responses per waypoint-pair so that only changed segments are re-fetched when waypoints are added or moved.
|
|
|
|
#### Scenario: Only changed segments re-fetched
|
|
- **WHEN** a user moves one waypoint in a multi-waypoint route
|
|
- **THEN** only the two segments adjacent to the moved waypoint are re-fetched from the proxy
|
|
- **AND** all other segments are served from the client-side cache without a network request
|
|
|
|
#### Scenario: Cache key is waypoint-pair coordinates
|
|
- **WHEN** the same start/end coordinate pair appears in a new route
|
|
- **THEN** the cached segment result is reused without a BRouter call
|
|
|
|
### Requirement: BRouter Docker deployment
|
|
BRouter SHALL run as a Docker container on a dedicated Hetzner host reached over a private Hetzner vSwitch, with planet-wide RD5 segments mounted as a volume. The BRouter container SHALL NOT be exposed on any public network interface.
|
|
|
|
#### Scenario: BRouter container starts
|
|
- **WHEN** the `docker compose up -d` command runs in `~trails/brouter/` on the dedicated host
|
|
- **THEN** the BRouter container is reachable only on the vSwitch-bound IP and can compute routes using the mounted planet-wide RD5 segments
|
|
|
|
#### Scenario: Public network isolation
|
|
- **WHEN** a request is sent to the dedicated host's public IP on the BRouter port
|
|
- **THEN** the request is refused at the host firewall or times out; BRouter does not respond
|
|
|
|
#### Scenario: JVM memory sizing
|
|
- **WHEN** the BRouter container starts
|
|
- **THEN** the JVM is launched with `-Xmx8g` (or equivalent) so that the heap does not exceed 8 GB on the 32 GB host
|
|
|
|
### Requirement: BRouter routing with constraints
|
|
Route computation SHALL include no-go area polygons as avoidance constraints.
|
|
|
|
#### Scenario: Route with no-go areas
|
|
- **WHEN** the routing host computes a route and no-go areas exist
|
|
- **THEN** the BRouter request includes nogo parameters for each polygon
|
|
|
|
### Requirement: Shared-secret auth on BRouter
|
|
The BRouter deployment SHALL require a shared-secret header on every request. Requests without a valid `X-BRouter-Auth` header SHALL be rejected before reaching the BRouter process.
|
|
|
|
#### Scenario: Valid token
|
|
- **WHEN** a request arrives at the BRouter host with the correct `X-BRouter-Auth` header
|
|
- **THEN** the Caddy sidecar forwards the request to BRouter and returns its response
|
|
|
|
#### Scenario: Missing or wrong token
|
|
- **WHEN** a request arrives without an `X-BRouter-Auth` header or with an incorrect value
|
|
- **THEN** the Caddy sidecar responds with HTTP 403 and does not forward the request to BRouter
|
|
|
|
#### Scenario: Token not logged
|
|
- **WHEN** Caddy emits an access log line for a BRouter request
|
|
- **THEN** the `X-BRouter-Auth` header value is redacted or omitted from the log line
|
|
|