ADR-055 — air-thread/air-process en #![no_std] : crate air-env + spawn ergonomique gardé per-cible
Statut : Accepté (2026-06-30, ratifié par le BDFL). RFC de structure (ADR-015).
Implémente ADR-048 (couche 1 std-free) et
ADR-052 D7.3 (le spawn ergonomique migrera vers air-runtime).
Companion d’ADR-054 (qui a retiré icu de la fermeture
runtime) et d’ADR-050 (cible *-linux-air).
Catégorie : Architecture (layering, no_std). Toute évolution structurante = RFC.
Contexte
ADR-054 a levé le blocage icu de la fermeture air-runtime → *-linux-air. La re-sonde a
révélé le 2ᵉ mur : air-thread et air-process ne sont pas #![no_std] et fuient std :
- air-process (3 usages, tous
command.rs) :std::ffi::{CStr,CString},std::os::unix::ffi::OsStringExt,std::env::vars_os()(héritage d’environnement dansbuild_envp). Le reste travaille déjà en octets (ADR-054 D4). - air-thread :
sync.rs/channel.rs/runtime_primitivessont déjà no_std en code lib (std seulement sous#[cfg(test)]). Le blocage estthread.rs:AirThreadBuilder::spawnenrobestd::thread, délibérément (en-tête dethread.rs) :spawn(F: FnOnce()->T)exécute une closure arbitraire qui peut toucher du code glibc ; sur un thread Air sans TCB glibc-correct ce seraitunsound(corruption tas, errno croisé). Cette contrainte est celle de l’HÔTE (glibc), pas de la cible : sur*-linux-airil n’y a pas de glibc, c’estair-runtimequi fournit le TCB/TLS (ADR-052).
Deux obstacles structurels : (1) air-process a besoin de l’environnement mais ne peut pas
dépendre d’air-runtime (cycle : air-runtime → air-process pour exit_process) ; (2) le spawn
ergonomique Air-natif relève d’air-runtime (TCB/TLS) et d’un allocateur réel (boxer la
closure ; aujourd’hui placeholder AbortOnAlloc), donc indisponible immédiatement.
Décision
D1 — Nouvelle crate air-env (couche 1, no_std) : porteuse de l’environnement
Créer crates/air-env (couche 1, #![no_std], zéro dépendance — l’environnement est manipulé
en octets bruts (&[u8]/*const *const u8), sans aucun type transverse ; réconciliation étape 6 :
le libellé initial « deps air-sys-types seulement » était une borne haute, l’implémentation a
finalement ZÉRO dépendance) qui porte la table
d’environnement de processus : static ENVIRON: AtomicPtr, set_environ(envp) (appelé au
démarrage), get(&[u8]) -> Option<&'static [u8]>, vars() -> impl Iterator<Item=(&[u8],&[u8])>
(octets, Principe 3). Vit sous air-runtime ET air-process ⇒ aucun cycle.
air-runtime::env devient un mince ré-export/wrapper d’air-env (compat ; bootstrap appelle
air_env::set_environ). air-process::build_envp lit air_env::vars().
Amendement (2026-07-03, décision BDFL, pré-sceau
couche-1-v1.0). La borne « zéro dépendance » de D1 est relevée :air-env(etair-runtime) dépendent désormais d’air-base-corepour rendre les types chaîne-OSAirOsStr/AirOsString. Dépendance acyclique (air-base-core ne dépend que de la couche 0) et fondatrice (vocabulaire de base couche 1). Les signatures de lecture sont remodelées à leur forme finale scellable :get(&AirOsStr) -> Option<AirOsString>(copie possédée, race-safe façonstd::env::var_os— au lieu d’un&'static [u8]que réécrirait un futurset_var) etvars()en paires possédées ;air-runtime::argsreste&'static AirOsStr(argv immortel, zéro-copie). Motif : audit de sceau-readiness (Flag 1+2). Consigné dansDEPENDENCIES.md.
D2 — air-process passe #![no_std]
#![cfg_attr(not(test), no_std)] + extern crate std sous cfg(test). std::ffi::CStr →
core::ffi::CStr ; std::ffi::CString → alloc::ffi::CString. build_envp lit l’environnement
via air_env (la face hôte gnu peut rester sur std::env::vars_os derrière
#[cfg(not(target_env="air"))] si utile aux tests ; la face cible lit air_env). Plus aucun
std::os::unix.
D3 — air-thread passe #![no_std] ; thread.rs gardé per-cible
#![cfg_attr(not(test), no_std)]. sync.rs/channel.rs/runtime_primitives passent tels quels
(std uniquement sous cfg(test)). thread.rs (spawn ergonomique sur std::thread) est gardé
#[cfg(not(target_env="air"))] : il reste disponible sur l’hôte (gnu, sound sous glibc) et
n’est pas compilé pour *-linux-air. Le crate compile donc no_std pour la cible (surface =
sync/channel/primitives/erreurs). Le spawn ergonomique Air-natif (closure sur TCB Air) est un
chantier distinct d’air-runtime, gaté sur l’allocateur réel (ADR-052 D7.3) — hors de cet
ADR.
D4 — Portée : « compile » ≠ « run »
Après D1–D3, la fermeture air-runtime compile no_std pour *-linux-air (objectif étape 6,
à reprouver par la re-sonde). Exécuter réellement Command/build_envp/le spawn ergonomique
sur la cible reste bloqué par l’allocateur AbortOnAlloc (toute alloc abort) : c’est le
chantier suivant (allocateur Air réel, « malloc échoue plutôt que hang »). Un binaire Air qui
ne fait que démarrer + spawn discipliné (entrée extern "C", sans alloc) n’est pas concerné.
Conséquences
- Étape 6 (build) débloquée : plus de fuite
stddans la fermeture (à confirmer empiriquement). - Cycle évité par
air-env(sous les deux).air-runtime::env/air-processinchangés côté appelants (ré-export). air-threadsur l’hôte inchangé : le spawn ergonomique std reste pour gnu (tests, outils). Sur la cible, air-thread n’expose pas (encore) de spawn ergonomique — assumé, fourni plus tard parair-runtime+ allocateur.- Couverture :
air-env(couche 1) = 100 % ; lethread.rscfg-gaté n’est compilé que sur gnu (couvert là). Ne PAS toucher l’--ignore-filename-regex/--excludesans décision (ADR-035). - Dette d’exécution clarifiée : l’allocateur réel devient le prochain prérequis pour faire tourner (vs compiler) les features runtime.
Alternatives rejetées
- Environnement dans
air-base-core(au lieu d’air-env) : moins de crates, mais l’environ est un global mutable étranger au caractère « types purs » d’air-base-core. Rejeté pour la netteté (responsabilité unique). - air-process dépend d’air-runtime pour l’environ : cycle (
air-runtime → air-process). Rejeté. - Réimplémenter le spawn std-free maintenant (au lieu du cfg-gate) : exige résoudre le cycle
air-thread↔air-runtime et l’allocateur réel ; relève d’un RFC propre (le doc de
thread.rsle dit). Disproportionné pour débloquer l’étape 6. Déféré (chantier allocateur).
Mise en œuvre (esquisse, code headless après ratification)
crates/air-env: extraireENVIRON/set_environ/get/varsd’air-runtime::env;air-runtime::envré-exporte/wrappe ;bootstrapappelleair_env::set_environ. 100 % cov.air-process:#![no_std];core/alloc::ffi;build_envp→air_env(cfg face hôte). Tests/doctests adaptés. 100 % cov.air-thread:#![no_std];thread.rs→#[cfg(not(target_env="air"))]; reste tel quel. 100 % cov.- Re-sonde
*-linux-air(carbon x86 + raspi/CI) : fermetureair-runtimecompile sans std. - Doc : ADR-055, registre, SUMMARY, INDEX, DEPENDENCIES (air-env + maj process/thread), renvois.