Chaque bogue qu’a réellement connu cette couche était silencieux : une
collection que Content écartait parce que son nom n’était pas un identifiant
JavaScript, une page en 404 parce qu’un lien avait vieilli, une navigation qui
revenait vide. Aucun des trois ne disait quoi que ce soit — la compilation était
verte et le site faux. Des vérifications s’exécutent donc après la compilation,
toutes lisant la même chose : le cache d’analyse que Content laisse derrière
lui.

| Vérification                                     | Gravité       |
| :----------------------------------------------- | :------------ |
| Une collection sans aucune page                   | erreur        |
| Un dossier en conflit avec un segment d’URL       | erreur        |
| Un `title` ou une `description` manquant          | avertissement |
| Un lien interne ou une ancre cassés               | avertissement |
| Ce qu’une section générée n’a pas pu lire         | avertissement |
| Ce que porte chaque langue et ce qui s’est figé   | note          |

## Pourquoi la gravité n’est pas uniforme

Une erreur coûte une page que personne ne peut atteindre et que rien sur le site
n’expliquerait jamais — une collection vide, c’est un 404 sur chaque page d’une
version. Un avertissement coûte de la qualité : la page s’affiche toujours, et
une source distante peut vieillir d’une publication à l’autre sans que ce soit
la faute de cette compilation. Faire échouer la compilation parce que le dépôt
de quelqu’un d’autre a avancé rendrait une compilation verte dépendante d’un
tiers.

Une **note** ne coûte rien du tout : c’est un état du site, pas un défaut dans
le site. Une page non traduite n’est pas une faute, et la signaler comme
avertissement apprendrait à tout le monde à passer les avertissements.

## Sections générées

Une [section générée](/concepts/generated-sections) est vérifiée comme toute autre
collection, dans ses propres termes. Ce qui arrive dans ce rapport est l’une de
trois choses :

- l’**artefact n’était pas là** à cette ref, la section n’a donc pas été
  construite ;
- le type **n’en a rien lu**, la section revendique donc des URL et n’en sert
  aucune ;
- le type **est passé outre quelque chose** — un `$ref` irrésoluble dans un
  document OpenAPI, un titre de changelog qui ressemble à une release mais ne
  s’analyse pas comme telle et s’est donc retrouvé intégré à la release
  au-dessus.

Les trois sont des avertissements, et ce n’est pas un compromis. Tout ce qu’un
artefact **local** peut avoir de faux — un chemin qui n’existe pas, un fichier
que le type ne sait pas lire du tout, une option qu’il ne connaît pas — fait
déjà échouer la compilation pendant le chargement de la configuration, bien
avant que ce rapport existe : la configuration propre du site est une faute que
la compilation ne doit pas porter. Ce qui reste ici est soit un artefact
**distant**, qui peut légitimement avoir vieilli d’une publication à l’autre,
soit une page qui s’est affichée avec quelque chose en moins.

::callout{type="tip" title="Ces constats étaient affichés puis perdus"}
Un artefact est analysé pendant le chargement de la configuration : un
avertissement sur un `$ref` écarté était donc affiché avant même que le serveur
de développement ait fini de démarrer, et avait défilé quand quelqu’un regardait
enfin — alors que tous les autres constats de cette couche attendaient dans un
seul rapport. Ils passent désormais par le rapport, et avec lui par le panneau
[Checks](/reference/devtools/checks).
::

## Le rapport de traduction

Une ligne par langue, à chaque compilation :

```
[duxt] translations
[duxt]   de: 118/121 pages, 3 behind the original
[duxt]     "docs/guides/deploying.md" has no de translation.
[duxt]     "docs/de/concepts/sources.md" has not moved since the original changed.
```

Le second chiffre exige `history: true` sur la source : les dates viennent de
git, et une source qui n’a pas demandé son historique n’en a aucune. Sans lui le
rapport compte la couverture et ne dit rien du décalage, plutôt que de le
deviner à partir de l’absence.

Il existe à cause d’un échec précis. OpenCode a traduit sa documentation en
dix-sept langues avec un agent en CI, a coupé le workflow, et nulle part il
n’était dit que les traductions avaient cessé de bouger. La couche sait déjà,
page par page, quelles langues la portent et quand chaque fichier a changé pour
la dernière fois ; un rapport est ce qui en fait quelque chose que quelqu’un
voit.

## Le même rapport, en commande

Les constats vivent à deux endroits difficiles à transmettre à qui que ce soit :
un journal de compilation qui a défilé, et des panneaux de devtools qui
n’existent qu’à l’intérieur d’un serveur de développement en cours d’exécution.
`duxt report` est le troisième — les mêmes données en Markdown sur stdout. Il
est livré avec le paquet : étendre la couche suffit donc à en disposer.

```bash
pnpm exec duxt report            # Markdown
pnpm exec duxt report --json     # les mêmes données, non rendues
pnpm exec duxt report --help     # ce qu'il accepte et ce qu'il renvoie
```

Il lit l’`app.config.ts` du site, les artefacts à côté et les pages depuis le
cache d’analyse de Content — **aucun serveur**, il s’exécute donc en CI et dans
un pipe. Il sort en `1` sur une erreur et en `0` sur des avertissements, ce qui
est la règle de gravité que suit la compilation, et en `2` lorsqu’il n’a pas
compris la ligne de commande — un constat sur votre invocation, pas sur votre
documentation.

Trois panneaux n’ont pas d’équivalent ici, et délibérément : l’index de
recherche, le débogueur de chemins et la liste du cache sont des questions sur
un processus en cours d’exécution, et y répondre depuis l’extérieur reviendrait
à deviner.

::callout{type="warning" title="Il rapporte la dernière analyse, pas cet instant"}
Les pages viennent du cache de Content : une page écrite depuis la dernière
compilation ou la dernière exécution en développement n’y est donc pas. Lancez
l’une ou l’autre d’abord — le rapport le dit clairement quand il ne trouve aucun
cache, plutôt que de signaler chaque collection comme vide.
::

## Ce que les vérifications savent et pas vous

- **Une collision de dossiers** n’est un problème que tant qu’un préfixe est
  actif. Un dossier de documentation nommé `v2` est correct sur un site non
  versionné et inaccessible sur un site versionné, car le préfixe l’emporte. La
  compilation sait de quel cas il s’agit, et le message nomme le fichier et
  propose la correction.
- **Un lien est résolu comme le thème le résout.** Un chemin absolu écrit dans
  une page est relatif à la source de cette page : il est donc essayé d’abord
  sous le préfixe de la source, puis nu. Un vérificateur ne faisant que la forme
  nue signalerait chaque lien correct d’un site multi-dépôts.
- **Un lien est résolu dans sa propre langue.** Chaque langue d’une source sert
  des chemins de contenu identiques — la locale se place devant l’URL, pas dans
  l’arbre de contenu — et les ancres dérivent du texte des titres : un titre
  allemand a donc une ancre allemande. Une vérification résolvant par le seul
  chemin confronterait un lien allemand à des titres anglais et signalerait
  chaque ancre correcte d’un site traduit. Un lien vers une page qu’une langue
  ne porte pas se replie sur l’original, ce que fait aussi le site.
- **Les URL externes ne sont pas vérifiées.** En vérifier une mettrait le réseau
  dans la compilation, et un hôte tiers injoignable n’est pas une raison pour
  que votre documentation ne soit pas publiée.

## Écrivez les liens en chemins de documentation absolus

`/concepts/sources`, et non `../2.concepts/2.sources.md`. Le préfixe sous lequel
une page est servie est décidé à la compilation : écrire des chemins sans
préfixe est donc ce qui permet au même Markdown de servir `/guides/deploying`
sur un site et `/acme/v2.0.0/guides/deploying` sur un autre. C’est aussi la
seule forme que voit le vérificateur de liens.