Aller au contenu

ADR 0006 — safeResolve en mode NEEDS-REVIEW

Contexte

Les checks de porte utilisent des patterns de chemin comme .product/.epics/{id}/hypothesis.md. Le {id} est substitué par l'identifiant de l'objet en transition. La fonction safeResolve garantit que le chemin résolu reste dans le projectRoot — sinon, le risque serait qu'un identifiant malicieux ou mal formé lise un fichier arbitraire du système.

En V2, un chemin échappant au projectRoot produisait un fail silencieux : le fichier « n'existait pas ». Ce comportement posait problème :

  • Un projet légitime avec un mapping custom (mono-repo, symlink) obtenait des fail cryptiques.
  • L'utilisateur ne comprenait pas pourquoi le check échouait.
  • L'option --force devenait un réflexe, masquant d'autres problèmes réels.

Options envisagées

  1. Garder le fail silencieux — statu quo, insatisfaisant.
  2. Abort dur : si le chemin échappe au projectRoot, erreur fatale de la CLI.
  3. fail explicite avec message clair — mais reste bloquant.
  4. warn qui déclenche NEEDS-REVIEW — message clair, décision humaine.

Décision

Retenu : mode warn + NEEDS-REVIEW (option 4).

Si safeResolve détecte qu'un chemin résout hors du projectRoot, le check renvoie un résultat status: warn avec reason: "chemin hors projet : <chemin>". Le verdict global de la porte bascule au minimum en NEEDS-REVIEW. L'humain est informé, peut évaluer si la configuration du projet est légitime, et confirmer la transition en connaissance de cause.

Conséquences

Positives :

  • Diagnostic clair : le message indique exactement quel chemin a été rejeté.
  • Pas de faux négatif : plus de fichiers « manquants » à cause d'un résolution hors scope.
  • Décision humaine préservée : l'utilisateur peut forcer en conscience si la config est voulue.
  • Pas de rupture : les projets avec config standard continuent à voir leurs checks passer normalement.

Négatives :

  • Un projet malveillant avec un pattern {id} construit pour lire /etc/passwd passerait en NEEDS-REVIEW au lieu d'être bloqué. Mitigé : les identifiants sont validés ailleurs (regex), et le pattern est défini dans schema.js (source versionnée, reviewée).

Neutres :

  • L'implémentation est simple : un try/catch autour de path.resolve + un test path.startsWith(projectRoot).

Règle dérivée

Tout nouveau check qui manipule des chemins doit utiliser safeResolve — pas path.resolve directement. Le contrat : on protège la frontière du projet, on informe quand elle est franchie.

Références