Aller au contenu

Événements de domaine

Les événements de domaine sont le mécanisme de découplage qui permet aux fonctionnalités de communiquer sans dépendances directes. Plutôt que la fonctionnalité A n’appelle la fonctionnalité B, la fonctionnalité A publie un événement et la fonctionnalité B s’y abonne.

Le DomainEventBus est un bus de publication/abonnement par diffusion, à l’intérieur du processus. Les éditeurs appellent publish(event); les abonnés consomment un flux typé on<T>(). Chaque événement implémente DomainEvent et porte occurredAt.

Le bus s’exécute à l’intérieur de cc_server. Chaque éditeur et chaque abonné actif y réside. Aucun client ne voit ce bus. Ce qu’un client voit, c’est un sous-ensemble choisi que le serveur réémet sous forme de trames JSON-RPC notifications/*.

Sans événements, chaque réaction transversale est un import. Le chemin des notifications devrait importer la fonctionnalité des agents, celle des demandes de tirage, la billetterie et les réunions juste pour savoir quand afficher un toast — et chacune de ces fonctionnalités devrait ensuite savoir que les notifications existent. La billetterie importerait les pipelines pour qu’un ticket terminé puisse faire avancer une exécution. Le graphe de dépendances se referme en nœud, et le noyau partagé cesse d’être partagé.

Publier un événement brise cette spirale. Le chemin des notifications s’abonne à vingt et une classes d’événements et n’importe aucune des fonctionnalités qui les lèvent. Le répartiteur de déclencheurs de pipeline s’abonne à tout le bus et ne connaît rien des demandes de tirage, des tickets ni des réunions. L’observabilité agrège les issues d’exécution sans s’immiscer dans le lancement.

Le coût est celui auquel on s’attend : un éditeur ne peut pas savoir si quelqu’un écoute, et un écouteur jamais construit reste silencieusement inerte. TicketRemoteSyncHandler en est l’exemple permanent : il est déclaré, couvert par des tests et jamais câblé en production.

Il vaut la peine d’être précis sur une chose que le bus ne possède pas.

La piste d’audit n’est pas une projection du flux d’événements. Chaque opération RPC qui modifie l’état est auditée par défaut — RepoOp.audited vaut true par défaut et une opération ne s’y soustrait qu’en déclarant audited: false, un acte délibéré visible sur l’opération elle-même. Le répartiteur ajoute la ligne « qui a fait quoi, d’où » directement après un appel réussi. Il n’existe pas de DomainEventAuditBridge. ActivityLogPersister écrit les lignes activity_log à partir des événements ActivityLogged; c’est un flux distinct de l’audit RPC.

Le bus d’événements est le transport des notifications et de l’automatisation. Il n’est pas le registre de référence.

  • Notifications. Vingt et une classes d’événements sont transformées en trames notifications/* par le serveur, consignées dans un fil durable par espace de travail et diffusées aux sessions connectées. Une trame dont le workspace_id désigne un espace de travail dont l’utilisateur de la session n’est pas membre est écartée sur le serveur; le client filtre ensuite le reste selon l’espace de travail qu’il consulte.
  • Automatisation pilotée par les événements. Les déclencheurs de pipeline s’abonnent au bus et démarrent automatiquement les pipelines correspondants (voir ci-dessous).
  • Réactions de cycle de vie. La création d’un espace de travail initialise son agent CEO et ses agents spécialisés, les modèles de pipeline intégrés et les suites d’évaluation de départ. Une exécution d’agent terminée reprend une étape de pipeline suspendue, ferme une tâche, alimente le superviseur des objectifs, peut réveiller un vérificateur nommé et peut relancer un chef d’équipe. Une PR fusionnée ou un espace supprimé déclenche le nettoyage des arbres de travail.
  • Synchronisation entre fournisseurs. Les changements locaux apportés aux tickets sont poussés vers les fournisseurs configurés par un coordinateur à l’écoute de cinq événements de ticket.

Les événements couvrent toute la surface du produit. En gros :

  • Espaces de travail, agents et dépôts — création d’espace de travail, fin d’exécution, enregistrement de dépôt, mises à jour de skills
  • Demandes de tirage — publication, changements de statut, fusions, demandes de révision, mentions, surveillance des PR dont vous êtes l’auteur, PR détectées de l’extérieur
  • Messagerie — messages, espaces et progression du provisionnement des espaces
  • Tickets — création, assignation, réassignation, détails, statut et les trois issues terminales
  • Tâches — trames de cycle de vie séquencées pour une exécution lancée, afin que les clients puissent les ordonner et les dédoublonner
  • Pipelines — événements d’exécution terminaux seulement (terminée, annulée, échouée)
  • Observabilité — entrées du journal d’activité et seuils budgétaires
  • Identité et appartenance — membre ajouté, retiré, rôle modifié
  • Calendrier et réunions — expiration de l’authentification, début des réunions et fin des enregistrements
  • Rigs — prise de contrôle, récupération et fermeture inattendue

La mémoire, l’orchestration, les documents de plan, les artéfacts, les approbations, la réception de synchronisation des tickets, l’actualisation du calendrier, les fusions d’arbres de travail, la création d’utilisateur, les invitations et la révocation d’appareil ne sont pas des événements de domaine; consultez la référence.

Pour le catalogue complet — les 53 classes d’événements concrètes, leurs charges utiles et les 24 abonnés qui les consomment — consultez la référence des événements de domaine. Cette page est l’inventaire; celle-ci est le modèle.

Le contrôle d’accès en direct repose sur deux mécanismes distincts

Section intitulée “Le contrôle d’accès en direct repose sur deux mécanismes distincts”

Les événements d’appartenance et d’appareil semblent symétriques et ne le sont pas, ce qui compte si vous réfléchissez à la vitesse à laquelle une révocation prend effet.

Révoquer un appareil met bel et bien fin à ses sessions. Le serveur surveille la table des appareils associés et abandonne toute session active dont l’appareil a quitté l’ensemble actif, en quelques secondes.

Retirer un membre ne ferme pas la connexion. WorkspaceMemberRemoved est consommé : LocalRpcServer et RemoteRelayHost abandonnent les abonnements d’espace de travail rattachés à cet utilisateur, et RemoteEventForwarder invalide son cache d’appartenance afin que d’autres trames visant cet espace de travail ne soient plus poussées. Ce qui refuse le prochain appel RPC, c’est toujours le contrôle de rôle, qui résout à nouveau l’appartenance à chaque appel — sa requête suivante est donc refusée avec « Not a member of this workspace ». L’effet est immédiat pour tout ce qu’il tente de faire; il n’est simplement pas obtenu en mettant fin à sa connexion.

Les déclencheurs de pipeline s’abonnent aux événements de domaine et démarrent automatiquement les pipelines correspondants. PipelineTriggerDispatcher écoute tout le bus, puis :

  1. Court-circuite à moins que le type d’événement fasse partie de ceux qu’il sait convertir en charge utile
  2. Recherche les déclencheurs activés correspondant à ce type d’événement, dans tous les espaces de travail
  3. Filtre chacun selon son propre espace de travail et applique son filtre de correspondance de charge utile
  4. Démarre une exécution pour chaque déclencheur qui survit

La contrainte importante est l’étape 1. Le bus transporte 53 types d’événements; le sélecteur d’ajout de déclencheur démarre une exécution pour ces quatorze (branches de charge utile de EventPayloadMapper) : ExternalPrDetected, PullRequestPublished, PullRequestStatusChanged, PrMerged, MessageReceived, TicketAssigned, TicketCompleted, TicketFailed, TicketCancelled, BudgetThresholdCrossed, RepoAdded, MeetingRecordingStopped, SkillUpdated et SpaceDeleted. Rien d’autre ne peut démarrer un pipeline.

RemoteEventForwarder traduit les événements de classe notification en trames et les pousse par RPC vers les sessions connectées, où un mappeur de trames les transforme en notifications dans l’application. Un client léger voit donc une projection du flux d’événements du serveur sans posséder d’exécution — ce qui est tout l’intérêt de la séparation client léger.

Toutes les trames poussées ne sont pas des notifications, par contre. Le flux de cycle de vie des tâches et la réassignation des tickets sont transmis en direct pour piloter l’interface, mais ils ne sont pas consignés dans le fil de notifications et n’affichent aucun toast.