candle-annotator/openspec/changes/archive/2026-02-20-code-review-fix/specs/backend-api/spec.md
Marko Djordjevic 925e7284e3 Archive code-review-fix change and sync specs to main
- Synced 14 capability delta specs to main specs
- Created 6 new main specs: api-authentication, error-boundary, input-validation, security-headers, shared-types
- Updated 8 existing specs with security, validation, and performance requirements
- Archived change to openspec/changes/archive/2026-02-20-code-review-fix/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 08:54:59 +01:00

3.9 KiB

ADDED Requirements

Requirement: Generic error responses

All Next.js API routes SHALL return generic error messages to clients for 500-level errors. The response body SHALL be { "error": "Internal server error" }. The full error details SHALL be logged server-side via console.error with request context.

Scenario: Internal error returns generic message

  • WHEN an API route handler throws an unexpected error
  • THEN the client receives HTTP 500 with { "error": "Internal server error" } and the full error is logged server-side

Scenario: No stack traces in response

  • WHEN a database query fails in any API route
  • THEN the response does NOT contain table names, file paths, connection strings, or stack traces

Requirement: Scoped bulk annotation delete

The DELETE /api/annotations endpoint SHALL require a chartId query parameter when all=true is specified. Unscoped delete-all (without chartId) SHALL be rejected with HTTP 400.

Scenario: Scoped bulk delete

  • WHEN DELETE /api/annotations?all=true&chartId=5 is called
  • THEN all annotations for chart 5 are deleted

Scenario: Unscoped bulk delete rejected

  • WHEN DELETE /api/annotations?all=true is called without chartId
  • THEN the route returns HTTP 400 with { "error": "chartId is required for bulk delete" }

Requirement: Transaction-wrapped chart cascade delete

The DELETE /api/charts/[id] route SHALL wrap all related deletions (annotations, span annotations, candles, chart) in a single database transaction using db.transaction().

Scenario: Cascade delete in transaction

  • WHEN DELETE /api/charts/5 is called
  • THEN span annotations, annotations, candles, and the chart record for chart 5 are all deleted within a single transaction

Scenario: Partial failure rolls back

  • WHEN the candles deletion fails mid-transaction
  • THEN all deletions are rolled back and the chart remains intact

Requirement: Span annotations included in chart cascade delete

The DELETE /api/charts/[id] route SHALL delete span annotations for the chart in addition to annotations and candles.

Scenario: Span annotations deleted with chart

  • WHEN DELETE /api/charts/5 is called
  • THEN all span_annotations rows with chart_id=5 are deleted before the chart record

Requirement: parseInt validation

All API routes that parse integer query parameters SHALL use parseInt(value, 10) with radix 10 and check for isNaN(). Invalid integer parameters SHALL return HTTP 400.

Scenario: Valid integer parameter

  • WHEN GET /api/candles?chartId=5 is called
  • THEN chartId is parsed as integer 5

Scenario: Invalid integer parameter

  • WHEN GET /api/candles?chartId=abc is called
  • THEN the route returns HTTP 400 with { "error": "Invalid chartId" }

Requirement: CSV injection protection on exports

All CSV export routes SHALL prefix cell values starting with =, +, -, or @ with a single quote (') to prevent spreadsheet formula injection.

Scenario: Dangerous cell value escaped

  • WHEN an annotation note contains =CMD("calc")
  • THEN the exported CSV cell contains '=CMD("calc")

Scenario: Normal values unchanged

  • WHEN an annotation note contains regular text
  • THEN the exported CSV cell contains regular text (no prefix)

Requirement: response.ok checks on all fetch calls

All fetch() calls in frontend components (page.tsx, CandleChart.tsx) SHALL check response.ok before calling response.json(). If !response.ok, the code SHALL throw an error or handle the failure explicitly.

Scenario: Successful response parsed

  • WHEN a fetch call returns HTTP 200
  • THEN response.json() is called and the data is used normally

Scenario: Error response handled

  • WHEN a fetch call returns HTTP 500
  • THEN the code detects !response.ok and shows an error message instead of attempting JSON parse