# 03-relations – Spécifications techniques

Ce document traduit les objectifs fonctionnels du pack Relations en décisions techniques :
modèle JSON dans le snapshot, invariants du graphe et points d’intégration avec Gestion,
Logique, Interactions, Teams et Activity.

On reste volontairement au niveau design : rien n’est encore “figé” dans le code, mais
les choix décrits ici servent de garde‑fous pour l’implémentation.

---

## 1. Concepts techniques clés

### 1.1. Modèle de relation (arêtes)

**Rappel fonctionnel :**

- Le pack Relations introduit un graphe de relations entre items d’un **même board**.
- Types de base : “dépend de”, “bloque”, “lié à”.
- On doit pouvoir lier des items de containers différents, y compris de workspaces différents,
  tant que c’est le même board.
- Les relations de dépendance sont **acycliques** (cf. `data-model-invariants.md`).

**Décision technique : bloc `rel.edges` au niveau du snapshot de board**

Les relations sont stockées au niveau du snapshot du board, sous la forme d’une liste d’arêtes :

```json
{
  "rel": {
    "edges": [
      {
        "id": "uuid-edge-1",
        "from": "uuid-node-A",
        "to": "uuid-node-B",
        "kind": "rel/depends-on",
        "source": {
          "mode": "manual"
        }
      }
    ]
  }
}
```

- `id` : identifiant unique de la relation (UUID, unique à l’intérieur du board).
- `from` : `nodeId` source (item A).
- `to` : `nodeId` cible (item B).
- `kind` : type de la relation, par exemple :
  - `rel/depends-on`  (A dépend de B, `depends=true`),
  - `rel/blocks`      (A bloque B, `depends=true`),
  - `rel/linked-to`   (lien souple sans logique forte, `depends=false`).
  Chaque type `rel/*` est déclaré dans un dataset MCC avec un flag `depends` qui le place (ou non) dans le DAG acyclique.
- `source` : métadonnées de provenance, par ex. :
  - `{ "mode": "manual" }`  (relation créée explicitement par l’utilisateur).

Les items eux‑mêmes ne portent pas de copie locale des relations : elles sont résolues
en lisant `rel.edges` (graphe centralisé).

**Invariants techniques de base :**

- `from` et `to` DOIVENT :
  - être différents (`from != to`) → **pas de self‑loop**,
  - référencer des `nodeId` existants dans le snapshot du board.
- `kind` DOIT être une valeur déclarée par le pack Relations dans un dataset MCC (`rel/*`).
- Aucune relation cross‑board n’est stockée dans `rel.edges` :
  - un edge ne peut relier que des items du **même board**.
- Les doublons exacts (`from`, `to`, `kind`, `source.mode` identiques)
  DOIVENT être traités comme strictement idempotents :
  - si un edge identique existe déjà, aucune nouvelle entrée n’est créée et aucune erreur n’est renvoyée,
  - l’Applier ne doit jamais produire deux arêtes identiques dans `rel.edges`.

### 1.2. Sémantique des directions et types

Pour éviter toute ambiguïté :

- Une relation `rel/depends-on` avec `{ "from": A, "to": B }` signifie :
  - **“A dépend de B”** : A ne peut pas être considéré comme complètement résolu tant que B ne l’est pas ;
  - B est un **prérequis** de A.
- Une relation `rel/blocks` avec `{ "from": A, "to": B }` signifie :
  - **“A bloque B”** : tant que A n’est pas respecté/terminé, B ne peut pas avancer.
- `rel/blocks` reste distinct de `rel/depends-on` ; aucune arête miroir implicite n’est créée. Les deux sont cependant marquées `depends=true` et appartiennent au même DAG acyclique.

**Réprésentation canonique de `rel/linked-to` :**

- Une relation de type `rel/linked-to` est stockée comme une **seule arête** `{ "from": A, "to": B }`.
- Les consommateurs (UI, Logique, Gestion, Interactions) la considèrent comme un **lien non orienté** :
  - “A lié à B” ≡ “B lié à A” du point de vue métier.
- La création d’une arête miroir inverse (`from = B, to = A, kind = "rel/linked-to"`) alors qu’une arête
  `A → B` existe déjà DOIT être rejetée ou normalisée en no‑op :
  - le graphe ne doit jamais contenir deux arêtes `rel/linked-to` pour le même couple `(A, B)`.

Ces définitions de direction sont utilisées par Gestion/Logique pour calculer :

- les items bloqués (via `rel/depends-on` ou `rel/blocks`),
- les chaînes de dépendances pour la progression agrégée.

### 1.3. Acyclicité des relations de dépendance

**Rappel fonctionnel :**

- Le graphe de dépendances ne doit jamais contenir de cycle logique
  (A dépend de B, B dépend de C, C dépend de A).
- `data-model-invariants.md` énonce “Acyclicité des relations” : dans le cadre de Relations,
  cela est précisé comme suit.

**Décision technique : périmètre de l’acyclicité**

- L’invariant d’acyclicité s’applique au **graphe des relations de dépendance** :
  - toute arête dont le type est déclaré avec `depends=true` (incluant `rel/depends-on` et `rel/blocks`),
  - les types `depends=false` (ex. `rel/linked-to`) ne sont pas concernés et peuvent cycler sans impact.

**Invariants techniques :**

- Pour tout edge `E = (from, to, kind = "rel/depends-on")` ajouté au graphe :
  - il NE DOIT PAS exister de chemin de `to` vers `from` dans le graphe courant des `rel/depends-on`,
  - sinon l’opération est rejetée (`422 RELATION_CYCLE_DETECTED` ou équivalent).
- Toute modification d’un edge de dépendance est traitée comme une suppression + ajout :
  - suppression de l’ancienne version (ne peut pas créer de cycle),
  - tentative d’ajout de la nouvelle version, avec vérification d’acyclicité.

Un algorithme incrémental (par ex. DFS limité au composant concerné) DOIT être utilisé en pratique
pour éviter de parcourir tout le graphe à chaque création de relation.

## 2. Points d’intégration

### 2.1. CommandBus & handlers Relations

Le pack Relations n’introduit pas de nouveau bus :

- toutes les mutations passent par `POST /api/commands`.

On introduit des commandes dédiées, par exemple :

- `Relations.CreateEdge`
- `Relations.DeleteEdge`
- `Relations.UpdateEdgeKind`

**Handlers :**

Chaque handler DOIT :

- valider les invariants de base :
  - nodes existants,
  - même board,
  - pas de self‑loop,
  - `kind` déclaré dans MCC ;
- rejeter les doublons exacts (ou les traiter comme idempotents) ;
- vérifier l’acyclicité pour les arêtes de dépendance (`rel/depends-on`) ;
- mettre à jour `rel.edges` de manière atomique (Apply = tout ou rien) ;
- émettre des événements métier comme :
  - `RelationCreated`,
  - `RelationDeleted`.

**Respect de Teams :**

- Tous les handlers Relations DOIVENT appliquer les règles de Teams :
  - aucune création/modification/suppression d’edge n’est effectuée si l’utilisateur
    n’a pas les droits nécessaires sur les items concernés (lecture/écriture structurelle
    ou fonctionnelle, selon le cas).

Ces événements sont consommables par :

- Gestion (pour marquer `state/blocked`, alimenter la progression agrégée),
- Logique (règles globales),
- Activity (journal d’activité),
- Interactions (actions déclenchées sur événements de relation).

### 2.2. RulesEngine & pack MCC Relations

Le pack MCC Relations :

- déclare les types de relation disponibles (`rel/depends-on`, `rel/blocks`, `rel/linked-to`, etc.)
  dans un dataset exploitable par l’UI,
- fournit des règles génériques, par exemple :
  - empêcher la création d’un edge si elle violerait l’acyclicité,
  - normaliser certains patterns (ex. création simultanée de A blocks B et B depends-on A, si on le souhaite).

Logique peut :

- désactiver ou compléter ces règles,
- définir des politiques globales (ex. comment propager des retards ou des blocages le long du graphe).

### 2.3. Intégration avec Gestion, Logique, Interactions

**Avec Gestion :**

- Gestion lit le graphe des `rel/depends-on` / `rel/blocks` pour :
  - déterminer quels items sont bloqués (candidats à `state/blocked`),
  - alimenter la progression agrégée (en s’appuyant sur le graphe, pas sur la structure).
- Les règles Gestion/Logique DOIVENT considérer `rel.edges` comme source de vérité pour
  les dépendances.

**Avec Logique :**

- Logique peut :
  - parcourir le graphe de relations pour détecter des patterns
    (chaînes critiques, composants, dépendances orphelines…),
  - déclencher des effets globaux (propagation d’états, correction d’incohérences).
- L’acyclicité des `rel/depends-on` garantit qu’un parcours en profondeur ou un tri topologique
  est toujours possible pour ces dépendances.

**Avec Interactions :**

- Les actions Interactions peuvent utiliser les relations comme scope ou filtre :
  - “Quand cette tâche est terminée, marquer tous les items qui en dépendent comme prêts”,
  - “Quand ce blocage est levé, enlever `state/blocked` sur les dépendants immédiats”.
- Les scopes basés sur les relations DOIVENT respecter les limites d’Interactions :
  - pas de scan implicite du board entier,
  - scopes explicites (dépendants directs, voisins à distance 1, listes ciblées, etc.).

---

## 3. Flux types

### 3.1. Création manuelle d’une relation “A dépend de B”

1. Le front ouvre le panneau Relations de A et l’utilisateur choisit :
   - type : “dépend de” → `kind = "rel/depends-on"`,
   - cible : B (sélection universelle).
2. Le front envoie une commande `Relations.CreateEdge` avec :
   - `boardId`,
   - `from = nodeId(A)`,
   - `to   = nodeId(B)`,
   - `kind = "rel/depends-on"`,
   - `source = { "mode": "manual" }`.
3. Le handler :
   - vérifie que A et B appartiennent au board,
   - vérifie `from != to`,
   - vérifie qu’il n’existe pas déjà un edge identique,
   - vérifie que l’ajout ne crée pas de cycle dans le graphe des `rel/depends-on`,
   - en cas de violation → `422 RELATION_CYCLE_DETECTED`,
   - sinon, ajoute l’edge à `rel.edges`.
4. Un événement `RelationCreated` est émis.

### 3.2. Suppression d’un item impliqué dans des relations

1. L’utilisateur supprime l’item X.
2. Le front envoie une commande `Node.Delete` (ou équivalent).
3. Lors de l’application :
   - toutes les arêtes de `rel.edges` où `from = X` ou `to = X` DOIVENT être supprimées,
4. Un ou plusieurs événements (`RelationDeleted`, etc.) sont émis.

---

## 4. Questions ouvertes / risques

- **Coût de la vérification d’acyclicité :**
  - sur des boards avec beaucoup de dépendances, il faudra veiller à ce que l’algorithme
    de détection de cycle soit incrémental et raisonnable (par ex. DFS limité au composant concerné).
- **Volume de relations générées par les assistants de liste :**
  - un assistant “Rendre cette liste fonctionnelle” appliqué sur une liste avec des centaines d’items
    génère des centaines d’arêtes en une fois ; à surveiller côté perf lors de cette opération ponctuelle.
- **Évolution des types de relations :**
  - si de nouveaux types de relations “porteuses de dépendance” sont ajoutés, il faudra clarifier
    s’ils rentrent dans le périmètre de l’acyclicité (même graphe ou graphe séparé).
- **Relations cross‑board :**
  - considérées comme hors périmètre V1 ; si nécessaires, elles nécessiteront une réflexion spécifique
    (adressage multi‑boards, invariants, impact sur les packs).
- **Rétro‑compatibilité :**
  - les boards existants sans `rel.edges` doivent être considérés comme valides ;
    les handlers doivent interpréter “aucun bloc de relations” comme “aucune relation déclarée”, sans erreur.
