Salta ai contenuti

Eventi di dominio

Gli eventi di dominio sono il meccanismo di disaccoppiamento che consente alle funzionalità di comunicare senza dipendenze dirette. Anziché far chiamare la funzionalità A alla funzionalità B, la funzionalità A pubblica un evento e la funzionalità B vi si sottoscrive.

DomainEventBus è un bus di pubblicazione/sottoscrizione con broadcast in-process. Chi pubblica chiama publish(event); chi si sottoscrive consuma un flusso tipizzato on<T>(). Ogni evento implementa DomainEvent e porta con sé occurredAt.

Il bus viene eseguito all’interno di cc_server. Ogni publisher e ogni sottoscrittore attivo risiede lì. Nessun client vede questo bus. Ciò che vede un client è un sottoinsieme selezionato che il server re-emette come frame JSON-RPC notifications/*.

Senza eventi, ogni reazione tra funzionalità è un’importazione. Il percorso delle notifiche dovrebbe importare la funzionalità degli agenti, quella delle pull request, quella dei ticket e quella delle riunioni solo per sapere quando mostrare un avviso, e ognuna di esse dovrebbe poi sapere che le notifiche esistono. La funzionalità dei ticket importerebbe le pipeline perché un ticket completato possa far avanzare un’esecuzione. Il grafo delle dipendenze si chiude in un nodo e il kernel condiviso smette di essere condiviso.

Pubblicare un evento spezza questo schema. Il percorso delle notifiche si sottoscrive a ventuno classi di evento e non importa nessuna delle funzionalità che le emettono. Il dispatcher dei trigger delle pipeline si sottoscrive all’intero bus e non sa nulla di pull request, ticket o riunioni. L’osservabilità aggrega gli esiti delle esecuzioni senza intromettersi nell’invio.

Il costo è quello di sempre: chi pubblica non può sapere se qualcuno è in ascolto, e un ascoltatore che non viene mai istanziato resta inerte nel silenzio. TicketRemoteSyncHandler ne è l’esempio perenne: è dichiarato, coperto dai test e mai collegato in produzione.

Vale la pena essere precisi su una cosa che il bus non possiede.

La traccia di controllo non è una proiezione del flusso di eventi. Ogni operazione RPC che modifica lo stato viene registrata nel controllo per impostazione predefinita: RepoOp.audited è true per impostazione predefinita e un’operazione può esimersi solo dichiarando audited: false, un atto deliberato visibile sull’operazione stessa. Il dispatcher aggiunge la riga «chi ha fatto cosa, da dove» subito dopo una chiamata riuscita. Non esiste alcun DomainEventAuditBridge. ActivityLogPersister scrive righe in activity_log a partire da eventi ActivityLogged; si tratta di un feed separato dal controllo RPC.

Il bus di eventi è il trasporto delle notifiche e dell’automazione. Non è il sistema contabile di riferimento.

  • Notifiche. Il server trasforma ventuno classi di evento in frame notifications/*, li registra in un feed durevole per spazio di lavoro e li trasmette alle sessioni connesse. Un frame il cui workspace_id indica uno spazio di lavoro di cui l’utente della sessione non è membro viene scartato sul server; il client filtra poi il resto rispetto allo spazio di lavoro che sta visualizzando.
  • Automazione guidata dagli eventi. I trigger delle pipeline si sottoscrivono al bus e avviano automaticamente le pipeline corrispondenti (vedi sotto).
  • Reazioni del ciclo di vita. La creazione di uno spazio di lavoro inizializza i suoi agenti CEO e specialisti, i modelli di pipeline integrati e le suite di valutazione iniziali. Un’esecuzione di agente completata riprende un passo di pipeline sospeso, chiude un’attività, alimenta il supervisore degli obiettivi, può risvegliare un revisore designato e può riattivare il responsabile di un team. Una PR unita o uno spazio eliminato attiva la garbage collection dei worktree.
  • Sincronizzazione tra fornitori. Un coordinatore in ascolto di cinque eventi dei ticket invia le modifiche locali dei ticket ai fornitori configurati.

Gli eventi coprono l’intera superficie del prodotto. A grandi linee:

  • Spazi di lavoro, agenti e repo — creazione di spazi di lavoro, completamento delle esecuzioni, registrazione dei repo, aggiornamenti delle skill
  • Pull request — pubblicazione, cambi di stato, unioni, richieste di revisione, menzioni, monitoraggio delle PR proprie, PR rilevate dall’esterno
  • Messaggistica — messaggi, spazi e avanzamento del provisioning degli spazi
  • Ticket — creazione, assegnazione, riassegnazione, dettagli, stato e i tre esiti terminali
  • Attività — frame sequenziati del ciclo di vita per una singola esecuzione inviata, così i client possono ordinarli e deduplicarli
  • Pipeline — solo eventi terminali di esecuzione (completata, annullata, non riuscita)
  • Osservabilità — voci del registro delle attività e soglie di budget
  • Identità e appartenenza — membro aggiunto, rimosso, ruolo modificato
  • Calendario e riunioni — scadenza dell’autenticazione, avvio delle riunioni e fine delle registrazioni
  • Rig — presa in carico, recupero e chiusura inaspettata

La memoria, l’orchestrazione, i documenti di piano, gli artefatti, le approvazioni, l’acquisizione della sincronizzazione dei ticket, l’aggiornamento del calendario, le unioni di worktree, la creazione di utenti, gli inviti e la revoca dei dispositivi non sono eventi di dominio; vedi il riferimento.

Per il catalogo completo — tutte le 53 classi di evento concrete, i loro payload e i 24 sottoscrittori che li consumano — vedi il Riferimento degli eventi di dominio. Quella pagina è l’inventario; questa è il modello.

Il controllo degli accessi in tempo reale si basa su due meccanismi diversi

Sezione intitolata “Il controllo degli accessi in tempo reale si basa su due meccanismi diversi”

Gli eventi di appartenenza e quelli dei dispositivi sembrano simmetrici e non lo sono, il che conta se stai ragionando su quanto rapidamente ha effetto una revoca.

Revocare un dispositivo termina davvero le sue sessioni. Il server tiene d’occhio la tabella dei dispositivi associati e chiude qualsiasi sessione attiva il cui dispositivo è uscito dall’insieme attivo, nel giro di secondi.

Rimuovere un membro non chiude il socket. WorkspaceMemberRemoved viene consumato: LocalRpcServer e RemoteRelayHost eliminano le sottoscrizioni agli spazi di lavoro collegate a quell’utente e RemoteEventForwarder invalida la sua cache di appartenenza, così non vengono più inviati frame per quello spazio di lavoro. Ciò che nega il successivo RPC è comunque il gate dei ruoli, che risolve di nuovo l’appartenenza a ogni chiamata, quindi la richiesta successiva viene rifiutata con «Not a member of this workspace». L’effetto è immediato per qualunque cosa tentino di fare; semplicemente non si ottiene chiudendo loro la connessione.

I trigger delle pipeline si sottoscrivono agli eventi di dominio e avviano automaticamente le pipeline corrispondenti. PipelineTriggerDispatcher ascolta l’intero bus, poi:

  1. Cortocircuita a meno che il tipo di evento non sia uno che sa mappare a un payload
  2. Cerca in ogni spazio di lavoro i trigger abilitati che corrispondono a quel tipo di evento
  3. Filtra ciascuno rispetto al proprio spazio di lavoro e applica il suo filtro di corrispondenza del payload
  4. Avvia un’esecuzione per ogni trigger che sopravvive

Il vincolo importante è il passo 1. Il bus trasporta 53 tipi di evento; il selettore per aggiungere un trigger avvia un’esecuzione per questi quattordici (rami di payload di EventPayloadMapper): ExternalPrDetected, PullRequestPublished, PullRequestStatusChanged, PrMerged, MessageReceived, TicketAssigned, TicketCompleted, TicketFailed, TicketCancelled, BudgetThresholdCrossed, RepoAdded, MeetingRecordingStopped, SkillUpdated e SpaceDeleted. Nient’altro può avviare una pipeline.

RemoteEventForwarder traduce gli eventi della classe delle notifiche in frame e li invia via RPC alle sessioni connesse, dove un mapper di frame li trasforma in notifiche in-app. Un client leggero vede quindi una proiezione del flusso di eventi del server senza essere responsabile di alcuna esecuzione, che è esattamente il senso della separazione del client leggero.

Non tutti i frame inviati sono però una notifica. Il flusso del ciclo di vita delle attività e la riassegnazione dei ticket vengono inoltrati in tempo reale per guidare l’interfaccia, ma non vengono registrati nel feed delle notifiche e non mostrano alcun avviso.