33800 Docs

← Retour

Audit PASS 3 — ulias-org-web — Integration & Contrats Inter-Services

Date : 15/02/2026 Status : IMPLEMENTEE Auditeur : Claude Opus 4.6 (audit independant) Projet : /stock_8to/33800-stack/projects/ulias-org-web/ Backend de reference : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts


Resume executif

Severite Nombre
BUG CERTAIN 4
BUG PROBABLE 7
RISQUE THEORIQUE 10
Total 21

Les findings les plus critiques concernent :


Findings


F-01 : Types RoutingEvent incomplets — champs du backend absents de l'interface TypeScript

// EventCard.tsx:72-76 — accede a des champs inexistants dans le type case 'routing': { const d = event.data; const conf = d.confidence ? (${Math.round(d.confidence * 100)}%) : ''; const model = d.model ? via ${d.model} : ''; const pipeline = d.pipeline ? | pipeline: ${d.pipeline} : '';

- **Impact** : TypeScript est contourne ; le code fonctionne uniquement parce que les types sont trop larges en pratique (union type avec `data: any` dans certains cas). Si un refactoring futur applique des types stricts, ces acces casseront.
- **Fix suggere** : Completer l'interface `RoutingEvent.data` avec tous les champs emis par le backend : `confidence: number; reasoning: string; multi_step: boolean; pipeline: string | null; model: string;`.

---

### F-02 : Types ThinkingEvent incomplets — champs `model`, `tools`, `agent` absents
- **Severite** : BUG CERTAIN
- **Fichier** : `/stock_8to/33800-stack/projects/ulias-org-web/src/lib/types.ts:63-72`
- **Description** : L'interface `ThinkingEvent.data` ne declare que `{ iteration: number; maxIterations: number }`, mais le backend emet `{ iteration, maxIterations, model, agent, tools }`. Le code `ChatMessages.tsx:52` accede a `d.model` et `EventCard.tsx:80-82` accede a `d.model` et `d.tools`, qui n'existent pas dans le type.
- **Code concerne** :
```typescript
// types.ts
data: { iteration: number; maxIterations: number; };

// ChatMessages.tsx:52
streamInfo.model = d?.model || streamInfo.model;

// EventCard.tsx:81
const model = d.model ? ` [${d.model}]` : '';
const tools = d.tools ? ` ${d.tools} outils` : '';

F-03 : Types ToolResultEvent incomplets — champs duration, success absents

// EventCard.tsx:87-89 const dur = d.duration ? (${formatDuration(d.duration)}) : ''; const status = d.success === false ? ' ERREUR' : '';

// ChatMessages.tsx:65 const dur = event.data?.duration;

- **Impact** : Meme que F-01.
- **Fix suggere** : Ajouter `duration?: number; success?: boolean;` a `ToolResultEvent.data`.

---

### F-04 : Types SubTaskStartedEvent/CompletedEvent incomplets — champs `model`, `duration`, `toolCalls`, `iterations` absents
- **Severite** : BUG CERTAIN
- **Fichier** : `/stock_8to/33800-stack/projects/ulias-org-web/src/lib/types.ts:156-181`
- **Description** : `SubTaskStartedEvent.data` ne declare pas `model`. `SubTaskCompletedEvent.data` ne declare pas `duration`, `toolCalls`, `iterations`. Le code `EventCard.tsx:105-112` accede a ces champs.
- **Code concerne** :
```typescript
// EventCard.tsx:105
const model = d.model ? ` [${d.model}]` : '';

// EventCard.tsx:110-112
const dur = formatDuration(d.duration);
return `Etape ... — ${dur}, ${d.toolCalls || 0} outils, ${d.iterations || 0} iter`;

F-05 : getEventSummary accede a event.data?.title et event.data?.summary pour les events WS de conversation, mais ces events n'ont pas de champ data


F-06 : Markdown renderer via dangerouslySetInnerHTML — injection potentielle via liens Markdown


F-07 : Markdown renderer corrompt les URLs contenant & a cause de l'echappement HTML pre-transformation


F-08 : ObjectiveCompletedEvent.data.originalMessage accede mais absent du type


F-09 : showArchived poll via setInterval ne fonctionne jamais


F-10 : useStore pour streamInfo dans ChatMessages — risque de serialisation Qwik


F-11 : WebSocket se connecte au mauvais endpoint — /ws/cli au lieu de /ws/webui


F-12 : /api/teams appele sans authentification


F-13 : /api/status appele sans authentification (verification API key dans login)


F-14 : Event decision_request ecoute dans decisions/index.tsx mais jamais emis par le layout


F-15 : decision_response envoye via ulias:send-raw sans champ apiKey

// layout.tsx:77 — le handleSendRaw passe detail directement sans verification de type

- **Impact** : Faible. Le type `ClientMessage` ne couvre pas tous les messages reellement envoyes, mais le code fonctionne car les handlers passent `as any`.
- **Fix suggere** : Ajouter `DecisionResponseMessage` a `ClientMessage` et `SetConversationMessage` egalement.

---

### F-16 : Reconnexion WebSocket limitee a 5 tentatives sans possibilite de reset manuel
- **Severite** : RISQUE THEORIQUE
- **Fichier** : `/stock_8to/33800-stack/projects/ulias-org-web/src/lib/ws-manager.ts:6-7,114-125`
- **Description** : Apres 5 tentatives de reconnexion echouees, le WebSocket reste deconnecte definitivement. Il n'y a pas de bouton "Reconnecter" dans l'UI (le `ConnectionBadge` est en read-only). L'utilisateur doit recharger la page.
- **Code concerne** :
```typescript
const MAX_RECONNECT_ATTEMPTS = 5;
private scheduleReconnect() {
  if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) return;
  // ...
}

F-17 : Navigation avec window.location.href au lieu de useNavigate dans history/index.tsx


F-18 : loadLastConversation utilise un setTimeout de 500ms fragile


F-19 : setConversation envoie un message WS avec type non couvert par ClientMessage


F-20 : Double fetch dans le handler onClick$ du bouton "Voir archivees" ET dans le setInterval


F-21 : Token/API key stocke en localStorage — pas de protection CSRF native


Notes de synthese

Architecture event bus CustomEvent

Le projet utilise un pattern de communication inter-composants base sur document.dispatchEvent(new CustomEvent('ulias:...')). Voici la cartographie complete :

Event Emetteur Listener(s)
ulias:event layout.tsx (onEvent WS) ChatMessages, EventStream, ConversationSidebar, decisions/index, layout.tsx (handleClearActive)
ulias:send ChatInput, index.tsx, history, ChatMessages (fork) layout.tsx
ulias:user-message ChatInput, index.tsx (onSuggestionSelect), layout.tsx (handleTemplateMessage) ChatMessages
ulias:command (aucun emetteur visible dans le code) layout.tsx
ulias:send-raw decisions/index layout.tsx
ulias:disconnect layout.tsx (bouton Deconnexion) layout.tsx
ulias:switch-conversation ConversationSidebar, ChatMessages (fork), EventCard (fork) layout.tsx
ulias:load-older ChatMessages layout.tsx
ulias:load-history layout.tsx ChatMessages
ulias:prepend-history layout.tsx ChatMessages
ulias:clear-chat layout.tsx ChatMessages
ulias:message-corrected EventCard ChatMessages
ulias:conversation-created layout.tsx, ChatMessages (fork), EventCard (fork), ConversationSidebar ConversationSidebar
ulias:send-template-message ConversationSidebar layout.tsx

Event orphelin : ulias:command a un listener dans layout.tsx mais aucun emetteur dans le code frontend.

Conformite WebSocket

Le WsManager gere correctement le cycle de vie WebSocket :

Serialisation Qwik

Aucun probleme de serialisation d'objets non-serialisables (WebSocket, DOM elements, etc.) n'a ete detecte. Les composants utilisent correctement useVisibleTask$ pour le code browser-only et useSignal/useStore pour les donnees reactives. La MEMORY.md mentionne le bug connu "WebSocket dans useSignal" — le code actuel evite correctement ce pattern en gardant le WsManager dans le scope de useVisibleTask$.

Mutations dans le render

Aucune mutation de tableau (.sort(), .reverse(), .splice()) dans le code JSX n'a ete detectee. Le filtering dans history/index.tsx (lignes 173-184) et decisions/index.tsx (lignes 95-96) utilise .filter() qui retourne un nouveau tableau sans muter l'original.