Aller au contenu

Déploiement et clients

Control Center n’est pas un seul programme. C’est une architecture à client léger : un serveur sans interface qui détient vos données et un ensemble de clients qui rendent une interface par une seule connexion RPC à ce serveur. Comprendre ce découpage explique pourquoi certaines choses s’exécutent là où elles le font et ce que chaque client peut atteindre ou non.

Un processus appelé cc_server détient les bases de données — un petit global.db pour l’état à l’échelle du serveur, plus un fichier SQLite par espace de travail, ouverts à la demande — et exécute le travail de longue durée : les moteurs de pipeline, les réconciliateurs, la surface d’outils MCP et l’écouteur d’orchestration. Tout ce qui doit survivre à une fenêtre, ou que deux clients doivent voir à l’identique, réside là.

Aucun client n’ouvre lui-même un fichier de base de données. Chaque client détient plutôt un RemoteRpcClient unique et émet par son entremise les opérations et les abonnements sur les dépôts. Le serveur authentifie la connexion à l’aide d’une clé prépartagée d’appareil associé — puis cesse de se soucier de la connexion. Une session n’est pas liée à un espace de travail. Le répartiteur est sans état : chaque appel propre à un espace de travail porte son propre workspace_id dans ses arguments, et tout appel qui en omet un est refusé.

C’est délibéré, et c’est ce qui permet à deux clients d’un même serveur de travailler dans des espaces de travail différents sans qu’un « espace de travail courant » détenu par le serveur les fasse diverger. L’isolation est plutôt appliquée par appel, en quatre étapes :

  1. L’opération déclare si elle est propre à un espace de travail; si c’est le cas et qu’aucun workspace_id n’est arrivé, l’appel est rejeté.
  2. L’identifiant nommé doit correspondre à un espace de travail enregistré, vérifié avant que quoi que ce soit n’ouvre une base de données — sinon un identifiant inconnu matérialiserait un workspace.db fantôme vide à chaque requête. Une absence donne un « introuvable ».
  3. Le WorkspaceRole de l’appelant doit atteindre le seuil minimal de l’opération, déduit de son type : la lecture exige guest, la mutation exige member, la destruction exige admin.
  4. Une opération qui expose le contenu d’un dépôt exige en plus une autorisation par dépôt au niveau qu’elle déclare — none / read / review / write — ainsi l’appartenance à un espace de travail ne surclasse jamais silencieusement l’hébergeur.

La phrase qui compte : détenir une clé d’appairage ne constitue pas la frontière d’accès; l’appartenance, oui.

Trois pressions ont mené à un serveur :

  1. Une source de vérité unique. Un poste de bureau et un téléphone, ou un ordinateur portable et un poste de bureau, devraient voir le même état de la flotte. Si chacun possédait sa propre base de données, ils divergeraient. Un serveur unique fait en sorte que chaque client lit les mêmes données en direct.
  2. Le web ne peut pas se servir lui-même. Un navigateur ne peut ni lancer un sous-processus ni ouvrir un fichier local, si bien qu’une version web ne peut jamais détenir la base de données ni exécuter un agent. Il doit parler à un serveur qui le peut. Plutôt que de bâtir un deuxième chemin de données réservé au web, l’application de bureau a été amenée sur le même chemin.
  3. L’exploitation sans interface. Un serveur que vous pouvez exécuter sur une petite machine — joignable depuis un téléphone dans le train ou un navigateur n’importe où — est plus utile qu’une interface graphique que vous devez laisser ouverte.

La contrepartie, c’est que le serveur est désormais une dépendance : s’il ne tourne pas, rien ne fonctionne. La réponse par défaut de l’application de bureau est d’en lancer un elle-même.

Cinq clients joignent le serveur, chacun avec une forme et un profil de confiance différents.

L’application de bureau Flutter (macOS, Windows, Linux) est le client complet. Au premier lancement, elle demande comment elle doit s’exécuter, puis se souvient du choix :

  • Local : lancer et superviser un cc_server sur cette machine — la configuration par défaut, autonome et mono-utilisateur. L’application de bureau lance le serveur, qui détient la base de données sous la racine de support de l’application et lui parle par la boucle locale.
  • Distant : se connecter à un cc_server exécuté ailleurs par une connexion WebSocket sécurisée. Les données résident sur ce serveur; ce poste de bureau n’est qu’un moteur de rendu.

Dans les deux cas, le client RPC résultant remplace le fournisseur de données de l’application, de sorte que l’interface entière — chaque écran, chaque fonctionnalité — lit et écrit par l’entremise du serveur. L’application de bureau ne touche jamais directement au fichier de base de données.

La version web est la même application Flutter compilée pour s’exécuter dans un navigateur, hébergée à app.usectrl.dev, de sorte qu’il n’y a rien à installer. Un navigateur ne peut pas lancer de sous-processus, le client web est donc toujours distant : il se connecte à un cc_server par wss:// et rend l’interface de bureau complète. Une fois connecté, il affiche les mêmes écrans par rapport à la même flotte, parce que c’est le même code sur le même RPC. Les lacunes sont la poignée d’endroits qui nécessitent une capacité native qu’un navigateur n’a pas — la couche de qualité de capture de réunion ci-dessous est la principale, et les notifications du système d’exploitation en sont une autre.

C’est pourquoi « aucun téléchargement » et « aucun serveur » sont deux affirmations différentes. Le client web supprime l’installation; il ne supprime pas le serveur. Ce à quoi il se connecte peut être un cc_server sur le même ordinateur portable — ws://localhost:9030, qui n’a besoin d’aucun certificat parce que les navigateurs considèrent la boucle locale comme fiable — ou un serveur sur une autre machine, qui doit être joignable par wss:// avec un certificat auquel le navigateur fait confiance. cc_server refuse de lier une adresse autre que la boucle locale sans TLS, donc un serveur distant suppose soit son propre certificat, soit un proxy qui termine le TLS, soit un tunnel ou un VPN qui en fournit un.

Le paquet lui-même est hébergé séparément du serveur auquel il parle. Un cc_server autonome sert le point de terminaison RPC, la surface MCP et ses proxys, mais pas de paquet web — le chemin des fichiers statiques existe dans le serveur, mais aucune racine web n’est configurée sur le binaire autonome, donc il n’a rien à servir à /. L’image de conteneur cc-webapp publiée est ce qui héberge le paquet, et scripts/build_web.sh est ce qui le construit. Un serveur n’accepte les origines de navigateur que sur sa liste --allowed-origins, qui vaut par défaut https://app.usectrl.dev, donc héberger le paquet soi-même signifie ajouter son origine.

Les médias (avatars, images du fil, captures d’écran de PR) passent par le point de terminaison proxy du serveur plutôt que de joindre directement les hôtes en amont, ainsi un navigateur ne récupère jamais une origine arbitraire. Le client déployé appose une politique de sécurité du contenu par requête à partir d’un témoin non sensible, de sorte que seule l’origine du serveur connecté est autorisée.

L’application Remote est un client distinct, plus léger — une PWA web Flutter à remote.usectrl.dev. Elle n’est délibérément pas l’application complète. Elle s’associe par l’entremise d’un relais WebSocket à courtier, scellé de bout en bout, et parle une tranche essentiellement en lecture de la surface d’outils : lire les tickets, les messages et le fil de nouvelles; envoyer une réponse ou mettre à jour un ticket.

Un téléphone est un principal moins privilégié qu’un agent local. Une politique d’outils qui refuse tout par défaut, une clé prépartagée scellant chaque trame, une capacité de session qui exclut les opérations privilégiées (l’association, par exemple) d’un client compagnon, les vérifications de rôle et d’autorisation par dépôt à chaque appel décrites ci-dessus et la limitation de débit empêchent une association approuvée (ou divulguée) de devenir un contrôle à distance complet. Voir Contrôle à distance et mobile pour le modèle de sécurité complet.

Les outils externes — éditeurs, autres agents, CLI — qui parlent le Model Context Protocol se connectent à la surface MCP du serveur et consomment le registre d’outils typé. C’est le chemin d’intégration programmatique; ce n’est pas une interface graphique. Voir Utiliser le serveur MCP et la référence des outils MCP.

Un cc_worker est un binaire sans interface en pur Dart qui s’associe à un cc_server, déclare les capacités de son hôte et tire des tâches louées à exécuter. Il diffuse en continu les événements de processus par le même RPC et ne détient aucun état durable : aucune base de données, aucune authentification, aucune approbation, aucun budget — ces éléments ne quittent jamais cc_server. Un worker est un membre, pas un deuxième cerveau. Un serveur faisant autorité, N membres sans intelligence; il n’y a ni consensus, ni trafic entre workers.

L’exécution louée reste de la plomberie de location. agentRun est le seul type qui exécute une vraie commande (CC_JOB_COMMAND, ou une invite renvoyée en écho si aucune n’a été fournie). pipelineStep, codeIndex, goldenRender, benchmark et evalBatch sondent actuellement avec git --version / rev-parse HEAD afin d’exercer le chemin de diffusion sans prétendre que ces tâches ont été exécutées.

Ce qu’il annonce, c’est le système d’exploitation, l’architecture du processeur, le nombre de cœurs, la mémoire vive, si un SDK Flutter est joignable et quels backends de bac à sable l’hôte possède (aucun sous Windows). Un worker dédié annonce aussi toujours alwaysOn et acceptsParallel, ce que le planificateur pin → prefer → spill utilise pour placer les tâches. Il n’y a aucune sonde GPU ni ML.

Un poste de bureau solo ne remarque rien de tout cela, parce que la répartition ordinaire ne passe pas du tout par la flotte. Il existe un worker implicite en cours de processus, enregistré pour l’hôte du serveur, mais ce n’est qu’une couture d’enregistrement sans exécuteur branché — une tâche réellement placée sur le worker local échoue plutôt que de s’exécuter. Aujourd’hui, seul un vrai cc_worker exécute des tâches louées.

Préoccupation Serveur (cc_server) Exécuteur de flotte (cc_worker) Client de bureau Client web / téléphonique
Bases de données (Drift/SQLite) les détient — — —
Authentification, approbations, budgets, identité les détient — — —
Pipelines, réconciliateurs, écouteur d’orchestration les exécute — — —
Surface d’outils MCP la sert — — —
Exécution d’agents l’exécute (ou la loue) exécute les tâches louées — —
Rendu de l’interface — — complet complet (web) / essentiellement en lecture (téléphone)
Capture de réunion (micro + audio de la réunion) — — capture native web seulement, par l’audio de partage d’écran
OAuth du calendrier (Google) stocke les jetons — — —

Les deux niveaux ne cloisonnent pas de la même façon. cc_server enveloppe les exécutions qu’il réalise lui-même dans le bac à sable du système d’exploitation de l’hôte là où un backend existe — Seatbelt sous macOS, bubblewrap sous Linux et WSL2 — et retombe sur les autres frontières sous Windows ou sans bwrap et socat. cc_worker ne cloisonne pas du tout. Il annonce une capacité sandbox pour que le serveur puisse placer les tâches par plateforme, mais rien dans le worker n’en applique une : une tâche louée est lancée directement, bornée par l’environnement de la location et l’arbre de travail fourni. Confier du travail en location déplace donc l’exécution hors du bac à sable, même sur un hôte qui en possède un. Voir Bac à sable et sécurité pour ce qui borne une exécution dans chaque cas.

La capture de réunion se fait sur un client parce qu’elle a besoin d’un microphone et de l’audio de la réunion — un serveur dans un placard ne peut pas enregistrer votre appel. L’enregistrement est ensuite résumé par le pipeline du serveur, et les notes sont stockées là où chaque client peut les lire.

Le client web enregistre lui aussi, ce que la forme du tableau peut masquer : il capte le microphone ainsi qu’une piste audio de partage d’écran dans le navigateur et diffuse du PCM à 16 kHz au serveur. Il exige cette piste audio, il échoue donc explicitement là où le navigateur n’en fournit pas. Ce qui est vraiment réservé au bureau, c’est la couche de qualité autour de la capture — l’annulation d’écho au niveau du signal par le processeur natif, l’indicateur de niveau d’entrée et l’avertissement de micro muet, la réexécution d’un résumé, l’annulation du traitement et l’action « Enregistrer et lier » du calendrier.

Chaque client prouve son identité au moyen d’une clé prépartagée provisionnée une seule fois :

  • L’application de bureau en mode local génère une nouvelle clé à chaque démarrage et la remet au serveur lancé par l’environnement, de sorte qu’aucun secret n’est persisté sur le bureau.
  • Les clients distants (bureau distant, web, téléphone) utilisent une clé émise par la commande pair du serveur et stockée dans le trousseau du système d’exploitation (ou, sur le téléphone, localement).

La révocation est immédiate, pas différée. Le serveur surveille sa table des appareils associés et coupe toute session ouverte dont l’appareil a quitté l’ensemble actif, en quelques secondes — tant sur le chemin WebSocket direct que sur le chemin relais. Vous n’avez pas à attendre que l’appareil se reconnecte pour le couper.

La clé ne transite jamais par une URL qu’un hôte statique peut voir. Lorsqu’elle arrive par un lien — sur le téléphone ou dans un navigateur — elle voyage dans le fragment d’URL, la partie après #, que le navigateur n’envoie jamais au serveur qui héberge la page. C’est l’invariant qu’il faut retenir, et il s’applique aux deux. Le client web a deux autres points d’entrée : le formulaire de connexion, où vous collez directement un serveur et une clé, et un code d’invitation, qu’il échange contre un justificatif d’authentification émis pour ce navigateur uniquement.

  • Espaces de travail et isolation : pourquoi chaque appel RPC porte son propre identifiant d’espace de travail et comment le découpage des bases de données rend l’isolation structurelle
  • Architecture : comment la base de code est structurée autour du serveur et de ses clients
  • Contrôle à distance et mobile : le modèle d’association et de sécurité du téléphone