ADR 0033 – Rattachement tâche-projet unique : lien dérivé d'un champ référence
Accepté (2026-10-16, mode autonome ; demande d'Aurélien, vague 1 du 2026-10-15)
ADR 0033 – Rattachement tâche-projet unique : lien dérivé d’un champ référence
Statut : Accepté (2026-10-16, mode autonome ; demande d’Aurélien, vague 1 du 2026-10-15)
Contexte
Une tâche pouvait être rattachée à un projet de deux façons :
- le champ référence
tache.projet(ADR 0005), saisi dans la fiche ; - le lien typé
concerne-projet, ajouté depuis le panneau de contexte.
Les deux pouvaient diverger, et un projet listait alors ses tâches à deux endroits (« Éléments du projet » et « Tâche · Projet »). Aurélien veut une seule façon de faire : on garde le champ, le lien en est dérivé, affiché mais jamais saisi deux fois.
Décision
Format : derivedFrom sur un type de lien
name: concerne-projet
derivedFrom:
- { aspect: tache, field: projet }
Pour un nœud source qui porte l’aspect tache, le lien concerne-projet est le champ tache.projet. Les autres nœuds (notes, factures, documents…) gardent le lien saisi. Au chargement, le chargeur vérifie :
- l’aspect est connu ;
- le champ est un champ référence de premier niveau (
format: node-ref) ; - chaque aspect n’est cité qu’une fois.
Le moteur ne connaît que la clé derivedFrom. Aucun nom de domaine n’apparaît dans le code.
Comportement
- Saisie :
POST /api/nodes/{id}/linksrépond 400typepour une source qui porte l’aspect (« se règle par le champ « Projet » de l’aspect Tâche »). Le panneau ne propose plus ce type pour un tel nœud. - Liens sortants : le lien dérivé vient en tête, en lecture seule. On y trouve
kind = "field", ni id ni date, etfield = "tache.projet". Une cible en corbeille est signalée, une cible purgée est omise. - Rétroliens : les sources du champ rejoignent le groupe du lien (« Éléments du projet »), après les liens saisis et une seule fois par source. Il n’y a plus de groupe « Tâche · Projet » séparé.
- Vues : une colonne
link:concerne-projetcomprend les cibles dérivées, sans doublon, triées par titre. - Export : les liens dérivés n’ont pas de ligne
node_link. L’export les donne déjà comme relation de champ (kind: field).
Reprise des liens saisis
Une même règle (LinkDerivation) sert à deux moments :
- au démarrage, pour les données existantes, par un réconciliateur exécuté avant l’ouverture du port HTTP, idempotent, avec une transaction par source ;
- quand on ajoute l’aspect à un nœud qui a déjà des liens saisis de ce type (
PUTd’aspect, changement de type), dans la même transaction que l’ajout.
La règle :
- Le champ est résolu s’il contient l’identifiant d’un nœud existant hors corbeille.
- Champ résolu : il gagne et les liens saisis sont retirés. Un lien dont la cible diffère du champ est signalé dans le journal (WARN, avec la source et la cible) ; l’historique le garde.
- Champ non résolu (vide, pas un UUID, cible purgée ou en corbeille) : il reçoit la plus ancienne cible vivante des liens, par une écriture d’aspect validée (événement
aspect.updated), puis les liens sont retirés. - Aucune cible vivante : rien ne change, les liens restent et la source est signalée. Il n’y a aucune perte.
Chaque retrait écrit link.removed avec reason: derived et field. Une source qui échoue au démarrage, quelle qu’en soit la cause, est laissée telle quelle et signalée : le démarrage continue. Une source en corbeille ne peut pas être écrite ; elle est signalée à chaque démarrage.
Cohérence des définitions
- Un champ ne porte qu’un seul type de lien : un second fichier qui cite le même
aspect.fieldest refusé. - Au rechargement, un lien gardé dans sa version précédente est revalidé contre les définitions fusionnées. Si son champ a disparu ou n’est plus une référence, il est écarté et signalé.
La reprise passe par du Java plutôt que par une migration Flyway, car elle dépend des définitions de config/ : une migration SQL ne peut pas connaître tache.projet.
Alternatives écartées
- Garder le lien et dériver le champ : le champ est déjà utilisé par les vues, l’expression
whereet la fiche. De plus, un lienmanyne se ramène pas à un champ unique. - Restreindre
sourcedu lien pour exclure les tâches : les contraintes ne savent pas exprimer « sauf », et le rattachement d’une tâche n’apparaîtrait plus dans les liens.
Conséquences
- Le champ référence étant unique, une tâche ne peut avoir qu’un projet. Une tâche qui avait plusieurs liens vers des projets garde la plus ancienne cible vivante dans le champ (ou son champ, s’il était déjà renseigné) ; les autres restent dans l’historique.
- Lecture : les liens sortants montrent une ligne par champ dérivé porté. Les colonnes de vue relisent les identifiants comme la fiche (UUID en Java, cibles par clé primaire) et ne comptent une cible qu’une fois.
- La même clé servira à d’autres paires champ/lien si
config/en déclare.