POC · CPS Registry (Kotlin)
Valide : ADR-026 (intégration Form Designer ↔ Risk Matrix via CPS), ADR-007 (CPS mapping obligatoire), ADR-025 (validation symboles côté Risk Matrix). Statut : 12/12 tests passent, démo CLI fonctionnelle, prêt à être industrialisé dans
profile-schema-svc.
Ce POC démontre le cœur du Client Profile Schema registry : la logique métier qui garantit la référentielle entre Form Designer et Risk Matrix, refuse les opérations dangereuses (suppression de champ consommé, validation d’un symbole inexistant, rename direct), et produit un hash canonique reproductible pour l’audit.
Source code : /poc-cps-registry/ dans le repo VitaKYC.
1. Lancer le POC
Section intitulée « 1. Lancer le POC »cd poc-cps-registry./gradlew test # 12/12 tests JUnit 5./gradlew run # démo CLI : flow Form Designer → CPS → Risk Matrix2. Ce que le POC démontre
Section intitulée « 2. Ce que le POC démontre »2.1 Modèle domaine
Section intitulée « 2.1 Modèle domaine »enum class VariableType { STRING, INTEGER, DECIMAL, BOOLEAN, ENUM, ISO3166_ALPHA2, ISO4217, ... }enum class Sensitivity { PUBLIC, INTERNAL, PII, SPI }enum class VariableStatus { ACTIVE, DEPRECATED, RETIRED }enum class ConsumerKind { RISK_RULE, RISK_OVERRIDE, AML_RULE, REPORT_FIELD }
data class Variable( val path: String, // "client.profession" val type: VariableType, val enumCatalog: String? = null, val source: String, // "form:FORM_KYC_INDIVIDUAL@v2.7" val required: Boolean = false, val sensitivity: Sensitivity, val status: VariableStatus = VariableStatus.ACTIVE, val deprecatedAt: String? = null, val deprecatedReason: String? = null, val deprecationWindowDays: Int = 90, val usedBy: List<UsedByRef> = emptyList(), val modelCard: String? = null)2.2 Invariants enforcés
Section intitulée « 2.2 Invariants enforcés »| Invariant | Erreur typée | Quand |
|---|---|---|
| Path unique par tenant | DuplicateVariable | declare() sur un path existant |
| Pas de symbole fantôme | SymbolUnknown (+ suggestions Levenshtein) | validateSymbols() Risk Matrix |
| Pas de suppression tant qu’il y a des consommateurs | DeleteInUse | retire() d’une variable avec usedBy.isNotEmpty() |
| Rename direct interdit | RenameNotAllowed | rename() — pattern correct : declare new + deprecate old |
| Fenêtre de dépréciation ≥ 90 jours | InvalidDeprecationWindow | deprecate(windowDays = X < 90) |
2.3 Suggestions intelligentes (Levenshtein)
Section intitulée « 2.3 Suggestions intelligentes (Levenshtein) »Quand un compliance officer tape un symbole mal orthographié, le CPS propose les variables existantes proches :
// Compliance officer tape "client.profesion" (manque un s)cps.validateSymbols(listOf("client.profesion"))→ CpsError.SymbolUnknown: 'client.profesion' not found. Suggestions: [client.profession, client.country_pros_bracket]2.4 Hash reproductible
Section intitulée « 2.4 Hash reproductible »val cps1 = CpsRegistry("TN-BANQUEX")cps1.declare(profession)cps1.declare(country)val hash1 = cps1.snapshot().hash
val cps2 = CpsRegistry("TN-BANQUEX")cps2.declare(country) // ordre inversecps2.declare(profession)val hash2 = cps2.snapshot().hash
assert(hash1 == hash2) // hash stable indépendamment de l'ordre d'insertionImportant pour l’audit : permet de vérifier que le contrat n’a pas été altéré.
3. Démo CLI — flow cross-module
Section intitulée « 3. Démo CLI — flow cross-module »====================================================================== CPS Registry — démo flow Form Designer → CPS → Risk Matrix======================================================================
▶ 1. Form Designer publie v2.7 : déclare client.profession, client.country, client.pep▶ 2. Module screening activé : déclare screening.ofacMatch, screening.pepConfirmed▶ 3. Risk Matrix publie policy TN-BANQUEX@2.1 → référence les variables▶ 4. Validation policy — tous symboles OK ✓▶ 5. Tentative de policy référant un symbole inexistant : client.homeowner_status ✓ Rejeté : CPS_SYMBOL_UNKNOWN▶ 6. Form Designer tente de supprimer client.pep (utilisé par 1 règle) ✓ Rejeté : utilisé par 1 consommateur(s)▶ 7. Form Designer publie v2.8 : ajoute client.income_bracket▶ 8. Deprecate client.homeowner_status_legacy → fenêtre 90j ✓
Snapshot final : 6 variables actives, 1 dépréciéehash = sha256:2e96741276aaf...Exemple de variable avec consommateur enregistré :
{ "path": "client.pep", "type": "ENUM", "enum_catalog": "PEP_STATUS", "source": "form:FORM_KYC_INDIVIDUAL@v2.7", "sensitivity": "PII", "status": "ACTIVE", "used_by": [ { "kind": "RISK_RULE", "ref": "policy:TN-BANQUEX@2.1#dimension=client#rule=pep" } ]}4. Tests (12 passants)
Section intitulée « 4. Tests (12 passants) »CPS Registry — invariants Form Designer ↔ Risk Matrix ✓ Déclarer une variable nouvelle → OK et retrievable ✓ Déclarer une variable déjà existante → DuplicateVariable ✓ Valider un symbole connu → OK sans exception ✓ Valider un symbole inconnu → SymbolUnknown avec suggestions ✓ Retirer une variable utilisée par Risk Matrix → DeleteInUse ✓ Retirer une variable libre (aucun consommateur) → OK, passe RETIRED ✓ Déprécier une variable → statut DEPRECATED + timestamp ✓ Fenêtre de dépréciation < 90j → InvalidDeprecationWindow ✓ Rename strict interdit → RenameNotAllowed ✓ Register + unregister consommateur → usedBy évolue correctement ✓ findDeprecatedAmong retourne les variables dépréciées ✓ Snapshot produit un hash stable et reproductible
BUILD SUCCESSFUL — 12 tests passed5. Architecture du code
Section intitulée « 5. Architecture du code »poc-cps-registry/├── build.gradle.kts — Kotlin 2.0 + kotlinx.serialization + JUnit 5├── settings.gradle.kts├── README.md└── src/ ├── main/kotlin/io/vitakyc/cps/ │ ├── Model.kt — data classes (Variable, ClientProfileSchema, enums, erreurs typées) │ ├── Registry.kt — CpsRegistry in-memory + invariants + Levenshtein suggestions │ └── Main.kt — CLI démo cross-module └── test/kotlin/io/vitakyc/cps/ └── RegistryTest.kt — 12 tests JUnit 5 + AssertJ5.1 Séparation des responsabilités
Section intitulée « 5.1 Séparation des responsabilités »- Model.kt : data classes immutables, enums, erreurs typées (
sealed class CpsError). Pas de logique métier. - Registry.kt : logique du registry, invariants, lifecycle. Pas de persistance (c’est un POC).
- Main.kt : démo CLI, pas utilisée en prod.
5.2 Choix techniques
Section intitulée « 5.2 Choix techniques »| Choix | Justification |
|---|---|
| Kotlin 2.0 + JVM 17 | Cohérent avec les autres POCs + stack VitaKYC |
In-memory LinkedHashMap | POC autonome. Industrialisation dans profile-schema-svc avec PostgreSQL JSONB append-only. |
sealed class CpsError | Erreurs typées Kotlin-idiomatic, exhaustives dans le when |
kotlinx.serialization | JSON natif Kotlin, utilisé pour le dump de snapshots |
| Levenshtein custom | Suggestions minimales (< 20 LOC). Suffit pour la UX autocomplete. |
6. Ce que le POC ne fait PAS (scope profile-schema-svc)
Section intitulée « 6. Ce que le POC ne fait PAS (scope profile-schema-svc) »Volontairement hors-scope :
- Persistance PostgreSQL JSONB append-only
- Kafka consumer (
form.published,screening.enabled, etc.) + producer (cps.updated) - API REST / gRPC exposée
- Cache Caffeine côté consommateurs
- mTLS + RBAC granulaire
- Audit WORM 10 ans avec hash chain
- UI bindings (les workflows 3 et 10 des mockups montrent l’UI)
- Validation JSONSchema 2020-12 complète (utilisée en interne)
Tout ça est documenté sur la page engineering CPS.
7. Intégration avec les autres POCs
Section intitulée « 7. Intégration avec les autres POCs »| POC | Relation |
|---|---|
| poc-risk-engine | Consommateur principal du CPS. La prochaine itération du POC Risk Engine intégrera CpsRegistry.validateSymbols() à la construction de policy pour refuser les règles faisant référence à des symboles inconnus. |
| poc-fatca-generator | Consommera le CPS pour client.tinUs, client.residentUsStatus, etc. |
| poc-goaml-generator | Consommera le CPS pour les variables de contexte STR. |
| poc-rne-connector | Source : déclarera entity.ubo.* au CPS quand le connecteur RNE est activé pour un tenant. |
8. Prochaines étapes
Section intitulée « 8. Prochaines étapes »- Sprint S03 — monter
profile-schema-svcautour de ce POC (Spring Boot + PostgreSQL + Kafka) - S04 — wire le Form Designer : émission
form.publishedevents + UI badges “Used by X rules” - S05 — wire le Risk Matrix editor : autocomplete depuis CPS + validation serveur stricte
- S06 — audit WORM + export JSON signé pour audit BCT
- S08 — migration des tenants pilotes : discovery + validation manuelle + go-live strict
9. Références
Section intitulée « 9. Références »- Source code :
/poc-cps-registry/dans le repo VitaKYC - ADR-026 — Intégration Form Designer ↔ Risk Matrix via CPS
- ADR-007 (amendement) — Form Designer + CPS mapping obligatoire
- ADR-025 (amendement) — Risk Matrix validates via CPS
- Page engineering CPS
- Autres POCs : Risk Engine, FATCA, RNE, goAML, Tx Normalizer, Temenos