ADR-007 — Finir ce qu'ADR-005 a décidé : le Mission Ledger comme seule source de tâches¶
- Statut : accepté (2026-09-17)
- Contexte : Plan 2026-Q4, phase 4, lot 4.1 (issue #559), préalable bloquant des lots 4.2 à 4.6 ; constat d'origine issue #521
Contexte¶
ADR-005 (2026-08-27) a déjà tranché : le MissionLedger est la source de
vérité, _grimoire/standard/task-board.yaml en est une projection exportée.
La conversion d'états (9 → 8), la projection (missions/board.py::build_board/
write_board) et la réprojection à chaque écriture (missions/service.py::TaskService)
sont écrites, testées, et n'ont pas besoin d'être refaites.
Ce que le code montre, mesuré et non supposé (issue #521, audit cockpit sur la
Forge, kit 3.51.1) : la décision d'ADR-005 n'est pas appliquée au point
d'entrée. grimoire standard init scaffold task-board.yaml en copiant tel
quel framework/agentic-standard/templates/task-board.yaml — un fichier
statique portant une tâche bootstrap à l'état proposed — sans jamais
ouvrir de MissionLedger. Résultat vérifié dans le code : tout projet gouverné
par le standard (profil governed, dont la Forge) a un board sans ledger.
src/grimoire/tools/workspace_api.py::tasks_view (lu par l'espace Exécuter et
le panneau Preuves du cockpit) ne lit que le ledger via TaskService :
sans lui, elle rend {"ledger": false, "note": "aucun Mission Ledger — \grimoire
task add` en ouvre un"}— le message exact que #521 signale. Elle ne
retombe jamais surtask-board.yaml, ce qui est cohérent avec ADR-005 (le YAML
est une sortie, pas une entrée) mais suppose que le ledger existe — ce questandard init` ne garantit pas.
En parallèle, src/grimoire/missions/task_flow_adapter.py::import_task_flow_events
importe déjà, de façon idempotente (empreinte SHA-256 par événement), des
événements runtime (_grimoire-runtime-output/task-flow/events.jsonl, hooks
et tâches VS Code) vers le ledger. C'est un troisième flux, orthogonal aux deux
systèmes de tâches proprement dits (il ne lit ni n'écrit task-board.yaml) ;
il ne change pas de comportement dans cette ADR.
Décision¶
On ne réouvre pas ADR-005. Le Mission Ledger reste l'unique source ; le board du standard reste sa projection. Le lot 4.1 ferme l'écart d'implémentation qui empêche cette décision de produire son effet :
-
grimoire standard initécrit désormais la tâchebootstrapviaTaskService/MissionLedger(ledger.create_mission+ledger.create_task, puiswrite_board(build_board(...))) au lieu de copier le template YAML statique. Tout projet nouvellement enrôlé a un ledger et un board cohérents dès l'init. -
Migration idempotente et réversible pour les projets déjà scaffoldés (
src/grimoire/missions/task_unification.py, nouveau module) : pour chaque tâche du board absente du ledger (appariée partask_id), reconstruit unMissionTaskviatask_state_of()— la fonction inverse, lossy par construction, qu'ADR-005 avait déjà écrite pour cet usage — en conservanttask_id, titre,acceptance_criteria, propriétaire, et les chemins d'enveloppe déjà déterministes (_grimoire-output/{context,decisions,evidence}/<task_id>/...). UneMissionde rattachement est créée si nécessaire (même patron que_DEFAULT_MISSION_IDdetask_flow_adapter.py). Le board est ensuite régénéré depuis le ledger (build_board/write_board) pour que les deux concordent à l'octet près à partir de cet instant.
Snapshot avant écriture et restauration par horodatage suivent exactement
le patron déjà éprouvé de grimoire migrate
(cmd_migrate.py::_snapshot/apply_migration/restore_migration,
manifeste JSON) plutôt que d'en inventer un nouveau.
-
grimoire updéclenche la migration quand un projet est enrôlé (standard_state.is_standard_enrolled) avec un board mais sans ledger complet — en meilleur effort, comme le reste de son pipeline. -
grimoire doctorsignale la divergence (board sans ledger, ou ledger incomplet par rapport au board) par unFAILnommé et un remède, jamais une correction automatique. -
Aucun changement requis côté cockpit ou CLI facades.
workspace_api.tasks_view/task_view(Exécuter, Preuves),grimoire tasketgrimoire standard gate checklisent déjà la bonne source (le ledger pour les tâches,task-board.yamlpour le gate) ; ils étaient corrects, seule l'alimentation manquait. Un test le vérifie explicitement plutôt que de le supposer. -
Champ
finitionpréparé, non exploité.MissionTaskgagne un champ optionnelfinition: str = "", restreint à{"", "maquette", "peaufine"}, propagé dans la projection board. Aucune interface ne le lit encore — c'est le terrain du lot 4.3 (issue #561).
Invariants opposables¶
- I1 — Une tâche du standard a toujours une enveloppe et des gates. Les
chemins
_grimoire-output/{context,decisions,evidence}/<task_id>/...et les portes de_TRANSITIONSne changent pas ; la migration ne déplace ni ne supprime aucun pack de preuve existant. - I2 — Une tâche Mission Ledger a toujours un id, un mission_id (board) et un
historique. La migration crée de vraies tâches via
ledger.create_task(qui journalise sa propre création) et uneMissionde rattachement — jamais une tâche orpheline. - I3 — Aucune perte de donnée à la migration. Chaque tâche du board devient
une tâche du ledger ; un instantané précède toute écriture (
_snapshot) ; rien sous_grimoire-output/evidence/**n'est lu en écriture. - I4 — Les hooks et
gate checkgardent leur comportement. Le format et le schéma detask-board.yamlne changent pas (mêmes clés, même cycle de vie) ; les suitestest_standard_state*.py,test_agentic_standard*.py,test_evidence_gate*.pyexistantes restent vertes sans modification. - I5 — Idempotence et réversibilité. Rejouer la migration sur un projet
déjà migré ne change rien (tâches déjà présentes dans le ledger, appariées
par id, ignorées) ;
restorepar horodatage remet le board d'avant en place et retire les tâches importées du ledger.
Alternatives écartées¶
Faire de task-board.yaml la source (à nouveau). ADR-005 l'a déjà écarté
et rien n'a changé qui invaliderait ce constat : le YAML n'a ni claims, ni
dépendances, ni incidents, ni journal — il perdrait l'historique à chaque
écriture.
Synchronisation continue bidirectionnelle. Toujours sans autorité de résolution de conflit définissable ; ADR-005 l'a déjà écarté pour cette raison, qui tient encore.
Un troisième modèle qui engloberait les deux. MissionTask est déjà un
sur-ensemble strict des champs du board (table d'ADR-005). En construire un
troisième rouvrirait des surfaces d'écriture stables aujourd'hui (hooks, gate
check, vérificateurs du standard) pour un gain nul : le problème n'est pas le
modèle, c'est qu'un point d'entrée ne l'alimentait pas.
Conséquences¶
- Tout projet gouverné existant doit passer par la migration une fois — faite
automatiquement par
grimoire up, ou à la main par la commande exposée par ce lot, avant que l'espace Exécuter et le panneau Preuves du cockpit affichent ses tâches. doctorpeut désormais échouer sur un projet enrôlé jamais migré — attendu, nommé, avec un remède, pas un changement silencieux.- Le lot 4.3 (finition propagée au kanban) trouve le champ déjà présent sur le schéma et la projection : il n'a plus qu'à le lire et l'écrire.
Références¶
- Issues #559 (ce lot), #521 (constat d'origine), #554 (épic phase 4)
- ADR-005 — Le Mission Ledger est la source, le task board une projection
src/grimoire/missions/{ledger.py,board.py,schemas.py,service.py,task_flow_adapter.py}src/grimoire/core/standard_state.py,src/grimoire/tools/workspace_api.pysrc/grimoire/cli/cmd_migrate.py— patron snapshot/apply/restore réutilisé