Full-stack QA from scratch · April 6, 2026 · Conducted by Claude (QA Agent)
| Test | User | Result | Details |
|---|---|---|---|
| Email/Password login | admin@tpm-test.com | Pass | JWT role=admin ✓ |
| Email/Password login | kam@tpm-test.com | Pass | JWT role=kam ✓ |
| Email/Password login | analyst@tpm-test.com | Pass | JWT role=analyst ✓ |
| Email/Password login | executive@tpm-test.com | Pass | JWT role=executive ✓ |
| Login page loads (deployed) | — | Pass | HTTP 200, form renders with email/password + magic link |
| Root (/) redirect | — | Pass | Client-side redirect to /login (unauth) or /chat (auth) |
| JWT role in app_metadata | All 4 users | Pass | Roles correctly stored in JWT claims |
| Invalid credentials | invalid token | Warn | Backend returns 500 instead of 401 for invalid tokens |
After successful email/password login, login/page.tsx line 51 hardcodes window.location.href = '/chat'. This means executives land on /chat instead of their /executive dashboard, and admins land on /chat instead of /admin. Role-aware redirect should use ROLE_NAV_CONFIG[role].defaultRoute.
The /register page renders a placeholder with no actual registration form. Self-registration is non-functional.
| Route | Status | Notes |
|---|---|---|
/ | 200 | Redirects via JS |
/login | 200 | Renders correctly |
/register | 200 | Stub page only |
/callback | 404 | Magic link callback broken on deployed site |
/chat | 200 | ✓ |
/evaluate | 200 | ✓ |
/plan | 200 | ✓ |
/reports | 200 | ✓ |
/anomalies | 200 | ✓ |
/admin | 200 | ✓ |
/analyst | 200 | ✓ |
/executive | 200 | ✓ |
/upload | 200 | ✓ |
/dashboard | 404 | No standalone dashboard route (expected — navigated via sub-routes) |
The magic link auth callback at /callback returns 404 on tpm-intelligence-engine.pages.dev, despite existing in the local out/ build directory. The latest build has not been deployed, or Cloudflare Pages is serving a stale build. Magic link authentication is completely broken in production.
| Endpoint | Status | Details |
|---|---|---|
Cloud Run /health | 200 | tpm-core v0.1.0 healthy |
Cloud Run /docs | 200 | FastAPI Swagger UI loads |
Cloud Run /api/skills | 200 | Endpoint available |
Cloudflare Worker /health | ECONNREFUSED | Worker is down / not deployed |
Cloudflare Worker /api/skills | ECONNREFUSED | All Worker endpoints unreachable |
The frontend .env.local points to https://tpm-api.aiguildpartner.workers.dev (the Worker), which returns ECONNREFUSED on all endpoints. The Cloud Run backend at tpm-core-907519043541.asia-southeast1.run.app works fine. The frontend in local dev mode will fail to reach the API. The .env.production correctly points to Cloud Run.
| Test | Status | Response |
|---|---|---|
| KAM: POST /api/chat | 200 | Stub response — "Full AI capabilities coming soon" |
| Analyst: POST /api/upload | 200 | Accepted |
| KAM: POST /api/evaluate | 200 | Empty evaluations (initializing) |
| Admin: GET /api/admin/users | 200 | Endpoint available |
| Admin: GET /api/admin/feature-flags | 200 | Endpoint available |
| Exec: GET /api/executive/metrics | 200 | Endpoint available |
| KAM: GET /api/anomalies | 200 | Empty anomalies (initializing) |
| KAM: GET /api/reports | 200 | Endpoint available |
| KAM: POST /api/plan | 200 | Endpoint available |
Note: Most endpoints return "Endpoint available" placeholders rather than real data or logic. The chat endpoint returns a hardcoded stub response.
| Test (should be DENIED) | Actual Status | Expected | Verdict |
|---|---|---|---|
| Executive → /api/admin/users | 200 | 403 | FAIL |
| Executive → /api/upload | 200 | 403 | FAIL |
| KAM → /api/admin/users | 200 | 403 | FAIL |
| KAM → /api/upload | 200 | 403 | FAIL |
| Invalid token → /api/chat | 500 | 401 | FAIL |
Only admin.py (2 routes) uses the require_role() dependency. The remaining 20+ routes in chat, evaluate, plan, report, anomalies, executive, corrections, consumers, and upload have zero role-based protection. Any authenticated user can access any endpoint regardless of their role.
Root cause: The JWTAuthMiddleware in app.py is a stub — in non-production mode it accepts any token. The require_role() dependency exists in rbac.py but is not wired into most route handlers. Additionally, corrections.py has docstrings claiming role enforcement that is NOT implemented.
Frontend (rbac.ts): executive(1) < kam(2) < analyst(3) < admin(4)
Backend (rbac.py): viewer(1) < analyst(2) < kam(3) < admin(4)
The backend places KAM above analyst, while the frontend places analyst above KAM. It also uses "viewer" instead of "executive". When backend RBAC is enforced, analysts would be unable to access KAM-level endpoints, contradicting the frontend behavior where analysts have broader access.
| Page | Protection Mechanism | Verdict |
|---|---|---|
| /admin | RoleGuard requiredRole="admin" | Protected |
| /analyst | Manual profile?.role check | Partial |
| /chat | Auth only (useAuth) | No role check |
| /evaluate | Auth only (useAuth) | No role check |
| /plan | Auth only (useAuth) | No role check |
| /reports | Auth only (useAuth) | No role check |
| /anomalies | Auth only (useAuth) | No role check |
| /executive | Auth only (useAuth), "enforced by layout" | Layout only |
| /upload | Auth only (useAuth) | No role check |
The DashboardShell layout does check ROLE_ALLOWED_PAGES and shows a 403 page if a user navigates outside their role. However, this is client-side only and can be bypassed by calling APIs directly. It also relies on pathname.startsWith() matching, which works because the static export produces flat routes.
| Table | Status | Data |
|---|---|---|
| accounts | Exists | Empty (0 rows) |
| products | Exists | Empty |
| promotions | Exists | Empty |
| time_series | Exists | Empty |
| raw_files | Exists | Empty |
| baselines | Exists | Empty |
| format_registry | Exists | Empty |
| vocabulary_map | Exists | Empty |
| corrections | Exists | Empty |
| audit_log | Exists | Empty |
| dead_letter_queue | Exists | Empty |
| tenant_config | Exists | Empty |
| compute_results | RLS Error | app.current_tenant undefined |
| reports | RLS Error | app.current_tenant undefined |
| anomalies | RLS Error | app.current_tenant undefined |
| evaluation_results | Not Found | Table does not exist |
| executive_metrics | Not Found | Hint: "Perhaps you meant monthly_metrics" |
| users | Not Found | No users table (roles in JWT only) |
| mcp_consumers | Not Found | Hint: "Perhaps you meant api_consumers" |
app.current_tenant not setThe tables compute_results, reports, and anomalies have RLS policies that depend on current_setting('app.current_tenant'), but this PostgreSQL configuration parameter is never set by the application. Every query to these tables returns:
ERROR 42704: unrecognized configuration parameter "app.current_tenant"
Impact: The anomaly detection page, reports page, and evaluation results are completely non-functional from the database layer. No data can be read from or written to these tables.
Several table names in the code don't match what exists in Supabase:
evaluation_results (code) → should be compute_results (DB)executive_metrics (code) → should be monthly_metrics (DB)mcp_consumers (code) → should be api_consumers (DB)Every table in the database has 0 rows. There is no seed data for testing or demonstration purposes. The frontend will render empty states for every view. You cannot QA functional workflows (evaluate, plan, report) without data.
| Check | Result | Details |
|---|---|---|
| TypeScript compilation | Pass | tsc --noEmit clean — zero errors |
| ESLint | Pass | next lint — zero warnings or errors |
| Static export (next build) | Warn | Build exists in out/ but times out in sandbox (resource constrained) |
| Jest test suite | Fail | Jest and test dependencies NOT installed in node_modules |
| Backend tests | N/A | Cannot run (Python deps not installed in this environment) |
package.json lists jest, @testing-library/react, @testing-library/jest-dom, and jest-environment-jsdom in devDependencies, but they are completely absent from node_modules/. Running npm test fails immediately. Either npm install was run with --production or these were never installed.
The JWTAuthMiddleware in app.py (line 82) does if env != "production": return await call_next(request) — meaning in development/staging, any request is accepted without authentication. Combined with the fact that TPM_ENV is typically not set (defaults to "development"), the backend is effectively completely open.
A RoleGuard component exists in src/components/auth/RoleGuard.tsx but is only used by the admin page. The analyst page implements a manual role check. All other pages have no page-level role protection (relying solely on the DashboardShell layout check).
If the JWT has no role in app_metadata, the useRBAC hook defaults to 'kam' with only a console.warn. This could grant unintended KAM-level access to malformed or migrated accounts.
POST /api/chat returns: {"response":"TPM Engine is starting up. Full AI capabilities coming soon."}. The chat SSE streaming endpoint and Anthropic Claude integration exist in code but are not active — the primary chat endpoint returns a hardcoded response.
| Severity | # | Issues |
|---|---|---|
| CRITICAL | 3 |
1. Backend RBAC not enforced on 9/11 API modules 2. Cloudflare Worker API down (ECONNREFUSED) 3. RLS policies broken on compute_results, reports, anomalies tables ( app.current_tenant)
|
| HIGH | 3 |
4. /callback returns 404 (magic link auth broken in production) 5. Backend/frontend RBAC hierarchy mismatch (viewer vs executive, KAM vs analyst ordering) 6. JWT auth middleware is a complete no-op in non-production |
| MEDIUM | 4 |
7. Login hardcodes redirect to /chat instead of role-appropriate route 8. Table name mismatches between code and database 9. All database tables empty — no seed data 10. Jest/test dependencies not installed |
| LOW | 3 |
11. Register page is a stub placeholder 12. useRBAC defaults silently to 'kam' role 13. Chat endpoint returns stub instead of AI response |
| Area | Status |
|---|---|
| Supabase authentication (email/password) for all 4 roles | Solid |
| JWT role assignment via app_metadata | Solid |
| Frontend TypeScript — zero compilation errors | Solid |
| Frontend ESLint — zero warnings | Solid |
| Cloud Run backend health, startup, and routing | Solid |
| Frontend layout with role-based sidebar navigation (DashboardShell) | Solid |
| Client-side 403 enforcement in DashboardShell | Solid |
| Static export to Cloudflare Pages (all dashboard routes serve correctly) | Solid |
| CORS configuration on Cloud Run | Solid |
| Supabase client singleton pattern (prevents Web Locks contention) | Solid |
| Frontend type system (auth.ts canonical, rbac.ts re-exports) | Solid |
| Backend code structure (20 skills, engine/data separation) | Solid |
| # | Action | Severity | Effort |
|---|---|---|---|
| 1 | Fix app.current_tenant RLS — either set it via SET LOCAL before queries or rewrite policies to use auth.uid() | CRITICAL | ~2h |
| 2 | Wire require_role() into all API route handlers (chat, evaluate, plan, report, anomalies, executive, upload, corrections, consumers) | CRITICAL | ~3h |
| 3 | Fix or remove the Cloudflare Worker; update .env.local to point to Cloud Run for local dev | CRITICAL | ~30m |
| 4 | Align backend RBAC hierarchy with frontend: executive(1) < kam(2) < analyst(3) < admin(4) | HIGH | ~1h |
| 5 | Redeploy frontend to Cloudflare Pages (fixes /callback 404) | HIGH | ~15m |
| 6 | Make login redirect role-aware using ROLE_NAV_CONFIG | MEDIUM | ~30m |
| 7 | Create seed data script for promotions, accounts, products | MEDIUM | ~2h |
| 8 | Fix table name references (evaluation_results → compute_results, etc.) | MEDIUM | ~1h |
| 9 | Install test dependencies and verify test suite runs | MEDIUM | ~30m |
| 10 | Apply RoleGuard consistently across all protected pages | MEDIUM | ~1h |