# 04-interactions – Spécifications techniques

Ce document traduit les objectifs fonctionnels du pack Interactions en décisions techniques :
modèle JSON des actions “Avant / Après”, déclencheurs, résolution des cibles et intégration
avec le RulesEngine, Gestion, Relations, 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. Action = configuration attachée à un item porteur

**Rappel fonctionnel :**

- Une action est toujours pensée comme : **Événement → Conditions (Avant) → Effets (Après)**.
- En V1, l’événement principal est : *“quand l’item porteur est terminé”* (`state/done` appliqué).
- L’action est **centrée sur l’item porteur** :
  - l’événement est observé sur cet item,
  - les conditions peuvent se référer à cet item et à son contexte,
  - les cibles sont définies par rapport à lui (même container, items dépendants, etc.).

**Décision technique : bloc `ext.interactions.actions` sur le node porteur**

Une action est modélisée comme un objet JSON dans un tableau `actions` attaché à l’item porteur :

```json
{
  "ext": {
    "interactions": {
      "actions": [
        {
          "id": "uuid-action-1",
          "enabled": true,
          "label": "Créer une tâche de suivi",
          "trigger": {
            "kind": "on-state-enter",
            "state": "state/done"
          },
          "before": {
            "conditions": [],
            "targets": [
              {
                "id": "target-1",
                "scope": "same-container",
                "filters": []
              }
            ]
          },
          "after": {
            "effects": [
              {
                "id": "effect-1",
                "type": "create-item",
                "targetRef": "target-1",
                "params": {
                  "titleTemplate": "Tâche de suivi pour {{porteur.title}}",
                  "defaultTags": ["state/todo"]
                }
              }
            ]
          },
          "meta": {
            "needsConfirmation": false,
            "description": "À la fin de cette tâche, créer une tâche de suivi dans le même container."
          }
        }
      ]
    }
  }
}
```

- `id` : identifiant unique de l’action (UUID).
- `enabled` : action active ou non.
- `label` / `description` : métadonnées pour l’UI (sans impact métier).
- `trigger` : décrit l’événement.
- `before` :
  - `conditions` : liste de conditions à vérifier avant exécution,
  - `targets` : descriptions de scopes/cibles possibles.
- `after` :
  - `effects` : effets à appliquer sur les cibles (ou le porteur).
- `meta.needsConfirmation` : indique si l’UI doit afficher un récapitulatif avant exécution.
- `meta.executionMode` : `"atomic"` en V1 (plan validé puis appliqué en un seul Apply). Le mode “best-effort” n’est pas exposé en V1.

Un “item de type Action” dans l’UI n’est qu’un node dont `ext.interactions.actions` est peuplé
(et éventuellement un style particulier), pas un type structurel distinct dans le snapshot.

### 1.2. Événement déclencheur (Trigger)

**Rappel fonctionnel :**

- V1 : un seul événement de base → *“quand l’item porteur est terminé”*.
- L’événement est **toujours local** à l’item porteur en V1.
- Les autres événements (temps, patterns globaux…) sont du ressort du pack Logique.

**Décision technique : champ `trigger`**

Le champ `trigger` de l’action a la forme :

```json
"trigger": {
  "kind": "on-state-enter",
  "state": "state/done"
}
```

- `kind = "on-state-enter"` : déclenché quand un tag d’état est ajouté.
- `state` : tag d’état cible (V1 : `state/done`).

**Sémantique précise de `on-state-enter` :**

- `on-state-enter` est déclenché uniquement lors d’une **transition** où :
  - l’item ne possède pas le tag `state/done` avant l’opération,
  - l’item possède le tag `state/done` après l’opération.
- L’événement métier sous‑jacent (`StateTagAdded(boardId, nodeId, "state/done")`) NE DOIT être émis
  que pour cette transition “absent → présent”.
  - Ré‑envoyer une commande qui ajoute `state/done` alors qu’il est déjà présent ne doit pas ré‑émettre
    `StateTagAdded`, donc ne pas re‑déclencher l’action.
- Si l’item repasse par un autre état (`doing` par exemple) puis revient à `done`, un nouvel événement `on-state-enter done` est émis (pas de blocage temporel arbitraire).

**Invariants techniques V1 :**

- `kind` DOIT être `"on-state-enter"`.
- `state` DOIT être `"state/done"`.

Plus tard, le pack Logique pourra introduire d’autres `kind`/`state` pour enrichir les triggers
porteur‑centrés (progression, ressources, `state/late`, etc.), mais Interactions NE DOIT PAS
introduire de trigger “global board” (du type “quand n’importe quel item du board…”).

Le moteur d’Interactions est abonné aux événements de changement d’état (`state/*`)
produits par les handlers de Gestion.

### 1.3. Bloc “Avant” : conditions & cibles

Le bloc `before` décrit :

- les **conditions** qui doivent être vraies pour que l’action soit exécutée,
- les **cibles candidates** sur lesquelles les effets vont s’appliquer.

#### 1.3.1. Conditions

Les conditions sont une liste d’objets de forme :

```json
{
  "id": "cond-1",
  "type": "state-is",
  "target": {
    "kind": "porteur"
  },
  "params": {
    "state": "state/done"
  }
}
```

Exemples possibles de `type` en V1 :

- `state-is` : vérifier qu’un item a (ou n’a pas) un certain tag d’état.
- `tag-has` : vérifier la présence d’un tag arbitraire.
- `progress-at-least` : vérifier que la progression d’un item atteint un seuil.
- `resource-at-least` : vérifier que la ressource cible a une quantité suffisante.
- `relation-exists` : vérifier qu’il existe au moins une relation d’un certain type.
- `deadline-passed` : vérifier qu’une échéance est dépassée.
- `actor-has-capability` : vérifier que l’acteur courant possède une capability Teams donnée.

**Target de condition :**

- `target.kind` peut être :
  - `"porteur"` : l’item porteur,
  - `"scope"` + `scopeRef` : une des cibles définies dans `before.targets`,
  - `"explicit"` : un item désigné explicitement (rare en V1, plutôt géré via scope).

Les conditions sont évaluées en conjonction (AND) : toutes doivent être vraies pour que l’action
soit exécutable.

#### 1.3.2. Cibles (targets)

Un “target” décrit **comment trouver des items** à partir du contexte du porteur :

```json
{
  "id": "target-1",
  "scope": "same-container",
  "filters": [
    {
      "type": "tag-has",
      "params": { "tag": "state/todo" }
    }
  ]
}
```

Exemples de valeurs de `scope` V1 (toutes **locales** au porteur) :

- `"self"` : l’item porteur lui-même.
- `"same-container"` : les siblings du porteur dans le même container.
- `"container"` : le container direct du porteur.
- `"container-children"` : les enfants du container du porteur.
- `"related-dependents"` : items qui **dépendent du porteur** (via `rel/depends-on`, pack Relations), distance = 1 uniquement.
- `"related-blocked"` : items **bloqués par le porteur** (via `rel/blocks`), distance = 1 uniquement.

**Invariants techniques sur le scope :**

- Un scope DOIT être **borné** :
  - pas de scope implicite “tous les items du board”,
  - tout scope doit pouvoir être évalué à partir de :
    - l’item porteur,
    - ses relations directes,
    - son container/voisinage immédiat.
- Les scopes basés sur les relations sont limités à la **distance 1** en V1 ; au-delà, passer par Logique.
- Les scopes qui nécessiteraient de parcourir tout le board DOIVENT passer par le pack Logique,
  pas par Interactions.
- La résolution des scopes DOIT être filtrée par Teams :
  - les items que l’utilisateur n’a pas le droit de voir sont exclus du scope,
  - les items qu’il peut voir mais pas modifier sont exclus des cibles effectives pour les effets
    (ils peuvent éventuellement être pris en compte dans des conditions purement en lecture) ;
  - en tout état de cause, l’ensemble effectif des cibles doit rester cohérent avec les droits Teams.

Les `filters` sont appliqués après la résolution brute du scope (tags, états, types, etc.).

### 1.4. Bloc “Après” : effets

Le bloc `after` liste les effets à appliquer, chacun faisant référence à une cible via `targetRef` :

```json
{
  "id": "effect-1",
  "type": "create-item",
  "targetRef": "target-1",
  "params": {
    "titleTemplate": "Tâche de suivi pour {{porteur.title}}",
    "defaultTags": ["state/todo"]
  }
}
```

Exemples d’effets V1 :

- `create-item` :
  - crée un nouvel item dans un container cible,
  - paramètres : titre, tags par défaut, position (avant/après le porteur, en fin de liste, etc.).
- `update-state` :
  - ajoute/enlève des tags d’état (`state/*`) sur les items cibles,
  - DOIT respecter les invariants de Gestion (états principaux exclusifs, combinaisons interdites).
- `update-tags` :
  - ajoute/enlève des tags arbitraires.
- `set-progress` :
  - met à jour la progression effective/maximale, en respectant les bornes de Gestion.
- `adjust-resource` :
  - envoie une commande Gestion pour ajuster `ext.gestion.resource.quantity`
    (consommation de ressources), en respectant la non-négativité configurée.
- `create-relation` / `delete-relation` :
  - crée ou supprime des relations via les commandes Relations appropriées,
  - DOIT respecter les invariants de Relations (acyclicité, pas de self‑loop).

**Invariants techniques sur les effets :**

- Aucun effet ne doit :
  - modifier directement les permissions (Teams reste unique source de vérité),
  - créer des items ou des relations dans un autre board que celui du porteur,
  - créer des actions planifiées ou modifier Logique/Teams/packs.
- Tout effet DOIT se traduire en une ou plusieurs **commandes standards** (`/api/commands`) :
  - création d’item,
  - ajout/retrait de tag,
  - mise à jour de progression,
  - ajustement de ressource,
  - création/suppression de relation, etc.
- Les erreurs des effets sont remontées action par action (pas de “silent fail”) et
  loggées dans Activity (avec code d’erreur et message).

### 1.5. Confirmation et explicabilité

Le champ `meta.needsConfirmation` permet de marquer une action comme “sensible” :

- l’UI DOIT, pour ces actions :
  - afficher un récapitulatif (nombre d’items affectés, types d’opérations),
  - attendre une confirmation explicite avant d’envoyer les commandes d’effets.

Les exécutions d’actions DOIVENT être traçables :

- chaque exécution porte un identifiant de “run”,
- les commandes générées sont liées à ce run dans Activity.

---

## 2. Points d’intégration

### 2.1. RulesEngine & déclenchement

Le pack Interactions ne définit **aucun moteur séparé** : il s’appuie entièrement sur le RulesEngine
existant et le format de règles de Logic. Concrètement :

- la configuration JSON stockée dans `ext.interactions.actions` est **compilée** au chargement du board
  en un ensemble de règles Logic équivalentes (porteur‑centrées, scopes locaux),
- à l’exécution, le RulesEngine ne “voit” que des règles Logic ; il ne distingue pas si elles proviennent
  de packs MCC, de `ext.logic.rules` ou de la compilation d’Interactions.

Le flux de déclenchement est alors :

1. Lorsqu’un handler de Gestion applique un changement d’état (`state/*`) à un item :
   - il émet un événement métier (ex. `StateTagAdded(boardId, nodeId, tag)`).
2. Le pack MCC Interactions (ou la compilation des actions déclarées dans `ext.interactions.actions`)
   contient des règles Logic du type :
   - “si `StateTagAdded` avec `tag = state/done` pour un node qui possède `ext.interactions.actions`…”
3. Pour chaque action de ce node :
   - si `trigger.kind = "on-state-enter"` et `trigger.state = "state/done"`,
   - alors la règle enfile une “évaluation d’action” (job interne ou message dans un canal dédié).
4. L’évaluation d’action (via la règle Logic issue d’Interactions) :
   - résout les conditions (`before.conditions`),
   - si elles sont toutes vraies :
     - résout les cibles (`before.targets`),
     - prépare les effets (`after.effects`),
     - si `needsConfirmation = false` : exécution directe,
     - sinon : l’UI est notifiée pour confirmation.
- L’**acteur** d’une exécution est l’utilisateur qui a déclenché l’événement (ex. celui qui a posé `state/done`) ;
  chaque effet est vérifié vis-à-vis de ses capabilities Teams avant émission des commandes.

Logique peut :

- ajouter de nouveaux types d’événements porteur‑centrés (par ex. `on-progress-threshold`, `on-late`, etc.),
- exposer ces événements comme triggers supplémentaires pour Interactions,
- mais NE DOIT PAS introduire de trigger global qui ne soit pas ancré sur un porteur.

### 2.2. CommandBus & exécution des effets

L’exécution des effets passe toujours par le CommandBus (`POST /api/commands`) :

- pas de canal parallèle, pas de mutation “hors bus”.

Pour un run d’action donné :

- l’exécutant (Logic/Interactions) construit un **plan** de commandes :
  - `CreateNode`,
  - `AddTagV3Command` / `RemoveTagV3Command`,
  - `Gestion.SetProgress`,
  - `Gestion.AdjustResource`,
  - `Relations.CreateEdge` / `Relations.DeleteEdge`,
  - etc.
- ces commandes sont ensuite envoyées de manière cohérente via le CommandBus, en respectant le
  modèle atomique du snapshot (chaque commande appliquée par le bus est “tout ou rien”).

**Granularité d’atomicité :**

- Chaque commande appliquée via le CommandBus est atomique (Apply = tout ou rien au niveau de la commande).
- En V1, Interactions ne propose **aucun mode best‑effort** : si, au moment du run, une condition ou une
  cible rend le plan invalide, le run est abandonné et aucun effet partiel n’est appliqué.

### 2.3. UI & datasets

Le pack MCC Interactions fournit :

- un dataset des types de conditions (`state-is`, `tag-has`, `progress-at-least`, …),
- un dataset des types d’effets (`create-item`, `update-state`, `adjust-resource`, …),
- un dataset des scopes (`self`, `same-container`, `related-dependents`, …),

que l’UI utilise pour construire le panneau d’édition des actions :

- chaque action est éditable via un formulaire structuré basé sur ces datasets,
- l’UI ne construit pas de JSON “libre” : elle remplit le schéma prévu pour `ext.interactions.actions`.

### 2.4. Budget d’exécution & garde‑fous

Pour éviter les boucles infinies ou les explosions combinatoires :

- Interactions DOIT appliquer un **budget d’exécution** par événement source (par exemple un `StateTagAdded`
  donné sur un porteur) :
  - limite sur le nombre total de runs d’actions déclenchés en cascade,
  - limite sur la profondeur de chaîne (A déclenche B qui déclenche C…),
  - limite sur le nombre total de commandes générées dans ce contexte.
- Lorsque ce budget est dépassé :
  - l’évaluation en cours est interrompue,
  - le run (ou la branche concernée) est marqué comme échoué pour cause de budget dépassé,
  - un événement d’erreur explicite est loggé dans Activity.

Les valeurs exactes de ce budget (limites, configuration) peuvent être définies dans une phase
ultérieure, mais l’existence même de ce garde‑fou est **obligatoire**.

### 2.5. Modes d’exécution (`meta.executionMode`)

- V1 : uniquement le mode `atomic` exposé.
- Sémantique `atomic` :
  1. Évaluer conditions et cibles (dry run) côté RulesEngine/Interactions.
  2. Construire un plan complet (cibles résolues + effets).
  3. Émettre les commandes correspondantes via le CommandBus, en respectant les invariants noyau.
- Le backend ne fait pas confiance à un plan calculé côté client : tout plan est au minimum validé /
  recalculé côté serveur avant application.
- Pas de mode best‑effort exposé en V1 (reste éventuel mode interne de débogage).

---

## 3. Structures de données

### 3.1. Schéma JSON d’une action

Sans figer un JSON Schema complet, les champs minimaux sont :

- `id: string` (UUID)
- `enabled: boolean`
- `label: string`
- `trigger: { kind: string, state?: string }`
- `before: { conditions: Condition[], targets: Target[] }`
- `after: { effects: Effect[] }`
- `meta: { needsConfirmation?: boolean, description?: string, executionMode?: "atomic" }` (V1 : toujours `"atomic"`, champ non exposé à l’UI)

**Condition (V1) :**

- `id: string`
- `type: "state-is" | "tag-has" | "progress-at-least" | "resource-at-least" | "relation-exists" | "deadline-passed" | "actor-has-capability" | ..."`
- `target: { kind: "porteur" | "scope" | "explicit", scopeRef?: string, nodeId?: string }`
- `params: object` (dépend du type)

**Target (V1) :**

- `id: string`
- `scope: "self" | "same-container" | "container" | "container-children" | "related-dependents" | "related-blocked" | ..."`
- `filters: Filter[]`

**Effect (V1) :**

- `id: string`
- `type: "create-item" | "update-state" | "update-tags" | "set-progress" | "adjust-resource" | "create-relation" | "delete-relation" | ..."`
- `targetRef: string` (référence à un `Target.id`)
- `params: object` (dépend du type)

**Meta (V1) :**

- `needsConfirmation?: boolean`
- `description?: string`
- `executionMode?: "best-effort" | "atomic"` (V1 : `"atomic"` seulement ; `"best-effort"` réservé à des usages internes / futurs, non exposé à l’UI)

### 3.2. Stockage & versioning

- Le bloc `ext.interactions` est optionnel :
  - absence = aucun comportement d’Interactions pour ce node.
- Le format des actions DOIT être versionné implicitement :
  - soit via un champ `version` sur chaque action,
  - soit via une convention de migration dans le pack MCC Interactions.

Les migrations de format (V1 → V2…) DOIVENT être gérées par des scripts/mécanismes dédiés,
pas par l’UI.

---

## 4. Flux types

### 4.1. “Quand cette tâche est terminée, créer une tâche de suivi”

1. L’utilisateur configure une action sur un item T (ou utilise un item de type “Action”) :
   - `trigger = { "kind": "on-state-enter", "state": "state/done" }`,
   - `before.conditions = []`,
   - `before.targets = [ { "id": "t-container", "scope": "same-container", "filters": [] } ]`,
   - `after.effects = [ { "type": "create-item", "targetRef": "t-container", "params": {...} } ]`.
2. Au moment où T reçoit `state/done` :
   - le handler Gestion émet `StateTagAdded(boardId, T, "state/done")`,
   - le pack Interactions repère les actions de T et programme leur évaluation.
3. L’évaluation :
   - vérifie `conditions` (ici : vide → OK),
   - résout la target `"t-container"` comme le container de T.
4. L’exécution :
   - construit une commande `CreateNode` dans ce container avec le titre/tag définis,
   - envoie la commande via le CommandBus,
   - logge un run d’action dans Activity.

### 4.2. Action “Tout est prêt” basée sur les relations

1. Un item X a plusieurs dépendants Y1, Y2, Y3 (via `rel/depends-on`).
2. Une action “Tout est prêt” est définie sur X :
   - `trigger` : `on-state-enter` sur `state/done`,
   - `before.targets` contient un `scope = "related-dependents"`,
   - `after.effects` contient un `update-state` pour ajouter un tag `state/ready` sur ces cibles.
3. Quand X est marqué `state/done` :
   - Interactions résout le scope `related-dependents` via le pack Relations (lecture de `rel.edges`),
   - applique, via des commandes de tags, le tag `state/ready` sur Y1, Y2, Y3,
   - respecte les invariants de Gestion (aucune combinaison d’états interdite).

### 4.3. Action de consommation de ressources

1. Une action est définie sur une tâche T pour consommer `10` unités d’une ressource R :
   - `before.targets` : `target-1` avec un `scope` local (par exemple `container-children`) et un
     filtre `resource-at-least` qui identifie la ressource R dans le container/stock ciblé,
   - `after.effects` : `effect-1` de type `adjust-resource` avec `targetRef = "target-1"` et `delta = -10`.
2. Quand T passe en `state/done` :
   - Interactions vérifie que R existe et a assez de quantité (via une condition `resource-at-least`),
   - envoie une `Gestion.AdjustResource(boardId, nodeId=R, delta=-10)`,
   - en cas de refus (quantité insuffisante, politique stricte…) :
     - l’effet est marqué comme échoué,
     - Activity enregistre l’erreur.

### 4.4. Exécution manuelle avec confirmation

1. Une action potentiellement destructive (ex. suppression en masse) a `needsConfirmation = true`.
2. L’UI propose un bouton “Exécuter l’action” sur l’item porteur.
3. Lors du clic :
   - au lieu d’exécuter immédiatement les effets, un “dry run” est effectué côté Interactions :
     - résolution des cibles,
     - construction de la liste des commandes **sans les envoyer**.
4. Le résultat (cibles + types d’opérations) est renvoyé au front pour affichage d’un résumé.
5. Si l’utilisateur confirme :
   - les commandes sont envoyées via le CommandBus,
   - Activity enregistre le run et les commandes associées.

---

## 5. Questions ouvertes / risques

- **Boucles d’actions complexes :**
  - même avec un budget, certains scénarios de configurations croisées peuvent produire
    des comportements non intuitifs ; il faudra prévoir de bons outils de debug (traces, visualisation).
- **Complexité de l’UI :**
  - besoin d’un mode “simple” (actions guidées prédéfinies) vs “avancé” (édition complète),
  - éviter que l’utilisateur se perde dans des configurations trop abstraites.
- **Performance et scopes :**
  - même avec des scopes bornés, certaines actions peuvent toucher beaucoup d’items ;
  - nécessité de prévoir une exécution incrémentale / paginée pour des effets massifs.
- **Debug & audit :**
  - prévoir des outils de visualisation dans Activity (filtre “actions exécutées”, détail des runs),
  - faciliter le débogage pour un utilisateur avancé (“Pourquoi cette action ne s’est pas déclenchée ?”).
