Aller au contenu

PD-293 — Specification Review

Auditeur : ChatGPT (OpenCode, agent factual) Documents audités : PD-293-specification.md, PD-293-tests.md Date : 2026-03-30


Écarts identifiés

ÉCART-01

Type : Ambiguïté Référence : Spec §5.1 — story_id regex ^PD-[0-9]{1,4}$ vs Spec §5.1 — story_id format "5–9 chars" Description : La regex autorise PD-1 (4 chars) qui est inférieur au minimum déclaré de 5 chars. La contrainte de longueur "5–9 chars" et la regex ^PD-[0-9]{1,4}$ sont incohérentes : PD-1 matche la regex (4 chars) mais viole la longueur minimale. Inversement, PD-1234 fait 7 chars (dans la plage) et matche la regex. Il faut déterminer lequel prime. Impact : Un validateur implémenté sur la regex accepterait PD-1 (4 chars), tandis qu'un validateur sur la longueur le rejetterait. Comportement non déterministe selon l'ordre des vérifications. Gravité : Majeur


ÉCART-02

Type : Ambiguïté Référence : Spec §5.3 flux 1 — start : "création d'un Ringbearer si quota disponible et story_id non déjà actif" vs Spec §5.1 — idempotency_key Description : Le flux start mentionne un rejet si story_id est déjà actif (ERR-293-02). Séparément, INV-293-12 impose l'idempotence par idempotency_key. La spec ne précise pas la sémantique d'un start avec une même story_id mais une clé d'idempotence différente vs un start avec même story_id et même clé. TC-ERR-02 teste "cle differente" → rejet, mais le mécanisme de décision (vérifie-t-on story_id en premier ou idempotency_key en premier ?) n'est pas contractualisé. Impact : Ambiguïté sur la priorité de validation : un implémenteur pourrait traiter le doublon story_id comme un cas d'idempotence (retour silencieux) au lieu d'un rejet explicite. Gravité : Mineur (TC-ERR-02 couvre le cas, mais la spec devrait expliciter l'ordre de précédence)


ÉCART-03

Type : Ambiguïté Référence : Spec §5.1 — peer_id : "UTF-8 opaque, 1–128 chars, printable UTF-8, non vide" Description : Le champ peer_id est déclaré "opaque" mais avec une contrainte "printable UTF-8" non définie formellement. "Printable" peut exclure ou inclure les caractères de contrôle, les emojis, les espaces, les null bytes, etc. L'absence de regex ou de définition Unicode précise (ex: [\x20-\x7E] ou \P{Cc}) rend la validation non déterministe. Impact : Deux implémentations pourraient diverger sur ce qu'elles acceptent. Le document de tests (§9 Règles non testables) reconnaît ce point mais le classe "Mineur" alors qu'un peer_id contenant un null byte ou un caractère de contrôle pourrait causer des problèmes d'injection dans les logs ou l'affichage. Gravité : Mineur


ÉCART-04

Type : Contradiction Référence : Spec §5.1 — story_id sensibilité casse "case-sensitive" + regex ^PD-[0-9]{1,4}$ vs Tests TC-NEG-01 — pd-293 (mauvaise casse) → rejet Description : La spec déclare story_id comme "case-sensitive" avec regex ^PD-[0-9]{1,4}$. Le test TC-NEG-01 vérifie le rejet de pd-293. Ceci est cohérent. Cependant, la spec ne précise pas explicitement que le PD- est un préfixe majuscule obligatoire — c'est implicite dans la regex. Si la regex est la source de vérité, alors "case-sensitive" est redondant ; si "case-sensitive" est la source de vérité, la regex devrait être marquée comme normative et non illustrative. Impact : Risque faible, mais la double source de vérité (propriété textuelle vs regex) peut créer une ambiguïté terminologique au sens du REX PD-250. Gravité : Mineur


ÉCART-05

Type : Hypothèse dangereuse Référence : Spec §5.2 — transition STARTING→RUNNING : "peer détecté avant timeout" + §5.1 start_detection_timeout : 30s Description : La transition STARTING→RUNNING repose sur la détection d'un peer enregistré dans le broker. Le mécanisme de détection n'est pas spécifié : est-ce un poll périodique (à quelle fréquence ?), un webhook, un watch ? Le peer_poll_interval (1s) est défini mais la spec ne contractualise pas explicitement que c'est ce paramètre qui gouverne la détection de démarrage. Si le poll est à 1s et le timeout à 30s, il y a potentiellement 30 polls — mais si le premier poll rate (broker latence), le mécanisme de retry/backoff n'est pas spécifié. Impact : Un Ringbearer qui s'enregistre à t=29s pourrait être détecté à t=30s (race condition) et être classé START_FAILED alors qu'il est fonctionnel. Gravité : Majeur


ÉCART-06

Type : Incohérence Spec↔Tests Référence : Spec §5.1 — SLA escalade_wait_ttl : 72h par défaut + comportement "ESCALADE_EXPIRED" vs Tests — aucun test couvrant ESCALADE_EXPIRED Description : La spec définit un SLA escalade_wait_ttl (72h défaut) dont l'expiration produit un état ESCALADE_EXPIRED nécessitant décision Sovereign. Or cet état n'apparaît pas dans la machine d'états §5.2 (les 8 états listés ne contiennent pas ESCALADE_EXPIRED). De plus, aucun test ne couvre cette transition. C'est à la fois une contradiction interne (état référencé mais non défini) et un trou de couverture. Impact : L'état ESCALADE_EXPIRED est un état fantôme : mentionné dans les SLA mais absent de la FSM. Un implémenteur ne sait pas s'il s'agit d'un sous-état de ESCALADED, d'un nouvel état, ou d'une alerte sans changement d'état. Gravité : Bloquant


ÉCART-07

Type : Non testable Référence : Spec INV-293-01 — "toute demande hors scope est refusée avec message explicite" Description : Le périmètre "hors scope" est défini par la liste d'exclusion §2 (lecture code, écriture fichier, build, tests, Jira, GitLab, Vault). Cette liste est fermée mais potentiellement incomplète. Un agent LLM peut recevoir des demandes arbitraires (ex: "envoie un email", "connecte-toi à Slack"). La spec ne précise pas si le One Ring utilise une allow-list (seules les commandes /gov-lord sont acceptées) ou une deny-list (les actions listées sont interdites, le reste est permis). Sans cette précision, l'invariant INV-293-01 n'est testable que pour les cas explicitement listés. Impact : Un testeur ne peut pas affirmer la conformité pour des demandes non prévues dans la deny-list. L'approche allow-list serait testable ; l'approche deny-list ne l'est pas exhaustivement. Gravité : Majeur


ÉCART-08

Type : Hypothèse dangereuse Référence : Spec §5.1 — idempotency_ttl : 24h + idempotency_key unicité dans "fenêtre de déduplication" Description : La spec ne précise pas est stocké le registre de déduplication (mémoire du One Ring, fichier local, broker ?). Si le One Ring crash et redémarre, le registre de déduplication est-il persisté ? Si non, l'idempotence est perdue après un crash, ce qui viole INV-293-12. De plus, avec un TTL de 24h et potentiellement des centaines de commandes, la croissance mémoire n'est pas bornée. Impact : L'idempotence n'est garantie que dans la session courante du One Ring si le registre est en mémoire. Un crash + restart du One Ring réinitialise le registre, permettant des double-exécutions. Gravité : Majeur


ÉCART-09

Type : Incohérence Spec↔Tests Référence : Spec §5.2 — transition STARTING→START_FAILED : "timeout démarrage" vs Tests — aucun test dédié à START_FAILED Description : L'état START_FAILED est défini dans la FSM et couvert par INV-293-10 (terminal), mais aucun test nominal ne vérifie la transition entrante STARTING→START_FAILED. TC-INV-02 vérifie que START_FAILED est terminal (pas de sortie), mais aucun test ne provoque le timeout de démarrage pour vérifier que la transition est effectivement déclenchée. Impact : Le chemin STARTING→START_FAILED n'est jamais exercé dans les scénarios de test. Un implémenteur pourrait omettre cette transition sans être détecté par la suite de tests. Gravité : Majeur


ÉCART-10

Type : Ambiguïté Référence : Spec §5.3 flux 2 — status : "consolidation de l'état de tous les Ringbearers observés/attendus" Description : La distinction entre "observés" et "attendus" n'est pas définie. Un Ringbearer STARTING qui n'a pas encore de peer est-il "attendu" ? Un Ringbearer CRASHED dont le peer a disparu est-il "observé" ? Le contenu exact de la vue consolidée (quels champs, quel format) n'est pas spécifié au-delà du CA-01 qui mentionne "liste consolidée". Impact : L'implémenteur doit deviner le format de sortie de la commande status. Deux implémentations pourraient produire des formats incompatibles. Gravité : Mineur


ÉCART-11

Type : Contradiction Référence : Spec §5.2 — diagramme d'état Mermaid vs Spec §5.1 — SLA pause_ttl Description : Le SLA pause_ttl (24h défaut) spécifie un comportement à expiration : "PAUSED persistant + alerte ; aucune reprise automatique". Cela implique que rien ne change d'état à l'expiration du pause_ttl — l'état reste PAUSED. Or si l'état ne change pas, le SLA n'a aucun effet observable autre qu'une alerte. La spec ne contractualise pas le contenu ni la destination de cette alerte (Sovereign ? log ? les deux ?). Comparé à escalade_wait_ttl qui produit un changement d'état (ESCALADE_EXPIRED), le traitement est incohérent. Impact : Le pause_ttl est une alerte "fire and forget" sans effet contractuel vérifiable, contrairement aux autres SLA qui ont des conséquences d'état. Gravité : Mineur


ÉCART-12

Type : Risque sécu/conformité Référence : Spec §5.1 — escalade_text : "1–2000 chars, UTF-8" + Spec diagramme séquence — send_message{type=PO_RESPONSE, escalade_text=<réponse>} Description : Le champ escalade_text transporte du texte libre UTF-8 entre Sovereign, One Ring et Ringbearer. Aucune contrainte de sanitisation n'est spécifiée. Dans un contexte Claude Code / LLM, un escalade_text malformé pourrait contenir des instructions d'injection de prompt (prompt injection via la réponse PO). Le One Ring transmet ce texte au Ringbearer qui l'injecte dans son contexte LLM. Impact : Un texte d'escalade contenant des instructions adversariales pourrait manipuler le Ringbearer. Risque atténué par le fait que le Sovereign est l'humain de confiance, mais un copier-coller d'un texte externe non sanitisé reste un vecteur. Gravité : Mineur (le Sovereign est trust boundary, mais le risque de copier-coller accidentel existe)


ÉCART-13

Type : Cohérence diagramme Référence : Spec §5bis — diagramme de séquence vs Spec §5.1 — modèle de données Description : Le diagramme de séquence montre O->>O: Parse commande -> CommandEnvelope{story_id, project_code, idempotency_key}. Le type CommandEnvelope n'est défini nulle part dans le modèle de données §5.1. Les champs story_id, project_code et idempotency_key sont définis individuellement mais pas leur agrégation en structure. De même, le diagramme ne montre pas la validation de schéma (INV-293-05) comme étape explicite. Impact : Le diagramme introduit un concept (CommandEnvelope) absent de la spécification textuelle. Un implémenteur pourrait créer cette structure ou non selon qu'il suit le diagramme ou la spec textuelle. Gravité : Mineur


ÉCART-14

Type : Cohérence diagramme Référence : Spec §5bis — diagramme d'état vs Spec §5.1 — SLA escalade_wait_ttlESCALADE_EXPIRED Description : Le diagramme d'état ne contient pas l'état ESCALADE_EXPIRED mentionné dans les SLA temporels §5.1. Si ESCALADE_EXPIRED est un état de la FSM, il manque dans le diagramme. S'il n'est pas un état mais un signal/alerte, la terminologie "État story ESCALADE_EXPIRED" est trompeuse. Impact : Incohérence directe entre le diagramme Mermaid (8 états) et la spécification textuelle (9ème état implicite). Voir ÉCART-06. Gravité : Bloquant (renforce ÉCART-06)


ÉCART-15

Type : Incohérence Spec↔Tests Référence : Tests TC-NEG-10 — "Tentative respond avec clé idempotence réutilisée mais payload différent → Rejet déterministe de conflit" vs Spec INV-293-12 Description : INV-293-12 spécifie l'idempotence "par clé dédiée" et que le "doublon [est] ignoré (réponse déterministe)". Le test TC-NEG-10 teste le cas d'un même clé mais payload différent et attend un "rejet de conflit". Or la spec ne contractualise pas ce cas précis : que se passe-t-il quand la même idempotency_key est envoyée avec un payload différent ? La spec dit "doublon ignoré", pas "conflit détecté". Le test invente un comportement non spécifié. Impact : Le test TC-NEG-10 suppose une sémantique de conflit d'idempotence qui n'est pas dans la spec. Un implémenteur qui retourne simplement le résultat original (ignorant le nouveau payload) serait conforme à la spec mais échouerait au test. Gravité : Majeur


ÉCART-16

Type : Hypothèse dangereuse Référence : Spec H-293-04 — "Le protocole messages requis est supporté par les peers sans adaptation de /gov" Description : La spec suppose que /gov (le workflow existant) enverra des messages typés (STATUS_UPDATE, ESCALADE, WORKFLOW_DONE) au One Ring via le broker sans modification. Or INV-293-07 stipule que /gov n'est pas modifié. Contradiction potentielle : soit /gov sait déjà envoyer ces types de messages (non documenté), soit il faudra le modifier (violation INV-293-07), soit un adaptateur intermédiaire est nécessaire (non spécifié). Impact : Si /gov ne sait pas envoyer de messages typés au broker, l'architecture entière est non fonctionnelle. La question Q-293-04 couvre partiellement ce point mais sans le qualifier comme risque bloquant. Gravité : Majeur


ÉCART-17

Type : Ambiguïté Référence : Spec §6 ERR-293-06 — "Broker indisponible → État dégradé + alerte Sovereign" vs Spec §5.2 — machine d'états Description : Le "mode dégradé" en cas d'indisponibilité du broker n'est pas défini dans la machine d'états. Est-ce un état des Ringbearers ? Du One Ring ? Les Ringbearers continuent-ils leur exécution /gov en autonome (car le broker est un canal de communication, pas un prérequis d'exécution) ? Le One Ring peut-il émettre des commandes pendant le mode dégradé ? TC-ERR-06 teste ce cas mais le résultat attendu ("passage en mode dégradé explicite") n'est pas spécifié au-delà de l'alerte. Impact : Le comportement du système avec un broker down est non déterministe. Les Ringbearers pourraient continuer à avancer sans que le One Ring puisse les observer, créant un split-brain. Gravité : Majeur


Synthèse

Gravité Nombre IDs
Bloquant 2 ÉCART-06, ÉCART-14
Majeur 7 ÉCART-01, ÉCART-05, ÉCART-07, ÉCART-08, ÉCART-09, ÉCART-15, ÉCART-16, ÉCART-17
Mineur 6 ÉCART-02, ÉCART-03, ÉCART-04, ÉCART-10, ÉCART-11, ÉCART-12, ÉCART-13

Bloquants : L'état ESCALADE_EXPIRED est référencé dans les SLA §5.1 mais absent de la machine d'états §5.2 et du diagramme Mermaid §5bis. Cet état fantôme doit être soit ajouté à la FSM avec ses transitions, soit reformulé comme un signal/alerte sans changement d'état.

Majeurs critiques : La race condition sur la détection de démarrage (ÉCART-05), l'absence de persistance du registre d'idempotence (ÉCART-08), l'incompatibilité potentielle /gov ↔ protocole messages (ÉCART-16), et le comportement non défini en mode dégradé broker (ÉCART-17) représentent des risques architecturaux significatifs.