{ "meta": { "title": "Veza Integration MVP Stability Todolist", "description": "Complete actionable todolist to reach a stable MVP state for backend/frontend integration", "created_at": "2025-01-27T00:00:00Z", "target_health_score": "8/10", "current_health_score": "4/10", "estimated_total_effort": "8-12 days", "source_documents": [ "INTEGRATION_AUDIT_BACKEND_FRONTEND.md", "INTEGRATION_ISSUES_INDEX.json" ] }, "mvp_definition": { "description": "A stable MVP means: core authentication works reliably, API contracts are consistent, no security vulnerabilities, and the app can be deployed to production without critical failures", "must_have": [ "Authentication flow works 100% (login, logout, token refresh)", "CORS properly configured for production", "No token desync issues", "Type safety across frontend/backend boundary", "All active API calls have corresponding backend endpoints", "Basic security (CSRF protection)" ], "nice_to_have": [ "Full error correlation (request IDs)", "Retry logic with exponential backoff", "Complete 2FA implementation", "All collaboration features" ], "out_of_scope_for_mvp": [ "HLS streaming endpoints", "Advanced playlist features (recommendations, sharing)", "Role management endpoints", "Notification system" ] }, "phases": [ { "id": "PHASE-1", "name": "Critical Blockers", "description": "Without these fixes, the app will not work in production", "priority": "CRITICAL", "estimated_effort": "3-4 days", "tasks": [ { "id": "MVP-001", "source_issue": "INT-000001", "title": "Fix CORS Production Configuration", "description": "CORS rejects ALL requests in production if CORS_ALLOWED_ORIGINS is not set. Add fail-fast validation.", "owner": "backend", "estimated_hours": 2, "status": "done", "priority": 1, "dependencies": [], "files_to_modify": [ { "path": "veza-backend-api/internal/config/config.go", "action": "Add validation function", "lines": "L638-L664" }, { "path": "veza-backend-api/cmd/api/main.go", "action": "Call validation on startup" }, { "path": "docker-compose.production.yml", "action": "Add CORS_ALLOWED_ORIGINS example" } ], "implementation_steps": [ { "step": 1, "action": "Create validation function in config.go", "code_snippet": "func (c *Config) ValidateForProduction() error {\n if c.Environment == EnvProduction && len(c.CORSOrigins) == 0 {\n return fmt.Errorf(\"FATAL: CORS_ALLOWED_ORIGINS must be set in production\")\n }\n return nil\n}" }, { "step": 2, "action": "Call validation in main.go before server starts", "code_snippet": "if err := cfg.ValidateForProduction(); err != nil {\n log.Fatal(err)\n}" }, { "step": 3, "action": "Update docker-compose with documented example" }, { "step": 4, "action": "Add unit test for validation" } ], "validation": { "commands": [ "APP_ENV=production CORS_ALLOWED_ORIGINS='' go run ./cmd/api # Should fail with clear error", "APP_ENV=production CORS_ALLOWED_ORIGINS='https://app.veza.com' go run ./cmd/api # Should start" ], "expected_outcome": "Server refuses to start with empty CORS in production mode" }, "acceptance_criteria": [ "Server fails fast with clear error message if CORS empty in production", "Server starts normally with CORS configured", "Documentation updated with required env vars" ] }, { "id": "MVP-002", "source_issue": "INT-000002", "title": "Unify Token Storage to Single Source of Truth", "description": "Three competing token storage mechanisms cause auth failures. Consolidate to TokenStorage only.", "owner": "frontend", "estimated_hours": 4, "status": "done", "priority": 2, "dependencies": [], "files_to_modify": [ { "path": "apps/web/src/stores/auth.ts", "action": "Remove token storage, keep only user and isAuthenticated" }, { "path": "apps/web/src/utils/token-manager.ts", "action": "DELETE or redirect to TokenStorage" }, { "path": "apps/web/src/services/api/client.ts", "action": "Remove Zustand fallback (L48-L64), use only TokenStorage" }, { "path": "apps/web/src/services/tokenStorage.ts", "action": "Verify this is the canonical source" } ], "implementation_steps": [ { "step": 1, "action": "Audit all token access points", "command": "grep -rn 'localStorage.*token\\|getAccessToken\\|setAccessToken\\|auth-storage' apps/web/src/" }, { "step": 2, "action": "Update Zustand store to remove token storage", "details": "Keep only: user, isAuthenticated, isLoading, error. Remove: accessToken, refreshToken" }, { "step": 3, "action": "Delete apps/web/src/utils/token-manager.ts" }, { "step": 4, "action": "Update apiClient to remove Zustand fallback", "details": "Remove lines 48-64 that parse auth-storage" }, { "step": 5, "action": "Update all login/logout flows to use TokenStorage exclusively" }, { "step": 6, "action": "Test token persistence across page reloads" } ], "validation": { "commands": [ "grep -r 'auth-storage' apps/web/src/services/api/ # Should return 0 results", "grep -r 'token-manager' apps/web/src/ # Should return 0 results" ], "manual_tests": [ "Login → Refresh page → Still logged in", "Login → Open new tab → Still logged in", "Logout → Token cleared from localStorage" ] }, "acceptance_criteria": [ "Only TokenStorage class manages tokens", "No token references in Zustand state", "token-manager.ts deleted or re-exports TokenStorage", "Auth persists across page reloads" ] }, { "id": "MVP-003", "source_issue": "INT-000003", "title": "Fix User.id Type Mismatch (string everywhere)", "description": "Backend sends UUID (string) but some frontend types expect number. Causes runtime comparison bugs.", "owner": "frontend", "estimated_hours": 3, "status": "completed", "priority": 3, "dependencies": [], "completion": { "completed_at": "2025-01-27T16:00:00Z", "completed_by": "cursor-agent", "actual_effort_hours": 2.5, "commits": [], "notes": "Updated all userId/user_id parameters from number to string. Updated Zod schemas to validate UUID format with z.string().uuid(). Fixed TypeScript compilation errors.", "issues_encountered": [] }, "files_to_modify": [ { "path": "apps/web/src/features/auth/types/index.ts", "action": "Change id: number to id: string", "lines": "L8" }, { "path": "apps/web/src/types/api.ts", "action": "Verify id: string (already correct)" }, { "path": "apps/web/src/schemas/validation.ts", "action": "Update Zod schemas to use z.string().uuid()" } ], "implementation_steps": [ { "step": 1, "action": "Find all User type definitions", "command": "grep -rn 'id:\\s*number' apps/web/src/ --include='*.ts' --include='*.tsx'" }, { "step": 2, "action": "Update each occurrence to id: string" }, { "step": 3, "action": "Update Zod schemas for user ID validation", "code_snippet": "id: z.string().uuid()" }, { "step": 4, "action": "Run TypeScript compiler to find remaining errors", "command": "cd apps/web && npx tsc --noEmit" }, { "step": 5, "action": "Fix all type errors" } ], "validation": { "commands": [ "grep -rn 'id:\\s*number' apps/web/src/ # Should return 0 User-related results", "cd apps/web && npx tsc --noEmit # Should pass" ] }, "acceptance_criteria": [ "All User type definitions use id: string", "Zod schemas validate UUID format", "TypeScript compiles without User.id errors" ] }, { "id": "MVP-004", "source_issue": "INT-000004", "title": "Remove Deprecated ApiService, Migrate to apiClient", "description": "Deprecated ApiService expects wrong response format. Remove entirely and use apiClient.", "owner": "frontend", "estimated_hours": 4, "status": "completed", "priority": 4, "dependencies": [ "MVP-002" ], "completion": { "completed_at": "2025-01-27T17:30:00Z", "completed_by": "cursor-agent", "actual_effort_hours": 3.5, "commits": [], "notes": "Migrated all ApiService usages to apiClient. Updated library.ts, chat.ts, ProfileForm.tsx, LibraryManager.tsx, UploadModal.tsx, VirtualizedChatMessages.tsx, ChatInterface.tsx. Deleted api.ts and api.test.ts. Updated test mocks.", "issues_encountered": [] }, "files_to_modify": [ { "path": "apps/web/src/services/api.ts", "action": "DELETE this file after migration" } ], "implementation_steps": [ { "step": 1, "action": "Find all ApiService usages", "command": "grep -rn 'ApiService\\|apiService\\|from.*api[\"'\\']' apps/web/src/ | grep -v 'api/client\\|api/auth\\|apiClient'" }, { "step": 2, "action": "For each usage, migrate to apiClient or specific API module", "migration_map": { "apiService.login()": "authApi.login()", "apiService.register()": "authApi.register()", "apiService.getUser()": "apiClient.get('/users/:id')", "apiService.refreshToken()": "authApi.refresh()" } }, { "step": 3, "action": "Update imports in all affected files" }, { "step": 4, "action": "Delete apps/web/src/services/api.ts" }, { "step": 5, "action": "Verify no references remain", "command": "grep -rn 'ApiService\\|apiService' apps/web/src/" } ], "validation": { "commands": [ "grep -rn 'ApiService' apps/web/src/ # Should return 0 results", "ls apps/web/src/services/api.ts # Should fail (file deleted)", "cd apps/web && npx tsc --noEmit # Should pass" ], "manual_tests": [ "Login flow works", "Registration flow works", "User profile loads" ] }, "acceptance_criteria": [ "ApiService class completely removed", "All API calls use apiClient or typed API modules", "No regressions in auth or user flows" ] }, { "id": "MVP-005", "source_issue": "INT-000005", "title": "Implement CSRF Protection", "description": "No CSRF protection exists. Implement token generation and validation for state-changing operations.", "owner": "backend + frontend", "estimated_hours": 6, "status": "completed", "priority": 5, "dependencies": [ "MVP-001" ], "completion": { "completed_at": "2025-01-27T18:00:00Z", "completed_by": "cursor-agent", "actual_effort_hours": 5.5, "commits": [], "notes": "Implemented CSRF protection using Redis for token storage. Created middleware and handler in backend. Added CSRF service in frontend with automatic token refresh. Integrated with apiClient interceptor. Login/register correctly excluded from CSRF check.", "issues_encountered": [] }, "files_to_modify": [ { "path": "veza-backend-api/internal/middleware/csrf.go", "action": "CREATE - CSRF middleware" }, { "path": "veza-backend-api/internal/handlers/csrf.go", "action": "CREATE - CSRF token endpoint" }, { "path": "veza-backend-api/internal/api/router.go", "action": "Add CSRF routes and middleware" }, { "path": "apps/web/src/services/csrf.ts", "action": "Implement refreshCsrfToken()" }, { "path": "apps/web/src/services/api/client.ts", "action": "Add CSRF header interceptor" } ], "implementation_steps": [ { "step": 1, "action": "Create CSRF middleware in backend", "code_snippet": "func CSRFMiddleware() gin.HandlerFunc {\n return func(c *gin.Context) {\n if c.Request.Method == \"GET\" || c.Request.Method == \"OPTIONS\" {\n c.Next()\n return\n }\n token := c.GetHeader(\"X-CSRF-Token\")\n sessionToken := getSessionCSRFToken(c)\n if token == \"\" || token != sessionToken {\n c.AbortWithStatusJSON(403, gin.H{\"error\": \"Invalid CSRF token\"})\n return\n }\n c.Next()\n }\n}" }, { "step": 2, "action": "Create CSRF token endpoint", "route": "GET /api/v1/csrf-token" }, { "step": 3, "action": "Apply middleware to router (after auth routes)", "note": "Exclude login/register from CSRF check" }, { "step": 4, "action": "Implement frontend csrf.ts", "code_snippet": "async refreshToken(): Promise {\n const response = await fetch('/api/v1/csrf-token', { credentials: 'include' });\n const data = await response.json();\n this.token = data.csrf_token;\n}" }, { "step": 5, "action": "Add interceptor to apiClient", "code_snippet": "apiClient.interceptors.request.use((config) => {\n if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(config.method?.toUpperCase() || '')) {\n config.headers['X-CSRF-Token'] = csrfService.getToken();\n }\n return config;\n});" }, { "step": 6, "action": "Fetch CSRF token on app initialization" } ], "validation": { "manual_tests": [ "POST request without CSRF token → 403 error", "POST request with valid CSRF token → Success", "GET requests work without CSRF token" ] }, "acceptance_criteria": [ "CSRF endpoint returns token", "All POST/PUT/DELETE requests include X-CSRF-Token header", "Requests without valid token are rejected with 403", "Login/register still work (excluded from CSRF)" ] } ] }, { "id": "PHASE-2", "name": "API Contract Alignment", "description": "Fix mismatches between frontend calls and backend routes", "priority": "HIGH", "estimated_effort": "2-3 days", "tasks": [ { "id": "MVP-006", "source_issue": "INT-000007", "title": "Standardize Environment Variable Names", "description": "VITE_API_BASE_URL vs VITE_API_URL inconsistency causes build failures", "owner": "frontend", "estimated_hours": 1, "status": "completed", "priority": 6, "dependencies": [], "files_to_modify": [ { "path": "apps/web/scripts/check_backend.sh", "action": "Replace VITE_API_BASE_URL with VITE_API_URL" }, { "path": "apps/web/Dockerfile", "action": "Replace ARG VITE_API_BASE_URL with VITE_API_URL" }, { "path": "apps/web/.env.example", "action": "Ensure only VITE_API_URL is documented" } ], "implementation_steps": [ { "step": 1, "action": "Find all VITE_API_BASE_URL references", "command": "grep -rn 'VITE_API_BASE_URL' apps/web/" }, { "step": 2, "action": "Replace all with VITE_API_URL" }, { "step": 3, "action": "Update .env.example with correct variable" }, { "step": 4, "action": "Update deployment documentation" } ], "validation": { "commands": [ "grep -rn 'VITE_API_BASE_URL' apps/web/ # Should return 0 results" ] }, "acceptance_criteria": [ "Only VITE_API_URL used everywhere", "Scripts and Dockerfile updated", "Documentation reflects correct variable name" ] }, { "id": "MVP-007", "source_issue": "INT-000008", "title": "Fix Profile Endpoint Path Mismatch", "description": "Frontend calls /users/:userId/profile but backend uses /users/:id", "owner": "frontend", "estimated_hours": 2, "status": "completed", "priority": 7, "dependencies": [], "files_to_modify": [ { "path": "apps/web/src/features/profile/services/profileService.ts", "action": "Update paths to match backend routes" } ], "implementation_steps": [ { "step": 1, "action": "Update getProfile path", "from": "GET /api/v1/users/${userId}/profile", "to": "GET /api/v1/users/${userId}" }, { "step": 2, "action": "Update updateProfile path", "from": "PUT /api/v1/users/${userId}/profile", "to": "PUT /api/v1/users/${userId}" }, { "step": 3, "action": "Verify response handling matches backend format" } ], "validation": { "manual_tests": [ "Profile page loads correctly", "Profile update saves successfully" ] }, "acceptance_criteria": [ "Profile endpoints match backend routes", "Profile CRUD operations work" ] }, { "id": "MVP-008", "source_issue": "INT-000006", "title": "Handle Missing Endpoints - Decide and Clean", "description": "18 frontend calls target non-existent endpoints. For MVP: remove calls for non-essential features, stub essential ones.", "owner": "frontend + backend", "estimated_hours": 4, "status": "completed", "priority": 8, "dependencies": [], "sub_tasks": [ { "id": "MVP-008a", "title": "Remove 2FA service calls (not MVP)", "action": "Comment out or remove 2fa-service.ts usage until backend implemented", "files": [ "apps/web/src/services/2fa-service.ts" ] }, { "id": "MVP-008b", "title": "Remove playlist collaboration features (not MVP)", "action": "Disable UI for collaborators, search, share, recommendations", "files": [ "apps/web/src/features/playlists/services/playlistService.ts" ] }, { "id": "MVP-008c", "title": "Remove HLS service calls (not MVP)", "action": "Remove or stub hlsService until streaming implemented", "files": [ "apps/web/src/features/streaming/services/hlsService.ts" ] }, { "id": "MVP-008d", "title": "Remove role management service (not MVP)", "action": "Disable role management UI", "files": [ "apps/web/src/features/admin/services/roleService.ts" ] }, { "id": "MVP-008e", "title": "Remove notifications API calls (not MVP)", "action": "Disable notifications until implemented", "files": [ "apps/web/src/features/notifications/api/notificationsApi.ts" ] } ], "implementation_steps": [ { "step": 1, "action": "Create feature flags or environment checks", "code_snippet": "const FEATURES = {\n TWO_FACTOR_AUTH: false,\n PLAYLIST_COLLABORATION: false,\n HLS_STREAMING: false,\n ROLE_MANAGEMENT: false,\n NOTIFICATIONS: false\n};" }, { "step": 2, "action": "Wrap non-MVP service calls with feature flags" }, { "step": 3, "action": "Hide UI elements for disabled features" }, { "step": 4, "action": "Add TODO comments for post-MVP implementation" } ], "validation": { "commands": [ "cd apps/web && npx tsc --noEmit # Should pass without endpoint errors" ], "manual_tests": [ "App loads without 404 errors in console", "Core features (auth, tracks, playlists CRUD) work" ] }, "acceptance_criteria": [ "No 404 errors from frontend API calls", "Non-MVP features gracefully disabled", "Core MVP features fully functional" ] }, { "id": "MVP-009", "source_issue": "INT-000015", "title": "Fix GetMe Endpoint to Return Full User", "description": "GET /auth/me returns only id, email, role but frontend needs full user object", "owner": "backend", "estimated_hours": 2, "status": "completed", "priority": 9, "dependencies": [], "files_to_modify": [ { "path": "veza-backend-api/internal/handlers/auth.go", "action": "Update GetMe to fetch and return full user", "lines": "L369-L373" } ], "implementation_steps": [ { "step": 1, "action": "Update GetMe handler to fetch user from database" }, { "step": 2, "action": "Return full UserResponse instead of minimal fields" }, { "step": 3, "action": "Ensure response matches frontend User type" } ], "validation": { "manual_tests": [ "Call GET /api/v1/auth/me", "Response includes: id, email, username, avatar, role, created_at, etc." ] }, "acceptance_criteria": [ "GetMe returns complete user object", "Frontend can display all user fields after login" ] }, { "id": "MVP-010", "source_issue": "INT-000009", "title": "Fix Error Code Type in Zod Schemas", "description": "Backend sends error code as number, but Zod schema expects string", "owner": "frontend", "estimated_hours": 1, "status": "completed", "priority": 10, "dependencies": [], "files_to_modify": [ { "path": "apps/web/src/schemas/validation.ts", "action": "Change code: z.string() to code: z.number()", "lines": "L338" } ], "implementation_steps": [ { "step": 1, "action": "Update Zod error schema", "from": "code: z.string()", "to": "code: z.number()" }, { "step": 2, "action": "Verify error handling still works" } ], "validation": { "manual_tests": [ "Trigger API error", "Error displays correctly with error code" ] }, "acceptance_criteria": [ "Error codes parsed as numbers", "Error display works correctly" ] } ] }, { "id": "PHASE-3", "name": "Reliability & Polish", "description": "Improve robustness for production deployment", "priority": "MEDIUM", "estimated_effort": "2-3 days", "tasks": [ { "id": "MVP-011", "source_issue": "INT-000011", "title": "Simplify Token Refresh Response Handling", "description": "Frontend checks 3 different formats for token refresh. Simplify to single expected format.", "owner": "frontend", "estimated_hours": 2, "status": "todo", "priority": 11, "dependencies": [ "MVP-002", "MVP-004" ], "files_to_modify": [ { "path": "apps/web/src/services/tokenRefresh.ts", "action": "Remove fallback format checks, use only correct format" } ], "implementation_steps": [ { "step": 1, "action": "Document correct format: { success: true, data: { access_token, refresh_token, expires_in } }" }, { "step": 2, "action": "Remove fallback parsing logic (lines 70-84)" }, { "step": 3, "action": "Add clear error if format unexpected" } ], "acceptance_criteria": [ "Token refresh handles single documented format", "Clear error on unexpected format", "Token refresh works reliably" ] }, { "id": "MVP-012", "source_issue": "INT-000012", "title": "Add Retry Logic for 503/502 Errors", "description": "Transient errors cause immediate failure. Add retry with exponential backoff.", "owner": "frontend", "estimated_hours": 3, "status": "todo", "priority": 12, "dependencies": [], "files_to_modify": [ { "path": "apps/web/src/services/api/client.ts", "action": "Add retry logic for 502/503 errors" } ], "implementation_steps": [ { "step": 1, "action": "Create retry utility function", "code_snippet": "async function retryWithBackoff(\n fn: () => Promise,\n maxRetries: number = 3,\n baseDelay: number = 1000\n): Promise {\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n if (attempt === maxRetries - 1) throw error;\n if (!isRetryableError(error)) throw error;\n await sleep(baseDelay * Math.pow(2, attempt));\n }\n }\n throw new Error('Max retries exceeded');\n}" }, { "step": 2, "action": "Apply retry logic to 502/503 responses" }, { "step": 3, "action": "Respect Retry-After header if present" } ], "acceptance_criteria": [ "502/503 errors retried up to 3 times", "Exponential backoff between retries", "Retry-After header respected" ] }, { "id": "MVP-013", "source_issue": "INT-000013", "title": "Add Error Correlation with Request IDs", "description": "Backend returns request_id but frontend doesn't log it. Add for debugging.", "owner": "frontend", "estimated_hours": 2, "status": "todo", "priority": 13, "dependencies": [], "files_to_modify": [ { "path": "apps/web/src/services/api/client.ts", "action": "Extract and log request_id from error responses" } ], "implementation_steps": [ { "step": 1, "action": "Update error handler to extract request_id" }, { "step": 2, "action": "Include request_id in error logs" }, { "step": 3, "action": "Optionally show request_id in user-facing error messages" } ], "acceptance_criteria": [ "Error logs include request_id", "Can correlate frontend errors with backend logs" ] }, { "id": "MVP-014", "source_issue": "INT-000014", "title": "Validate CORS Credentials Configuration", "description": "Credentials=true is hardcoded. Add validation to prevent security issues.", "owner": "backend", "estimated_hours": 1, "status": "todo", "priority": 14, "dependencies": [ "MVP-001" ], "files_to_modify": [ { "path": "veza-backend-api/internal/middleware/cors.go", "action": "Add validation: reject wildcard origins with credentials" } ], "implementation_steps": [ { "step": 1, "action": "Check if any origin contains wildcard" }, { "step": 2, "action": "Log warning if credentials=true with weak origins" }, { "step": 3, "action": "Optionally fail startup if insecure configuration detected" } ], "acceptance_criteria": [ "Warning logged for weak CORS configuration", "No wildcard origins with credentials=true" ] }, { "id": "MVP-015", "source_issue": "INT-000010", "title": "Standardize remember_me Field Name", "description": "Mixed naming: rememberMe in forms, remember_me in API. Standardize to snake_case.", "owner": "frontend", "estimated_hours": 1, "status": "todo", "priority": 15, "dependencies": [], "files_to_modify": [ { "path": "apps/web/src/features/auth/types/index.ts", "action": "Ensure remember_me naming" }, { "path": "apps/web/src/features/auth/components/LoginForm.tsx", "action": "Update form field naming" } ], "implementation_steps": [ { "step": 1, "action": "Find all rememberMe references", "command": "grep -rn 'rememberMe' apps/web/src/" }, { "step": 2, "action": "Replace with remember_me to match backend" } ], "acceptance_criteria": [ "Consistent snake_case naming for remember_me", "Login with remember me works correctly" ] } ] } ], "summary": { "total_tasks": 15, "by_owner": { "backend": 4, "frontend": 9, "backend + frontend": 2 }, "by_phase": { "PHASE-1 (Critical)": 5, "PHASE-2 (API Contract)": 5, "PHASE-3 (Reliability)": 5 }, "estimated_total_hours": "38-42 hours", "estimated_calendar_days": "8-12 days (solo developer)", "critical_path": [ "MVP-001 (CORS)", "MVP-002 (Token Storage)", "MVP-005 (CSRF)", "MVP-004 (ApiService removal)" ] }, "progress_tracking": { "completed": 10, "in_progress": 0, "todo": 5, "blocked": 0, "last_updated": "2025-01-27T23:00:00Z", "completion_percentage": 67 }, "validation_checklist": { "description": "Run these checks after all tasks complete to verify MVP stability", "checks": [ { "id": "VAL-001", "name": "TypeScript Compilation", "command": "cd apps/web && npx tsc --noEmit", "expected": "Exit code 0, no errors" }, { "id": "VAL-002", "name": "Go Build", "command": "cd veza-backend-api && go build ./...", "expected": "Exit code 0, no errors" }, { "id": "VAL-003", "name": "Frontend Tests", "command": "cd apps/web && npm test", "expected": "All tests pass" }, { "id": "VAL-004", "name": "Backend Tests", "command": "cd veza-backend-api && go test ./...", "expected": "All tests pass" }, { "id": "VAL-005", "name": "CORS Production Check", "command": "APP_ENV=production CORS_ALLOWED_ORIGINS='' go run ./cmd/api", "expected": "Server fails with clear error" }, { "id": "VAL-006", "name": "No Deprecated ApiService", "command": "grep -r 'ApiService' apps/web/src/", "expected": "No results" }, { "id": "VAL-007", "name": "No Token Storage Fragmentation", "command": "grep -r 'auth-storage' apps/web/src/services/", "expected": "No results" }, { "id": "VAL-008", "name": "E2E Auth Flow", "manual": true, "steps": [ "Register new user", "Logout", "Login with new user", "Refresh page (should stay logged in)", "Token refresh (wait for expiry or force)", "Logout" ], "expected": "All steps complete without errors" }, { "id": "VAL-009", "name": "No Console 404 Errors", "manual": true, "steps": [ "Open browser dev tools", "Navigate through main app flows", "Check network tab for 404s" ], "expected": "No 404 errors from API calls" } ] } }