Update Keycloak probe/realm import behavior and authority config so auth services start reliably on the dev cluster, while keeping CD deployment steps aligned with the actual Kubernetes overlay behavior.
The signup/cancel endpoints were passing the Keycloak sub claim (external UUID)
directly as MemberId, but ShiftSignup.MemberId references the internal Member.Id.
Now ShiftService resolves ExternalUserId to the internal Member.Id before creating
the signup record. Integration tests updated to seed proper Member entities.
Stabilize test harness across full stack:
Backend integration tests:
- Fix Auth/Club/Migration/RLS/Member/Tenant/RLS Isolation/Shift/Task test suites
- Add AssemblyInfo.cs for test configuration
- Enhance CustomWebApplicationFactory + TestAuthHandler for stable test environment
- Expand RlsIsolationTests with comprehensive multi-tenant RLS verification
Frontend test harness:
- Align vitest.config.ts with backend API changes
- Add bunfig.toml for bun test environment stability
- Enhance api.test.ts with proper test setup integration
- Expand test/setup.ts with fixture initialization
All tests now passing: backend 12/12 unit + 63/63 integration, frontend 45/45
Resolve post-login routing and tenant context issues by proxying frontend API
calls, redirecting authenticated users away from /login, and hardening club
loading with retries/loading guards.
Align tenant identity end-to-end by returning tenantId in /api/clubs/me and
sending X-Tenant-Id from cookie-backed tenantId instead of local clubId,
restoring authorized tasks/shifts data access after club selection.
Create fresh NpgsqlConnection per tenant iteration instead of reusing
EF Core's managed connection. This prevents connection disposal issues
when iterating over multiple tenant IDs from the JWT clubs claim.
The fix ensures each iteration has its own connection lifecycle with
proper SET LOCAL app.current_tenant_id for RLS compliance.
- Add path exemption in TenantValidationMiddleware for /api/clubs/me
- Change authorization policy from RequireMember to RequireViewer
- Fix KEYCLOAK_CLIENT_ID in docker-compose.yml (workclub-app not workclub-api)
- Endpoint now works without X-Tenant-Id header as intended
- Other endpoints still protected by tenant validation
This fixes the chicken-and-egg problem where frontend needs to call
/api/clubs/me to discover available clubs before selecting a tenant.
- Add path exemption in TenantValidationMiddleware for /api/clubs/me
- Change authorization policy from RequireMember to RequireViewer
- Fix KEYCLOAK_CLIENT_ID in docker-compose.yml (workclub-app)
- Resolves frontend chicken-and-egg problem for club discovery
Verified:
- /api/clubs/me returns 200 OK without X-Tenant-Id header
- /api/tasks still requires X-Tenant-Id (400 Bad Request)
- Other endpoints unaffected
Reorganize SeedDataService to establish RLS policies before granting
app_admin role to prevent permission issues. Remove --no-restore flag
from Dockerfile.dev to ensure proper build.
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Set tenant context before querying DB in ClubRoleClaimsTransformation.TransformAsync
to avoid chicken-and-egg problem where tenant context is needed but not yet available.
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
PostgreSQL SET LOCAL only persists within a transaction scope. Added explicit transaction creation if none exists, ensuring tenant context is properly set before queries execute. Fixes tenant isolation for multi-tenant RLS filtering.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Allow SET LOCAL execution for all database commands by removing the transaction check.
EF Core creates implicit transactions for queries, so SET LOCAL works regardless.
This fixes the issue where read operations without explicit transactions were not getting
tenant context set properly, leading to incorrect RLS filtering and data visibility.
- Added Keycloak audience protocol mapper to workclub-app client
- Maps 'workclub-api' to aud claim in access tokens
- Disabled issuer validation in API for local dev
- External clients use localhost:8080, internal use keycloak:8080
- Prevents validation mismatch in Docker network environment
This resolves 401 Unauthorized errors on all authenticated endpoints.
Ref: .sisyphus/evidence/final-f3-manual-qa.md lines 418-444
Implements Tasks 23 & 24: Backend and Frontend Dockerfiles
Backend Dockerfiles:
- Dockerfile.dev: Development with dotnet watch hot reload
- Base: sdk:10.0, installs dotnet-ef tool
- Layer caching: csproj files copied before source
- Entry: dotnet watch run with --no-restore
- Dockerfile: Production multi-stage build
- Build stage: sdk:10.0, restore + build + publish
- Runtime stage: aspnet:10.0-alpine (~110MB)
- Health check: /health/live endpoint
- Non-root: USER app (built-in)
Frontend Dockerfiles:
- Dockerfile.dev: Development with Bun hot reload
- Base: node:22-alpine, installs Bun globally
- Layer caching: package.json + bun.lock before source
- Command: bun run dev
- Dockerfile: Production standalone 3-stage build
- Deps stage: Install with --frozen-lockfile
- Build stage: bun run build → standalone output
- Runner stage: node:22-alpine with non-root nextjs user
- Copies: .next/standalone, .next/static, public
- Health check: Node.js HTTP GET to port 3000
- Entry: node server.js (~240MB)
All Dockerfiles use layer caching optimization and security best practices.
Note: Docker build verification skipped (Docker daemon not running).
Implement Task 16: Club + Member API endpoints with MemberSyncService
Services:
- ClubService: GetMyClubsAsync (user's clubs), GetCurrentClubAsync (tenant club)
- MemberService: GetMembersAsync (list), GetMemberByIdAsync, GetCurrentMemberAsync
- MemberSyncService: Auto-creates Member records from JWT on first request
Middleware:
- MemberSyncMiddleware: Runs after auth, calls MemberSyncService
Endpoints:
- GET /api/clubs/me (list user's clubs)
- GET /api/clubs/current (current tenant's club)
- GET /api/members (list members, RLS filtered)
- GET /api/members/{id} (member detail)
- GET /api/members/me (current user's membership)
Tests: 14 integration tests (6 club + 8 member)
- Club filtering by user membership
- Multi-tenant isolation via RLS
- Member auto-sync on first request
- Cross-tenant access blocked
- Role-based authorization
Build: 0 errors, all tests compile
Pattern: TypedResults, RequireAuthorization policies, TDD approach
- Use consolidated Finbuckle.MultiTenant namespace instead of separate imports
- Switch TenantProvider to use untyped IMultiTenantContextAccessor (Finbuckle 9.x pattern)
- Register TenantDbConnectionInterceptor and SaveChangesTenantInterceptor as singletons
- Add interceptors to DbContext configuration for RLS tenant context support
- Update evidence files for Task 7 and Task 8 verification
- Create SeedDataService in Infrastructure/Seed with idempotent seeding
- Seed 2 clubs: Sunrise Tennis Club, Valley Cycling Club
- Seed 7 member records (5 unique Keycloak test users)
- Seed 8 work items covering all status states
- Seed 5 shifts with date variety (past, today, future)
- Seed shift signups for realistic partial capacity
- Register SeedDataService in Program.cs with development-only guard
- Use deterministic GUID generation from club names
- Ensure all tenant IDs match for RLS compliance
- Track in learnings.md and evidence files for Task 22 QA