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 affichent 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 ou non atteindre.

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 intermédiaire, les opérations sur les dépôts et les abonnements. 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 se trouver dans des espaces de travail différents sans qu’un « espace de travail courant » détenu par le serveur leur fasse diverger. L’isolation est plutôt appliquée à chaque 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 équivaut à 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 — de sorte que l’appartenance à un espace de travail ne surclasse jamais silencieusement l’hébergeur.

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

Trois pressions ont poussé vers un serveur :

  1. Une source de vérité unique. Un ordinateur de bureau et un téléphone, ou un ordinateur portable et un ordinateur de bureau, doivent voir le même état de la flotte. Si chacun possédait sa propre base de données, ils divergeraient. Un serveur unique fait 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 ; une version web ne peut donc jamais détenir la base de données ni exécuter un agent. Elle doit parler à un serveur qui le peut. Plutôt que de créer un second chemin de données réservé au web, l’application de bureau a été ramené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 n’est qu’un moteur de rendu.

Dans les deux cas, le client RPC obtenu remplace le fournisseur de données de l’application, si bien que toute l’interface — chaque écran, chaque fonctionnalité — lit et écrit par l’intermédiaire 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, si bien 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 affiche l’interface de bureau complète. Une fois connecté, il présente les mêmes écrans face à 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 la 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 distinctes. 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’exige aucun certificat puisque 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 se lier à une adresse autre que la boucle locale sans TLS ; un serveur distant suppose donc 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, qui n’a donc 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 ; héberger le paquet soi-même suppose donc d’y 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, si bien qu’un navigateur ne récupère jamais une origine arbitraire. Le client déployé appose une Content-Security Policy par requête à partir d’un cookie non sensible, de sorte que seule l’origine du serveur connecté est autorisée.

L’application Remote est un client distinct et 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’intermédiaire d’un relais WebSocket à courtage, 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 d’actualités ; 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 en refus par défaut, une clé pré-partagée scellant chaque trame, une capacité de session qui exclut les opérations privilégiées (l’appairage, 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 un appairage approuvé (ou divulgué) 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 bras, pas un second cerveau. Un serveur qui fait autorité, N bras sans intelligence ; aucun consensus, aucun 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 que le chemin de diffusion soit exercé 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 les backends de bac à sable dont dispose l’hôte (aucun sous Windows). Un worker dédié annonce par ailleurs toujours alwaysOn et acceptsParallel, ce sur quoi l’ordonnanceur pin → prefer → spill s’appuie pour placer les tâches. Il n’existe aucune sonde GPU ni ML.

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

Aspect 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 uniquement, via 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 lorsqu’un backend existe — Seatbelt sous macOS, bubblewrap sous Linux et WSL2 — et se rabat 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 qui lui a été fourni. Louer du travail 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 savoir ce qui borne une exécution dans chaque cas.

La capture de réunion se fait sur un client, car 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 et échoue donc explicitement là où le navigateur n’en fournit pas. Ce qui est réellement 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, si bien qu’aucun secret n’est persisté sur le poste de 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, et non différée. Le serveur surveille sa table des appareils associés et coupe, en quelques secondes, toute session ouverte dont l’appareil a quitté l’ensemble actif — sur le chemin WebSocket direct comme 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 à retenir, et il s’applique aux deux. Le client web dispose de 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 identifiant é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’appairage et de sécurité du téléphone