ADR-103 — Discipline de build linux-air : tout exécutable Air est lié à la std linux-air, jamais à la std gnu
Statut : Accepté (2026-07-24, décision BDFL). RFC de direction
(ADR-015).
S’appuie sur ADR-088 (std Rust sur
PAL safe couche 1, sans libc C), ADR-050 (cible
*-unknown-linux-air), ADR-048
(couche livrée no_std), ADR-090
(fermeture std par rustc-dep-of-std).
Catégorie : Gouvernance de build + doctrine de livraison. N’altère aucune couche scellée ; impose une cible de livraison et un marquage par crate.
Contexte
[ADR-088] a fait de la std linux-air — la std Rust portée sur le PAL safe
d’Air (target_os = "air", bindant la couche 1 Rust safe, sans libc C,
static-pie, zéro glibc) — la bibliothèque standard cible d’Air. [ADR-050] en
définit la cible (x86_64/aarch64-unknown-linux-air). La raison d’être du projet
(charte ; [ADR doctrine userland Rust safe]) est un userland Rust safe devant le
kernel Linux, sans la frontière C unsafe d’une libc.
Constat (2026-07-24). Rien n’IMPOSE aujourd’hui que les exécutables Air soient
produits pour cette cible. Les premiers binaires déployés (air-account, air-ssh,
air-sshd sur speedy/carbon/raspi-srv-2) l’ont été par un simple
cargo build --release avec la toolchain hôte par défaut — cible
*-unknown-linux-gnu, donc liés à la std gnu (glibc). C’est un écart à la
doctrine : ces binaires embarquent une libc C et son runtime, exactement ce que le
PAL safe élimine.
Directive BDFL. « Tous les exécutables Air (air-account, air-ssh, air-sshd, et les
suivants) doivent être compilés en linux-air, jamais gnu. Un exécutable Air ne doit
jamais dépendre de *-unknown-linux-gnu, uniquement de *-unknown-linux-air. On ne
doit plus pouvoir créer de nouveau crate sur le projet sans qu’il soit identifié
“linux-air”. »
Décision
1. Les exécutables livrés sont *-unknown-linux-air — exclusivement
Tout binaire Air destiné à s’exécuter sur une machine Air est produit uniquement
pour la cible canonique *-unknown-linux-air (std linux-air, PAL couche 1, sans
libc C, static-pie, 0 NEEDED, aucun interpréteur ELF). Aucun artefact livré ne
dépend de la std gnu. Un binaire lié à glibc n’est pas un livrable Air valide.
2. La std gnu reste un banc de test hôte, jamais un livrable
La barrière CI (tests unitaires/intégration/doc, couverture 100 % couches 0/1,
fuzz, clippy -D warnings, loom) continue de tourner sur *-unknown-linux-gnu :
c’est un harnais de développement (rapidité, outillage llvm-cov/cargo-fuzz
mûr sur gnu ; la cible os=air exige nightly + build-std). La std gnu n’est jamais
un artefact distribué. La conformité fonctionnelle des exécutables est prouvée
séparément par un build + run linux-air (lane dédiée, §5). Les crates de
bibliothèque sont donc buildées deux fois : gnu (test) et linux-air
(livraison, via les bins qui les tirent).
Amendement du 2026-07-25 — la std gnu cesse d’être un banc de test acceptable
Décision BDFL (2026-07-25) : aucun code Rust d’Air, tests compris, ne doit
s’exécuter contre la glibc — c’est-à-dire contre la std livrée par la toolchain.
Tous les tests doivent s’exécuter contre notre std (*-unknown-linux-air).
Le §2 ci-dessus autorisait la std gnu comme banc de test. Cet amendement retire cette autorisation. Le §2 reste vrai sur un point — la std gnu n’est jamais un artefact distribué — mais sa conclusion (« harnais de développement » pérenne) est caduque : c’est un état transitoire à résorber, plus une position tenable.
Constat qui a déclenché la décision (inventaire du 2026-07-25) : la totalité de la
suite tourne aujourd’hui sur gnu — 3 417 tests unitaires + 268 tests d’intégration
sur 69 crates, via trois invocations CI sans --target (cargo test --workspace
dans test-coverage et aarch64-native, cargo llvm-cov --workspace). Seul le harnais
rt/ (selftest + démonstrateurs hello-*) s’exécute réellement sur notre std, et ce
sont des exécutables de preuve, pas une suite de tests.
Ce que l’écart a coûté, concrètement : le test air-process
drop_to_user_resolves_and_drops_under_root s’interbloque sous couverture. Il
clone3 puis alloue dans l’enfant. Sur notre cible, air_alloc::reset_after_fork
(ADR-056 D8) remet le futex de l’arène à UNLOCKED et le cas est résolu ; sur l’hôte
gnu, l’allocateur est celui de la glibc, clone3 en syscall brut court-circuite
ses gestionnaires pthread_atfork, et rien ne réinitialise ses verrous. Le défaut
n’existe que parce que le test tourne contre la glibc.
Conséquence : le chantier de complétion du PAL (lancé sur directive BDFL du même
jour) prend pour critère de réussite l’exécution de la suite de tests sur
*-unknown-linux-air. Cela suppose notamment que libtest fonctionne sur notre std
(std::process, std::thread, std::io, affichage, codes de sortie) et que
cargo test --target *-unknown-linux-air sache exécuter les binaires produits.
3. Marquage obligatoire par crate — un crate ne peut plus être créé sans cible
Chaque crate du workspace déclare son rapport à la cible linux-air dans
[package.metadata.air] (déjà porteur de layer) :
kind = "bin"(ou la présence d’un[[bin]]) ⇒ le crate DOIT être buildable et exécutable en*-unknown-linux-air(il dépend d’air-std-entry, cf. §4) ; il ne gate jamais de logique livrée exclusivement surnot(target_os = "air").kind = "lib"⇒ le crate doit compiler en*-unknown-linux-air(aucun usage non-portable non gaté).- Un nouveau crate sans le marquage
[package.metadata.air](layer + intention de cible) est REFUSÉ par la barrière.
Enforcement : un gate cargo xtask check-target (branché dans la barrière, au
même titre que check-layers/check-dependencies) vérifie ces invariants. La
métadonnée est inerte pour Cargo (comme layer), lue par le gate.
4. Fondations techniques (ratifiées par cet ADR)
Construire un programme complet (pas seulement std + hello-std-air) sur
*-unknown-linux-air a requis trois corrections, prouvées conjointes (carbon,
x86_64 : air-account, arbre RustCrypto + capnp + io_uring, compile + link
static-pie 0 glibc + s’exécute exit 0) :
restricted_std—os=airajouté à l’allowlist delibrary/std/build.rs(air-pal.patch) : la std linux-air étant complète,airest une cible connue, ce qui débloque toute crate tierce utilisantstd(subtle/ RustCrypto,capnp…). (PR fix 2/3.).weak __air_rt_sigreturn_trampoline(air-sys-syscall) : sur cette cible,air-sys-syscallest compilé deux fois (closurestdviarustc-dep-of-stdet closure applicative) ⇒ le symbole à nom fixe serait dupliqué au link ;.weakautorise la coalescence. (PR fix 1/3.)air-std-entry: crate dédiée (hors closure std) fournissant le point d’entrée ELF_startque la std exporte comme pont__air_startmais ne pose pas. Tout binaire Air en dépend (use air_std_entry as _;). (PR fix 3/3.)
5. Packaging & CI linux-air
cargo xtask debproduit les exécutables pour*-unknown-linux-air(-Z build-std=std,panic_abort+ cible JSON + rust-src patché) — plus jamais gnu.- Une lane CI nightly linux-air build (et, sur runner de l’arche, exécute) les
binaires livrables des deux arches (
x86_64/aarch64-unknown-linux-air), en complément de la barrière gnu.
Conséquences
- Migration : les exécutables déployés (
air-account/air-ssh/air-sshd) sont recompilés linux-air et redéployés sur les 3 machines (remplacent les binaires gnu). - Discipline de création : tout nouveau crate déclare son intention de cible ; un crate bin non-linux-air-buildable est un échec de barrière, pas un choix.
- Double build des libs : coût CI accru (gnu pour la mesure, linux-air pour la livraison) — assumé (rigueur > vitesse).
- La std gnu ne disparaît pas : elle reste le banc de test hôte (couverture/fuzz), sans jamais être livrée.
Alternatives rejetées
- Tout migrer sur linux-air, y compris la barrière de test (couverture/fuzz sur
os=air) — l’outillagellvm-cov/cargo-fuzzest mûr sur gnu,os=airestrestricted/nightly/build-std; migrer la mesure ferait chuter la vélocité et la fiabilité du gate 100 %. Rejeté : gnu = banc de test, linux-air = livraison. - Statu quo (bins gnu) — viole la doctrine userland Rust safe sans libc C ([ADR-088], charte). Rejeté (BDFL).
- Marquage implicite (heuristique sur les deps) au lieu d’une métadonnée explicite
— fragile, contournable, non auditable. Rejeté au profit de
[package.metadata.air]explicite + gatecheck-target.