Chapitres
Sur cette page
DOC-05 / Référence technique · Chapitre 06
Le Hub
Le cockpit privé du vaisseau-mère : routes /hub/*, modules en couches, consoles agents et authentification à deux étages.
À quoi sert le Hub
Le Hub est l'interface d'administration privée du harnais agentique Synedre OS — une application Nuxt servie en local sur la machine hôte du vaisseau-mère, accessible uniquement par tunnel SSH derrière une porte d'authentification nginx (« knock-gate »). C'est depuis ce cockpit que le Fondateur pilote agents, chantiers, runs, négociations, mémoire et infrastructure.
Le Hub est standalone : il n'étend aucune autre application. Pas d'internationalisation ni de bascule de thème — c'est un cockpit dark-only, en français, conçu pour un seul opérateur.
Architecture en couches (Nuxt layers)
Le Hub n'est pas un monolithe : ses fonctionnalités sont éclatées en modules, chargés comme Nuxt layers via la clé extends: de la configuration Nuxt. Chaque layer expose un manifest.ts et sa propre configuration Nuxt — c'est ce pattern « layer » (et non « module Nuxt ») qui permet le chargement ; rangés au mauvais endroit, ces dossiers échouent au démarrage.
Chaque layer auto-découvre ses dossiers via Nitro et peut donc apporter :
- des pages sous la route
/hub/*; - des endpoints serveur sous
/api/hub/*,/api/bo/*,/api/voice/*; - du code serveur (accès base, utilitaires).
mothership-app/
├── nuxt.config.ts extends: [ ./modules/* ] (≈28 layers)
├── layouts/hub.vue layout unique du cockpit
├── middleware/hub-auth.ts no-op (auth en amont)
├── server/
│ ├── middleware/ injecte la session founder
│ ├── utils/session.ts requireEmployeeSession / getSession / ...
│ └── api/{bo,hub,voice}/ endpoints CORE
├── pages/hub/ quelques pages CORE (index = redirection 302)
└── modules/
├── agents/ (~37 pages /hub/*, ~100 endpoints /api/hub) ← le gros
├── chantier/ (cockpits + chantiers/[codename] + console CLI)
├── drill/ cicatrices/ conduite/ automates/ backlog/ …
└── fleet/ monitoring/ incidents/ invoicing/ reunions/ academy/ (server-only)
Une douzaine de modules sont server-only (API + base uniquement, aucune page Vue) ; leur surface est consommée par d'autres pages ou par les automates Python.
La carte des routes /hub/*
Quelques pages CORE vivent directement dans l'app ; l'essentiel des pages /hub/* est porté par les modules. Le module agents en concentre la grande majorité (agents, brainstorm, chantier, clients, conseil, négociations, runs, sessions, mémoire, doctrine, skills, paramètres, dispatch, réacteur…).
La route racine /hub n'est pas une page distincte : c'est une simple redirection 302 vers le tableau de bord /hub/synedre.
| Module | Pages /hub/* |
|---|---|
agents | le cœur (agents, brainstorm, chantier, clients, négociations, runs, mémoire, doctrine, skills…) |
backlog | /hub/backlog, /hub/planning, /hub/system/backlog |
chantier | /hub/chantiers/[codename], /hub/cockpits |
drill | /hub/drill, /hub/drill/[agent] |
cicatrices | /hub/cicatrices |
conduite | /hub/conduites |
automates | /hub/system/automates |
La navigation (sidebar)
La barre latérale du cockpit est codée en dur dans le layout, organisée en 7 zones par activité :
| Zone | Entrées |
|---|---|
| PILOTAGE | Tableau de bord · emails · brainstorm · user-model |
| BUSINESS | clients · négociations · conseil · juridique |
| EXÉCUTION | backlog · chantier · runs · questions · nouvelle tâche · sessions |
| DOCTRINE & APPRENTISSAGE | doctrine · constitution · cicatrices · drill |
| AGENTIQUE | agents · réacteur · skills · propositions · automates · dispatch · apprentissage |
| MÉMOIRE | memory (Brain) · recall sémantique (RAG) |
| SYSTÈME | sre · cost · backups · settings |
La marque d'entrée active se déclenche au match exact ou sur sous-route (route.path.startsWith(to + '/')).
Les trois familles d'endpoints
Le découpage /api/{bo,hub,voice} n'est pas qu'organisationnel : il porte une sémantique de droits.
/api/bo/* Back-Office "founder/cron" — sous knock-gate, parfois SANS auth applicative
/api/hub/* Surface cockpit employé — requireEmployeeSession quasi-systématique
/api/voice/* Transcription audio — requireEmployeeSession + garde CSRF
/api/bo/* — Back-Office
Endpoints organisés par sous-domaines (apprentissage, chantier, cicatrices, cost, inbox, mothership, sre, tenants). Particularité : certains /api/bo/* s'appuient sur le knock-gate plutôt que sur requireEmployeeSession — l'endpoint est déjà « owner-only » en amont. C'est notamment le cas de l'endpoint de synchronisation de la boîte mail, qu'un cron système appelle directement (le scheduler natif n'étant plus opérationnel, un cron de l'hôte sert de filet).
/api/hub/* — surface cockpit
Environ 125 endpoints, dont l'écrasante majorité dans le module agents. La quasi-totalité appelle requireEmployeeSession ; les exceptions sont des helpers internes ou des flux SSE. Pattern type :
export default defineEventHandler(async (event) => {
requireEmployeeSession(event)
...
})
Familles principales : chantier/, travail/, runs/, negociations/, conseil/, clients/, brainstorm/, memory/, doctrine/, skills/, sessions/, questions/, settings/, dispatch, synedre-cli/.
/api/voice/* — transcription
Un seul endpoint : un POST multipart (audio WAV obligatoire, locale optionnel) renvoyant { text, locale, duration_ms, model, backend }. Garde-fous : requireEmployeeSession, taille maximale de l'audio, rate-limit par session, audio jamais persisté sur disque (RAM seulement), et défense CSRF par vérification de l'origine.
Authentification à deux étages
┌─────────────────────────────────────────────────────────────┐
│ COUCHE INFRA (la vraie gate) │
│ Tunnel SSH (local) + knock-gate nginx (cookie + slug) │
└───────────────────────────────┬─────────────────────────────┘
│ requête arrive déjà "owner-only"
┌────────────────────────────────▼─────────────────────────────┐
│ COUCHE APPLICATIVE (Nuxt) │
│ middleware : injecte AUTOMATIQUEMENT une session founder │
│ → requireEmployeeSession voit toujours un employé valide │
└──────────────────────────────────────────────────────────────┘
Les gardes de session
getSession(event): décode le cookie de session (JSON base64url signé HMAC-SHA256). Renvoienullsi absent ou invalide.requireEmployeeSession(event): renvoie la session siuserType === 'employee', sinon lève une erreur 401. C'est la garde standard de/api/hub/*et/api/voice/*.requireFounderSession: exige en plus le statut SuperAdmin (403 sinon).requireRoleOrSaas(event, roles): exige un rôle donné OU le statut SuperAdmin.isSuperAdminSaaS: whitelist statique d'adresses, volontairement en dur dans le code (pas en base) — « toucher cette liste = changement de code + revue ».
Forme de la session : employeeId, email, firstname, lastname, role, profileId, clientId, userType, isAdmin.
L'auto-session founder
Un middleware serveur, à chaque requête sans cookie de session valide, signe et pose un cookie dont le payload est une constante FOUNDER_SESSION (employé n°1, rôle SuperAdmin, admin, valable 30 jours). Conséquence : sur le runtime du vaisseau-mère, requireEmployeeSession passe toujours — la sécurité réelle est l'infrastructure (SSH + knock-gate). Ce middleware est scopé vaisseau-mère : il n'existe ni dans le core OSS ni dans les tenants ; sur les sites publics, requireEmployeeSession reste obligatoire.
Le middleware hub-auth est un no-op
Le middleware Nuxt hub-auth est aujourd'hui un no-op (defineNuxtRouteMiddleware(() => {})), désactivé parce que le knock-gate nginx et le tunnel SSH font l'authentification en amont. Il reste exporté car de nombreuses pages le référencent encore via definePageMeta({ middleware: 'hub-auth' }).
Le knock-gate nginx (cookie + slug) vit côté infrastructure nginx, pas dans le dépôt Nuxt. Voir le runbook VPS.
Layout du Hub
- Layout unique : l'app étant standalone, il n'override aucun layout parent.
- Pas d'utilisateur dynamique : un
userstub fixe est posé (auth Nuxt retirée). useHead: titleTemplate « … — Synedre OS », favicon de la pieuvre.- Les pages déclarent
definePageMeta({ layout: 'hub', middleware: 'hub-auth', ssr: false }). Lessr: falseest fréquent sur les pages temps-réel (réacteur, chantier) — rendu client-only.
Les surfaces console
Le Hub matérialise plusieurs « consoles » de pilotage des agents. Le modèle de données central repose sur les entités Python sous synedre/ac_entities/ et des tables PostgreSQL.
Runs — /hub/runs
Un run (entité sy_run) est une exécution scopée d'Atlas (chat / email / cron) sur un périmètre vaisseau-mère ou tenant. La réponse expose { id_run, source, trigger, scope, title, status, ref_type, ref_id, date_add, finished_at }. Source atlas-inbox = email, console = chat. Les emails d'intention question/chantier/bruit ne sont pas matérialisés en run. À noter : sy_task_run n'est pas un run.
Chantiers — /hub/chantier, /hub/cockpits
Un chantier se décompose en travaux, eux-mêmes en tâches (entités ChantierEntity / TravailEntity / TacheEntity). Le module chantier ajoute notamment la console CLI live (cli-session/{start,stop,reset}) et un flux SSE des événements agents, ainsi que les endpoints atlas-runs (draft-reply, spawn-fix, profile). La création canonique passe par create_with_skeleton (procédure 7 étapes).
Dette de naming : le chantier et le travail restent sur le préfixe legacyps_ac_*, tandis que les tâches et les tables de liens sont déjà passées au préfixesy_*(chantier Strangler Fig en cours).
Console CLI — synedre-cli
Les endpoints synedre-cli/ (status, send-keys, stream) forwardent vers un daemon de l'hôte qui pilote une session Claude CLI réelle depuis le cockpit. En cas de daemon indisponible : pas de 5xx, l'état renvoie alive=false, et le message d'erreur n'est jamais exposé au client.
Négociations — /hub/negociations
Table sy_negociation. Pipeline : forward d'un email → Atlas classe l'intention en negociation → création d'un item sy_negociation → la page de négociation où les agents travaillent (besoin, audit, proposition, qualification…).
Fleet — module server-only
Le module fleet n'a pas (encore) de page propre. Sa table décrit les instances tenants. La source de vérité opérationnelle des VPS reste cependant une autre table dédiée à la cartographie des serveurs.
La table fleet contient des colonnes de type mot de passe / clé d'API héritées de l'ancien PaaS — incohérent avec la doctrine « zéro secret en base métier ». À auditer ; ne pas considérer comme la source canonique des secrets.
Réacteur — /hub/reactor
Une vue orbitale live : trois anneaux concentriques par groupe d'agents (direction, cadrage/exécution, validation), logo pieuvre au centre, flux SSE via un composable dédié. Rendu client-only, adapté de la page marketing publique.
Points d'entrée pour reprendre le code
| Tu veux… | Va voir |
|---|---|
| Ajouter une entrée sidebar | le layout du cockpit (constante de navigation) |
| Ajouter une page /hub/X | modules/<module>/pages/hub/X.vue + definePageMeta({ layout:'hub' }) |
| Ajouter un endpoint cockpit | modules/<module>/server/api/hub/... + requireEmployeeSession(event) |
| Comprendre l'auth | server/utils/session.ts + le middleware d'auto-session |
| Brancher un nouveau layer | la clé extends: de la config Nuxt + manifest.ts du module |
| Runs / chantiers (base) | entités synedre/ac_entities/ + tables sy_run, ps_ac_chantier, sy_negociation |
Aucun secret en clair ici. Le secret de signature des cookies de session est consommé par un utilitaire de chiffrement (HMAC) ; sa variable d'environnement vit dans les fichiers .env non versionnés, jamais dans le code.