Second Brain documentation

ADR 0005 – Champ référence : `format: node-ref` stocké dans les données d'aspect

Accepté (2026-10-14)

← Toutes les décisions

ADR 0005 – Champ référence : format: node-ref stocké dans les données d’aspect

Statut : Accepté (2026-10-14)

Contexte

Le cahier (jalon 1, « Champ référence ») demande un champ d’aspect qui pointe vers un nœud filtré par type ou par aspect. Les relations typées (node_link, ADR 0001) existent déjà, avec des contraintes source/target. Il faut fixer trois choses difficiles à changer une fois que l’utilisateur a écrit ses fichiers et saisi des données : l’écriture YAML du champ, l’emplacement de la valeur, et le comportement quand la cible change ensuite (corbeille, aspect retiré).

Décision

  • Format : un champ référence est une propriété de premier niveau du JSON Schema de l’aspect, de type chaîne, portant format: node-ref et une contrainte facultative x-target :

    projet:
      type: string
      format: node-ref
      title: Projet
      x-target: { aspects: [projet] }   # facultatif ; types: [...] possible aussi

    x-target a la même sémantique que source/target d’un lien : le nœud cible porte tous les aspects listés et, si types est présent, a l’un de ces types. Un seul record Java (NodeFilter) et un seul contrôle (LinkConstraints) servent aux deux.

  • Stockage : la valeur est l’UUID du nœud cible, en chaîne, dans le JSONB node_aspect.data ({"projet": "6f1c…"}). Aucune ligne node_link n’est créée. La modification passe par aspect.added / aspect.updated (avant/après), comme tout champ.

  • Contrôles au chargement : x-target n’est accepté qu’avec format: node-ref ; node-ref n’est accepté qu’au premier niveau et sur type: string (jalon 1 : pas de tableau) ; les noms de x-target doivent correspondre à des fichiers déclarés dans config/aspects/ et config/types/ (contrôle des fautes de frappe, sans invalidation en cascade).

  • Validation à l’écriture : la syntaxe UUID est vérifiée par le JSON Schema (format node-ref enregistré dans JsonSchemas). Ensuite, pour les seules références nouvelles ou modifiées : le nœud existe, n’est pas en corbeille, n’est pas le nœud lui-même et satisfait x-target.

  • Tolérance à la lecture : une référence inchangée n’est pas recontrôlée. Une cible passée ensuite en corbeille, retirée de son aspect ou purgée n’empêche ni la lecture ni la modification des autres champs. Le titre de la cible est résolu côté serveur dans NodeView.references, avec un indicateur deleted.

Alternatives écartées

  • Créer et maintenir un node_link miroir pour chaque référence : deux sources de vérité à synchroniser (un retrait dans le panneau Liens désynchroniserait le champ), un type de lien implicite à inventer, des événements doublés. Les rétroliens peuvent être obtenus sans copie par une requête data @> '{"champ": "uuid"}', couverte par l’index GIN jsonb_path_ops existant.
  • Remplacer le champ par un lien typé (le formulaire écrirait un node_link) : la valeur sortirait des données d’aspect. Elle ne serait plus visible dans les expressions (tache.projet), ni dans les colonnes de vues, ni dans l’export frontmatter.
  • Mot-clé target sans préfixe, ou $ref-like propriétaire : x- signale une extension non standard, que les outils JSON Schema ignorent proprement.
  • Recontrôler toutes les références à chaque écriture : une cible mise en corbeille bloquerait toute modification du nœud source.
  • Vérifier les noms de x-target contre les définitions valides : invaliderait en cascade un aspect quand un autre fichier est cassé. La contrainte porte sur des noms stockés (node.type, node_aspect.aspect) et reste évaluable même si la définition visée est absente.

Conséquences

  • Les références mono-valuées sont des attributs de l’aspect. Les relations multiples, ou celles qui ont leur propre cycle de vie, restent des liens typés. Le skill definitions-format documente cette distinction.
  • Les rétroliens issus des champs référence, les colonnes de vues (jalon 2) et l’export pourront lire la valeur dans le JSONB sans migration.
  • Un tableau de références (type: array, items: { format: node-ref }, x-target à côté du format dans items) pourra être ajouté plus tard sans changer l’écriture actuelle.
  • Rien n’empêche une référence de devenir pendante. L’interface doit afficher les cas « en corbeille » et « introuvable ».
  • Changer un champ existant en node-ref rend non conformes les anciennes valeurs non UUID. Elles restent lisibles, mais toute réécriture de l’aspect sera refusée tant qu’elles ne sont pas corrigées (même règle que pour tout changement de schéma, migrations en V2).

Amendement (ADR 0033) : un champ cité par le derivedFrom d’un type de lien ne forme plus son propre groupe de rétroliens (« Tâche · Projet ») ; ses sources rejoignent le groupe du lien (« Éléments du projet »).