Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Outillage cargo xtask — gates qualité gravés

Ce document décrit la crate d’outillage xtask : elle grave en outillage déterministe et réutilisable les gates qualité qu’on vérifiait à la main — en particulier ceux qui nous ont mordus (numéros de syscalls par arche, invariant « couvrable vide », dérive du décompte d’exceptions).

Statut. xtask est de l’outillage de développement, pas du code Air livré. Il est membre du workspace (pour que cargo xtask <gate> se résolve via l’alias .cargo/config.toml), mais exclu de la mesure de couverture (--exclude xtask) et non soumis au 100 % des couches 0/1 (carve-out esprit ADR-030). Il reste linté (clippy --workspace -D warnings), testé (cargo test --workspace exécute ses tests de parseurs) et audité (deny/audit/machete). Il n’introduit aucune dépendance externe (std seulement) : le Cargo.lock du produit et la reproductibilité (ADR-025) restent intacts.

Invocation

cargo xtask barrier [--no-coverage]        # toute la barrière, un seul point d'entrée
cargo xtask couvrable-vide [--strict] [--no-root]
cargo xtask check-syscalls
cargo xtask check-layers
cargo xtask audit-exceptions [--strict]
cargo xtask repro [--keep]

Chaque sous-commande accepte --json (sortie machine) et rend un code de sortie : 0 = vert (ou signalements non bloquants), 1 = violation, 2 = erreur d’usage / interne. Conçu pour la CI comme pour Claude Code.

Les cinq gates

1. barrier — un seul point d’entrée pour la barrière

Enchaîne et agrège : fmt (stable et nightly), clippy --workspace --all-targets -D warnings, cargo test --workspace, couverture lignes + branches en root (planchers ADR-031 : lignes ≥ 96, branches ≥ 78), cargo audit, cargo deny check, cargo machete, présence d’un // SAFETY: sur chaque bloc unsafe { … } du socle, cohérence Cargo.toml ↔ DEPENDENCIES.md. Récapitulatif structuré pass/fail + chiffres.

--no-coverage saute les étapes root (boucle de dev rapide sans sudo).

2. couvrable-vide — anti-gaming (le plus important)

Lance llvm-cov (root), parse les lignes non couvertes du code production et les croise avec COVERAGE-EXCEPTIONS.md.

  • Gate DUR (mécaniquement décidable) : le plancher de couverture (lignes ≥ 96, branches ≥ 78). C’est le filet anti-gaming central — la couverture brute ne peut pas chuter en douce.
  • Assist (advisory / --strict) : l’invariant « couvrable VIDE » (toute ligne rouge atteignable ⊆ registre) n’est pas mécaniquement vérifiable — la reachability est une décision humaine (cf. CI.md) et les fichier:ligne du registre dérivent. La réconciliation se fait donc par symbole (fonction englobante mentionnée au registre) ou par proximité de ligne (±40, pour absorber la dérive). Les lignes non rapprochées sont signalées ; --strict les durcit en violations (et fait échouer aussi sur un fichier production non couvert sans aucune section au registre). Les références du registre au-delà du fichier sont signalées comme orphelines.

--no-root mesure sans sudo (couverture partielle, ebpf plafonné — diagnostic seulement).

3. check-syscalls — numéros déclarés vs headers uapi (ce qui nous a piégés)

Extrait les numéros de syscalls déclarés dans air-sys-syscall (commentaires // SAFETY: SYS_<nom> (<arche> = <N>) et constantes d’arche const SYS_… / const NR_IO_URING_…) et les compare, pour chaque arche, aux headers uapi :

ArcheHeader (overridable)
x86_64/usr/include/x86_64-linux-gnu/asm/unistd_64.h (XTASK_UNISTD_X86_64)
aarch64/usr/include/asm-generic/unistd.h (XTASK_UNISTD_AARCH64) — aarch64 utilise la table générique

Le header générique est présent sur les deux exécuteurs, donc les deux arches sont vérifiables depuis n’importe lequel. Tout écartVIOLATION (exit ≠ 0), avec le détail syscall : code_déclaré vs header. Si un header manque pour une arche, c’est signalé clairement (et ignoré), jamais un échec silencieux. La résolution des indirections (__NR_mmap → __NR3264_mmap → 222) est gérée.

4. audit-exceptions — format/taxonomie ADR-035 du registre

Valide chaque entrée de COVERAGE-EXCEPTIONS.md :

  • catégorie ∈ {STRUCTURAL, PRIVILEGE, FEATURE-KERNEL, CHILD-EXIT, DEFERRED-TOOLING} ; aucun TEST-HARNESS au registre production (ADR-035 §2) ;
  • justification non vide ;
  • fichier:ligne existant : tout .rs cité doit exister, toute ligne citée ≤ la taille du fichier, et tout symbole entre backticks doit être présent dans le fichier cité (anti-dérive — résout la dette de re-sync notée au sceau) ;
  • récapitulatif recomputé à partir des rangées et confronté au récapitulatif affiché — toute divergence est une VIOLATION (anti-dérive du décompte, le travers dénoncé par l’audit 053).

Les entrées DEFERRED-TOOLING sont signalées (dette à couvrir). --strict durcit les orphelines fichier:ligne / symbole en violations.

5. repro — double-build bit-pour-bit (ADR-025)

Construit le même arbre deux fois en environnement contrôlé (toolchain pinné, SOURCE_DATE_EPOCH fixe, --remap-path-prefix sur la source / le target de chaque build / CARGO_HOME), dans deux target distincts remappés vers le même préfixe logique, puis diffe bit-pour-bit l’ensemble des *.rlib du socle. Toute divergence ⇒ exit ≠ 0, avec le détail des fichiers non déterministes (premier octet divergent). C’est l’attestation de reproductibilité visée par l’ADR-025 (le sceau couche 0 n’avait qu’un constat same-host non outillé). --keep conserve target/repro pour inspection.

check-layers — doctrine de couche mécanisée (ADR-052 / ADR-077)

La doctrine de couche — « seule la couche 1 consomme la couche 0 » ; « les toits C-ABI (air-libc-*, *-capi) bindent la couche 1, jamais la 0 en direct » (ADR-077) ; air-sys-types transverse (ADR-052 D6) — était tenue à la main / en revue. Ce gate la mécanise : chaque crate air-* déclare sa couche dans [package.metadata.air] (layer = 0|1|2), zone ignorée par Cargo (même précédent que [package.metadata.cargo-machete]) donc inerte ; le gate lit ces déclarations et prouve que le graphe de dépendances les respecte :

  • (a) toute crate air-* déclare layer (sinon « crate sans couche ») ;
  • (b) pas de dépendance montante (M ≤ N) ;
  • (c) pas de saut (M ≥ N-1) : un toit layer=2 ne touche pas la couche 0 en direct — il passe par la couche 1.

air-sys-types est exclu de (b)/(c) (transverse). Les arêtes air-* cible-only ([target.'cfg(…)'.dependencies]) qui violeraient les règles — p.ex. l’escape hatch syscall de la couche 0 cadré par ADR-087, absent du graphe hôte livré — sont signalées en advisory (non bloquant), pas rouge. Branché dans barrier.

Intégration CI

Le job supply-chain du workflow ci appelle cargo xtask check-syscalls et cargo xtask audit-exceptions (rapides, sans root) en source unique des gates correspondants. La couverture reste mesurée par les étapes llvm-cov existantes du job test-coverage (en root, ADR-031), inchangées hormis l’ajout de --exclude xtask. couvrable-vide --strict, repro et barrier sont disponibles localement / à la demande (ils rejouent la couverture en root et ne sont pas câblés en dur pour ne pas déstabiliser la CI ; ils pourront l’être quand le registre sera ré-ancré par symbole).

Pourquoi un gate buggé est pire que pas de gate

Un gate qui rougit au hasard (faux positif) ou qui passe à tort (faux négatif) donne une fausse confiance. C’est pourquoi les parseurs de xtask (couvrable-vide, check-syscalls, audit-exceptions, et les helpers de barrier/repro) sont testés sur entrées synthétiques (cargo test --workspace), et pourquoi la frontière entre gate dur (mécaniquement décidable : planchers, numéros de syscalls, taxonomie, cohérence du décompte) et assist advisory (reachability, dérive fichier:ligne) est explicite.


Licence : MPL 2.0