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
/govexistant. - 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_EXPIREDen 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_PENDINGn'est defini. - En v1, aucun etat
ESCALADE_EXPIREDn'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¶
- La regex
story_idest la source de verite unique (pas de contrainte textuelle de longueur independante). - Ordre de precedance dedoublonnage:
- si
idempotency_keydeja vue avec payload identique: retour deterministe sans effet secondaire, - si
idempotency_keydeja vue avec payload different: rejet de conflit, - sinon validation/pilotage normal par
story_id. - Ordre de gardes pour
start(et seulementstart) : quota -> doublon idempotence -> validation format complete. - 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}\n\r]{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 avantfirst_liveness_timeout. - Regle
STARTING->START_FAILED:start_detection_timeoutdepasse 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(apresPO_RESPONSEvalide),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)¶
{
"$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)
PAUSED --> CRASHED: peer disparu (>2 polls)
DONE --> [*]
ABORTED --> [*]
CRASHED --> [*]
START_FAILED --> [*] Diagramme de sequence (>= 2 services)¶
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, Whenstart PD-XXX, Then etatRUNNINGavant timeout et apres first liveness valide. -
T-02 Limite capacite
Given 5 actifs, Whenstart6e story, Then rejet explicite sans nouveau peer. -
T-03 Escalade bloquante
Given story enRUNNING, When messageESCALADE, Then story enESCALADEDjusqu'aPO_RESPONSE. -
T-04 Reponse escalade
Given storyESCALADED, Whenrespondvalide, Then retourRUNNINGet 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 etatCRASHED+ alerte en <=20s (defaut). -
T-07 Idempotence commande
Givenidempotency_keydeja traitee, When repetition meme payload, Then meme resultat sans doublon d'action. -
T-08 Mobile control (manuel)
Given session One Ring accessible mobile, Whenstatus/escalade/respond/pause/resume, Then memes resultats fonctionnels que desktop. -
T-09 Pause/Resume
Given story enRUNNING, Whenpausepuisresume, Then transitionsRUNNING->PAUSED->RUNNINGobservees. -
T-10 Multi-escalade FIFO
Given 3 escalades ouvertes pour une story, Whenrespondest 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).