Changes: - Updated docker-compose.yml MLflow service port binding from 5000:5000 to 127.0.0.1:5000:5000 to restrict access to localhost only for security - Marked task 1.7 as complete in tasks.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
74 lines
3.9 KiB
Markdown
74 lines
3.9 KiB
Markdown
## 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
|