Agent-Logs-Url: https://github.com/trails-cool/trails/sessions/ad77fee7-e03f-4695-86e2-1a6cb3c28977 Co-authored-by: stigi <13815+stigi@users.noreply.github.com>
7.7 KiB
7.7 KiB
Spec ↔ Implementation Drift Report
This document lists all known drifts between the OpenSpec specifications in openspec/specs/ and the actual implementation. Each drift is categorized by severity.
Generated: 2026-04-01
Critical Drifts
These drifts represent missing or fundamentally different functionality.
1. PostGIS geometry column is never populated
- Spec:
openspec/specs/route-management/spec.md - Requirement: "Route geometries SHALL be stored as PostGIS LineString geometries extracted from the GPX"
- Implementation: The
geomcolumn exists in thejournal.routesandjournal.activitiesdatabase schema (packages/db/src/schema/journal.ts), but no code ever populates it. Route creation inapps/journal/app/lib/routes.server.tsand activity creation inapps/journal/app/lib/activities.server.tsinsert rows without setting thegeomfield. GPX coordinates are parsed for stats but never converted to a PostGIS LineString. - Impact: Spatial queries (e.g. route discovery via bounding box) are impossible. The
route-discoverychange depends on this working.
2. Planner metrics gauges are defined but never updated
- Spec:
openspec/specs/observability/spec.md - Requirement: Planner exposes
planner_active_sessionsandplanner_connected_clientsPrometheus gauges reflecting live counts. - Implementation: Both gauges are registered in
apps/planner/app/lib/metrics.server.ts, and helper functionsgetDocCount()andgetClientCount()exist inapps/planner/app/lib/yjs-server.ts, but nothing ever calls.set(),.inc(), or.dec()on these gauges. They always report0. - Impact: Grafana dashboards and alerts relying on session/client counts show no data.
Moderate Drifts
These drifts represent implementation choices that differ from spec but the feature still works.
3. Email provider is SMTP/Nodemailer, not Resend
- Spec:
openspec/specs/transactional-emails/spec.md - Requirement: "Production via Resend provider"
- Implementation:
apps/journal/app/lib/email.server.tsusesnodemailerwith a genericSMTP_URLconnection string. There is no Resend SDK integration. TheSMTP_FROMenv var controls the sender address. - Impact: Functional — emails are sent correctly. The provider is different from what the spec documents, which could cause confusion during onboarding or incident response.
4. No-go areas are converted to circles for BRouter, not passed as polygons
- Spec:
openspec/specs/no-go-areas/spec.md - Requirement: "BRouter request includes nogo parameters for each polygon"
- Implementation:
apps/planner/app/lib/brouter.ts(noGoAreasToParam()) converts each polygon to a circle by computing the centroid and using the maximum distance from centroid to any vertex as the radius. BRouter receiveslon,lat,radiusparameters, not polygon coordinates. - Impact: Routing avoidance is approximate. Elongated or concave polygons may not be accurately represented as circles, allowing routes to pass through parts of the drawn area.
5. Infrastructure server type is cx23, not CX21
- Spec:
openspec/specs/infrastructure/spec.md - Requirement: "CX21 server (2 vCPU, 4 GB RAM, 40 GB SSD)"
- Implementation:
infrastructure/terraform/main.tfspecifiesserver_type = "cx23", which is a larger instance. - Impact: Higher cost than spec anticipates. The application works correctly — the server is simply more powerful than specified.
6. Secret management uses two files instead of one
- Spec:
openspec/specs/secret-management/spec.md - Requirement: Single SOPS-encrypted file
infrastructure/secrets.env - Implementation: Two SOPS-encrypted files exist:
infrastructure/secrets.app.env(application secrets) andinfrastructure/secrets.infra.env(infrastructure secrets). The.sops.yamlcreation rule matchessecrets\..*\.env$. - Impact: The split is a reasonable evolution for separation of concerns. CD workflows decrypt and merge both files at deploy time. The spec should be updated to reflect this.
7. BRouter host failover is immediate, not "within 5 seconds"
- Spec:
openspec/specs/brouter-integration/spec.md - Requirement: "Host failover on disconnect (within 5 seconds)"
- Implementation:
apps/planner/app/lib/host-election.tsuses deterministic client-ID-based election. When the host disconnects, the Yjs awareness change event fires immediately and the client with the lowest remaining ID becomes host. There is no 5-second delay or timeout mechanism. - Impact: The implementation is actually better than the spec — failover is instant rather than delayed. The spec's "within 5 seconds" was likely a maximum latency target, not a deliberate delay.
Minor Drifts
These drifts are small deviations that don't affect functionality.
8. Health endpoint includes extra version field
- Spec:
openspec/specs/observability/spec.md - Requirement: Health response is
{ "status": "ok"|"degraded", "db": "connected"|"unreachable" } - Implementation: Both
apps/journal/app/routes/api.health.tsand the Planner'sserver.tsinclude an additionalversionfield (process.env.SENTRY_RELEASE ?? "dev") in the response. - Impact: Additive — does not break spec consumers. Useful for debugging which version is deployed.
9. BRouter request duration metric is not in spec
- Spec:
openspec/specs/observability/spec.md - Requirement: Lists
http_request_duration_seconds,planner_active_sessions, andplanner_connected_clientsas custom metrics. - Implementation: The Planner also registers
brouter_request_duration_secondshistogram inapps/planner/app/lib/metrics.server.ts, which is not mentioned in the spec. - Impact: Additive — extra observability. Spec should be updated to document this metric.
10. Routing profiles are broader than spec examples
- Spec:
openspec/specs/brouter-integration/spec.md - Requirement: Spec scenarios mention
trekkingandshortestprofiles. - Implementation:
apps/planner/app/components/ProfileSelector.tsxoffers 5 profiles:trekking,fastbike,safety,shortest,car. - Impact: Additive — more profiles available than spec examples suggest. The spec is under-specified rather than contradicted.
11. Planner session max age ceiling is not enforced
- Spec:
openspec/specs/planner-session/spec.md - Requirement: "Session auto-expiry (7 days default, max 30 days)"
- Implementation:
apps/planner/app/lib/sessions.tshasexpireSessions(maxAgeDays: number = 7)with a configurable parameter, but there is no hard ceiling at 30 days. Callers could pass any value. - Impact: In practice the default 7-day value is always used. The 30-day max is a spec constraint that isn't enforced in code.
Spec Coverage Gaps
These are areas where the spec describes features implemented in a different location than expected.
12. Map package contains only core components; interactive features live in Planner app
- Spec:
openspec/specs/shared-packages/spec.md - Requirement:
@trails-cool/mapprovides "React Leaflet components (MapView, RouteLayer)" - Implementation:
packages/map/src/containsMapViewandRouteLayeras specified. However, interactive map features (ghost markers, route drag-reshape, cursor rendering, no-go area drawing, colored routes, elevation chart) are all implemented directly inapps/planner/app/components/rather than in the shared package. - Impact: These features are Planner-specific and not reused by the Journal app, so placing them in the app is a reasonable architecture decision. The spec is not violated — it only requires MapView and RouteLayer in the shared package.