# 01-foundations – Spécifications techniques

## 1. Concepts techniques clés

Cette section précise comment les concepts fonctionnels des fondations se traduisent dans
le code V4 actuel et dans les futures évolutions. On reste au niveau design : rien n’est
encore “figé” dans le code.

### 1.1. Identité & adressage

**État actuel :**

- Les nodes sont identifiés par un `nodeId` :
  - généré via `Domain/Shared/Identifiers::new()` (UUID v4),
  - utilisé comme clé dans le snapshot du board (`state['nodes'][$nodeId]`).
- Les boards sont identifiés par un `boardId` :
  - identifiant interne (entier auto-incrément en base, cf. table `boards.id`),
  - exposé dans l’API (paramètres de commandes, endpoints `/api/boards/{boardId}`, etc.),
  - transporté dans les commandes comme une **chaîne** (mais conceptuellement c’est l’ID du board).

**Décision technique : adresse canonique :**

- L’adresse **canonique minimale** d’un item est :

  ```json
  {
    "boardId": "<board-id>",
    "nodeId": "<uuid-node-id>"
  }
  ```

- Même si `nodeId` est globalement unique, on **conserve toujours `boardId`** dans l’adresse pour :
  - simplifier la résolution (chargement du bon snapshot),
  - simplifier les logs et la traçabilité,
  - simplifier la gestion des permissions (Teams) et des publications (Community).

**Invariants techniques :**

- `nodeId` :
  - généré **une fois** via `Identifiers::new()`,
  - jamais réécrit ni réutilisé pour un autre node.
- `boardId` :
  - identifie le snapshot sur lequel on applique un `BoardPatch`,
  - est systématiquement présent dans toutes les commandes qui ciblent un node
    (`CreateNodeCommand`, `UpdateNodeCommand`, `MoveNodeCommand`, `DeleteNodeCommand`,
    commandes de tags, etc.),
  - est manipulé comme chaîne côté commandes et HTTP, mais représente un ID de board.

### 1.2. Sélection universelle (front + contrat backend)

La sélection universelle est avant tout un **service front** qui produit des paires
`{boardId, nodeId}`. Le backend ne “connaît” pas la session de sélection.

**Contrat fonctionnel traduit en technique (UiSelection) :**

- Le front expose conceptuellement un service de ce genre :

  ```ts
  type NodeAddress = { boardId: string; nodeId: string };

  function requestSelection(options: {
    label: string;                         // message utilisateur : "Choisissez un container cible…"
    allowedShapes?: ("container" | "leaf")[]; // filtrage sur sys.shape
    requiredTags?: string[];               // éventuellement filtrage sur certains tags
    multi?: boolean;                       // sélection simple ou multiple
  }): Promise<NodeAddress[]>;
  ```

- Ce service :
  - met l’UI en mode « sélection » (overlay, feedback),
  - capte le clic sur un node et renvoie les adresses,
  - ne parle jamais directement au backend : il se contente de produire des adresses.

On appelle cette sélection **UiSelection** : un ensemble d’adresses éphémères, propres à la session
de l’utilisateur, utilisées pour des actions UI “brutes” (ex. appliquer un tag à une sélection).

**Côté backend / domaine (DomainSelection) :**

- **Aucune** API spécifique de sélection n’est ajoutée.
- Les fonctionnalités qui ont besoin d’une cible reçoivent du front une commande contenant déjà
  `{ boardId, nodeId }` (ou une liste de ces adresses) lorsqu’il s’agit d’actions UI simples
  en best‑effort.
- Les packs Interactions et Logic, eux, travaillent sur des **DomainSelections** :
  - des scopes et filtres décrits de façon déclarative (ex. “same‑container”, “related‑dependents”),
  - toujours **recalculés côté backend** à partir du snapshot et de Teams au moment du run,
  - sans jamais considérer une UiSelection brute comme “vérité” (existence, droits) pour une action atomique.

Exemple (MoveNode) :

  ```json
  {
    "type": "MoveNode",
    "boardId": "123",
    "payload": {
      "nodeId": "550e8400-e29b-41d4-a716-446655440000",
      "toParentId": "3b8bb262-9b65-43a1-a589-bd565fcb3235",
      "toIndex": 0
    }
  }
  ```

### 1.3. Sémantique `container` / `leaf`

**État actuel :**

- Dans le snapshot du board, chaque node possède un champ :

  ```json
  {
    "sys": {
      "shape": "container" | "leaf"
    }
  }
  ```

- Dans la V4 actuelle, un réacteur (`StructureTagReactor`) écoute encore un tag historique
  (`type/list`) et propose des mises à jour de `sys.shape` :
  - `tag.add type/list` → `sys.shape = "container"` (idempotent),
  - `tag.remove type/list` → `sys.shape = "leaf"` si aucun enfant, sinon veto.

**Décision (cible) :**

- `sys.shape` reste la **seule source de vérité structurelle** pour “peut avoir des enfants ou non”.
- Les modules métier (Progression, Relations, Interactions, etc.) ne doivent **jamais**
  contourner cette vérité :
  - pas de children pour un node avec `shape = "leaf"`,
  - les transformations container ↔ leaf passent par des opérations explicites
    (et ne laissent jamais l’arbre dans un état invalide).
- Deux tags système `structure/*` sont réservés pour donner une **sémantique** aux containers :
  - `structure/folder`  : “dossier” neutre,
  - `structure/hierarchy` : “conteneur hiérarchique” (étapes / sous-tâches).
- Ces tags ont une magie **strictement limitée** :
  - appliqués à un node `leaf`, ils **proposent** automatiquement la promotion de ce node en
    `sys.shape = "container"` avec une liste d’enfants vide, puis appliquent le tag ;
  - retirés d’un node `container`, ils **n’entraînent jamais** un retour automatique en `leaf`
    (le node reste container, seule la sémantique `structure/*` disparaît).

**Évolution et garde-fous :**

- Le lien tag structurel ↔ `sys.shape` est limité à ces deux tags noyau `structure/folder` et
  `structure/hierarchy`.
- On évite de multiplier ce type de couplage :
  - aucun pack utilisateur ne peut introduire de nouveau tag “magique” qui mute `sys.shape`,
  - toute nouvelle logique métier liée aux containers/leafs doit passer par :
    - des règles dans le RulesEngine,
    - ou des flags explicites dans des champs d’extension (`ext.*`), pas par une inflation
      de tags structurels supplémentaires.

### 1.4. Tags : système vs utilisateur

**État actuel :**

- Les tags sont gérés via :
  - des commandes `AddTagV3Command`, `RemoveTagV3Command`,
  - des déclarations de tags système dans les manifests MCC des packs (`packs/*/manifest.mcc.json`),
  - un registre des tags système (`Infrastructure/Packs/SystemTagRegistry`),
  - des datasets côté front pour l’affichage (labels, couleurs, icônes).

**Décision : espaces de noms & rôles :**

- On sépare explicitement :
  - **tags système** :
    - préfixés par des familles bien définies (exemples) :
      - `state/*` (états de tâches : `state/todo`, `state/doing`, `state/done`, `state/late`…),
      - `type/*` (types d’items : `type/note`, etc.),
      - `structure/*` (sémantique structurelle noyau : `structure/folder`, `structure/hierarchy`),
      - `rel/*` (relations : `rel/depends-on`, `rel/blocks`, …),
      - `action/*` (tags d’action, si besoin),
    - déclarés via les packs MCC (pas “en dur” dans le code métier),
    - interprétés par des règles explicites dans `RulesEngine`.
  - **tags utilisateur** :
    - non réservés,
    - non interprétés globalement par le noyau,
    - utilisés uniquement comme critères dans les règles que l’utilisateur configure (via Interactions / Logic).

**Implication technique :**

- Les règles ne doivent jamais “deviner” un comportement implicite à partir d’un tag inconnu :
  - un tag non présent dans le dictionnaire des tags système est traité comme un simple label.
- Le code central (Domain / Application) n’utilise que :
  - des tags système nommés explicitement (ex. `structure/folder` / `structure/hierarchy`
    dans le `StructureTagReactor` cible),
  - ou des tags passés par le RulesEngine via des règles MCC.

### 1.5. Temps & événements

**État actuel :**

- Le snapshot du board inclut au minimum :
  - `version` (schéma V4),
  - `updatedAt` en secondes (timestamp du dernier changement du snapshot),
  - des timestamps au niveau des nodes (`updatedAt`).
- La base de données suit également des timestamps en millisecondes pour les métadonnées
  de board (`boards.updated_at`, etc.).
- Un scheduler existe déjà pour exécuter périodiquement des tâches (notifications, digest, etc.).

**Décision : modèle temporel minimal :**

- On considère comme acquis :
  - que chaque board/snapshot peut être évalué à un instant `now` (UTC),
  - que les items peuvent porter des attributs de type date/heure (échéances, deadlines, jalons),
    stockés dans des champs normalisés (ex. `ext.dueDate`, à préciser dans Progression/Logic).

- Les **événements** (au sens logique) seront modélisés comme des conditions dérivées, évaluées
  par `RulesEngine`, de type :
  - “tag `state/done` vient d’être ajouté”,
  - “`now > dueDate`”,
  - “relation `rel/depends-on` supprimée”.

- Le moteur de règles utilisera des **triggers** symboliques (chaînes) pour représenter
  ces événements, par exemple :
  - `command:createNode` (opération `node.create` vue par le moteur),
  - `tag:added:state/done`,
  - `time:tick-daily`.

La définition précise des triggers disponibles et de leur encodage MCC sera effectuée
dans les phases Progression, Interactions, Logic, mais les fondations actent que :

- le temps est évalué côté backend,
- les événements sont soit dérivés des données, soit générés par le scheduler,
- dans la V4 actuelle (sans le pack Activity), il n’y a pas de stockage persistant d’événements individuels ; Activity introduira ce stockage dédié en s’appuyant sur ces mêmes concepts de temps/événements.

## 2. Points d’intégration dans le code existant

### 2.1. Application/Commands

- Toutes les commandes qui ciblent des nodes suivent le schéma général :

  ```php
  final class SomeNodeCommand implements Command
  {
      public function __construct(
          private readonly string $boardId,
          private readonly string $actorId,
          private readonly string $nodeId,
          // autres paramètres...
      ) {
      }

      public function boardId(): ?string { return $this->boardId; }
      public function actorId(): string { return $this->actorId; }
      public function nodeId(): string { return $this->nodeId; }
      // ...
  }
  ```

- Pour les commandes qui créent des nodes (`CreateNodeCommand`) :
  - soit le `nodeId` est généré côté handler via `Identifiers::new()`,
  - soit (pour des cas avancés) il peut être fourni explicitement mais reste soumis à l’unicité.

### 2.2. Domain/Boards (BoardState, BoardPatch, BoardStateApplier)

- `BoardState` reste la source de vérité de la structure et des métadonnées du board
  (snapshot JSON v4, cf. `BoardState::fromArray()`).

- `BoardPatch` ne manipule jamais directement :
  - des références “faibles” (par nom ou position),
  - seulement des `nodeId` pour désigner les nodes.

- `BoardStateApplier` :
  - vérifie systématiquement la cohérence de `sys.shape` (leaf sans enfants, etc.),
  - est l’endroit qui modifie effectivement la structure (liste d’enfants, parents, ordre),
  - peut être étendu pour de nouvelles opérations, mais sans violer les invariants V4
    (cf. `BoardInvariantChecker`).

### 2.3. Domain/Rules (RulesEngine)

- Les triggers d’événements et les actions référenceront les items par `{ boardId, nodeId }` :
  - `RuleContext` inclura toujours ces identifiants pour une cible donnée.
  - Les conditions sur le temps ou les tags seront évaluées à partir du snapshot du board
    et de `now`.

- Les projecteurs (`RuleContextProjector`, `OperationTargetProjector`, `TagSnapshotProjector`) restent
  responsables de :
  - trouver les nodes touchés par un patch,
  - produire les “cibles” sur lesquelles les règles sont évaluées.

### 2.4. Infrastructure/Packs (MCC, tags, triggers)

- Les manifests MCC continuent de déclarer :
  - des tags système (avec leur famille, label, icône),
  - des datasets (listes de catégories, etc.),
  - des règles (triggers, conditions, actions).

- Les triggers temporels ou événementiels (échéances, “item terminé”, etc.) seront introduits
  progressivement dans les packs ciblés (Progression, Interactions, Logic) et consommés par
  `RulesEngine`.

### 2.5. Interfaces/HTTP (API)

- L’API ne reçoit et ne renvoie que des identifiants :
  - pas de références implicites par nom ou par position,
  - les clients front doivent déjà posséder les `boardId` et `nodeId`.

- Pour les opérations déclenchées par l’utilisateur sur une **UiSelection** (ex. appliquer un tag
  à la sélection courante) :
  - l’API reçoit uniquement le résultat de la sélection (adresses best‑effort),
  - l’API ne gère pas de flux interactifs (pas de “session de sélection” côté backend).
- Pour les exécutions Interactions/Logic (DomainSelection) :
  - les scopes/cibles sont décrits dans la configuration (règles, actions),
  - la sélection effective est **recalculée côté backend** à partir du snapshot courant,
  - les adresses obsolètes ou non autorisées sont filtrées avant tout effet (0 cible valide ⇒ aucun effet).

## 3. Structures de données / schémas

### 3.1. Adresse d’un node

Dans les payloads de commandes, les règles MCC, les logs et les permissions, on considère :

```json
{
  "boardId": "123",
  "nodeId": "550e8400-e29b-41d4-a716-446655440000"
}
```

comme forme canonique d’une adresse de node.

### 3.2. Snapshot du board (rappel des champs pertinents)

Exemple simplifié, aligné sur `BoardState::empty()` :

```json
{
  "version": 4,
  "updatedAt": 1710000000,
  "rootId": "root",
  "nodes": {
    "root": {
      "id": "root",
      "parentId": null,
      "children": [],
      "order": 0,
      "tags": [],
      "title": "Workspace",
      "description": null,
      "content": null,
      "props": [],
      "sys": {
        "shape": "container"
      },
      "updatedAt": 1710000000
    }
  },
  "filters": [],
  "ext": {}
}
```

- `version` : version de schéma du snapshot (V4).
- `rootId` : identifiant du node racine (`"root"` dans l’état actuel).
- `nodes` : dictionnaire `{ nodeId → node }`, où chaque node porte notamment :
  - `sys.shape` : `container` ou `leaf`,
  - `children` : présent seulement pour les containers, liste ordonnée de `nodeId`.

Le `boardId` lui-même est stocké en dehors du snapshot (dans la table `boards`) et est passé
aux commandes et au RulesEngine.

### 3.3. Représentation des événements (conceptuelle)

Les événements métier ne sont pas stockés comme entités persistées, mais représentés par :

- des triggers dans les règles MCC :

  ```json
  {
    "id": "rule:late-if-deadline-passed",
    "scope": "board",
    "triggers": ["time:tick-daily"],
    "conditions": [
      { "type": "field-gt", "field": "ext.dueDate", "value": "now" },
      { "type": "tag-not-present", "tag": "state/done" }
    ],
    "actions": [
      { "type": "add-tag", "tag": "state/late" }
    ]
  }
  ```

- des conditions dérivées à partir :
  - des timestamps (`sys.updatedAt`, `updatedAt` global),
  - des champs d’extension temporels (`ext.dueDate`, etc.),
  - des tags (`state/done`, `state/late`, etc.).

## 4. Flux types (avant / après)

### 4.1. Exemple : déplacement d’un item vers un autre container

1. **Front**
   - L’utilisateur clique sur “Déplacer dans…”.
   - Le front appelle `requestSelection({ allowedShapes: ["container"] })`.
   - L’utilisateur clique sur un container cible → le service renvoie `{ boardId, nodeId: toParentId }`.

2. **API**
   - Envoie une commande `MoveNodeCommand` avec :
     - `boardId` (string),
     - `payload` contenant `nodeId`, `toParentId`, `toIndex`.

3. **Application**
   - `MoveNodeHandler` construit un `BoardPatch` avec une op `node.move`.
   - `CommandBus` passe par l’Arbitre, le `RulesEngine`, les Reactors, etc.

4. **Domain**
   - `BoardStateApplier` :
     - vérifie que `toParentId` est un container (`sys.shape = "container"`),
     - met à jour la liste `children` du parent source et du parent cible,
     - remet à jour les ordres, etc.
   - `BoardInvariantChecker` valide la structure (pas de cycles, leaf sans children, etc.).

5. **Retour**
   - snapshot mis à jour ou delta renvoyé au front pour mise à jour de l’UI.

### 4.2. Exemple : règle temporelle “en retard si échéance dépassée”

1. **Scheduler**
   - Déclenche périodiquement un trigger logique (par exemple `time:tick-daily` pour un board).

2. **Application / RulesEngine**
   - Le `RulesEngine` évalue toutes les règles avec trigger `time:tick-daily`.
   - Pour chaque item ayant une échéance :
     - test `now > dueDate`,
     - test `state/done` absent.

3. **Actions**
   - Pour les items concernés, construction d’un `BoardPatch` avec des op `tag.add state/late`.

4. **Domain**
   - `BoardStateApplier` applique les tags,
   - `BoardInvariantChecker` vérifie la cohérence (structure inchangée).

5. **Front**
   - lors de la prochaine ouverture / rafraîchissement du board, l’état “En retard” apparaît.

## 5. Questions ouvertes / risques

### 5.1. Identité & adressage

- Faut-il introduire un **UUID global pour les boards** (en plus du `boardId` interne) pour faciliter
  certaines intégrations externes / partages publics ?
  - Décision différée : non nécessaire pour les fonctionnalités internes immédiates.

### 5.2. Root & nodes spéciaux

- Le `rootId` doit-il rester une constante spéciale (`"root"`) ou devenir un node UUID comme les autres ?
  - À clarifier si certaines fonctionnalités (Community, Teams) souhaitent s’accrocher à des sous-arbres
    particuliers ou à des “workspaces” logiques.

### 5.3. Temps & scheduler

- Faut-il prévoir dès maintenant une granularité plus fine :
  - events temps réel (webhooks internes) vs ticks périodiques ?
- Pour l’instant, un scheduler périodique simple (quotidien/horaire) est suffisant pour Progression.

### 5.4. Multiplication des tags système

- Risque : explosion du nombre de tags système (`state/*`, `type/*`, `rel/*`, `action/*`, etc.)
  qui compliquerait la compréhension globale.
- Mitigation :
  - centraliser leur définition dans les packs MCC et/ou un “dictionnaire” global,
  - documenter clairement les familles de tags et leurs usages.

### 5.5. Couplage container/leaf – tags structurels

- Le mécanisme existant (`type/list` → `container`) est déjà un couplage fort.
- Risque :
  - vouloir reproduire ce pattern pour d’autres comportements, créant une forêt de tags “magiques”.
- Position :
  - les fondations actent que `sys.shape` est structurel,
  - les tags ne sont pas le principal vecteur pour encoder de nouveaux comportements structurels :
    on privilégie les règles et les assistants.
