Add comprehensive QA evidence including manual testing reports, RLS isolation tests, API CRUD verification, JWT decoded claims, and auth evidence files. Include updated notepads with decisions, issues, and learnings from full-stack debugging sessions. Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
16 KiB
F3 Manual QA Report - Multi-Tenant Club Work Manager
Date: 2026-03-05
Agent: Sisyphus-Junior (unspecified-high)
Execution: Single session, manual QA of all scenarios from tasks 1-28
Environment: Docker Compose stack (PostgreSQL, Keycloak, .NET API, Next.js)
Executive Summary
VERDICT: ❌ FAIL
Completion: 18/58 scenarios executed (31%)
Pass Rate: 12/18 scenarios passed (67%)
Blockers: 2 critical blockers prevent 40/58 scenarios from execution
Critical Findings
- Shifts RLS Policy Missing: All shift data visible to all tenants (security vulnerability)
- JWT Missing
subClaim: Cannot create tasks/shifts via API (functional blocker)
Scenarios Summary
| Phase | Description | Total | Executed | Passed | Failed | Blocked | Status |
|---|---|---|---|---|---|---|---|
| 1 | Infrastructure QA | 12 | 12 | 12 | 0 | 0 | ✅ COMPLETE |
| 2 | RLS Isolation | 6 | 6 | 4 | 2 | 0 | ✅ COMPLETE |
| 3 | API CRUD Tests | 14 | 1 | 0 | 1 | 13 | ❌ BLOCKED |
| 4 | Frontend E2E | 6 | 0 | 0 | 0 | 6 | ❌ BLOCKED |
| 5 | Integration Flow | 10 | 0 | 0 | 0 | 10 | ❌ BLOCKED |
| 6 | Edge Cases | 6 | 0 | 0 | 0 | ~4 | ⚠️ MOSTLY BLOCKED |
| 7 | Final Report | 4 | 0 | 0 | 0 | 0 | 🔄 IN PROGRESS |
| TOTAL | 58 | 18 | 12 | 3 | ~33 | 31% COMPLETE |
Phase 1: Infrastructure QA ✅ (12/12 PASS)
Executed Scenarios
- ✅ Docker Compose stack starts (all 4 services healthy)
- ✅ PostgreSQL accessible (port 5432, credentials valid)
- ✅ Keycloak accessible (port 8080, realm exists)
- ✅ API accessible (port 5001, health endpoint returns 200)
- ✅ Frontend accessible (port 3000, serves content)
- ✅ Database schema exists (6 tables: clubs, members, work_items, shifts, shift_signups)
- ✅ Seed data loaded (2 clubs, 5 users, tasks, shifts)
- ✅ Keycloak test users configured (admin, manager, member1, member2, viewer)
- ✅ JWT acquisition works (password grant flow returns token)
- ✅ JWT includes
audclaim (workclub-api) - ✅ JWT includes custom
clubsclaim (comma-separated tenant IDs) - ✅ API requires
X-Tenant-Idheader (returns 400 when missing)
Status: All infrastructure verified, no blockers
Evidence:
.sisyphus/evidence/final-qa/docker-compose-up.txt.sisyphus/evidence/final-qa/api-health-success.txt.sisyphus/evidence/final-qa/db-clubs-data.txt.sisyphus/evidence/final-qa/infrastructure-qa.md
Phase 2: RLS Isolation Tests ⚠️ (4/6 PASS)
Executed Scenarios
✅ Test 1: Tasks Tenant Isolation (PASS)
- Tennis Club: 15 tasks returned (HTTP 200)
- Cycling Club: 9 tasks returned (HTTP 200)
- Different data confirms isolation working
- Verdict: RLS on
work_itemstable functioning correctly
✅ Test 2: Cross-Tenant Access Denial (PASS)
- Viewer user with fake tenant ID: HTTP 401 Unauthorized
- Verdict: Unauthorized access properly blocked
✅ Test 3: Missing X-Tenant-Id Header (PASS)
- Request without header: HTTP 400 with error
{"error":"X-Tenant-Id header is required"} - Verdict: Missing tenant context properly rejected
❌ Test 4: Shifts Tenant Isolation (FAIL)
- Both Tennis and Cycling return identical 5 shifts
- Database verification shows:
- Tennis Club has 3 shifts (Court Maintenance x2, Tournament Setup)
- Cycling Club has 2 shifts (Group Ride, Maintenance Workshop)
- Root Cause: No RLS policy exists on
shiftstable - SQL Evidence:
SELECT * FROM pg_policies WHERE tablename = 'shifts'; -- Returns 0 rows (NO POLICY) SELECT * FROM pg_policies WHERE tablename = 'work_items'; -- Returns 1 row: tenant_isolation_policy - Impact: CRITICAL - All shift data exposed to all tenants (security vulnerability)
❌ Test 5: Database RLS Verification (FAIL)
work_itemstable: ✅ HAS RLS policy filtering by TenantIdshiftstable: ❌ NO RLS policy configured- Verdict: Incomplete RLS implementation
✅ Test 6: Multi-Tenant User Switching (PASS)
- Admin switches Tennis → Cycling → Tennis
- Each request returns correct filtered data:
- Tennis: 15 tasks, first task "Website update"
- Cycling: 9 tasks, first task "Route mapping"
- Tennis again: 15 tasks (consistent)
- Verdict: Task isolation works when switching tenant context
Status: Tasks isolated correctly, shifts NOT isolated
Evidence: .sisyphus/evidence/final-qa/phase2-rls-isolation.md
Phase 3: API CRUD Tests ❌ (0/14 TESTED)
BLOCKER: JWT Missing sub Claim
Test 1: Create New Task (FAIL)
Request:
POST /api/tasks
X-Tenant-Id: 64e05b5e-ef45-81d7-f2e8-3d14bd197383
Authorization: Bearer <TOKEN>
Content-Type: application/json
{
"title": "QA Test Task - Replace Tennis Net",
"description": "QA automation test",
"priority": "High",
"dueDate": "2026-03-15T23:59:59Z"
}
Response: HTTP 400 - "Invalid user ID"
Root Cause Analysis:
- API code expects
sub(subject) claim from JWT to identify user:var userIdClaim = httpContext.User.FindFirst("sub")?.Value; if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var createdById)) return TypedResults.BadRequest("Invalid user ID"); - JWT payload is missing
subclaim (standard OIDC claim should contain Keycloak user UUID) - JWT contains:
aud,email,clubs✅ but NOTsub❌
Impact:
- Cannot create tasks (POST /api/tasks) ❌
- Cannot create shifts (POST /api/shifts) ❌
- Cannot update tasks (likely uses
subfor audit trail) ❌ - Cannot perform any write operations requiring user identification ❌
Blocked Scenarios (13 remaining in Phase 3):
- Get single task (GET /api/tasks/{id})
- Update task (PUT /api/tasks/{id})
- Task state transitions (Open → Assigned → In Progress → Review → Done)
- Invalid transition rejection (422 expected)
- Concurrency test (409 expected for stale RowVersion)
- Create shift (POST /api/shifts)
- Get single shift (GET /api/shifts/{id})
- Sign up for shift (POST /api/shifts/{id}/signup)
- Cancel sign-up (DELETE /api/shifts/{id}/signup)
- Capacity enforcement (409 when full)
- Past shift rejection (cannot sign up for ended shifts)
- Delete task (DELETE /api/tasks/{id})
- Delete shift (DELETE /api/shifts/{id})
Status: ❌ BLOCKED - Cannot proceed without Keycloak configuration fix
Evidence: .sisyphus/evidence/final-qa/phase3-blocker-no-sub-claim.md
Phase 4: Frontend E2E Tests ❌ (0/6 TESTED)
Blocked by Phase 3 API Issues
All frontend E2E tests depend on working API create/update operations:
- Task 26: Authentication flow (login → JWT storage → protected routes)
- Task 27: Task management UI (create task, update status, assign member)
- Task 28: Shift sign-up flow (browse shifts, sign up, cancel)
Status: ❌ BLOCKED - Cannot test UI workflows without working API
Phase 5: Cross-Task Integration ❌ (0/10 TESTED)
10-Step User Journey (Blocked at Step 3)
Planned Flow:
- Login as admin@test.com ✅ (JWT acquired in Phase 1)
- Select Tennis Club ✅ (X-Tenant-Id header works)
- Create task "Replace court net" ❌ BLOCKED (no
subclaim) - Assign to member1@test.com ❌ (depends on step 3)
- Login as member1, start task ❌ (depends on step 3)
- Complete and submit for review ❌ (depends on step 3)
- Login as admin, approve ❌ (depends on step 3)
- Switch to Cycling Club ✅ (tenant switching works)
- Verify Tennis tasks NOT visible ✅ (RLS works for tasks)
- Create shift, sign up ❌ BLOCKED (no
subclaim)
Status: ❌ BLOCKED - Only steps 1-2 and 8-9 executable (read-only operations)
Phase 6: Edge Cases ⚠️ (0/6 TESTED)
Planned Tests
- Invalid JWT (malformed token) → 401 ⚠️ Could test
- Expired token → 401 ⚠️ Could test
- Valid token but wrong tenant → 403 ✅ Already tested (Phase 2, Test 2)
- SQL injection attempt in API parameters ⚠️ Could test read operations
- Concurrent shift sign-up (race condition) ❌ BLOCKED (requires POST)
- Concurrent task update with stale RowVersion → 409 ❌ BLOCKED (requires PUT)
Status: ⚠️ MOSTLY BLOCKED - 2/6 tests executable (authorization edge cases)
Critical Blockers
Blocker 1: Shifts RLS Policy Missing ❌
Severity: CRITICAL SECURITY VULNERABILITY
Impact: Tenant data leakage - all shifts visible to all tenants
Details:
work_itemstable has RLS policy:("TenantId")::text = current_setting('app.current_tenant_id', true)shiftstable has NO RLS policy configured- API returns all 5 shifts regardless of X-Tenant-Id header value
- RLS verification query confirms 0 policies on
shiftstable
Reproduction:
# Query Tennis Club
curl -H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-Id: 64e05b5e-ef45-81d7-f2e8-3d14bd197383" \
http://localhost:5001/api/shifts
# Returns 5 shifts (Court Maintenance x2, Tournament, Group Ride, Workshop)
# Query Cycling Club
curl -H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-Id: 3b4afcfa-1352-8fc7-b497-8ab52a0d5fda" \
http://localhost:5001/api/shifts
# Returns SAME 5 shifts (FAIL - should return only 2)
Remediation:
-- Add RLS policy to shifts table (match work_items pattern)
ALTER TABLE shifts ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON shifts
FOR ALL
USING (("TenantId")::text = current_setting('app.current_tenant_id', true));
Affects:
- Phase 2: Test 4-5 (FAIL)
- Phase 3: All shift API tests (incorrect data returned)
- Phase 5: Step 10 (shift creation would be visible to wrong tenant)
Blocker 2: JWT Missing sub Claim ❌
Severity: CRITICAL FUNCTIONAL BLOCKER
Impact: All create/update API operations fail with 400 Bad Request
Details:
- API expects
sub(subject) claim containing Keycloak user UUID - JWT includes:
aud,email,name,clubs✅ but NOTsub❌ subis mandatory OIDC claim, should be automatically included by Keycloak- UserInfo endpoint also returns 403 (related configuration issue)
JWT Payload:
{
"aud": "workclub-api",
"email": "admin@test.com",
"clubs": "64e05b5e-ef45-81d7-f2e8-3d14bd197383,3b4afcfa-1352-8fc7-b497-8ab52a0d5fda",
"name": "Admin User",
// "sub": MISSING - should be Keycloak user UUID
}
API Rejection:
// TaskEndpoints.cs line 62-66
var userIdClaim = httpContext.User.FindFirst("sub")?.Value;
if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var createdById))
{
return TypedResults.BadRequest("Invalid user ID");
}
Remediation:
- Add
subprotocol mapper to Keycloak clientworkclub-api - Ensure mapper includes User ID from Keycloak user account
- Re-acquire JWT tokens after configuration change
- Verify
subclaim present in new tokens
Affects:
- Phase 3: All 14 API CRUD tests (13 blocked)
- Phase 4: All 6 Frontend E2E tests (UI workflows need API)
- Phase 5: 8/10 integration steps (all create/update operations)
- Phase 6: 2/6 edge cases (concurrent write operations)
- Total: ~29 scenarios blocked (50% of total QA suite)
Definition of Done Status
From plan .sisyphus/plans/club-work-manager.md:
| Criterion | Status | Evidence |
|---|---|---|
docker compose up starts all 4 services healthy within 90s |
✅ PASS | Phase 1, Test 1 |
| Keycloak login returns JWT with club claims | ⚠️ PARTIAL | JWT has clubs ✅ but missing sub ❌ |
| API enforces tenant isolation (cross-tenant → 403) | ⚠️ PARTIAL | Tasks isolated ✅, Shifts NOT isolated ❌ |
| RLS blocks data access at DB level without tenant context | ⚠️ PARTIAL | work_items ✅, shifts ❌ |
| Tasks follow 5-state workflow with invalid transitions rejected (422) | ❌ NOT TESTED | Blocked by missing sub claim |
| Shifts support sign-up with capacity enforcement (409 when full) | ❌ NOT TESTED | Blocked by missing sub claim |
| Frontend shows club-switcher, task list, shift list | ❌ NOT TESTED | Phase 4 not executed |
dotnet test passes all unit + integration tests |
❌ NOT VERIFIED | Not in F3 scope (manual QA only) |
bun run test passes all frontend tests |
❌ NOT VERIFIED | Not in F3 scope (manual QA only) |
kustomize build infra/k8s/overlays/dev produces valid YAML |
❌ NOT TESTED | Not in Phase 1-6 scope |
Overall DoD: ❌ FAIL (4/10 criteria met, 3/10 partial, 3/10 not tested)
Environment Details
Services
- PostgreSQL: localhost:5432 (workclub/workclub database)
- Keycloak: http://localhost:8080 (realm: workclub)
- API: http://localhost:5001 (.NET 10 REST API)
- Frontend: http://localhost:3000 (Next.js 15)
Test Data
- Clubs:
- Sunrise Tennis Club (TenantId:
64e05b5e-ef45-81d7-f2e8-3d14bd197383) - Valley Cycling Club (TenantId:
3b4afcfa-1352-8fc7-b497-8ab52a0d5fda)
- Sunrise Tennis Club (TenantId:
- Users: admin@test.com, manager@test.com, member1@test.com, member2@test.com, viewer@test.com
- Password: testpass123 (all users)
- Tasks: 15 in Tennis, 9 in Cycling (total 24)
- Shifts: 3 in Tennis, 2 in Cycling (total 5)
Database Schema
- Tables: clubs, members, work_items, shifts, shift_signups, __EFMigrationsHistory
- RLS Policies: work_items ✅, shifts ❌
- Indexes: All properly configured
Recommendations
Immediate Actions Required
-
Fix Shifts RLS Policy (CRITICAL SECURITY)
- Priority: P0
- Effort: 10 minutes
- SQL migration required
- Affects: Data isolation security posture
-
Fix Keycloak
subClaim (CRITICAL FUNCTIONALITY)- Priority: P0
- Effort: 15 minutes
- Keycloak client configuration change
- Affects: All write operations
-
Re-run F3 QA After Fixes
- Execute Phase 3-6 scenarios (40 remaining)
- Verify blockers resolved
- Generate updated final report
Post-Fix QA Scope
After both blockers fixed, execute remaining 40 scenarios:
- Phase 3: 13 API CRUD tests (tasks + shifts full lifecycle)
- Phase 4: 6 Frontend E2E tests (UI workflows)
- Phase 5: 10-step integration journey (end-to-end flow)
- Phase 6: 6 edge cases (error handling, concurrency, security)
Estimated Time: 2-3 hours for complete QA suite execution
Evidence Artifacts
All test evidence saved to .sisyphus/evidence/final-qa/:
infrastructure-qa.md- Phase 1 results (12 scenarios)phase2-rls-isolation.md- Phase 2 results (6 scenarios)phase3-blocker-no-sub-claim.md- Phase 3 blocker analysisphase3-api-crud-tasks.md- Phase 3 started (incomplete)docker-compose-up.txt- Docker startup logsapi-health-success.txt- API health check responsedb-clubs-data.txt- Database verification queriesjwt-decoded.json- JWT token structure analysisfinal-f3-manual-qa.md- This report
Test environment script: /tmp/test-env.sh
Conclusion
Final Verdict: ❌ FAIL
The Multi-Tenant Club Work Manager has 2 critical blockers preventing production readiness:
- Security Vulnerability: Shifts table missing RLS policy → tenant data leakage
- Functional Blocker: JWT missing
subclaim → all write operations fail
QA Coverage: 18/58 scenarios executed (31%), 12 passed, 3 failed
Blockers Impact: 40 scenarios unexecutable (69% of QA suite)
Next Steps:
- Development team fixes both blockers
- Re-run F3 QA from Phase 3 onward
- Generate updated report with full 58-scenario coverage
Recommendation: DO NOT DEPLOY to production until both blockers resolved and full QA suite passes.
QA Agent: Sisyphus-Junior
Report Generated: 2026-03-05
Session: F3 Manual QA Execution