1784 lines
62 KiB
JSON
1784 lines
62 KiB
JSON
|
|
{
|
||
|
|
"meta": {
|
||
|
|
"generated_at": "2025-01-27T00:00:00Z",
|
||
|
|
"scope": "backend veza-backend-api <-> frontend apps/web integration",
|
||
|
|
"repo_commit": "e4212ee59420e1c2d53e84af5cba1d9351d123d5",
|
||
|
|
"auditor": "Cursor AI",
|
||
|
|
"severity_model": {
|
||
|
|
"P0": "Critical - breaks core functionality or security, must fix immediately",
|
||
|
|
"P1": "High - frequent functional breakage or major UX regression",
|
||
|
|
"P2": "Medium - correctness/consistency issues that will create bugs soon",
|
||
|
|
"P3": "Low - maintainability, dead code, tech debt"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"summary": {
|
||
|
|
"health_score_0_to_10": 4,
|
||
|
|
"issue_counts": {
|
||
|
|
"P0": 5,
|
||
|
|
"P1": 10,
|
||
|
|
"P2": 8,
|
||
|
|
"P3": 7
|
||
|
|
},
|
||
|
|
"top5_issue_ids": [
|
||
|
|
"INT-000001",
|
||
|
|
"INT-000002",
|
||
|
|
"INT-000003",
|
||
|
|
"INT-000004",
|
||
|
|
"INT-000005"
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"issues": [
|
||
|
|
{
|
||
|
|
"id": "INT-000001",
|
||
|
|
"severity": "P0",
|
||
|
|
"priority_rank": 1,
|
||
|
|
"title": "CORS Configuration Will Break Production - Empty Origins Reject All Requests",
|
||
|
|
"component": {
|
||
|
|
"frontend": "",
|
||
|
|
"backend": "veza-backend-api/internal/api/router.go, internal/config/config.go, internal/middleware/cors.go",
|
||
|
|
"infra": "deployment configs"
|
||
|
|
},
|
||
|
|
"category": ["cors", "env", "production"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": true,
|
||
|
|
"breaks_core_flow": true,
|
||
|
|
"security_risk": "medium",
|
||
|
|
"notes": "All API requests will fail in production if CORS_ALLOWED_ORIGINS is not set"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/api/router.go",
|
||
|
|
"lines": "L119-L128",
|
||
|
|
"excerpt": "if r.config != nil {\n router.Use(middleware.CORS(r.config.CORSOrigins))\n if len(r.config.CORSOrigins) == 0 {\n r.logger.Warn(\"CORS origins not configured - strict mode enabled: ALL CORS requests will be rejected.\")\n }\n}"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/config/config.go",
|
||
|
|
"lines": "L638-L664",
|
||
|
|
"excerpt": "case EnvProduction:\n return []string{} // Empty list = reject all"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/middleware/cors.go",
|
||
|
|
"lines": "L34-L37",
|
||
|
|
"excerpt": "if len(allowed) == 0 {\n return false // Reject all origins"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Set APP_ENV=production",
|
||
|
|
"Leave CORS_ALLOWED_ORIGINS unset",
|
||
|
|
"Start backend server",
|
||
|
|
"Make CORS request from frontend",
|
||
|
|
"Observe: Request rejected with CORS error"
|
||
|
|
],
|
||
|
|
"expected": "CORS requests should work with configured origins",
|
||
|
|
"actual": "All CORS requests rejected because origins list is empty"
|
||
|
|
},
|
||
|
|
"root_cause": "Production environment defaults to strict mode (empty origins list) but frontend expects CORS to work. No validation ensures CORS_ALLOWED_ORIGINS is set in production.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Add startup validation: fail fast if APP_ENV=production and CORS_ALLOWED_ORIGINS empty",
|
||
|
|
"Document CORS_ALLOWED_ORIGINS as REQUIRED in production deployment guide",
|
||
|
|
"Update docker-compose.production.yml with example CORS_ALLOWED_ORIGINS",
|
||
|
|
"Add integration test with production-like CORS config"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"E2E test: Verify CORS works with configured origins",
|
||
|
|
"Unit test: Verify startup fails if prod + empty CORS"
|
||
|
|
],
|
||
|
|
"regression_risks": ["None - this is a new validation"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000002",
|
||
|
|
"severity": "P0",
|
||
|
|
"priority_rank": 2,
|
||
|
|
"title": "Multiple Auth Storage Mechanisms Cause Token Sync Failures",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/services/tokenStorage.ts, apps/web/src/services/api/client.ts, apps/web/src/stores/auth.ts, apps/web/src/utils/token-manager.ts",
|
||
|
|
"backend": "",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["auth", "state-management"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": true,
|
||
|
|
"breaks_core_flow": true,
|
||
|
|
"security_risk": "low",
|
||
|
|
"notes": "Tokens stored in localStorage, Zustand persist, and TokenStorage can desync, causing auth failures"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/tokenStorage.ts",
|
||
|
|
"lines": "L35-L36",
|
||
|
|
"excerpt": "localStorage.setItem(ACCESS_TOKEN_KEY, accessToken);\nlocalStorage.setItem(REFRESH_TOKEN_KEY, refreshToken);"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/api/client.ts",
|
||
|
|
"lines": "L48-L64",
|
||
|
|
"excerpt": "// Fallback: Si pas de token dans TokenStorage, chercher dans auth-storage Zustand\nif (!token && typeof window !== 'undefined') {\n const authStorage = localStorage.getItem('auth-storage');\n // Parse Zustand storage..."
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/utils/token-manager.ts",
|
||
|
|
"lines": "L25",
|
||
|
|
"excerpt": "localStorage.setItem(this.ACCESS_TOKEN_KEY, accessToken);"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Login via Zustand store",
|
||
|
|
"Token stored in auth-storage (Zustand)",
|
||
|
|
"API call uses TokenStorage.getAccessToken()",
|
||
|
|
"TokenStorage returns null",
|
||
|
|
"Fallback to Zustand storage works but fragile"
|
||
|
|
],
|
||
|
|
"expected": "Single source of truth for token storage",
|
||
|
|
"actual": "Tokens stored in 3+ different places, can desync"
|
||
|
|
},
|
||
|
|
"root_cause": "No single source of truth for token storage. Multiple classes (TokenStorage, token-manager, Zustand) all manage tokens independently.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Standardize on TokenStorage class as single source",
|
||
|
|
"Remove token storage from Zustand (keep only user and isAuthenticated flags)",
|
||
|
|
"Remove token-manager.ts or migrate all usage to TokenStorage",
|
||
|
|
"Update apiClient interceptor to only use TokenStorage",
|
||
|
|
"Add tests for token persistence across page reloads"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Token persists after page reload",
|
||
|
|
"Test: Token sync across multiple tabs",
|
||
|
|
"Test: Token cleared on logout"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Existing code using Zustand token storage may break"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000003",
|
||
|
|
"severity": "P0",
|
||
|
|
"priority_rank": 3,
|
||
|
|
"title": "Type Mismatch: User.id (string vs number) Causes Runtime Errors",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/types/api.ts, apps/web/src/features/auth/types/index.ts, apps/web/src/services/api/auth.ts",
|
||
|
|
"backend": "veza-backend-api/internal/dto/user_response.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["schema-drift", "type-safety"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": true,
|
||
|
|
"breaks_core_flow": true,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "TypeScript type errors and runtime coercion bugs when accessing user.id"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/types/api.ts",
|
||
|
|
"lines": "L3",
|
||
|
|
"excerpt": "id: string; // ✅ Correct"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/features/auth/types/index.ts",
|
||
|
|
"lines": "L8",
|
||
|
|
"excerpt": "id: number; // ❌ Wrong type"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/dto/user_response.go",
|
||
|
|
"lines": "L30",
|
||
|
|
"excerpt": "ID: uuid.UUID // Serializes as string"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Import User type from features/auth/types",
|
||
|
|
"Access user.id in TypeScript",
|
||
|
|
"TypeScript expects number but receives string",
|
||
|
|
"Runtime: String operations on number or vice versa"
|
||
|
|
],
|
||
|
|
"expected": "User.id is string (UUID) everywhere",
|
||
|
|
"actual": "Mixed types: string in some places, number in others"
|
||
|
|
},
|
||
|
|
"root_cause": "Inconsistent type definitions across frontend modules. Backend uses UUID (string) but some frontend types use number.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Audit all User type definitions in frontend",
|
||
|
|
"Update apps/web/src/features/auth/types/index.ts to use id: string",
|
||
|
|
"Update all Zod schemas to validate UUID format",
|
||
|
|
"Run TypeScript strict mode checks",
|
||
|
|
"Fix all type errors"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"TypeScript compilation test",
|
||
|
|
"Runtime validation: Verify user.id is string"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Code expecting number may break"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000004",
|
||
|
|
"severity": "P0",
|
||
|
|
"priority_rank": 4,
|
||
|
|
"title": "Deprecated ApiService Expects Wrong Response Format",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/services/api.ts",
|
||
|
|
"backend": "veza-backend-api/internal/handlers/auth.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["api-contract", "error-handling"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": true,
|
||
|
|
"breaks_core_flow": true,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Code using deprecated ApiService will break when backend response format changes"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/api.ts",
|
||
|
|
"lines": "L238-L239",
|
||
|
|
"excerpt": "const { user, token } = response.data.data; // Expects flat structure"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/handlers/auth.go",
|
||
|
|
"lines": "L96-L106",
|
||
|
|
"excerpt": "RespondSuccess(c, http.StatusOK, dto.LoginResponse{\n User: {...},\n Token: {...}\n}) // Returns { success: true, data: { user: {...}, token: {...} } }"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/api.ts",
|
||
|
|
"lines": "L74-L80",
|
||
|
|
"excerpt": "@deprecated Cette classe est dépréciée. Utilisez apiClient"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Use ApiService.login()",
|
||
|
|
"Backend returns { success: true, data: { user: {...}, token: {...} } }",
|
||
|
|
"ApiService expects { user, token } directly in data",
|
||
|
|
"Code works but fragile - depends on exact structure"
|
||
|
|
],
|
||
|
|
"expected": "All code uses apiClient with consistent response unwrapping",
|
||
|
|
"actual": "Deprecated ApiService still used, expects different format"
|
||
|
|
},
|
||
|
|
"root_cause": "Legacy ApiService class expects flat structure in data, but backend wraps in nested structure. Class marked deprecated but not fully migrated.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Complete migration from ApiService to apiClient (see MIGRATION_GUIDE.md)",
|
||
|
|
"Search for all ApiService usage",
|
||
|
|
"Replace with apiClient calls",
|
||
|
|
"Remove ApiService class entirely",
|
||
|
|
"Update all imports"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Verify all API calls use apiClient",
|
||
|
|
"No references to ApiService in codebase"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Code still using ApiService will break"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000005",
|
||
|
|
"severity": "P0",
|
||
|
|
"priority_rank": 5,
|
||
|
|
"title": "Missing CSRF Protection for State-Changing Operations",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/services/csrf.ts, apps/web/src/services/secure-auth.ts",
|
||
|
|
"backend": "veza-backend-api (no CSRF middleware found)",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["security", "csrf"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "high",
|
||
|
|
"notes": "Vulnerable to CSRF attacks on POST/PUT/DELETE requests"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/csrf.ts",
|
||
|
|
"lines": "L14-L17",
|
||
|
|
"excerpt": "public async refreshCsrfToken(): Promise<void> {\n // Placeholder: fetch from backend if needed\n // this.csrfToken = ...\n}"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/secure-auth.ts",
|
||
|
|
"lines": "L119",
|
||
|
|
"excerpt": "await csrfService.refreshCsrfToken(); // Does nothing"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Create malicious website",
|
||
|
|
"Include form that POSTs to https://veza.com/api/v1/tracks (user logged in)",
|
||
|
|
"User visits malicious site",
|
||
|
|
"Form submits automatically",
|
||
|
|
"Request succeeds (no CSRF protection)"
|
||
|
|
],
|
||
|
|
"expected": "CSRF token required for POST/PUT/DELETE",
|
||
|
|
"actual": "No CSRF protection implemented"
|
||
|
|
},
|
||
|
|
"root_cause": "CSRF protection not implemented. Frontend has placeholder service, backend has no CSRF middleware. Chat server has CSRF but REST API does not.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Implement CSRF token generation endpoint: GET /api/v1/csrf-token",
|
||
|
|
"Backend: Add CSRF middleware for POST/PUT/DELETE (exclude GET, OPTIONS)",
|
||
|
|
"Frontend: Fetch CSRF token on app init",
|
||
|
|
"Update apiClient interceptor to add X-CSRF-Token header",
|
||
|
|
"Implement csrfService.refreshCsrfToken() to fetch from backend"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"CSRF attack simulation test",
|
||
|
|
"Verify tokens validated on backend",
|
||
|
|
"Test token rotation"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Existing API calls may fail if CSRF required"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000006",
|
||
|
|
"severity": "P1",
|
||
|
|
"priority_rank": 6,
|
||
|
|
"title": "18 Frontend API Calls Target Missing Backend Endpoints",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/features/*/services/*.ts, apps/web/src/services/2fa-service.ts",
|
||
|
|
"backend": "veza-backend-api/internal/api/router.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["api-contract"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": true,
|
||
|
|
"breaks_core_flow": true,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Frontend calls endpoints that don't exist, causing 404 errors"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/2fa-service.ts",
|
||
|
|
"lines": "L17, L22, L27, L31, L35",
|
||
|
|
"excerpt": "GET /api/v1/2fa/status, POST /api/v1/2fa/setup, POST /api/v1/2fa/enable, POST /api/v1/2fa/disable, POST /api/v1/2fa/verify"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/features/playlists/services/playlistService.ts",
|
||
|
|
"lines": "L125, L136, L175, L186, L218",
|
||
|
|
"excerpt": "Missing: /playlists/:id/collaborators, /playlists/search, /playlists/:id/share, /playlists/recommendations"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/api/router.go",
|
||
|
|
"lines": "L494-L528",
|
||
|
|
"excerpt": "Playlist routes only: GET, POST, PUT, DELETE /playlists, POST/DELETE tracks"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Call playlistService.addCollaborator()",
|
||
|
|
"Request: POST /api/v1/playlists/:id/collaborators",
|
||
|
|
"Backend returns 404",
|
||
|
|
"Feature broken"
|
||
|
|
],
|
||
|
|
"expected": "All frontend API calls have corresponding backend endpoints",
|
||
|
|
"actual": "18 endpoints called but not implemented"
|
||
|
|
},
|
||
|
|
"root_cause": "Frontend features implemented but backend endpoints not created. Incomplete feature implementation.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Audit all frontend API calls (grep apiClient.get/post/put/delete)",
|
||
|
|
"Compare with backend routes (router.go)",
|
||
|
|
"For each missing endpoint: implement OR remove frontend call",
|
||
|
|
"Update API documentation",
|
||
|
|
"Add integration tests"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Integration test: Verify all frontend calls have backend endpoints",
|
||
|
|
"E2E test: Verify feature works end-to-end"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Removing frontend calls may break UI features"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000007",
|
||
|
|
"severity": "P1",
|
||
|
|
"priority_rank": 7,
|
||
|
|
"title": "Environment Variable Naming Inconsistency (VITE_API_BASE_URL vs VITE_API_URL)",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/config/env.ts, apps/web/scripts/*.sh, apps/web/Dockerfile",
|
||
|
|
"backend": "",
|
||
|
|
"infra": "deployment configs"
|
||
|
|
},
|
||
|
|
"category": ["env", "build"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": true,
|
||
|
|
"breaks_core_flow": true,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Some scripts use VITE_API_BASE_URL, others use VITE_API_URL, causing build failures"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/config/env.ts",
|
||
|
|
"lines": "L6",
|
||
|
|
"excerpt": "VITE_API_URL: z.string().url().default('http://127.0.0.1:8080/api/v1')"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "apps/web/scripts/check_backend.sh",
|
||
|
|
"lines": "L16",
|
||
|
|
"excerpt": "API_URL=\"${VITE_API_BASE_URL:-http://localhost:8080/api/v1}\""
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "apps/web/Dockerfile",
|
||
|
|
"lines": "L17",
|
||
|
|
"excerpt": "ARG VITE_API_BASE_URL"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Set VITE_API_BASE_URL in environment",
|
||
|
|
"Frontend code reads VITE_API_URL",
|
||
|
|
"VITE_API_URL is undefined",
|
||
|
|
"Uses default or throws error"
|
||
|
|
],
|
||
|
|
"expected": "Consistent env var name: VITE_API_URL",
|
||
|
|
"actual": "Mixed usage: VITE_API_BASE_URL and VITE_API_URL"
|
||
|
|
},
|
||
|
|
"root_cause": "Migration from VITE_API_BASE_URL to VITE_API_URL incomplete. Some scripts and configs still use old name.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Search for all VITE_API_BASE_URL usage",
|
||
|
|
"Replace with VITE_API_URL",
|
||
|
|
"Update scripts/check_backend.sh",
|
||
|
|
"Update Dockerfile",
|
||
|
|
"Update deployment docs"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Verify env var name consistent everywhere",
|
||
|
|
"Build test with VITE_API_URL"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Scripts using old name will break"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000008",
|
||
|
|
"severity": "P1",
|
||
|
|
"priority_rank": 8,
|
||
|
|
"title": "Path Mismatch: /users/:userId/profile vs /users/:id",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/features/profile/services/profileService.ts",
|
||
|
|
"backend": "veza-backend-api/internal/api/router.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["api-contract"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": true,
|
||
|
|
"breaks_core_flow": true,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Frontend calls /users/:userId/profile but backend expects /users/:id"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/features/profile/services/profileService.ts",
|
||
|
|
"lines": "L17, L43",
|
||
|
|
"excerpt": "GET /api/v1/users/${userId}/profile, PUT /api/v1/users/${userId}/profile"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/api/router.go",
|
||
|
|
"lines": "L344, L358",
|
||
|
|
"excerpt": "GET /api/v1/users/:id, PUT /api/v1/users/:id"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Call profileService.getProfile(userId)",
|
||
|
|
"Request: GET /api/v1/users/:userId/profile",
|
||
|
|
"Backend returns 404",
|
||
|
|
"Feature broken"
|
||
|
|
],
|
||
|
|
"expected": "Frontend paths match backend routes",
|
||
|
|
"actual": "Frontend uses /profile suffix, backend doesn't"
|
||
|
|
},
|
||
|
|
"root_cause": "Frontend uses /profile suffix but backend routes don't include it. Inconsistent API design.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Option A: Update frontend to use /users/:id (recommended)",
|
||
|
|
"Option B: Add /profile routes to backend",
|
||
|
|
"Update profileService to use correct paths",
|
||
|
|
"Test profile features"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Integration test: Verify profile endpoints work",
|
||
|
|
"E2E test: Verify profile page loads"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Changing paths may break existing code"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000009",
|
||
|
|
"severity": "P1",
|
||
|
|
"priority_rank": 9,
|
||
|
|
"title": "Error Code Type Mismatch: number vs string in ApiError",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/types/api.ts, apps/web/src/schemas/validation.ts",
|
||
|
|
"backend": "veza-backend-api/internal/errors/codes.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["schema-drift", "error-handling"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Type mismatches in error handling, potential runtime errors"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/types/api.ts",
|
||
|
|
"lines": "L245",
|
||
|
|
"excerpt": "code: number; // ✅ Correct"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/schemas/validation.ts",
|
||
|
|
"lines": "L338",
|
||
|
|
"excerpt": "code: z.string(), // ❌ Wrong type"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/errors/codes.go",
|
||
|
|
"lines": "L1-L32",
|
||
|
|
"excerpt": "ErrorCode is int type"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Backend returns error with code: 1000 (number)",
|
||
|
|
"Frontend Zod schema expects code: string",
|
||
|
|
"Validation fails or coerces incorrectly"
|
||
|
|
],
|
||
|
|
"expected": "Error code is number everywhere",
|
||
|
|
"actual": "Mixed types: number in types, string in Zod schemas"
|
||
|
|
},
|
||
|
|
"root_cause": "Inconsistent error code types. Backend uses int, frontend types use number, but Zod schemas use string.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Update Zod schemas to use z.number() for error code",
|
||
|
|
"Verify all error parsing uses number type",
|
||
|
|
"Update apiErrorHandler to handle number codes",
|
||
|
|
"Test error handling"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify error codes parsed as numbers",
|
||
|
|
"Test: Error display works correctly"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Code expecting string may break"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000010",
|
||
|
|
"severity": "P1",
|
||
|
|
"priority_rank": 10,
|
||
|
|
"title": "Remember Me Field Handling Inconsistency",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/services/api/auth.ts, apps/web/src/features/auth/types/index.ts",
|
||
|
|
"backend": "veza-backend-api/internal/dto/login_request.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["api-contract"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Field name inconsistency: remember_me vs rememberMe, may cause issues"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/api/auth.ts",
|
||
|
|
"lines": "L150",
|
||
|
|
"excerpt": "remember_me: data.remember_me || false"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/features/auth/types/index.ts",
|
||
|
|
"lines": "L11",
|
||
|
|
"excerpt": "remember_me?: boolean;"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/dto/login_request.go",
|
||
|
|
"lines": "L6",
|
||
|
|
"excerpt": "RememberMe bool `json:\"remember_me\"`"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Login form uses rememberMe (camelCase)",
|
||
|
|
"API call converts to remember_me (snake_case)",
|
||
|
|
"Backend expects remember_me",
|
||
|
|
"Works but inconsistent naming"
|
||
|
|
],
|
||
|
|
"expected": "Consistent field naming",
|
||
|
|
"actual": "Mixed: rememberMe in forms, remember_me in API"
|
||
|
|
},
|
||
|
|
"root_cause": "Frontend uses camelCase in forms but backend expects snake_case. Conversion happens but inconsistent.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Standardize on snake_case for API (matches backend)",
|
||
|
|
"Update form types to use remember_me",
|
||
|
|
"Update all references",
|
||
|
|
"Test login with remember me"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Login with remember_me=true",
|
||
|
|
"Test: Token expiration matches remember_me setting"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Forms using rememberMe may break"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000011",
|
||
|
|
"severity": "P1",
|
||
|
|
"priority_rank": 11,
|
||
|
|
"title": "Token Refresh Response Format Ambiguity",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/services/tokenRefresh.ts",
|
||
|
|
"backend": "veza-backend-api/internal/handlers/auth.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["api-contract", "error-handling"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": true,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Multiple format checks in tokenRefresh suggest uncertainty about response structure"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/tokenRefresh.ts",
|
||
|
|
"lines": "L70-L84",
|
||
|
|
"excerpt": "// Format with wrapper { success: true, data: {...} }\nif (response.data?.data?.access_token) { ... }\n// Format direct { access_token, refresh_token, expires_in }\nelse if (response.data?.access_token) { ... }\n// Format with token nested { token: { access_token, refresh_token } }\nelse if (response.data?.token?.access_token) { ... }"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/handlers/auth.go",
|
||
|
|
"lines": "L197-L201",
|
||
|
|
"excerpt": "RespondSuccess(c, http.StatusOK, dto.TokenResponse{...}) // Returns { success: true, data: {...} }"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Token refresh called",
|
||
|
|
"Backend returns { success: true, data: { access_token, refresh_token, expires_in } }",
|
||
|
|
"Frontend checks 3 different formats",
|
||
|
|
"Works but fragile"
|
||
|
|
],
|
||
|
|
"expected": "Single, documented response format",
|
||
|
|
"actual": "Multiple format checks suggest uncertainty"
|
||
|
|
},
|
||
|
|
"root_cause": "Frontend handles multiple response formats to be defensive, but this indicates contract uncertainty.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Document exact response format in FRONTEND_INTEGRATION.md",
|
||
|
|
"Simplify tokenRefresh to only handle correct format",
|
||
|
|
"Remove fallback format checks",
|
||
|
|
"Add test for refresh response parsing"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify refresh response parsed correctly",
|
||
|
|
"Test: Error handling for invalid format"
|
||
|
|
],
|
||
|
|
"regression_risks": ["If format changes, code will break"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000012",
|
||
|
|
"severity": "P1",
|
||
|
|
"priority_rank": 12,
|
||
|
|
"title": "No Retry Logic for Transient Errors (503, 502)",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/services/api/client.ts",
|
||
|
|
"backend": "",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["error-handling", "reliability"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": true,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "503/502 errors are handled but not retried, causing user-visible failures"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/api/client.ts",
|
||
|
|
"lines": "L201-L213",
|
||
|
|
"excerpt": "if (status === 503) { return Promise.reject(apiError); }\nif (status === 502) { return Promise.reject(apiError); } // No retry"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Backend returns 503 (Service Unavailable)",
|
||
|
|
"Frontend rejects immediately",
|
||
|
|
"User sees error",
|
||
|
|
"No automatic retry"
|
||
|
|
],
|
||
|
|
"expected": "Transient errors (503, 502) should be retried with exponential backoff",
|
||
|
|
"actual": "Errors rejected immediately, no retry"
|
||
|
|
},
|
||
|
|
"root_cause": "No retry logic for transient errors. 429 (rate limit) has retry but 503/502 do not.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Add retry logic for 503/502 errors",
|
||
|
|
"Implement exponential backoff (max 3 retries)",
|
||
|
|
"Add retry-after header support",
|
||
|
|
"Update error messages to indicate retry"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify 503 retries with backoff",
|
||
|
|
"Test: Verify 502 retries with backoff",
|
||
|
|
"Test: Max retries respected"
|
||
|
|
],
|
||
|
|
"regression_risks": ["May cause excessive retries if not configured correctly"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000013",
|
||
|
|
"severity": "P1",
|
||
|
|
"priority_rank": 13,
|
||
|
|
"title": "Missing Error Correlation IDs in Frontend Error Boundaries",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src (error boundaries)",
|
||
|
|
"backend": "veza-backend-api/internal/response/response.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["observability", "error-handling"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Backend returns request_id but frontend doesn't log it, making debugging difficult"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/response/response.go",
|
||
|
|
"lines": "L106",
|
||
|
|
"excerpt": "RequestID: c.GetString(\"request_id\")"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"API error occurs",
|
||
|
|
"Backend returns { error: { request_id: \"req-123\", ... } }",
|
||
|
|
"Frontend logs error but doesn't include request_id",
|
||
|
|
"Debugging difficult"
|
||
|
|
],
|
||
|
|
"expected": "Frontend error logs include request_id for correlation",
|
||
|
|
"actual": "Request ID not logged in frontend"
|
||
|
|
},
|
||
|
|
"root_cause": "Backend provides request_id for error correlation but frontend doesn't extract or log it.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Update apiErrorHandler to extract request_id",
|
||
|
|
"Include request_id in error logs",
|
||
|
|
"Update error boundaries to log request_id",
|
||
|
|
"Add request_id to user-facing error messages (optional)"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify request_id logged in errors",
|
||
|
|
"Test: Error correlation works"
|
||
|
|
],
|
||
|
|
"regression_risks": ["None"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000014",
|
||
|
|
"severity": "P1",
|
||
|
|
"priority_rank": 14,
|
||
|
|
"title": "CORS Allow-Credentials Always True (Security Risk if Origins Misconfigured)",
|
||
|
|
"component": {
|
||
|
|
"frontend": "",
|
||
|
|
"backend": "veza-backend-api/internal/middleware/cors.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["cors", "security"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "medium",
|
||
|
|
"notes": "If CORS origins misconfigured, credentials flag enables cookie theft"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/middleware/cors.go",
|
||
|
|
"lines": "L21",
|
||
|
|
"excerpt": "c.Header(\"Access-Control-Allow-Credentials\", \"true\") // Always true"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"CORS origins misconfigured (e.g., contains wildcard or wrong domain)",
|
||
|
|
"Malicious site makes credentialed request",
|
||
|
|
"Credentials flag allows cookies/tokens to be sent",
|
||
|
|
"Security risk"
|
||
|
|
],
|
||
|
|
"expected": "Credentials only allowed if origins correctly whitelisted",
|
||
|
|
"actual": "Credentials always true, relies on origins being correct"
|
||
|
|
},
|
||
|
|
"root_cause": "Credentials flag hardcoded to true. Relies on origins being correctly configured. If origins misconfigured, security risk.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Add validation: credentials only if origins non-empty and no wildcard",
|
||
|
|
"Log warning if credentials=true but origins contain wildcard",
|
||
|
|
"Document security implications",
|
||
|
|
"Add test for credentials + wildcard combination"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify credentials rejected with wildcard",
|
||
|
|
"Test: Verify credentials allowed with specific origins"
|
||
|
|
],
|
||
|
|
"regression_risks": ["If origins misconfigured, requests may fail"]
|
||
|
|
},
|
||
|
|
"dependencies": ["INT-000001"],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000015",
|
||
|
|
"severity": "P1",
|
||
|
|
"priority_rank": 15,
|
||
|
|
"title": "GetMe Endpoint Returns Incomplete User Data",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/features/auth/api/authApi.ts",
|
||
|
|
"backend": "veza-backend-api/internal/handlers/auth.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["api-contract"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": true,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "GET /auth/me returns only id, email, role but frontend expects full user object"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/handlers/auth.go",
|
||
|
|
"lines": "L369-L373",
|
||
|
|
"excerpt": "RespondSuccess(c, http.StatusOK, gin.H{\n \"id\": userID,\n \"email\": c.GetString(\"email\"),\n \"role\": c.GetString(\"role\")\n}) // Missing username, avatar, etc."
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/features/auth/api/authApi.ts",
|
||
|
|
"lines": "L34-L36",
|
||
|
|
"excerpt": "getMe: async (): Promise<User> => { ... } // Expects full User object"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Call authApi.getMe()",
|
||
|
|
"Backend returns { id, email, role }",
|
||
|
|
"Frontend expects full User object with username, avatar, etc.",
|
||
|
|
"Type mismatch or missing fields"
|
||
|
|
],
|
||
|
|
"expected": "GET /auth/me returns full user object",
|
||
|
|
"actual": "Returns only id, email, role"
|
||
|
|
},
|
||
|
|
"root_cause": "GetMe handler returns minimal user data from context, not full user object from database.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Update GetMe handler to fetch full user from database",
|
||
|
|
"Return complete UserResponse object",
|
||
|
|
"Update frontend types if needed",
|
||
|
|
"Test getMe returns all expected fields"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify getMe returns full user object",
|
||
|
|
"Test: Verify all User fields present"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Frontend may break if response structure changes"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000016",
|
||
|
|
"severity": "P2",
|
||
|
|
"priority_rank": 16,
|
||
|
|
"title": "Field Name Mismatch: cover_art_path vs cover_art_url",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/types/api.ts",
|
||
|
|
"backend": "veza-backend-api (track models)",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["schema-drift"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Field name inconsistency may cause display issues"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/types/api.ts",
|
||
|
|
"lines": "L66",
|
||
|
|
"excerpt": "cover_art_path?: string; // Backend uses cover_art_path"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Backend returns cover_art_path",
|
||
|
|
"Frontend expects cover_art_path",
|
||
|
|
"Works but naming inconsistent with other _url fields"
|
||
|
|
],
|
||
|
|
"expected": "Consistent field naming",
|
||
|
|
"actual": "Mixed: _path vs _url"
|
||
|
|
},
|
||
|
|
"root_cause": "Inconsistent naming convention. Some fields use _url, others use _path.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Document field naming convention",
|
||
|
|
"Standardize on _url for URLs, _path for file paths",
|
||
|
|
"Update types and backend if needed",
|
||
|
|
"Update documentation"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify cover art displays correctly",
|
||
|
|
"Test: Verify field names consistent"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Changing field names may break existing code"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000017",
|
||
|
|
"severity": "P2",
|
||
|
|
"priority_rank": 17,
|
||
|
|
"title": "Pagination Format Inconsistencies",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/types/api.ts",
|
||
|
|
"backend": "veza-backend-api (pagination responses)",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["schema-drift"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Different pagination formats may cause parsing issues"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/types/api.ts",
|
||
|
|
"lines": "L222-L231",
|
||
|
|
"excerpt": "PaginationData: { page, limit, total, total_pages, has_next, has_prev, next_cursor?, prev_cursor? }"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Backend returns pagination in different format",
|
||
|
|
"Frontend expects specific format",
|
||
|
|
"Parsing may fail or be inconsistent"
|
||
|
|
],
|
||
|
|
"expected": "Consistent pagination format across all endpoints",
|
||
|
|
"actual": "May vary by endpoint"
|
||
|
|
},
|
||
|
|
"root_cause": "No standardized pagination format. Different endpoints may return different structures.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Document standard pagination format",
|
||
|
|
"Audit all paginated endpoints",
|
||
|
|
"Standardize pagination responses",
|
||
|
|
"Update frontend types"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify pagination format consistent",
|
||
|
|
"Test: Verify pagination parsing works"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Changing pagination format may break existing code"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000018",
|
||
|
|
"severity": "P2",
|
||
|
|
"priority_rank": 18,
|
||
|
|
"title": "Missing Validation Schema Alignment Between Frontend and Backend",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/schemas/validation.ts",
|
||
|
|
"backend": "veza-backend-api/internal/validators",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["schema-drift", "validation"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Frontend and backend validation rules may differ, causing inconsistent errors"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/schemas/validation.ts",
|
||
|
|
"lines": "L1-L359",
|
||
|
|
"excerpt": "Zod schemas for validation"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Frontend validates email format",
|
||
|
|
"Backend validates differently",
|
||
|
|
"Frontend passes, backend rejects (or vice versa)",
|
||
|
|
"Inconsistent user experience"
|
||
|
|
],
|
||
|
|
"expected": "Frontend and backend validation rules match",
|
||
|
|
"actual": "Validation rules may differ"
|
||
|
|
},
|
||
|
|
"root_cause": "No synchronization between frontend Zod schemas and backend validators. Rules may drift.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Audit validation rules in both frontend and backend",
|
||
|
|
"Document validation rules in shared doc",
|
||
|
|
"Align frontend Zod schemas with backend validators",
|
||
|
|
"Add tests to verify alignment"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify validation rules match",
|
||
|
|
"Test: Verify error messages consistent"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Changing validation may break existing forms"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000019",
|
||
|
|
"severity": "P2",
|
||
|
|
"priority_rank": 19,
|
||
|
|
"title": "No Request ID Propagation to Frontend Logs",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/services/api/client.ts",
|
||
|
|
"backend": "veza-backend-api/internal/middleware/request_id.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["observability"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Backend generates request_id but frontend doesn't log it, making debugging difficult"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/middleware/request_id.go",
|
||
|
|
"lines": "L1-L20",
|
||
|
|
"excerpt": "RequestID middleware generates X-Request-ID header"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"API request made",
|
||
|
|
"Backend generates request_id",
|
||
|
|
"Frontend doesn't log request_id",
|
||
|
|
"Debugging difficult"
|
||
|
|
],
|
||
|
|
"expected": "Frontend logs include request_id for correlation",
|
||
|
|
"actual": "Request ID not logged in frontend"
|
||
|
|
},
|
||
|
|
"root_cause": "Backend generates request_id but frontend doesn't extract or log it in request/response interceptors.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Update apiClient interceptor to extract X-Request-ID from response",
|
||
|
|
"Include request_id in request logs",
|
||
|
|
"Include request_id in error logs",
|
||
|
|
"Add request_id to error objects"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify request_id logged",
|
||
|
|
"Test: Verify request_id in error objects"
|
||
|
|
],
|
||
|
|
"regression_risks": ["None"]
|
||
|
|
},
|
||
|
|
"dependencies": ["INT-000013"],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000020",
|
||
|
|
"severity": "P2",
|
||
|
|
"priority_rank": 20,
|
||
|
|
"title": "Inconsistent Error Detail Formats",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/utils/apiErrorHandler.ts",
|
||
|
|
"backend": "veza-backend-api/internal/errors/errors.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["error-handling"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Different error detail formats may cause parsing issues"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/utils/apiErrorHandler.ts",
|
||
|
|
"lines": "L145-L154",
|
||
|
|
"excerpt": "normalizeApiError handles multiple error formats"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Backend returns error with details array",
|
||
|
|
"Frontend expects specific format",
|
||
|
|
"Parsing may fail or be inconsistent"
|
||
|
|
],
|
||
|
|
"expected": "Consistent error detail format",
|
||
|
|
"actual": "Multiple formats handled, suggests inconsistency"
|
||
|
|
},
|
||
|
|
"root_cause": "Backend may return errors in different formats, frontend handles multiple formats defensively.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Standardize error detail format in backend",
|
||
|
|
"Simplify frontend error parsing",
|
||
|
|
"Remove fallback format handling",
|
||
|
|
"Update error documentation"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify error parsing works",
|
||
|
|
"Test: Verify error details displayed correctly"
|
||
|
|
],
|
||
|
|
"regression_risks": ["If format changes, parsing may break"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000021",
|
||
|
|
"severity": "P2",
|
||
|
|
"priority_rank": 21,
|
||
|
|
"title": "Missing Health Check Integration Tests",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/e2e",
|
||
|
|
"backend": "veza-backend-api/internal/handlers/health.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["testing", "observability"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "No E2E tests verify health check endpoints work"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/e2e/qa-audit.spec.ts",
|
||
|
|
"lines": "L80",
|
||
|
|
"excerpt": "const response = await page.request.get(`${API_URL}/health`); // Basic check but no assertion"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Health check endpoint called",
|
||
|
|
"No test verifies response format",
|
||
|
|
"If format changes, no test catches it"
|
||
|
|
],
|
||
|
|
"expected": "E2E tests verify health check endpoints",
|
||
|
|
"actual": "Basic check exists but no assertions"
|
||
|
|
},
|
||
|
|
"root_cause": "Health check endpoints exist but not properly tested in E2E suite.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Add E2E test for GET /api/v1/health",
|
||
|
|
"Verify response format",
|
||
|
|
"Verify health status",
|
||
|
|
"Add tests for /healthz and /readyz"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"E2E test: Verify /health returns correct format",
|
||
|
|
"E2E test: Verify /healthz returns 200",
|
||
|
|
"E2E test: Verify /readyz returns 200"
|
||
|
|
],
|
||
|
|
"regression_risks": ["None"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000022",
|
||
|
|
"severity": "P2",
|
||
|
|
"priority_rank": 22,
|
||
|
|
"title": "No API Versioning Strategy",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/services/api/client.ts",
|
||
|
|
"backend": "veza-backend-api/internal/api/router.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["api-contract"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "All routes under /api/v1, no versioning strategy for future changes"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/api/router.go",
|
||
|
|
"lines": "L152",
|
||
|
|
"excerpt": "v1 := router.Group(\"/api/v1\") // Hardcoded v1"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"API needs breaking change",
|
||
|
|
"No versioning strategy",
|
||
|
|
"Must break existing clients or maintain backward compatibility forever"
|
||
|
|
],
|
||
|
|
"expected": "Versioning strategy for API changes",
|
||
|
|
"actual": "All routes under /api/v1, no migration path"
|
||
|
|
},
|
||
|
|
"root_cause": "No API versioning strategy. All routes under /api/v1 with no plan for v2.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Document API versioning strategy",
|
||
|
|
"Plan for v2 when needed",
|
||
|
|
"Add version negotiation if needed",
|
||
|
|
"Update documentation"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify versioning works",
|
||
|
|
"Test: Verify backward compatibility"
|
||
|
|
],
|
||
|
|
"regression_risks": ["None - planning only"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000023",
|
||
|
|
"severity": "P2",
|
||
|
|
"priority_rank": 23,
|
||
|
|
"title": "WebSocket Token in Query Params (Security Risk)",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/services/api.ts",
|
||
|
|
"backend": "",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["security", "websocket"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "medium",
|
||
|
|
"notes": "WebSocket tokens in URL query params exposed in logs, browser history"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/api.ts",
|
||
|
|
"lines": "L565",
|
||
|
|
"excerpt": "return `${WS_BASE_URL}?token=${token}`; // Token in query param"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"WebSocket connection established",
|
||
|
|
"Token in URL: ws://example.com/ws?token=xxx",
|
||
|
|
"Token exposed in browser history, server logs",
|
||
|
|
"Security risk"
|
||
|
|
],
|
||
|
|
"expected": "Token in header or subprotocol, not query param",
|
||
|
|
"actual": "Token in query param"
|
||
|
|
},
|
||
|
|
"root_cause": "WebSocket authentication uses query param instead of header or subprotocol.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Move token to WebSocket subprotocol or header",
|
||
|
|
"Update WebSocket client to use header",
|
||
|
|
"Update chat server to read from header",
|
||
|
|
"Remove token from query params"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify WebSocket auth works with header",
|
||
|
|
"Test: Verify token not in URL"
|
||
|
|
],
|
||
|
|
"regression_risks": ["WebSocket connections may break if not updated correctly"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000024",
|
||
|
|
"severity": "P3",
|
||
|
|
"priority_rank": 24,
|
||
|
|
"title": "Deprecated Routes Still Accessible",
|
||
|
|
"component": {
|
||
|
|
"frontend": "",
|
||
|
|
"backend": "veza-backend-api/internal/api/router.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["api-contract", "maintainability"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Deprecated routes still work, causing confusion and maintenance burden"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "veza-backend-api/internal/api/router.go",
|
||
|
|
"lines": "L596-L603",
|
||
|
|
"excerpt": "router.GET(\"/health\", deprecationMW, healthCheckHandler) // Deprecated but still works"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Call deprecated /health endpoint",
|
||
|
|
"Returns deprecation warning but still works",
|
||
|
|
"No removal plan"
|
||
|
|
],
|
||
|
|
"expected": "Deprecated routes removed or have removal timeline",
|
||
|
|
"actual": "Deprecated routes still accessible indefinitely"
|
||
|
|
},
|
||
|
|
"root_cause": "Deprecated routes marked with warning but not removed. No removal timeline.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Document removal timeline for deprecated routes",
|
||
|
|
"Add removal date to deprecation warnings",
|
||
|
|
"Plan migration path",
|
||
|
|
"Remove deprecated routes after migration period"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify deprecated routes return warnings",
|
||
|
|
"Test: Verify new routes work"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Removing routes may break existing clients"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000025",
|
||
|
|
"severity": "P3",
|
||
|
|
"priority_rank": 25,
|
||
|
|
"title": "Inconsistent Logging Formats",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src (logging)",
|
||
|
|
"backend": "veza-backend-api (structured logging)",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["observability"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Frontend uses console.log, backend uses structured logging, making correlation difficult"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/api/client.ts",
|
||
|
|
"lines": "L62",
|
||
|
|
"excerpt": "console.error('[API] Failed to parse auth storage:', e); // Unstructured"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Frontend logs error with console.error",
|
||
|
|
"Backend logs with structured format",
|
||
|
|
"Log aggregation difficult",
|
||
|
|
"Correlation challenging"
|
||
|
|
],
|
||
|
|
"expected": "Structured logging in frontend for correlation",
|
||
|
|
"actual": "Mixed: console.log in frontend, structured in backend"
|
||
|
|
},
|
||
|
|
"root_cause": "Frontend uses console.log while backend uses structured logging. No correlation.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Introduce structured logging library for frontend",
|
||
|
|
"Replace console.log with structured logger",
|
||
|
|
"Include request_id, user_id in logs",
|
||
|
|
"Update log aggregation config"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify logs structured",
|
||
|
|
"Test: Verify correlation works"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Changing logging may break log aggregation"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000026",
|
||
|
|
"severity": "P3",
|
||
|
|
"priority_rank": 26,
|
||
|
|
"title": "Missing Observability Hooks in Frontend",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src (error boundaries, API calls)",
|
||
|
|
"backend": "",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["observability"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "No error tracking (Sentry), no performance monitoring, making production debugging difficult"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Error occurs in production",
|
||
|
|
"No error tracking",
|
||
|
|
"Debugging difficult",
|
||
|
|
"No visibility into issues"
|
||
|
|
],
|
||
|
|
"expected": "Error tracking and performance monitoring in frontend",
|
||
|
|
"actual": "No observability tools integrated"
|
||
|
|
},
|
||
|
|
"root_cause": "No error tracking or performance monitoring integrated in frontend.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Integrate Sentry or similar error tracking",
|
||
|
|
"Add performance monitoring",
|
||
|
|
"Add error boundaries with tracking",
|
||
|
|
"Configure error reporting"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify errors tracked",
|
||
|
|
"Test: Verify performance monitored"
|
||
|
|
],
|
||
|
|
"regression_risks": ["None"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000027",
|
||
|
|
"severity": "P3",
|
||
|
|
"priority_rank": 27,
|
||
|
|
"title": "No Rate Limit Headers in Responses",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/services/api/client.ts",
|
||
|
|
"backend": "veza-backend-api (rate limiting)",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["observability", "api-contract"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Rate limit info not exposed, frontend can't show remaining requests"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/services/api/client.ts",
|
||
|
|
"lines": "L181-L199",
|
||
|
|
"excerpt": "Handles 429 but no rate limit headers checked"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Rate limit hit",
|
||
|
|
"Backend returns 429",
|
||
|
|
"Frontend doesn't know remaining requests",
|
||
|
|
"Can't show user-friendly message"
|
||
|
|
],
|
||
|
|
"expected": "Rate limit headers (X-RateLimit-*) in responses",
|
||
|
|
"actual": "No rate limit headers"
|
||
|
|
},
|
||
|
|
"root_cause": "Backend rate limiting exists but doesn't expose rate limit info in headers.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Add X-RateLimit-* headers to rate limit middleware",
|
||
|
|
"Update frontend to read and display rate limit info",
|
||
|
|
"Update error messages to show remaining requests",
|
||
|
|
"Document rate limit headers"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify rate limit headers present",
|
||
|
|
"Test: Verify frontend displays rate limit info"
|
||
|
|
],
|
||
|
|
"regression_risks": ["None"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000028",
|
||
|
|
"severity": "P3",
|
||
|
|
"priority_rank": 28,
|
||
|
|
"title": "Missing API Documentation Updates",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/FRONTEND_INTEGRATION.md",
|
||
|
|
"backend": "veza-backend-api/docs",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["documentation"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "API documentation may be out of sync with implementation"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/FRONTEND_INTEGRATION.md",
|
||
|
|
"lines": "L1-L307",
|
||
|
|
"excerpt": "API documentation"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"API endpoint changes",
|
||
|
|
"Documentation not updated",
|
||
|
|
"Developers use wrong format",
|
||
|
|
"Errors occur"
|
||
|
|
],
|
||
|
|
"expected": "API documentation always up to date",
|
||
|
|
"actual": "Documentation may be outdated"
|
||
|
|
},
|
||
|
|
"root_cause": "No automated process to keep API documentation in sync with implementation.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Use OpenAPI/Swagger for API documentation",
|
||
|
|
"Generate docs from code",
|
||
|
|
"Update FRONTEND_INTEGRATION.md from OpenAPI spec",
|
||
|
|
"Add CI check for doc sync"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify docs match implementation",
|
||
|
|
"Test: Verify CI catches doc drift"
|
||
|
|
],
|
||
|
|
"regression_risks": ["None"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000029",
|
||
|
|
"severity": "P3",
|
||
|
|
"priority_rank": 29,
|
||
|
|
"title": "No Vite Proxy Configuration for Development",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/vite.config.ts",
|
||
|
|
"backend": "",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["env", "build"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": false,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "Direct CORS requests in dev, no proxy. Works but not ideal."
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/vite.config.ts",
|
||
|
|
"lines": "L58-L92",
|
||
|
|
"excerpt": "server config but no proxy setup"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Frontend on localhost:3000",
|
||
|
|
"Backend on localhost:8080",
|
||
|
|
"CORS required",
|
||
|
|
"No proxy, direct requests"
|
||
|
|
],
|
||
|
|
"expected": "Vite proxy for /api to backend (optional but cleaner)",
|
||
|
|
"actual": "No proxy, direct CORS requests"
|
||
|
|
},
|
||
|
|
"root_cause": "No Vite proxy configuration. Relies on CORS for dev. Works but proxy would be cleaner.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Add Vite proxy config for /api to http://localhost:8080",
|
||
|
|
"Update VITE_API_URL to use relative path /api/v1",
|
||
|
|
"Test proxy works",
|
||
|
|
"Update dev docs"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify proxy works",
|
||
|
|
"Test: Verify API calls work through proxy"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Changing to proxy may break existing dev setup"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "frontend"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "INT-000030",
|
||
|
|
"severity": "P3",
|
||
|
|
"priority_rank": 30,
|
||
|
|
"title": "Missing HLS Endpoints (Frontend Calls But Backend Doesn't Implement)",
|
||
|
|
"component": {
|
||
|
|
"frontend": "apps/web/src/features/streaming/services/hlsService.ts",
|
||
|
|
"backend": "veza-backend-api/internal/api/router.go",
|
||
|
|
"infra": ""
|
||
|
|
},
|
||
|
|
"category": ["api-contract"],
|
||
|
|
"status": "open",
|
||
|
|
"impact": {
|
||
|
|
"user_visible": true,
|
||
|
|
"breaks_core_flow": false,
|
||
|
|
"security_risk": "none",
|
||
|
|
"notes": "HLS streaming features may not work"
|
||
|
|
},
|
||
|
|
"evidence": {
|
||
|
|
"files": [
|
||
|
|
{
|
||
|
|
"path": "apps/web/src/features/streaming/services/hlsService.ts",
|
||
|
|
"lines": "L85, L97",
|
||
|
|
"excerpt": "GET /api/v1/tracks/:id/hls/info, GET /api/v1/tracks/:id/hls/status"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"runtime": []
|
||
|
|
},
|
||
|
|
"repro": {
|
||
|
|
"steps": [
|
||
|
|
"Call hlsService.getHLSInfo()",
|
||
|
|
"Request: GET /api/v1/tracks/:id/hls/info",
|
||
|
|
"Backend returns 404",
|
||
|
|
"HLS features broken"
|
||
|
|
],
|
||
|
|
"expected": "HLS endpoints implemented or frontend calls removed",
|
||
|
|
"actual": "Frontend calls endpoints that don't exist"
|
||
|
|
},
|
||
|
|
"root_cause": "HLS streaming features in frontend but backend endpoints not implemented.",
|
||
|
|
"fix_plan": {
|
||
|
|
"minimal_steps": [
|
||
|
|
"Implement HLS endpoints in backend OR",
|
||
|
|
"Remove HLS service calls from frontend",
|
||
|
|
"Update documentation",
|
||
|
|
"Test streaming works"
|
||
|
|
],
|
||
|
|
"tests_to_add": [
|
||
|
|
"Test: Verify HLS endpoints work",
|
||
|
|
"Test: Verify streaming works end-to-end"
|
||
|
|
],
|
||
|
|
"regression_risks": ["Removing calls may break streaming features"]
|
||
|
|
},
|
||
|
|
"dependencies": [],
|
||
|
|
"owner_suggestion": "backend"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|