> ## Documentation Index
> Fetch the complete documentation index at: https://docs.befailproof.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture

> Fonctionnement interne du gestionnaire de hooks, du chargement de la configuration et de l'évaluation des politiques

Ce document explique le fonctionnement interne de failproofai : comment le système de hooks intercepte les appels d'outils des agents, comment la configuration est chargée et fusionnée, comment les politiques sont évaluées, et comment le tableau de bord surveille l'activité des agents.

***

## Vue d'ensemble

failproofai comporte deux sous-systèmes indépendants :

1. **Gestionnaire de hooks** - Un sous-processus CLI rapide que Claude Code invoque à chaque appel d'outil de l'agent. Il évalue les politiques et retourne une décision.
2. **Moniteur d'agents (Tableau de bord)** - Une application web Next.js pour surveiller les sessions d'agents et gérer les politiques.

Les deux sous-systèmes partagent des fichiers de configuration dans `~/.failproofai/` et le répertoire `.failproofai/` du projet, mais ils s'exécutent en tant que processus séparés et ne communiquent que via le système de fichiers.

***

## Gestionnaire de hooks

### Intégration avec Claude Code

Lorsque vous exécutez `failproofai policies --install`, des entrées comme celle-ci sont écrites dans `~/.claude/settings.json` :

```json theme={null}
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "failproofai --hook PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [ ... ]
  }
}
```

Claude Code invoque ensuite `failproofai --hook PreToolUse` en tant que sous-processus avant chaque appel d'outil, en transmettant une charge utile JSON sur stdin.

### Format de la charge utile

```json theme={null}
{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/myproject/sessions/abc123.jsonl",
  "cwd": "/home/user/myproject",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "sudo apt install nodejs" }
}
```

Pour les événements `PostToolUse`, la charge utile contient également `tool_result` avec la sortie de l'outil.

Le gestionnaire impose une limite de 1 Mo sur stdin. Les charges utiles dépassant cette taille sont ignorées et toutes les politiques autorisent implicitement.

### Format de réponse

**Refus (PreToolUse) :**

```json theme={null}
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Blocked by failproofai: sudo command blocked"
  }
}
```

**Refus (PostToolUse) :**

```json theme={null}
{
  "hookSpecificOutput": {
    "additionalContext": "Blocked by failproofai because: API key detected in output"
  }
}
```

**Instruction (tout événement sauf Stop) :**

```json theme={null}
{
  "hookSpecificOutput": {
    "additionalContext": "Instruction from failproofai: Verify tests pass before committing."
  }
}
```

**Instruction pour l'événement Stop :**

* Code de sortie : `2`
* La raison est écrite sur stderr (pas sur stdout)

**Autorisation :**

* Code de sortie : `0`
* stdout vide

**Autorisation avec message :**

`allow(message)` permet à une politique d'envoyer du contexte informatif à Claude même lorsque l'opération est autorisée. Le gestionnaire de hooks écrit le JSON suivant sur **stdout** (pas dans un fichier de configuration — c'est la réponse du gestionnaire à Claude Code, tout comme les réponses de refus et d'instruction ci-dessus) :

```json theme={null}
// Written to stdout by the hook handler process
{
  "hookSpecificOutput": {
    "additionalContext": "All CI checks passed on branch 'feat/my-feature'."
  }
}
```

* Code de sortie : `0` (l'opération est autorisée)
* Lorsque plusieurs politiques retournent `allow` avec un message, leurs messages sont joints par des sauts de ligne en une seule chaîne `additionalContext`
* Si aucune politique ne fournit de message, stdout est vide (comme avant)

### Pipeline de traitement

`src/hooks/handler.ts` implémente le pipeline complet :

```text theme={null}
stdin JSON
  → analyse de la charge utile (max 1 Mo)
  → extraction des métadonnées de session (session_id, cwd, tool_name, tool_input, etc.)
  → readMergedHooksConfig(cwd)    ← fusionne la config projet + locale + globale
  → enregistrement des politiques intégrées activées avec les paramètres résolus
  → chargement des politiques personnalisées depuis customPoliciesPath (si défini)
  → enregistrement des politiques personnalisées dans le registre de politiques
  → évaluation de toutes les politiques (intégrées d'abord, puis personnalisées)
      → le premier refus court-circuite
      → les décisions d'instruction s'accumulent
      → les messages d'autorisation s'accumulent
  → écriture de la décision JSON sur stdout
  → persistance de l'événement dans ~/.failproofai/hook-activity.jsonl
  → sortie
```

L'ensemble du processus s'exécute en moins de 100 ms pour les charges utiles classiques, sans aucun appel LLM.

***

## Chargement de la configuration

`src/hooks/hooks-config.ts` implémente le chargement de configuration à trois niveaux.

```text theme={null}
[1] {cwd}/.failproofai/policies-config.json        ← projet   (priorité la plus haute)
[2] {cwd}/.failproofai/policies-config.local.json  ← local
[3] ~/.failproofai/policies-config.json             ← global   (priorité la plus basse)
```

Logique de fusion :

* `enabledPolicies` - union dédupliquée sur les trois fichiers
* `policyParams` - par clé de politique, le premier fichier qui la définit l'emporte entièrement
* `customPoliciesPath` - le premier fichier qui la définit l'emporte
* `llm` - le premier fichier qui la définit l'emporte

Le tableau de bord web utilise `readHooksConfig()` (global uniquement) pour la lecture et l'écriture, car il n'est pas invoqué avec un répertoire de travail de projet.

***

## Évaluation des politiques

`src/hooks/policy-evaluator.ts` exécute les politiques dans l'ordre.

Pour chaque politique :

1. Rechercher le schéma `params` de la politique (si elle en possède un).
2. Lire `policyParams[policy.name]` depuis la configuration fusionnée.
3. Fusionner les valeurs fournies par l'utilisateur par-dessus les valeurs par défaut du schéma pour produire `ctx.params`.
4. Appeler `policy.fn(ctx)` avec le contexte résolu.
5. Si le résultat est `deny`, arrêter immédiatement et retourner cette décision.
6. Si le résultat est `instruct`, accumuler le message et continuer.
7. Si le résultat est `allow`, passer à la politique suivante.

Après l'exécution de toutes les politiques :

* Si un `deny` a été retourné, émettre la réponse de refus.
* Si des retours `instruct` ont été collectés, émettre une seule réponse d'instruction avec tous les messages joints.
* Sinon, émettre une réponse d'autorisation (stdout vide, sortie 0).

***

## Politiques intégrées

`src/hooks/builtin-policies.ts` définit les 39 politiques intégrées en tant qu'objets `BuiltinPolicyDefinition` :

```typescript theme={null}
interface BuiltinPolicyDefinition {
  name: string;
  description: string;
  fn: (ctx: PolicyContext) => PolicyResult;
  match: {
    events: HookEventType[];
    tools?: string[];
  };
  defaultEnabled: boolean;
  category: string;
  beta?: boolean;
  params?: PolicyParamsSchema;
}
```

Les politiques qui acceptent des `params` déclarent un `PolicyParamsSchema` avec les types et valeurs par défaut de chaque paramètre. L'évaluateur de politiques injecte les valeurs résolues dans `ctx.params` avant d'appeler `fn`. Les fonctions de politique lisent `ctx.params` sans vérification de nullité car les valeurs par défaut sont toujours appliquées en premier.

La correspondance de motifs au sein des politiques utilise des jetons de commande analysés (argv), et non une correspondance de chaînes brutes. Cela empêche le contournement via l'injection d'opérateurs shell (par exemple, un motif pour `sudo systemctl status *` ne peut pas être contourné en ajoutant `; rm -rf /` à la commande).

***

## Politiques personnalisées

`src/hooks/custom-hooks-registry.ts` implémente un registre adossé à `globalThis` :

```typescript theme={null}
const REGISTRY_KEY = "__failproofai_custom_hooks__";

export const customPolicies = {
  add(hook: CustomHook): void { ... }
};

export function getCustomHooks(): CustomHook[] { ... }
export function clearCustomHooks(): void { ... }  // used in tests
```

`src/hooks/custom-hooks-loader.ts` charge le fichier de politique de l'utilisateur :

1. Lire `customPoliciesPath` depuis la configuration ; ignorer si absent.
2. Résoudre vers un chemin absolu ; vérifier que le fichier existe.
3. Réécrire toutes les importations `from "failproofai"` vers le chemin dist réel afin que `customPolicies` se résolve vers le même registre `globalThis`.
4. Réécrire récursivement les importations locales transitives pour assurer la compatibilité ESM.
5. Écrire des fichiers `.mjs` temporaires et `import()` le fichier d'entrée.
6. Appeler `getCustomHooks()` pour récupérer les hooks enregistrés.
7. Nettoyer tous les fichiers temporaires dans un bloc `finally`.

En cas d'erreur (fichier introuvable, erreur de syntaxe, échec d'importation), l'erreur est consignée dans `~/.failproofai/hook.log` et le chargeur retourne un tableau vide. Les politiques intégrées ne sont pas affectées.

Les politiques personnalisées sont évaluées après toutes les politiques intégrées. Un `deny` d'une politique personnalisée court-circuite toujours les politiques personnalisées suivantes (mais toutes les politiques intégrées ont déjà été exécutées à ce stade).

***

## Journalisation de l'activité

Après chaque événement de hook, le gestionnaire ajoute une ligne JSONL à `~/.failproofai/hook-activity.jsonl` :

```json theme={null}
{
  "timestamp": "2026-04-06T12:34:56.789Z",
  "sessionId": "abc123",
  "eventType": "PreToolUse",
  "toolName": "Bash",
  "policyName": "block-sudo",
  "decision": "deny",
  "reason": "sudo command blocked by failproofai",
  "durationMs": 12
}
```

Une ligne par politique ayant pris une décision autre que allow. Les décisions allow ne sont pas journalisées (pour garder le fichier compact).

***

## Architecture du tableau de bord

Le tableau de bord est une application **Next.js 16** utilisant l'App Router avec des React Server Components et des Server Actions.

```text theme={null}
app/
  layout.tsx                  ← Mise en page racine (thème, télémétrie, navigation)
  projects/page.tsx           ← Composant serveur : liste tous les projets Claude
  project/[name]/page.tsx     ← Composant serveur : liste les sessions d'un projet
  project/[name]/session/
    [sessionId]/page.tsx      ← Composant serveur : affiche le visualiseur de session
  policies/page.tsx           ← Composant client : gestion des politiques + journal d'activité
  actions/
    get-hooks-config.ts       ← Lecture de la config + liste des politiques
    update-hooks-config.ts    ← Activation/désactivation d'une politique
    update-policy-params.ts   ← Mise à jour des paramètres de politique
    get-hook-activity.ts      ← Pagination/recherche dans le journal d'activité
    install-hooks-web.ts      ← Installation/suppression des hooks depuis le navigateur
  api/
    download/[project]/[session]/route.ts   ← Export de session CLI (JSONL ou JSON)
```

**Flux de données :**

* Les composants de page appellent `lib/projects.ts` et `lib/log-entries.ts` pour lire les données de projet/session directement depuis le système de fichiers (pas de couche API pour les lectures).
* La page Politiques utilise des Server Actions pour toutes les mutations (activation, mise à jour des paramètres, installation/suppression).
* Le visualiseur de session analyse le format de transcript JSONL de Claude et affiche une chronologie des messages et appels d'outils.

**Décisions de conception clés :**

* Pas de base de données - tout l'état persistant est dans des fichiers simples (`~/.failproofai/`, `~/.claude/projects/`).
* Server Actions pour les mutations - pas d'API REST nécessaire pour les opérations CRUD.
* React Server Components pour les pages de lecture - chargement initial plus rapide, pas de bundle client pour la récupération des données.
* Composants client uniquement là où l'interactivité est nécessaire (bascules de politiques, recherche d'activité, visualiseur de logs).

***

## Organisation des fichiers

```text theme={null}
failproofai/
├── bin/
│   └── failproofai.mjs           # Routeur CLI (hook / tableau de bord / install / etc.)
├── src/hooks/
│   ├── handler.ts                # Pipeline d'événements de hook
│   ├── builtin-policies.ts       # 39 définitions de politiques
│   ├── policy-evaluator.ts       # Moteur d'exécution des politiques
│   ├── policy-registry.ts        # Enregistrement et recherche de politiques
│   ├── policy-types.ts           # Interfaces TypeScript
│   ├── hooks-config.ts           # Chargement de configuration multi-niveaux
│   ├── custom-hooks-registry.ts  # Registre de hooks adossé à globalThis
│   ├── custom-hooks-loader.ts    # Chargeur ESM pour les hooks JS utilisateur
│   ├── manager.ts                # Opérations d'installation / suppression / listage
│   ├── install-prompt.ts         # Invite interactive de sélection de politiques
│   ├── hook-logger.ts            # Journalisation vers hook.log
│   ├── hook-activity-store.ts    # Persistance de l'activité vers hook-activity.jsonl
│   └── llm-client.ts             # Client API LLM (pour les politiques alimentées par IA)
├── app/                          # Tableau de bord Next.js (pages + server actions)
├── lib/                          # Utilitaires partagés
│   ├── projects.ts               # Énumération des projets Claude depuis le système de fichiers
│   ├── log-entries.ts            # Analyse du format JSONL de transcript Claude
│   ├── paths.ts                  # Résolution des chemins système
│   └── ...
├── components/                   # Composants React UI partagés
├── contexts/                     # Fournisseurs de contexte React (thème, actualisation automatique, télémétrie)
├── examples/                     # Exemples de fichiers de hooks personnalisés
└── __tests__/                    # Tests unitaires et E2E
```
