# Architecture

## Deux chaînes séparées

```text
          Application Next.js / Node
                     │
        ┌────────────┴────────────┐
        │                         │
 @workspace/analytics    @workspace/observability
        │                         │
 Événements typés          SDK OpenTelemetry
 Consentement, identité    Journaux, métriques, traces
 Files, routage                   │
        │                  Collecteur OTLP
 ┌──────┼──────┐                  │
 │      │      │                  ▼
Rybbit Umami Matomo          OpenObserve
```

Audience et observabilité ne se mélangent pas. Un événement métier ne part pas dans les traces, et
un span ne part pas chez un fournisseur d'audience. Seuls `traceId` et `spanId` circulent dans les
journaux, comme identifiants de corrélation.

## Pourquoi `packages/` sans monorepo

Le cahier des charges interdit de transformer un dépôt applicatif en monorepo, et ce dépôt n'en est
pas un : `pnpm-workspace.yaml` existe mais ne déclare aucun paquet.

Les paquets vivent donc dans `packages/`, autonomes, résolus par un alias `@workspace/*` dans
`tsconfig.json`. Aucune clé `packages:` n'est ajoutée à `pnpm-workspace.yaml`, aucune sémantique
d'installation ne change. Copier `packages/` dans un autre projet et ajouter deux lignes d'alias
suffit à le réutiliser.

## Frontière navigateur / serveur

| Entrée                            | Contenu                                       | Interdits                            |
| --------------------------------- | --------------------------------------------- | ------------------------------------ |
| `@workspace/analytics`            | Client, consentement, caviardage, adaptateurs | Aucun secret, aucune dépendance Node |
| `@workspace/analytics/server`     | Idem plus lecture des secrets                 | Ne jamais importer côté client       |
| `@workspace/observability`        | Façade `log` / `counter` / `withSpan`         | —                                    |
| `@workspace/observability/server` | SDK Node                                      | Ne jamais importer côté client       |

`browserConfigFromEnv` ne lit **que** des variables `NEXT_PUBLIC_*`. Les jetons d'écriture
(`MATOMO_TOKEN_AUTH`, `RYBBIT_API_KEY`) ne sont lus que par `serverConfigFromEnv`.

## Chemin d'un événement

1. `analytics.track('feature_used', { feature: 'recherche' })` — nom et charge vérifiés à la
   compilation.
2. **Consentement** : refus ou mode non satisfait → rejet compté, rien ne part.
3. **Validation Zod** : charge non conforme → rejet compté et diagnostic, rien ne part.
4. **Caviardage** : clés interdites et motifs sensibles remplacés.
5. **Enveloppe** : `eventId`, version de schéma, horodatage, environnement, identité anonyme.
6. **Mise en file**, une file par fournisseur.
7. **Livraison** par lots, avec réessais à délai croissant et gigue.

Aucune de ces étapes ne lève d'exception chez l'appelant. Une panne d'analytics ne doit jamais
casser une requête métier.

## Isolation des pannes

Une file et un compteur par fournisseur. Un fournisseur injoignable accumule, réessaie, puis
abandonne au-delà du plafond — sans affecter les autres. Vérifié par le test de fumée.

**Limite connue** : les files sont indexées par nom de fournisseur, donc une seule instance par
type.

## Ce que la livraison garantit

Au mieux, avec déduplication quand le fournisseur la gère via `eventId`. **Pas** d'exactement-une-
fois. Les abandons sont comptés (`droppedOverflow`, `droppedExpired`, `droppedExhausted`) et
lisibles par `getStats()`.

## Réutilisation ailleurs

Dépendant du projet : `core/events.ts` (le catalogue) et les variables d'environnement.
Indépendant : tout le reste. Les adaptateurs ne connaissent aucun nom d'événement.
