Roadmap — libc C-ABI scopée OpenSSH (sur la couche 1 scellée)
✅ STATUT 2026-07-10 — M0–M4 livrés ; JALON M5 (toolchain
std-sur-Air) ATTEINT. La libc C-ABI compte ~256 symboles (Rust pur) ;stdlie et tourne contrelibair(zéro glibc),hello-stds’exécute on-target 2 arches (hello from std on air
- exit 0) — la toolchain Rust full-Air est prouvée (ADR-076).
⚠️ Recadrage BDFL (2026-07-10) — le jalon « compiler OpenSSH réel » est RETIRÉ. Cette roadmap a rempli son rôle : compiler OpenSSH n’était qu’une fonction de forçage (le consommateur C le plus exigeant, pour piloter la libc et révéler les additifs couche 1). Elle a donné une libc C-ABI de ~256 symboles et une toolchain
stdprouvée. On ne vise pas à faire tourner le C d’OpenSSH upstream sur Air. La vraie cible =air-sshd, notre serveur SSH full Rust (async io_uring, wire-compat OpenSSH, conf binaire — ADR-074). Le §6 « M5 — OpenSSH » ci-dessous est conservé comme historique ; la production courante passe àair-sshd. Statut vivant :etat-avancement.md.
But. Produire la libc C-ABI d’Air, bornée à ce dont OpenSSH a besoin (ADR-046/047), comme toit sur la couche 1 scellée (
couche-1-v1.0, re-scellée additivement jusqu’àcouche-1-v1.9). Jalon final =sshd+sshconfigurent, linkent et tournent sur Air. La libc sert de spec-de-certitude pour le PAL (std::sys) et de driver pour révéler les additifsv1.xde la couche 1.Ordre stratégique (décision BDFL) : libc d’abord (consommateur le plus exigeant, sur-ensemble des besoins du PAL) → PAL ensuite (sous-ensemble + delta Rust) → ABI C
air_*(riche/sécure/hors-normes) en couronnement.Priorité de production, dans cet ordre : SÉCURITÉ > QUALITÉ > PERFORMANCE (Principe 5 : sur-sécuriser puis mesurer, jamais l’inverse).
1. Périmètre & non-buts
- Cible unique : OpenSSH (sshd + ssh + ssh-keygen/agent). Rien de plus.
- Familles dans le scope (ADR-046, ~25-30) :
crt/startup,errno,stdio(FILE*),string/mem,stdlib(malloc/exit/getenv/qsort…),unistd/fcntl,stat/dirent,signal,pthread,network(socket/getaddrinfo),time,termios/pty,pwd/grp/shadow,setjmp/longjmp,misc(getopt/realpath/getpass/openpty/syslog/basename). - Non-buts (hors scope, différés) : locale complète (minimum vital seulement),
printf/scanfexotiques (positions%n$, locale numérique),aio, IPC SysV,mq,dlopen/ldso,wordexp/glob(rejetés RCE, ADR-046 D7), NSS/pam,iconvlarge. Ajoutés additivement si un besoin OpenSSH réel émerge. - Kernel = bible, pas POSIX : conformité fonctionnelle (SHOULD→MUST), pas conformité de certification. Voir §3.
2. Architecture
- Rust-first, shim mince. Chaque symbole libc = un
#[no_mangle] pub extern "C"fin au-dessus d’une brique couche 1 scellée (dual-face : la brique Rust est conçue 1 fois, consommée par la libc et le PAL). L’unsafe/FFI est assumé côté frontière C (C n’a pas de sûreté) ; l’implémentation derrière reste des appels couche 1 sûrs. - Structure de crates (à figer en M0, sur le modèle
air-base-capi) : des crates*-capipar famille (ex.air-stdio-capi,air-posix-io-capi…) agrégées en l’artefact finallibc(.so+.a) + objetscrt1.o/crti.o/crtn.o. - Architecture « logique mesurée / shims C-ABI exclus » (gravée dès M1-b, étendue
M1-c) — le POURQUOI : nos symboles
#[no_mangle] extern "C"(malloc,memcpy,fwrite…) surchargent les symboles du runtime de couverture LLVM et de l’allocateur du harnais ⇒ l’écriture du profilllvm-covéchoue (crate à 0 %). Règle absolue : une crate dont on veut MESURER la couverture ne lie AUCUNE crate exportant un symbole libc#[no_mangle](ni sien, ni transitif). On scinde donc : la LOGIQUE pure (politique d’allocation, moteurformat,FILE*bufferisé,atexit/getenv…) vit dans des crates sans#[no_mangle], donc mesurées à ~100 % (air-libc-fmt,air-libc-alloc,air-libc-stdio,air-libc-stdlib, …) ; les shims#[no_mangle] extern "C"minces qui délèguent vivent dansair-libc-capi(exclus du gate de couverture,--ignore-filename- regex). Les shims divergents (exit/abort/_Exit -> !) y vivent aussi (insondables par un test du gate, commeair-process/src/exit.rs). - crt / startup :
_start→__libc_start_main→main, câblé surair_runtime::start::bootstrap(déjà prouvé on-target, selftest exit 42) : TLS,environ/argv/envp,atexit,errno#[thread_local]. errno: per-thread viaair-runtime(déjà en place). Zéro globale libc (vision Air) ;errnoin-band, pas d’état caché.FILE*: struct C de la couche libc (au-dessus de la couche 1), bufferisée (_IOFBF/_IOLBF/_IONBF), enveloppant unAirFile/air-stdio(fd) + tampon + orientation + flags error/eof + verrou (flockfile). La couche 1 reste sans tampon ; toute la bufferisation vit ici.sigaction: au-dessus d’air-signal. Sur les fautes synchrones : fourni mais INERTE (compile, réussit, handler jamais appelé, coredump toujours — ADR-064 §2). Signaux asynchrones : émulés via signalfd + drainage (ousigactionréel non- faute, décision M3).
3. Doctrine de conformité (rigueur exigée)
Chaque symbole porte, dans sa doc (§4), une étiquette de conformité :
| Étiquette | Sens |
|---|---|
POSIX | PILE conforme C/POSIX : comportement, valeurs de retour, errno identiques à la norme. |
POSIX-PARTIEL | Fonctionnellement POSIX, avec déviations documentées (ex. erreur plus stricte, zéro troncature muette, borne explicite). Chaque déviation listée + justifiée. |
NON-POSIX-DÉLIBÉRÉ | On choisit de dévier (ex. sigaction inerte sur fautes) — raison obligatoire + renvoi ADR. |
AIR-EXT | Extension Air (air_* : variantes riches/sécures qui restituent le Result). Hors POSIX, valeur ajoutée. |
- Registre central :
docs/libc-conformance.md— table symbole → étiquette → (déviation/justification/ADR). Vérifié en CI (chaque symbole exporté a une étiquette). - Cas phares à tracer :
sigaction/signalinertes sur fautes (NON-POSIX-DÉLIBÉRÉ, ADR-064) ;free/air_free(POSIXvoidvsAIR-EXTint) ; toute fonction qui refuse une troncature muette (POSIX-PARTIEL, ADR-032).
4. Documentation — 3 sorties depuis UNE source
Source de vérité unique : les blocs de doc sur les extern "C" Rust (///),
incluant l’étiquette de conformité (§3) et le POURQUOI (transparence Air : dire ce
qu’on fait, les pièges, les alternatives — pas que la signature).
- Rust :
rustdocnatif sur les fonctions FFI. - Doxygen : le header C committé (
.h, déjà discipliné en CI — gate « header ABI C ≟ committé ») porte les commentaires doxygen/** */, miroir de la doc Rust (mêmes étiquettes). Doxygen → HTML + XML. - man (3ᵉ format) : dérivé de doxygen —
GENERATE_MAN = YESproduit les pagesman(3)depuis la même source doxygen. → Oui, le 3ᵉ format est produisible depuis les deux premiers (précisément : depuis la source doxygen).
- Cohérence outillée : un gate CI vérifie Rust-doc ↔ header ↔ étiquettes (extension
du gate
header ABI C committéexistant). Aucune dérive silencieuse. - Pinning (ADR-025) : version de doxygen figée (reproductibilité).
5. Règles de production (intégralement respectées, dans l’ordre)
① SÉCURITÉ. Zéro UB ; toute donnée externe parsée (format de printf/scanf,
getaddrinfo, strtol…) est bornée + fuzzée (cargo-fuzz). Pas de troncature
muette (ADR-032). Les tables d’état couche 1 (handles/allocs) alimentent la
traçabilité (vision instrumentable, ADR-063 en debug). Frontière C = unsafe assumé
et localisé ; implémentation derrière = couche 1 sûre.
② QUALITÉ. cargo xtask barrier vert ; couverture (100 % lignes sur la logique
Rust ; les shims triviaux couverts par les tests de conformité fonctionnels +
harnais C réels) ; tests de conformité ABI (ADR-012 : air-abi-check/air-symver
sur les symboles exportés) ; corpus de vecteurs (ex. printf contre une référence).
Zéro Co-Authored-By ; DCO + signature.
③ PERFORMANCE. Après sécurité + qualité (Principe 5) : mesurer (chemins chauds
= memcpy/stdio/malloc), justifier par écrit toute optimisation qui allège une
défense, garantir l’invariant par construction. Cible matériel modeste (Pi 4).
6. Jalons (topologiques — chacun testable, additif, PR par PR)
- M0 — Fondations. ☑ fait. Structure de crates
*-capi+ conventions ABI-C figées ; pipeline doc (rustdoc + doxygen + man) câblé + gate cohérence ;crt(crt1/i/n surair-runtime::bootstrap) +errno;libc-conformance.mdinitialisé. Test :int main(){return 42;}C linke sur la libc Air et sort 42 on-target. - M1 — stdio + string/mem + stdlib cœur.
FILE*bufferisé surair-stdio/AirFile(fopen/fclose/fread/fwrite/fflush/setvbuf/fgets/fputs), moteurvfprintffuzzé,malloc/free/calloc/realloc(surair-alloc),str*/mem*,exit/atexit/abort,getenv/setenv(surair-env). **Test :printfhello-world C- une poignée de programmes C réels.** Découpé en tranches PR : M1-a string/mem ☑,
M1-b malloc + moteur de format fuzzé ☑, M1-c
FILE*+exit/atexit/getenv ☑, M1-d frontière variadiqueprintf(option A : shim Cva_arg+ glue Rust mesuréeair-libc-printf+ hello-world C) ☑.
- Frontière variadique
printf— décision BDFL : option (A). Rust-stable ne peut définir une fonction C variadique (c_variadic= nightly), or la libc doit rester sur stable (ADR-025, barrier, couverture). Retenu : parseur + moteur de format 100 % Rust fuzzé (surface d’attaque N°1 en Rust) + un shim C mince faisant le seulva_argmécanique (piloté par le plan d’args du parseur, puis re-délègue au moteur Rust). C’est la 1ʳᵉ exception nommée à « zéro C » — minimale, sans logique ni surface d’attaque, justifiée par l’impossibilitéc_variadicsur stable. À consigner dansdocs/EXCEPTIONS.md+ le registre de conformité à l’impl (M1-d).
- une poignée de programmes C réels.** Découpé en tranches PR : M1-a string/mem ☑,
M1-b malloc + moteur de format fuzzé ☑, M1-c
- M2 — I/O fichier & métadonnées. ☑ fait (validé).
open/ openat/creat/read/write/close/lseek/pread/pwrite/dup/dup2/dup3/fcntl/pipe/pipe2(sur couche 0, FD brut,EINTRremonté,O_CLOEXECPOSIX rétabli),stat/fstat/ lstat/fstatat/access/faccessat(struct statABI Air),opendir/fdopendir/ readdir/closedir/rewinddir(DIRopaque +struct dirent, parseurgetdents64fuzzé),mkstemp/mkdtemp(entropie kernel),realpath(surcanonicalize),getcwd/chdir/fchdir. Architecture : logique mesuréeair-libc-fileio+ shimsair-libc-capi::fileio+ shim C variadiquefileio_shim.c(open/openat/fcntl, option A — même exception « zéro C » queprintf). Déviations gravées D-M2.1..D-M2.5 (cf.libc-conformance.md). Test : I/O fichier + parcours de répertoire (round-trips réels + fuzzfuzz_libc_dirent). - M3 — signaux & threads. ☑ fait.
sigaction/signal(inertes sur fautes, ADR-064),sigprocmask/kill/raise/sigsuspend(surair-signal) ;pthread_*(create/ join/mutex/cond/rwlock/once/key+dtor, surair-thread+TLS). Test : threading + signaux non-faute drainés ; faute → coredump, handler ignoré. - M4 — réseau, temps, tty, comptes, setjmp, misc. ☑ fait.
socket/bind/connect/listen/ accept/send/recv/getaddrinfo/getnameinfo/inet_pton(surair-socket) ;time/ clock_gettime/nanosleep/gmtime/strftime;tcgetattr/tcsetattr/cfmakeraw/openpty(surair-terminal) ;getpwnam/getpwuid/getgrnam(backends /etcair-config) ;setjmp/longjmp(îlot asm, ADR-047 §4-H) ;getopt/getpass/syslog/basename. M5 — OpenSSH.RETIRÉ (recadrage BDFL 2026-07-10) — conservé comme historique. L’intention d’origine : fournir les headers manquants, compiler OpenSSH upstream contre la libc Air, combler les manques révélés. Cette étape est abandonnée en tant que jalon : compiler OpenSSH n’était qu’une fonction de forçage (elle a rempli son rôle — libc ~256 symboles + toolchainstd). Remplacée parair-sshd: notre serveur SSH full Rust (async io_uring, wire-compat OpenSSH, conf binaire — ADR-074). Le jalon « toolchainstd-sur-Air » (M5 au sens du suivi) est, lui, ATTEINT (cf. bandeau). Les manques libc éventuels révélés parair-sshdrestent traités par descellement additifv1.xdocumenté.
(Chaque jalon = une ou plusieurs PR ; barrier vert + conformité + doc + coverage
avant merge. Chaque additif couche 1 révélé est traité par RFC additif.)
7. Points de cohabitation avec le PAL (à capturer au fil de l’eau)
Dans un process mixte Rust(PAL)+C(libc), à documenter dès qu’un jalon les touche :
allocateur partagé (air-alloc sous les deux) · errno cohérent (per-thread
air-runtime) · TLS (un seul modèle) · fd 0/1/2 (bufferisation FILE* vs
stdio du PAL) · délivrance des signaux (un seul régime). Ces notes deviennent la
spec de certitude du PAL.
8. Rapport de fin de production (livrable exigé)
En fin de production (et à chaque jalon majeur mergé), un rapport détaillé :
- Choix faits + POURQUOI, famille par famille (dont structure de crates, modèle
FILE*,errno, gestion signaux). - Table de conformité consolidée (POSIX / POSIX-PARTIEL / NON-POSIX-DÉLIBÉRÉ / AIR-EXT) + journal des déviations (chacune justifiée + ADR).
- Additifs couche 1
v1.xdéclenchés par la libc (descellements additifs). - Notes de cohabitation PAL (§7).
- Mesures de performance (chemins chauds) + toute défense allégée justifiée.
9. Exécution autonome
Même boucle que la campagne de sceau : production déléguée claude -p headless
(carbon/speedy), superviseur pilote git (branche/valide/barrier/PR/squash-merge/
re-vérif main+trailer) ; doc pure = commit direct main signé (worktree isolé) ;
zéro Co-Authored-By. Pilotage autonome jalon par jalon ; rapport détaillé
en fin de production (§8). Un seul acte réservé à autorisation BDFL explicite : si un
choix structurant non prévu émerge (ex. un NON-POSIX-DÉLIBÉRÉ nouveau, ou un
compromis sécurité), je le remonte avant de graver.