Kalli
Server

Roles & Tags

Rôles & tags

Le routeur apps/server/src/routers/roles-tags.ts gère les rôles applicatifs et les tags. Ils déterminent quels chunks et paires Q&A entrent dans l'index de chaque rôle. Ce système est distinct du rôle Better Auth (user.role) ; le lien entre les deux est décrit plus bas.

Modèle

Tag        → étiquette posée sur des chunks ou des paires Q&A
Rôle       → groupe d'utilisateurs, avec un rang (display_order) et un statut super admin
Role-Tag   → quels tags un rôle reçoit directement
Item-Tag   → quels tags portent un chunk ou une paire Q&A

Quels éléments entrent dans l'index d'un rôle

La régénération construit un index FAISS par rôle (voir rag-agent). Pour chaque rôle :

  • seuls les éléments de catégorie dynamic ou static sont retenus (chunks et paires Q&A) ;
  • un élément sans tag est inclus pour tous les rôles ;
  • un élément avec tags est inclus si le rôle reçoit au moins un de ces tags, directement ou par héritage ;
  • un super admin reçoit tous les éléments dynamic et static, tagués ou non.

Les catégories not an info et finetuned n'entrent jamais dans un index.

Héritage (hiérarchie des rôles)

Les rôles sont ordonnés par display_order : plus la valeur est petite, plus le rang est élevé. Le rôle admin (super admin) est à 0.

Un rôle hérite des tags de tous les rôles de rang inférieur, c'est-à-dire ceux dont display_order est supérieur ou égal au sien.

Un rôle marqué is_hierarchy_excluded :

  • ne transmet pas ses tags aux rôles de rang inférieur ;
  • ne reçoit pas de tags hérités : il n'a que ses tags propres.

getEffectiveRoleTags renvoie les tags propres et hérités d'un rôle, avec le champ isInherited. Le même calcul est fait côté rag-agent (get_role_tag_ids) lors de la construction des index.

Initialisation

ensureDefaultRoles() crée au démarrage :

  • admin : isSuperAdmin: true, displayOrder: 0 ;
  • user : isSuperAdmin: false, displayOrder: 1.

Ces rôles ne sont créés que s'ils n'existent pas.

Création automatique depuis les documents

Les tags et les rôles peuvent être posés directement dans un document Markdown. Le document-processor lit :

  • le frontmatter YAML : tags: et roles: s'appliquent à tout le fichier ;
  • des plages inline <!-- tags: a, b --> … <!-- /tags --> et <!-- roles: x --> … <!-- /roles -->.

Pendant l'ingestion, le serveur crée les tags absents (couleur aléatoire) et les associe aux chunks. Les noms de rôles sont aussi enregistrés comme tags : un chunk marqué roles: x reçoit le tag x. Un rôle x ne voit ce chunk que si le rôle reçoit le tag x.

Les tags peuvent aussi être posés ensuite à la main avec setItemTags ou setFileTags.


Procédures tags

getAllTags (admin)

Entrée : aucune. Sortie : tous les tags, triés par nom.

type Tag = {
  id: string;
  name: string;            // unique
  description: string | null;
  color: string;           // "#rrggbb", défaut "#6366f1"
  createdAt: Date;
  createdBy: string | null;
  lastUpdatedAt: Date;
  lastUpdatedBy: string | null;
};

createTag (admin)

Entrée : { name: string (1 à 50 car.), color?: "#rrggbb" }. Sortie : le tag créé. Un nom déjà pris renvoie BAD_REQUEST.

updateTag (admin)

Entrée : { id: uuid, name?: string, color?: "#rrggbb" }. Sortie : le tag mis à jour.

deleteTag (admin)

Entrée : { id: uuid }. Sortie : { success: true }. Supprime en cascade les liens role_tags et item_tags.


Procédures rôles

getAllRoles (admin)

Entrée : aucune. Sortie : tous les rôles, super admins d'abord, puis par displayOrder.

type Role = {
  id: string;
  name: string;
  description: string | null;
  displayOrder: number;
  isSuperAdmin: boolean;
  isHierarchyExcluded: boolean;
  createdAt: Date;
};

createRole (admin)

Entrée : { name: string (1 à 50 car.), isSuperAdmin?: boolean, isHierarchyExcluded?: boolean }. Sortie : le rôle créé.

  • Si isSuperAdmin est vrai, les autres rôles sont décalés de +1 et le nouveau prend displayOrder: 0.
  • Sinon, il est placé en fin de liste.

updateRole (admin)

Entrée : { id, name?, isSuperAdmin?, isHierarchyExcluded? }. Sortie : le rôle mis à jour. Passer isSuperAdmin à vrai décale les autres rôles (transaction).

deleteRole (admin)

Entrée : { id: uuid }. Sortie : { success: true }. Supprime en cascade les role_tags. Les index déjà construits pour ce rôle gardent role_id à NULL.

reorderRoles (admin)

Entrée : [{ id: uuid, displayOrder: number }]. Sortie : { success: true }. Chaque mise à jour est une requête séparée, sans transaction.


Procédures role-tag

getRoleTags (admin)

Entrée : { roleId: uuid }. Sortie : les tags directement reçus par le rôle ({ id, roleId, tagId, tagName, tagColor }).

getAllRoleTags (admin)

Entrée : aucune. Sortie : toutes les associations rôle-tag.

getEffectiveRoleTags (admin)

Entrée : { roleId: uuid }. Sortie : tags propres et hérités du rôle.

type EffectiveRoleTag = {
  tagId: string;
  tagName: string;
  tagColor: string;
  roleId: string;          // rôle qui transmet le tag
  roleName: string;
  isInherited: boolean;    // true si le tag vient d'un autre rôle
};

setRoleTags (admin)

Entrée : { roleId: uuid, tagIds: uuid[] }. Sortie : { success: true }. Remplace tous les tags du rôle (suppression puis insertion, sans transaction).


Procédures item-tag

Un item est un chunk ou un qa_pair.

getItemTags (protégé)

Entrée : { itemId: uuid, itemType: "chunk" | "qa_pair" }. Sortie : les tags de l'item.

getItemTagsBatch (protégé)

Entrée : { itemIds: uuid[], itemType }. Sortie : les tags de tous les items demandés, en une requête.

setItemTags (admin)

Entrée : { itemId: uuid, itemType, tagIds: uuid[] }. Sortie : { success: true }. Remplace les tags de l'item.

getFileTags (protégé)

Entrée : { fileId: uuid, fileType: "regular" | "qa" }. Sortie : les tags distincts présents sur les chunks (ou paires) du fichier.

setFileTags (admin)

Entrée : { fileId: uuid, fileType, tagIds: uuid[] }. Sortie : { success: true, itemCount }. Applique la même liste de tags à tous les chunks ou paires du fichier, en remplaçant leurs tags actuels, dans une transaction.


Procédures utilisateur

getUserRole (protégé)

Entrée : { userId: string }. Sortie : le rôle applicatif dont le nom correspond à user.role, ou null.

setUserRole (admin)

Entrée : { userId: string, roleName: string }. Sortie : { success: true }. Écrit roleName dans user.role.

Ce champ sert à la fois au rôle applicatif (choix de l'index) et au contrôle d'accès du serveur, qui n'accorde les droits admin que si user.role vaut admin. Assigner un rôle applicatif à un utilisateur retire donc ses droits admin si ce rôle n'est pas admin.


Flux de travail type

  1. Créer les tags (à la main, ou via les annotations Markdown lors de l'ingestion).
  2. Créer les rôles, avec leur rang et le statut super admin ou exclu de la hiérarchie.
  3. Associer les tags à chaque rôle (setRoleTags).
  4. Étiqueter les chunks et les paires (setItemTags ou setFileTags).
  5. Affecter chaque utilisateur à un rôle (setUserRole).
  6. Lancer regenerateIndex : un index par rôle est reconstruit et stocké en base (voir Schéma).
  7. À chaque question, rag-agent résout le rôle de l'utilisateur, puis charge l'index de ce rôle.