Aller au contenu

POC Case Management — state machine, router, 4-eyes, SLA, audit chaîné

POC : poc-case-mgmt/ (~700 lignes Kotlin pur, 0 dépendance externe runtime). Référence pour case-mgmt-svc MVP.

Status : 18/18 tests passants. Démo CLI 10 étapes.

Ce POC démontre les invariants critiques du module Case Management en code exécutable :

  1. State machine déterministe — 10 états, transitions strictement énumérées, illegal-transition rejetée
  2. Router skill-based scoré — algorithme déterministe + fairness round-robin sur égalité
  3. 4-eyes engine — détection auto des cas sensibles + validation contrainte signataire
  4. SLA engine — calcul rungs croisés + breach detection avec gestion paused-time
  5. Audit trail append-only — chaînage hash SHA-256 + verify-chain end-to-end

poc-case-mgmt/
├── build.gradle.kts // Kotlin 2.0, kotlinx.serialization, JUnit 5
├── src/main/kotlin/io/vitakyc/cases/
│ ├── Model.kt // Case, AgentProfile, FourEyesPolicy, errors typés
│ ├── StateMachine.kt // transitions énumérées, assertTransition
│ ├── Router.kt // scoring + filters + tie-break round-robin
│ ├── FourEyes.kt // requiresSecondSignature + assertCanConfirm
│ ├── SlaEngine.kt // rungsCrossed, isBreached, deadline calc
│ ├── AuditTrail.kt // append-only + chainage SHA-256 + verify
│ ├── CaseService.kt // orchestration end-to-end
│ └── Main.kt // démo CLI 10 étapes
└── src/test/kotlin/io/vitakyc/cases/
└── CaseTest.kt // 18 tests

TestVérifie
state machine — happy path NEW to CLOSEDparcours nominal complet avec 4-eyes
state machine — illegal transition rejectedclaim sans assignment → IllegalTransition
state machine — pause and resumeIN_REVIEW ↔ PAUSED
state machine — closed is terminal, no further transitionstout depuis CLOSED jette
TestVérifie
router — selects best skill match within tenantscoring privilégie skill match exact
router — refuses cross-tenant agentfilter tenantId obligatoire
router — fairness round-robin on equal scorestie-break par lastAssignedAt le plus ancien
router — no eligible agent throwsNoEligibleAgent si aucun candidat
router — respects max loadfilter currentLoad < maxLoad
TestVérifie
4-eyes — required for PEPconfirmer ≠ proposer enforcé
4-eyes — confirmer must be L2 or MLROseniority constraint
4-eyes — required for high transaction amountseuil tenant (100 000 par défaut)
4-eyes — required on rejectiontoute REJECTED requiert 2 sig
4-eyes — not required for low-risk approved single signaturepath single-sig OK
TestVérifie
audit — chain hashes verifiableverifyChain retourne null après workflow nominal
audit — first event has zero previous hash, others chain forwardchainage explicite
TestVérifie
SLA — STANDARD case 24h deadline, breached at 25hdétection breach + rungs NOTIFY_AGENT, NOTIFY_MANAGER
SLA — paused time excluded from breach calculation25h - 5h paused = 20h, pas breach
reassignment — limited to 2 times3e tentative jette ReassignmentLimitReached
escalation — bumps min seniority to L2 on first escalationrouter suivant exigera L2+

=== VitaKYC POC Case Management — démo ===
[1] Ingest case STANDARD retail PEP
→ case_1 state=NEW
[2] Router assigne au top-1
→ assigné à agent_amine state=ASSIGNED
[3] Agent claim → IN_REVIEW
→ state=IN_REVIEW
[4] Agent propose APPROVED
→ state=DECISION_PENDING, proposedBy=agent_amine
[5] 4-eyes requis (PEP) → second agent confirme
→ finalDecision=APPROVED, confirmedBy=agent_leila
[6] Close
→ state=CLOSED
[7] Audit trail du case 1 — chaîne de hashes vérifiable
CREATED actor=ingest hash=sha256:227b7ed508a53…
ASSIGNED actor=router hash=sha256:7df212543f1da…
CLAIMED actor=agent_amine hash=sha256:4c0cdafaddf8c…
PROPOSED_DECISION actor=agent_amine hash=sha256:d453366783093…
CONFIRMED_DECISION actor=agent_leila hash=sha256:535f112a8ecde…
DECIDED actor=engine hash=sha256:7cb66fb662967…
CLOSED actor=engine hash=sha256:0f8685fd9c030…
chain integrity: ✅ OK
[8] Tentative 4-eyes par le même agent → REJET
✅ rejeté : 4-eyes violation: same agent cannot propose and confirm
[9] Cross-tenant isolation : case TN-BANQUEX ne peut être pris par agent FR-BANQUEY
→ case TN assigné à agent_amine (jamais agent_other_tenant)
✅ isolation tenant respectée
[10] SLA breach — case STANDARD non décidé après 24 h
rungs crossed à 25h : [NOTIFY_AGENT, NOTIFY_MANAGER]
isBreached : true

  • Signatures Ed25519 réelles — le POC chaîne les hashes ; la prod ajoute la signature par acteur via Vault HSM.
  • Workflow Temporal CaseSlaWorkflow — le POC évalue SLA en synchronie ; la prod utilise Temporal pour exactly-once des escalades.
  • PostgreSQL append-only avec RLS — le POC stocke en mémoire ; la prod persiste avec contraintes Postgres et policies RLS multi-tenant.
  • Bulk operations MLRO — pas implémentées (transactions multi-cases avec audit individuel).
  • Search full-text — pas implémentée (en prod via OpenSearch ICU).
  • Notifications email + Kafka — pas implémentées (en prod émis via outbox pattern).
  • OIDC + MFA agent — pas implémentés (auth simulée par String).

Fenêtre de terminal
cd poc-case-mgmt
./gradlew test # 18/18 tests verts
./gradlew run # démo CLI 10 étapes

Dépendances : Kotlin 2.0.20, JVM 17, kotlinx-serialization-json 1.7.3, JUnit 5.11, AssertJ 3.26. Aucune dépendance runtime externe.