- 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>
13 KiB
ADDED Requirements
Requirement: Predict proxy endpoint
The system SHALL provide a POST /api/predict Next.js API route that proxies requests to the Python inference service at ${INFERENCE_API_URL}/predict. The route SHALL forward the request body (pair, timeframe, candles array) and return the Python service's response. If the inference service is unreachable, the route SHALL return HTTP 503 with { "error": "Inference service unavailable" }.
Scenario: Successful prediction proxy
- WHEN POST /api/predict is called with valid candle data and the Python service is running
- THEN the route forwards the request to the inference service and returns the prediction response with HTTP 200
Scenario: Inference service down
- WHEN POST /api/predict is called but the Python inference service is unreachable
- THEN the route returns HTTP 503 with
{ "error": "Inference service unavailable" }
Scenario: Inference service error
- WHEN the Python inference service returns an error status (4xx or 5xx)
- THEN the route forwards the error status and message to the client
Requirement: Batch predict proxy endpoint
The system SHALL provide a POST /api/predict/batch Next.js API route that proxies batch prediction requests to ${INFERENCE_API_URL}/predict/batch. The route SHALL forward pair, timeframe, start_date, and end_date.
Scenario: Successful batch prediction
- WHEN POST /api/predict/batch is called with valid parameters
- THEN the route forwards to the inference service and returns the batch prediction response
Scenario: Timeout on large batch
- WHEN the batch prediction takes longer than INFERENCE_BATCH_TIMEOUT
- THEN the route returns HTTP 504 with
{ "error": "Batch prediction timed out" }
Requirement: Pattern detection endpoint
The FastAPI service SHALL provide a POST /patterns/detect endpoint that accepts candle data and a list of CDL pattern names. The endpoint SHALL run the specified TA-Lib CDL functions on the candle data and return detected patterns as span annotation objects. Each returned annotation SHALL include start_time, end_time, label, confidence, and source ("talib").
Scenario: Detect specific patterns
- WHEN
POST /patterns/detectis called with{candles: [...], patterns: ["CDLENGULFING", "CDLHAMMER"]} - THEN the endpoint runs only Engulfing and Hammer detection and returns matching span annotations
Scenario: Detect all patterns
- WHEN
POST /patterns/detectis called with{candles: [...], patterns: []}(empty list) - THEN the endpoint runs all available CDL pattern functions
Scenario: No patterns found
- WHEN detection runs but no patterns match
- THEN the endpoint returns
{annotations: [], metadata: {count: 0}}
Scenario: Invalid pattern name
- WHEN a pattern name is not a valid TA-Lib CDL function
- THEN the endpoint returns HTTP 400 with the invalid pattern name in the error message
Requirement: Available patterns endpoint
The FastAPI service SHALL provide a GET /patterns/available endpoint that returns the list of all supported CDL pattern names with their friendly display names.
Scenario: List available patterns
- WHEN
GET /patterns/availableis called - THEN the endpoint returns a list of
{function_name, display_name}for all supported CDL patterns
Requirement: Training start endpoint
The FastAPI service SHALL provide a POST /training/start endpoint that triggers a training run in a background thread. The endpoint SHALL accept {model_type} and return immediately with a run_id and status "running". Only one training run SHALL be allowed at a time.
Scenario: Start training
- WHEN
POST /training/startis called with{model_type: "random_forest"} - THEN the endpoint returns
{run_id, status: "running"}and training begins in the background
Scenario: Training already in progress
- WHEN
POST /training/startis called while a training run is active - THEN the endpoint returns HTTP 409 with
{error: "Training already in progress", run_id: "<active_run_id>"}
Scenario: Invalid model type
- WHEN
POST /training/startis called with an unsupported model type - THEN the endpoint returns HTTP 400 with
{error: "Unsupported model type. Available: random_forest, xgboost"}
Requirement: Training runs endpoint
The FastAPI service SHALL provide a GET /training/runs endpoint that returns training run history from the database. Each entry SHALL include run_id, model_type, status, created_at, completed_at, and metrics_summary. Results SHALL be sorted by created_at descending.
Scenario: List training runs
- WHEN
GET /training/runsis called - THEN the endpoint returns training run records sorted by date descending
Scenario: No training runs
- WHEN no training runs exist in the database
- THEN the endpoint returns
{runs: []}
Requirement: Model load endpoint
The FastAPI service SHALL provide a POST /model/load endpoint that loads a model by run_id. The endpoint SHALL look up the training run, find the model artifact (MLflow or local), and replace the currently loaded model. The endpoint SHALL return the new model's info.
Scenario: Load model by run_id
- WHEN
POST /model/loadis called with{run_id: "abc123"} - THEN the endpoint loads the model associated with that run, updates the active model, and returns model info
Scenario: Run not found
- WHEN
POST /model/loadis called with a non-existent run_id - THEN the endpoint returns HTTP 404 with
{error: "Training run not found"}
Scenario: Model artifact missing
- WHEN the training run exists but the model file is missing
- THEN the endpoint returns HTTP 500 with
{error: "Model artifact not found for run"}
Requirement: Dataset info endpoint
The FastAPI service SHALL provide a GET /training/dataset-info endpoint that returns information about the training dataset: file path, existence status, file size, and last modified date.
Scenario: Dataset exists
- WHEN
GET /training/dataset-infois called and the labeled dataset file exists - THEN the endpoint returns
{path, exists: true, size_bytes, last_modified, row_count}
Scenario: Dataset missing
- WHEN
GET /training/dataset-infois called and the labeled dataset file does not exist - THEN the endpoint returns
{path, exists: false}
Requirement: Pattern detection proxy
The Next.js API SHALL provide a POST /api/patterns/detect route that proxies to the FastAPI /patterns/detect endpoint.
Scenario: Proxy pattern detection
- WHEN
POST /api/patterns/detectis called - THEN the route forwards the request to the FastAPI service and returns the response
Requirement: Available patterns proxy
The Next.js API SHALL provide a GET /api/patterns/available route that proxies to the FastAPI /patterns/available endpoint.
Scenario: Proxy available patterns
- WHEN
GET /api/patterns/availableis called - THEN the route forwards to the FastAPI service and returns the pattern list
Requirement: Training proxy endpoints
The Next.js API SHALL provide proxy routes for training operations: POST /api/training/start, GET /api/training/runs, and GET /api/training/dataset-info.
Scenario: Proxy training start
- WHEN
POST /api/training/startis called - THEN the route forwards to the FastAPI service and returns the response
Scenario: Proxy training runs
- WHEN
GET /api/training/runsis called - THEN the route forwards to the FastAPI service and returns the run list
Requirement: Model load proxy
The Next.js API SHALL provide a POST /api/model/load route that proxies to the FastAPI /model/load endpoint.
Scenario: Proxy model load
- WHEN
POST /api/model/loadis called with a run_id - THEN the route forwards to the FastAPI service and returns the response
Requirement: Bulk delete by source
The Next.js API DELETE /api/span-annotations endpoint SHALL support a source query parameter for bulk deletion. When source is provided, all span annotations matching that source (and optionally label filter) for the current chart SHALL be deleted.
Scenario: Bulk delete TA-Lib annotations
- WHEN
DELETE /api/span-annotations?chartId=1&source=talibis called - THEN all span annotations with
source: "talib"for chart 1 are deleted
Scenario: Bulk delete by source and label
- WHEN
DELETE /api/span-annotations?chartId=1&source=talib&label=Engulfingis called - THEN only TA-Lib annotations containing "Engulfing" in the label for chart 1 are deleted
Requirement: Model info proxy endpoint
The system SHALL provide a GET /api/model/info Next.js API route that proxies to ${INFERENCE_API_URL}/model/info. This endpoint returns model metadata and per-class metrics.
Scenario: Successful model info
- WHEN GET /api/model/info is called and the inference service is running
- THEN the route returns the model metadata JSON
Scenario: No model available
- WHEN GET /api/model/info is called and the inference service returns 503
- THEN the route returns HTTP 503 with
{ "error": "No model available" }
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=5is called - THEN all annotations for chart 5 are deleted
Scenario: Unscoped bulk delete rejected
- WHEN
DELETE /api/annotations?all=trueis 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/5is 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/5is called - THEN all
span_annotationsrows withchart_id=5are 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=5is called - THEN chartId is parsed as integer 5
Scenario: Invalid integer parameter
- WHEN
GET /api/candles?chartId=abcis 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.okand shows an error message instead of attempting JSON parse