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

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 sur not(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) :

  1. restricted_stdos=air ajouté à l’allowlist de library/std/build.rs (air-pal.patch) : la std linux-air étant complète, air est une cible connue, ce qui débloque toute crate tierce utilisant std (subtle/ RustCrypto, capnp…). (PR fix 2/3.)
  2. .weak __air_rt_sigreturn_trampoline (air-sys-syscall) : sur cette cible, air-sys-syscall est compilé deux fois (closure std via rustc-dep-of-std et closure applicative) ⇒ le symbole à nom fixe serait dupliqué au link ; .weak autorise la coalescence. (PR fix 1/3.)
  3. air-std-entry : crate dédiée (hors closure std) fournissant le point d’entrée ELF _start que la std exporte comme pont __air_start mais 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 deb produit 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’outillage llvm-cov/cargo-fuzz est mûr sur gnu, os=air est restricted/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 + gate check-target.

Références

  • ADR-088 — std sur PAL safe couche 1, sans libc C.
  • ADR-050 — spec de la cible *-unknown-linux-air.
  • ADR-090 — fermeture std (rustc-dep-of-std).
  • ADR-048 — couche livrée no_std.
  • PRs fondations : .weak trampoline (#408), air-std-entry (#409), restricted_std (#410).