Audit FACE-LIBC — completeness critic pré-sceau couche-1-v1.0 (roadmap §5.1)
Rôle du document. Livrable de l’étape §5.1 de la roadmap couche 1 (
docs/roadmap-couche1-libc-pal-fr.md). Il rejoue le périmètre libc v1 (ADR-046) contre l’inventaire réel des crates couche 1 et statue, famille par famille, si la brique couche 1 (API Rust) existe pour porter la face C correspondante. Ce n’est pas l’implémentation de la libc (qui vit au-dessus du sceau) : c’est le critère de complétude qui autorise le sceaucouche-1-v1.0(§5.4).Méthode. S’appuie sur les 41 audits
docs/notes/audits/audit-musl-*.md(périmètre de fonctions attendu par famille) et leRAPPORT-CONSOLIDE-libc-fr.md. L’inventaire couche 1 a été établi par lecture directe despub fn/struct/enumdes cratescrates/. Tous les symboles cités sont réels (vérifiés par grep, pas devinés).
0. Rappel du cadre décisionnel (ce qui compte pour le verdict)
Trois clauses d’ADR-046 gouvernent l’analyse :
- D2 — layering strict. La libc ne dépend QUE de la couche 1, jamais de la
couche 0 (
air-sys-*). Conséquence contraignante : « c’est à la couche 1 d’exposer tout ce dont la libc a besoin, y compris des passthroughs fidèles au kernel ». La couche 1 offre deux tiers : un tier kernel-faithful (fidèle, EINTR remonté, sans politique) et des helpers ergonomiques. La libc se lie au tier fidèle. - D4 —
errnothread-local. Fourni en bordure (shim POSIX), jamais globale interne. - D7 — périmètre. ~25-30 familles ; cible pilote OpenSSH ~15 familles / 250-350 fn ; différés v1+ : aio, SysV IPC, mq, ldso, wordexp/glob, regex.
Grille de lecture pour « bloquant vs additif ». Le modèle de re-sceau de la couche 1
(roadmap §5.3/§5.4, précédent couche 0 ADR-051)
est le descellement additif v1.x : les manques futurs s’ajoutent sans refonte des
jalons scellés. Donc :
- BLOQUANT pour le sceau = une brique fondatrice absente dont l’ajout obligerait à refondre une API déjà scellée (rupture de signature / de contrat figé).
- ADDITIF = le manque se comble par un nouveau
pub fn/pub usedans une crate existante (ou une crate neuve), sans toucher aux signatures scellées. La face C se construit au-dessus.
⚠️ Constat structurant, répété tout au long de cet audit. La couche 0 (
air-sys-syscall) possède déjà la surface kernel complète dont la libc a besoin (openat2/read/write/lseek/dup_fd,signalfd_create/block_signals/kill/tgkill, familleterminalcomplète,ppoll,mmap/mprotect/munmap…). Mais la couche 1 n’en re-expose aujourd’hui qu’un sous-ensemble ergonomique curé. Le tier kernel-faithful que D2 impose à la couche 1 est donc, pour plusieurs familles fondatrices, pas encore construit. C’est exactement le travail annoncé par ADR-046 D2 (« la couche 1 doit grandir ») et séquencé par la roadmap en §5.3 (combler les manques additifs) avant §5.4 (sceller). Aucun de ces manques n’est bloquant au sens refonte — mais leur volume est substantiel et fondateur pour la cible OpenSSH.
1. Inventaire couche 1 (rappel synthétique des briques réelles)
| Crate | Rôle libc-face | Symboles-clés réels |
|---|---|---|
air-base-core | errno, chemins octets, temps, encodages, log, id | AirError/AirErrorKind/AirResult, AirPath, AirInstant/AirSystemTime/AirDuration, sleep/sleep_until, encoding::{base64,hex}, AirLog, AirUuid/AirId128 |
air-base-lib | i18n Unicode (icu4x), calendaire | AirString (norm./casing-locale/segmentation/collation), AirLocale, AirDateTime/AirCalendar |
air-libm | math no_std | pub use libm::* (surface fdlibm complète) |
air-env | environ octets (lecture) | get, vars/Vars, set_environ (pas de set/unset/secure_get) |
air-stdio | handles bruts 0/1/2 | stdin/stdout/stderr, StandardStream::{read,write,is_terminal,lock}, reset_after_fork |
air-terminal | tty + modèle/codec | tty::{isatty,ttyname}, AirTerminal/AnsiCodec/TerminalCodec, modèle TermIn/TermOut, Winsize (re-export). Pas de tcgetattr/tcsetattr/PTY publics |
air-filesystem | fs haut-niveau + watch | canonicalize/resolve_within/exists, write_atomic/read_to_bytes, read_dir/glob, copy/copy_tree, AirTempDir, AirFileSystemWatcher (inotify). Pas de handle open/read/write/lseek générique |
air-socket | TCP/UDP/Unix + DNS | AirTcpSocket/AirTcpListener/AirUdpSocket/AirUnix*, AirNameResolver/AirDnsSource (RFC1035), AirIpAddress::parse, send_fd/recv_fd |
air-process | clone3/exec/wait/pidfd | AirCommand/AirProcess, wait_timeout/wait_until/try_wait, signal/kill (enfant), air_pipe, drop_privileges, exit_process. Re-export PollFd/PollEvents |
air-thread | mutex/rwlock/sem/chan + spawn | AirMutex/AirRwLock/AirSemaphore, air_channel, atomic::*, AirThreadBuilder/join, runtime_primitives::{current_tid,create_thread,futex_wait_word} |
air-runtime | TCB/TLS/errno/fork natif | AirRuntime::{errno,set_errno,current_tid}, thread::spawn/JoinHandle, ThreadControlBlock, ThreadLocalStorage, fork::AtForkHandlers, start::bootstrap |
air-memory | arènes/pool/slab + mapping | AirArena/AirObjectPool/AirSlab, AirMemoryTracker, raw_mapping::{map_anonymous,unmap,RawMapping} |
air-alloc | allocateur global | GlobalAllocator (impl GlobalAlloc), Arena::{allocate,deallocate,reallocate,allocate_zeroed}, reset_after_fork |
air-crypto | CSPRNG + hash/AEAD/KDF/sig | AirRandom::{fill,u64,generate_key}, AirHash/AirHmac/AirAead/AirHkdf, AirArgon2::{hash_password,verify_password}, AirSigningKey/AirEcdh* |
air-config | backends /etc (pwd/grp/shadow…) | PasswdBackend (emit/import passwd/group/shadow), AccountsDomain, accounts_config::Reader, BackendRegistry |
2. Tableau famille → brique(s) couche 1 → statut → bloquant/additif
Légende : ✅ brique présente · ◐ partielle (manque précisé) · ❌ manquante · ⏭ différée v1+ (ADR-046 D7). Colonne « B/A » : B = bloquant sceau · A = additif · — = n/a.
2.1 Cœur userland + cible OpenSSH (~15 familles)
| # | Famille (audit musl) | Brique(s) couche 1 réelle(s) | Statut | B/A |
|---|---|---|---|---|
| 1 | errno (errno) | air-base-core::AirError/AirErrorKind (round-trip errno()), shim thread-local air-runtime::AirRuntime::{errno,set_errno} + ThreadControlBlock::errno (D4) | ✅ | — |
| 2 | string / mem (string) | air-base-core::AirPath (octets), encoding; air-base-lib::AirString (UTF-8). memcpy/strlen/strcmp/explicit_bzero = calcul pur sur core/alloc (aucune brique syscall requise) | ✅ | — |
| 3 | ctype (ctype) | air-base-lib::AirString::to_*case_in_locale + propriétés icu4x ; ASCII isalpha/isdigit = pur sur core | ✅ | — |
| 4 | multibyte / wchar (multibyte) | air-base-lib::AirString (UTF-8 garanti), segmentation grapheme/word/sentence (icu4x), NormalizationForm. La machine d’état C mbstate_t/mbrtowc se construit au-dessus (ADR-053) | ◐ (state-machine C = face-C) | A |
| 5 | locale / i18n (locale) | air-base-lib::AirLocale (BCP 47), collation compare_in_locale, casing locale, normalisation — socle icu4x présent (ADR-053). setlocale/localeconv POSIX = face-C | ✅ | — |
| 6 | time (time) | air-base-core::{AirInstant,AirSystemTime,AirDuration}, sleep/sleep_until (nanosleep, EINTR retry couche 1) ; air-base-lib::AirDateTime/AirCalendar (calcul calendaire icu4x). strftime/mktime/gmtime = formatage face-C au-dessus du calcul présent | ✅ (horloge/sleep/calcul) ; ◐ formatage | A |
| 7 | math (libm) (math/complex/fenv) | air-libm = pub use libm::* (surface fdlibm complète, no_std). Prérequis ADR-053 satisfait | ✅ (base réelle) ; ⏭ complex/fenv avancés | A |
| 8 | malloc / stdlib (malloc,stdlib) | air-alloc::GlobalAllocator (impl GlobalAlloc) + Arena::{allocate,deallocate,reallocate,allocate_zeroed} (malloc/free/calloc/realloc). atoi/strtol/qsort/bsearch/abs = pur sur core | ✅ | — |
| 9 | stdio (brut) (stdio) | air-stdio : handles fd 0/1/2, read/write (1 syscall, EINTR remonté), lock/StreamGuard, is_terminal, reset_after_fork. FILE*/vfprintf/scanf/bufferisation = face-C au-dessus (normal, D7) | ✅ (brut requis) | — |
| 10 | env (env) | air-env::{get,vars,set_environ} (octets). Manque : setenv/putenv/unsetenv/clearenv (mutation différée, ADR-047) et secure_getenv (OpenSSH utilise setenv) | ◐ | A |
| 11 | I/O fichier — unistd cœur (unistd,fcntl) | Couche 0 complète (openat2/openat/read/write/pread/pwrite/lseek/close/dup_fd). Couche 1 : air-filesystem n’expose que des ops ergonomiques fichier-entier (write_atomic/read_to_bytes/copy) ; aucun handle open/read/write/lseek/fstat/dup/fcntl générique. air-process::air_pipe/AirPipeReader/Writer + air-socket couvrent read/write sur pipes/sockets uniquement | ◐ (tier passthrough fichier absent en couche 1) | A (fondateur, à combler §5.3) |
| 12 | stat (stat) | Couche 0 statx (utilisé en interne par copy). Couche 1 : pas de stat/fstat/lstat public retournant les métadonnées ; seuls exists/AirFileType (via read_dir) affleurent | ◐ | A |
| 13 | dirent (dirent) | air-filesystem::read_dir (getdents64 → Vec<AirDirEntry> bornes ADR-032). DIR* streaming + seekdir/telldir/rewinddir = face-C au-dessus | ✅ (getdents64 couvert) | — |
| 14 | poll / select (select) | Couche 0 ppoll. Couche 1 : seulement air-process::AirProcess::wait_until(&mut [PollFd]) (couplé au pidfd de l’enfant), + re-export des types PollFd/PollEvents. Aucun multiplexeur poll()/select() générique sur un ensemble de fd arbitraire (boucle principale OpenSSH) | ◐ (types oui, multiplexeur générique absent) | A (fondateur, à combler §5.3) |
| 15 | termios / PTY (termios) | Couche 0 air-sys-syscall::terminal complète (tcgetattr/tcsetattr/tcdrain/tcflush/tcflow/tcsendbreak/get_winsize/set_winsize/open_pty_master/pty_number/pty_unlock/pty_peer/tcgetpgrp/tcsetpgrp/session_id/set_ctty/clear_ctty). Couche 1 air-terminal : seulement isatty/ttyname + modèle/codec ; le tier termios/PTY/job-control n’est PAS re-exposé (usage interne pour isatty, et sous #[cfg(test)]) | ◐ (passthrough termios/PTY absent en couche 1) | A (fondateur OpenSSH, à combler §5.3) |
| 16 | mman (mman) | Couche 0 mmap/mprotect/munmap. Couche 1 : air-memory::{map_anonymous,unmap,RawMapping} (anonyme seul) ; air-alloc::MmapBacking (interne). Manque : mmap fichier-adossé, mprotect, madvise, shm_open, mlock | ◐ | A |
| 17 | réseau (network) | air-socket : AirTcpSocket/AirTcpListener/AirUdpSocket/AirUnix* (cycle complet socket/bind/connect/listen/accept/send/recv), résolveur AirNameResolver/AirDnsSource (RFC1035, /etc/hosts, resolv.conf) = équivalent getaddrinfo, AirIpAddress::parse (inet_pton), send_fd/recv_fd (SCM_RIGHTS). Manque additif : getsockopt/setsockopt génériques (seuls set_nodelay/set_keepalive exposés) | ✅ | A (options socket) |
| 18 | process / exec (process,exit) | air-process : AirCommand::spawn (clone3+execve, enfant AS-safe), AirProcess::{wait_timeout,wait_until,try_wait,wait_forever} (waitid), pidfd, drop_privileges (setresuid/gid/setgroups/capset/seccomp/Landlock), air_pipe, exit_process (exit_group) ; fork natif via air-runtime::fork::AtForkHandlers+thread::spawn. posix_spawn-classe couvert | ✅ | — |
| 19 | signal (signal) | Couche 0 complète (signalfd_create/block_signals/unblock_signals/set_signal_mask/current_signal_mask/wait_for_signal/kill/tgkill/rt_sigqueueinfo). Couche 1 : seulement air-process::AirProcess::{signal,kill} (→ pidfd_send_signal sur l’enfant possédé). Manque en couche 1 : création signalfd, masquage (sigprocmask/pthread_sigmask), kill(pid) arbitraire, raise/sigsuspend/sigpending. ADR-020 pose la primauté signalfd, mais la brique n’est pas exposée en couche 1 (l’appelant « passe un signalfd pré-construit » — construit en couche 0) | ◐ (envoi-enfant seul ; face signal générale absente en couche 1) | A (fondateur OpenSSH, à combler §5.3) |
| 20 | thread / pthread (thread,sched) | air-thread : AirMutex/AirRwLock/AirSemaphore (futex, sans poison), air_channel, atomic::*, AirThreadBuilder/join, runtime_primitives::{current_tid,create_thread,futex_wait_word,set_thread_pointer} ; air-runtime : thread::spawn/JoinHandle (clone3+CLONE_SETTLS), ThreadControlBlock/ThreadLocalStorage (TLS/TCB), errno TLS. Manque additif : condvar publique (futex interne), pthread_key/TSD ergonomique, sched_yield/sched_setaffinity public (affinité en set via AirThreadBuilder::cpu_affinity) | ✅ (primitives + spawn/join + TLS) | A (condvar/TSD/sched) |
2.2 Familles « longue traîne » (dans/adjacentes au périmètre v1)
| Famille (audit musl) | Brique(s) couche 1 | Statut | B/A |
|---|---|---|---|
crypt (crypt) | air-crypto::AirArgon2 (Argon2id, salt CSPRNG interne, vérif constant-time) = réponse Air au hachage mot de passe. crypt() DES/MD5 legacy = compat face-C optionnelle | ✅ (hachage moderne) | A (compat legacy) |
prng (prng) | air-crypto::AirRandom::{fill,u64} (getrandom kernel, fail-closed) = getentropy/arc4random. rand/random/rand48 déterministes = pur sur core | ✅ | — |
temp (temp) | air-filesystem::AirTempDir (mkdtemp, O_EXCL 0700). mkstemp/mkostemp → **nécessite le handle fichier générique O_EXCL | 0600** (cf. famille 11) | ◐ |
misc (misc) | realpath ≈ air-filesystem::canonicalize ✅ ; syslog ≈ air-base-core::AirLog (journald, transport différent — assumé) ✅ ; getopt/basename/dirname = pur ; openpty/getpass → dépendent du tier termios/PTY (cf. #15) ; wordexp NON porté (RCE, RAPPORT-CONSOLIDE §H) | ◐ | A |
search (search) | qsort/bsearch/lsearch/tsearch/hsearch = pur sur core/alloc (état non-global chez Air) | ✅ (aucune brique) | — |
conf (conf) | sysconf/pathconf/confstr non exposés ; constantes + rares syscalls (nb CPU via sched_getaffinity). Non-critique | ❌ (mineur) | A |
stdlib divers (stdlib) | parsing/tri/abs/div = pur ; getenv/setenv cf. #10 ; exit/abort via air-process::exit_process + panic/abort Rust | ✅ | A (setenv) |
legacy (legacy) | getpass/utmp/ftw… — majoritairement évités/différés (RAPPORT-CONSOLIDE §G6). N/A pour le sceau | ⏭ | — |
linux (linux) | syscalls Linux divers (prctl, getrandom, sysinfo, epoll, sendfile…) — couche 0 les a ; couche 1 sélective (getrandom via air-crypto ✅). Reste additif | ◐ | A |
setjmp / longjmp (setjmp) | ABSENT de tout le code (crates/ : zéro setjmp/longjmp/jmp_buf). Îlot asm incompressible identifié par ADR-047 P6 / RAPPORT-CONSOLIDE §D. À confiner en surface FFI unsafe le moment venu (OpenSSH n’en a guère besoin) | ❌ | A (îlot asm, FFI-only) |
2.3 Familles différées v1+ (ADR-046 D7 — hors périmètre du sceau libc-v1)
| Famille (audit musl) | Justification du report | Statut |
|---|---|---|
aio (aio) | Réimplémentée sur io_uring (roadmap §4-K air-runtime async) ; consommateur = couche 2+, incluse dans la surface scellée mais pas d’audit face-toit ici | ⏭ |
SysV IPC (ipc) | Explicitement hors v1 (ADR-046 D7). Couche 0 a ipc.rs, mais la famille libc shmget/semop/msgget est différée | ⏭ |
mq (mq) | POSIX message queues — différé D7 | ⏭ |
ldso (ldso) | Éditeur de liens dynamique — hors libc statique initiale ; air-runtime::reloc ne couvre que le static-PIE (R_*_RELATIVE). ld.so complet = post-v1 | ⏭ |
| wordexp / glob | wordexp jamais porté (fork+exec /bin/sh = RCE). glob() libc différé (mais air-filesystem::glob existe pour l’usage interne Air) | ⏭ |
regex (regex) | Différé post-sceau (roadmap §4-I) : crate additive neuve, son ajout ne descelle rien. Ne bloque que regcomp/find_regex (non-critiques) | ⏭ |
complex / fenv (complex,fenv) | Axe FP avancé, adjacent à libm ; base air-libm suffit à icu4x. Sentinelle FE_DFL_ENV=(-1) bannie ADR-021 | ⏭ |
3. Points d’attention spécifiques (tranchés)
- stdio.
air-stdiocouvre bien le brut requis (fd 0/1/2, read/write 1-syscall EINTR-fidèle, verrou par flux,is_terminal,reset_after_fork). LeFILE*bufferisé,vfprintf(moteur de format, sans%npar défaut — RAPPORT-CONSOLIDE §B6) etscanfrestent légitimement face-C au-dessus (D7). Verdict : brique suffisante. - pwd / grp / shadow.
air-config::PasswdBackendest la brique : il émet et importepasswd/group/shadow(hash enshadowseul, mode 0600 dès création viaopenat2, buffersZeroizing, élision du secret dans les erreurs) et exposeaccounts_config::Reader(name/uid/gid/ gecos/home/shell/password_hash). C’est la source de vérité contre laquellegetpwnam/getpwuid/getgrnam/getgrgid/getspnamse câblent en face-C. Nuance : l’API est orientée projection d’artefact ↔ /etc, pas lookup ponctuel indexé ; les itérateurs/lookupsgetpw*_rowned se construisent au-dessus (additif). Verdict : brique présente (◐ sur l’ergonomie lookup, additif). - string / ctype / multibyte.
air-base-core(octets/encodages) +air-base-lib(icu4x : casing-locale, normalisation, collation, segmentation) fournissent le socle ADR-053. Le ctype ASCII et la machinembstate_tsont du calcul pur/face-C au-dessus du socle. Verdict : brique présente. - setjmp / longjmp. Absent (grep
crates/= 0). C’est l’unique résidu asm réellement incompressible (ADR-047 P6, RAPPORT-CONSOLIDE §D) : inexprimable en Rust safe (saute par-dessus lesDrop). À confiner en surface FFI C-ABI unsafe le moment venu. Additif, non-bloquant (nouvelle unité asm/FFI, aucune API scellée touchée ; OpenSSH n’en dépend guère). - regex. Différé post-sceau (ADR-046 / roadmap §4-I). Crate additive : ne descelle rien.
4. Gaps BLOQUANTS pour le sceau
Aucun gap bloquant au sens refonte. Justification (à charge, un completeness critic doit prouver l’absence de bloquant, pas la postuler) :
- Toutes les briques kernel nécessaires existent déjà en couche 0 (
air-sys-syscall) — fichier, signal, termios/PTY, poll, mman — et tous les types sont enair-sys-types. Aucune brique fondatrice n’est à inventer ; il s’agit de re-exposer un tier couche-1 fidèle. - Chaque manque identifié en §2 se comble par ajout (
pub fn/pub use/module neuf, voire crate neuve) sans modifier une signature couche-1 déjà figée → descellement additifv1.x(modèle roadmap §5.3/§5.4, précédent ADR-051). Aucun ne force une rupture de contrat scellé. - Le seul « trou » structurellement irréductible (setjmp/longjmp) est un îlot asm FFI-only, par nature hors de l’API Rust scellée.
Réserve honnête (ne pas la lire comme un bloquant, mais ne pas la sous-estimer). Le fait que rien ne soit bloquant-refonte ne signifie pas que la face libc v1 est portable en l’état. Un tier passthrough substantiel et fondateur pour OpenSSH reste à construire en couche 1 (§5.3) avant que la libc puisse être écrite : I/O fichier générique (#11), poll/select générique (#14), termios/PTY (#15), signal générique (#19), plus mman (#16) et
setenv(#10). Ce sont les manques listés ci-dessous — additifs, mais prioritaires.
5. Gaps ADDITIFS post-sceau (attendus : la face C n’est pas encore écrite)
Classés par priorité pour la cible OpenSSH. Tous relèvent du §5.3 « combler les manques additifs » et aucun ne descelle l’API figée.
P0 — fondateurs pour OpenSSH, à exposer en couche 1 avant d’écrire la libc :
- I/O fichier générique (#11). Exposer en couche 1 un tier fidèle
open/openat/read/write/pread/pwrite/lseek/close/dup/fcntl(F_GETFL/F_SETFL/F_SETFD)/ftruncate(la couche 0 a tout :openat2/read/write/lseek/dup_fd). Sans lui,open(2)/read(2)/write(2)de la libc sont inexprimables (D2 interdit le saut vers couche 0). - poll/select générique (#14). Exposer un multiplexeur
ppollsur fd arbitraires (la couche 0 appoll; seulair-process::wait_untill’affleure, couplé au pidfd). Boucle principale OpenSSH. - termios / PTY / job-control (#15). Re-exposer en
air-terminalle tiertcgetattr/tcsetattr/tc*/get_winsize/set_winsize/open_pty_master/pty_*/tcgetpgrp/tcsetpgrp/session_id/set_ctty(tout en couche 0).cfmakeraw(Result, pasvoid— RAPPORT-CONSOLIDE §B2),openpty,getpassen dépendent. - Signal générique (#19). Exposer en couche 1 la création
signalfd(primauté ADR-020), le masquage (sigprocmask/pthread_sigmask),kill(pid)arbitraire,raise/sigsuspend. Ne PAS forcerSA_RESTART(EINTR remonté, RAPPORT-CONSOLIDE §H).
P1 — fondateurs mais moins critiques au démarrage :
- mman (#16). mmap fichier-adossé,
mprotect,madvise,shm_open(..+O_NOFOLLOW, RAPPORT-CONSOLIDE §F4),mlock. Couche 0 a mmap/mprotect/munmap ; couche 1 n’a que l’anonyme. - env mutation (#10).
setenv/putenv/unsetenv/clearenv(+ gouvernance ADR-047) etsecure_getenv(gatingAT_SECURE, RAPPORT-CONSOLIDE §F9). - stat/fstat/lstat public (#12) ; mkstemp/mkostemp (dépend de #11, O_EXCL|0600).
P2 — ergonomie / complétude :
- thread (#20) : condvar publique,
pthread_key/TSD,sched_yield/sched_setaffinitypublic. - réseau (#17) :
getsockopt/setsockoptgénériques. - stdlib/time : moteur
vfprintf/strftime(face-C au-dessus des briques présentes). - setjmp/longjmp : îlot asm FFI-only (ADR-047 P6) — à créer si un userland C l’exige.
- conf/linux divers :
sysconf/pathconfréels (pas de faux succès, RAPPORT-CONSOLIDE §H11).
Rappel de durcissement (RAPPORT-CONSOLIDE, à porter dans la face C, pas des gaps de brique) :
malloc zero-on-free absent d’air-alloc (choix de durcissement, additif) ; neutraliser %n
par défaut ; entropie unique CSPRNG (déjà air-crypto::AirRandom, fail-closed) ; zeroize secrets
(déjà Zeroizing/ZeroizeOnDrop en air-config/air-crypto).
6. VERDICT de sceau-readiness FACE-LIBC
Le verdict se lit à deux niveaux, tous deux nécessaires à une réponse honnête :
(a) Au sens de la question posée — « rien de FONDATEUR ne manque, les manques sont additifs » : OUI.
Les fondations de la couche 1 sont présentes et saines : errno + shim thread-local (D4),
allocateur global, primitives thread futex + TLS/TCB, temps + sleep, environ (lecture), clone3/exec/
wait/pidfd/drop_privileges, réseau complet + résolveur RFC1035, socle i18n icu4x + libm, crypto
CSPRNG/hachage, backends passwd/group/shadow. Aucun manque n’exige de refondre une API
scellée : tous sont additifs (re-exposition d’un tier couche-1 fidèle par-dessus une couche 0
déjà complète, ou crate neuve). Le modèle de descellement additif v1.x (ADR-051, roadmap
§5.3/§5.4) absorbe tout manque résiduel découvert après le sceau. Le sceau couche-1-v1.0 est
donc SÛR : le sceller ne fige aucune erreur de conception irréversible.
(b) Au sens fonctionnel — « la face libc v1 (OpenSSH) est-elle portable sur la couche 1 en l’état » : NON, pas encore.
Un tier passthrough kernel-faithful fondateur reste à exposer en couche 1 (travail §5.3
explicitement prévu avant §5.4) : I/O fichier générique, poll/select générique, termios/PTY,
face signal générique (P0), puis mman et setenv (P1). Ce sont exactement les « manques additifs
remontés par 5.1 » que la roadmap ordonne de combler avant de sceller.
Conclusion opérationnelle. OUI au sceau sous le régime additif — à la condition explicite
d’exécuter d’abord §5.3 sur les 6 chantiers P0/P1 ci-dessus (aucun n’est un bloquant-refonte ;
tous sont des ajouts de pub/crates au-dessus d’une couche 0 déjà dotée). Sceller sans §5.3
resterait techniquement sûr (rien d’irréversible) mais produirait une couche 1 incapable de
porter la face libc v1 sans un descellement additif immédiat — ce que la séquence §5.3→§5.4 vise
précisément à éviter. Recommandation : §5.3 (P0+P1) puis §5.4.
Complétude critique — checklist du critique. Périmètre v1 rejoué famille par famille (✅/◐/❌/⏭) ;
symboles réels vérifiés par grep ; couche 0 confirmée comme socle des passthroughs manquants ;
setjmp confirmé absent ; wordexp confirmé exclu ; distinction bloquant-refonte / additif tranchée
et justifiée (pas postulée). Points d’attention du prompt (stdio, pwd/grp, string/ctype, setjmp,
regex) tous tranchés en §3.