Saltar para o conteúdo

Eventos de domínio

Os eventos de domínio são o mecanismo de desacoplamento que permite às funcionalidades comunicar sem dependências diretas. Em vez de a funcionalidade A chamar a funcionalidade B, a funcionalidade A publica um evento e a funcionalidade B subscreve-o.

O DomainEventBus é um barramento de publicação/subscrição com difusão e em processo. Os publicadores chamam publish(event); os subscritores consomem um stream tipado on<T>(). Todos os eventos implementam DomainEvent e transportam occurredAt.

O barramento é executado dentro do cc_server. Todos os publicadores e todos os subscritores em atividade residem aí. Nenhum cliente vê este barramento. O que um cliente vê é um subconjunto curado que o servidor reemite como frames JSON-RPC notifications/*.

Sem eventos, qualquer reação entre funcionalidades é uma importação. O percurso das notificações teria de importar a funcionalidade dos agentes, a das pull requests, a dos tickets e a das reuniões só para saber quando mostrar um aviso — e cada uma delas teria então de saber que as notificações existem. A parte dos tickets importaria os pipelines para que um ticket concluído pudesse fazer avançar uma execução. O grafo de dependências fecha-se num nó, e o kernel partilhado deixa de ser partilhado.

Publicar um evento quebra isso. O percurso das notificações subscreve vinte e uma classes de eventos e não importa nenhuma das funcionalidades que os emitem. O despachante de disparadores de pipeline subscreve o barramento inteiro e não sabe nada sobre pull requests, tickets ou reuniões. A observabilidade agrega resultados de execuções sem se intrometer no despacho.

O custo é o habitual: um publicador não consegue saber se alguém está a escutar, e um ouvinte que nunca é construído fica silenciosamente inerte. O TicketRemoteSyncHandler é o exemplo permanente: está declarado, tem cobertura de testes e nunca é ligado em produção.

Vale a pena ser preciso quanto a uma coisa que o barramento não possui.

O registo de auditoria não é uma projeção do fluxo de eventos. Todas as operações RPC que alteram estado são auditadas por predefinição — RepoOp.audited tem o valor true por predefinição e uma operação só prescinde disso declarando audited: false, um ato deliberado visível na própria operação. O despachante acrescenta a linha «quem fez o quê, a partir de onde» imediatamente a seguir a uma chamada bem-sucedida. Não existe nenhum DomainEventAuditBridge. O ActivityLogPersister escreve linhas em activity_log a partir de eventos ActivityLogged; esse é um feed separado da auditoria de RPC.

O barramento de eventos é o transporte das notificações e da automatização. Não é o sistema de registo oficial.

  • Notificações. Vinte e uma classes de eventos são convertidas em frames notifications/* pelo servidor, registadas num feed durável por espaço de trabalho e difundidas para as sessões ligadas. Um frame cujo workspace_id identifica um espaço de trabalho do qual o utilizador da sessão não é membro é descartado no servidor; o cliente filtra depois o resto pelo espaço de trabalho que está a ver.
  • Automatização orientada a eventos. Os disparadores de pipeline subscrevem o barramento e iniciam automaticamente os pipelines correspondentes (ver abaixo).
  • Reações de ciclo de vida. A criação de um espaço de trabalho semeia os seus agentes CEO e especialistas, os modelos de pipeline incorporados e as suites de avaliação iniciais. Uma execução de agente concluída retoma um passo de pipeline suspenso, fecha uma tarefa, alimenta o supervisor de objetivos, pode acordar um verificador com nome e pode voltar a acionar um líder de equipa. Uma PR com merge ou um espaço eliminado desencadeia a recolha de lixo dos worktrees.
  • Sincronização entre fornecedores. As alterações locais de tickets são enviadas para os fornecedores configurados por um coordenador que escuta cinco eventos de tickets.

Os eventos abrangem toda a superfície do produto. A um nível geral:

  • Espaços de trabalho, agentes e repos — criação de espaços de trabalho, conclusão de execuções, registo de repos, atualizações de skills
  • Pull requests — publicação, alterações de estado, merges, pedidos de revisão, menções, vigilância de PRs próprias, PRs detetadas externamente
  • Mensagens — mensagens, espaços e progresso do aprovisionamento de espaços
  • Tickets — criar, atribuir, reatribuir, detalhes, estado e os três desfechos terminais
  • Tarefas — frames sequenciados do ciclo de vida de uma execução despachada, para que os clientes os possam ordenar e deduplicar
  • Pipelines — apenas eventos terminais de execução (concluída, cancelada, falhada)
  • Observabilidade — entradas do registo de atividade e limiares de orçamento
  • Identidade e associação — membro adicionado, removido, papel alterado
  • Calendário e reuniões — expiração da autenticação, início de reuniões e conclusão de gravações
  • Rigs — tomada de controlo, recuperação e fecho inesperado

A memória, a orquestração, os documentos de plano, os artefactos, as aprovações, a entrada de sincronização de tickets, a atualização do calendário, os merges de worktrees, a criação de utilizadores, os convites e a revogação de dispositivos não são eventos de domínio; ver a referência.

Para o catálogo completo — as 53 classes de eventos concretas, os respetivos payloads e os 24 subscritores que as consomem — ver a referência de eventos de domínio. Essa página é o inventário; esta é o modelo.

O controlo de acesso em tempo real assenta em dois mecanismos diferentes

Secção intitulada “O controlo de acesso em tempo real assenta em dois mecanismos diferentes”

Os eventos de associação e de dispositivos parecem simétricos e não o são, o que importa se estiver a raciocinar sobre a rapidez com que uma revogação produz efeito.

Revogar um dispositivo termina mesmo com as suas sessões. O servidor vigia a tabela de dispositivos emparelhados e elimina qualquer sessão ativa cujo dispositivo tenha saído do conjunto ativo, em segundos.

Remover um membro não fecha o socket. O WorkspaceMemberRemoved é consumido: o LocalRpcServer e o RemoteRelayHost eliminam as subscrições de espaços de trabalho associadas a esse utilizador, e o RemoteEventForwarder invalida a sua cache de associação para que não sejam enviados mais frames desse espaço de trabalho. O que recusa o RPC seguinte continua a ser a verificação de papéis, que volta a resolver a associação em todas as chamadas — por isso o pedido seguinte é recusado com «Not a member of this workspace». O efeito é imediato para tudo o que essa pessoa tente fazer; simplesmente não é alcançado desligando-lhes a ligação.

Os disparadores de pipeline subscrevem eventos de domínio e iniciam automaticamente os pipelines correspondentes. O PipelineTriggerDispatcher escuta o barramento inteiro e, em seguida:

  1. Sai imediatamente, a menos que o tipo de evento seja um dos que sabe mapear para um payload
  2. Procura, em todos os espaços de trabalho, os disparadores ativados que correspondem a esse tipo de evento
  3. Filtra cada um para o seu próprio espaço de trabalho e aplica o respetivo filtro de correspondência de payload
  4. Inicia uma execução por cada disparador que sobrevive

A restrição importante é o passo 1. O barramento transporta 53 tipos de eventos; o seletor de adicionar disparador inicia uma execução para estes catorze (ramos de payload do EventPayloadMapper): ExternalPrDetected, PullRequestPublished, PullRequestStatusChanged, PrMerged, MessageReceived, TicketAssigned, TicketCompleted, TicketFailed, TicketCancelled, BudgetThresholdCrossed, RepoAdded, MeetingRecordingStopped, SkillUpdated e SpaceDeleted. Nada mais pode iniciar um pipeline.

O RemoteEventForwarder traduz os eventos da classe das notificações em frames e envia-os por RPC para as sessões ligadas, onde um mapeador de frames os transforma em notificações dentro da aplicação. Um cliente leve vê, por conseguinte, uma projeção do fluxo de eventos do servidor sem deter qualquer execução — que é precisamente o objetivo da separação de cliente leve.

Nem todos os frames enviados são uma notificação, no entanto. O fluxo do ciclo de vida das tarefas e a reatribuição de tickets são reencaminhados em tempo real para alimentar a interface, mas não são registados no feed de notificações nem mostram qualquer aviso.