From bbb729ffddec818ecd9782b6d12409ff61313d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 24 May 2026 11:20:05 +0200 Subject: [PATCH] Fix OpenSpec validation failures on high-severity specs - road-type-coloring: add proper ## Purpose/## Requirements structure (redirect file) - planner-journal-handoff: add inline #### Scenario: blocks to each requirement; add SHALL to JWT token requirement - osm-tile-overlays: add SHALL keyword to profile-aware requirement body - shared-packages: add #### Scenario: blocks to ui, api, db, jobs, sentry-config requirements Co-Authored-By: Claude Sonnet 4.6 --- openspec/specs/osm-tile-overlays/spec.md | 2 +- .../specs/planner-journal-handoff/spec.md | 42 ++++++++++++++++++- openspec/specs/road-type-coloring/spec.md | 15 +++++-- openspec/specs/shared-packages/spec.md | 20 +++++++++ 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/openspec/specs/osm-tile-overlays/spec.md b/openspec/specs/osm-tile-overlays/spec.md index 94f51af..7108caf 100644 --- a/openspec/specs/osm-tile-overlays/spec.md +++ b/openspec/specs/osm-tile-overlays/spec.md @@ -58,7 +58,7 @@ Each tile overlay SHALL display proper attribution when enabled. ### Requirement: Profile-aware overlay suggestions (not yet implemented) -`profileOverlayDefaults` is exported from `@trails-cool/map-core` but is not currently wired in the Planner app. The scenarios below describe the intended behaviour once wired. +The Planner SHALL auto-enable relevant tile overlays when the routing profile changes. This is not yet implemented; `profileOverlayDefaults` is exported from `@trails-cool/map-core` but not currently wired in the Planner app. The scenarios below describe the intended behaviour once wired. #### Scenario: Switch to cycling profile - **WHEN** the routing profile is changed to a cycling variant diff --git a/openspec/specs/planner-journal-handoff/spec.md b/openspec/specs/planner-journal-handoff/spec.md index ae38906..512be3c 100644 --- a/openspec/specs/planner-journal-handoff/spec.md +++ b/openspec/specs/planner-journal-handoff/spec.md @@ -49,6 +49,14 @@ When a Journal user triggers "Edit in Planner", the Journal action (`POST /api/r The Journal MUST return 502 if the Planner session creation request fails. +#### Scenario: Successful handoff initiation +- **WHEN** an authenticated route owner clicks "Edit in Planner" +- **THEN** the Journal POSTs to the Planner's `/api/sessions`, receives a session URL, and returns a redirect URL to the browser + +#### Scenario: Unauthenticated user +- **WHEN** an unauthenticated user triggers the edit-in-planner action +- **THEN** the Journal returns 401 + --- ### Requirement: Planner creates a session @@ -74,6 +82,14 @@ The Journal MUST return 502 if the Planner session creation request fails. ``` Omit `initialWaypoints`, `initialNoGoAreas`, and `initialNotes` when absent. +#### Scenario: Session created with GPX +- **WHEN** the Journal POSTs a valid GPX string to `/api/sessions` +- **THEN** the Planner parses waypoints, no-go areas, and notes and returns them in the 201 response + +#### Scenario: Invalid GPX silently ignored +- **WHEN** the Journal POSTs a malformed GPX string +- **THEN** the Planner creates an empty session and returns HTTP 201 with no initial planning data + --- ### Requirement: Planner session page initialises route data @@ -101,6 +117,14 @@ Population is gated on `waypoints.length === 0` to avoid duplicating data on rec The `callbackUrl`, `callbackToken`, and `returnUrl` are passed as props to `SessionView` → `SaveToJournalButton`. They are **not** stored in the Yjs document. +#### Scenario: Session page loads with route data +- **WHEN** a browser navigates to `/session/:id?waypoints=…¬es=…&returnUrl=…` +- **THEN** on first Yjs sync the document is populated with the provided waypoints and notes + +#### Scenario: Reconnect does not duplicate data +- **WHEN** the Yjs WebSocket reconnects and fires `synced` again +- **THEN** the initialization guard prevents re-inserting the initial data + --- ### Requirement: Planner saves the route back to the Journal @@ -127,6 +151,10 @@ The `callbackUrl`, `callbackToken`, and `returnUrl` are passed as props to `Sess 5. On success, show a "saved" confirmation and a "Return to Journal" link targeting `returnUrl`. 6. On failure, surface the error message from the response body. +#### Scenario: Save to Journal +- **WHEN** a user clicks "Save to Journal" +- **THEN** the Planner generates GPX from the current Yjs state and POSTs it to the callback URL with the Bearer token + --- ### Requirement: Journal callback endpoint writes a new version @@ -148,11 +176,19 @@ The `callbackUrl`, `callbackToken`, and `returnUrl` are passed as props to `Sess All responses include CORS headers so the browser can read them. +#### Scenario: Valid callback saves new version +- **WHEN** the Planner POSTs a valid GPX with a valid Bearer token to the callback endpoint +- **THEN** the Journal writes a new route version and returns `{ success: true }` + +#### Scenario: Expired or tampered token +- **WHEN** the JWT is expired or the signature is invalid +- **THEN** the Journal returns HTTP 401 + --- ### Requirement: JWT callback token -Tokens are HS256 JWTs signed with `JWT_SECRET` (env var; defaults to `"dev-jwt-secret-change-in-production"` in development). +The Journal SHALL issue HS256 JWTs signed with `JWT_SECRET` (env var; defaults to `"dev-jwt-secret-change-in-production"` in development). | Claim | Value | |---|---| @@ -164,6 +200,10 @@ Tokens are HS256 JWTs signed with `JWT_SECRET` (env var; defaults to `"dev-jwt-s The Planner stores the token opaquely in the `planner.sessions` table and presents it verbatim in the `Authorization: Bearer` header on callback. The Planner never inspects the token contents. +#### Scenario: Token claims verified on callback +- **WHEN** the callback endpoint receives a request +- **THEN** it verifies the HS256 signature, checks `route_id` matches the URL param, and confirms `permissions` includes `"write"` + --- ## Notes flow (round-trip) diff --git a/openspec/specs/road-type-coloring/spec.md b/openspec/specs/road-type-coloring/spec.md index a8718c2..714f2a7 100644 --- a/openspec/specs/road-type-coloring/spec.md +++ b/openspec/specs/road-type-coloring/spec.md @@ -1,7 +1,14 @@ -## Merged into `route-coloring` +## Purpose -All requirements previously in this spec have been incorporated into `route-coloring`, which describes all color modes (plain, elevation, grade, surface, highway/road-type, speed limit, smoothness, track type, cycleway, bike route) in a unified way. +This spec has been merged into `route-coloring`. See `openspec/specs/route-coloring/spec.md`. -See `openspec/specs/route-coloring/spec.md`. +## Requirements -This file is retained as a redirect so existing cross-references don't 404. +### Requirement: Road-type coloring is covered by route-coloring + +All road-type coloring requirements SHALL be found in `openspec/specs/route-coloring/spec.md`, which covers all color modes (plain, elevation, grade, surface, highway/road-type, speed limit, smoothness, track type, cycleway, bike route) in a unified way. This file is retained as a redirect so existing cross-references resolve correctly. + +#### Scenario: Cross-reference redirect + +- **WHEN** a reader follows a cross-reference to `road-type-coloring` +- **THEN** they are directed to `route-coloring` for the authoritative requirements diff --git a/openspec/specs/shared-packages/spec.md b/openspec/specs/shared-packages/spec.md index ad62c31..6a15f30 100644 --- a/openspec/specs/shared-packages/spec.md +++ b/openspec/specs/shared-packages/spec.md @@ -74,6 +74,10 @@ The `@trails-cool/map-core` package SHALL provide framework-free map constants a ### Requirement: UI component package The `@trails-cool/ui` package SHALL provide shared React components (buttons, layout primitives, form elements) styled with Tailwind CSS, used by both apps. +#### Scenario: Import shared component +- **WHEN** either app imports a button or layout component from `@trails-cool/ui` +- **THEN** it renders with Tailwind CSS styling consistent across both apps + ### Requirement: i18n package The `@trails-cool/i18n` package SHALL provide react-i18next configuration and translation strings for English (primary) and German. @@ -88,11 +92,27 @@ The `@trails-cool/i18n` package SHALL provide react-i18next configuration and tr ### Requirement: API contracts package The `@trails-cool/api` package SHALL define shared API contracts: endpoint URL constants, request/response types, pagination shapes, error codes, and API version. Both apps import from this package; neither defines its own duplicate API types. +#### Scenario: Import API types in Planner +- **WHEN** the Planner imports from `@trails-cool/api` +- **THEN** it has access to shared request/response types for the Journal API + ### Requirement: Database package The `@trails-cool/db` package SHALL provide the Drizzle ORM schema (all tables across `planner.*` and `journal.*` schemas), the database client factory, and migration helpers. The Journal app is the sole runtime consumer; the Planner references only `planner.*` tables. All schema changes flow through this package. +#### Scenario: Apply schema changes +- **WHEN** a developer runs `pnpm db:push` +- **THEN** Drizzle applies the schema from `@trails-cool/db` to the local PostgreSQL instance + ### Requirement: Background jobs package The `@trails-cool/jobs` package SHALL provide the pg-boss client factory (`createBoss`), the worker registration helper, and the `JobDefinition` type that each job exports. Apps construct boss instances from this package rather than importing pg-boss directly. +#### Scenario: Register a job worker +- **WHEN** the Journal app imports `@trails-cool/jobs` and calls `createBoss` +- **THEN** it obtains a configured pg-boss instance without importing pg-boss directly + ### Requirement: Sentry configuration package The `@trails-cool/sentry-config` package SHALL provide shared Sentry initialisation helpers used by both apps. It sets user context, attaches session IDs as tags, and configures source map upload. Apps call the shared helper from their Sentry entry points rather than configuring Sentry inline. + +#### Scenario: Initialise Sentry in Journal +- **WHEN** the Journal app's Sentry entry point calls the shared helper from `@trails-cool/sentry-config` +- **THEN** Sentry is configured with user context and source map upload without inline configuration