PD-296 — Plan d'implémentation¶
Résolution des conditions RESERVE Gate 3 v1¶
Ce plan intègre les corrections des 3 écarts bloquants identifiés en Gate 3 v1 (verdict RESERVE 7.438/10) :
| Écart | Condition | Résolution dans ce plan |
|---|---|---|
| Écart 1 | mandatory_gates_formal=3 sans checkpoint Gate 3 | Ajout de GATE3_SPEC_FORMAL (checkpoint pré-Gate 3) |
| Écart 2 | CA-296-01 (step 0+1) vs INV-296-01 (5 checkpoints) | Alignement sur 6 checkpoints (pas 5). L'exécution step 0 (/coherence) reste hors comptage formel — elle est un contrôle préliminaire. Le 6e checkpoint est GATE3_SPEC_FORMAL. |
| Écart 3 | TC-INV-01..04 référencés mais non définis | Scénarios GIVEN/WHEN/THEN détaillés dans §5 |
Les écarts majeurs (4-9, 12) sont traités dans les hypothèses (§8) et points de vigilance (§9).
1. Découpage en composants¶
| # | Composant | Responsabilité | Fichier(s) | Type |
|---|---|---|---|---|
| C1 | formal-check.sh | Wrapper central : validation entrées, invocation /coherence ou /formal-verify, mapping sortie → formal_check_result_json (D-296-14), gestion timeout/fail-closed | scripts/formal-check.sh | Script shell (nouveau) |
| C2 | formal-state.sh | Gestion d'état des checkpoints : lecture/écriture dans .gov-local.json, FSM des checkpoints (PENDING→RUNNING→PASSED/FAILED_BLOCKING), guard de transition aval | scripts/lib/formal-state.sh | Librairie shell (nouvelle) |
| C3 | formal-lock.sh | Lock distribué (flock), idempotence (SHA-256 → fichier flat), rate-limiting (compteur par minute), réconciliation orphelins | scripts/lib/formal-lock.sh | Librairie shell (nouvelle) |
| C4 | Skills modifiés | Injection des appels formal-check.sh aux 6 points d'intégration + guards de blocage | gov-step.md, gov-gate.md, gov-check-plan.md, gov-accept.md | Skills Claude Code (modification) |
| C5 | Article VIII + refs | Ajout Article VIII dans CONSTITUTIONAL.md, mise à jour table CLAUDE.md, mise à jour vérification constitutionnelle obligatoire | governance/CONSTITUTIONAL.md, CLAUDE.md | Documentation constitutionnelle (modification) |
| C6 | FSM + procédures | Mise à jour data/fsm.yaml (événements formels), procedures.md (nouvelles procédures), workflow-rules.md (nouvelles règles) | data/fsm.yaml, .claude/rules/procedures.md, .claude/rules/workflow-rules.md | Configuration + documentation (modification) |
2. Flux techniques¶
2.1 Flux nominal — Checkpoint formel¶
1. Le skill (gov-step, gov-gate, gov-check-plan, gov-accept) atteint un point d'intégration
2. Appel : ./scripts/formal-check.sh <story_id> <project_code> <checkpoint_id> <trigger_event> <artifact_path>
3. formal-check.sh :
a. Valide les entrées (D-296-01..D-296-05, D-296-10) → si invalide : FAILED_BLOCKING immédiat
b. Génère correlation_id (uuidgen) et idempotency_key (sha256 du payload)
c. Acquiert le lock flock sur /tmp/formal-lock-<story_id>-<checkpoint_id>.lock (TTL via timeout)
d. Vérifie idempotence (fichier .formal-idempotency/<key>) → si replay identique : retourne résultat caché
e. Met l'état à RUNNING dans .gov-local.json via formal-state.sh
f. Invoque l'outil formel avec timeout :
- coherence_spec → /coherence check PD-XX (via unset CLAUDECODE && claude -p)
- formal_contracts, formal_plan, formal_code → /formal-verify (via unset CLAUDECODE && claude -p)
- formal_full → /formal-verify pipeline complet
g. Mappe la sortie vers verdict GO|FAIL :
- /coherence : "blocking": true + issues > 0 → FAIL, sinon GO
- /formal-verify : exit code 0 → GO, exit code 1/2/3 → FAIL
- Timeout (180s) → FAIL + failure_code=TIMEOUT
- Outil absent → FAIL + failure_code=TOOL_UNAVAILABLE
h. Construit formal_check_result_json (D-296-14) avec toutes les clés requises
i. Met l'état à PASSED ou FAILED_BLOCKING via formal-state.sh
j. Libère le lock, enregistre idempotence, écrit résultat en audit
k. Retourne le JSON sur stdout (exit 0 si PASSED, exit 1 si FAILED_BLOCKING)
4. Le skill lit le code de retour :
- exit 0 → continue le workflow
- exit 1 → affiche les détails, bloque la transition aval, propose correction
2.2 Flux de correction — FAILED_BLOCKING → PENDING → relance¶
1. Le skill affiche les détails d'échec (details[]) à l'utilisateur
2. L'utilisateur corrige l'artefact source (spec, plan, code...)
3. Le skill relance formal-check.sh avec le même checkpoint_id
4. formal-check.sh :
a. Détecte état courant FAILED_BLOCKING → transition vers PENDING (autorisée)
b. Génère nouveau correlation_id, nouvelle idempotency_key (payload différent)
c. Exécute le contrôle formel normalement
d. Si GO → PASSED, transition aval débloquée
e. Si FAIL → FAILED_BLOCKING à nouveau, nouvelle boucle
2.3 Flux de ré-évaluation — PASSED → PENDING (résolution Écart 4)¶
1. Après une gate NON_CONFORME, l'artefact source est modifié (spec v2, code corrigé...)
2. Le skill appelle formal-check.sh avec flag --force-recheck
3. formal-check.sh :
a. Détecte état courant PASSED + flag --force-recheck → transition vers PENDING
b. Conserve l'historique du PASSED précédent dans details[]
c. Exécute le contrôle formel sur l'artefact modifié
d. Résultat : nouveau PASSED ou FAILED_BLOCKING
Note : Sans --force-recheck, PASSED reste terminal (protection contre les relances inutiles)
2bis. Diagramme de dépendances agents (step 6b)¶
graph LR
subgraph Wave 1 — Parallélisable
A1[C1: formal-check.sh]
A2[C2: formal-state.sh]
A3[C3: formal-lock.sh]
end
subgraph Wave 2 — Dépend de Wave 1
A4[C5: Article VIII + CLAUDE.md]
A5[C6: FSM + procédures]
end
subgraph Wave 3 — Dépend de Wave 1 + Wave 2
A6[C4: Skills modifiés]
end
A1 --> A6
A2 --> A6
A3 --> A6
A4 --> A6
A5 --> A6 Wave 1 : Les 3 scripts/librairies (C1, C2, C3) sont indépendants — développables en parallèle. Wave 2 : Les modifications documentaires (C5, C6) dépendent de la connaissance des mécanismes Wave 1 pour les référencer correctement. Wave 3 : Les modifications de skills (C4) intègrent les appels aux scripts Wave 1 et référencent les articles/procédures Wave 2.
3. Mapping invariants → mécanismes¶
| Invariant ID | Exigence | Mécanisme | Composant | Observable | Risque |
|---|---|---|---|---|---|
| INV-296-01 | Chaque story exécute exactement 6 checkpoints formels | Mapping déterministe dans formal-check.sh : 6 checkpoint_id canoniques. Guard dans formal-state.sh : count_checkpoints_passed() vérifie 6/6 avant Done | C1, C2 | jq '.formal_checkpoints | length' .gov-local.json == 6 | Oubli d'un point d'appel dans un skill |
| INV-296-02 | STEP1_SPEC_COHERENCE obligatoire avant step 2 | Appel formal-check.sh dans gov-step.md après sauvegarde spec. Guard dans formal-state.sh : check_formal_passed STEP1_SPEC_COHERENCE avant transition step 1→2 | C1, C2, C4 | Checkpoint PASSED dans .gov-local.json avant step 2 | Spec sauvegardée mais checkpoint oublié |
| INV-296-03 | STEP4_CONTRACTS_FORMAL obligatoire avant Gate 5 | Appel formal-check.sh dans gov-step.md après sauvegarde plan+contracts. Guard dans formal-state.sh | C1, C2, C4 | Checkpoint PASSED avant Gate 5 | Plan sauvé sans contracts |
| INV-296-04 | GATE5_PLAN_FORMAL dans /gov-check-plan Phase 4 | Nouvelle Phase 4 dans gov-check-plan.md appelant formal-check.sh | C1, C4 | Phase 4 visible dans traces avant verdict Gate 5 | Phase 4 sautée si check-plan échoue avant |
| INV-296-05 | STEP6_CODE_FORMAL obligatoire avant step 7 | Appel formal-check.sh dans gov-step.md/gov-impl.md après step 6c | C1, C2, C4 | Checkpoint PASSED avant step 7 | Timeout sur gros codebases |
| INV-296-06 | GATE8_FINAL_FORMAL obligatoire avant Gate 8 | Appel formal-check.sh dans gov-accept.md post-Phase 2 | C1, C2, C4 | Checkpoint PASSED avant soumission Gate 8 | Durée cumulée > 180s sur pipeline complet |
| INV-296-07 | FAIL → transition aval BLOQUÉE | Guard check_formal_passed() dans formal-state.sh retourne exit 1 si état ≠ PASSED. Chaque skill vérifie avant progression | C2, C4 | Transition aval absente dans audit | Guard contourné par appel direct |
| INV-296-08 | Outil indisponible ou timeout → FAIL (fail-closed) | formal-check.sh : which swipl, which claude → TOOL_UNAVAILABLE. timeout 180 → TIMEOUT. Défaut = FAIL | C1 | failure_code dans résultat JSON | Outil présent mais qui hang |
| INV-296-09 | Pas de Done Jira si FAIL ouvert | Guard check_all_formal_passed() dans formal-state.sh appelé avant transition Done (31). Couvre aussi DONE_ANOMALY (2) et REJECTED (3) — résolution Écart 5 | C2, C4 | Transition refusée dans audit | Transition Jira directe hors workflow |
| INV-296-10 | Tous projets sans exception | Aucune condition if project_code == ... dans formal-check.sh. Validation regex D-296-02 accepte les 8 codes | C1 | Mêmes checkpoints sur backend et site | Projet non listé ajouté plus tard |
| INV-296-11 | Article VIII dans CONSTITUTIONAL.md + ref CLAUDE.md | Texte ajouté manuellement (C5). Vérifié par grep "Article VIII" governance/CONSTITUTIONAL.md | C5 | Article détectable par grep | Oubli de mise à jour CLAUDE.md |
| INV-296-12 | 4 états uniquement : PENDING, RUNNING, PASSED, FAILED_BLOCKING | Enum dans formal-state.sh : FORMAL_STATES=(PENDING RUNNING PASSED FAILED_BLOCKING). Toute valeur hors enum → rejet | C2 | set_checkpoint_state rejette états invalides | Corruption manuelle du JSON |
| INV-296-13 | Transitions hors §5.4 INTERDITES | Matrice de transitions dans formal-state.sh : ALLOWED_TRANSITIONS associative array. Transitions non listées → rejet avec log | C2 | Tentative interdite → message d'erreur + exit 1 | Appel direct jq sur le JSON |
| INV-296-14 | Payload résultat conforme D-296-14 | Validation JSON avec jq dans formal-check.sh : vérification des 12 clés requises + regex sur chaque valeur | C1 | Validation réussie sur chaque run | Clé details vide vs absente |
| INV-296-15 | Lock + idempotence + réconciliation + rate-limit | formal-lock.sh : flock (lock), fichier SHA-256 (idempotence), cron-like scan (réconciliation), compteur /tmp/formal-rate-<story> (rate-limit) | C3 | Lock acquis/rejeté dans logs, replay idempotent | flock non atomique sur NFS (non applicable ici — exécution locale) |
| INV-296-16 | Données invalides → rejet + blocage | Validation regex dans formal-check.sh pour chaque D-296-* avant toute exécution. Rejet immédiat avec FAILED_BLOCKING | C1 | Regex match/mismatch dans stderr | Encoding UTF-8 non-ASCII dans story_id |
| INV-296-17 | Aucun mode warning/informative/skip | Pas de flag --warning-only, --skip, --dry-run dans formal-check.sh. Aucun if GOV_FORMAL_SKIP dans les skills | C1, C4 | Absence de ces modes dans le code | Variable d'environnement non documentée |
| INV-296-18 | Toutes les règles testables | Matrice TC-* dans §5 couvre chaque invariant. TC-INV-04 vérifie la complétude | Tous | Matrice de couverture complète | Évolution spec sans mise à jour tests |
4. Mapping critères d'acceptation → mécanismes¶
| Critère ID | Mécanisme(s) | Composant | Observable | Risque |
|---|---|---|---|---|
| CA-296-01 | /coherence check invoqué au step 0 (existant, hors comptage) ET step 1 (STEP1_SPEC_COHERENCE via formal-check.sh) | C1, C4 | Deux exécutions dans session-log avec correlation_id distincts | Confusion step 0 / step 1 dans le comptage |
| CA-296-02 | 4 checkpoints formal_* : STEP4_CONTRACTS_FORMAL, GATE5_PLAN_FORMAL, STEP6_CODE_FORMAL, GATE8_FINAL_FORMAL + GATE3_SPEC_FORMAL (nouveau) | C1, C2, C4 | 5 checkpoints formal_* + 1 coherence = 6 dans .gov-local.json | Oubli d'un point d'appel |
| CA-296-03 | Guard check_formal_passed() avant chaque transition aval. Exit 1 = blocage | C2, C4 | Transition absente tant que FAILED_BLOCKING | Appel direct sans guard |
| CA-296-04 | formal-check.sh : which <tool> + timeout 180 → FAIL + TOOL_UNAVAILABLE/TIMEOUT | C1 | failure_code dans résultat JSON | Tool présent mais qui retourne erreur inattendue |
| CA-296-05 | Article VIII rédigé dans governance/CONSTITUTIONAL.md | C5 | grep -c "Article VIII" governance/CONSTITUTIONAL.md ≥ 1 | Texte incorrect ou incomplet |
| CA-296-06 | Table constitutionnelle dans CLAUDE.md mise à jour avec ligne Article VIII | C5 | grep "VIII" CLAUDE.md | Oubli de mise à jour |
| CA-296-07 | Aucun filtre projet dans formal-check.sh. Regex D-296-02 accepte 8 codes | C1 | Même comportement sur backend, app, site, infra | Test exhaustif des 8 projets non réaliste → échantillonnage |
| CA-296-08 | Guard check_all_formal_passed() avant Done (31), DONE_ANOMALY (2), REJECTED (3) | C2, C4 | Transition refusée si FAIL ouvert | Transition Jira hors workflow |
| CA-296-09 | formal-lock.sh : flock (lock), SHA-256 fichier (idempotence) | C3 | Double déclenchement → CONCURRENT_RUN ou replay identique | Race condition sur flock |
| CA-296-10 | timeout 180 dans formal-check.sh (configurable via GOV_FORMAL_TIMEOUT_SEC) | C1 | Dépassement → FAILED_BLOCKING + TIMEOUT | Timeout trop court sur machines lentes |
| CA-296-11 | Validation JSON jq dans formal-check.sh vérifie schéma D-296-14 | C1 | Validation réussie/échouée sur chaque run | Champ optionnel vs obligatoire |
| CA-296-12 | Phase 4 dans gov-check-plan.md : appel formal-check.sh GATE5_PLAN_FORMAL | C4 | Phase visible dans traces avant verdict Gate 5 | Phase sautée si phases 0-3 échouent |
5. Mapping tests (TC-*) → mécanismes + observables¶
5.1 Flux nominaux¶
| Test ID | Référence spec | Mécanisme(s) | Point(s) d'observation | Niveau de test visé |
|---|---|---|---|---|
| TC-NOM-01 | INV-296-02, CA-296-01 | formal-check.sh STEP1_SPEC_COHERENCE + guard dans formal-state.sh | État PENDING→RUNNING→PASSED dans .gov-local.json + résultat JSON archivé | Integration |
| TC-NOM-02 | INV-296-03, CA-296-02 | formal-check.sh STEP4_CONTRACTS_FORMAL + guard | Checkpoint PASSED + éligibilité Gate 5 | Integration |
| TC-NOM-03 | INV-296-04, CA-296-12 | Phase 4 gov-check-plan.md → formal-check.sh GATE5_PLAN_FORMAL | Phase formelle visible dans traces avant verdict | Integration |
| TC-NOM-04 | INV-296-05, CA-296-02 | formal-check.sh STEP6_CODE_FORMAL + guard | Transition step 6→7 autorisée | Integration |
| TC-NOM-05 | INV-296-06, CA-296-02 | formal-check.sh GATE8_FINAL_FORMAL dans gov-accept.md | Soumission Gate 8 autorisée | Integration |
| TC-NOM-06 | CA-296-01 | /coherence au step 0 (existant) + formal-check.sh STEP1_SPEC_COHERENCE au step 1 | Deux correlation_id distincts dans session-log | Integration |
| TC-NOM-07 | INV-296-01, INV-296-10 | Aucun filtre projet dans formal-check.sh + validation regex D-296-02 | 6 checkpoints identiques sur chaque project_code testé | E2E (échantillonnage) |
| TC-NOM-08 | INV-296-11, CA-296-05, CA-296-06 | Contenu CONSTITUTIONAL.md + CLAUDE.md vérifié par grep | Article VIII détectable + référence dans table | Unit (grep) |
| TC-NOM-09 | INV-296-14, CA-296-11 | Validation JSON jq dans formal-check.sh | 12 clés requises + regex conformes | Unit |
| TC-NOM-10 | INV-296-15, CA-296-09 | Fichier idempotence .formal-idempotency/<key> | Même résultat, pas de double exécution | Integration |
| TC-NOM-11 | INV-296-13 | Transition FAILED_BLOCKING→PENDING dans formal-state.sh + relance | Historique conservé + verrous actifs jusqu'à PASSED | Integration |
| TC-NOM-12 | INV-296-15 | formal-lock.sh : scan orphelins + clearing cycles | Orphelin → FAILED_BLOCKING, levée après N cycles PASSED | Integration |
5.2 Cas d'erreur¶
| Test ID | Référence spec | Mécanisme(s) | Point(s) d'observation | Niveau de test visé |
|---|---|---|---|---|
| TC-ERR-01 | D-296-01, INV-296-16 | Validation regex ^PD-[0-9]{1,4}$ dans formal-check.sh | Rejet immédiat, exit 1, pas de RUNNING | Unit |
| TC-ERR-02 | D-296-03, INV-296-16 | Validation regex checkpoint_id dans formal-check.sh | Rejet + FAILED_BLOCKING, audit | Unit |
| TC-ERR-03 | D-296-14, INV-296-14 | Validation jq des clés obligatoires dans résultat | Rejet + INTERNAL_ERROR | Unit |
| TC-ERR-04 | INV-296-07, CA-296-03 | Guard check_formal_passed retourne exit 1 | Step 2 refusé, contradictions dans details[] | Integration |
| TC-ERR-05 | INV-296-07 | Guard check_formal_passed pour STEP4 ou GATE5 | Gate 5 refusée | Integration |
| TC-ERR-06 | INV-296-07 | Guard check_formal_passed pour STEP6 | Step 7 interdit | Integration |
| TC-ERR-07 | INV-296-07 | Guard check_formal_passed pour GATE8 | Gate 8 refusée | Integration |
| TC-ERR-08 | INV-296-08, CA-296-04 | which <tool> échoue dans formal-check.sh | FAIL + TOOL_UNAVAILABLE | Unit |
| TC-ERR-09 | INV-296-08, CA-296-10 | timeout 180 expire dans formal-check.sh | RUNNING→FAILED_BLOCKING + TIMEOUT | Integration |
| TC-ERR-10 | INV-296-15 | flock --nonblock échoue dans formal-lock.sh | FAIL + CONCURRENT_RUN | Unit |
| TC-ERR-11 | INV-296-15 | Fichier idempotence existant + payload différent | Rejet de la relance | Unit |
| TC-ERR-12 | INV-296-17 | Tentative --skip ou variable GOV_FORMAL_SKIP | Rejet (flag non reconnu) | Unit |
| TC-ERR-13 | INV-296-09, CA-296-08 | Guard check_all_formal_passed avant Done/DONE_ANOMALY | Transition refusée | Integration |
5.3 Tests d'invariants (TC-INV-01..04 — résolution Écart 3)¶
TEST-ID: TC-INV-01
Référence spec: INV-296-12
GIVEN
- Le composant formal-state.sh est chargé
- Un checkpoint existe dans .gov-local.json avec un état valide (PENDING)
WHEN
- Une tentative de set_checkpoint_state est faite avec un état invalide ("DONE", "SKIPPED", "WARNING", "N/A")
THEN
- La fonction retourne exit 1 avec message "Invalid state: <state>. Allowed: PENDING, RUNNING, PASSED, FAILED_BLOCKING"
- L'état dans .gov-local.json reste inchangé (PENDING)
AND
- Seuls les 4 états PENDING, RUNNING, PASSED, FAILED_BLOCKING sont acceptés par set_checkpoint_state
- Le test vérifie les 4 états valides (acceptés) ET au moins 5 états invalides (rejetés)
TEST-ID: TC-INV-02
Référence spec: INV-296-13
GIVEN
- formal-state.sh est chargé avec la matrice de transitions ALLOWED_TRANSITIONS
- Un checkpoint est dans l'état RUNNING
WHEN
- Une transition RUNNING → PENDING est tentée (transition interdite §5.4)
THEN
- La fonction retourne exit 1 avec message "Forbidden transition: RUNNING -> PENDING"
- L'état reste RUNNING
AND
- Le test vérifie exhaustivement les 12 transitions (4 autorisées, 8 interdites) de la matrice §5.4
- Chaque transition interdite est rejetée avec message explicite
- Chaque transition autorisée est acceptée
TEST-ID: TC-INV-03
Référence spec: INV-296-17
GIVEN
- formal-check.sh est disponible sur le PATH
- Un checkpoint valide est prêt à être déclenché
WHEN
- L'appel est fait avec des flags de contournement :
a) --skip
b) --warning-only
c) --dry-run
d) --informative
e) Variable d'environnement GOV_FORMAL_SKIP=1
THEN
- Chaque tentative retourne exit 1 avec message "Unknown flag" ou "GOV_FORMAL_SKIP is not supported"
- Aucun contrôle formel n'est exécuté
- Aucun changement d'état ne se produit
AND
- Le code source de formal-check.sh ne contient aucune branche conditionnelle sur ces flags/variables
- grep -c "warning.only\|skip\|dry.run\|informative" scripts/formal-check.sh == 0 (hors commentaires)
TEST-ID: TC-INV-04
Référence spec: INV-296-18
GIVEN
- La matrice de couverture tests §2 (TC-* → INV-* → CA-*)
- Les 18 invariants INV-296-01..18
- Les 12 critères d'acceptation CA-296-01..12
WHEN
- Un audit de couverture est exécuté (script ou vérification manuelle)
THEN
- Chaque INV-296-* est couvert par au moins 1 test TC-*
- Chaque CA-296-* est couvert par au moins 1 test TC-*
- Les tests TC-INV-01..04 (ce document) sont définis avec GIVEN/WHEN/THEN
AND
- Les réserves de testabilité du document tests §9 sont documentées :
- GATE3_SPEC_FORMAL (nouveau checkpoint) : testable via TC-NOM ajouté
- Configurabilité SLA : testable via variable d'environnement GOV_FORMAL_TIMEOUT_SEC
- TC-NOM-07 exhaustif (8 projets) : test par échantillonnage (≥2 projets)
- L'audit est reproductible (script ou checklist vérifiable)
5.4 Tests négatifs et non-régression¶
| Test ID | Référence spec | Mécanisme(s) | Point(s) d'observation | Niveau de test visé |
|---|---|---|---|---|
| TC-NEG-01..12 | D-296-*, INV-296-16 | Validation regex dans formal-check.sh | Rejet + FAILED_BLOCKING pour chaque entrée invalide | Unit |
| TC-NR-01..05 | F-296-*, §5.4 | Replay déterministe, scope guard, historique, no-retry, terminalité | Comportement identique sur replays | Integration |
6. Gestion des erreurs¶
| Code erreur | Cause | Traitement dans formal-check.sh | Observable |
|---|---|---|---|
INPUT_INVALID | Entrée non conforme D-296-01..15 | Rejet immédiat, exit 1, pas de RUNNING | stderr + résultat JSON |
TOOL_UNAVAILABLE | which swipl/claude/alloy échoue | FAIL immédiat, failure_code=TOOL_UNAVAILABLE | résultat JSON + stderr |
TIMEOUT | timeout $GOV_FORMAL_TIMEOUT_SEC expire | Kill process, failure_code=TIMEOUT | résultat JSON + stderr |
CONCURRENT_RUN | flock --nonblock échoue | FAIL immédiat, failure_code=CONCURRENT_RUN | résultat JSON |
INTERNAL_ERROR | Sortie outil non parseable, erreur jq, état incohérent | FAIL, failure_code=INTERNAL_ERROR | résultat JSON + stderr |
CONTRADICTION_DETECTED | /coherence détecte contradiction(s) | FAIL, contradictions dans details[] | résultat JSON |
MODEL_VIOLATION | /formal-verify détecte violation de modèle | FAIL, violations dans details[] | résultat JSON |
NONE | Succès (GO) | PASSED, failure_code=NONE | résultat JSON |
Mapping erreur → flux de reprise¶
| failure_code | Reprise possible | Action utilisateur |
|---|---|---|
CONTRADICTION_DETECTED | Oui | Corriger l'artefact, relancer le checkpoint |
MODEL_VIOLATION | Oui | Corriger l'artefact, relancer le checkpoint |
TOOL_UNAVAILABLE | Oui (après install) | Installer l'outil manquant, relancer |
TIMEOUT | Oui | Augmenter GOV_FORMAL_TIMEOUT_SEC ou optimiser l'artefact |
CONCURRENT_RUN | Oui (attendre) | Attendre la fin du run concurrent |
INPUT_INVALID | Oui | Corriger les paramètres d'appel |
INTERNAL_ERROR | Selon le cas | Investiguer stderr, corriger ou ESCALADE |
7. Impacts sécurité¶
| Risque | Mitigation | Observable |
|---|---|---|
Contournement via DONE (with anomaly) (Écart 5) | Guard check_all_formal_passed couvre Done (31), DONE_ANOMALY (2) ET REJECTED (3) | Transition refusée pour les 3 |
| Contournement via mode « force » gate (Écart 12) | Article VIII interdit explicitement tout override/force pour les échecs formels. Le guard dans formal-state.sh ne vérifie pas de flag force — il est binaire (PASSED ou bloqué) | Aucune branche « force » dans le code |
Injection dans artifact_path | Validation regex D-296-10 : chemin POSIX absolu sous ProbatioVault-*. Résolution via GOV_WORKSPACE_ROOT (résolution Écart 6 — voir §8 H-PLAN-02) | Path hors racine → rejet |
| Lock stale (crash pendant RUNNING) | Réconciliation : scan fichiers .formal-state-* > orphan_threshold_sec → FAILED_BLOCKING | Orphelin converti dans logs |
| Audit trail manipulation | Résultats formels écrits en append-only dans .gov-local.json (clé formal_audit[]). Pas de suppression possible via l'API formal-state.sh | Historique complet dans le JSON |
8. Hypothèses techniques¶
| ID | Hypothèse | Impact si faux |
|---|---|---|
| H-PLAN-01 | Les sorties /coherence (JSON {blocking, issues}) et /formal-verify (exit code 0/½/3) sont mappables sans ambiguïté vers verdict=GO\|FAIL via les règles de §2.1 étape g. Le mapping STOP → FAIL est explicite dans le code. | Adaptateur à réécrire si le format de sortie change. |
| H-PLAN-02 | artifact_path utilise une variable GOV_WORKSPACE_ROOT (défaut /Users/loic/Developpement/ProbatioVault) au lieu d'un chemin hardcodé. La regex D-296-10 est construite dynamiquement : ^${GOV_WORKSPACE_ROOT}/ProbatioVault-[a-z-]+/.+$. Résout Écart 6. | Si la variable n'est pas définie, fallback sur le chemin par défaut (comportement actuel). |
| H-PLAN-03 | Le lock distribué utilise flock (POSIX, atomique sur filesystem local). Suffisant pour le contexte d'exécution (même machine, sessions cmux concurrentes). Résout Écart 11. | Si exécution sur machines différentes → nécessiterait Redis/SQLite. Non applicable pour PD-296. |
| H-PLAN-04 | L'idempotency_key est un SHA-256 du payload canonique : sha256("${story_id}|${project_code}|${checkpoint_id}|${trigger_event}|$(sha256sum ${artifact_path})"). Résout Écart 8. | Si l'artefact est modifié entre deux appels → clé différente → nouveau run (comportement attendu). |
| H-PLAN-05 | L'état « dégradé » (clearing conditionnel) est défini comme : un checkpoint ayant été FAILED_BLOCKING au moins une fois dans la session courante. Le scope est par story_id + checkpoint_id. Le compteur clearing_success_cycles est stocké dans .gov-local.json. Résout Écart 7. | Si le scope est trop fin (par checkpoint), risque de clearing prématuré. |
| H-PLAN-06 | La transition PASSED → PENDING est autorisée uniquement via le flag --force-recheck dans formal-check.sh. Sans ce flag, PASSED est terminal. Résout Écart 4. | Si un skill oublie le flag après correction → le checkpoint obsolète n'est pas relancé. Les skills doivent être modifiés pour passer --force-recheck systématiquement lors des boucles de correction gate. |
| H-PLAN-07 | Les formats d'entrée des outils formels (/coherence, /formal-verify) ne sont pas contractualisés par PD-296 (hors périmètre — Écart 9). PD-296 contractualise uniquement le format de sortie (D-296-14). L'interface d'entrée est celle documentée dans les skills existants. | Si les skills formels changent leur interface → formal-check.sh à adapter. |
| H-PLAN-08 | Le checkpoint GATE3_SPEC_FORMAL (nouveau, résolution Écart 1) invoque /formal-verify avec scope formal_spec pour vérifier l'alignement spec↔tests↔invariants avant Gate 3. Distinct de STEP1_SPEC_COHERENCE qui ne vérifie que la cohérence inter-EB. | Si le périmètre de vérification Gate 3 n'est pas défini → vérification redondante avec STEP1. |
| H-PLAN-09 | Le rate-limit (Écart 14) produit un rejet simple (pas de FAILED_BLOCKING) : le checkpoint reste dans son état actuel, la requête est refusée avec message. Ceci évite qu'un pic de requêtes bloque artificiellement le workflow. | Si FAILED_BLOCKING est attendu → modifier le comportement. |
9. Points de vigilance (risques, dette, pièges)¶
9.1 Risques d'implémentation¶
| Risque | Probabilité | Impact | Mitigation |
|---|---|---|---|
Timeout 180s insuffisant sur formal_full (GATE8) | Moyenne | FAILED_BLOCKING systématique | Variable GOV_FORMAL_TIMEOUT_SEC configurable. Documenter dans CLAUDE.md. |
Race condition sur .gov-local.json (deux agents écrivent simultanément) | Faible | État incohérent | formal-state.sh utilise flock sur le fichier JSON avant chaque écriture. |
Oubli d'appel --force-recheck dans un skill après correction | Moyenne | Checkpoint PASSED obsolète non relancé | Convention documentée : toute boucle de correction gate DOIT appeler formal-check avec --force-recheck pour les checkpoints concernés. |
claude -p échoue silencieusement (subprocess) | Faible | TOOL_UNAVAILABLE non détecté | Vérifier exit code ET taille stdout > 0. |
9.2 Dette technique acceptée¶
| Dette | Justification | Ticket futur |
|---|---|---|
| Pas de base de données pour l'idempotence (fichiers flat) | Suffisant pour exécution locale mono-machine. Refactoring si LOTR multi-machine. | — |
| Pas de dashboard temps réel des checkpoints formels | Les traces dans .gov-local.json sont lisibles. Dashboard si volume > 10 stories/semaine. | — |
| TC-NOM-07 par échantillonnage (pas 8 projets exhaustifs) | 8 workflows complets = ~16h. Échantillonner ≥2 projets (backend + un autre). | — |
9.3 Pièges connus¶
unset CLAUDECODE: Tout appelclaude -pdansformal-check.shDOIT être préfixé parunset CLAUDECODE &&(convention existante).- Encoding UTF-8 : Les
details[]peuvent contenir des caractères non-ASCII (noms de variables français). Le JSON doit être UTF-8. - Correction multi-checkpoint : Si Gate 8 échoue → le code est corrigé → il faut relancer
STEP6_CODE_FORMALETGATE8_FINAL_FORMAL(pas seulement le dernier). Les skills doivent gérer cette cascade.
10. Hors périmètre¶
| Élément | Raison |
|---|---|
| Création d'un nouveau modèle formel | Explicitement exclu par la spec §2 |
Refonte de ProbatioVault-formal | Explicitement exclu |
| Optimisation de performance des moteurs formels | Explicitement exclu |
Changement du job CI GitLab tla-verify | Explicitement exclu |
| Contractualisation des formats d'entrée des outils formels (Écart 9) | Hors périmètre — les skills existants documentent leur interface |
| Dashboard temps réel des checkpoints formels | Dette acceptée (§9.2) |
| Statut Jira dédié aux échecs formels (Q-296-07) | Réutilisation des statuts existants — le blocage est géré localement par les guards |
Périmètre de test¶
| Niveau de test | In scope | Hors scope (justification) |
|---|---|---|
| Unitaire | Validation entrées (regex D-296-*), FSM transitions, états autorisés/interdits, parsing JSON, lock/idempotence | — |
| Intégration | Flux complet checkpoint (PENDING→RUNNING→PASSED/FAILED_BLOCKING), guards de blocage, interaction skills↔formal-check.sh, correction+relance | — |
| E2E | Workflow /gov complet sur 1 story avec les 6 checkpoints | Workflow sur les 8 projets (échantillonnage ≥2 — justification : 16h de workflow exhaustif) |
Tous les niveaux de test sont couverts. Exclusion partielle E2E justifiée par le coût d'exécution.
Mécanismes cross-module¶
| Élément | Détail |
|---|---|
| Commandes à protéger | /gov-step (steps 1, 4, 6), /gov-check-plan (Phase 4), /gov-accept (post Phase 2), /gov-gate (G3, G5, G8) |
| Guard formel | Fonction check_formal_passed() dans formal-state.sh, appelée avant chaque transition aval |
| Données cross-module | story_id, checkpoint_id, état courant .gov-local.json, artefact(s) source |
| Résolution de liaison | Mapping déterministe step/gate → checkpoint_id (6 mappings) |
| Scope d'enregistrement | Local aux commandes listées — pas de guard global hors workflow /gov |
| Exceptions | Aucune. Article VIII interdit tout bypass y compris mode « force » gate |
Les 6 checkpoints formels (résolution Écarts 1 + 2)¶
| # | Checkpoint ID | Déclencheur | Scope | Outil | Bloque |
|---|---|---|---|---|---|
| 1 | STEP1_SPEC_COHERENCE | SPEC_SAVED | coherence_spec | /coherence check | Step 2 |
| 2 | GATE3_SPEC_FORMAL | GATE3_REVIEW_START | formal_spec | /formal-verify | Verdict Gate 3 |
| 3 | STEP4_CONTRACTS_FORMAL | PLAN_AND_CONTRACTS_SAVED | formal_contracts | /formal-verify | Gate 5 |
| 4 | GATE5_PLAN_FORMAL | CHECK_PLAN_PHASE4_START | formal_plan | /formal-verify | Verdict Gate 5 |
| 5 | STEP6_CODE_FORMAL | STEP6C_DONE | formal_code | /formal-verify | Step 7 |
| 6 | GATE8_FINAL_FORMAL | GOV_ACCEPT_PHASE2_DONE | formal_full | /formal-verify | Gate 8 |
Bornes corrigées : mandatory_checkpoints_per_story = 6, mandatory_gates_formal = 3 (Gate 3, Gate 5, Gate 8 — tous couverts).