# Le même audit, en contrôle de build.

> apiglow audit en CI : faire échouer un build sur les constats du schéma, l'adopter avec une baseline, alimenter GitHub, GitLab et SARIF.
>
> Source: https://apiglow.dev/fr/docs/audit-ci/index.md


`apiglow audit` lance l'[audit de schéma](/fr/docs/audit/) en ligne de commande. C'est le même moteur et les mêmes 137 règles que la page `#/audit`, et le même rapport en sortie. La commande ajoute seulement ce qu'un pipeline réclame autour : un code de sortie, des rapports qu'une machine ou une plateforme de CI sait lire, et une baseline pour qu'une API existante adopte le contrôle sans d'abord solder tout son passé.

Elle est livrée dans le paquet npm, sous la même licence MIT, et tourne sur Node 24 ou plus récent, comme le [bake](/fr/docs/seo/). Pas de compte, pas de service, pas de jeu de règles à écrire.

## La lancer

```bash
npx apiglow@0.3.0 audit openapi.yaml
```

Le rapport part sur stdout. Tout ce qui concerne le passage — chaque contrôle, le verdict, les avertissements — part sur stderr :

```text
PASS  --fail-on error: 0 finding(s) at this severity or above
Audit passed
```

**Épinglez la version.** Une nouvelle version peut ajouter une règle, et une règle ajoutée sous un pipeline, c'est un build qui casse sans que personne n'ait rien changé. Montez l'épingle exprès, comme vous montez le tag du CDN.

Ce que la commande accepte :

- **Un schéma** — un chemin ou une URL, en JSON ou en YAML, valide ou non. Un fichier abîmé a quand même son rapport (voir le [code de sortie](#le-code-de-sortie)).
- **Plusieurs schémas** — plusieurs chemins, ou un motif entre guillemets pour que ce soit la commande qui l'étende et non le shell : `'apis/**/openapi.{yaml,json}'`. Chaque fichier est audité comme sa propre spec, sous son chemin relatif au répertoire de travail : les rapports et la baseline nomment le fichier, et ajouter un schéma ne décale jamais les autres. `node_modules` et `.git` ne sont jamais parcourus. Un motif qui ne trouve rien donne un avertissement ; `--fail-on-unmatched-globs` en fait un arrêt.
- **`--config apidoc.config.json`** — l'objet JSON que votre page place dans `#api-doc-config`. L'audit porte alors sur ce que la documentation montre vraiment : ses [overlays](/fr/docs/schema-support/) appliqués, ses opérations cachées marquées comme telles, chaque spec d'une installation [multi-spec](/fr/docs/multi-spec/). Les fichiers que la configuration nomme se lisent depuis son propre répertoire, comme le [bake](/fr/docs/seo/) les lit.

## Ce qui fait échouer le build

Quatre contrôles, chacun sur sa propre ligne `PASS` / `FAIL`. Un seul contrôle en échec fait échouer le passage.

| Option | Échoue quand | Par défaut |
|---|---|---|
| `--fail-on error\|warning\|info\|none` | un constat de cette sévérité, ou d'une pire, est présent | `error` |
| `--min-grade A\|B\|C\|D\|F` | la note globale est en dessous | désactivé |
| `--min-score 0-100` | le score global est en dessous | désactivé |
| `--baseline <fichier>` | la baseline liste des constats qui n'existent plus | actif avec une baseline |

Quand `--fail-on` échoue, stderr liste les constats responsables — les vingt premiers, chacun en `fichier:ligne:colonne`, puis un décompte :

```text
FAIL  --fail-on warning: 14 finding(s) at this severity or above
  warning responses-success — openapi.yaml:2149:9 · GET /failures/forbidden · /paths/~1failures~1forbidden/get/responses
  warning oauth-legacy-flows — openapi.yaml:2924:11 · components.securitySchemes.petstore_auth · …
```

Ces lignes de la commande restent en anglais quelle que soit la langue du rapport.

### Le code de sortie

| Code | Signification |
|---|---|
| `0` | tous les contrôles sont passés |
| `1` | un contrôle a échoué |
| `2` | l'audit n'a pas pu tourner : une option inconnue, une valeur hors bornes, un schéma injoignable, une configuration de règles illisible |

Chaque option est validée avant le moindre chargement : une faute de frappe dans un seuil ne coûte ni un téléchargement, ni un « pas de seuil » silencieux.

Un fichier que la commande peut ouvrir a toujours son rapport, aussi abîmé soit-il. Une clé en double, une tabulation en guise d'indentation, une virgule de trop, un fichier tronqué : chacun devient un constat `document-syntax` à sa ligne et sa colonne, et le reste du fichier est noté quand même. Un fichier sans aucune description OpenAPI — vide, une liste, du Swagger 1.x, de l'OpenAPI 4 — devient un constat `document-openapi`. Les deux sont des erreurs : le `--fail-on error` par défaut fait donc échouer le passage avec le code `1`. Le code `2` est réservé à un schéma qu'on n'a pas pu atteindre.

## L'adopter sur une API existante

Une API qui a des années d'histoire ne passera pas le premier jour, et n'a pas à le faire. Une **baseline** enregistre les constats que l'équipe a vus et acceptés. Versionnée à côté du schéma, elle fait que `--fail-on` ne compte que les constats qu'elle ne liste pas :

```bash
# une fois : enregistrer l'existant
npx apiglow@0.3.0 audit openapi.yaml --write-baseline audit-baseline.json

# en CI : n'échouer que sur la nouveauté
npx apiglow@0.3.0 audit openapi.yaml --baseline audit-baseline.json
```

Son comportement :

- **Un constat s'identifie par sa règle et son pointeur JSON** — les deux choses qui survivent à une modification sans rapport. Les messages et les numéros de ligne, non : un paramètre renommé réécrit les deux.
- **Elle compte, ce n'est pas un ensemble.** Un pointeur listé deux fois couvre deux occurrences ; une troisième est nouvelle.
- **La note et le score l'ignorent.** Ils décrivent le document. Une baseline accepte des constats, pas une note plus basse.
- **Ce n'est pas une liste d'exclusion.** Chaque constat reste dans le rapport ; en JSON, les connus portent `known: true`.
- **Le fichier est trié de bout en bout.** Le réécrire sur un schéma inchangé le reproduit octet pour octet : un diff de revue ne montre que ce qui a bougé.

```json
{ "format": "apiglow-audit-baseline", "version": 1,
  "specs": { "default": { "parameter-described": ["/paths/~1pets/get/parameters/0"] } } }
```

**Les entrées périmées font échouer le passage.** Quand un constat est corrigé, son entrée ne correspond plus à rien. Laissée là, elle masquerait un jour un nouveau constat tombé sur le même pointeur — le passage échoue donc et la liste. `--prune-baseline <fichier>` réécrit la baseline sans ses entrées périmées (ce peut être le fichier `--baseline` lui-même) et passe. `--allow-stale-baseline` se contente de les lister.

Un fichier qui n'est pas une baseline écrite par cette version arrête le passage avec le code `2`. Il n'est jamais lu comme vide, ce qui ferait échouer chaque constat, ni comme couvrant tout, ce qui laisserait passer une régression.

## Les rapports

| `--format` | Pour | Ce que c'est |
|---|---|---|
| `text` | un terminal | le rapport console, replié par règle comme la page ; le format par défaut |
| `markdown` | une personne | l'export Markdown de la page — un commentaire de pull request, un résumé de job GitHub |
| `json` | un script ou un agent | le rapport complet, dans un contrat versionné |
| `sarif` | le code scanning de GitHub | SARIF 2.1.0 : les constats s'ouvrent et se ferment comme des alertes |
| `github` | une pull request GitHub | des commandes de workflow qui annotent le diff à la ligne |
| `codequality` | une merge request GitLab | GitLab Code Quality, pour le widget de merge request |

**Un passage, plusieurs rapports.** `--format` et `--output` règlent le rapport sur stdout. `--report <format>=<fichier>`, répétable, écrit un rapport de plus par occurrence, depuis le même passage — le JSON pour un script, le Markdown pour le résumé du job, sans auditer deux fois :

```bash
npx apiglow@0.3.0 audit openapi.yaml --format github \
  --report markdown="$GITHUB_STEP_SUMMARY" --report sarif=audit.sarif
```

**Chaque constat est placé.** Un schéma lu depuis un fichier donne à chaque constat son `fichier:ligne:colonne` — l'endroit où son nœud se trouve dans le fichier que vous éditez, en suivant les `$ref` jusque dans d'autres fichiers. Terminaux et éditeurs en font un lien, et les trois formats de CI s'en servent pour poser le constat sur la bonne ligne.

**Ce qu'un rapport liste**, dans tous les formats :

- `--min-severity error|warning|info` garde les constats de cette sévérité ou pire. Le schéma REST de GitHub donne quelque 5 000 constats, dont 4 000 notes.
- `--only-new`, avec `--baseline`, ne garde que les constats que la baseline ne liste pas.

Aucun des deux ne change le verdict : les comptes, les scores, la note et chaque contrôle lisent toujours le rapport entier. Le texte et le Markdown disent combien de constats chaque filtre a laissés de côté. Un `--fail-on` moins sévère que `--min-severity` est refusé, puisqu'un job échouerait sur des constats que son rapport ne montre pas.

**`--language fr`** écrit le rapport en français : messages, justifications, correctifs. Les lignes de la commande elle-même, sur stderr, restent en anglais.

### Le rapport JSON

Ce que lit un script ou un agent. `format` et `version` nomment la forme, et tout changement de forme incrémente `version` : un consommateur peut refuser ce qu'il ne connaît pas.

```json
{ "format": "apiglow-audit-report", "version": 1,
  "tool": { "name": "apiglow", "version": "0.3.0" },
  "passed": false,
  "specs": [{ "id": "default", "source": "openapi.yaml", "passed": false,
              "gates": [{ "gate": "fail-on", "threshold": "error", "actual": 2, "passed": false }],
              "report": { "…": "…", "categories": [{ "findings": [{ "fingerprint": "9f3c…" }] }] } }],
  "rules": { "parameter-described": { "category": "completeness",
             "label": "…", "message": "…", "why": "…", "fix": "…" } } }
```

- Chaque constat porte une **empreinte** qui reste la même d'un passage à l'autre tant que le document change autour de lui. C'est sur elle que s'appuie une plateforme de CI ou un agent qui suit un constat.
- `rules` porte, une seule fois, les textes de chaque règle déclenchée — son libellé, pourquoi elle compte, comment corriger — si bien qu'un consommateur qui explique un constat n'a besoin de rien d'autre que le fichier.

## La configuration des règles

Éteignez une règle, ou changez sa sévérité, pour tout le document ou sous certains chemins seulement. Le bloc est celui que lit la page : la note publiée et la note de CI concordent.

```json
{
  "audit": {
    "rules": { "property-described": "off", "parameter-described": "error" },
    "overrides": [
      { "paths": ["/paths/~1legacy~1*"], "rules": { "response-example": "off" }, "reason": "frozen legacy API" }
    ]
  }
}
```

La CLI le lit dans la configuration passée par `--config`, ou dans `--audit-config <fichier>` — un fichier JSON qui ne contient que le bloc, pour un passage sans configuration de page, ou qui doit noter autrement que la page publiée. Le [guide de l'audit](/fr/docs/audit/#configurer-les-règles) détaille l'ordre de priorité.

Deux écarts avec la page. Une entrée que la CLI ne sait pas lire arrête le passage avec le code `2`, chaque entrée nommée : un pipeline ne passe jamais sur une configuration que personne n'a écrite. Et `features.audit: false` ne s'applique pas — ce réglage retire la page de la documentation publiée, alors qu'un auteur qui lance la commande a demandé le rapport.

Une note calculée avec l'un de ces réglages porte la mention **Règles personnalisées** partout où elle apparaît : elle ne passe jamais pour la note par défaut.

## Le réseau

Un schéma, un `$ref` externe ou un overlay derrière une URL sont téléchargés. Deux options rendent ça prévisible dans un job de CI :

- `--fetch-timeout <secondes>` (30 par défaut) : un hôte qui accepte la connexion et ne répond jamais fait échouer le job dans sa première minute, pas au délai maximal de la plateforme.
- `--offline` refuse tout téléchargement et arrête le passage avec le code `2` — même celui qui, sinon, serait ignoré avec un avertissement, comme un overlay. Un job censé rester hors réseau ne passe jamais sur un autre document.

## Les règles, depuis le terminal

```bash
npx apiglow@0.3.0 audit --explain oauth-legacy-flows --language fr  # une règle : catégorie, sévérité, pourquoi, comment corriger
npx apiglow@0.3.0 audit --list-rules                                # toutes les règles, en JSON
```

Les deux respectent `--language` et refusent tout le reste sur la ligne : un passage qui aurait l'air d'un audit et imprimerait une règle tromperait quiconque lit son code de sortie.

## Les recettes

### GitHub Actions

L'action [`apiglow/audit-action`](https://github.com/apiglow/audit-action) lance la commande en une étape : des annotations sur le diff de la pull request, le rapport Markdown dans le résumé du job, et du SARIF vers le code scanning quand vous le demandez. Son tag est la version de la CLI qu'elle lance.

```yaml
on: pull_request

jobs:
  schema-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: apiglow/audit-action@v0.3.0
        with:
          schema: openapi.yaml
          baseline: audit-baseline.json
```

| Entrée | Par défaut | |
|---|---|---|
| `schema` | — | Chemin, URL ou motif entre guillemets ; un par ligne pour plusieurs |
| `config` | — | La configuration JSON de la page, à la place de `schema` |
| `baseline` | — | Un fichier de baseline : seuls les constats qu'il ne liste pas font échouer le job |
| `fail-on` | `error` | `error`, `warning`, `info` ou `none` |
| `min-severity` | `info` | Ne lister que les constats de cette sévérité ou pire |
| `annotations` | `true` | Annoter le diff |
| `summary` | `true` | Écrire le rapport Markdown dans le résumé du job |
| `sarif` | `false` | Envoyer le SARIF au code scanning — le job a besoin de `security-events: write` |
| `args` | — | Toute autre option : `--min-grade B --language fr` |
| `version` | celle de l'action | La version d'`apiglow` à lancer |
| `node-version` | `24` | La version de Node.js sur laquelle la lancer |

Ses sorties sont `passed`, le chemin du `report` JSON, et celui du fichier `sarif` quand il y en a un.

Le même job à la main, chaque pièce en vue :

```yaml
permissions:
  contents: read
  security-events: write
steps:
  - uses: actions/checkout@v7
  - uses: actions/setup-node@v7
    with:
      node-version: 24
  - name: Schema audit
    run: |
      npx --yes apiglow@0.3.0 audit openapi.yaml --baseline audit-baseline.json \
        --format github --report markdown="$GITHUB_STEP_SUMMARY" --report sarif=audit.sarif
  - if: always()
    uses: github/codeql-action/upload-sarif@v4
    with:
      sarif_file: audit.sarif
```

GitHub montre dix annotations de chaque type par étape et jette le reste sans prévenir : les nouveaux constats passent donc en premier, et une dernière notice dit combien ont été laissés de côté. Le code scanning lit au plus 25 000 résultats par passage ; au-delà, les nouveaux constats passent d'abord, puis les plus sévères.

### GitLab CI

Les constats dans le widget de merge request, le rapport JSON gardé comme artefact :

```yaml
schema-audit:
  image: node:24
  script:
    - npx --yes apiglow@0.3.0 audit 'apis/**/openapi.yaml'
        --baseline audit-baseline.json
        --report codequality=gl-code-quality-report.json --report json=audit.json
  artifacts:
    when: always
    reports:
      codequality: gl-code-quality-report.json
    paths: [audit.json]
```

C'est l'empreinte qui permet au widget de distinguer les nouveaux constats des constats résolus.

### pre-commit

Un hook local. `pass_filenames: false`, parce que la commande nomme son schéma elle-même — si on lui passait les fichiers indexés, elle n'auditerait qu'eux, sous des identifiants que la baseline ne connaît pas. Toute modification de YAML le déclenche, puisqu'un `$ref` peut vivre dans un autre fichier.

```yaml
repos:
  - repo: local
    hooks:
      - id: apiglow-audit
        name: Schema audit
        entry: npx --yes apiglow@0.3.0 audit openapi.yaml --baseline audit-baseline.json
        language: system
        files: \.ya?ml$
        pass_filenames: false
```

### Les ruptures de contrat

Hors périmètre, à dessein. L'audit note une version du document, et sa baseline répond à « qu'est-ce qui est nouveau », pas à « le contrat a-t-il cassé ». [oasdiff](https://github.com/oasdiff/oasdiff) compare deux versions ; lancez-le à côté de l'audit :

```yaml
- uses: actions/checkout@v7
  with:
    fetch-depth: 0
- name: Breaking changes
  run: |
    git show "origin/$GITHUB_BASE_REF:openapi.yaml" > base.yaml
    docker run --rm -v "$PWD:/specs:ro" tufin/oasdiff:v1.32.1 \
      breaking /specs/base.yaml /specs/openapi.yaml --fail-on ERR
```

### Un agent qui corrige le schéma

Donnez-lui le rapport JSON. Chaque constat est placé en `fichier:ligne:colonne`, le message, la justification et le correctif de sa règle sont dans `rules`, et l'empreinte survit aux modifications autour. La boucle : auditer, corriger, ré-auditer jusqu'à ce que chaque contrôle passe ; avec une baseline, `--only-new` garde l'agent sur ce qu'une modification a introduit.

```bash
npx apiglow@0.3.0 audit openapi.yaml --format json --min-severity warning
```

Le paquet npm livre cette boucle sous forme de skill d'agent : `skills/apiglow-audit/SKILL.md`. Copiez le dossier depuis `node_modules/apiglow/skills/` là où votre agent lit ses skills — `.claude/skills/` pour Claude Code, par exemple. Au-delà des commandes, la skill dit ce qu'un agent ne doit pas faire de lui-même : inventer ce que fait l'API pour remplir une description ou un exemple, ou faire passer le contrôle en éteignant une règle, en écrivant une baseline ou en baissant `--fail-on`. Chacune de ces décisions revient au propriétaire.

## Toutes les options

| Option | Ce qu'elle fait |
|---|---|
| `<spec>…` | Chemin, URL ou motif entre guillemets du schéma ; plusieurs sont audités chacun sous son chemin |
| `--config <fichier>` | Auditer ce que la documentation montre : overlays, opérations cachées, configuration des règles, chaque spec |
| `--audit-config <fichier>` | La configuration des règles seule ; remplace le bloc `audit` de la configuration |
| `--fail-on-unmatched-globs` | S'arrêter quand un motif ne trouve aucun fichier |
| `--fail-on <sévérité>` | Échouer sur un constat de cette sévérité ou pire : `error` (par défaut), `warning`, `info`, `none` |
| `--min-grade <lettre>` | Échouer sous cette note |
| `--min-score <0-100>` | Échouer sous ce score |
| `--baseline <fichier>` | Ne compter que les constats que la baseline ne liste pas ; échouer sur les entrées périmées |
| `--write-baseline <fichier>` | Enregistrer tous les constats actuels, et passer |
| `--prune-baseline <fichier>` | Avec `--baseline` : la réécrire sans ses entrées périmées |
| `--allow-stale-baseline` | Avec `--baseline` : lister les entrées périmées sans échouer |
| `--format <format>` | `text` (par défaut), `markdown`, `json`, `sarif`, `github`, `codequality` |
| `--output <fichier>` | Écrire le rapport dans un fichier plutôt que sur stdout |
| `--report <format>=<fichier>` | Écrire un rapport de plus depuis le même passage ; répétable |
| `--min-severity <sévérité>` | Ne lister que les constats de cette sévérité ou pire — le verdict ne change pas |
| `--only-new` | Avec `--baseline` : ne lister que les constats qu'elle ne connaît pas |
| `--language <code>` | `en` (par défaut) ou `fr`, pour les textes du rapport |
| `--offline` | Ne jamais télécharger ; une URL où que ce soit arrête le passage |
| `--fetch-timeout <secondes>` | Attente maximale pour chaque document téléchargé (30 par défaut) |
| `--explain <règle>` | Imprimer une règle, ne rien auditer |
| `--list-rules` | Imprimer toutes les règles en JSON, ne rien auditer |

