Aller au contenu

PD-293 - Plan d'implementation : Revue

1. References

  • Specification : PD-293-specification.md
  • Tests contractuels : PD-293-tests.md
  • Plan d'implementation : PD-293-plan.md
  • Date de revue : 2026-03-30
  • Reviewer : OpenCode (auditeur technique independant)

2. Constatations (ecarts)

Type Reference (Spec/Test/Plan) Description Impact Gravite (BLOQUANT/MAJEUR/MINEUR)
Non-conformite Spec Spec §4, §5.1, §5.2, §5.3, §5.4, §7 / Plan PD-293-plan.md Le document de plan fourni ne contient pas le plan d'implementation detaille (seulement un resume en 12 lignes), donc les mecanismes contractuels ne sont pas auditablement decrits. La conformite stricte a la specification ne peut pas etre etablie sur base documentaire opposable. BLOQUANT
Couverture manquante Invariants INV-293-01..INV-293-14 / Plan PD-293-plan.md Les mappings invariants -> mecanismes sont annonces mais non presentes dans l'artefact plan fourni. Impossibilite de verifier que chaque invariant est non contournable par design. BLOQUANT
Test irrealisable CA-02, TC-NOM-14 (mentionne dans plan) / Tests PD-293-tests.md Le plan annonce "TC-NOM-14 ajoute", mais ce test n'existe pas dans le document de tests contractuels fourni. La preuve contractuelle du demarrage nominal CA-02 reste non etablissable de facon deterministe. BLOQUANT
Test irrealisable TC-NOM-01..TC-ERR-11, TC-INV-01..03, TC-MAN-01 / Plan PD-293-plan.md Aucun point d'observabilite explicite par scenario de test n'est present dans l'artefact plan fourni. Les scenarios ne sont pas executables/auditables a partir du plan comme document contractuel. BLOQUANT
Hypothese implicite Spec §5bis (diagrammes) / Plan PD-293-plan.md + Code contracts PD-293-code-contracts.yaml Le plan mentionne des diagrammes Mermaid (waves + sequence), mais ces diagrammes ne sont pas fournis dans l'artefact plan; la coherence "dependances agents vs contracts" et "sequence vs mecanismes" n'est pas verifiable. Rupture de tracabilite formelle sur l'axe 5bis; risque d'interpretations divergentes en implementation. MAJEUR
Code Contract - Invariant Code contracts module tests / Spec §4 (invariants) Le contract tests introduit l'invariant "Couverture >= 80% du perimetre in scope", qui n'est pas un invariant de la specification canonique. Le jeu d'invariants des contracts n'est pas strictement un sous-ensemble de la spec. MAJEUR
Code Contract - Coherence Code contracts module logger / Spec §6.1 Le contract logger impose une convention additionnelle (peer_id='sovereign', message_type='COMMAND') non definie dans la specification canonique. Comportement contractuel ajoute hors spec, source potentielle de contestation en audit tiers. MAJEUR
Risque secu/conformite Spec §5.4 + conventions depot (etat local) / Code contracts module state-store Le contract state-store impose l'ecriture dans .gov-lord-state.json sans localisation explicite de stockage (epic vs racine). Risque de non-conformite de tracabilite locale et d'exposition de donnees hors perimetre documentaire attendu. MAJEUR
Contrainte technique non documentee Plan PD-293-plan.md (section "Contraintes techniques" absente) Les dependances inter-PD et leur statut (DONE/TODO/STUB) ne sont pas documentees. Visibilite de sequence de livraison incomplete. MINEUR
Contrainte technique non documentee Plan PD-293-plan.md (section "Contraintes techniques" absente) Le framework de test Jest/Vitest n'est pas explicite; le code contract mentionne bats-core, sans decision contractuelle explicite au niveau plan. Ambiguite de runner de test au regard de la contrainte documentaire demandee. MAJEUR
Contrainte technique non documentee Plan PD-293-plan.md (section "Contraintes techniques" absente) La compatibilite ESM/CJS et l'impact de la dependance claude-peers-mcp@^1.x ne sont pas documentes dans le plan. Risque de non-reproductibilite CI et d'echec de tests/exec selon runtime. MAJEUR
Contrainte technique non documentee Plan PD-293-plan.md (section "Contraintes techniques" absente) Les variables CI requises pour les tests d'integration ne sont pas documentees. Ambiguite d'execution en CI/CD. MINEUR

3. Synthese

  • Nombre d'ecarts par gravite : BLOQUANT=4, MAJEUR=6, MINEUR=2.
  • Points critiques : artefact plan non auditable (contenu manquant), couverture invariants non prouvable, executabilite des tests non demontree, ecart CA-02/TC-NOM-14 non resolu dans les tests fournis.

4. Verdict de la revue

  • Statut : ⛔ Rejete
  • Motif synthetique : Le plan fourni ne permet pas de demontrer la conformite contractuelle ni la realisabilite des tests sur preuves documentaires opposables; plusieurs ecarts bloquants subsistent sur couverture et auditabilite.

DOCUMENT COMPLÉMENTAIRE : CODE CONTRACTS

code_contracts:
  - module: state-machine
    owner_agent: agent-fsm
    interfaces:
      - transition(from_state, to_state) -> OK | REJECTED(reason)
      - is_terminal(state) -> boolean
      - get_allowed_transitions(state) -> list
    invariants:
      - "INV-293-09 : Seules les transitions listees en §5.2 sont autorisees"
      - "INV-293-10 : DONE, ABORTED, CRASHED, START_FAILED sont terminaux  aucune transition sortante"
      - "8 etats contractuels : STARTING, RUNNING, ESCALADED, PAUSED, DONE, ABORTED, CRASHED, START_FAILED"
    forbidden:
      - "Ajouter un etat non liste en §5.2 (ESCALADE_PENDING, ESCALADE_EXPIRED interdits en v1)"
      - "Accepter une transition non autorisee silencieusement (toujours rejeter + logguer)"
      - "Modifier les etats terminaux (is_terminal hardcode, pas configurable)"
    architectural_decisions:
      - decision: "Matrice de transitions en tableau associatif Bash (declare -A)"
        rationale: "Structure declarative lisible, O(1) lookup, coherent avec §5.2 sans logique conditionnelle."
        alternatives_considered:
          - "Case/switch imbriques (verbose, risque d'oubli de transition)"
          - "Fichier YAML externe parse par jq (overhead I/O pour chaque transition)"
        trade_offs:
          - "Avantage : deterministe, exhaustif, testable unitairement"
          - "Inconvenient : limite aux types Bash (string keys uniquement)"
    files:
      - "scripts/lib/lord-fsm.sh"

  - module: validator
    owner_agent: agent-validator
    interfaces:
      - validate_story_id(value) -> OK | ERROR(reason)
      - validate_project_code(value) -> OK | ERROR(reason)
      - validate_message_type(value) -> OK | ERROR(reason)
      - validate_timestamp(value) -> OK | ERROR(reason)
      - validate_peer_id(value) -> OK | ERROR(reason)
      - validate_escalade_text(value) -> OK | ERROR(reason)
      - validate_idempotency_key(value) -> OK | ERROR(reason)
      - validate_message(json_payload) -> OK | ERROR(reason)
    invariants:
      - "INV-293-05 : Tout message invalide est rejete de facon deterministe avec raison explicite"
      - "Regex story_id : ^PD-[0-9]{1,4}$ (source de verite unique, case-sensitive)"
      - "Timestamps : seuls Z et +00:00 acceptes (strictement UTC, decision plan §9.9)"
    forbidden:
      - "Accepter un message partiellement valide (tout-ou-rien)"
      - "Utiliser eval ou interpolation directe des valeurs validees dans du shell"
      - "Accepter des offsets non-UTC dans les timestamps (ex: +02:00)"
    architectural_decisions:
      - decision: "Fonctions de validation par champ avec regex Bash natif ([[ =~ ]])"
        rationale: "Zero dependance externe, performance native, coherent avec la stack shell du projet."
        alternatives_considered:
          - "jq pour validation JSON schema (necessite jq installe, overhead par appel)"
          - "Python script de validation (overhead subprocess pour chaque message)"
        trade_offs:
          - "Avantage : zero dependance, rapide, testable isolement"
          - "Inconvenient : regex complexes moins lisibles en Bash qu'en Python"
    files:
      - "scripts/lib/lord-validator.sh"

  - module: logger
    owner_agent: agent-logger
    interfaces:
      - log_event(event_type, fields...) -> void
      - log_rejection(peer_id, message_type, reason, story_id?) -> void
      - log_transition(story_id, from_state, to_state) -> void
      - log_alert(severity, message) -> void
    invariants:
      - "Format JSONL §6.1 : champs obligatoires timestamp, peer_id, message_type, reason pour les rejets"
      - "Timestamps RFC3339 UTC (suffixe Z)"
      - "Convention rejets commande Sovereign : peer_id='sovereign', message_type='COMMAND' (decision plan H-PLAN-02)"
    forbidden:
      - "Ecrire des logs dans un format autre que JSONL"
      - "Omettre un champ obligatoire dans un log de rejet"
      - "Logger le contenu complet de escalade_text (risque RGPD)  logger uniquement le motif de rejet et l'escalade_id"
    architectural_decisions:
      - decision: "Convention peer_id='sovereign' et message_type='COMMAND' pour les rejets CLI"
        rationale: "§6.1 impose peer_id et message_type pour tout rejet, mais ces champs ne sont pas definis pour les commandes Sovereign. Convention deterministe documentee."
        alternatives_considered:
          - "Omettre les champs (viole §6.1)"
          - "Utiliser des valeurs vides (non conforme minLength)"
        trade_offs:
          - "Avantage : conforme §6.1, testable, coherent"
          - "Inconvenient : valeur non canonique, a documenter pour auditeurs"
    files:
      - "scripts/lib/lord-logger.sh"

  - module: state-store
    owner_agent: agent-state
    interfaces:
      - load_state() -> GovLordState
      - save_state(state) -> void
      - count_active_stories() -> int
      - add_story(story_id, project_code) -> void
      - update_story_state(story_id, new_state) -> void
      - update_story_peer(story_id, peer_id, last_seen_at) -> void
      - enqueue_escalade(story_id, escalade_id, text, timestamp) -> void
      - dequeue_escalade(story_id) -> escalade | null
      - check_idempotency(key, payload_hash) -> HIT(result) | CONFLICT | NEW
      - store_idempotency(key, command, story_id, payload_hash, result_hash) -> void
      - purge_expired_idempotency() -> int
    invariants:
      - "INV-293-13 : escalade_queue est FIFO  dequeue retourne la plus ancienne OPEN de la story"
      - "INV-293-12 : idempotency_cache indexe par key, hash SHA-256 du payload, TTL 24h"
      - "Schema §5.4 respecte pour .gov-lord-state.json (version, updated_at, stories, escalade_queue, idempotency_cache)"
      - "Localisation : fichier dans le dossier epic (docs/epics/tooling/PD-293-one-ring-orchestration/), pas a la racine"
    forbidden:
      - "Ecrire dans un fichier autre que .gov-lord-state.json"
      - "Modifier l'ordre de la queue d'escalades (toujours FIFO par created_at)"
      - "Supprimer des entrees idempotency_cache avant expiration TTL (sauf purge explicite)"
      - "Stocker des credentials ou tokens dans le state"
    architectural_decisions:
      - decision: "Fichier JSON unique (.gov-lord-state.json) avec read-modify-write atomique via temp file + mv"
        rationale: "Coherent avec le schema §5.4. mv est atomique sur POSIX. Pas besoin de base de donnees pour 5 stories max."
        alternatives_considered:
          - "SQLite (overkill pour 5 entrees, ajout dependance)"
          - "Fichiers separes par story (fragmentation, pas de vue consolidee)"
        trade_offs:
          - "Avantage : schema unique, atomicite, zero dependance"
          - "Inconvenient : contention si acces concurrent (mitige par execution sequentielle One Ring)"
      - decision: "Valeur sentinelle '__pending__' pour peer_id en etat STARTING"
        rationale: "Le schema §5.4 impose peer_id required, mais en STARTING le peer n'est pas encore detecte. La sentinelle satisfait minLength:1 sans polluer le modele."
        alternatives_considered:
          - "Rendre peer_id optional dans le schema (viole §5.4)"
          - "Creer le peer avant l'ajout au state (race condition)"
        trade_offs:
          - "Avantage : conforme schema, semantiquement clair"
          - "Inconvenient : valeur magique a documenter"
    files:
      - "scripts/lib/lord-state.sh"

  - module: broker-adapter
    owner_agent: agent-broker
    interfaces:
      - broker_list_peers() -> peers[] | ERROR
      - broker_send_message(peer_id, message_json) -> OK | ERROR
      - broker_check_health() -> UP | DEGRADED | DOWN
      - broker_reconnect() -> OK | ERROR
    invariants:
      - "INV-293-02 : seul canal de communication entre One Ring et Ringbearers"
      - "Reconnexion exponentielle bornee : 1s, 2s, 4s, 8s, 10s max (§5.3 point 10)"
      - "Mode degrade explicite tant que broker indisponible (broker.status dans state)"
    forbidden:
      - "Communiquer avec les Ringbearers autrement que via broker (pas de fichier partage, pas de signal Unix)"
      - "Acceder aux repos cibles des stories (git, fichiers, API)"
      - "Ignorer silencieusement une erreur broker (toujours propager ou mode degrade)"
    architectural_decisions:
      - decision: "Appel claude-peers-mcp via MCP tools (use_mcp_tool) en subprocess"
        rationale: "claude-peers-mcp est un serveur MCP. L'appel via tools est le contrat d'interface standard."
        alternatives_considered:
          - "API REST directe sur le broker SQLite (contourne l'abstraction MCP, fragile)"
          - "CLI custom wrapping le broker (overhead de maintenance)"
        trade_offs:
          - "Avantage : interface stable, coherent avec l'ecosysteme MCP"
          - "Inconvenient : dependance sur disponibilite du serveur MCP"
    files:
      - "scripts/lib/lord-broker.sh"

  - module: supervisor
    owner_agent: agent-supervisor
    interfaces:
      - start_supervision_loop() -> void (boucle infinie)
      - poll_once() -> PollResult
      - check_liveness(story_id) -> ALIVE | MISSING(cycles)
      - reconcile() -> ReconciliationDelta
      - detect_crashes() -> crashed_stories[]
    invariants:
      - "INV-293-08 : crash detecte en <= 2 cycles (crash_detection_cycles_max = 2, non configurable)"
      - "INV-293-11 : reconciliation periodique a chaque cycle de polling"
      - "Derive SLA crash : 2 * peer_poll_interval (defaut 20s)"
    forbidden:
      - "Attendre plus de 2 cycles pour declarer un crash"
      - "Modifier l'etat d'une story terminale lors de la reconciliation"
      - "Ignorer un ecart detecte sans le logguer"
    architectural_decisions:
      - decision: "Boucle polling avec sleep peer_poll_interval entre chaque iteration"
        rationale: "Simple, deterministe, coherent avec les parametres contractuels §5.1."
        alternatives_considered:
          - "Event-driven via inotify/fswatch (complexite, pas de support natif MCP events)"
          - "Webhook callback du broker (claude-peers-mcp ne supporte pas les webhooks)"
        trade_offs:
          - "Avantage : simplicite, parametres contractuels respectes"
          - "Inconvenient : latence = peer_poll_interval (acceptable pour use case local)"
    files:
      - "scripts/lib/lord-supervisor.sh"

  - module: command-router
    owner_agent: agent-commands
    interfaces:
      - dispatch(command, args) -> Result
      - cmd_start(story_id, project_code, idempotency_key?) -> Result
      - cmd_status(story_id?) -> Result
      - cmd_stop(story_id, reason?, idempotency_key?) -> Result
      - cmd_escalade(story_id?) -> Result
      - cmd_respond(story_id, response, idempotency_key?) -> Result
      - cmd_pause(story_id, reason?, idempotency_key?) -> Result
      - cmd_resume(story_id, idempotency_key?) -> Result
    invariants:
      - "INV-293-01 : whitelist de commandes (start, status, stop, escalade, respond, pause, resume)  tout autre = rejet"
      - "INV-293-14 / §5.1 : start -> quota, idempotence, format. Autres -> idempotence, format (decision plan H-PLAN-01)"
      - "INV-293-04 : rejet si active >= max_ringbearers sur start"
    forbidden:
      - "Executer une action metier (lecture/ecriture code, build, tests, Jira, GitLab, Vault)"
      - "Accepter une commande hors whitelist"
      - "Modifier l'ordre des gardes"
      - "Lancer un Ringbearer sans passer par les 3 gardes de start"
    architectural_decisions:
      - decision: "Case/dispatch central avec gardes en fonctions separees"
        rationale: "Chaque commande a ses gardes specifiques (§5.1 points 3-4). Fonctions separees permettent test unitaire de chaque garde."
        alternatives_considered:
          - "Middleware chain (pattern overkill pour 7 commandes shell)"
          - "Fichier de config des gardes (over-engineering pour un ordre fixe)"
        trade_offs:
          - "Avantage : lisible, testable, ordre explicite"
          - "Inconvenient : duplication mineure entre start et autres (2 vs 3 gardes)"
    files:
      - "scripts/lib/lord-commands.sh"

  - module: skill-gov-lord
    owner_agent: agent-skill
    interfaces:
      - Parsing arguments CLI (/gov-lord <command> [args] [--flags])
      - Affichage consolide (tableaux, alertes, messages)
    invariants:
      - "INV-293-01 : le skill ne delegue qu'aux commandes connues de command-router"
      - "CA-06 : compatible /remote-control (iPhone/Safari)  pas de dependance UI specifique"
    forbidden:
      - "Executer du code metier directement dans le skill"
      - "Appeler des outils hors broker (pas de Read, Write, Bash sur repos cibles)"
      - "Hardcoder des paths de repos cibles"
    files:
      - ".claude/commands/gov-lord.md"

  - module: tests
    owner_agent: agent-tests
    interfaces:
      - Tests unitaires bats-core pour C1, C2, C3, C7 (gardes)
      - Tests integration bats-core pour C4 (complet), C5 (broker stubbe), C6, C7
      - Tests E2E pour TC-NOM-14 (start->RUNNING), TC-NR-01 (baseline /gov)
      - Tests negatifs TC-NEG-01..10
    invariants:
      - "Chaque TC-* de la spec a un test bats correspondant"
      - "Tests unitaires sans I/O reseau (broker stubbe)"
    forbidden:
      - "Mocker la machine d'etats (tester les vraies transitions)"
      - "Dependre d'un broker reel pour les tests unitaires"
      - "Sauter les tests negatifs TC-NEG-01..10"
    files:
      - "tests/lord/**"

TEMPLATE DE SORTIE

Génère le document conformément à cette structure :

PD-293 — Plan d’implémentation : Revue

1. Références

  • Spécification : PD-293-specification.md
  • Tests contractuels : PD-293-tests.md
  • Plan d’implémentation : PD-293-plan.md
  • Date de revue : …
  • Reviewer : …

2. Constatations (écarts)

Type Référence (Spec/Test/Plan) Description Impact Gravité (BLOQUANT/MAJEUR/MINEUR)
Non-conformité Spec Spec §… / Plan §… BLOQUANT
Couverture manquante Invariant INV-… MAJEUR
Test irréalisable TC-… BLOQUANT
Hypothèse implicite Plan §… MAJEUR
Risque sécu/conformité MAJEUR

3. Synthèse

  • Nombre d’écarts par gravité : …
  • Points critiques : …

4. Verdict de la revue

  • Statut : ⛔ Rejeté / ⚠️ Accepté avec réserves / ✅ Accepté
  • Motif synthétique : …

codex Je vais auditer les artefacts comme documents contractuels opposables et produire une revue strictement au format demandé (sans proposer de correction). Je commence par vérifier les fichiers réels du workspace (spec, tests, plan, template, code-contracts) pour aligner exactement les références. exec /bin/zsh -lc pwd in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance exec /bin/zsh -lc "rg --files | rg 'PD-293-(specification|tests|plan|plan-review|code-contracts).md|PD-293-code-contracts.yaml|PD-293-plan-review.md|ProbatioVault-doc/docs/meta/templates/PD-293-plan-review.md'" in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance succeeded in 833ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance

succeeded in 1025ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan.md docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-specification.md docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-code-contracts.yaml docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan-review.md

codex Je vois bien les 5 artefacts PD-293 dans docs/epics/tooling/PD-293-one-ring-orchestration/. Je vais maintenant lire ces fichiers et le template pour auditer sur le contenu effectif, pas seulement le texte du prompt. exec /bin/zsh -lc "rg --files | rg 'PD-293-plan-review.md"'$|templates/PD-293-plan-review'".md"'$|meta/templates'"'" in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance exec /bin/zsh -lc 'ls -la docs/epics/tooling/PD-293-one-ring-orchestration' in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance succeeded in 881ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan-review.md data/specs-index/doc/meta/templates/index.yaml data/specs-index/doc/meta/templates/guide-technique.yaml

succeeded in 883ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable total 400 drwxr-xr-x@ 18 loic staff 576 Mar 31 00:09 . drwxr-xr-x@ 5 loic staff 160 Mar 30 22:18 .. -rw-r--r--@ 1 loic staff 9749 Mar 30 22:31 PD-293-besoin.md -rw-r--r--@ 1 loic staff 13399 Mar 30 23:54 PD-293-code-contracts.yaml -rw-r--r--@ 1 loic staff 3218 Mar 30 23:24 PD-293-confrontation-step3-v2.md -rw-r--r--@ 1 loic staff 3218 Mar 30 23:23 PD-293-confrontation-step3.md -rw-r--r--@ 1 loic staff 2761 Mar 30 23:24 PD-293-dossier-conformite-step3-v2.md -rw-r--r--@ 1 loic staff 4000 Mar 30 23:05 PD-293-dossier-conformite-step3.md -rw-r--r--@ 1 loic staff 4955 Mar 30 23:40 PD-293-plan-review.md -rw-r--r--@ 1 loic wheel 49730 Mar 31 00:26 PD-293-plan.md -rw-r--r--@ 1 loic staff 12938 Mar 30 23:21 PD-293-review-step3-v2.md -rw-r--r--@ 1 loic staff 16138 Mar 30 23:03 PD-293-review-step3.md -rw-r--r--@ 1 loic staff 5179 Mar 30 23:19 PD-293-specification-review.md -rw-r--r--@ 1 loic staff 22108 Mar 30 23:11 PD-293-specification.md -rw-r--r--@ 1 loic staff 17447 Mar 30 23:11 PD-293-tests.md -rw-r--r--@ 1 loic staff 471 Mar 30 23:05 PD-293-verdict-step3-v1.yaml -rw-r--r--@ 1 loic staff 631 Mar 30 23:25 PD-293-verdict-step3-v2.yaml -rw-r--r--@ 1 loic staff 3662 Mar 30 23:38 code-contracts.yaml

exec /bin/zsh -lc "sed -n '1,220p' docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan.md" in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance exec /bin/zsh -lc "sed -n '1,260p' docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-code-contracts.yaml" in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance succeeded in 786ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable

PD-293 — Plan d'implémentation

1. Découpage en composants

La story est décomposée en 7 composants (C1-C7), cohérents avec la stack existante (scripts/lib/*.sh, Bash 5.x, jq, YAML/JSON) du repo ProbatioVault-ia-governance.

ID Composant Responsabilité Fichier(s) Dépendances
C1 state-machine Machine d'états Ringbearer (§5.2) — transitions, gardes terminaux scripts/lib/lord-state-machine.sh Aucune
C2 validator Validation de tous les champs §5.1 (regex, enum, bornes) + log de rejet §6.1 scripts/lib/lord-validator.sh Aucune
C3 broker-adapter Couche d'abstraction claude-peers-mcp — listing, envoi, réception, reconnexion scripts/lib/lord-broker.sh claude-peers-mcp@^1.x (H-293-01)
C4 persistence Lecture/écriture .gov-lord-state.json (schema §5.4), FIFO escalades, cache idempotency, audit JSONL scripts/lib/lord-persistence.sh jq (H-TECH-05)
C5 orchestrator Logique métier des 7 commandes /gov-lord, boucle de supervision, reconciliation, détection crash scripts/gov-lord.sh C1, C2, C3, C4
C6 commands Skill Claude Code /gov-lord — parsing CLI, dispatch vers C5, whitelist de commandes .claude/commands/gov-lord.md C5
C7 tests Suites BATS couvrant TC-NOM-*, TC-ERR-*, TC-INV-*, TC-NEG-*, TC-NR-* tests/lord/**/*.bats bats-core (H-TECH-06), mocks C3

Aucune modification d'autres modules : PD-293 ne touche aucune route d'un autre module. Tous les composants sont nouveaux, créés dans le repo ia-governance.

2. Flux techniques

2.1 Flux start (nominal)

Sovereign -> C6: "/gov-lord start PD-42 backend --idempotency-key K1"
C6 -> C5.lord_start(PD-42, backend, K1)
  C5 -> C4.lord_count_active_stories()            # Garde 1 : quota
    => si >= 5 : REJET ERR-293-01, lord_log_rejection(), return
  C5 -> C4.lord_check_idempotency(K1, hash(PD-42+backend))  # Garde 2 : idempotence
    => si REPLAY : retour déterministe, return
    => si CONFLICT : REJET ERR-293-09, return
  C5 -> C2.lord_validate_story_id(PD-42)           # Garde 3 : format
  C5 -> C2.lord_validate_project_code(backend)
    => si REJECTED : REJET ERR-293-03, lord_log_rejection(), return
  C5 -> C4 : story déjà active pour PD-42 ?        # Garde 4 : doublon story
    => si oui : REJET ERR-293-02, return
  C5 -> C4.lord_create_story(PD-42, backend, STARTING, "pending-PD-42")
  C5 -> lance Ringbearer : `unset CLAUDECODE && /Users/loic/.local/bin/claude -p "/gov PD-42 backend"` (background)
  C5 -> C4.lord_record_idempotency(K1, start, PD-42, hash, result_hash, ttl=24h)
  # La supervision continue prend le relais (§2.4)

2.2 Flux respond (escalade FIFO)

Sovereign -> C6: "/gov-lord respond PD-42 'Approuvé pour production'"
C6 -> C5.lord_respond(PD-42, text, K2)
  C5 -> C4.lord_check_idempotency(K2, hash)         # Garde 1 : idempotence
  C5 -> C2.lord_validate_story_id(PD-42)             # Garde 2 : format
  C5 -> C4 : lire state(PD-42)
    => si state != ESCALADED : REJET ERR-293-04, return
  C5 -> C4.lord_dequeue_oldest_escalade(PD-42)       # FIFO (INV-293-13)
    => marque ANSWERED
  C5 -> C3.lord_broker_send_message(peer_id, PO_RESPONSE, {text})
  C5 -> C1.lord_transition(PD-42, ESCALADED, RUNNING)
  C5 -> C4.lord_update_story(PD-42, {state:RUNNING})
  # Si autres escalades en OPEN : story reste ESCALADED (multi-escalade)
  # Correction : si escalade_queue contient encore des OPEN pour cette story,
  # on NE fait PAS la transition ESCALADED->RUNNING ; on attend toutes les réponses.

Précision multi-escalade : La transition ESCALADED->RUNNING ne s'effectue que lorsque toutes les escalades OPEN de la story sont traitées. Tant qu'il reste au moins une escalade OPEN, la story reste ESCALADED.

2.3 Flux pause / resume

# PAUSE
C5 -> C1.lord_transition(PD-42, RUNNING, PAUSED)   # Garde machine d'états
C5 -> C3.lord_broker_send_message(peer_id, PAUSE, {reason})
C5 -> C4.lord_update_story(PD-42, {state:PAUSED})

# RESUME
C5 -> C1.lord_transition(PD-42, PAUSED, RUNNING)
C5 -> C3.lord_broker_send_message(peer_id, RESUME, {})
C5 -> C4.lord_update_story(PD-42, {state:RUNNING})

2.4 Boucle de supervision (flux continu)

lord_supervision_loop():
  while true:
    sleep $peer_poll_interval  # défaut 10s

    # 1. Vérifier status broker
    broker_status = C3.lord_broker_status()
    si DOWN : alerte Sovereign, tenter C3.lord_broker_reconnect()

    # 2. Lister peers observés
    observed_peers = C3.lord_broker_list_peers()

    # 3. Pour chaque story non-terminale en état local :
    pour chaque story dans C4.stories (state NOT IN [DONE, ABORTED, CRASHED, START_FAILED]) :

      si story.state == STARTING :
        si peer_id trouvé dans observed_peers ET first liveness reçue :
          C1.lord_transition(story_id, STARTING, RUNNING)
          C4.lord_update_story(story_id, {state:RUNNING, peer_id:real_peer_id})
        si elapsed > start_detection_timeout OU (peer trouvé mais pas de liveness avant first_liveness_timeout) :
          C1.lord_transition(story_id, STARTING, START_FAILED)
          C4.lord_update_story(story_id, {state:START_FAILED})
          cleanup session
          alerte Sovereign

      sinon (RUNNING, ESCALADED, PAUSED) :
        si peer absent dans observed_peers :
          incrémenter missed_polls
          si missed_polls >= crash_detection_cycles_max (=2) :
            C1.lord_transition(story_id, current_state, CRASHED)
            C4.lord_update_story(story_id, {state:CRASHED})
            alerte critique Sovereign
        sinon :
          missed_polls = 0
          traiter messages entrants (STATUS_UPDATE, ESCALADE, GATE_RESULT, WORKFLOW_DONE)

    # 4. Réconciliation (INV-293-11)
    pour chaque peer observé non reflété localement : signaler écart
    pour chaque story locale non-terminale sans peer observé : compter missed_polls

    # 5. Traitement messages entrants
    pour chaque message de type ESCALADE :
      C2.lord_validate (tous champs §5.1)
      C4.lord_enqueue_escalade(story_id, escalade_id, text, timestamp)
      C1.lord_transition(story_id, RUNNING, ESCALADED) # si pas déjà ESCALADED
      notifier Sovereign (SLA <= 5s, mesurable par delta timestamp)

    pour chaque message de type WORKFLOW_DONE :
      C1.lord_transition(story_id, RUNNING, DONE)
      C4.lord_update_story(story_id, {state:DONE})

    # 6. Purge idempotency expirée (opportuniste)
    C4.lord_purge_expired_idempotency()

2bis. Diagramme de dépendances agents (step 6b)

graph LR
    subgraph "Wave 1 — Modules fondamentaux (parallélisables)"
        C1[C1: state-machine<br/>agent-fsm]
        C2[C2: validator<br/>agent-validator]
    end

    subgraph "Wave 2 — Modules infrastructure"
        C3[C3: broker-adapter<br/>agent-broker]
        C4[C4: persistence<br/>agent-persistence]
    end

    subgraph "Wave 3 — Orchestrateur"
        C5[C5: orchestrator<br/>agent-orchestrator]
    end

    subgraph "Wave 4 — Interface + Tests"
        C6[C6: commands<br/>agent-skill]
        C7[C7: tests<br/>agent-tests]
    end

    C1 --> C5
    C2 --> C5
    C3 --> C5
    C4 --> C5
    C5 --> C6
    C1 --> C7
    C2 --> C7
    C3 --> C7
    C4 --> C7
    C5 --> C7

Waves d'exécution : - Wave 1 : C1 (state-machine) et C2 (validator) — zéro dépendance mutuelle, parallélisables. - Wave 2 : C3 (broker-adapter) et C4 (persistence) — dépendent de C2 pour la validation, parallélisables entre eux. - Wave 3 : C5 (orchestrator) — dépend de C1+C2+C3+C4, séquentiel. - Wave 4 : C6 (commands) dépend de C5 ; C7 (tests) dépend de tous. Parallélisables entre eux car C7 mock C5.

2ter. Diagramme de séquence enrichi (mécanismes techniques)

sequenceDiagram
    participant S as Sovereign (Humain)
    participant C6 as C6: /gov-lord<br/>(Skill CLI)
    participant C5 as C5: gov-lord.sh<br/>(Orchestrator)
    participant C2 as C2: lord-validator.sh
    participant C4 as C4: lord-persistence.sh
    participant C1 as C1: lord-state-machine.sh
    participant C3 as C3: lord-broker.sh<br/>(claude-peers-mcp)
    participant R as Ringbearer<br/>(claude -p "/gov PD-42")

    S->>C6: /gov-lord start PD-42 backend --idempotency-key K1
    C6->>C5: lord_start(PD-42, backend, K1)
    C5->>C4: lord_count_active_stories()
    C4-->>C5: 3 (< 5, quota OK)
    C5->>C4: lord_check_idempotency(K1, sha256)
    C4-->>C5: NEW
    C5->>C2: lord_validate_story_id(PD-42)
    C2-->>C5: OK
    C5->>C4: lord_create_story(PD-42, backend, STARTING, pending-PD-42)
    C5->>R: lance en background (claude -p)
    C5->>C4: lord_record_idempotency(K1, ...)

    Note over C5: Boucle supervision (toutes les 10s)
    C5->>C3: lord_broker_list_peers()
    C3-->>C5: [{peer_id: "rb-PD-42", ...}]
    C5->>C1: lord_transition(PD-42, STARTING, RUNNING)
    C1-->>C5: OK

    R->>C3: send_message(ESCALADE, {text: "Besoin clarification §3"})
    C5->>C3: lord_broker_get_messages(rb-PD-42)
    C3-->>C5: [{type: ESCALADE, ...}]
    C5->>C2: validate message
    C5->>C4: lord_enqueue_escalade(PD-42, esc-1, text, ts)
    C5->>C1: lord_transition(PD-42, RUNNING, ESCALADED)
    C5-->>S: Escalade PD-42: "Besoin clarification §3"

    S->>C6: /gov-lord respond PD-42 "Cf. §3.2 du cahier des charges"
    C6->>C5: lord_respond(PD-42, text, K2)
    C5->>C4: lord_dequeue_oldest_escalade(PD-42)
    C5->>C3: lord_broker_send_message(rb-PD-42, PO_RESPONSE, {text})
    C5->>C1: lord_transition(PD-42, ESCALADED, RUNNING)

succeeded in 777ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable code_contracts: - module: state-machine owner_agent: agent-fsm interfaces: - transition(from_state, to_state) -> OK | REJECTED(reason) - is_terminal(state) -> boolean - get_allowed_transitions(state) -> list invariants: - "INV-293-09 : Seules les transitions listees en §5.2 sont autorisees" - "INV-293-10 : DONE, ABORTED, CRASHED, START_FAILED sont terminaux — aucune transition sortante" - "8 etats contractuels : STARTING, RUNNING, ESCALADED, PAUSED, DONE, ABORTED, CRASHED, START_FAILED" forbidden: - "Ajouter un etat non liste en §5.2 (ESCALADE_PENDING, ESCALADE_EXPIRED interdits en v1)" - "Accepter une transition non autorisee silencieusement (toujours rejeter + logguer)" - "Modifier les etats terminaux (is_terminal hardcode, pas configurable)" architectural_decisions: - decision: "Matrice de transitions en tableau associatif Bash (declare -A)" rationale: "Structure declarative lisible, O(1) lookup, coherent avec §5.2 sans logique conditionnelle." alternatives_considered: - "Case/switch imbriques (verbose, risque d'oubli de transition)" - "Fichier YAML externe parse par jq (overhead I/O pour chaque transition)" trade_offs: - "Avantage : deterministe, exhaustif, testable unitairement" - "Inconvenient : limite aux types Bash (string keys uniquement)" files: - "scripts/lib/lord-fsm.sh"

  • module: validator owner_agent: agent-validator interfaces:

    • validate_story_id(value) -> OK | ERROR(reason)
    • validate_project_code(value) -> OK | ERROR(reason)
    • validate_message_type(value) -> OK | ERROR(reason)
    • validate_timestamp(value) -> OK | ERROR(reason)
    • validate_peer_id(value) -> OK | ERROR(reason)
    • validate_escalade_text(value) -> OK | ERROR(reason)
    • validate_idempotency_key(value) -> OK | ERROR(reason)
    • validate_message(json_payload) -> OK | ERROR(reason) invariants:
    • "INV-293-05 : Tout message invalide est rejete de facon deterministe avec raison explicite"
    • "Regex story_id : ^PD-[0-9]{1,4}$ (source de verite unique, case-sensitive)"
    • "Timestamps : seuls Z et +00:00 acceptes (strictement UTC, decision plan §9.9)" forbidden:
    • "Accepter un message partiellement valide (tout-ou-rien)"
    • "Utiliser eval ou interpolation directe des valeurs validees dans du shell"
    • "Accepter des offsets non-UTC dans les timestamps (ex: +02:00)" architectural_decisions:
    • decision: "Fonctions de validation par champ avec regex Bash natif ([[ =~ ]])" rationale: "Zero dependance externe, performance native, coherent avec la stack shell du projet." alternatives_considered:
      • "jq pour validation JSON schema (necessite jq installe, overhead par appel)"
      • "Python script de validation (overhead subprocess pour chaque message)" trade_offs:
      • "Avantage : zero dependance, rapide, testable isolement"
      • "Inconvenient : regex complexes moins lisibles en Bash qu'en Python" files:
    • "scripts/lib/lord-validator.sh"
  • module: logger owner_agent: agent-logger interfaces:

    • log_event(event_type, fields...) -> void
    • log_rejection(peer_id, message_type, reason, story_id?) -> void
    • log_transition(story_id, from_state, to_state) -> void
    • log_alert(severity, message) -> void invariants:
    • "Format JSONL §6.1 : champs obligatoires timestamp, peer_id, message_type, reason pour les rejets"
    • "Timestamps RFC3339 UTC (suffixe Z)"
    • "Convention rejets commande Sovereign : peer_id='sovereign', message_type='COMMAND' (decision plan H-PLAN-02)" forbidden:
    • "Ecrire des logs dans un format autre que JSONL"
    • "Omettre un champ obligatoire dans un log de rejet"
    • "Logger le contenu complet de escalade_text (risque RGPD) — logger uniquement le motif de rejet et l'escalade_id" architectural_decisions:
    • decision: "Convention peer_id='sovereign' et message_type='COMMAND' pour les rejets CLI" rationale: "§6.1 impose peer_id et message_type pour tout rejet, mais ces champs ne sont pas definis pour les commandes Sovereign. Convention deterministe documentee." alternatives_considered:
      • "Omettre les champs (viole §6.1)"
      • "Utiliser des valeurs vides (non conforme minLength)" trade_offs:
      • "Avantage : conforme §6.1, testable, coherent"
      • "Inconvenient : valeur non canonique, a documenter pour auditeurs" files:
    • "scripts/lib/lord-logger.sh"
  • module: state-store owner_agent: agent-state interfaces:

    • load_state() -> GovLordState
    • save_state(state) -> void
    • count_active_stories() -> int
    • add_story(story_id, project_code) -> void
    • update_story_state(story_id, new_state) -> void
    • update_story_peer(story_id, peer_id, last_seen_at) -> void
    • enqueue_escalade(story_id, escalade_id, text, timestamp) -> void
    • dequeue_escalade(story_id) -> escalade | null
    • check_idempotency(key, payload_hash) -> HIT(result) | CONFLICT | NEW
    • store_idempotency(key, command, story_id, payload_hash, result_hash) -> void
    • purge_expired_idempotency() -> int invariants:
    • "INV-293-13 : escalade_queue est FIFO — dequeue retourne la plus ancienne OPEN de la story"
    • "INV-293-12 : idempotency_cache indexe par key, hash SHA-256 du payload, TTL 24h"
    • "Schema §5.4 respecte pour .gov-lord-state.json (version, updated_at, stories, escalade_queue, idempotency_cache)"
    • "Localisation : fichier dans le dossier epic (docs/epics/tooling/PD-293-one-ring-orchestration/), pas a la racine" forbidden:
    • "Ecrire dans un fichier autre que .gov-lord-state.json"
    • "Modifier l'ordre de la queue d'escalades (toujours FIFO par created_at)"
    • "Supprimer des entrees idempotency_cache avant expiration TTL (sauf purge explicite)"
    • "Stocker des credentials ou tokens dans le state" architectural_decisions:
    • decision: "Fichier JSON unique (.gov-lord-state.json) avec read-modify-write atomique via temp file + mv" rationale: "Coherent avec le schema §5.4. mv est atomique sur POSIX. Pas besoin de base de donnees pour 5 stories max." alternatives_considered:
      • "SQLite (overkill pour 5 entrees, ajout dependance)"
      • "Fichiers separes par story (fragmentation, pas de vue consolidee)" trade_offs:
      • "Avantage : schema unique, atomicite, zero dependance"
      • "Inconvenient : contention si acces concurrent (mitige par execution sequentielle One Ring)"
    • decision: "Valeur sentinelle 'pending' pour peer_id en etat STARTING" rationale: "Le schema §5.4 impose peer_id required, mais en STARTING le peer n'est pas encore detecte. La sentinelle satisfait minLength:1 sans polluer le modele." alternatives_considered:
      • "Rendre peer_id optional dans le schema (viole §5.4)"
      • "Creer le peer avant l'ajout au state (race condition)" trade_offs:
      • "Avantage : conforme schema, semantiquement clair"
      • "Inconvenient : valeur magique a documenter" files:
    • "scripts/lib/lord-state.sh"
  • module: broker-adapter owner_agent: agent-broker interfaces:

    • broker_list_peers() -> peers[] | ERROR
    • broker_send_message(peer_id, message_json) -> OK | ERROR
    • broker_check_health() -> UP | DEGRADED | DOWN
    • broker_reconnect() -> OK | ERROR invariants:
    • "INV-293-02 : seul canal de communication entre One Ring et Ringbearers"
    • "Reconnexion exponentielle bornee : 1s, 2s, 4s, 8s, 10s max (§5.3 point 10)"
    • "Mode degrade explicite tant que broker indisponible (broker.status dans state)" forbidden:
    • "Communiquer avec les Ringbearers autrement que via broker (pas de fichier partage, pas de signal Unix)"
    • "Acceder aux repos cibles des stories (git, fichiers, API)"
    • "Ignorer silencieusement une erreur broker (toujours propager ou mode degrade)" architectural_decisions:
    • decision: "Appel claude-peers-mcp via MCP tools (use_mcp_tool) en subprocess" rationale: "claude-peers-mcp est un serveur MCP. L'appel via tools est le contrat d'interface standard." alternatives_considered:
      • "API REST directe sur le broker SQLite (contourne l'abstraction MCP, fragile)"
      • "CLI custom wrapping le broker (overhead de maintenance)" trade_offs:
      • "Avantage : interface stable, coherent avec l'ecosysteme MCP"
      • "Inconvenient : dependance sur disponibilite du serveur MCP" files:
    • "scripts/lib/lord-broker.sh"
  • module: supervisor owner_agent: agent-supervisor interfaces:

    • start_supervision_loop() -> void (boucle infinie)
    • poll_once() -> PollResult
    • check_liveness(story_id) -> ALIVE | MISSING(cycles)
    • reconcile() -> ReconciliationDelta
    • detect_crashes() -> crashed_stories[] invariants:
    • "INV-293-08 : crash detecte en <= 2 cycles (crash_detection_cycles_max = 2, non configurable)"
    • "INV-293-11 : reconciliation periodique a chaque cycle de polling"
    • "Derive SLA crash : 2 * peer_poll_interval (defaut 20s)" forbidden:
    • "Attendre plus de 2 cycles pour declarer un crash"
    • "Modifier l'etat d'une story terminale lors de la reconciliation"
    • "Ignorer un ecart detecte sans le logguer" architectural_decisions:
    • decision: "Boucle polling avec sleep peer_poll_interval entre chaque iteration" rationale: "Simple, deterministe, coherent avec les parametres contractuels §5.1." alternatives_considered:
      • "Event-driven via inotify/fswatch (complexite, pas de support natif MCP events)"
      • "Webhook callback du broker (claude-peers-mcp ne supporte pas les webhooks)" trade_offs:
      • "Avantage : simplicite, parametres contractuels respectes"
      • "Inconvenient : latence = peer_poll_interval (acceptable pour use case local)" files:
    • "scripts/lib/lord-supervisor.sh"
  • module: command-router owner_agent: agent-commands interfaces:

    • dispatch(command, args) -> Result
    • cmd_start(story_id, project_code, idempotency_key?) -> Result
    • cmd_status(story_id?) -> Result
    • cmd_stop(story_id, reason?, idempotency_key?) -> Result
    • cmd_escalade(story_id?) -> Result
    • cmd_respond(story_id, response, idempotency_key?) -> Result
    • cmd_pause(story_id, reason?, idempotency_key?) -> Result
    • cmd_resume(story_id, idempotency_key?) -> Result invariants:
    • "INV-293-01 : whitelist de commandes (start, status, stop, escalade, respond, pause, resume) — tout autre = rejet"
    • "INV-293-14 / §5.1 : start -> quota, idempotence, format. Autres -> idempotence, format (decision plan H-PLAN-01)"
    • "INV-293-04 : rejet si active >= max_ringbearers sur start" forbidden:
    • "Executer une action metier (lecture/ecriture code, build, tests, Jira, GitLab, Vault)"
    • "Accepter une commande hors whitelist"
    • "Modifier l'ordre des gardes"
    • "Lancer un Ringbearer sans passer par les 3 gardes de start" architectural_decisions:
    • decision: "Case/dispatch central avec gardes en fonctions separees" rationale: "Chaque commande a ses gardes specifiques (§5.1 points 3-4). Fonctions separees permettent test unitaire de chaque garde." alternatives_considered:
      • "Middleware chain (pattern overkill pour 7 commandes shell)"
      • "Fichier de config des gardes (over-engineering pour un ordre fixe)" trade_offs:
      • "Avantage : lisible, testable, ordre explicite"
      • "Inconvenient : duplication mineure entre start et autres (2 vs 3 gardes)" files:
    • "scripts/lib/lord-commands.sh"
  • module: skill-gov-lord owner_agent: agent-skill interfaces:

    • Parsing arguments CLI (/gov-lord [args] [--flags])
    • Affichage consolide (tableaux, alertes, messages) invariants:
    • "INV-293-01 : le skill ne delegue qu'aux commandes connues de command-router"
    • "CA-06 : compatible /remote-control (iPhone/Safari) — pas de dependance UI specifique" forbidden:
    • "Executer du code metier directement dans le skill"
    • "Appeler des outils hors broker (pas de Read, Write, Bash sur repos cibles)"
    • "Hardcoder des paths de repos cibles" files:
    • ".claude/commands/gov-lord.md"
  • module: tests owner_agent: agent-tests interfaces:

    • Tests unitaires bats-core pour C1, C2, C3, C7 (gardes)
    • Tests integration bats-core pour C4 (complet), C5 (broker stubbe), C6, C7
    • Tests E2E pour TC-NOM-14 (start->RUNNING), TC-NR-01 (baseline /gov)
    • Tests negatifs TC-NEG-01..10 invariants:
    • "Chaque TC-* de la spec a un test bats correspondant"
    • "Tests unitaires sans I/O reseau (broker stubbe)" forbidden:
    • "Mocker la machine d'etats (tester les vraies transitions)"
    • "Dependre d'un broker reel pour les tests unitaires"
    • "Sauter les tests negatifs TC-NEG-01..10" files:
    • "tests/lord/**"

codex Je poursuis l’extraction du plan (sections mapping, erreurs, hypothèses, contraintes) pour relever uniquement les écarts contractuels démontrables, en particulier sur l’alignement Plan↔Tests et Plan↔Code Contracts. exec /bin/zsh -lc "sed -n '220,520p' docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan.md" in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance succeeded in 674ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable

Vérification couverture diagramme d'état §5bis : Chaque transition du stateDiagram de la spec est couverte par un mécanisme dans C1 (lord_transition) appelé depuis C5. Les transitions terminales (->CRASHED, ->DONE, ->ABORTED, ->START_FAILED) sont gérées dans la boucle de supervision (§2.4) et les commandes stop respectivement.

3. Mapping invariants → mécanismes

Invariant ID Exigence Mécanisme Composant Observable Risque
INV-293-01 One Ring n'exécute aucune action hors interface/routage Whitelist de 7 commandes dans C6. Toute autre entrée retourne message de blocage. C5 n'importe jamais jira-api.sh, git, curl vers repos cibles. C6 + C5 TC-NOM-05, TC-ERR-05 : message de blocage systématique, audit de refus tracé Faible — vérifiable par grep sur les imports de C5
INV-293-02 Communication uniquement via broker/peers C3 est l'unique point d'accès réseau. C5 n'appelle jamais curl, git, ou tout outil réseau directement. Toute communication Ringbearer transite par lord_broker_send_message / lord_broker_get_messages. C3 + C5 TC-NOM-01 : traces broker exclusivement. TC-NR-02 : absence totale d'opération directe Moyen — dépend de la discipline de code review
INV-293-03 Isolation inter-story (contexte, credentials) Chaque Ringbearer est un process claude -p séparé. Aucun partage de variable shell, aucun fichier partagé entre stories. C4 indexe tout par story_id. C5 + C4 TC-NOM-09 : action sur story A sans impact B. TC-NR-03 : absence partage credentials Faible — garanti par l'isolation process OS
INV-293-04 Maximum 5 Ringbearers actifs lord_count_active_stories() dans C4 (count des stories non-terminales). Vérifié en garde 1 de start. C4 + C5 TC-NOM-02, TC-ERR-01 : 6e start rejeté, count reste 5 Faible
INV-293-05 Validation stricte §5.1, rejet + journalisation §6.1 C2 implémente une fonction par champ (lord_validate_*). Chaque rejet appelle lord_log_rejection() qui écrit en JSONL. C2 TC-NOM-10 : tous valides acceptés. TC-ERR-03 : invalides rejetés. TC-NEG-01..10 : 10 cas négatifs Moyen — regex \p{C} nécessite grep -P (Perl regex) pour peer_id/escalade_text
INV-293-06 Escalade suspend jusqu'à PO_RESPONSE En état ESCALADED, C1 n'autorise que ->RUNNING (PO_RESPONSE), ->ABORTED, ->CRASHED. C5 refuse tout resume/pause depuis ESCALADED (non dans transitions autorisées). C1 + C5 TC-NOM-03 : blocage effectif. TC-ERR-04 : respond sans escalade rejeté Faible
INV-293-07 /gov inchangé PD-293 ne modifie aucun fichier existant (scripts/gov-workflow.sh, scripts/gov-step.sh, etc.). Le Ringbearer exécute /gov tel quel. Architecture TC-NR-01 : baseline vs orchestré identiques Faible — vérifiable par git diff post-implémentation
INV-293-08 Crash détecté en ≤ 2 cycles (≤ 20s défaut) Compteur missed_polls par story dans C5. Incrémenté si peer absent, reset à 0 si présent. Transition ->CRASHED si missed_polls >= 2. C5 TC-NOM-06, TC-ERR-07 : transition CRASHED + alerte ≤ 20s Faible — déterministe, dépend uniquement du poll_interval
INV-293-09 Transitions strictement §5.2 C1 utilise un declare -A TRANSITIONS (tableau associatif Bash) avec chaque paire FROM->TO autorisée. Toute transition non présente dans le tableau est rejetée. C1 TC-INV-01 : toutes transitions non listées rejetées Faible — matrice exhaustive vérifiable par diff
INV-293-10 États terminaux immuables C1 : lord_is_terminal(state) retourne true pour DONE, ABORTED, CRASHED, START_FAILED. lord_transition() rejette immédiatement si from_state est terminal. C1 TC-INV-02 : aucune sortie acceptée depuis terminal Faible
INV-293-11 Réconciliation périodique À chaque cycle de supervision (§2.4, étape 4), C5 compare C4.stories (non-terminaux) avec C3.list_peers(). Écarts signalés en log + alerte si divergence. C5 + C3 + C4 TC-NOM-07 : désynchronisation volontaire corrigée Moyen — dépend de la fiabilité du listing broker
INV-293-12 Idempotence par clé dédiée C4 : lord_check_idempotency(key, payload_hash). 3 résultats : NEW (première fois), REPLAY (même payload → même résultat), CONFLICT (payload différent → rejet). TTL configurable (défaut 24h). C4 + C5 TC-NOM-08, TC-ERR-08, TC-ERR-11 Faible
INV-293-13 FIFO multi-escalade C4 : escalade_queue est un tableau JSON trié par created_at. lord_enqueue_escalade() append en fin. lord_dequeue_oldest_escalade(story_id) retire le premier élément OPEN de la story. C4 TC-NOM-13 : 3 escalades traitées dans l'ordre t1, t2, t3 Faible — tri par created_at garanti par jq
INV-293-14 Ordre des gardes : quota→idempotence→format (start) ; idempotence→format (autres) C5 applique les gardes dans l'ordre contractuel. Décision (résolution contradiction spec review) : INV-293-14 est interprété comme l'ordre spécifique par commande (§5.1 points 3-4 font foi). Pour start : quota→idempotence→format→doublon story. Pour stop/respond/pause/resume : idempotence→format. C5 TC-INV-03 : garde quota en premier, format après idempotence Faible

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

Critère ID Mécanisme(s) Composant Observable Risque
CA-01 lord_status() dans C5 : lit C4.stories[] + C3.lord_broker_list_peers(), produit JSON consolidé avec story_id, state, last_seen_at, peer_id, escalades OPEN. C5, C4, C3 TC-NOM-01 : sortie contient liste consolidée. Log audit trace consultation broker. Faible
CA-02 lord_start() dans C5 : gardes → création story STARTING → lancement Ringbearer background → supervision détecte peer + first liveness → transition STARTING→RUNNING. Nouveau test : TC-NOM-14 (cf. §5 infra) couvre le chemin nominal complet. C5, C4, C1, C3 TC-NOM-14 : story visible en RUNNING avant start_detection_timeout. First liveness vérifiée. Moyen — dépend du timing de démarrage du Ringbearer
CA-03 Delta timestamps RFC3339 entre émission (Ringbearer) et réception (One Ring). Horloge unique locale MacBook (pas de synchronisation d'horloge nécessaire — décision H-TECH-08bis, cf. §8). C5, C4 TC-NOM-04 : P95 ≤ 5s sur 100 escalades, timestamps exportés Faible (horloge locale unique)
CA-04 lord_respond()dequeue_oldest_escalade()send_message(PO_RESPONSE)lord_transition(ESCALADED, RUNNING) C5, C4, C3, C1 TC-NOM-03 : transition ESCALADED→RUNNING observée après respond Faible
CA-05 Whitelist dans C6 : 7 commandes autorisées. Toute autre entrée → message de blocage. C5 n'a aucun import d'outil métier. C6, C5 TC-NOM-05, TC-ERR-05 : blocage explicite + audit de refus Faible
CA-06 /remote-control Claude Code → exécute /gov-lord → mêmes commandes C6. Aucune adaptation spécifique mobile nécessaire. C6 TC-MAN-01 : test manuel iPhone/Safari. Résultats identiques desktop. Élevé — dépend de /remote-control (H-293-02)
CA-07 Compteur missed_polls dans C5 supervision loop. ≥ 2 → transition ->CRASHED + alerte. C5, C1 TC-NOM-06, TC-ERR-07 : CRASHED en ≤ 2 cycles (≤ 20s) Faible
CA-08 /gov non modifié. Le Ringbearer est un process autonome claude -p "/gov PD-XX projet". Architecture TC-NR-01 : baseline identique. Aucun fichier /gov modifié. Faible
CA-09 lord_count_active_stories() ≥ 5 → rejet start. C4, C5 TC-NOM-02, TC-ERR-01 : 6e start rejeté, count = 5 Faible
CA-10 C2 valide chaque champ avec regex/enum §5.1. Rejet → log JSONL §6.1. C2 TC-NOM-10, TC-ERR-03, TC-NEG-01..10 Moyen — nécessite grep -P pour Unicode
CA-11 C4.escalade_queue trié par created_at. FIFO strict. C4 TC-NOM-13 : 3 escalades en ordre t1 < t2 < t3 Faible
CA-12 C3 : reconnexion exponentielle bornée (1s, 2s, 4s, 8s, 10s max). Mode DEGRADED explicite. Reprise auto → reconciliation. C3, C5 TC-ERR-10 : mode dégradé puis reprise automatique Moyen — dépend du broker

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

Test ID Référence spec Mécanisme(s) Point(s) d'observation Niveau
TC-NOM-01 INV-293-02, CA-01 C5.lord_status() → C3.list_peers() + C4.stories Sortie JSON consolidée, log audit broker, absence opération directe Integration
TC-NOM-02 INV-293-04, CA-09 C4.lord_count_active_stories() en garde start Rejet explicite, count reste 5, log rejet Unit (C4) + Integration
TC-NOM-03 INV-293-06, CA-04 C5.lord_respond() → C4.dequeue → C3.send → C1.transition Transition ESCALADED→RUNNING, STATUS_UPDATE post-respond Integration
TC-NOM-04 CA-03, INV-293-05 Timestamps RFC3339 delta émission/réception (horloge locale unique) Export CSV 100 mesures, P95 ≤ 5s Perf (mock broker avec délai calibré)
TC-NOM-05 INV-293-01, CA-05 Whitelist C6, message de blocage Sortie "commande non reconnue", log audit refus, absence side-effect Unit (C6)
TC-NOM-06 INV-293-08, CA-07 C5 supervision loop, missed_polls ≥ 2 Transition →CRASHED, timestamp ≤ 2×poll_interval, alerte Integration (mock broker sans peer)
TC-NOM-07 INV-293-11, CA-01 C5 réconciliation : compare C4.stories vs C3.list_peers() Écarts détectés, vue alignée, log reconciliation Integration
TC-NOM-08 INV-293-12, CA-02 C4.lord_check_idempotency() → REPLAY Second appel identique, résultat déterministe, aucun Ringbearer supplémentaire Unit (C4) + Integration
TC-NOM-09 INV-293-03, CA-08 Isolation process : 2 stories actives, action sur A State B inchangé, aucun partage message/credential Integration
TC-NOM-10 INV-293-05, CA-10 C2.lord_validate_*() sur lot valide complet Tous acceptés, log audit association type+story+timestamp Unit (C2)
TC-NOM-12 CA-02, INV-293-09 C5.lord_pause() → C1.transition(RUNNING, PAUSED) → C5.lord_resume() → C1.transition(PAUSED, RUNNING) Transitions RUNNING→PAUSED→RUNNING observées, logs idempotency Integration
TC-NOM-13 INV-293-13, CA-11 C4.lord_enqueue_escalade() × 3 + C4.lord_dequeue_oldest_escalade() × 3 Ordre FIFO t1→t2→t3, escalade_queue traçable Unit (C4) + Integration
TC-NOM-14 (ajouté) CA-02, INV-293-08 C5.lord_start() complet : gardes → STARTING → peer détecté → first liveness → RUNNING Story visible en RUNNING, peer_id réel, elapsed < start_detection_timeout Integration
TC-ERR-01 ERR-293-01 C4.lord_count_active_stories() → 5 → rejet Message "quota atteint", aucun peer créé Unit + Integration
TC-ERR-02 ERR-293-02 C4 : story déjà active → rejet Message "story déjà active", pas de second peer Integration
TC-ERR-03 ERR-293-03, §6.1 C2.lord_validate_story_id() → REJECTED + lord_log_rejection() Log JSONL avec timestamp, peer_id="sovereign", message_type="START", reason Unit (C2)
TC-ERR-04 ERR-293-04 C4 : story state != ESCALADED → rejet respond Message "pas d'escalade active" Integration
TC-ERR-05 ERR-293-05 Whitelist C6 → blocage Absence totale d'effet de bord Unit (C6)
TC-ERR-06 ERR-293-06 C3.lord_broker_status() → DOWN → mode DEGRADED + alerte Mode dégradé explicite, alerte Sovereign, aucune décision silencieuse Integration (broker mock DOWN)
TC-ERR-07 ERR-293-07 C5 supervision, missed_polls ≥ 2 Transition →CRASHED, alerte critique, état terminal Integration
TC-ERR-08 ERR-293-08 C4.lord_check_idempotency(K2, hash) → REPLAY Retour déterministe identique, aucun doublon Unit (C4)
TC-ERR-09 ERR-293-10 C5 supervision : peer détecté mais pas de liveness avant first_liveness_timeout Transition STARTING→START_FAILED, cleanup, alerte Integration
TC-ERR-10 CA-12 C3 reconnexion exponentielle + C5 reconciliation à la reprise Mode dégradé, timing retries (1s,2s,4s,8s,10s), reprise auto Integration
TC-ERR-11 ERR-293-09 C4.lord_check_idempotency(K3, hash_P2) → CONFLICT Rejet conflit, aucun effet Unit (C4)
TC-INV-01 INV-293-09 C1 : matrice TRANSITIONS, toute paire non listée → REJECTED Rejet explicite pour chaque transition non autorisée, log Unit (C1) — test exhaustif matrice 8×8
TC-INV-02 INV-293-10 C1 : lord_is_terminal() + lord_transition() → rejet si terminal Aucune mutation depuis DONE/ABORTED/CRASHED/START_FAILED Unit (C1)
TC-INV-03 INV-293-14 C5.lord_start() avec quota plein + payload invalide Premier rejet = "quota atteint" (pas "format invalide") Integration
TC-NEG-01..10 INV-293-05 C2.lord_validate_*() sur 10 entrées invalides (cf. §7 tests) Rejet spécifique par champ, log JSONL, zéro side-effect Unit (C2)
TC-NR-01 INV-293-07 Diff comportement /gov PD-XX standalone vs orchestré Mêmes étapes/gates/verdicts, aucun fichier gov modifié E2E (1 story complète)
TC-NR-02 INV-293-01 grep -r sur C5 pour curl/git/cd-vers-repo Zéro match d'opération hors scope Static analysis
TC-NR-03 INV-293-03 2 stories actives : envoi message ciblé A, vérification B inchangé State B identique avant/après Integration
TC-NR-04 INV-293-10 Réconciliation + polling sur stories terminales États terminaux jamais modifiés Integration
TC-MAN-01 CA-06 iPhone/Safari + /remote-control Résultats fonctionnels identiques desktop Manuel

Note sur TC-NOM-14 : Ce test est ajouté pour couvrir CA-02 (résolution de l'incohérence spec-review §2 : CA-02 était mappé à TC-NOM-08 qui teste l'idempotence, pas le démarrage nominal).

6. Gestion des erreurs

ERR ID Cas Garde(s) Composant Message/Format Observable
ERR-293-01 start quota ≥ 5 C4.count_active ≥ 5 C5 "Rejet: quota atteint (5/5 sessions actives)" + log JSONL : {event: "COMMAND_REJECTED", reason: "quota_exceeded", ...} Aucun peer créé, log tracé
ERR-293-02 start story déjà active C4.stories contient story_id non-terminal C5 "Rejet: story {id} déjà active (état: {state})" + log Pas de second peer
ERR-293-03 Message sans story_id valide C2.lord_validate_*() → REJECTED C2 Log JSONL §6.1 : {timestamp, peer_id, message_type, reason, event: "MESSAGE_REJECTED"} Aucun changement d'état
ERR-293-04 respond sans escalade active C4.state != ESCALADED ou queue vide C5 "Rejet: pas d'escalade active pour {story_id}" État inchangé
ERR-293-05 Commande hors scope Whitelist C6 C6 "Commande non reconnue. Commandes disponibles: start, status, escalade, respond, pause, resume, stop" Zéro action métier
ERR-293-06 Broker indisponible C3.lord_broker_status() = DOWN C3+C5 Mode DEGRADED + alerte "⚠ Broker indisponible — mode dégradé" + retries exponentiel Aucune décision silencieuse
ERR-293-07 Peer disparu > 2 cycles C5.missed_polls ≥ 2 C5 "🚨 CRASHED: {story_id} — peer disparu depuis {elapsed}s" + transition →CRASHED État terminal, alerte critique
ERR-293-08 Idempotency replay (même payload) C4.lord_check_idempotency → REPLAY C4 Retour résultat précédent, {event: "IDEMPOTENCY_REPLAY", key: K} Aucun double effet
ERR-293-09 Idempotency conflit (payload ≠) C4.lord_check_idempotency → CONFLICT C4 "Rejet: conflit idempotence (clé {K}, payload différent)" + log Aucun effet
ERR-293-10 First liveness timeout C5.elapsed > first_liveness_timeout C5 Transition →START_FAILED, cleanup, alerte Session nettoyée

Format log de rejet §6.1 — Décision pour rejets de commandes Sovereign (résolution point spec review §6.1) : - peer_id : "sovereign" (identifiant convention One Ring pour les commandes entrantes) - message_type : nom de la commande en MAJUSCULES ("START", "RESPOND", "PAUSE", etc.)

Exemple pour ERR-293-01 :

{"timestamp":"2026-03-30T10:15:30Z","peer_id":"sovereign","message_type":"START","reason":"quota_exceeded (5/5)","story_id":"PD-299","event":"COMMAND_REJECTED"}

7. Impacts sécurité

Risque Mitigation Composant Observable
Exécution hors scope par One Ring (lecture repo, build, Jira, GitLab, Vault) INV-293-01 : whitelist stricte 7 commandes dans C6. C5 n'importe aucun module métier. Vérifiable par analyse statique (TC-NR-02). C6, C5 grep -r sur imports/sources de C5
Contamination inter-story (credentials, contexte) INV-293-03 : isolation process OS. Chaque Ringbearer = process claude -p séparé avec son propre environnement. Aucun fichier partagé. C5 TC-NOM-09, TC-NR-03
Données sensibles dans escalade_text/export (RGPD) Décision (résolution point spec review §9) : C4 stocke escalade_text uniquement dans .gov-lord-state.json (fichier local non commité, ajouté au .gitignore). Les logs d'audit ne contiennent que escalade_id + story_id, jamais le texte brut. L'export probatoire anonymise les escalade_text (SHA-256 tronqué). C4 Vérifiable par grep sur .gov-lord-audit.jsonl
Injection dans story_id/project_code C2 : validation regex stricte ^PD-[0-9]{1,4}$ et enum project_code. Aucun passage de valeur non validée à claude -p ou au broker. C2 TC-NEG-01..10
Crash persistance (corruption state) C4 : écriture atomique via fichier temporaire + mv. Conforme JSON Schema §5.4 validé par jq à chaque écriture. C4 Test corruption : kill pendant write, vérifier intégrité
Exposition fichier state hors périmètre Décision : .gov-lord-state.json est stocké à la racine du repo ia-governance (pas dans un epic). Ajouté au .gitignore. Justification : c'est un état runtime global, pas un artefact de story. C4 Vérification .gitignore

8. Hypothèses techniques

ID Hypothèse Impact si faux Mitigation
H-TECH-01 claude-peers-mcp@^1.x expose list_peers(), send_message(), get_messages() en MCP tools Blocage complet de C3 C3 est une couche d'abstraction — si l'API diffère, seul C3 est adapté. Mock disponible pour tests.
H-TECH-02 claude-peers-mcp fonctionne en local MacBook sans serveur distant Blocage C3 Vérifier à l'installation. Fallback : fichiers partagés (hors scope v1).
H-TECH-03 Bash 5.x disponible (declare -A pour tableaux associatifs) C1 non fonctionnel macOS avec brew install bash. Vérifiable : bash --version.
H-TECH-04 grep -P (Perl regex) disponible pour validation Unicode \p{C} C2 : validation peer_id/escalade_text dégradée brew install grep (GNU grep). Fallback : validation plus permissive avec exclusion manuelle des contrôles communs.
H-TECH-05 jq installé (manipulation JSON) C4 non fonctionnel brew install jq. Vérification au démarrage de C5.
H-TECH-06 bats-core installé (framework de test Bash) C7 non exécutable brew install bats-core. CI : step d'installation dans le pipeline.
H-TECH-07 /remote-control fonctionne sur iPhone/Safari pour Claude Code CA-06 dégradé (contrôle desktop uniquement) Test manuel. Si non disponible, CA-06 est documenté comme limitation v1.
H-TECH-08 Timestamp strictement UTC Z — les offsets non-zero (+02:00, -05:00) sont rejetés par C2 Aucun — c'est une décision de design, pas une hypothèse externe Résolution de l'ambiguïté spec review §5.1. Regex : ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$
H-TECH-08bis Horloge unique locale — émetteur (Ringbearer) et récepteur (One Ring) sont sur le même MacBook Le SLA P95 de CA-03 est fiable car aucun décalage d'horloge possible Résolution de l'hypothèse dangereuse spec review. Si multi-machine en v2, NTP obligatoire.
H-TECH-09 peer_id en état STARTING — valeur sentinelle "pending-{story_id}" Conforme minLength:1 du schema §5.4 Résolution de l'ambiguïté spec review §5.4 vs §5.2. Remplacé par le vrai peer_id dès RUNNING.

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

9.1 Risques opérationnels

# Risque Probabilité Impact Mitigation
R1 claude-peers-mcp API non documentée ou instable Élevée (v1.x early) Bloquant C3 abstraction + mocks complets pour tests
R2 Ringbearer ne produit pas de STATUS_UPDATE (protocole non natif dans /gov) Élevée STARTING→START_FAILED systématique H-293-04 de la spec. Si confirmé, story séparée de compatibilité /gov.
R3 Timing start_detection_timeout trop court (30s) si MacBook chargé Moyenne START_FAILED faux positif Paramètre configurable (min 5, max 120). Logging du elapsed pour calibrage.
R4 Multi-escalade : Ringbearer n'attend pas le PO_RESPONSE pour continuer Moyenne Incohérence état Le message PAUSE explicite depuis One Ring force l'arrêt. Documenter dans le REX.

9.2 Dette technique acceptée

# Dette Justification Story de résolution
D1 Pas d'état ESCALADE_EXPIRED — l'escalade reste ouverte indéfiniment avec alertes répétées Hors périmètre v1 (spec §4.1) Évolution future
D2 Test TC-NOM-04 (P95 SLA) dépend d'un mock broker avec délai calibré, pas du vrai broker Le vrai broker introduirait du non-déterminisme Validation E2E manuelle
D3 grep -P pour Unicode — non disponible nativement sur macOS (nécessite GNU grep) Seul moyen de valider \p{C} en Bash Documenter dans les prérequis d'installation

9.3 Pièges d'implémentation

  • Écriture atomique : mv n'est atomique que sur le même filesystem. .gov-lord-state.json.tmp doit être dans le même répertoire que .gov-lord-state.json.
  • Boucle de supervision : sleep en Bash bloque les signaux. Utiliser sleep $interval & + wait $! pour que le trap SIGINT/SIGTERM soit réactif.
  • Regex Bash : [[ $val =~ ^PD-[0-9]{1,4}$ ]] — attention, les accolades {1,4} sont interprétées par le shell en Bash 3. Tester avec Bash 5 impérativement.
  • jq concurrent : Si la supervision loop écrit le state pendant qu'une commande le lit, risque de lecture partielle. Utiliser un lock file (flock) autour des écritures C4.
  • Idempotency payload_hash : Utiliser sha256sum sur les arguments normalisés (story_id + project_code triés) pour un hash déterministe.

9.4 Décisions sur les points de la specification-review

Point review Gravité Décision
Contradiction INV-293-14 vs §5.1 (ordre gardes) Bloquant §5.1 points 3-4 font foi. start : quota→idempotence→format. Autres : idempotence→format. INV-293-14 est lu comme "ordre contractuel par commande".
Contradiction diagramme séquence vs INV-293-02 (flèches directes O→R) Bloquant INV-293-02 fait foi. Les flèches directes du diagramme §5bis sont des raccourcis visuels. Toute communication passe par C3 (broker-adapter). Le diagramme enrichi §2ter corrige cette représentation.
Incohérence CA-02 mappé à TC-NOM-08 Majeur TC-NOM-14 ajouté pour couvrir le chemin nominal de CA-02 (start complet jusqu'à RUNNING). TC-NOM-08 couvre uniquement l'idempotence (INV-293-12).
Incohérence INV-293-10 mappé à CA-01 Majeur INV-293-10 (terminaux immuables) est couvert par TC-INV-02, pas par CA-01. La matrice de couverture dans le plan utilise le mapping corrigé.
peer_id/message_type non définis pour rejets Sovereign (§6.1) Majeur Convention : peer_id="sovereign", message_type=<COMMAND_NAME> (START, RESPOND, etc.). Documenté dans C2 (validator).
Ambiguïté timestamp "offset UTC" Mineur Strictement Z uniquement. Tout offset non-zero est rejeté (H-TECH-08).
Hypothèse horloge SLA CA-03 Majeur Horloge locale unique MacBook (H-TECH-08bis). Pas de décalage possible en v1 (single machine).
Tests TC-NR-*/TC-NEG-* non détaillés Given/When/Then Majeur TC-NEG-01..10 : chacun est une variante de C2.lord_validate_*() avec une entrée invalide spécifique. Le niveau de détail est dans la matrice §5. TC-NR-* : détaillés dans le mapping §5 avec observables.
peer_id obligatoire en STARTING (schema §5.4) Majeur Valeur sentinelle "pending-{story_id}" (H-TECH-09). Conforme minLength:1.
Risque RGPD escalade_text dans exports Majeur escalade_text stocké uniquement dans .gov-lord-state.json (local, .gitignore). Logs d'audit : escalade_id seulement. Export probatoire : texte anonymisé (SHA-256 tronqué).

10. Hors périmètre

Exclusion Justification
Modification du workflow /gov existant INV-293-07, CA-08. Le Ringbearer exécute /gov tel quel.
Communication directe Ringbearer↔Ringbearer Spec §2 exclu. Toute communication transite par One Ring via broker.
Application iOS native Spec §2 exclu. Le contrôle mobile passe par /remote-control (web).
État ESCALADE_EXPIRED Spec §4.1. Hors périmètre v1.
Mutualisation/partage de credentials entre sessions Spec §2 exclu. Chaque Ringbearer a son propre contexte.
Tests E2E multi-machines MacBook local uniquement en v1. Les tests d'intégration utilisent des mocks broker.

Périmètre de test

Niveau de test In scope Hors scope (justification)
Unitaire C1 (matrice transitions, terminaux), C2 (10 validateurs + log rejet), C4 (CRUD state, FIFO escalade, idempotency)
Intégration C5 avec mocks C3 : supervision loop, gardes commandes, réconciliation. Interactions C5↔C1↔C4
E2E TC-NR-01 : 1 story complète /gov standalone vs orchestrée Multi-stories simultanées E2E (trop long, couvert par intégration)
Perf TC-NOM-04 : P95 SLA escalade (mock broker) Perf sous charge réelle (5 stories simultanées) — mesure manuelle
Manuel TC-MAN-01 : iPhone/Safari /remote-control

Couverture minimale : 80% sur le périmètre in scope (C1-C6). C7 est le module de tests lui-même.


Maintenant le fichier code-contracts.yaml corrigé (intégrant les retours de la review) :


# PD-293-code-contracts.yaml
# Frontières de code entre agents — PD-293 One Ring Orchestration
# Cohérent avec le plan d'implémentation v2

code_contracts:
  - module: state-machine
    owner_agent: agent-fsm
    interfaces:
      - "lord_transition(story_id, from_state, to_state) -> OK | REJECTED"
      - "lord_is_terminal(state) -> true | false"
      - "lord_allowed_transitions(from_state) -> list[to_state]"
    invariants:
      - "INV-293-09 : Toutes transitions non listees en §5.2 sont rejetees."
      - "INV-293-10 : Etats terminaux (DONE, ABORTED, CRASHED, START_FAILED) n'acceptent aucune transition sortante."
      - "INV-293-06 : Depuis ESCALADED, seules les transitions vers RUNNING (PO_RESPONSE), ABORTED et CRASHED sont autorisees."
    forbidden:
      - "Ajouter un etat non contractuel (ex: ESCALADE_PENDING, ESCALADE_EXPIRED)."
      - "Accepter une transition non listee dans la matrice §5.2."
      - "Modifier un etat terminal apres qu'il a ete atteint."
    architectural_decisions:
      - decision: "Matrice de transitions en tableau associatif Bash (declare -A)"
        rationale: "Bash 5+ supporte les tableaux associatifs. Lookup O(1) par cle 'FROM->TO'. Plus maintenable qu'une cascade de if/case."
        alternatives_considered:
          - "Case/switch imbriques (lisible mais verbose, risque d'oubli d'un cas)"
          - "Fichier YAML externe parse par yq (overhead inutile pour une matrice statique)"
        trade_offs:
          - "Avantage : Lookup O(1), matrice exhaustive verifiable par diff avec §5.2"
          - "Inconvenient : Requiert Bash 5+ (brew install bash)"
    files:
      - "scripts/lib/lord-state-machine.sh"

  - module: validator
    owner_agent: agent-validator
    interfaces:
      - "lord_validate_story_id(value) -> OK | REJECTED"
      - "lord_validate_project_code(value) -> OK | REJECTED"
      - "lord_validate_message_type(value) -> OK | REJECTED"
      - "lord_validate_peer_id(value) -> OK | REJECTED"
      - "lord_validate_timestamp(value) -> OK | REJECTED"
      - "lord_validate_escalade_text(value) -> OK | REJECTED"
      - "lord_validate_summary_text(value) -> OK | REJECTED"
      - "lord_validate_idempotency_key(value) -> OK | REJECTED"
      - "lord_log_rejection(timestamp, peer_id, message_type, reason, story_id, event) -> void"
    invariants:
      - "INV-293-05 : Chaque champ est valide selon §5.1 (regex, enum, bornes). Rejet deterministe et journalise en JSONL §6.1."
      - "Timestamp RFC3339 strictement UTC : seul le suffixe Z est accepte (decision H-TECH-08)."
      - "Les rejets de commandes Sovereign utilisent peer_id='sovereign' et message_type=nom de la commande en majuscules (decision spec-review §6.1)."
    forbidden:
      - "Accepter un champ invalide sans log de rejet §6.1."
      - "Accepter un timestamp avec offset non-Z (ex: +02:00)."
      - "Valider partiellement un message (tous les champs obligatoires doivent etre verifies)."
    architectural_decisions:
      - decision: "Validation par regex Bash natif ([[ =~ ]]) sauf peer_id/escalade_text (grep -P)"
        rationale: "Les regex §5.1 simples (story_id, project_code, etc.) sont faisables en Bash natif. peer_id et escalade_text necessitent Unicode property class \p{C} via grep -P (GNU grep)."
        alternatives_considered:
          - "Python script dedie (overhead de process pour chaque validation)"
          - "jq schema validation (ne supporte pas les regex)"
        trade_offs:
          - "Avantage : Zero dependance pour 6/8 validateurs, performance native"
          - "Inconvenient : Necessite GNU grep installe pour 2 validateurs (H-TECH-04)"
    files:
      - "scripts/lib/lord-validator.sh"

  - module: broker-adapter
    owner_agent: agent-broker
    interfaces:
      - "lord_broker_list_peers() -> json_array | ERROR"
      - "lord_broker_send_message(peer_id, message_type, payload) -> OK | ERROR"
      - "lord_broker_get_messages(peer_id) -> json_array | ERROR"
      - "lord_broker_status() -> UP | DEGRADED | DOWN"
      - "lord_broker_reconnect() -> OK | STILL_DEGRADED"
    invariants:
      - "INV-293-02 : Toute communication One Ring <-> Ringbearer passe par le broker. Aucun acces direct aux repos cibles."
      - "Reconnexion exponentielle bornee : 1s, 2s, 4s, 8s, 10s (plafond). Mode DEGRADED explicite tant que indisponible."
      - "Les messages recus sont valides par C2 (lord-validator) avant traitement dans C5."
    forbidden:
      - "Acceder directement a un repo cible (cd, git, curl vers GitLab/Jira/Vault)."
      - "Communiquer avec un Ringbearer sans passer par le broker."
      - "Ignorer silencieusement une erreur broker (toute erreur doit basculer en DEGRADED)."
    architectural_decisions:
      - decision: "Couche d'abstraction Bash wrappant claude-peers-mcp MCP tools"
        rationale: "Isole le risque H-TECH-01/02. Si l'API differe, seul ce module est adapte. Mockable pour tests."
        alternatives_considered:
          - "Appels MCP directs dans l'orchestrateur (couplage fort, impossible a mocker)"
          - "IPC par fichiers partages (fallback si broker indisponible  hors scope v1)"
        trade_offs:
          - "Avantage : Abstraction propre, testable, isolee"
          - "Inconvenient : Overhead d'une couche supplementaire"
    files:
      - "scripts/lib/lord-broker.sh"

  - module: persistence
    owner_agent: agent-persistence
    interfaces:
      - "lord_state_read() -> json_object"
      - "lord_state_write(json_object) -> OK | ERROR"
      - "lord_count_active_stories() -> integer"
      - "lord_create_story(story_id, project_code, state, peer_id) -> OK | ERROR"
      - "lord_update_story(story_id, updates) -> OK | ERROR"
      - "lord_get_story(story_id) -> json_object | NONE"

exec
/bin/zsh -lc "sed -n '520,920p' docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan.md" in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance
 succeeded in 698ms:
pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable
pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable
      - "lord_get_story(story_id) -> json_object | NONE"
      - "lord_enqueue_escalade(story_id, escalade_id, text, timestamp) -> OK | ERROR"
      - "lord_dequeue_oldest_escalade(story_id) -> escalade_json | NONE"
      - "lord_count_open_escalades(story_id) -> integer"
      - "lord_check_idempotency(key, payload_hash) -> NEW | REPLAY(result_hash) | CONFLICT"
      - "lord_record_idempotency(key, command, story_id, payload_hash, result_hash, ttl) -> OK"
      - "lord_purge_expired_idempotency() -> count_purged"
    invariants:
      - "INV-293-13 : escalade_queue est un tableau FIFO ordonne par created_at. dequeue retire l'element OPEN le plus ancien pour la story cible."
      - "INV-293-12 : Idempotency cache verifie key + payload_hash. Meme key + meme payload = REPLAY. Meme key + payload different = CONFLICT."
      - "Le fichier .gov-lord-state.json est conforme au schema JSON §5.4 a chaque ecriture."
      - "Purge des entrees idempotency expirees a chaque demarrage et a chaque lecture du cache."
      - "Ecriture atomique : fichier temporaire + mv (meme filesystem)."
      - "Acces concurrent protege par flock."
    forbidden:
      - "Ecrire un fichier .gov-lord-state.json non conforme au schema §5.4."
      - "Stocker escalade_text dans les logs d'audit (uniquement escalade_id + story_id)."
      - "Acceder a des donnees d'une story depuis une autre story sans indexation explicite par story_id."
      - "Ecrire directement le fichier state sans passer par fichier temporaire + mv."
    architectural_decisions:
      - decision: "JSON manipule par jq + ecriture atomique tmp+mv + flock"
        rationale: "jq est le standard JSON en shell. L'ecriture atomique empeche la corruption. flock protege les acces concurrents entre supervision loop et commandes CLI."
        alternatives_considered:
          - "Python json module (plus puissant mais overhead de process)"
          - "SQLite (overkill pour 5 stories max)"
        trade_offs:
          - "Avantage : Expressif, robuste, large adoption, pas de corruption"
          - "Inconvenient : Dependance jq (H-TECH-05)"
      - decision: "Stockage a la racine du repo ia-governance (pas dans un epic)"
        rationale: "Etat runtime global, pas un artefact de story. Ajoute au .gitignore."
    files:
      - "scripts/lib/lord-persistence.sh"
      - ".gov-lord-state.json"
      - ".gov-lord-audit.jsonl"

  - module: orchestrator
    owner_agent: agent-orchestrator
    interfaces:
      - "lord_start(story_id, project_code, idempotency_key) -> OK | ERROR"
      - "lord_status(story_id?) -> json_output"
      - "lord_escalade(story_id?) -> json_output"
      - "lord_respond(story_id, response_text, idempotency_key) -> OK | ERROR"
      - "lord_pause(story_id, reason?, idempotency_key) -> OK | ERROR"
      - "lord_resume(story_id, idempotency_key) -> OK | ERROR"
      - "lord_stop(story_id, reason?, idempotency_key) -> OK | ERROR"
      - "lord_supervision_loop() -> never_returns"
    invariants:
      - "INV-293-14 : Ordre des gardes pour start : quota -> idempotence -> format -> story non-dupliquee."
      - "INV-293-14 : Ordre des gardes pour stop/respond/pause/resume : idempotence -> format."
      - "INV-293-08 : Crash detecte en <= 2 cycles de polling (missed_polls >= crash_detection_cycles_max)."
      - "INV-293-11 : Reconciliation periodique entre etat local (C4) et peers observes (C3) a chaque cycle."
      - "INV-293-01 : Aucune action hors interface/routage. Aucun acces direct aux repos cibles."
      - "Multi-escalade : transition ESCALADED->RUNNING uniquement quand TOUTES les escalades OPEN de la story sont traitees."
    forbidden:
      - "Executer une commande systeme hors perimetre (cd vers repo, git, curl Jira/GitLab/Vault)."
      - "Contourner l'ordre des gardes contractuel."
      - "Modifier l'etat d'une story sans passer par C1 (state-machine)."
      - "Communiquer avec un Ringbearer sans passer par C3 (broker-adapter)."
      - "Lire ou ecrire le state sans passer par C4 (persistence)."
      - "Importer jira-api.sh, state.sh, ou tout module du workflow /gov existant."
    architectural_decisions:
      - decision: "Boucle de supervision en background avec trap + sleep interruptible"
        rationale: "Le One Ring doit tourner en continu. Trap SIGINT/SIGTERM pour persistance propre. Sleep via 'sleep N & wait $!' pour reactivite signal."
        alternatives_considered:
          - "Cron job (trop lent pour SLA 5s)"
          - "Daemon launchd (overhead infra pour outil dev local)"
        trade_offs:
          - "Avantage : Simple, pas d'infra additionnelle"
          - "Inconvenient : Depend du terminal ouvert (acceptable MacBook local)"
    files:
      - "scripts/gov-lord.sh"

  - module: commands
    owner_agent: agent-skill
    interfaces:
      - "Skill Claude Code /gov-lord : parse arguments, delegue a C5"
    invariants:
      - "INV-293-01 : Whitelist de 7 commandes (start, status, escalade, respond, pause, resume, stop). Toute autre commande retourne un message de blocage explicite."
      - "CA-05 : Message de blocage coherent et systematique pour les commandes hors scope."
    forbidden:
      - "Ajouter une commande qui execute une action metier (lecture repo, build, Jira, GitLab)."
      - "Deleguer une commande non reconnue a C5 sans blocage."
    files:
      - ".claude/commands/gov-lord.md"

  - module: tests
    owner_agent: agent-tests
    interfaces:
      - "Suites BATS : tests/lord/state-machine.bats, tests/lord/validator.bats, tests/lord/persistence.bats, tests/lord/orchestrator.bats, tests/lord/integration.bats, tests/lord/negative.bats, tests/lord/non-regression.bats"
    invariants:
      - "Chaque test produit un resultat deterministe (pas de dependance a l'horloge reelle sauf TC-NOM-04)."
      - "Les tests utilisent un mock broker (pas de dependance a claude-peers-mcp en CI)."
      - "TC-INV-01 teste exhaustivement la matrice 8x8 (64 paires) de transitions."
    forbidden:
      - "Tester avec un broker reel sans fallback mock."
      - "Ignorer un test d'invariant (TC-INV-*) ou de non-regression (TC-NR-*)."
      - "Dependre de fichiers ou d'etat existant dans .gov-lord-state.json entre tests (setup/teardown obligatoire)."
    architectural_decisions:
      - decision: "BATS (Bash Automated Testing System) avec mock broker en fichier"
        rationale: "BATS est le standard de facto pour Bash. Le mock broker est un ensemble de fonctions shell qui simulent list_peers/send_message/get_messages via fichiers JSON temporaires."
        alternatives_considered:
          - "shunit2 (moins maintenu)"
          - "pytest + subprocess (overhead Python pour Bash)"
        trade_offs:
          - "Avantage : Natif Bash, large communaute, TAP output, facile CI"
          - "Inconvenient : Dependance externe bats-core (H-TECH-06)"
    files:
      - "tests/lord/**/*.bats"
      - "tests/lord/mocks/**"
      - "tests/lord/fixtures/**"

Le plan est complet et prêt pour soumission à Gate 5. Les 4 points bloquants de la review précédente sont résolus : 1. Contenu complet (plus un résumé 12 lignes) — toutes les sections détaillées 2. Mappings invariants→mécanismes — 14 invariants avec composant, observable, risque 3. TC-NOM-14 ajouté — couvre CA-02 (démarrage nominal) 4. Points d'observation par test — 40+ tests avec mécanismes et observables explicites

Les 10 écarts de la specification-review sont traités en §9.4 avec décisions documentées.

exec /bin/zsh -lc "sed -n '1,260p' docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-specification.md" in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance exec /bin/zsh -lc "sed -n '1,260p' docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md" in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance succeeded in 865ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable

PD-293 - Scenarios de tests contractuels (v2 corriges)

1. References

  • Specification : PD-293-specification.md
  • Epic : Reference epique non fournie dans l'entree (donnee manquante)

2. Matrice de couverture

ID Invariant ID Critere ID Test Couverture Commentaire
INV-293-01 CA-05 TC-NOM-05 Oui Les commandes hors scope sont refusees avec message explicite et sans action metier.
INV-293-02 CA-01 TC-NOM-01 Oui Les observations passent par broker/peers; aucun acces direct aux repos cibles.
INV-293-03 CA-08 TC-NOM-09 Oui Isolation stricte des stories et absence de contamination inter-story.
INV-293-04 CA-09 TC-NOM-02 Oui La limite de 5 sessions actives est appliquee strictement.
INV-293-05 CA-10 TC-NOM-10 Oui Validation stricte du schema canonique et rejet deterministe des invalides.
INV-293-06 CA-04 TC-NOM-03 Oui Une escalade bloque la progression jusqu'a reception d'une reponse PO valide.
INV-293-07 CA-08 TC-NR-01 Oui Le workflow /gov reste inchange et fonctionnel en execution Ringbearer.
INV-293-08 CA-07 TC-NOM-06 Oui Passage en CRASHED et alerte en <=2 cycles de polling.
INV-293-09 CA-04 TC-INV-01 Oui Toutes transitions hors machine d'etats sont refusees.
INV-293-10 CA-01 TC-INV-02 Oui Les etats terminaux sont immuables (aucune transition sortante).
INV-293-11 CA-01 TC-NOM-07 Oui Reconciliation periodique detecte et corrige les ecarts de vue consolidee.
INV-293-12 CA-02 TC-NOM-08 Oui Commandes start/stop/respond/pause/resume idempotentes par cle dediee.
INV-293-13 CA-11 TC-NOM-13 Oui Les escalades simultanees sont traitees en FIFO sans remplacement.
INV-293-14 CA-09 TC-INV-03 Oui L'ordre de gardes quota -> doublon -> format est respecte sur start.
INV-293-05 CA-03 TC-NOM-04 Oui Les messages d'escalade valides sont visibles dans la fenetre SLA P95.
INV-293-02 CA-06 TC-MAN-01 Manuel Le controle mobile utilise les memes commandes d'orchestration via One Ring.
INV-293-05 CA-12 TC-ERR-10 Oui Reconnexion broker geree en mode degrade explicite puis reprise automatique.

3. Scenarios de test - Flux nominaux

TEST-ID: TC-NOM-01
Reference spec: INV-293-02, CA-01

GIVEN
  - One Ring demarre avec broker disponible
  - 2 Ringbearers actifs enregistres via peers
WHEN
  - Le Sovereign execute `/gov-lord status`
THEN
  - La sortie contient la liste consolidee des 2 stories actives
  - Chaque entree contient au minimum `story_id`, etat contractuel, horodatage de derniere observation
AND
  - Le journal d'audit trace une consultation broker (listing peers)
  - Aucune operation sur repo cible n'est enregistree
TEST-ID: TC-NOM-02
Reference spec: INV-293-04, CA-09

GIVEN
  - 5 Ringbearers actifs (`max_ringbearers=5`)
WHEN
  - Le Sovereign execute un 6e `/gov-lord start PD-299 backend`
THEN
  - La commande est rejetee explicitement (quota atteint)
  - Aucun nouveau Ringbearer n'est cree
AND
  - Le nombre d'actifs reste strictement a 5
  - Le rejet est journalise
TEST-ID: TC-NOM-03
Reference spec: INV-293-06, CA-04

GIVEN
  - Une story est en etat `RUNNING`
  - Une escalade valide est emise (`message_type=ESCALADE`, `story_id` valide, `escalade_text` non vide)
WHEN
  - Le Sovereign execute `/gov-lord respond <story_id> <reponse>` avec payload valide
THEN
  - La story passe `ESCALADED -> RUNNING`
  - La reprise est observable par un `STATUS_UPDATE` ulterieur
AND
  - La progression etait bloquee tant que `PO_RESPONSE` n'etait pas recue
TEST-ID: TC-NOM-04
Reference spec: CA-03, INV-293-05

GIVEN
  - Campagne de 100 escalades valides en conditions stables locales
  - Horodatages emission/reception RFC3339 UTC collectes pour chaque escalade
WHEN
  - Les 100 escalades sont traitees par One Ring
THEN
  - Le P95 du delai de visibilite est <= `escalade_visibility_sla` (defaut 5s)
AND
  - Les mesures horodatees sont exportees pour preuve
TEST-ID: TC-NOM-05
Reference spec: INV-293-01, CA-05

GIVEN
  - One Ring actif
WHEN
  - Le Sovereign soumet une commande hors scope (ex: lecture repo, build, Jira, GitLab, Vault)
THEN
  - One Ring retourne un message de blocage explicite et coherent
  - Aucune action metier/projet n'est executee
AND
  - Un evenement d'audit de refus est present
TEST-ID: TC-NOM-06
Reference spec: INV-293-08, CA-07

GIVEN
  - Une story en `RUNNING` avec peer precedemment observe
  - Le peer devient absent
WHEN
  - Deux cycles de polling successifs s'executent (`peer_poll_interval` defaut 10s)
THEN
  - L'etat passe en `CRASHED` en <= 2 cycles (<=20s par defaut)
  - Une alerte Sovereign est emise
AND
  - La transition est tracee avec timestamp
TEST-ID: TC-NOM-07
Reference spec: INV-293-11, CA-01

GIVEN
  - Etat consolide One Ring volontairement desynchronise (ex: peer present non reflechi localement)
WHEN
  - Une reconciliation periodique est declenchee
THEN
  - La vue consolidee est alignee avec les peers observes
  - Les ecarts detectes sont explicitement signales
AND
  - L'operation est journalisee sans modifier les stories terminales
TEST-ID: TC-NOM-08
Reference spec: INV-293-12, CA-02

GIVEN
  - Commande `start` valide avec `idempotency_key=K1`
WHEN
  - La meme commande est rejouee avec la meme cle dans la fenetre `idempotency_ttl`
THEN
  - Le second resultat est deterministe et identique au premier
  - Aucun Ringbearer supplementaire n'est cree
AND
  - Le dedoublonnage est trace en audit
TEST-ID: TC-NOM-09
Reference spec: INV-293-03, CA-08

GIVEN
  - 2 stories actives (`PD-293`, `PD-294`) avec contexts et credentials distincts
WHEN
  - Une escalade/reponse est emise pour `PD-293`
THEN
  - Seule `PD-293` change d'etat selon la machine contractuelle
  - `PD-294` conserve son etat et son contexte sans impact
AND
  - Aucun partage de credential ou message inter-story n'est observe
TEST-ID: TC-NOM-10
Reference spec: INV-293-05, CA-10

GIVEN
  - Un lot de messages valides couvrant tous les champs §5.1
WHEN
  - Le lot est ingere par One Ring
THEN
  - Tous les messages valides sont acceptes et traites selon leur type
  - Aucun rejet injustifie n'est observe
AND
  - Le journal d'audit associe type de message, story cible, timestamp
TEST-ID: TC-NOM-12
Reference spec: CA-02, INV-293-09

GIVEN
  - Story en `RUNNING`
WHEN
  - Le Sovereign execute `/gov-lord pause <story_id>` puis `/gov-lord resume <story_id>`
THEN
  - Transitions observees: `RUNNING -> PAUSED -> RUNNING`
  - Aucune perte de contexte n'est observee
AND
  - Les deux commandes sont journalisees avec idempotency_key
TEST-ID: TC-NOM-13
Reference spec: INV-293-13, CA-11

GIVEN
  - Trois escalades ouvertes pour la meme story avec timestamps t1 < t2 < t3
WHEN
  - Le Sovereign execute trois commandes `respond` successives valides
THEN
  - Les escalades sont cloturees dans l'ordre FIFO t1, puis t2, puis t3
  - Aucune escalade n'est remplacee ni perdue
AND
  - La file `escalade_queue` conserve une tracabilite complete

4. Scenarios de test - Cas d'erreur

TEST-ID: TC-ERR-01
Reference spec: ERR-293-01

GIVEN
  - 5 Ringbearers actifs
WHEN
  - Requete `start` additionnelle
THEN
  - Rejet explicite `quota atteint`
  - Aucun effet secondaire de creation
TEST-ID: TC-ERR-02
Reference spec: ERR-293-02

GIVEN
  - Story `PD-293` deja active
WHEN
  - Nouvelle commande `start PD-293` (cle differente)
THEN
  - Rejet explicite idempotent (`story deja active`)
  - Pas de second peer pour la meme story
TEST-ID: TC-ERR-03
Reference spec: ERR-293-03, §6.1

GIVEN
  - Message entrant sans `story_id` valide (absent ou non conforme regex)
WHEN
  - One Ring tente la validation de schema
THEN
  - Message rejete
  - Erreur journalisee au format JSONL obligatoire avec `timestamp`, `peer_id`, `message_type`, `reason`
  - Aucun changement d'etat story
TEST-ID: TC-ERR-04

 succeeded in 866ms:
pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable
pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable
# PD-293 — Orchestration contractuelle multi-stories “One Ring” via claude-peers et controle mobile (v2 corrigee)

## 1. Objectif

La User Story PD-293 permet a un Sovereign (humain) de piloter, depuis une session unique (One Ring), jusqu'a 5 Ringbearers executant chacun un workflow `/gov PD-XXX [projet]`, avec :

- supervision consolidee des stories,
- remontee et traitement des escalades bloquantes,
- pilotage a distance via `/remote-control` (iPhone/Safari),
- separation stricte des responsabilites (One Ring route, Ringbearers executent).

## 2. Perimetre / Hors perimetre

### Inclus

- Orchestration multi-stories par une session One Ring unique.
- Commandes `/gov-lord` : `start`, `status`, `stop`, `escalade`, `respond`, `pause`, `resume`.
- Protocole de messagerie Ringbearer ↔ One Ring (types, validation, routage, dedoublonnage).
- Detection de disparition/crash d'un Ringbearer et signalement au Sovereign.
- Affichage consolide de l'etat de N stories.
- Controle mobile du One Ring via `/remote-control`.
- Persistance locale d'un etat consolide minimal `.gov-lord-state.json`.

### Exclu

- Toute modification du workflow `/gov` existant.
- Toute execution metier/projet par le One Ring (lecture code, ecriture fichier, build, tests, Jira, GitLab, Vault).
- Communication directe Ringbearer ↔ Ringbearer.
- Application iOS native dediee.
- Mutualisation/partage de credentials entre sessions.
- Etat `ESCALADE_EXPIRED` en v1 (hors perimetre, evolution future).

## 3. Definitions

| Terme | Definition |
| -- | -- |
| Sovereign | Humain decisionnaire final (PO/Architecte). |
| One Ring | Session d'orchestration unique, strictement interface/routeur. |
| Ringbearer | Session autonome dediee a une story, executant `/gov`. |
| Story | Identifiant de workflow valide selon regex normative `^PD-[0-9]{1,4}$`. |
| Broker | Service `claude-peers-mcp` local assurant enregistrement/listing/messagerie des peers. |
| Escalade | Demande bloquante d'un Ringbearer necessitant reponse humaine. |
| File d'escalades | Queue FIFO des escalades ouvertes (multi-escalade simultanee). |
| Etat Ringbearer | Etat contractuel du cycle de vie d'une story orchestree par One Ring. |

## 4. Invariants (non negociables)

| ID | Regle | Justification |
| -- | ----- | ------------- |
| INV-293-01 | One Ring n'execute aucune action hors interface/routage ; toute demande hors scope est refusee avec message explicite. | Separation des pouvoirs, securite, tracabilite. |
| INV-293-02 | One Ring ne communique avec les stories qu'au travers du broker/peers ; aucune action directe sur les repos cibles. | Confinement operationnel. |
| INV-293-03 | Un Ringbearer est isole (contexte, credentials, execution) ; aucune contamination inter-story. | Non-regression et robustesse. |
| INV-293-04 | Maximum 5 Ringbearers actifs simultanes. Au-dela : rejet explicite. | Limite de capacite declaree. |
| INV-293-05 | Tout message doit etre valide selon le modele §5.1 ; sinon rejet deterministe et journalisation d'erreur au format contractuel §6.1. | Contrat protocolaire testable. |
| INV-293-06 | Une escalade suspend la progression de la story jusqu'a reception d'un `PO_RESPONSE` valide. | Gouvernance humaine obligatoire. |
| INV-293-07 | Le workflow `/gov` est consomme tel quel ; PD-293 ne modifie pas son comportement interne. | Respect du perimetre. |
| INV-293-08 | One Ring signale un Ringbearer disparu en etat `CRASHED` en 2 cycles de polling maximum (<= 20s par defaut). | Resilience operationnelle. |
| INV-293-09 | Les transitions d'etat Ringbearer respectent strictement la machine d'etats §5.2 ; toute transition non listee est interdite. | Cohesion formelle. |
| INV-293-10 | Etats terminaux (`DONE`, `ABORTED`, `CRASHED`, `START_FAILED`) : `-> * INTERDITE (etat terminal, resolution manuelle uniquement)`. | Fermeture explicite imposee. |
| INV-293-11 | Reconciliation periodique obligatoire entre etat consolide One Ring et peers observes. | Tolerance aux crashes/ecarts. |
| INV-293-12 | Idempotence des commandes de controle (`start`, `stop`, `respond`, `pause`, `resume`) par cle dediee (§5.1). | Retry-safe en environnement distribue. |
| INV-293-13 | Multi-escalade simultanee geree en FIFO ; aucune escalade n'en remplace une autre. | Determinisme de traitement et auditabilite. |
| INV-293-14 | Ordre de gardes de commande contractuel : quota -> doublon idempotence -> validation format. | Comportement stable et testable. |

### 4.1 Notes de coherence etats

- En v1, **aucun** etat `ESCALADE_PENDING` n'est defini.
- En v1, **aucun** etat `ESCALADE_EXPIRED` n'est defini (hors perimetre, evolution future).

### 4.2 Commandes `/gov-lord` normatives

- `/gov-lord start <story_id> <project_code> [--idempotency-key <key>]`
- `/gov-lord status [--story <story_id>]`
- `/gov-lord escalade [--story <story_id>]`
- `/gov-lord respond <story_id> <reponse> [--idempotency-key <key>]`
- `/gov-lord pause <story_id> [--reason <texte>] [--idempotency-key <key>]`
- `/gov-lord resume <story_id> [--idempotency-key <key>]`
- `/gov-lord stop <story_id> [--reason <texte>] [--idempotency-key <key>]`

## 5. Flux nominaux

### 5.1 Modele de donnees canonique (referentiel unique)

#### Regles globales normatives

1. La regex `story_id` est la **source de verite unique** (pas de contrainte textuelle de longueur independante).
2. Ordre de precedance dedoublonnage:
   - si `idempotency_key` deja vue avec payload identique: retour deterministe sans effet secondaire,
   - si `idempotency_key` deja vue avec payload different: rejet de conflit,
   - sinon validation/pilotage normal par `story_id`.
3. Ordre de gardes pour `start` (et seulement `start`) : **quota -> doublon idempotence -> validation format complete**.
4. Pour `stop/respond/pause/resume` : **doublon idempotence -> validation format complete** (garde quota non applicable).

| Donnee | Format / encodage | Caractere | Sensibilite casse | Regex / contrainte normative | Si invalide |
| -- | -- | -- | -- | -- | -- |
| `story_id` | ASCII | `[A-Z0-9-]` | case-sensitive | `^PD-[0-9]{1,4}$` | Rejet |
| `project_code` | ASCII enum | `[a-z-]` | case-sensitive | `^(backend|app|site|infra|doc|formal|pixel-governance|ia-governance)$` | Rejet |
| `message_type` | ASCII enum | `[A-Z_]` | case-sensitive | `^(STATUS_UPDATE|ESCALADE|GATE_RESULT|WORKFLOW_DONE|PO_RESPONSE|PAUSE|RESUME|ABORT)$` | Rejet |
| `peer_id` | UTF-8 opaque | printable UTF-8 | case-sensitive | `^[^\p{C}

]{1,128}$` | Rejet |
| `timestamp` | RFC3339 UTC | ASCII | case-sensitive | horodatage UTC parseable (suffixe `Z` ou offset UTC) | Rejet |
| `escalade_text` | UTF-8 | printable UTF-8 | case-sensitive | `^[^\p{C}]{1,2000}$` | Rejet |
| `summary_text` | UTF-8 | printable UTF-8 | case-sensitive | `^[^\p{C}]{1,512}$` | Rejet |
| `idempotency_key` | ASCII | `[A-Za-z0-9:_-]` | case-sensitive | `^[A-Za-z0-9:_-]{16,80}$`, unique dans fenetre TTL | Doublon: resultat deterministe |

#### Parametres numeriques contractuels

| Parametre | Defaut | Min | Max | Unite | Contexte | Hors bornes |
| -- | -- | -- | -- | -- | -- | -- |
| `max_ringbearers` | 5 | 1 | 5 | sessions | MacBook local | Rejet `start` |
| `peer_poll_interval` | 10 | 1 | 10 | secondes | Broker local | Clamp + avertissement |
| `escalade_visibility_sla` | 5 | 1 | 10 | secondes | One Ring -> Sovereign | Alerte SLA |
| `start_detection_timeout` | 30 | 5 | 120 | secondes | Creation Ringbearer | `START_FAILED` |
| `first_liveness_timeout` | 10 | 2 | 30 | secondes | 1ere verification apres apparition peer | `START_FAILED` |
| `crash_detection_cycles_max` | 2 | 2 | 2 | cycles | Detection crash | Non configurable |
| `crash_detection_sla` | 20 | 2 | 20 | secondes | Derive: `peer_poll_interval * 2` (defaut 10*2) | Alerte critique |
| `idempotency_ttl` | 24 | 1 | 168 | heures | Dedup commandes | Rejet config |

#### SLA temporels (transitions)

| Parametre | Defaut | Min | Max | Configurabilite | Comportement a expiration |
| -- | -- | -- | -- | -- | -- |
| `escalade_wait_ttl` | 72h | 1h | 30j | Oui | Pas de nouvel etat en v1: reste `ESCALADED`, alerte repetee au Sovereign jusqu'a decision explicite |
| `pause_ttl` | 24h | 5min | 7j | Oui | `PAUSED` persistant + alerte ; aucune reprise automatique |
| `orphan_threshold` | derive | N/A | N/A | Non (derive) | `orphan_threshold = 2 * peer_poll_interval`; depassement -> `CRASHED` |

### 5.2 Machine d'etats Ringbearer (avec transitions retour)

Etats officiels v1: `STARTING`, `RUNNING`, `ESCALADED`, `PAUSED`, `DONE`, `ABORTED`, `CRASHED`, `START_FAILED`.

- `STARTING` :
  - Autorisees : `STARTING->RUNNING`, `STARTING->START_FAILED`
  - Regle `STARTING->RUNNING` : peer detecte **et** premiere liveness valide recue avant `first_liveness_timeout`.
  - Regle `STARTING->START_FAILED` : `start_detection_timeout` depasse **ou** peer detecte mais aucune liveness valide dans la fenetre.
- `RUNNING` :
  - Autorisees : `RUNNING->ESCALADED`, `RUNNING->PAUSED`, `RUNNING->DONE`, `RUNNING->ABORTED`, `RUNNING->CRASHED`
- `ESCALADED` :
  - Autorisees : `ESCALADED->RUNNING` (apres `PO_RESPONSE` valide), `ESCALADED->ABORTED`, `ESCALADED->CRASHED`
  - Retour couvert : `RUNNING<->ESCALADED`
- `PAUSED` :
  - Autorisees : `PAUSED->RUNNING` (resume), `PAUSED->ABORTED`, `PAUSED->CRASHED`
  - Retour couvert : `RUNNING<->PAUSED`
- `DONE` (terminal) : `DONE->* INTERDITE`.
- `ABORTED` (terminal) : `ABORTED->* INTERDITE`.
- `CRASHED` (terminal) : `CRASHED->* INTERDITE`.
- `START_FAILED` (terminal) : `START_FAILED->* INTERDITE`.

Checklist machine a etats :
- [x] Chaque etat a ses transitions sortantes autorisees/interdites.
- [x] Chaque etat terminal declare explicitement `-> * : INTERDITE`.
- [x] Couverture par invariants `INV-293-09` et `INV-293-10`.
- [x] Aucune reference a `ESCALADE_PENDING` ou `ESCALADE_EXPIRED` en etats v1.

### 5.3 Flux metier

1) `start` : garde quota -> idempotence -> validation format ; creation d'un Ringbearer si autorise.  
2) `start` post-apparition : premiere verification de liveness obligatoire avant passage `RUNNING`.  
3) `status` : consolidation de l'etat de tous les Ringbearers observes/attendus.  
4) `escalade` : affichage de la file bloquante **FIFO** a traiter par le Sovereign.  
5) `respond` : injection d'une decision humaine sur la plus ancienne escalade ouverte de la story cible (FIFO intra-story).  
6) `pause` : gel intentionnel (`RUNNING->PAUSED`) sans perte de contexte.  
7) `resume` : reprise (`PAUSED->RUNNING`) si gardes valides.  
8) `stop` : arret intentionnel de la story (`->ABORTED` selon transitions autorisees).  
9) Supervision continue : detection d'absence peer en <= 2 cycles et signalement `CRASHED`.  
10) Reconnexion broker : tentative de reconnexion exponentielle bornee (1s, 2s, 4s, 8s, 10s max), mode degrade explicite tant que indisponible, reprise automatique des polls a la reconnexion.

### 5.4 Format de persistance `.gov-lord-state.json` (schema minimal v1)

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "GovLordState",
  "type": "object",
  "required": ["version", "updated_at", "stories", "escalade_queue", "idempotency_cache"],
  "properties": {
    "version": { "type": "string", "const": "1.0" },
    "updated_at": { "type": "string", "format": "date-time" },
    "broker": {
      "type": "object",
      "required": ["status", "last_seen_at"],
      "properties": {
        "status": { "type": "string", "enum": ["UP", "DEGRADED", "DOWN"] },
        "last_seen_at": { "type": "string", "format": "date-time" }
      },
      "additionalProperties": false
    },
    "stories": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["story_id", "project_code", "state", "peer_id", "last_seen_at"],
        "properties": {
          "story_id": { "type": "string", "pattern": "^PD-[0-9]{1,4}$" },
          "project_code": { "type": "string", "enum": ["backend", "app", "site", "infra", "doc", "formal", "pixel-governance", "ia-governance"] },
          "state": { "type": "string", "enum": ["STARTING", "RUNNING", "ESCALADED", "PAUSED", "DONE", "ABORTED", "CRASHED", "START_FAILED"] },
          "peer_id": { "type": "string", "minLength": 1, "maxLength": 128 },
          "last_seen_at": { "type": "string", "format": "date-time" },
          "current_escalade_id": { "type": ["string", "null"] }
        },
        "additionalProperties": false
      }
    },
    "escalade_queue": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["escalade_id", "story_id", "created_at", "text", "status"],
        "properties": {
          "escalade_id": { "type": "string", "minLength": 1, "maxLength": 128 },
          "story_id": { "type": "string", "pattern": "^PD-[0-9]{1,4}$" },
          "created_at": { "type": "string", "format": "date-time" },
          "text": { "type": "string", "minLength": 1, "maxLength": 2000 },
          "status": { "type": "string", "enum": ["OPEN", "ANSWERED", "CLOSED"] }
        },
        "additionalProperties": false
      }
    },
    "idempotency_cache": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["key", "command", "story_id", "payload_hash", "result_hash", "expires_at"],
        "properties": {
          "key": { "type": "string", "pattern": "^[A-Za-z0-9:_-]{16,80}$" },
          "command": { "type": "string", "enum": ["start", "stop", "respond", "pause", "resume"] },
          "story_id": { "type": "string", "pattern": "^PD-[0-9]{1,4}$" },
          "payload_hash": { "type": "string", "minLength": 8, "maxLength": 128 },
          "result_hash": { "type": "string", "minLength": 8, "maxLength": 128 },
          "expires_at": { "type": "string", "format": "date-time" }
        },
        "additionalProperties": false
      }
    }
  },
  "additionalProperties": false
}

5bis. Diagrammes (si applicable)

Diagramme d'etat (>= 3 etats)

stateDiagram-v2
    [*] --> STARTING
    STARTING --> RUNNING: peer detecte + first liveness OK
    STARTING --> START_FAILED: timeout demarrage ou liveness KO

    RUNNING --> ESCALADED: message ESCALADE
    ESCALADED --> RUNNING: message PO_RESPONSE valide

    RUNNING --> PAUSED: commande PAUSE
    PAUSED --> RUNNING: commande RESUME

    RUNNING --> DONE: WORKFLOW_DONE
    RUNNING --> ABORTED: commande ABORT/STOP
    ESCALADED --> ABORTED: commande ABORT/STOP
    PAUSED --> ABORTED: commande ABORT/STOP

    RUNNING --> CRASHED: peer disparu (>2 polls)
    ESCALADED --> CRASHED: peer disparu (>2 polls)

codex
J’ai confirmé que les artefacts réels correspondent bien au corpus du prompt. Je finis de relire la fin des tests (erreurs, négatifs, observabilité) pour verrouiller les références TC exactes dans le rapport final.
exec
/bin/zsh -lc "sed -n '260,620p' docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md" in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance
 succeeded in 688ms:
pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable
pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable
TEST-ID: TC-ERR-04
Reference spec: ERR-293-04

GIVEN
  - Story cible non en `ESCALADED`
WHEN
  - Commande `respond` recue
THEN
  - Rejet explicite (`pas d'escalade active`)
  - Aucun changement d'etat
TEST-ID: TC-ERR-05
Reference spec: ERR-293-05

GIVEN
  - One Ring actif
WHEN
  - Demande hors scope de routage (ex: ecrire fichier, lancer tests)
THEN
  - Blocage explicite conforme INV-293-01
  - Absence totale d'effet de bord metier
TEST-ID: TC-ERR-06
Reference spec: ERR-293-06

GIVEN
  - Broker indisponible
WHEN
  - Commande de supervision (`status`) ou de routage est executee
THEN
  - Passage en mode degrade explicite
  - Alerte Sovereign emise
  - Aucune decision silencieuse non tracee
TEST-ID: TC-ERR-07
Reference spec: ERR-293-07

GIVEN
  - Story observee precedemment en `RUNNING` puis peer absent
WHEN
  - Deux cycles de polling expirent sans retour peer
THEN
  - Transition `-> CRASHED`
  - Alerte critique emise
  - Etat marque terminal
TEST-ID: TC-ERR-08
Reference spec: ERR-293-08, INV-293-12

GIVEN
  - Commande `stop` deja traitee avec `idempotency_key=K2`
WHEN
  - Reemission stricte de la meme commande avec `K2`
THEN
  - Retour deterministe identique
  - Aucun effet secondaire additionnel
TEST-ID: TC-ERR-09
Reference spec: ERR-293-10, §5.2

GIVEN
  - `start` accepte, peer enregistre au broker
WHEN
  - Aucun message de first liveness valide n'est recu avant `first_liveness_timeout`
THEN
  - Etat `STARTING -> START_FAILED`
  - Session partielle nettoyee et alerte emise
TEST-ID: TC-ERR-10
Reference spec: CA-12, §5.3

GIVEN
  - Broker indisponible pendant une periode puis retabli
WHEN
  - One Ring applique la strategie de reconnexion bornee (1s,2s,4s,8s,10s)
THEN
  - Mode degrade explicite durant indisponibilite
  - Reprise automatique des polls a la reconnexion
  - Reconciliation immediate de l'etat consolide
TEST-ID: TC-ERR-11
Reference spec: ERR-293-09, §5.1

GIVEN
  - Commande `respond` traitee avec `idempotency_key=K3` et payload P1
WHEN
  - Rejeu avec meme cle `K3` mais payload P2 different
THEN
  - Rejet deterministe de conflit idempotence
  - Aucun effet secondaire additionnel

5. Tests d'invariants (non negociables)

Invariant Test(s) dedies Observable Commentaire
INV-293-01 TC-NOM-05, TC-ERR-05 Rejet explicite des actions hors scope, zero action metier Separation des pouvoirs preservee.
INV-293-02 TC-NOM-01, TC-MAN-01 Traces d'usage broker/peers uniquement Pas d'acces direct aux repos cibles.
INV-293-03 TC-NOM-09, TC-NR-03 Absence de contamination et de partage credentials entre stories Isolation contractuelle verifiee.
INV-293-04 TC-NOM-02, TC-ERR-01 6e start rejete sans creation Limite capacitaire stricte.
INV-293-05 TC-NOM-10, TC-ERR-03, TC-NEG-01..TC-NEG-10 Validation schema stricte, rejet deterministe des invalides Contrat de message testable.
INV-293-06 TC-NOM-03, TC-ERR-04 Blocage en ESCALADED jusqu'a PO_RESPONSE valide Gouvernance humaine effective.
INV-293-07 TC-NR-01 Comportement /gov identique baseline/post-orchestration Non-regression fonctionnelle du workflow.
INV-293-08 TC-NOM-06, TC-ERR-07 Detection crash en <=2 polls + alerte Resilience operationnelle.
INV-293-09 TC-INV-01 Toute transition non listee est refusee Cohesion machine d'etats.
INV-293-10 TC-INV-02 Aucun DONE/ABORTED/CRASHED/START_FAILED -> * accepte Terminaux verrouilles.
INV-293-11 TC-NOM-07 Reconciliation periodique aligne la vue consolidee Tolerance aux ecarts/crashes.
INV-293-12 TC-NOM-08, TC-ERR-08, TC-ERR-11 Rejeu avec meme cle sans double effet / conflit bloque Retry-safe en distribue.
INV-293-13 TC-NOM-13 FIFO strict multi-escalades Pas de remplacement d'escalade.
INV-293-14 TC-INV-03 Gardes quota -> doublon -> format Ordre contractuel stable.
TEST-ID: TC-INV-01
Reference spec: INV-293-09

GIVEN
  - Jeu complet des etats (`STARTING`, `RUNNING`, `ESCALADED`, `PAUSED`, `DONE`, `ABORTED`, `CRASHED`, `START_FAILED`)
WHEN
  - Chaque transition non listee en §5.2 est tentee
THEN
  - Chaque transition non autorisee est rejetee explicitement
  - Aucune mutation d'etat n'est appliquee
AND
  - Les rejets sont journalises de facon deterministe
TEST-ID: TC-INV-02
Reference spec: INV-293-10

GIVEN
  - Stories placees dans chacun des etats terminaux (`DONE`, `ABORTED`, `CRASHED`, `START_FAILED`)
WHEN
  - Une tentative de transition sortante est emise pour chaque etat terminal
THEN
  - Rejet explicite `etat terminal`
  - Etat reste strictement inchange
AND
  - Le dossier de preuve montre resolution manuelle requise
TEST-ID: TC-INV-03
Reference spec: INV-293-14

GIVEN
  - `max_ringbearers=5` et 5 stories deja actives
  - Commande `start` avec payload invalide (`story_id=pd-001`) et `idempotency_key` nouvelle
WHEN
  - Le Sovereign execute la commande
THEN
  - Le premier rejet observe est `quota atteint` (garde 1)
  - Aucune evaluation bloquante ulterieure ne change le motif principal
AND
  - Le journal trace explicitement l'ordre des gardes appliquees

6. Tests de non-regression

Test ID Objet Observable Commentaire
TC-NR-01 Workflow /gov consomme tel quel Baseline vs run orchestre: memes etapes/gates/verdicts pour une story identique Couvre INV-293-07 et CA-08.
TC-NR-02 Separation One Ring / execution metier Aucune commande One Ring n'effectue lecture/ecriture/build/test/Jira/GitLab/Vault Couvre perimetre exclu et INV-293-01.
TC-NR-03 Isolation inter-story Action sur story A sans impact sur story B (etat, contexte, credentials) Couvre INV-293-03.
TC-NR-04 Etats terminaux stables dans le temps Reconciliation et polling n'ouvrent jamais les etats terminaux Couvre INV-293-10.

7. Tests negatifs et adversariaux

Test ID Entree invalide / abus Resultat attendu Observable
TC-NEG-01 story_id=pd-293 (mauvaise casse) Rejet validation Erreur regex ^PD-[0-9]{1,4}$, aucun effet de bord
TC-NEG-02 story_id=PD-12345 (hors regex) Rejet validation Log de rejet, aucun changement d'etat
TC-NEG-03 project_code=backend-prod (hors enum) Rejet validation Message explicite project_code invalide
TC-NEG-04 project_code=ia-gov (alias interdit) Rejet validation Seule valeur valide: ia-governance
TC-NEG-05 message_type=ESCALATE (valeur non enum) Rejet validation Journal d'erreur schema, etat inchange
TC-NEG-06 timestamp non RFC3339 UTC Rejet validation Motif parse timestamp invalide
TC-NEG-07 idempotency_key <16 chars Rejet validation Commande non traitee
TC-NEG-08 escalade_text vide Rejet validation Escalade non creee
TC-NEG-09 peer_id vide ou controle Unicode Rejet validation Message ignore, erreur audit tracee
TC-NEG-10 Injection message inter-story (story_id A dans canal B) Rejet routage Aucun impact sur A ni B

8. Observabilite requise pour les tests

  • Etat systeme : snapshot consolide des stories (etat contractuel, derniere observation peer, file d'escalades FIFO, statut broker).
  • Reponse API/commande : sortie normalisee des commandes /gov-lord start/status/escalade/respond/pause/resume/stop avec statut success/rejet et motif.
  • Journal d'audit : trace horodatee de validation schema, transitions d'etat, rejets, dedoublonnage idempotence, alertes SLA, ordre des gardes.
  • Evenement signe/horodate : emission/reception des messages critiques (ESCALADE, PO_RESPONSE, WORKFLOW_DONE, CRASHED) en RFC3339 UTC.
  • Export probatoire : lot versionne contenant logs, mesures SLA (P95), matrice de transitions testees, preuves de non-regression et verdict final.

9. Regles non testables / test manuel

Regle Statut de test Raison Impact
CA-06 (controle mobile) Test manuel obligatoire (TC-MAN-01) Contrat mobile depend d'un environnement iPhone/Safari reel ; automatisation exhaustive non normative en v1 Majeur
Validation syntaxique fine de peer_id au-dela de printable UTF-8 Non applicable en v1 Format opaque, contrainte contractuelle limitee a non-vacuite + exclusion caracteres de controle Mineur
TEST-ID: TC-MAN-01
Reference spec: CA-06

Type: Manuel
Preconditions:
  - iPhone + Safari
  - Session `/remote-control` authentifiee

GIVEN
  - One Ring actif et stories existantes
WHEN
  - Le Sovereign execute `status`, `escalade`, `respond`, `pause`, `resume` depuis mobile
THEN
  - Resultats fonctionnels identiques a desktop
  - Aucune action hors scope One Ring n'est possible

10. Verdict QA

  • Testable majoritairement de facon deterministe (avec un lot manuel explicite pour CA-06)

Le contrat PD-293 est testable de facon deterministe pour invariants, machine d'etats, SLA, idempotence, ordre des gardes, FIFO d'escalades et non-regression. La couverture mobile CA-06 est maintenue via test manuel obligatoire (TC-MAN-01), clairement identifie dans la matrice.

exec /bin/zsh -lc "sed -n '1,260p' docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan-review.md" in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance succeeded in 723ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable

PD-293 - Plan d'implementation : Revue

1. References

  • Specification : PD-293-specification.md
  • Tests contractuels : PD-293-tests.md
  • Plan d'implementation : PD-293-plan.md
  • Date de revue : 2026-03-30
  • Reviewer : OpenCode (auditeur technique independant)

2. Constatations (ecarts)

Type Reference (Spec/Test/Plan) Description Impact Gravite (BLOQUANT/MAJEUR/MINEUR)
Non-conformite Spec Spec §4, §5.1, §5.2, §5.3, §5.4, §7 / Plan PD-293-plan.md Le document de plan fourni ne contient pas le plan d'implementation detaille (seulement un resume en 12 lignes), donc les mecanismes contractuels ne sont pas auditablement decrits. La conformite stricte a la specification ne peut pas etre etablie sur base documentaire opposable. BLOQUANT
Couverture manquante Invariants INV-293-01..INV-293-14 / Plan PD-293-plan.md Les mappings invariants -> mecanismes sont annonces mais non presentes dans l'artefact plan fourni. Impossibilite de verifier que chaque invariant est non contournable par design. BLOQUANT
Test irrealisable CA-02, TC-NOM-14 (mentionne dans plan) / Tests PD-293-tests.md Le plan annonce "TC-NOM-14 ajoute", mais ce test n'existe pas dans le document de tests contractuels fourni. La preuve contractuelle du demarrage nominal CA-02 reste non etablissable de facon deterministe. BLOQUANT
Test irrealisable TC-NOM-01..TC-ERR-11, TC-INV-01..03, TC-MAN-01 / Plan PD-293-plan.md Aucun point d'observabilite explicite par scenario de test n'est present dans l'artefact plan fourni. Les scenarios ne sont pas executables/auditables a partir du plan comme document contractuel. BLOQUANT
Hypothese implicite Spec §5bis (diagrammes) / Plan PD-293-plan.md + Code contracts PD-293-code-contracts.yaml Le plan mentionne des diagrammes Mermaid (waves + sequence), mais ces diagrammes ne sont pas fournis dans l'artefact plan; la coherence "dependances agents vs contracts" et "sequence vs mecanismes" n'est pas verifiable. Rupture de tracabilite formelle sur l'axe 5bis; risque d'interpretations divergentes en implementation. MAJEUR
Code Contract - Invariant Code contracts module tests / Spec §4 (invariants) Le contract tests introduit l'invariant "Couverture >= 80% du perimetre in scope", qui n'est pas un invariant de la specification canonique. Le jeu d'invariants des contracts n'est pas strictement un sous-ensemble de la spec. MAJEUR
Code Contract - Coherence Code contracts module logger / Spec §6.1 Le contract logger impose une convention additionnelle (peer_id='sovereign', message_type='COMMAND') non definie dans la specification canonique. Comportement contractuel ajoute hors spec, source potentielle de contestation en audit tiers. MAJEUR
Risque secu/conformite Spec §5.4 + conventions depot (etat local) / Code contracts module state-store Le contract state-store impose l'ecriture dans .gov-lord-state.json sans localisation explicite de stockage (epic vs racine). Risque de non-conformite de tracabilite locale et d'exposition de donnees hors perimetre documentaire attendu. MAJEUR
Contrainte technique non documentee Plan PD-293-plan.md (section "Contraintes techniques" absente) Les dependances inter-PD et leur statut (DONE/TODO/STUB) ne sont pas documentees. Visibilite de sequence de livraison incomplete. MINEUR
Contrainte technique non documentee Plan PD-293-plan.md (section "Contraintes techniques" absente) Le framework de test Jest/Vitest n'est pas explicite; le code contract mentionne bats-core, sans decision contractuelle explicite au niveau plan. Ambiguite de runner de test au regard de la contrainte documentaire demandee. MAJEUR
Contrainte technique non documentee Plan PD-293-plan.md (section "Contraintes techniques" absente) La compatibilite ESM/CJS et l'impact de la dependance claude-peers-mcp@^1.x ne sont pas documentes dans le plan. Risque de non-reproductibilite CI et d'echec de tests/exec selon runtime. MAJEUR
Contrainte technique non documentee Plan PD-293-plan.md (section "Contraintes techniques" absente) Les variables CI requises pour les tests d'integration ne sont pas documentees. Ambiguite d'execution en CI/CD. MINEUR

3. Synthese

  • Nombre d'ecarts par gravite : BLOQUANT=4, MAJEUR=6, MINEUR=2.
  • Points critiques : artefact plan non auditable (contenu manquant), couverture invariants non prouvable, executabilite des tests non demontree, ecart CA-02/TC-NOM-14 non resolu dans les tests fournis.

4. Verdict de la revue

  • Statut : ⛔ Rejete
  • Motif synthetique : Le plan fourni ne permet pas de demontrer la conformite contractuelle ni la realisabilite des tests sur preuves documentaires opposables; plusieurs ecarts bloquants subsistent sur couverture et auditabilite.

exec /bin/zsh -lc "sed -n '260,620p' docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-specification.md" in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance succeeded in 732ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable ESCALADED → CRASHED: peer disparu (>2 polls) PAUSED → CRASHED: peer disparu (>2 polls)

DONE --> [*]
ABORTED --> [*]
CRASHED --> [*]
START_FAILED --> [*]

```

Diagramme de sequence (>= 2 services)

mermaid sequenceDiagram participant S as Sovereign participant O as One Ring participant B as Broker participant R as Ringbearer S->>O: "/gov-lord start PD-293 ia-governance" O->>O: Gardes start: quota -> idempotence -> format O->>B: list_peers() B-->>O: peers[] O->>R: demande demarrage story_id=PD-293 R->>B: register_peer(summary_text) R-->>O: STATUS_UPDATE (first liveness) O->>O: STARTING->RUNNING R->>O: ESCALADE{story_id, escalade_text, timestamp} O->>O: enqueue FIFO escalade_queue O-->>S: notification escalade la plus ancienne S->>O: "/gov-lord respond PD-293 <reponse>" O->>R: PO_RESPONSE{story_id, escalade_text=<reponse>} R-->>O: STATUS_UPDATE (reprise RUNNING) S->>O: "/gov-lord pause PD-293" O->>R: PAUSE S->>O: "/gov-lord resume PD-293" O->>R: RESUME

6. Cas d'erreur

ID Cas Reponse attendue
ERR-293-01 start avec quota atteint (>=5) Rejet explicite, aucune creation
ERR-293-02 start sur story_id deja active Rejet explicite idempotent
ERR-293-03 Message sans story_id valide Rejet + journalisation contractuelle
ERR-293-04 respond sans escalade active Rejet explicite
ERR-293-05 Commande hors scope One Ring Blocage explicite (INV-293-01)
ERR-293-06 Broker indisponible Etat degrade + alerte Sovereign + retries reconnection
ERR-293-07 Peer disparu au-dela de 2 cycles de polling Transition CRASHED + alerte
ERR-293-08 Reemission commande deja traitee (idempotency_key) Retour deterministe sans effet secondaire
ERR-293-09 idempotency_key rejouee avec payload different Rejet de conflit deterministe
ERR-293-10 Peer cree mais 1ere liveness absente avant first_liveness_timeout START_FAILED + cleanup session

6.1 Format de log de rejet (normatif)

Chaque rejet de validation/message doit produire une ligne JSON (JSONL) avec champs obligatoires:

{
  "timestamp": "2026-03-30T10:15:30Z",
  "peer_id": "peer-abc-123",
  "message_type": "ESCALADE",
  "reason": "story_id invalide (regex ^PD-[0-9]{1,4}$)",
  "story_id": "PD-99999",
  "event": "MESSAGE_REJECTED"
}

Champs obligatoires minimum: timestamp, peer_id, message_type, reason.

7. Criteres d'acceptation (testables)

ID Critere Observable
CA-01 One Ring liste les Ringbearers actifs status retourne la liste consolidee
CA-02 One Ring demarre un Ringbearer autonome Story visible en RUNNING avant start_detection_timeout et apres first liveness
CA-03 Escalade visible en moins de 5s P95 Mesure horodatee emission/reception
CA-04 respond debloque la story ciblee Transition ESCALADED->RUNNING observee
CA-05 One Ring refuse les actions hors scope Message de blocage explicite systematique
CA-06 Controle mobile operationnel Commandes /gov-lord utilisables depuis iPhone (test manuel)
CA-07 Crash Ringbearer detecte en <= 2 cycles (<=20s defaut) Transition ->CRASHED et alerte
CA-08 /gov inchange fonctionnel en Ringbearer Execution complete sans adaptation du workflow
CA-09 Limite de 5 appliquee 6e start rejete
CA-10 Validation de schema messages stricte Message invalide rejete sans effet de bord
CA-11 Multi-escalade simultanee preserve l'ordre Traitement FIFO sans ecrasement
CA-12 Reconnexion broker robuste Mode degrade explicite puis reprise automatique

8. Scenarios de test (Given / When / Then)

  • T-01 Start nominal
    Given quota disponible et broker operationnel, When start PD-XXX, Then etat RUNNING avant timeout et apres first liveness valide.

  • T-02 Limite capacite
    Given 5 actifs, When start 6e story, Then rejet explicite sans nouveau peer.

  • T-03 Escalade bloquante
    Given story en RUNNING, When message ESCALADE, Then story en ESCALADED jusqu'a PO_RESPONSE.

  • T-04 Reponse escalade
    Given story ESCALADED, When respond valide, Then retour RUNNING et reprise observable.

  • T-05 Commande hors scope
    Given One Ring actif, When demande lecture/ecriture/build/Jira, Then blocage explicite sans execution.

  • T-06 Crash detection
    Given peer present puis absent > 2 cycles, When cycle de supervision, Then etat CRASHED + alerte en <=20s (defaut).

  • T-07 Idempotence commande
    Given idempotency_key deja traitee, When repetition meme payload, Then meme resultat sans doublon d'action.

  • T-08 Mobile control (manuel)
    Given session One Ring accessible mobile, When status/escalade/respond/pause/resume, Then memes resultats fonctionnels que desktop.

  • T-09 Pause/Resume
    Given story en RUNNING, When pause puis resume, Then transitions RUNNING->PAUSED->RUNNING observees.

  • T-10 Multi-escalade FIFO
    Given 3 escalades ouvertes pour une story, When respond est emis 3 fois, Then traitement dans l'ordre d'arrivee sans remplacement.

  • T-11 Reconnexion broker
    Given broker indisponible puis restaure, When boucle de retries, Then mode degrade puis reprise automatique et reconciliation.

9. Hypotheses explicites

ID Hypothese Impact si faux
H-293-01 claude-peers-mcp est disponible et compatible avec version ^1.x. Blocage orchestration multi-sessions.
H-293-02 /remote-control est utilisable sur iPhone (Safari/claude.ai/code). Perte du cas d'usage mobile, pilotage desktop uniquement.
H-293-03 Le broker local supporte 1 One Ring + 5 Ringbearers sans instabilite majeure. Reduction du quota max ou reconfiguration infra.
H-293-04 Le protocole messages requis est supporte par les peers sans adaptation de /gov. Necessite d'une story separee de compatibilite.

10. Points a clarifier

10.1 Contraintes techniques (stack projet cible)

  • Projet cible de l'artefact : ProbatioVault-ia-governance.
  • Stack reelle constatee : Shell (Bash) + Markdown + YAML/JSON de configuration + outillage Python.
  • Dependance externe : claude-peers-mcp@^1.x (npm) comme composant d'integration.
  • Contrainte de conformite : One Ring reste une couche d'orchestration textuelle, sans logique applicative projet.

10.2 Questions ouvertes

ID Question Impact
Q-293-01 Disponibilite exacte de /remote-control en contexte mobile web (et limites eventuelles). Peut degrader CA-06 (manuel).
Q-293-02 Format normatif exact de peer_id expose par list_peers au-dela de "printable UTF-8". Ajustement mineur du validateur §5.1.
Q-293-03 Semantique officielle de pause/reprise cote sessions autonomes. Peut contraindre RUNNING<->PAUSED.
Q-293-04 Politique de persistance locale autorisee pour l'etat consolide One Ring. Impact auditabilite/recuperation apres crash.
Q-293-05 Politique de purge de idempotency_cache au redemarrage. Impact stockage local et forensique.

References

  • Epic : Reference epique non fournie dans l'entree (donnee manquante).
  • JIRA : PD-293
  • Repos concernes : ProbatioVault-ia-governance (orchestrateur), plus repos cibles des stories (backend, app, infra, site, etc.).
  • Documents associes : Besoin PD-293 (texte fourni), claude-peers-mcp (github.com/louislva/claude-peers-mcp).

codex Je consolide maintenant les écarts avec preuves textuelles (sections exactes) pour éviter toute interprétation libre dans le rapport. Je vais extraire les passages-clés contradictoires (timestamp, respond, TC-NOM-14, diagrammes, contrats). exec /bin/zsh -lc 'rg -n "H-TECH-08|Strictement Z|Strictement Z|TC-NOM-14|ESCALADED->RUNNING|toutes les escalades OPEN|C5->>R|pending-|Couverture minimale" docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan.md' in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance exec /bin/zsh -lc "rg -n \"module: |files:|lord-state-machine|lord-fsm|lord-persistence|lord-state.sh|orchestrator|command-router|logger|supervisor|message_type='COMMAND'|Localisation : fichier dans le dossier epic|Chaque TC-*|tests/lord/**\" docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-code-contracts.yaml" in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance exec /bin/zsh -lc 'rg -n "timestamp|offset UTC|ESCALADED->RUNNING|INV-293-02|CA-04|TC-NOM-03|TC-NOM-14|Contraintes techniques" docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-specification.md docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md' in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance succeeded in 827ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable zsh:1: command not found: Z zsh:1: command not found: Z 36: C5 -> C4.lord_create_story(PD-42, backend, STARTING, "pending-PD-42") 58: # on NE fait PAS la transition ESCALADED->RUNNING ; on attend toutes les réponses. 61:Précision multi-escalade : La transition ESCALADED->RUNNING ne s'effectue que lorsque toutes les escalades OPEN de la story sont traitées. Tant qu'il reste au moins une escalade OPEN, la story reste ESCALADED. 196: C5->>C4: lord_create_story(PD-42, backend, STARTING, pending-PD-42) 197: C5->>R: lance en background (claude -p) 247:| CA-02 | lord_start() dans C5 : gardes → création story STARTING → lancement Ringbearer background → supervision détecte peer + first liveness → transition STARTING→RUNNING. Nouveau test : TC-NOM-14 (cf. §5 infra) couvre le chemin nominal complet. | C5, C4, C1, C3 | TC-NOM-14 : story visible en RUNNING avant start_detection_timeout. First liveness vérifiée. | Moyen — dépend du timing de démarrage du Ringbearer | 248:| CA-03 | Delta timestamps RFC3339 entre émission (Ringbearer) et réception (One Ring). Horloge unique locale MacBook (pas de synchronisation d'horloge nécessaire — décision H-TECH-08bis, cf. §8). | C5, C4 | TC-NOM-04 : P95 ≤ 5s sur 100 escalades, timestamps exportés | Faible (horloge locale unique) | 275:| TC-NOM-14 (ajouté) | CA-02, INV-293-08 | C5.lord_start() complet : gardes → STARTING → peer détecté → first liveness → RUNNING | Story visible en RUNNING, peer_id réel, elapsed < start_detection_timeout | Integration | 297:Note sur TC-NOM-14 : Ce test est ajouté pour couvrir CA-02 (résolution de l'incohérence spec-review §2 : CA-02 était mappé à TC-NOM-08 qui teste l'idempotence, pas le démarrage nominal). 345:| H-TECH-08 | Timestamp strictement UTC Z — les offsets non-zero (+02:00, -05:00) sont rejetés par C2 | Aucun — c'est une décision de design, pas une hypothèse externe | Résolution de l'ambiguïté spec review §5.1. Regex : ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$ | 346:| H-TECH-08bis | Horloge unique locale — émetteur (Ringbearer) et récepteur (One Ring) sont sur le même MacBook | Le SLA P95 de CA-03 est fiable car aucun décalage d'horloge possible | Résolution de l'hypothèse dangereuse spec review. Si multi-machine en v2, NTP obligatoire. | 347:| H-TECH-09 | peer_id en état STARTING — valeur sentinelle "pending-{story_id}" | Conforme minLength:1 du schema §5.4 | Résolution de l'ambiguïté spec review §5.4 vs §5.2. Remplacé par le vrai peer_id dès RUNNING. | 382:| Incohérence CA-02 mappé à TC-NOM-08 | Majeur | TC-NOM-14 ajouté pour couvrir le chemin nominal de CA-02 (start complet jusqu'à RUNNING). TC-NOM-08 couvre uniquement l'idempotence (INV-293-12). | 385:| Ambiguïté timestamp "offset UTC" | Mineur | Strictement Z uniquement. Tout offset non-zero est rejeté (H-TECH-08). | 386:| Hypothèse horloge SLA CA-03 | Majeur | Horloge locale unique MacBook (H-TECH-08bis). Pas de décalage possible en v1 (single machine). | 388:| peer_id obligatoire en STARTING (schema §5.4) | Majeur | Valeur sentinelle "pending-{story_id}" (H-TECH-09). Conforme minLength:1. | 412:Couverture minimale : 80% sur le périmètre in scope (C1-C6). C7 est le module de tests lui-même. 466: - "Timestamp RFC3339 strictement UTC : seul le suffixe Z est accepte (decision H-TECH-08)." 572: - "Multi-escalade : transition ESCALADED->RUNNING uniquement quand TOUTES les escalades OPEN de la story sont traitees." 637:3. TC-NOM-14 ajouté — couvre CA-02 (démarrage nominal)

succeeded in 882ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable 2: - module: state-machine 25: files: 26: - "scripts/lib/lord-fsm.sh" 28: - module: validator 56: files: 59: - module: logger 60: owner_agent: agent-logger 69: - "Convention rejets commande Sovereign : peer_id='sovereign', message_type='COMMAND' (decision plan H-PLAN-02)" 73: - "Logger le contenu complet de escalade_text (risque RGPD) — logger uniquement le motif de rejet et l'escalade_id" 75: - decision: "Convention peer_id='sovereign' et message_type='COMMAND' pour les rejets CLI" 83: files: 84: - "scripts/lib/lord-logger.sh" 86: - module: state-store 104: - "Localisation : fichier dans le dossier epic (docs/epics/tooling/PD-293-one-ring-orchestration/), pas a la racine" 127: files: 128: - "scripts/lib/lord-state.sh" 130: - module: broker-adapter 154: files: 157: - module: supervisor 158: owner_agent: agent-supervisor 182: files: 183: - "scripts/lib/lord-supervisor.sh" 185: - module: command-router 214: files: 217: - module: skill-gov-lord 223: - "INV-293-01 : le skill ne delegue qu'aux commandes connues de command-router" 229: files: 232: - module: tests 240: - "Chaque TC-* de la spec a un test bats correspondant" 246: files: 247: - "tests/lord/**"

succeeded in 882ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:13:| INV-293-02 | CA-01 | TC-NOM-01 | Oui | Les observations passent par broker/peers; aucun acces direct aux repos cibles. | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:17:| INV-293-06 | CA-04 | TC-NOM-03 | Oui | Une escalade bloque la progression jusqu'a reception d'une reponse PO valide. | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:20:| INV-293-09 | CA-04 | TC-INV-01 | Oui | Toutes transitions hors machine d'etats sont refusees. | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:27:| INV-293-02 | CA-06 | TC-MAN-01 | Manuel | Le controle mobile utilise les memes commandes d'orchestration via One Ring. | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:34:Reference spec: INV-293-02, CA-01 docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:66:TEST-ID: TC-NOM-03 docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:67:Reference spec: INV-293-06, CA-04 docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:124: - La transition est tracee avec timestamp docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:184: - Le journal d'audit associe type de message, story cible, timestamp docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:207: - Trois escalades ouvertes pour la meme story avec timestamps t1 < t2 < t3 docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:255: - Erreur journalisee au format JSONL obligatoire avec timestamp, peer_id, message_type, reason docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:371:| INV-293-02 | TC-NOM-01, TC-MAN-01 | Traces d'usage broker/peers uniquement | Pas d'acces direct aux repos cibles. | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:375:| INV-293-06 | TC-NOM-03, TC-ERR-04 | Blocage en ESCALADED jusqu'a PO_RESPONSE valide | Gouvernance humaine effective. | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md:449:| TC-NEG-06 | timestamp non RFC3339 UTC | Rejet validation | Motif parse timestamp invalide | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-specification.md:51:| INV-293-02 | One Ring ne communique avec les stories qu'au travers du broker/peers ; aucune action directe sur les repos cibles. | Confinement operationnel. | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-specification.md💯| timestamp | RFC3339 UTC | ASCII | case-sensitive | horodatage UTC parseable (suffixe Z ou offset UTC) | Rejet | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-specification.md:137: - Autorisees : ESCALADED->RUNNING (apres PO_RESPONSE valide), ESCALADED->ABORTED, ESCALADED->CRASHED docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-specification.md:287: R->>O: ESCALADE{story_id, escalade_text, timestamp} docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-specification.md:322: "timestamp": "2026-03-30T10:15:30Z", docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-specification.md:331:Champs obligatoires minimum: timestamp, peer_id, message_type, reason. docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-specification.md:340:| CA-04 | respond debloque la story ciblee | Transition ESCALADED->RUNNING observee | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-specification.md:396:### 10.1 Contraintes techniques (stack projet cible)

exec /bin/zsh -lc 'rg -n "''^## .*Contraintes techniques|Contraintes techniques" docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan.md' in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance exited 1 in 707ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable

exec /bin/zsh -lc 'rg -n "TC-NOM-14" docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-tests.md docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan.md' in /Users/loic/Developpement/ProbatioVault/ProbatioVault-ia-governance succeeded in 773ms: pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable pyenv: cannot rehash: /Users/loic/.pyenv/shims isn't writable docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan.md:247:| CA-02 | lord_start() dans C5 : gardes → création story STARTING → lancement Ringbearer background → supervision détecte peer + first liveness → transition STARTING→RUNNING. Nouveau test : TC-NOM-14 (cf. §5 infra) couvre le chemin nominal complet. | C5, C4, C1, C3 | TC-NOM-14 : story visible en RUNNING avant start_detection_timeout. First liveness vérifiée. | Moyen — dépend du timing de démarrage du Ringbearer | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan.md:275:| TC-NOM-14 (ajouté) | CA-02, INV-293-08 | C5.lord_start() complet : gardes → STARTING → peer détecté → first liveness → RUNNING | Story visible en RUNNING, peer_id réel, elapsed < start_detection_timeout | Integration | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan.md:297:Note sur TC-NOM-14 : Ce test est ajouté pour couvrir CA-02 (résolution de l'incohérence spec-review §2 : CA-02 était mappé à TC-NOM-08 qui teste l'idempotence, pas le démarrage nominal). docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan.md:382:| Incohérence CA-02 mappé à TC-NOM-08 | Majeur | TC-NOM-14 ajouté pour couvrir le chemin nominal de CA-02 (start complet jusqu'à RUNNING). TC-NOM-08 couvre uniquement l'idempotence (INV-293-12). | docs/epics/tooling/PD-293-one-ring-orchestration/PD-293-plan.md:637:3. TC-NOM-14 ajouté — couvre CA-02 (démarrage nominal)

codex J’ai terminé l’audit croisé spec/tests/plan/code-contracts et j’ai identifié des écarts bloquants restants, notamment sur la preuve CA-02 et la cohérence des code contracts. Je prépare maintenant la restitution finale strictement au template, sans proposition de correction. codex

PD-293 — Plan d’implémentation : Revue

1. Références

  • Spécification : PD-293-specification.md
  • Tests contractuels : PD-293-tests.md
  • Plan d’implémentation : PD-293-plan.md
  • Date de revue : 2026-03-31
  • Reviewer : Codex (auditeur technique indépendant)

2. Constatations (écarts)

Type Référence (Spec/Test/Plan) Description Impact Gravité (BLOQUANT/MAJEUR/MINEUR)
Test irréalisable Spec §7 (CA-02) / Plan §4-§5 (TC-NOM-14) / Tests (absence TC-NOM-14) Le plan fonde la preuve de CA-02 sur TC-NOM-14, mais ce test n’existe pas dans le référentiel contractuel de tests fourni. La conformité CA-02 n’est pas démontrable de façon opposable. BLOQUANT
Code Contract — Cohérence Plan §1 (C1..C7), §2bis/§2ter, bloc “code-contracts corrigé” / PD-293-code-contracts.yaml Deux définitions concurrentes et incompatibles des contrats existent (modules, interfaces et fichiers différents : ex. lord-state-machine.sh vs lord-fsm.sh, lord-persistence.sh vs lord-state.sh, orchestrator monolithique vs split supervisor/command-router). Impossible d’identifier un contrat de référence unique pour audit tiers. BLOQUANT
Non-conformité Spec Spec §5.2 (ESCALADED->RUNNING après PO_RESPONSE), §7 (CA-04) / Tests TC-NOM-03 / Plan §2.2 Le plan ajoute une condition non spécifiée : rester ESCALADED tant qu’il reste une escalade OPEN. respond peut ne pas “débloquer la story” comme contractualisé par CA-04. MAJEUR
Non-conformité Spec Spec §5.1 (timestamp : Z ou offset UTC) / Plan §8 (H-TECH-08) Le plan impose timestamp strictement suffixé Z et rejette les offsets. Rejet possible d’entrées valides au regard du contrat canonical. MAJEUR
Hypothèse implicite Plan §2.1 (lancement Ringbearer) Dépendance implicite à un chemin absolu local (/Users/loic/.local/bin/claude) et à un état d’environnement (unset CLAUDECODE) non contractualisés. Non-reproductibilité hors poste/localité d’origine ; risque d’échec start. MAJEUR
Couverture manquante INV-293-13 / Plan §3 (mécanisme FIFO par tri created_at) / Tests TC-NOM-13 Le mécanisme FIFO repose sur created_at sans règle de départage explicite en cas d’égalité d’horodatage. Invariant FIFO non garanti de façon déterministe dans tous les cas. MAJEUR
Risque sécu/conformité INV-293-13, CA-11 / Plan §3 (tri par created_at) L’ordre de traitement dépend d’un timestamp de message ; un horodatage forgé permet de réordonner la file. Contournement possible de la gouvernance FIFO et auditabilité contestable. MAJEUR
Risque sécu/conformité INV-293-05 / Plan §8 (H-TECH-04 fallback “validation plus permissive”) En absence de grep -P, le plan prévoit un mode de validation assoupli. Le rejet déterministe des messages invalides devient dépendant de l’environnement. MAJEUR
Hypothèse implicite Spec INV-293-02 / Plan §2ter (diagramme séquence C5->>R) Le diagramme de séquence conserve un canal direct orchestrator→Ringbearer sans cadrage contractuel explicite de cette exception. Ambiguïté d’implémentation et de contrôle sur la frontière broker-only. MAJEUR
Code Contract — Complétude Plan §1 (C5 orchestrator) / PD-293-code-contracts.yaml Le composant C5 du plan (scripts/gov-lord.sh) n’a pas de contrat dédié explicite dans le fichier fourni (responsabilité éclatée). Couverture contractuelle incomplète d’un composant central. MAJEUR
Code Contract — Invariant PD-293-code-contracts.yaml (modules logger, state-store, tests) / Spec §4 (invariants) Plusieurs “invariants” de code contracts ne sont pas des sous-ensembles des invariants de la spec (ex. convention message_type='COMMAND', localisation imposée, invariant de complétude de tests). Dérive contractuelle et contestabilité en audit externe. MAJEUR
Code Contract — Frontière Plan §7 (state à la racine repo) / PD-293-code-contracts.yaml state-store (fichier dans dossier epic) Frontière de stockage contradictoire entre plan et code contracts. Périmètre de persistance non univoque, traçabilité et exploitation ambiguës. MAJEUR
Code Contract — Forbidden PD-293-code-contracts.yaml state-store forbidden (“écrire dans un fichier autre que .gov-lord-state.json”) / Plan §7 (.gov-lord-audit.jsonl) Le pattern interdit bloque un artefact d’audit explicitement prévu par le plan. Contradiction interne sur la capacité probatoire/audit. MAJEUR
Contrainte technique non documentée Plan (section “Contraintes techniques” absente) Les dépendances inter-PD avec statut (DONE/TODO/STUB) ne sont pas documentées. Visibilité de dépendances de livraison incomplète. MINEUR
Contrainte technique non documentée Plan (section “Contraintes techniques” absente) Choix explicite Jest/Vitest absent ; le plan parle BATS uniquement. Ambiguïté de runner au regard de la contrainte documentaire imposée. MAJEUR
Contrainte technique non documentée Plan (section “Contraintes techniques” absente) Compatibilité ESM/CJS non documentée (dont impact dépendances ESM-only). Risque d’échec d’exécution/tests selon runtime CI. MAJEUR
Contrainte technique non documentée Plan (section “Contraintes techniques” absente) Variables CI requises pour tests d’intégration non listées (DATABASE_URL, CI=true, etc.). Reproductibilité CI/CD incomplète. MINEUR

3. Synthèse

  • Nombre d’écarts par gravité : BLOQUANT=2, MAJEUR=13, MINEUR=2.
  • Points critiques : preuve CA-02 non opposable (TC manquant), référentiel de code contracts non univoque, écarts de conformité sur respond multi-escalade et validation timestamp.

4. Verdict de la revue

  • Statut : ⛔ Rejeté
  • Motif synthétique : le plan n’est pas auditablement conforme en l’état (preuves de tests incomplètes et contrats de code contradictoires), avec des écarts majeurs de conformité/testabilité/sécurité restant ouverts. tokens used 95 936

PD-293 — Plan d’implémentation : Revue

1. Références

  • Spécification : PD-293-specification.md
  • Tests contractuels : PD-293-tests.md
  • Plan d’implémentation : PD-293-plan.md
  • Date de revue : 2026-03-31
  • Reviewer : Codex (auditeur technique indépendant)

2. Constatations (écarts)

Type Référence (Spec/Test/Plan) Description Impact Gravité (BLOQUANT/MAJEUR/MINEUR)
Test irréalisable Spec §7 (CA-02) / Plan §4-§5 (TC-NOM-14) / Tests (absence TC-NOM-14) Le plan fonde la preuve de CA-02 sur TC-NOM-14, mais ce test n’existe pas dans le référentiel contractuel de tests fourni. La conformité CA-02 n’est pas démontrable de façon opposable. BLOQUANT
Code Contract — Cohérence Plan §1 (C1..C7), §2bis/§2ter, bloc “code-contracts corrigé” / PD-293-code-contracts.yaml Deux définitions concurrentes et incompatibles des contrats existent (modules, interfaces et fichiers différents : ex. lord-state-machine.sh vs lord-fsm.sh, lord-persistence.sh vs lord-state.sh, orchestrator monolithique vs split supervisor/command-router). Impossible d’identifier un contrat de référence unique pour audit tiers. BLOQUANT
Non-conformité Spec Spec §5.2 (ESCALADED->RUNNING après PO_RESPONSE), §7 (CA-04) / Tests TC-NOM-03 / Plan §2.2 Le plan ajoute une condition non spécifiée : rester ESCALADED tant qu’il reste une escalade OPEN. respond peut ne pas “débloquer la story” comme contractualisé par CA-04. MAJEUR
Non-conformité Spec Spec §5.1 (timestamp : Z ou offset UTC) / Plan §8 (H-TECH-08) Le plan impose timestamp strictement suffixé Z et rejette les offsets. Rejet possible d’entrées valides au regard du contrat canonical. MAJEUR
Hypothèse implicite Plan §2.1 (lancement Ringbearer) Dépendance implicite à un chemin absolu local (/Users/loic/.local/bin/claude) et à un état d’environnement (unset CLAUDECODE) non contractualisés. Non-reproductibilité hors poste/localité d’origine ; risque d’échec start. MAJEUR
Couverture manquante INV-293-13 / Plan §3 (mécanisme FIFO par tri created_at) / Tests TC-NOM-13 Le mécanisme FIFO repose sur created_at sans règle de départage explicite en cas d’égalité d’horodatage. Invariant FIFO non garanti de façon déterministe dans tous les cas. MAJEUR
Risque sécu/conformité INV-293-13, CA-11 / Plan §3 (tri par created_at) L’ordre de traitement dépend d’un timestamp de message ; un horodatage forgé permet de réordonner la file. Contournement possible de la gouvernance FIFO et auditabilité contestable. MAJEUR
Risque sécu/conformité INV-293-05 / Plan §8 (H-TECH-04 fallback “validation plus permissive”) En absence de grep -P, le plan prévoit un mode de validation assoupli. Le rejet déterministe des messages invalides devient dépendant de l’environnement. MAJEUR
Hypothèse implicite Spec INV-293-02 / Plan §2ter (diagramme séquence C5->>R) Le diagramme de séquence conserve un canal direct orchestrator→Ringbearer sans cadrage contractuel explicite de cette exception. Ambiguïté d’implémentation et de contrôle sur la frontière broker-only. MAJEUR
Code Contract — Complétude Plan §1 (C5 orchestrator) / PD-293-code-contracts.yaml Le composant C5 du plan (scripts/gov-lord.sh) n’a pas de contrat dédié explicite dans le fichier fourni (responsabilité éclatée). Couverture contractuelle incomplète d’un composant central. MAJEUR
Code Contract — Invariant PD-293-code-contracts.yaml (modules logger, state-store, tests) / Spec §4 (invariants) Plusieurs “invariants” de code contracts ne sont pas des sous-ensembles des invariants de la spec (ex. convention message_type='COMMAND', localisation imposée, invariant de complétude de tests). Dérive contractuelle et contestabilité en audit externe. MAJEUR
Code Contract — Frontière Plan §7 (state à la racine repo) / PD-293-code-contracts.yaml state-store (fichier dans dossier epic) Frontière de stockage contradictoire entre plan et code contracts. Périmètre de persistance non univoque, traçabilité et exploitation ambiguës. MAJEUR
Code Contract — Forbidden PD-293-code-contracts.yaml state-store forbidden (“écrire dans un fichier autre que .gov-lord-state.json”) / Plan §7 (.gov-lord-audit.jsonl) Le pattern interdit bloque un artefact d’audit explicitement prévu par le plan. Contradiction interne sur la capacité probatoire/audit. MAJEUR
Contrainte technique non documentée Plan (section “Contraintes techniques” absente) Les dépendances inter-PD avec statut (DONE/TODO/STUB) ne sont pas documentées. Visibilité de dépendances de livraison incomplète. MINEUR
Contrainte technique non documentée Plan (section “Contraintes techniques” absente) Choix explicite Jest/Vitest absent ; le plan parle BATS uniquement. Ambiguïté de runner au regard de la contrainte documentaire imposée. MAJEUR
Contrainte technique non documentée Plan (section “Contraintes techniques” absente) Compatibilité ESM/CJS non documentée (dont impact dépendances ESM-only). Risque d’échec d’exécution/tests selon runtime CI. MAJEUR
Contrainte technique non documentée Plan (section “Contraintes techniques” absente) Variables CI requises pour tests d’intégration non listées (DATABASE_URL, CI=true, etc.). Reproductibilité CI/CD incomplète. MINEUR

3. Synthèse

  • Nombre d’écarts par gravité : BLOQUANT=2, MAJEUR=13, MINEUR=2.
  • Points critiques : preuve CA-02 non opposable (TC manquant), référentiel de code contracts non univoque, écarts de conformité sur respond multi-escalade et validation timestamp.

4. Verdict de la revue

  • Statut : ⛔ Rejeté
  • Motif synthétique : le plan n’est pas auditablement conforme en l’état (preuves de tests incomplètes et contrats de code contradictoires), avec des écarts majeurs de conformité/testabilité/sécurité restant ouverts.