# Plan de correction — Audit V4 (17/10/2025)

## Lot 1 — Unifier les mutations via `/api/commands`
- **Intention** : remplacer les endpoints REST mutateurs (boards, files, packs, admin) par des commandes explicites du CommandBus.
- **Impact attendu** : R1, R5, R6, R8 (fermeture des flux parallèles, applier exhaustif, MCC neutres).
- **Étapes** :
  - *Serveur* :
    1. Déclarer de nouvelles commandes (`Board.Create`, `Board.Rename`, `File.Compose`, `PackDataset.Upsert`, …) et leurs handlers.
    2. Exposer ces commandes dans `CommandController::mapCommand` avec validation (`code`/`message`).
    3. Supprimer / déprécier les routes REST mutantes en renvoyant 410 ou en les rebranchant sur le bus.
  - *Front* :
    1. Migrer `services/boards.js`, `services/files.js`, `packages/modules/runtime.js` vers le client CommandBus.
    2. Adapter les slots MCC (`board-structure`, `editor-tabs`, `settings-panel`) pour émettre uniquement des intentions (pas de `fetch`).
  - *CI / Guard* :
    1. Ajouter un grep bloquant pour `requestJson('/api/boards` etc. dans `tools/ci/guards.sh`.
    2. Vérifier que `traces/r1_*` deviennent vides lors du prochain audit.
- **Risques & rollback** : migration progressive possible (feature flag `REST_MUTATIONS_FALLBACK`) pour rerouter vers REST en cas d’incident.
- **Tests manuels** :
  1. Créer / renommer / supprimer un board (UI) et vérifier le payload de commande.
  2. Créer / éditer / supprimer un fichier Markdown depuis le slot `org`.
  3. Importer un dataset `org:categories` et vérifier l’ETag.
  4. Exécuter un drag & drop et confirmer que la commande `MoveNode` passe toujours.
- **Greps CI** :
  - `rg "requestJson\('/api/(boards|files|packs)" public/assets`
  - `rg "->add\('POST', '/api/(boards|files|packs|admin)" public/index.php`

## Lot 2 — Canoniser les réponses JSON & supprimer la normalisation
- **Intention** : produire `{ ok, code, message }` nativement, retirer le header `X-Envelope-Normalized`.
- **Impact attendu** : R2, R12.
- **Étapes** :
  - *Serveur* :
    1. Mettre à jour tous les contrôleurs REST restants (`BoardController::autosave`, `UserFilesController::error`, `PacksController`, etc.) pour retourner `code` explicite + `message` lisible.
    2. Ajouter des constantes d’erreurs (ex. `BOARD_CONFLICT`, `FILE_CONFLICT`, `DATASET_PRECONDITION_FAILED`).
    3. Après couverture complète, supprimer le bloc `X-Envelope-Normalized` dans `Response::json` et renforcer les tests d’intégration.
  - *Front* :
    1. Adapter les toasts / catchs pour consommer `code` au lieu de `status`/`error`.
  - *CI / Guard* :
    1. Ajouter un test HTTP (Pest/PHPUnit) qui échoue si `Response::json` rajoute encore le header en dev.
- **Risques & rollback** : faible ; conserver un flag `ENVELOPE_SMOKE` pour réactiver le header temporairement.
- **Tests manuels** :
  1. Provoquer un conflit autosave et vérifier `code: BOARD_CONFLICT`.
  2. Uploader un fichier trop volumineux (`413`) et valider le toast `FILE_TOO_LARGE`.
  3. Déclencher une erreur CSRF pour s’assurer du format.
- **Greps CI** :
  - `rg "'error' =>" src/Interfaces/Http/Controllers`
  - `rg "'status' =>" src/Interfaces/Http/Controllers`

## Lot 3 — Stabiliser l’arbitre structurel et les tags système
- **Intention** : rendre le réacteur structurel obligatoire et encapsuler les clés `system:*` / `type/*`.
- **Impact attendu** : R3, R4, R7.
- **Étapes** :
  - *Serveur* :
    1. Forcer `STRUCTURE_TAG_REACTIONS` à `true` (supprimer le flag runtime) et déplacer la clé structurante dans un registre système (`StructureTagRegistry`).
    2. Publier un helper `SystemTags::for('list')` côté moteur et pack.
  - *Front* :
    1. Remplacer les comparaisons `key === 'type/list'` par des helpers injectés (manifest/datasets → UI).
    2. Nettoyer les slots pour qu’ils ne définissent plus `sys.shape` manuellement (laisser le réacteur gérer).
  - *CI / Guard* :
    1. Grep bloquant `type/list` dans `public/assets` hors constants de mapping.
- **Risques & rollback** : prévoir un flag `STRUCTURE_TAG_REACTIONS_FORCE` pour réactiver l’ancienne logique en cas de bug.
- **Tests manuels** :
  1. Créer une liste et vérifier que le node reçoit `sys.shape=container` depuis le moteur.
  2. Retirer le tag `type/list` d’une liste vide pour observer le refus 422 si enfants présents.
  3. Contrôler l’affichage des badges `system:*` (plus de clé brute dans l’UI).
- **Greps CI** :
  - `rg "type/list" public/assets`
  - `rg "STRUCTURE_TAG_REACTIONS" -g"*.php"`

## Lot 4 — Politique temps & applier robuste
- **Intention** : harmoniser les timestamps (secondes) et refuser les opérations inconnues.
- **Impact attendu** : R3, R5.
- **Étapes** :
  - *Serveur* :
    1. Introduire un service `Clock` renvoyant des secondes (`int`) et l’utiliser dans `BoardState`, `BoardStateApplier`, `MySqlBoardRepository`.
    2. Modifier `applyOperation` pour lever une exception `UnknownOperation` avec log structuré.
    3. Ajouter des tests unitaires sur chaque opération couverte (node, tag, filters, workspace, column).
  - *Front* :
    1. Remplacer `Date.now()` par des conversions serveur ou des helpers centralisés (pas de timestamp forcé dans les slots).
  - *CI / Guard* :
    1. Grep bloquant `Date.now()` dans les contributions MCC.
    2. Ajouter un test d’intégration qui injecte une opération inconnue et attend un 422.
- **Risques & rollback** : possibilité d’ignorer temporairement les opérations inconnues via feature flag `APPLIER_STRICT=false`.
- **Tests manuels** :
  1. Exécuter une commande invalide (op inconnue) et vérifier la réponse 422 structurée.
  2. Vérifier que `updatedAt` / `revision` restent cohérents après plusieurs mutations.
  3. Charger un board existant et vérifier l’historique (timestamps seconds).
- **Greps CI** :
  - `rg "Date.now" public/assets/packs`
  - `rg "default => \$state" src/Domain/Boards/BoardStateApplier.php`

## Lot 5 — Pack “organisation” & datasets gouvernés
- **Intention** : déplacer la gestion du dataset `org:categories` dans le moteur avec validations, ETag et commandes dédiées.
- **Impact attendu** : R6, R7, R8.
- **Étapes** :
  - *Serveur* :
    1. Créer un handler `OrgCategoriesUpsert` (validation slug unique, couleur, icône) et `OrgCategoriesDelete`.
    2. Implémenter des contrôles ETag (`PRECONDITION_FAILED`) et journaux (`code`, `message`).
  - *Front* :
    1. Adapter `registries.datasets.patch` pour déléguer au bus (ou un proxy) ; retirer l’incrément de version côté UI.
    2. Utiliser les helpers pack → UI pour afficher les catégories.
  - *CI / Guard* :
    1. Grep `registries?.datasets?.patch?.('org:categories'` pour s’assurer de la migration.
- **Risques & rollback** : prévoir un mode compat qui sérialise la commande vers l’ancien REST si l’arbitre refuse (flag `ORG_DATASET_FALLBACK`).
- **Tests manuels** :
  1. Ajouter, modifier, supprimer une catégorie via MCC et vérifier la commande.
  2. Tester un conflit ETag (double onglet) → toast `DATASET_CONFLICT`.
  3. Vérifier l’affichage des badges catégorie dans le board.
- **Greps CI** :
  - `rg "registries\?\.datasets\?\.patch" public/assets`
  - `rg "org:categories" src/Interfaces/Http/Controllers`

## Lot 6 — Observabilité & garde-fous
- **Intention** : assainir les logs HTTP et renforcer les vérifications automatiques post-migration.
- **Impact attendu** : R2, R12 (suivi), support des lots précédents.
- **Étapes** :
  - *Serveur* :
    1. Ajouter des logs structurés (`json_encode`) pour les commandes refusées (code, boardId, userId).
    2. Désactiver `@error_log` dans `Response::json` après convergence et remplacer par un logger PSR conditionnel.
  - *CI / Guard* :
    1. Script de smoke-test HTTP (curl) pour `/api/commands` et `/api/files` → vérifier `Content-Type` + corps JSON.
    2. Surveillance des traces `traces/r12_observability.txt` (doit devenir vide).
- **Risques & rollback** : aucun (changement observabilité).
- **Tests manuels** :
  1. Observer les logs lors d’une erreur commande (attendre JSON structuré).
  2. Vérifier via DevTools qu’aucune réponse JSON n’a `X-Envelope-Normalized`.
  3. Déclencher une erreur 500 simulée et vérifier le masque message en prod.
- **Greps CI** :
  - `rg "X-Envelope-Normalized" src/Infrastructure`
  - `rg "RESPONSE_NORMALIZED" src/Infrastructure`

