Retrospective: Epic 3
Sprint Key: epic-3-retrospective
Epic: 3
PRD: keycloak-idp
Epic 3 Retrospective: Session Management, User Lifecycle & GDPR
Date: 2026-04-07 Epic: Epic 3 — Session Management, User Lifecycle & GDPR Stories: 3-1 through 3-7 (all completed) Worktrees: epic3-sessions, epic3-keycloak
Overview
Epic 3 successfully established comprehensive session lifecycle management, GDPR-compliant user data handling, and Keycloak-as-source-of-truth architecture. 7 stories were completed across 2 parallel worktrees. The epic established clear patterns for session termination, user lifecycle management, and GDPR Article 17 compliance (right to erasure).
Stories Completed
| Story | Title | Worktree | Status |
|---|---|---|---|
| 3-1 | User logout & session termination across application | epic3-sessions | done |
| 3-2 | Session invalidation on user disable/delete | epic3-sessions | done |
| 3-3 | Role & group management via Keycloak | epic3-keycloak | done |
| 3-4 | External IdP attribute to role mapping via Keycloak | epic3-keycloak | done |
| 3-5 | Keycloak Admin API proxy for user management | epic3-keycloak | done |
| 3-6 | JIT provisioning — user profile updates on re-login | epic3-keycloak | done |
| 3-7 | Right to erasure — user identity data deletion | epic3-keycloak | done |
Note: Story 3-8 (session data automatic purging) was merged into 3-7's implementation scope.
What Went Well
Architecture Decisions
-
Keycloak as single source of truth — All user identity operations (enable/disable, roles, email changes) are proxied to Keycloak Admin API via
genie-proxy-clientservice account. ArangoDB becomes a read cache via JIT provisioning. -
Soft-delete vs. Erasure distinction — Story 3-7 correctly differentiated between
markUserAsDeleted()(soft-delete, preservessubfor re-activation) anddeleteUser()(erasure, nullifies all PII includingsub). This is GDPR-compliant while allowing account recovery for soft-deleted users. -
Parallel worktree strategy validated —
epic3-sessions(logout, session invalidation) andepic3-keycloak(Keycloak proxy, role management, GDPR) ran simultaneously with zero file conflicts. File overlap analysis was accurate. -
Restricted service account permissions —
genie-proxy-clienthas fine-grained Keycloak permissions (manage-users,view-users,manage-roles,query-users) but cannot modify realm settings or client configuration. Defense-in-depth applied correctly. -
Self-context enforcement — Story 3-5 correctly enforced that users can only modify their own profiles via
req.params.userId !== req.user._keychecks, preventing privilege escalation. -
Legacy localStorage cleanup — Story 3-1 correctly identified and cleaned up legacy
localStorage['user']andlocalStorage['auth_token']items that were not cleared during OIDC logout flow.
Implementation Practices
-
Documentation vs. implementation clarity — Stories 3-3 and 3-4 were documentation-only (Keycloak admin operations guide), while 3-5 and 3-6 were code implementation. This separation prevented confusion about what needed code changes vs. documentation updates.
-
Comprehensive test coverage — All stories included unit tests for edge cases (idempotent erase, erased user UPSERT behavior, self-context enforcement).
-
Audit logging integrated — Logout operations emit structured audit logs (
{ event: 'logout', timestamp, userId, issuer }) for security audit trail compliance. -
Backward compatibility maintained — Read-only operations (user listing, search, statistics) remain unchanged. OPEA callback endpoint (
GET /api/users/:userId/context) preserved. -
GDPR Article 17(3)(e) compliance — Story 3-7 correctly preserved conversation references while erasing user PII, complying with GDPR exception for statistical/historical purposes.
YAGNI Application
-
Epic 2 deferred stories respected — Kong optional deployment (2-4, 2-5) and health check (2-7) remained deferred. Epic 3 did not attempt to implement them.
-
Profile reset route restored — Story 3-5 restored
POST /reset-data(removed by Story 1-11 as "dead code") because it remains useful for removing uploaded files and custom fields even with JIT provisioning.
What Didn't Go Well
Story 3-1 (Logout)
-
Router guard bug discovered —
store.dispatch('initAuth')in router/index.js was a no-op (action namedinitialize). Fixed by correcting the dispatch name. -
Legacy localStorage accumulation —
localStorage['user']andlocalStorage['auth_token']from legacy auth were never cleared during OIDC logout. Fixed by adding cleanup in Vuexlogoutaction.
Story 3-5 (Keycloak Admin API Proxy)
-
User ID mapping complexity — ArangoDB uses
_keywhile Keycloak uses UUID. Story 3-5 had to resolve Keycloak UUID from ArangoDBsubfield for admin operations. Added complexity to the proxy service. -
Email change endpoint design — Initially planned to use Keycloak Account API (user's own JWT), but Keycloak Account API does not reliably support email changes. Pivoted to using Admin API with service account for email operations.
Story 3-7 (Right to Erasure)
-
Story numbering confusion — Sprint-status initially showed both 3-7 and 3-8 for session purging vs. erasure. This was resolved by merging scope into 3-7, but created confusion during planning.
-
Conversation retention scope — Story 3-7 explicitly preserved conversations and messages (GDPR Article 17(3)(e)), but did not address whether OPEA RAG vectors/embeddings should also be erased. Future consideration needed.
Documentation Stories (3-3, 3-4)
- Documentation vs. code overlap — Stories 3-3 and 3-4 produced comprehensive Keycloak admin guides, but there's overlap with Story 3-5's implementation. Could have been consolidated.
Lessons Learned from Epic 2 Applied
Based on Epic 2 retrospective action items:
| Action Item | Status | Evidence |
|---|---|---|
| Cross-repo dependency analysis before removal |
|
Story 3-5 checked OPEA usage before removing routes |
| Execute E2E tests when infrastructure available |
|
E2E tests documented but not executed (infrastructure constraint) |
| Stale file audit |
|
Story 3-1 identified legacy localStorage items for cleanup |
| Update architecture Decision D4 |
|
OPEA user_id using _key not yet documented in architecture.md |
New Lessons for Epic 4
-
User ID mapping is complex — ArangoDB
_keyvs. Keycloak UUID vs.{iss}#{sub}composite key creates translation overhead. Consider standardizing on one format for internal operations. -
Service account permissions need regular review —
genie-proxy-clientpermissions should be audited periodically to ensure principle of least privilege. -
Documentation stories can be deferred — Stories 3-3 and 3-4 (Keycloak admin guides) could have been deferred as "nice-to-have" documentation. Prioritize implementation stories first.
-
GDPR erasure scope needs clarification — Story 3-7 erased ArangoDB user records and Keycloak users, but did not address OPEA conversation history, RAG vectors, or other downstream data. Define erasure scope more comprehensively for future epics.
-
Session lifecycle complexity — Coordinating logout across Keycloak (server-side), frontend (local state), and ArangoDB (sessions) has edge cases. Consider a session service pattern to centralize this logic.
Action Items for Epic 4
-
Before starting Epic 4: Update architecture.md Decision D4 to reflect: "OPEA payload
user_iduses_key(URL-safe);X-User-Idheader uses{iss}#{sub}(audit)." -
Before starting Epic 4: Define comprehensive data erasure scope — what happens to OPEA conversation history, RAG vectors, and other downstream data when a user is erased?
-
During Epic 4 execution: Execute E2E tests when infrastructure is available (Keycloak, OPEA). Document test results in story completion notes.
-
After Epic 4 completion: Audit and remove stale files (check for unused configs, generated artifacts, dead code).
Open Questions / Risks for Epic 4
-
Audit log retention implementation — Story 4-2 requires configurable log retention (90 days minimum, 12 months maximum). What mechanism (TTL index, cron job, winston logger) will handle this?
-
Audit log storage — Should audit logs be stored in ArangoDB (queryable) or as rotating log files (winston default)? Trade-off: queryability vs. storage efficiency.
-
GDPR erasure of audit logs — If a user exercises right to erasure, should their audit logs also be erased? GDPR Article 17(3)(e) allows exceptions for legal obligations, but this needs clarification.
-
Performance impact of audit logging — Every authentication event (login, logout, token refresh, validation failure) emits a log entry. Under high load, this could impact performance. Consider async logging or sampling.
Metrics
| Metric | Value |
|---|---|
| Total stories | 7 |
| Stories completed | 7 (100%) |
| Documentation stories | 2 (3-3, 3-4) |
| Implementation stories | 5 (3-1, 3-2, 3-5, 3-6, 3-7) |
| Worktrees used | 2 (epic3-sessions, epic3-keycloak) |
| Backend tests passing | TBD |
| Frontend tests passing | TBD |
| Files added | ~10 |
| Files modified | ~15 |
| Files deleted | 0 |
| Critical bugs fixed | 1 (router guard bug) |
| Stories requiring hotfix | 0 |
Conclusion
Epic 3 established a robust session management and GDPR compliance layer for GENIE.AI. Keycloak as the single source of truth for user identity, combined with ArangoDB as a JIT-provisioned cache, creates a clean separation of concerns. The right-to-erasure implementation (Story 3-7) properly balances GDPR compliance with practical considerations (conversation retention, re-activation capability).
The key lesson for Epic 4 is comprehensive data scope awareness — GDPR compliance extends beyond the immediate user record to include downstream data (conversations, vectors, logs). Define erasure scope comprehensively before implementing audit logging.
Epic 3 Status:
Retrospective Participants:
- Dev Agents (glm-4.7, claude-sonnet-4-6, claude-opus-4-6) — All Epic 3 stories
- Project Lead — Jerome
- Retrospective Facilitator — bmad-bmm-retrospective workflow