Eliminates the silent-failure pattern where a route/activity row could be committed with gpx IS NOT NULL but geom IS NULL if the PostGIS write failed after the row insert. - New gpx-save.server.ts owns all GPX validation (GpxValidationError, validateGpx) and PostGIS geometry writes (writeGeom, tx-aware) - createRoute, updateRoute, createActivity, createRouteFromActivity all wrapped in db.transaction() covering row + geom + version snapshot - demo-bot uses createRoute/createActivity instead of raw inserts; errors propagate loudly - Callback endpoint returns 400 for GpxValidationError instead of 401 - ADR-0006 documents the invariant for future explorers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.2 KiB
gpx-save Specification
Purpose
Atomic GPX validation and PostGIS geometry persistence for routes and activities. Every write of a GPX track in the Journal SHALL validate the track and persist the row + geometry in a single transaction. Silent geometry failures are not permitted.
Requirements
Requirement: GPX validation before any DB write
The Journal SHALL validate any GPX string before writing a route or activity row. Validation SHALL confirm the GPX parses successfully, contains at least 2 track points, and all track points have coordinates within valid ranges (latitude −90..90, longitude −180..180). Validation SHALL throw GpxValidationError on failure.
Scenario: Valid GPX proceeds to save
- WHEN a caller passes a GPX string with ≥2 track points and valid coordinates to
createRoute,updateRoute,createActivity, orcreateRouteFromActivity - THEN the save proceeds and no
GpxValidationErroris thrown
Scenario: GPX with fewer than 2 track points is rejected
- WHEN a caller passes a GPX string that produces fewer than 2 track points
- THEN
GpxValidationErroris thrown before any DB write occurs
Scenario: GPX with out-of-range coordinates is rejected
- WHEN a caller passes a GPX string containing coordinates outside valid ranges
- THEN
GpxValidationErroris thrown before any DB write occurs
Scenario: Unparseable GPX is rejected
- WHEN a caller passes a malformed GPX string that cannot be parsed
- THEN
GpxValidationErroris thrown before any DB write occurs
Requirement: Atomic row + geometry persistence
Every route or activity save that includes a GPX string SHALL wrap the row write, PostGIS geometry write, and version snapshot in a single database transaction. If any step fails, the entire transaction SHALL be rolled back — a route or activity row with a non-null gpx column and a null geom column SHALL NOT be possible through the normal save path.
Scenario: Successful save stores row and geometry atomically
- WHEN
createRouteis called with valid GPX - THEN the
routesrow, thegeomcolumn update, and therouteVersionsrow are all committed in a single transaction
Scenario: PostGIS failure rolls back the row insert
- WHEN the PostGIS geometry write fails during a
createRoutecall - THEN the transaction is rolled back and no
routesrow is persisted
Scenario: Route update is atomic
- WHEN
updateRouteis called with valid GPX - THEN the row update, geometry update, and new version snapshot are committed atomically
Scenario: Activity save is atomic
- WHEN
createActivityis called with valid GPX - THEN the
activitiesrow andgeomcolumn update are committed atomically
Requirement: Loud failure on geometry errors
The Journal SHALL NOT swallow PostGIS geometry write errors. Any failure during the geometry write SHALL propagate as a thrown exception, causing the enclosing transaction to roll back and the error to surface to the caller.
Scenario: Geometry write error surfaces to caller
- WHEN the PostGIS
UPDATE geomstatement fails - THEN an exception is thrown, the transaction is rolled back, and the caller receives the error
Scenario: demo-bot route creation fails loudly
- WHEN
createRouteis called from the demo-bot and the PostGIS write fails - THEN the exception propagates — no partial route row is committed
Requirement: Single gpx-save module owns geometry persistence
The Journal SHALL have exactly one module responsible for GPX validation and PostGIS geometry writes: apps/journal/app/lib/gpx-save.server.ts. No other module SHALL call PostGIS geometry update statements directly or implement GPX validation independently.
Scenario: activities.server.ts uses gpx-save for geometry
- WHEN
createActivityorcreateRouteFromActivitywrites geometry - THEN the write goes through the
gpx-savemodule, not an inline raw SQL statement
Scenario: demo-bot uses createRoute and createActivity
- WHEN the demo-bot creates synthetic routes and activities
- THEN it calls
createRouteandcreateActivityrather than issuing raw inserts and callingsetGeomFromGpxdirectly