Chapitres

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.

ModulePages /hub/*
agentsle 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é :

ZoneEntrées
PILOTAGETableau de bord · emails · brainstorm · user-model
BUSINESSclients · négociations · conseil · juridique
EXÉCUTIONbacklog · chantier · runs · questions · nouvelle tâche · sessions
DOCTRINE & APPRENTISSAGEdoctrine · constitution · cicatrices · drill
AGENTIQUEagents · réacteur · skills · propositions · automates · dispatch · apprentissage
MÉMOIREmemory (Brain) · recall sémantique (RAG)
SYSTÈMEsre · 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). Renvoie null si absent ou invalide.
  • requireEmployeeSession(event) : renvoie la session si userType === '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 user stub 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 }). Le ssr: false est 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 legacy ps_ac_*, tandis que les tâches et les tables de liens sont déjà passées au préfixe sy_* (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 sidebarle layout du cockpit (constante de navigation)
Ajouter une page /hub/Xmodules/<module>/pages/hub/X.vue + definePageMeta({ layout:'hub' })
Ajouter un endpoint cockpitmodules/<module>/server/api/hub/... + requireEmployeeSession(event)
Comprendre l'authserver/utils/session.ts + le middleware d'auto-session
Brancher un nouveau layerla 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.