Aller au contenu

PD-281 — Plan d'implémentation

1. Découpage en composants

C1 — Discriminateur enum dans extract-facts.py

Fichier : scripts/formal/extract-facts.py Responsabilité : Modifier la fonction _collect_zlint_entities() et la boucle de génération enum_states dans write_zlint_config() pour ne produire des checks Anchor enum que pour les colonnes status ou explicitement mappées dans _z_enum_type_mappings.

Modifications :

  1. _collect_zlint_entities() (lignes 1498-1522) : Adapter la collecte pour stocker tous les enums d'une entité (pas seulement le dernier), en conservant le nom de colonne (field) pour chaque enum.
  2. Boucle enum_states (lignes 1607-1612 dans write_zlint_config()) : Ajouter un filtre de discrimination : seules les entrées dont field == 'status' ou dont le couple (entity_name) est présent dans _z_enum_type_mappings pour le slug courant sont incluses dans anchored_enums.

Mécanisme de discrimination (v2 — correction AMB-02, couple entité+colonne) :

# Pseudo-code du filtre — itère sur le couple (entity_name, field)
for ent_name, enum_list in enum_states.items():
    # enum_list = [{field, values}, ...] (multi-enum par entité)
    for enum_data in enum_list:
        field = enum_data['field']
        values = enum_data['values']
        # DISCRIMINATION sur le couple (ent_name, field)
        if field == 'status':
            # Colonne status → inclusion automatique
            z_type = enum_type_map.get(ent_name, f'{ent_name}_state')
            anchored_enums[ent_name] = {'values': values, 'expected_z_type': z_type}
        elif ent_name in enum_type_map:
            # Mapping explicite dans _z_enum_type_mappings → inclusion
            z_type = enum_type_map[ent_name]
            anchored_enums[ent_name] = {'values': values, 'expected_z_type': z_type}
        # Sinon → exclusion (pas de check Anchor enum pour ce couple)

Observable : Les _generated-z-lint.yaml régénérés ne contiennent plus anchor_batch_event, key_envelope, legal_access_event, legal_mandate, deposit (pour leurs colonnes non-status) dans anchored.enum_states.

C2 — Complétion des types Z dans les fichiers .zed

Fichiers :

  • docs/normes/rfc-3161/formal/RFC_3161.zed
  • docs/normes/nf-z42-013/formal/NF_Z42_013.zed
  • docs/normes/iso-14641/formal/ISO_14641.zed
  • docs/normes/pv-pre/formal/PV_PRE.zed

Responsabilité : Ajouter les déclarations de types Z manquants dans la SECTION 1 (Basic Types) de chaque fichier .zed cible, avec les valeurs exactes spécifiées.

Types à ajouter :

Type Z Fichier .zed Valeurs Emplacement
TimestampBatchStatus RFC_3161.zed OPEN \| SEALED \| TIMESTAMPED \| FAILED SECTION 1 — ajout explicite du type (même si BatchState existe avec les mêmes valeurs, TimestampBatchStatus est exigé par la Spec §5.3 et vérifié par TC-NOM-05)
AnchorBatchStatus NF_Z42_013.zed PENDING \| BUILDING \| SUBMITTED \| PENDING_FINALITY \| FINALIZED \| FAILED SECTION 1
AnchorBatchStatus ISO_14641.zed PENDING \| BUILDING \| SUBMITTED \| PENDING_FINALITY \| FINALIZED \| FAILED SECTION 1
LegalReKeyStatus PV_PRE.zed ACTIVE \| REVOKED \| EXPIRED \| COMPLETED \| DESTROYED SECTION 1

Observable : Chaque type Z est présent dans le fichier .zed cible avec exactement les valeurs listées, en notation Z standard (TypeName ::= VAL1 | VAL2 | ...).

C3 — Régénération et validation globale

Fichiers :

  • scripts/formal/extract-facts.py (exécution)
  • Tous les _generated-z-lint.yaml (9 normes)
  • docs/normes/AUDIT-SYNTHESIS.md

Responsabilité : Exécuter la chaîne complète extract-facts.pyverify-norm.sh sur les 9 normes, valider le résultat SUCCESS global, mettre à jour AUDIT-SYNTHESIS.md.

Observable : verify-norm.sh retourne SUCCESS sur 9/9 normes, AUDIT-SYNTHESIS.md affiche Z-lint 9/9 PASS.

C4 — Règle conditionnelle DepositStatus

Fichiers : NF_Z42_013.zed, ISO_14641.zed (conditionnellement)

Responsabilité : Vérifier dans les faits extraits si deposit.status existe comme colonne lifecycle. Si oui, ajouter DepositStatus dans les .zed concernés. Si non, ne rien ajouter.

Mécanisme : Rechercher entity_enum_values(deposit, status, [...]) dans les faits Prolog générés. Présence = colonne lifecycle confirmée. Absence = pas d'exigence DepositStatus.

Observable : Présence/absence de DepositStatus dans les .zed conforme à l'existence de deposit.status dans les faits extraits.

2. Flux techniques

Flux A — Discrimination (C1)

1. extract-facts.py extrait entity_enum_values depuis les entités TypeORM
2. _collect_zlint_entities() collecte TOUS les enums (entity, field, values)
3. write_zlint_config() itère sur enum_states :
   3a. Si field == 'status' → inclusion (Anchor enum check généré)
   3b. Si entity dans _z_enum_type_mappings pour ce slug → inclusion (mapping explicite)
   3c. Sinon → exclusion (pas de check Anchor enum)
4. anchored.enum_states dans _generated-z-lint.yaml ne contient que les state machines

Flux B — Complétion (C2)

1. Identifier les types Z manquants (tableau §5.3 de la spec)
2. Pour chaque type Z manquant :
   2a. Ouvrir le fichier .zed cible
   2b. Localiser SECTION 1 (Basic Types)
   2c. Ajouter la déclaration TypeName ::= VAL1 | VAL2 | ...
   2d. Vérifier cohérence avec les valeurs enum du code (faits Prolog)
3. Sauvegarder sans modifier les autres sections

Flux C — Régénération et validation (C3)

1. Exécuter extract-facts.py pour régénérer :
   - _generated-facts.pl (faits Prolog)
   - _generated-z-lint.yaml (configs Z-lint) pour chaque norme
2. Pour chaque norme (9 au total) :
   2a. Exécuter verify-norm.sh <norm-dir> <reports-dir>
   2b. Vérifier statut de sortie = 0 (SUCCESS)
3. Vérifier AUDIT-SYNTHESIS.md : Z-lint = 9/9 PASS
4. Vérifier idempotence : relancer le flux → même résultat

Flux D — Condition DepositStatus (C4)

1. Exécuter extract-facts.py
2. Rechercher entity_enum_values(deposit, status, [...]) dans les faits
3. Si trouvé :
   3a. Extraire les valeurs enum
   3b. Ajouter DepositStatus dans NF_Z42_013.zed et ISO_14641.zed
   3c. Mettre à jour _z_enum_type_mappings si nécessaire
4. Si non trouvé :
   4a. Ne rien ajouter
   4b. Vérifier qu'aucun check DepositStatus n'est exigé

3. Mapping invariants → mécanismes

Invariant ID Exigence Mécanisme Composant Observable Risque
INV-281-01-discrimination Seules les colonnes status et celles mappées dans _z_enum_type_mappings sont éligibles au check Anchor enum Filtre conditionnel dans write_zlint_config() : if data['field'] != 'status' and ent_name not in enum_type_map: continue C1 Absence des couples exclus dans anchored.enum_states des _generated-z-lint.yaml Faible — logique de filtre simple et déterministe
INV-281-02-exclusion-classification Les 5 colonnes de classification sont exclues du check Conséquence directe du filtre INV-281-01 (ces colonnes ne sont pas status et n'ont pas de mapping explicite) C1 Aucun des 5 couples (anchor_batch_event.event_type, key_envelope.envelope_type, legal_access_event.event_type, legal_mandate.validation_status, deposit.document_category) dans enum_states Faible — couvert par INV-01
INV-281-03-completude-types-z Chaque colonne status doit avoir un type Z dans le .zed cible Ajout manuel des 4 types Z manquants dans les fichiers .zed (SECTION 1) C2 Présence textuelle de TimestampBatchStatus, AnchorBatchStatus, LegalReKeyStatus avec valeurs exactes Faible — ajout déclaratif
INV-281-04-non-regression-anchor-entity Checks Anchor entity inchangés Aucune modification de la logique Anchor entity dans z-notation-lint.py ni dans la boucle anchored_entities de extract-facts.py C1 Diff avant/après sur sections anchored.entities et check_anchoring() pour les entities : identique Moyen — vérifier que le refactoring de _collect_zlint_entities ne perturbe pas le dict entities
INV-281-05-idempotence Deux exécutions identiques → même résultat Le script extract-facts.py est déterministe (tri alphabétique des clés, même input → même output). Aucun élément aléatoire ajouté. C1, C3 Deux runs consécutifs → diff des _generated-z-lint.yaml = vide Faible — le code actuel est déjà déterministe
INV-281-06-resultat-global verify-norm.sh = SUCCESS, Z-lint = 9/9 PASS Exécution complète de la chaîne de vérification après corrections C1+C2+C4 C3 Statut de sortie verify-norm.sh = 0 sur 9 normes, AUDIT-SYNTHESIS.md affiche 9/9 PASS Moyen — dépend de la correction complète des 3 volets
INV-281-07-modele-etats-lint Pas de spécification de transitions métier Aucun ajout de règles de transitions dans les .zed ou le linter. Les types Z ajoutés (C2) déclarent les valeurs enum sans contraindre les transitions. C2 Inspection des .zed modifiés : aucun Δ-schema de transition ajouté pour les nouveaux types Faible — périmètre limité aux déclarations de types
INV-281-08-responsabilite-doc-only Changements limités à ProbatioVault-doc Tous les fichiers modifiés sont dans ProbatioVault-doc : scripts/formal/extract-facts.py, docs/normes/*/formal/*.zed, docs/normes/*/formal/_generated-z-lint.yaml C1, C2, C3 git diff --stat ne montre que des chemins dans ProbatioVault-doc Faible — périmètre naturellement doc-only

4. Mapping critères d'acceptation → mécanismes

Critère ID Mécanisme(s) Composant Observable Risque
CA-01 Exécution verify-norm.sh sur 9 normes après corrections C1+C2+C4 C3 Statut de sortie = 0, 0 échec Z-lint Moyen — dépend de l'intégration des 3 volets
CA-02 Ajout des 4 types Z dans les .zed cibles + condition DepositStatus C2, C4 Lecture des fichiers .zed : types présents avec valeurs exactes Faible — ajout déclaratif
CA-03 Filtre discrimination dans write_zlint_config() C1 Absence des couples exclus dans anchored.enum_states des configs générées Faible
CA-04 Mise à jour de AUDIT-SYNTHESIS.md après run complet C3 Texte Z-lint 9/9 PASS visible dans le document Faible — conséquence de CA-01
CA-05 Filtre discrimination (C1) élimine les faux positifs des configs C1 Vérification textuelle : aucun des 5 couples exclus dans enum_states Faible
CA-06 Non-modification de la logique Anchor entity dans z-notation-lint.py et de la boucle anchored_entities dans extract-facts.py C1 Comparaison avant/après des sections anchored.entities et des checks Anchor entity : identiques Moyen
CA-07 Déterminisme du script (tri alphabétique, pas de random, entrées identiques) C1, C3 Deux runs → diff des artefacts = vide Faible
CA-08 Vérification de l'existence de deposit.status dans les faits Prolog + ajout conditionnel de DepositStatus C4 Si entity_enum_values(deposit, status, ...) existe → DepositStatus dans les .zed; sinon → absent Moyen — dépend de la source de vérité

5. Mapping tests (TC-*) → mécanismes + observables

Test ID Référence spec Mécanisme(s) Point(s) d'observation Niveau de test visé
TC-NOM-01 INV-281-01, CA-03 Filtre discrimination field != 'status' dans write_zlint_config() _generated-z-lint.yaml pour pv-anchor : anchor_batch_event absent de enum_states Intégration
TC-NOM-02 INV-281-01 Filtre discrimination field == 'status' → inclusion _generated-z-lint.yaml : anchor_batch présent dans enum_states avec expected_z_type Intégration
TC-NOM-03 INV-281-01, CA-03 Mapping explicite dans _z_enum_type_mappings → inclusion _generated-z-lint.yaml : entité mappée présente avec le z_type du mapping Intégration
TC-NOM-04 INV-281-02, CA-03, CA-05 Filtre discrimination exclut les 5 couples contractuels Vérification textuelle des 9 _generated-z-lint.yaml : aucun des 5 couples dans enum_states Intégration
TC-NOM-05 INV-281-03, CA-02 Présence des 4 types Z dans les .zed cibles Lecture des fichiers : TimestampBatchStatus, AnchorBatchStatus, LegalReKeyStatus présents avec valeurs exactes Unitaire
TC-NOM-06 INV-281-03, CA-08 Règle conditionnelle DepositStatus (branche vraie) Si deposit.status existe : DepositStatus dans NF_Z42_013.zed et ISO_14641.zed Intégration
TC-NOM-07 INV-281-03, CA-08 Règle conditionnelle DepositStatus (branche fausse) Si deposit.status absent : DepositStatus non exigé Intégration
TC-NOM-08 INV-281-06, CA-01, CA-04 Exécution complète verify-norm.sh sur 9 normes Statut SUCCESS, 0 échec Z-lint, AUDIT-SYNTHESIS.md = 9/9 PASS E2E
TC-NOM-09 INV-281-05, CA-07 Deux exécutions consécutives de extract-facts.py + verify-norm.sh diff des _generated-z-lint.yaml et des verdicts = vide E2E
TC-NOM-10 INV-281-04, CA-06 Non-modification de la logique Anchor entity diff avant/après des sections anchored.entities = vide Intégration
TC-NOM-11 INV-281-07 Inspection des .zed modifiés : aucune règle de transition métier Aucun Δ-schema de transition ajouté pour les nouveaux types Unitaire
TC-NOM-12 INV-281-08 git diff --stat limité à ProbatioVault-doc Aucune modification hors projet doc Unitaire
TC-ERR-01 ERR-281-01 Validation des formats §5.1 dans le linter Message FAIL lint avec champ invalide identifié Intégration
TC-ERR-02 ERR-281-02, INV-281-02 Détection de faux positif post-correction Non-conformité majeure si check Anchor enum pour colonne non-status non mappée Intégration
TC-ERR-03 ERR-281-03, INV-281-03 Type Z absent du .zed cible verify-norm.sh échoue avec motif "type Z manquant" E2E
TC-ERR-04 ERR-281-04, CA-01 verify-norm.sh ≠ SUCCESS Story non acceptée E2E
TC-ERR-05 ERR-281-05, CA-04 AUDIT-SYNTHESIS.md9/9 PASS Story non acceptée E2E
TC-ERR-06 ERR-281-06, INV-281-04 Régression Anchor entity détectée Story non acceptée, régression bloquante Intégration
TC-ERR-07 ERR-281-07, INV-281-05 Non-idempotence entre deux runs Story non acceptée, défaut pipeline E2E
TC-NR-01 INV-281-04 Baseline Anchor entity avant/après Diff strictement identique Intégration
TC-NR-02 CA-06 Contrôles non ciblés inchangés Aucun changement des règles hors scope Intégration
TC-NR-03 INV-281-08 Périmètre doc-only git diff --stat limité à ProbatioVault-doc Unitaire
TC-NR-04 INV-281-05 Idempotence Run #1 == Run #2 E2E
TC-NEG-01 à TC-NEG-10 ERR-281-01, §5.1 Validation des formats (regex, longueur, casse) Messages FAIL lint explicites Unitaire

6. Gestion des erreurs

Erreur Condition Traitement Code/Statut Observable
ERR-281-01 Donnée non conforme aux formats §5.1 Le linter Z (z-notation-lint.py) produit un FAIL avec cause explicite traçant le champ invalide FAIL lint Message d'erreur dans le rapport JUnit XML
ERR-281-02 Faux positif (Anchor enum check pour colonne non-status non mappée) Détecté lors de l'inspection des _generated-z-lint.yaml post-régénération Non-conformité majeure Couple fautif visible dans anchored.enum_states
ERR-281-03 Type Z requis absent du .zed cible z-notation-lint.py produit un FAIL "Z type {z_type} not found in spec" FAIL lint Échec dans le rapport JUnit
ERR-281-04 verify-norm.sh ≠ SUCCESS Script retourne exit code 1 (FAILURES > 0) Exit code 1 Sortie console + rapport
ERR-281-05 AUDIT-SYNTHESIS.md9/9 PASS Divergence d'observable audit Non-conformité Texte dans le document
ERR-281-06 Régression Anchor entity Détectée par comparaison diff avant/après Bloquante Différence dans sections anchored.entities
ERR-281-07 Non-idempotence Détectée par double exécution + diff Non-idempotence pipeline Diff non vide entre run #1 et #2

7. Impacts sécurité

Aucun impact sécurité identifié.

  • PD-281 modifie uniquement des scripts d'analyse statique et des spécifications formelles (*.zed)
  • Aucune donnée utilisateur traitée
  • Aucune modification de code backend (entités, services, controllers)
  • Aucun secret, token ou credential manipulé
  • Les scripts modifiés (extract-facts.py) ne sont exécutés qu'en CI/CD (pipeline de vérification formelle), pas en production

8. Hypothèses techniques

ID Hypothèse Impact si faux
H-281-01 Le pipeline cible couvre exactement 9 normes : rfc-3161, rfc-5054, nf-z42-013, iso-14641, pv-anchor, pv-audit, pv-envelope, pv-pre, pv-proof (v2 — vérifié sur le filesystem : docs/normes/*/formal/ contient ces 9 dossiers. La Spec §5.1 liste etsi-119-xxx, eidas, rgpd qui n'existent pas — divergence AMB-01 résolue, la Spec doit être mise à jour). Les CAs numériques (9/9 PASS) deviennent invalides. Ajuster le nombre cible.
H-281-02 La structure actuelle de _collect_zlint_entities() stocke un seul enum par entité (le dernier trouvé). La modification pour filtrer par field doit conserver ce comportement mono-enum ou le rendre multi-enum. Si multi-enum nécessaire (entité avec status ET event_type), adapter le dict pour une liste d'enums par entité. Le filtre doit alors itérer sur chaque enum de l'entité.
H-281-03 Les fichiers .zed référencés (RFC_3161.zed, NF_Z42_013.zed, ISO_14641.zed, PV_PRE.zed) sont les sources de vérité de leurs normes respectives Risque de corriger le mauvais artefact si un autre fichier .zed est la référence
H-281-04 Le check Anchor entity et le check Anchor enum sont orthogonaux dans z-notation-lint.py (check_anchoring traite les deux sections indépendamment) Si couplage caché, la modification du filtre enum pourrait affecter les checks entity
H-281-05 deposit n'a qu'une seule colonne enum (document_category). L'existence de deposit.status est vérifiable dans les faits Prolog extraits. Si deposit a un status, il faut ajouter DepositStatus (CA-08). Si la source de vérité est inaccessible, CA-08 n'est pas testable.
H-281-06 RFC_3161.zed contient déjà BatchState ::= OPEN \| SEALED \| TIMESTAMPED \| FAILED (constaté). (v2 — correction ECT-01) : TimestampBatchStatus est ajouté explicitement dans le .zed conformément à la Spec §5.3. Le mapping _z_enum_type_mappings référence {'timestamp_batch': 'TimestampBatchStatus'}. BatchState coexiste comme type interne. Si les deux types avec mêmes valeurs causent un conflit dans le linter → renommer ou supprimer BatchState (risque faible, le linter ne valide que les types référencés par expected_z_type).

9. Points de vigilance (risques, dette, pièges)

9.1 Multi-enum par entité

Risque : _collect_zlint_entities() stocke actuellement enum_states[entity_name] = {field, values}, écrasant les enums précédents de la même entité. Si une entité a à la fois status et event_type, seul le dernier extrait sera conservé.

Mitigation : Adapter la structure pour stocker une liste d'enums par entité : enum_states[entity_name] = [{field, values}, ...]. Le filtre itère ensuite sur chaque enum et n'inclut que ceux avec field == 'status' ou mapping explicite.

9.2 TimestampBatchStatus dans RFC_3161.zed

Contexte : RFC_3161.zed contient déjà BatchState ::= OPEN | SEALED | TIMESTAMPED | FAILED avec les mêmes valeurs que TimestampBatchStatus.

Décision (v2 — correction Gate 5) : Créer TimestampBatchStatus explicitement dans RFC_3161.zed (SECTION 1), conformément à la Spec §5.3 et au test TC-NOM-05. Le mapping _z_enum_type_mappings pour rfc-3161 doit référencer 'timestamp_batch': 'TimestampBatchStatus' (et non BatchState).

Justification : La Spec est le contrat canonique. BatchState peut coexister comme type interne non lié à l'ancrage Z-lint. L'ajout de TimestampBatchStatus est déclaratif et non-breaking.

9.3 Risque de régression sur les entity checks

Risque : La modification de _collect_zlint_entities() pourrait altérer le dict entities utilisé pour anchored_entities (les checks Anchor entity).

Mitigation : Le dict entities et le dict enum_states sont des structures séparées dans le retour de la fonction. La modification porte uniquement sur enum_states. Vérifier par diff que anchored.entities reste identique avant/après.

9.4 Ordre des faits Prolog et idempotence

Risque : Si l'ordre d'extraction des faits change entre runs (parcours de fichiers non déterministe), le multi-enum par entité pourrait produire des résultats différents.

Mitigation : extract-facts.py utilise déjà sorted() pour les clés. S'assurer que les valeurs enum sont également triées avant écriture.

9.5 Source de vérité deposit.status

Risque : La spec indique que l'existence de deposit.status est "à confirmer" (H-281-05). Si non confirmé au moment de l'implémentation, CA-08 reste en suspens.

Mitigation : Implémenter la logique conditionnelle dans le code. Le test TC-NOM-06/TC-NOM-07 couvrira les deux branches selon la source de vérité disponible.

9.6 Cohérence _z_enum_type_mappings entre normes

Attention : Une même entité peut avoir des mappings différents selon la norme. Par exemple anchor_batch est mappé BatchStatus dans pv-anchor mais pourrait nécessiter AnchorBatchStatus dans nf-z42-013 et iso-14641.

Action : Vérifier la cohérence des noms de types Z dans chaque .zed cible et ajuster les mappings par norme.

10. Hors périmètre

  • Modification des entités TypeORM backend : aucune colonne base de données n'est ajoutée, modifiée ou supprimée
  • Ajout de nouvelles normes au pipeline de vérification formelle
  • Refonte du système de vérification formelle (architecture TLA+/Alloy/Prolog/Z)
  • Modification du check Anchor entity (mapping entité → schéma Z) dans z-notation-lint.py
  • Spécification des transitions métier entre valeurs d'état (ex: PENDING → FINALIZED). PD-281 ne définit que l'ancrage des types, pas les workflows.
  • Modification de z-notation-lint.py : le linter consomme les configs générées. La correction porte sur le générateur (extract-facts.py) et les specs (.zed), pas sur le linter.

11. Mécanismes cross-module

Aucune modification d'autres modules. PD-281 est entièrement contenu dans ProbatioVault-doc (scripts, specs .zed, configs générées). Aucun guard, middleware ou intercepteur n'est ajouté sur d'autres modules.

12. Périmètre de test

Niveau de test In scope Hors scope (justification)
Unitaire Vérification de la présence/absence des types Z dans les .zed (TC-NOM-05, TC-NOM-11, TC-NOM-12, TC-NEG-01 à TC-NEG-10)
Intégration Exécution de extract-facts.py + vérification des _generated-z-lint.yaml (TC-NOM-01 à TC-NOM-04, TC-NOM-06, TC-NOM-07, TC-NOM-10, TC-NR-01, TC-NR-02)
E2E Chaîne complète extract-facts.pyverify-norm.sh sur 9 normes (TC-NOM-08, TC-NOM-09, TC-NR-04, TC-ERR-03 à TC-ERR-07)

Tous les niveaux de test sont couverts, aucune exclusion.

La couverture minimale de 80% s'applique à l'ensemble du périmètre. Les tests E2E sont le niveau principal (la chaîne extract-facts.pyverify-norm.sh est l'observable final pour la majorité des CAs).