PD-236 — Plan d'implémentation
1. Découpage en composants
| Composant | Responsabilité | Dépendances |
| TokenSearchController | Exposition API (indexation, recherche) | TokenSearchService, RequestValidator, ProtocolMapper |
| RequestValidator | Validation des requêtes (T_kw format, métadonnées) | TokenFormatValidatorRegistry |
| TokenFormatValidatorRegistry | Registre de validateurs de format T_kw par algorithm_id | TokenFormatValidator[] |
| TokenFormatValidator | Interface de validation du format T_kw pour un algorithm_id donné | — |
| TokenSearchService | Orchestration métier (indexation, recherche, vérification M_I) | TokenIndexRepository, MetadataRepository |
| TokenIndexRepository | Persistance des associations (T_kw, resource_id) avec égalité stricte | PostgreSQL |
| MetadataRepository | Persistance et lecture de M_I par index_id | PostgreSQL |
| AuditEventEmitter | Émission d'événements d'audit (ERR-BE-05) | EventBus/Logger |
| ProtocolMapper | Abstraction du mapping ERR-BE-xx vers le protocole de transport | — |
2. Flux techniques
2.1 Indexation
Client → TokenSearchController
→ RequestValidator.validateIndexationRequest(body)
→ TokenFormatValidatorRegistry.getValidator(algorithm_id).validate(T_kw)
→ vérifier présence métadonnées {algorithm_id, canonicalization_id, k_search_scope}
→ rejeter si champ "keyword" en clair détecté → AuditEventEmitter.emit()
→ TokenSearchService.index(index_id, entries, metadata)
→ MetadataRepository.getOrInitialize(index_id, M_req)
→ si M_I existe : comparer M_req == M_I (égalité champ-à-champ)
→ si M_I n'existe pas : persister M_I = M_req (avec garantie d'atomicité)
→ si mismatch → ERR-BE-04
→ pour chaque (T_kw, resource_id) : TokenIndexRepository.persist(index_id, T_kw, resource_id)
→ Succès | Rejet avec ERR-BE-xx
2.2 Recherche
Client → TokenSearchController
→ RequestValidator.validateSearchRequest(body)
→ TokenFormatValidatorRegistry.getValidator(algorithm_id).validate(T_kw)
→ vérifier présence métadonnées
→ TokenSearchService.search(index_id, T_kw, metadata)
→ MetadataRepository.get(index_id)
→ si M_I n'existe pas : retourner ensemble vide (index non initialisé)
→ comparer M_req == M_I : si mismatch → ERR-BE-04
→ TokenIndexRepository.findByToken(index_id, T_kw) [égalité stricte]
→ Succès { resource_ids: [...] } | Rejet avec ERR-BE-xx
Note : Le comportement « index non initialisé » (M_I inexistant lors d'une recherche) n'est pas explicitement défini par la spec. Le choix retenu est de retourner un ensemble vide de resource_id, ce qui est sémantiquement cohérent (aucune association n'existe). Ce point est consigné comme hypothèse technique (HT-06).
3. Mapping invariants → mécanismes
| Invariant ID | Exigence | Mécanisme | Composant | Observable | Risque |
| INV-01 | Pas de keyword en clair reçu ni persisté | Schéma API sans champ "keyword" + détection champ interdit + aucun stockage clair | RequestValidator, TokenIndexRepository | Inspection payload API + dump DB | Champ non prévu dans body JSON |
| INV-02 | Recherche par égalité stricte uniquement | Requête SQL WHERE token = $1 (opérateur =, pas de LIKE/fuzzy/full-text) + configuration stockage exact match | TokenIndexRepository | Query plan / logs / tests | Configuration DB altérée |
| INV-03 | Métadonnées homogènes par index | M_I immuable après création, comparaison stricte à chaque requête | MetadataRepository, TokenSearchService | Contrainte DB unique (index_id) + assertions | Race condition à la création |
| INV-04 | Mismatch → rejet explicite | Comparaison champ-à-champ M_req vs M_I, retour ERR-BE-04 si différence | TokenSearchService | Code erreur dans réponse | Comparaison partielle oubliée |
| INV-05 | Pas d'inférence sémantique | Aucun traitement NLP/ML sur T_kw, traitement opaque | Architecture (absence de code) | Revue de code | Non testable en boîte noire |
4. Mapping critères d'acceptation → mécanismes
| Critère ID | Mécanisme(s) | Composant | Observable | Risque |
| CA-01 | Validation format T_kw via validateur spécifique à l'algorithm_id | TokenFormatValidatorRegistry | Rejet avec ERR-BE-02 si invalide | Validateur manquant pour un algorithm_id |
| CA-02 | Query exacte SELECT resource_id FROM entries WHERE index_id = $1 AND token = $2 | TokenIndexRepository | Résultat = ensemble attendu | Index DB manquant ou mal configuré |
| CA-03 | Schéma DB sans colonne "keyword", schéma API sans champ "keyword" | Schema migration, Contrat API | Inspection schema | Migration non appliquée |
| CA-04 | Comparaison M_req.algorithm_id == M_I.algorithm_id && M_req.canonicalization_id == M_I.canonicalization_id && M_req.k_search_scope == M_I.k_search_scope | TokenSearchService | ERR-BE-04 retourné | Oubli d'un champ |
| CA-05 | Opérations INSERT atomiques, pas d'UPDATE des entrées existantes | TokenIndexRepository | Requêtes avant/après identiques pour tokens existants | Transaction mal configurée |
5. Mapping tests (TC-*) → mécanismes + observables
| Test ID | Référence spec | Mécanisme(s) | Point(s) d'observation | Niveau de test |
| TC-NOM-01 | §5.0, §5.1, CA-01, INV-03 | Validation format + persistance + vérification M_I | Réponse succès, DB contient N entrées, M_I cohérent | Integration |
| TC-NOM-02 | §5.2, CA-02, INV-02 | Query exacte TokenIndexRepository | Réponse succès, resource_ids = expected set | Integration |
| TC-NOM-03 | CA-03, INV-01 | Absence champ clair dans schema | Dump DB, payload API | Integration + Inspection |
| TC-NOM-04 | §5.0, §5.2, INV-03 | Comparaison M_req vs M_I | Q1 → succès, Q2 → rejet ERR-BE-04 | Integration |
| TC-ERR-01 | ERR-BE-01 | RequestValidator.validatePresence(T_kw) | Réponse rejet, code ERR-BE-01 | Unit + Integration |
| TC-ERR-02 | ERR-BE-02 | TokenFormatValidatorRegistry.validate() | Réponse rejet, code ERR-BE-02 | Unit + Integration |
| TC-ERR-03 | ERR-BE-03 | RequestValidator.validateMetadata() | Réponse rejet, code ERR-BE-03 | Unit + Integration |
| TC-ERR-04 | ERR-BE-04, INV-04 | TokenSearchService.checkMetadataMatch() | Réponse rejet, code ERR-BE-04, raison mismatch | Integration |
| TC-ERR-05 | ERR-BE-05, INV-01 | RequestValidator.detectClearKeyword() + AuditEventEmitter | Réponse rejet, event audit émis | Integration + Audit log |
| TC-INV-01 | INV-01 | Schema validation + inspection | Aucun clair en DB/API | Integration + Inspection |
| TC-INV-02 | INV-02 | Query exacte uniquement | Résultat T1 = expected, T2 = vide | Integration |
| TC-INV-03 | INV-03 | M_I immuable | Rejet si M_L != M_I | Integration |
| TC-INV-04 | INV-04 | checkMetadataMatch() | Rejet explicite | Integration |
| TC-INV-05 | INV-05 | — | NON TESTABLE | — |
| TC-NR-01 | CA-05 | INSERT only, pas d'UPDATE | Résultats S0 = S1 pour tokens existants | Integration |
| TC-NR-02 | CA-05 | Isolation des tokens | Résultats inchangés après ajout nouveau token | Integration |
| TC-NEG-01 | ERR-BE-03 | validateMetadata() | Rejet | Integration |
| TC-NEG-02 | ERR-BE-04 | checkMetadataMatch(k_search_scope) | Rejet mismatch scope | Integration |
| TC-NEG-03 | ERR-BE-05 | detectClearKeyword() | Rejet + audit | Integration |
6. Gestion des erreurs
| Code | Situation | Réponse | Observable |
| ERR-BE-01 | T_kw absent | Rejet explicite avec code ERR-BE-01 et raison contractuelle | Log applicatif |
| ERR-BE-02 | T_kw format invalide pour l'algorithm_id déclaré | Rejet explicite avec code ERR-BE-02 et raison contractuelle | Log applicatif |
| ERR-BE-03 | Métadonnées absentes/incomplètes | Rejet explicite avec code ERR-BE-03 et raison contractuelle | Log applicatif |
| ERR-BE-04 | Mismatch métadonnées (M_req != M_I) | Rejet explicite avec code ERR-BE-04 et raison contractuelle | Log applicatif |
| ERR-BE-05 | Keyword en clair détecté | Rejet explicite avec code ERR-BE-05 et raison contractuelle | Event audit + log sécurité |
Abstraction protocolaire : Le mapping des codes ERR-BE-xx vers un protocole de transport (HTTP, gRPC, etc.) est délégué au composant ProtocolMapper. Ce mapping est un point à clarifier selon la spec (§10). L'implémentation doit permettre de configurer ce mapping sans modifier la logique métier.
Garanties transactionnelles : Tout rejet n'entraîne aucune modification d'état (rollback implicite ou absence de commit).
7. Impacts sécurité
| Risque | Mitigation | Observable |
| Injection de keyword en clair | Détection de champ "keyword" dans le body JSON, rejet ERR-BE-05 | Event audit |
| Fuite de T_kw dans les logs | Masquage/troncature des T_kw dans les logs applicatifs | Configuration logger |
| Attaque par fréquence/dictionnaire | Hors périmètre (risque accepté, cf. §2.2 spec) | — |
| Accès non autorisé à l'index | Authentification/autorisation en amont (hors scope PD-236) | — |
| Corruption de M_I | Contrainte UNIQUE sur (index_id) dans table metadata + atomicité à la création | Contrainte DB |
Journalisation obligatoire : - ERR-BE-05 → event audit avec timestamp, requête anonymisée, IP source - Toutes les opérations d'indexation/recherche loguées (sans T_kw en clair)
8. Hypothèses techniques
| ID | Hypothèse | Justification | Impact si faux |
| HT-01 | Le format T_kw est défini par PD-42 pour chaque algorithm_id (ex: DET-HMAC-SHA256-B64T22-V1 → Base64 URL-safe sans padding, longueur 22) | Référence PD-42 §4.3 | Validation incorrecte (faux positifs/négatifs) |
| HT-02 | Le registre de validateurs (TokenFormatValidatorRegistry) est extensible pour supporter plusieurs algorithm_id | La spec ne restreint pas à un seul algorithm_id | Refus de tokens valides si algorithm_id inconnu |
| HT-03 | L'index_id est fourni par le client dans la requête | Choix d'implémentation (non spécifié par la spec) | Changement de contrat API si génération serveur |
| HT-04 | La création de M_I est atomique : si deux requêtes concurrentes tentent de créer M_I pour le même index_id, une seule réussit et l'autre compare avec M_I créé | Garantie INV-03 | Race condition → incohérence M_I |
| HT-05 | PostgreSQL est utilisé pour la persistance avec configuration par défaut (collation, opérateur = exact) | Choix d'implémentation | Adaptation si autre SGBD |
| HT-06 | Une recherche sur un index non initialisé (M_I inexistant) retourne un ensemble vide de resource_id | Comportement non spécifié par la spec, choix cohérent sémantiquement | Divergence si autre interprétation attendue |
| HT-07 | Le stockage garantit l'égalité stricte (exact match) pour les requêtes sur T_kw : pas de collation case-insensitive, pas d'index full-text | Garantie INV-02 | Résultats incorrects si recherche floue activée |
9. Points de vigilance (risques, dette, pièges)
| Point | Description | Recommandation |
| Race condition M_I | Deux requêtes concurrentes sur un nouvel index peuvent créer des M_I différents | Utiliser INSERT ... ON CONFLICT DO NOTHING + relecture immédiate avec verrou |
| Validateurs par algorithm_id | Chaque algorithm_id de PD-42 requiert un validateur dédié | Implémenter un registre extensible (pattern Strategy) |
| Détection keyword clair | La spec mentionne "champ explicitement qualifié de keyword" → définir la liste des champs interdits dans le contrat API | Documenter dans le contrat API |
| Performance recherche | Index DB sur (index_id, token) obligatoire pour garantir performance | Migration à valider |
| Collation DB | La collation par défaut de PostgreSQL doit garantir l'égalité stricte | Vérifier collation = 'C' ou binaire sur la colonne token |
| Comparaison M_I stricte | Attention à l'ordre des champs JSON si sérialisation → utiliser comparaison champ-à-champ | Tests sur ordre de champs différent |
| Mapping protocolaire | Le mapping ERR-BE-xx vers HTTP/gRPC est un point à clarifier (§10 spec) | Implémenter via abstraction ProtocolMapper |
10. Hors périmètre
- Génération/canonicalisation/tokenisation des keywords (PD-42)
- Recherche floue, partielle, full-text, sémantique
- Scoring/ranking des résultats
- Protection contre analyse de fréquence/dictionnaire (risque accepté)
- Migration d'index existants (évolution PD-42)
- Purge/reconstruction d'index
- Authentification/autorisation (géré en amont)
- Choix du protocole de transport (HTTP/gRPC) — abstrait via ProtocolMapper
Références
- Spec : PD-236-specification.md
- Tests : PD-236-tests.md
- Dépendance : PD-42 — Recherche déterministe chiffrée
- Epic : PD-186 BACKEND CORE