Use selectedSpanIdRef to avoid capturing stale selectedSpanId in the keyboard event handler closure. Wrap handleDeleteSpan in useCallback with stable dependencies (reads selectedSpanIdRef.current instead of the prop directly). Remove selectedSpanId from the useEffect dependency array since the ref is used instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
141 lines
13 KiB
Markdown
141 lines
13 KiB
Markdown
## 1. Security Critical — Git & Credentials
|
|
|
|
- [x] 1.1 `[haiku]` Add `.env` to `.gitignore` and run `git rm --cached .env` to untrack it
|
|
- [x] 1.2 `[haiku]` Add `models/` and `*.pkl` to `.gitignore`
|
|
- [x] 1.3 `[haiku]` Replace real credentials in `.env.example` with placeholders (`POSTGRES_PASSWORD=change_me_to_a_strong_password`)
|
|
- [x] 1.4 `[haiku]` Remove SQL comment with credentials from `services/ml/app/db.py` and add fail-fast check for missing `DATABASE_URL`
|
|
- [x] 1.5 `[sonnet]` Update `docker-compose.yml` to use `${POSTGRES_USER}`, `${POSTGRES_PASSWORD}`, `${POSTGRES_DB}` env var interpolation in all DATABASE_URL values
|
|
- [x] 1.6 `[haiku]` Bind PostgreSQL port to `127.0.0.1:5432:5432` in `docker-compose.yml`
|
|
- [x] 1.7 `[haiku]` Bind MLflow port to `127.0.0.1:5000:5000` in `docker-compose.yml`
|
|
- [x] 1.8 `[haiku]` Bind ML service port to `127.0.0.1:8001:8001` in `docker-compose.yml`
|
|
|
|
## 2. Security Critical — Input Validation & CORS
|
|
|
|
- [x] 2.1 `[haiku]` Validate `run_id` matches `/^[a-zA-Z0-9_-]+$/` in `src/app/api/training/runs/[run_id]/route.ts` before interpolation
|
|
- [x] 2.2 `[sonnet]` Validate `run_id` format and use `Path.resolve()` + directory containment check in `services/ml/app/main.py` (model load at line 1203, delete at line 1312)
|
|
- [x] 2.3 `[sonnet]` Add file size check (reject >10MB) and row count limit (500,000) to `src/app/api/upload/route.ts`
|
|
- [x] 2.4 `[haiku]` Add file type validation (`.csv` extension, text MIME type) to `src/app/api/upload/route.ts`
|
|
- [x] 2.5 `[haiku]` Fix CORS in `services/ml/app/main.py`: replace `allow_origins=["*"]` with `["http://localhost:3000"]` and support `CORS_ORIGINS` env var
|
|
|
|
## 3. Authentication
|
|
|
|
- [x] 3.1 `[sonnet]` Create `src/middleware.ts` with API key auth middleware: read `API_KEY` env var, check `X-API-Key` header on all `/api/*` routes except `/api/health`, return 401 if invalid
|
|
- [x] 3.2 `[sonnet]` Add FastAPI `Depends()` API key dependency in `services/ml/app/main.py`: read `API_KEY` env var, check `X-API-Key` header, exempt `/health` endpoint
|
|
- [x] 3.3 `[sonnet]` Update all Next.js proxy routes to forward `X-API-Key` header to ML service
|
|
- [x] 3.4 `[haiku]` Add `API_KEY` to `.env.example` with placeholder value and instructions
|
|
|
|
## 4. API Route Hardening (Next.js)
|
|
|
|
- [x] 4.1 `[sonnet]` Add Zod schema validation to `src/app/api/predict/route.ts` (validate pair, timeframe, candles array)
|
|
- [x] 4.2 `[sonnet]` Add Zod schema validation to `src/app/api/predict/batch/route.ts` (validate pair, timeframe, start_date, end_date)
|
|
- [x] 4.3 `[sonnet]` Add Zod schema validation to `src/app/api/model/load/route.ts` (validate run_id)
|
|
- [x] 4.4 `[sonnet]` Add Zod schema validation to `src/app/api/training/start/route.ts` (validate model_type)
|
|
- [x] 4.5 `[sonnet]` Add Zod schema validation to `src/app/api/patterns/detect/route.ts` (validate candles, patterns array)
|
|
- [x] 4.6 `[sonnet]` Replace `error.message` with generic `"Internal server error"` in all catch blocks across 7+ route files: `health/route.ts`, `candles/route.ts`, `annotations/route.ts`, `annotations/[id]/route.ts`, `upload/route.ts`, `export/route.ts`, `span-annotations/export/route.ts`
|
|
- [x] 4.7 `[sonnet]` Require `chartId` for bulk delete in `src/app/api/annotations/route.ts` — reject `?all=true` without chartId with HTTP 400
|
|
- [x] 4.8 `[sonnet]` Wrap chart cascade delete in `db.transaction()` and add `spanAnnotations` deletion in `src/app/api/charts/[id]/route.ts`
|
|
- [x] 4.9 `[haiku]` Add `parseInt(value, 10)` with `isNaN()` guard to all routes parsing integer query params
|
|
- [x] 4.10 `[sonnet]` Add CSV injection protection (prefix `=+@-` cells with `'`) to all export routes
|
|
- [x] 4.11 `[sonnet]` Add `response.ok` checks before `.json()` in `src/app/page.tsx` (lines 214, 230, 245, 257)
|
|
- [x] 4.12 `[sonnet]` Add `response.ok` checks before `.json()` in `src/components/CandleChart.tsx` (lines 163, 178, 192)
|
|
|
|
## 5. ML Service Hardening (Python)
|
|
|
|
- [x] 5.1 `[sonnet]` Replace `error.message` / traceback details with generic `"Internal server error"` in FastAPI exception handlers at lines 640, 778, 1091, 1134, 1199, 1296 of `services/ml/app/main.py`
|
|
- [x] 5.2 `[opus]` Add SHA256 model integrity check: create `models/checksums.sha256` manifest, verify hash before `joblib.load()` in `services/ml/app/main.py:266`
|
|
- [x] 5.3 `[sonnet]` Add `_model_swap_lock` to prediction reads (not just writes) in `services/ml/app/main.py` for thread-safe model access
|
|
- [x] 5.4 `[sonnet]` Add date range validation (max 1 year) to `POST /predict/batch` in `services/ml/app/main.py`
|
|
- [x] 5.5 `[sonnet]` Add candle time-sort validation/auto-sort to `POST /predict` in `services/ml/app/main.py`
|
|
- [x] 5.6 `[sonnet]` Implement real health checks: `SELECT 1` for PostgreSQL, MLflow API ping in `services/ml/app/main.py:396-409`
|
|
- [x] 5.7 `[sonnet]` Add training resource limits: 500MB dataset size check, 30-minute timeout with status update on expiry in `services/ml/app/main.py:907-1030`
|
|
- [x] 5.8 `[haiku]` Add `run_id` format validation to `DELETE /training/runs/{run_id}` and `GET /training/runs/{run_id}` endpoints
|
|
|
|
## 6. Infrastructure & Docker
|
|
|
|
- [x] 6.1 `[sonnet]` Add `headers()` function to `next.config.js` with X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Content-Security-Policy
|
|
- [x] 6.2 `[sonnet]` Add `USER appuser` to `services/ml/Dockerfile`: create user with `useradd`, set ownership, add USER directive before CMD
|
|
- [x] 6.3 `[haiku]` Create `.dockerignore` with `.git`, `.env`, `.env*`, `node_modules`, `.next`, `data/`, `*.md`, `__pycache__/`, `mlruns/`, `models/`
|
|
- [x] 6.4 `[haiku]` Change TA-Lib download URL to HTTPS in `services/ml/Dockerfile:10`
|
|
- [x] 6.5 `[sonnet]` Add SHA256 checksum verification for TA-Lib download in `services/ml/Dockerfile`
|
|
- [x] 6.6 `[haiku]` Remove `COPY --from=builder /app/node_modules ./node_modules` line from `Dockerfile:29` (standalone doesn't need it)
|
|
- [x] 6.7 `[sonnet]` Pin Docker base images to `@sha256:` digests in both Dockerfiles
|
|
- [x] 6.8 `[haiku]` Fix healthcheck tool mismatch: use same tool (curl) in Dockerfile and docker-compose.yml
|
|
|
|
## 7. Frontend — Stale Closures & Race Conditions
|
|
|
|
- [x] 7.1 `[opus]` Fix stale closure in `fetchPredictions`: extract `modelInfo` into a `useRef` that stays in sync with state, use ref in `generateCacheKey` (`src/app/page.tsx:489-552`)
|
|
- [x] 7.2 `[opus]` Add AbortController to `fetchPredictions` and `handleFetchBatchPredictions`: store controller in ref, abort previous on new request, discard stale responses (`src/app/page.tsx:494-663`)
|
|
- [x] 7.3 `[opus]` Fix stale closure in CandleChart click handler: convert `drawingState`, `selectedLineId`, `dragState`, `annotations` to refs, update refs alongside setState, read refs in handler (`src/components/CandleChart.tsx:572-971`)
|
|
- [x] 7.4 `[opus]` Fix stale closure in SpanAnnotationManager keyboard handler: wrap `handleDeleteSpan` in `useCallback`, use ref for `selectedSpanId` (`src/components/SpanAnnotationManager.tsx:461-535`)
|
|
|
|
## 8. Frontend — Memory Leaks & Performance
|
|
|
|
- [ ] 8.1 `[opus]` Fix SpanAnnotationManager preview primitive memory leak: replace `useState` with `useRef` for preview primitive, add cleanup on unmount (`src/components/SpanAnnotationManager.tsx:265-324`)
|
|
- [ ] 8.2 `[sonnet]` Use `chart.applyOptions()` for theme changes instead of re-creating chart (`src/components/CandleChart.tsx:333`)
|
|
- [ ] 8.3 `[sonnet]` Remove `fitContent()` from reconciliation effect, only call on initial load (`src/components/SpanAnnotationManager.tsx:160`)
|
|
- [ ] 8.4 `[opus]` Implement incremental primitive updates in SpanAnnotationManager: update only selection state on selection change, full reconciliation only on annotation list changes (`src/components/SpanAnnotationManager.tsx:104-161`)
|
|
- [ ] 8.5 `[sonnet]` Add bounded prediction cache (max 100 entries, FIFO eviction) to `predictionCacheRef` (`src/app/page.tsx:195-199`)
|
|
- [ ] 8.6 `[sonnet]` Fix hardcoded 1-minute candle interval: detect interval from data, use for span iteration (`src/components/CandleChart.tsx:452`)
|
|
- [ ] 8.7 `[haiku]` Extract magic numbers (8px, 60s, colors) to named constants in `CandleChart.tsx`
|
|
- [ ] 8.8 `[haiku]` Move `new Set<string>()` default prop to module-level constant in `CandleChart.tsx:125`
|
|
|
|
## 9. Frontend — Shared Types & Type Safety
|
|
|
|
- [ ] 9.1 `[sonnet]` Create `src/types/candles.ts` with `Candle` interface
|
|
- [ ] 9.2 `[sonnet]` Create `src/types/annotations.ts` with `Annotation`, `AnnotationType`, `Geometry` interfaces
|
|
- [ ] 9.3 `[sonnet]` Create `src/types/charts.ts` with `Chart` interface
|
|
- [ ] 9.4 `[sonnet]` Create `src/types/predictions.ts` with `PredictionSpan`, `PredictionState`, `ModelInfo` interfaces
|
|
- [ ] 9.5 `[sonnet]` Create `src/types/span-annotations.ts` with `SpanAnnotation`, `SpanLabelType`, `SubSpan` interfaces
|
|
- [ ] 9.6 `[haiku]` Create `src/types/index.ts` barrel file re-exporting all types
|
|
- [ ] 9.7 `[sonnet]` Replace duplicate interfaces in `page.tsx`, `CandleChart.tsx`, `SpanAnnotationManager.tsx`, `Toolbox.tsx`, `SpanAnnotationList.tsx`, `SpanPopover.tsx` with imports from `@/types`
|
|
- [ ] 9.8 `[sonnet]` Replace all `any` types with proper interfaces: `geometry` → `Geometry | null`, `sub_spans` → `SubSpan[]`, candle arrays → `Candle[]`, prediction cache → `Map<string, PredictionSpan[]>`
|
|
|
|
## 10. Frontend — Error Boundary & UX
|
|
|
|
- [ ] 10.1 `[sonnet]` Create `src/components/ErrorBoundary.tsx` React class component with error state, fallback UI (error message + "Try Again" + "Reload" buttons), and `console.error` logging
|
|
- [ ] 10.2 `[haiku]` Wrap `{children}` with ErrorBoundary in `src/app/layout.tsx`
|
|
- [ ] 10.3 `[sonnet]` Add confirmation dialog before delete-all annotations in `src/app/page.tsx:412-425` using Radix Dialog
|
|
- [ ] 10.4 `[haiku]` Fix `SpanAnnotationList.tsx:110-115` confidence check: replace falsy check with `!= null` for confidence value 0
|
|
|
|
## 11. Frontend — Accessibility
|
|
|
|
- [ ] 11.1 `[sonnet]` Add `aria-label` attributes to all toolbar buttons in `Toolbox.tsx`
|
|
- [ ] 11.2 `[sonnet]` Add `role="dialog"`, `aria-modal="true"`, and focus trapping to `KeyboardShortcutsModal.tsx`
|
|
- [ ] 11.3 `[sonnet]` Make `ChartSelector.tsx` keyboard accessible: add arrow key navigation and click-outside close handler
|
|
- [ ] 11.4 `[sonnet]` Add `aria-pressed` to toggle buttons (span drawing mode, prediction visibility)
|
|
|
|
## 12. Frontend — Polish & Cleanup
|
|
|
|
- [ ] 12.1 `[haiku]` Remove unused imports `TrendingUp`, `ChevronUp` from `Toolbox.tsx:4`
|
|
- [ ] 12.2 `[haiku]` Remove `@ts-ignore` in `Toolbox.tsx:246-247`, replace with proper type assertion for CSS custom property
|
|
- [ ] 12.3 `[sonnet]` Fix dark theme on `annotation-types/page.tsx` and `span-label-types/page.tsx`: replace hardcoded light colors with theme-aware CSS variables
|
|
- [ ] 12.4 `[sonnet]` Add debounce (150ms) or `onPointerUp` to confidence slider in `PredictionPanel.tsx:148-155`
|
|
- [ ] 12.5 `[sonnet]` Replace Google Font CSS `@import` in `globals.css` with `next/font/google` in `layout.tsx`
|
|
- [ ] 12.6 `[haiku]` Remove manual Escape handler in `SpanPopover.tsx:114-117` (conflicts with Radix Dialog)
|
|
- [ ] 12.7 `[sonnet]` Fix `TalibPatternPanel.tsx:39` re-fetch when API returns empty array: add `hasLoaded` flag
|
|
- [ ] 12.8 `[haiku]` Add `[candles]` to `useImperativeHandle` dependency array in `CandleChart.tsx:202-221`
|
|
- [ ] 12.9 `[haiku]` Add `activeChartId` to primitive cleanup effect dependency arrays in `CandleChart.tsx:1059-1110`
|
|
- [ ] 12.10 `[sonnet]` Implement or remove no-op Tooltip component in `ui/tooltip.tsx` (replace with Radix Tooltip if used)
|
|
- [ ] 12.11 `[haiku]` Remove `eslint-disable` in `TrainingPanel.tsx:128-129`, include stable callbacks in dependency array
|
|
- [ ] 12.12 `[haiku]` Remove dead filter code (TODO comment, no-op) in `page.tsx:49-53`
|
|
|
|
## 13. Package & Dependencies
|
|
|
|
- [ ] 13.1 `[haiku]` Move `@types/node`, `@types/react`, `@types/react-dom`, `@types/papaparse`, `@types/pg` from `dependencies` to `devDependencies` in `package.json`
|
|
- [ ] 13.2 `[haiku]` Move `typescript`, `eslint`, `eslint-config-next`, `autoprefixer`, `postcss` from `dependencies` to `devDependencies` in `package.json`
|
|
- [ ] 13.3 `[haiku]` Add `zod` to `dependencies` in `package.json`
|
|
|
|
## 14. Dead Code Removal
|
|
|
|
- [ ] 14.1 `[haiku]` Delete `src/lib/db/migrate.ts` (dead SQLite migration code)
|
|
- [ ] 14.2 `[haiku]` Remove `get_db_session()` function from `services/ml/app/db.py:99-111`
|
|
- [ ] 14.3 `[haiku]` Remove dead `inference*` package reference from `pyproject.toml:32`
|
|
- [ ] 14.4 `[haiku]` Fix inconsistent `uuid as uuid_lib` import in `services/ml/app/main.py:9` — use standard `import uuid`
|
|
- [ ] 14.5 `[haiku]` Remove duplicate `TALIB_PATTERNS` dict — import from one location (`app/patterns.py` or `generate_talib_annotations.py`)
|
|
|
|
## 15. Deprecated Python API Replacements
|
|
|
|
- [ ] 15.1 `[sonnet]` Replace `@app.on_event("startup")` with FastAPI lifespan pattern in `services/ml/app/main.py:307`
|
|
- [ ] 15.2 `[haiku]` Replace `declarative_base()` with `class Base(DeclarativeBase)` in `services/ml/app/db.py:45`
|
|
- [ ] 15.3 `[haiku]` Replace all `datetime.utcnow()` with `datetime.now(datetime.UTC)` in `services/ml/app/main.py:325,1000,1019,1080`
|
|
- [ ] 15.4 `[haiku]` Add missing fallback return for volume indicators in `features/talib_features.py:169-190`
|