Air — Journal de bord
Archive chronologique append-only (détail session par session, PR par PR). Pour l’état vivant « où en est-on / prochaine tâche / tâches à venir », le document de suivi est
etat-avancement.md— à lire en premier. Ce journal reste la trace historique fine.
2026-07-10 — 🎉 M5 ATTEINT : std tourne sur Air, zéro glibc, 2 arches
Jalon majeur : un programme Rust std (hello-std) lié intégralement contre
libair (zéro glibc) s’exécute on-target — hello from std on air + exit 0 sur
x86_64-air (carbon natif) ET aarch64-air (raspi natif). Static-PIE, pari
ADR-076 tenu de bout en bout.
Le lien de hello-std révélait 12 symboles indéfinis ; comblés en 6 PR :
- #308
__xpg_strerror_r(alias XSI destrerror_r). - #309
open/fcntl— exports non-variadiques cible (#[cfg(target_vendor="air")]). - #310 couche 1
air-runtime::process_context(captureauxv+ bornes de pile au bootstrap) +AirProcessManager::resource_limit— ADR-086. - #311
getauxval+pthread_self+ détection de pile (pthread_getattr_np/attr_getstack/attr_getguardsize). - #312 TLS keys cible (
pthread_key_create/delete/setspecific) via#[thread_local]natif (discipline.tbss) + allocateurKEYSextrait/partagé. - #313
syscallgénérique — couche 0raw_syscall(asm 2 arches) + shim libc cible. EXCEPTION assumée temporaire (ADR-087) : entorse à ADR-021 conv.3 + « libc binde couche 1 » ; à retirer avec l’équipe Rust (le palunixdestdappellefutex/gettiden direct).
Re-sceaux (PR #314, tags signés GPG) : couche-0-v1.12 (raw_syscall) +
couche-1-v1.9 (introspection thread ; l’earmark ADR-078 crypto glisse en v1.10).
Prochaine phase = OpenSSH / air-sshd (ADR-074).
(La campagne M4 réseau + posix_spawn, 2026-07-05→09, n’a pas d’entrée dédiée ici — voir
etat-avancement.md et le registre des ADR.)
2026-07-04 — 🧵 libc M3 Phase B : familles signaux + pthread (couche 2)
Toit libc M3 Phase B (branche feat/libc-m3-phaseB, sur la Phase A ADR-066). Deux
crates de logique mesurée (aucun #[no_mangle]), empilement strict libc →
couche 1 (jamais air-sys-syscall) : air-libc-signal (sur air-signal) et
air-libc-thread (sur air-thread::raw_futex). Shims extern "C" dans
air-libc-capi::{signal,thread}, ré-export/ancrage air-libc-c, types+prototypes
hand-written dans le trailer cbindgen.toml (union struct sigaction,
sighandler_t, opaques pthread + initialiseurs, SIG*/SA_*/PTHREAD_*), exclus de
la génération cbindgen.
- Sécurité #1 — 4 fautes INERTES (ADR-064) :
sigaction/signalsurSIGSEGV/SIGBUS/SIGFPE/SIGILLacceptent (retour 0) et mémorisent la disposition (illusion d’API : table process-globale pouroldact) mais n’installent RIEN → la faute prend toujours l’action par défaut (coredump). Les signaux gérables sont installés en async réel (air_signal::install_handler, le kernel appelle le handler C).sigprocmask/pthread_sigmask→masque ;kill/raise→envoi ;sigpending→pending;sigsuspend→replace_mask+futex_waitinterruptible. - pthread sur
raw_futex(mémoire opaque de l’appelant, jamaisAirMutex) : mutex 3 étatsNORMAL/RECURSIVE/ERRORCHECK(owner =Tid) +timedlock; cond par compteur de séquence +timedwait(échéanceREALTIME/MONOTONIC) ; rwlock lecteur-préférentiel ;once(3 états) ; TLS (key_create+dtor à la sortie de thread viastd, hôte). Cycle de viecreate/join/detach/self/equalsur lespawnd’air-thread(hôte,pthread_t = Tid). Types opaques ABI Air largeur fixe (mutex 40/cond 48/rwlock 56/once 4,sigset_t128) + initialiseurs zéro. - Additifs REMONTÉS (non inventés) : sortie de thread propre (
pthread_exitendort le thread faute de primitive couche 1 +panic=abort), crochet de destruction TLS cible,rt_sigsuspendatomique,killgroupe de processus. Déviations gravées D-M3.1..D-M3.9 (docs/libc-conformance.md). - Validation superviseur en attente (cargo/git gated dans l’environnement de
production) :
gen-capi-header(régénèreair_c.h— trailer M3), barrière +nm -D(sigaction/kill/pthread_create/pthread_mutex_lock…), programme C multi-thread, signal non-faute délivré async + faute → coredump, sur x86_64 ET aarch64.Cargo.lockà régénérer (2 nouvelles crates).
2026-06-29 — 🧭 CORRECTION D’ARCHITECTURE : air-rt est un objet couche 1 (ADR-052)
Relecture du layering : le crate rt/crates/air-rt (phase 2) appelle la couche 0 en direct
(arch::set_fs, mem::mmap, process::clone_thread, futex, exit_group) — violation de la
doctrine d’Air (seule la couche 1 consomme la couche 0 ; personne d’autre). ADR-052 (ratifié BDFL)
grave la correction : air-rt est un objet couche 1, AirRuntime, composé de nouvelles briques
couche 1 à créer (elles n’existent pas encore) — ThreadControlBlock, ThreadLocalStorage,
outils de relocation (cible PIE/static-PIE, ADR-050) — s’appuyant sur air-thread/air-mem/air-proc.
AirRuntime ne dépend jamais de air-sys-syscall (la surface kernel) — air-sys-types (types
transverses : AirError/Errno, Pid/Tid… ADR-052 D6) reste autorisé. Les wrappers de syscalls (dont set_fs, ADR-051)
restent en couche 0 ; leur consommateur devient un objet couche 1 (jamais le runtime). Le tree
hors-arbre rt/ ne garde que le point d’entrée ELF _start + la plomberie cible (build-std/nightly).
Doc mise à jour / tagué obsolète : ADR-049 D8 (bannière + biffure), ADR-051 (note layering +
reformulation « affaire d’air-rt »), macro-architecture (§Couche 1 : objet air-runtime + tableau
d’artefacts), family-arch.md/family-process.md, INDEX, rt/README.md/rt/DECISIONS.md, registre
(v3.17) + SUMMARY. Gain : la logique de runtime, devenue couche 1, retombe sous le **100 % couverture
- barrière 2-arches** (le carve-out
rt/ne couvre plus que_start+ cible). Code à refactorer (création des objets couche 1, déplacement, retrait dépendanceair-sys-syscall—air-sys-typesconservé) = chantier suivi séparément.
2026-06-28 — 🔒 RE-SCEAU couche-0-v1.7 : 7 syscalls pour le runtime Air et la libc
Descellement unique (ADR-051, ratifié BDFL) ajoutant 7 wrappers typés à la couche 0, pour débloquer la phase 2 du programme *-linux-air (runtime air-rt) et pré-équiper l’objectif libc (ADR-046) — un seul re-sceau plutôt que des cycles répétés. arch_prctl (set_fs/get_fs, x86_64-only, nouvelle famille arch) programme le registre TLS du thread principal x86 (sur aarch64 = tpidr_el0 en asm, affaire d’air-rt ; threads spawnés = CLONE_SETTLS). process : getppid (→ Option<Pid>, 0=parent hors namespace), set_tid_address (primitif de join), sched_yield, umask, getcwd (octets, Principe 3), getrusage (+ types Rusage/RusageWho/Timeval #[repr(C)]). Tous en asm 2-arches avec // SAFETY:. 16 tests dont stratégies sûres pour les cas délicats (arch_prctl : no-op sur la base courante ; set_tid_address : threads détachés + Arc). check-syscalls PASS (numéros validés vs headers uapi, 2 arches), couvrable VIDE (2 exceptions STRUCTURAL/EFAULT-SAFE : bras Err de get_fs/getrusage inatteignables par construction — buffer valide + who typé), audit-exceptions cohérent (77 entrées, STRUCTURAL 46). Reporté en couche-0-v1.8 (phase 5, avec OpenSSH comme consommateur) : termios (ioctl typé), PTY, rt_sigaction/sigaltstack (+ décision ADR-020).
2026-06-28 — 🔒 RE-SCEAU couche-0-v1.6 : couche 0 std-free + futex(2) + types FD natifs + clone3 spawn de thread
Descellement unique de la couche 0 (ADR-048 + Amendement 1, ratifiés BDFL) regroupant : (1) famille futex(2) classique (FUTEX_WAIT/WAKE typés, EINTR remonté ; PR #147) ; (2) internalisation des types FD (OwnedFd/BorrowedFd/RawFd natifs d’Air + close(2) dans air-sys-types — core::os::fd n’existe pas en stable ; PR #148) ; (3) std-free #![cfg_attr(not(test), no_std)] (ffi→alloc/core, Arc→alloc, OsString→Vec<u8>, std::sync::Mutex→FutexMutex ; PR #149) ; (4) clone3 spawn de thread (clone_thread + trampoline asm x86_64/aarch64, join CHILD_CLEARTID/futex ; PR #150). Critère de sortie atteint : cargo build -p air-sys-types -p air-sys-syscall --lib compile en #![no_std] (preuve par le compilateur de l’absence de std en code lib). Couverture couvrable VIDE (branches ~84,3 %), // SAFETY: sur chaque unsafe, vérifié sur les 2 arches (bug asm aarch64 x19 réservé LLVM → x21, attrapé par la vérif 2-arches). Débloque la réimpl air-thread std-free (couche 1) sur futex/clone3, prérequis de la libc Air (ADR-046/047). Points à réconcilier par RFC : la spec air-sys-types.md montre encore CloneResult::Child pour les threads (unsound → clone_thread distinct) ; outillage ABI Rust air-abi-check/air-symver non implémenté (ADR-012).
Rôle de ce document
Tableau de bord dynamique du projet. Donne en un coup d’œil :
- L’état d’avancement courant.
- Le prochain chantier prioritaire et son contexte.
- Le backlog priorisé (vue condensée).
- L’historique des sessions précédentes.
Pour la checklist structurelle exhaustive (tous les documents à produire, arborescence cible, statistiques), voir INDEX.md. Pour le détail des ADRs, voir adrs/registre-adrs-fr.md. Les deux sont à consulter quand on cherche un détail précis ; JOURNAL.md sert à savoir où on en est et ce qu’on fait ensuite.
Convention d’usage en début de session : prompt initial type — « Bonjour, on continue sur Air. Lis docs/JOURNAL.md pour le contexte de reprise, puis on attaque [sujet]. »
État courant
2026-06-25 — 🎯 JALON : air-config COMPLET (5 backends + validation cas-réels). Suite des tranches 1-3 (chaîne verticale), livrés : tranche 4 (backends systemd — symlinks *.wants/mask/drop-ins/default.target, remplacement atomique ; fstab — 6 colonnes déterministes ; PR #122) et tranche 5 (passwd/group/shadow, PR #125 — SÉCURITÉ : shadow créé 0600 dès la naissance via openat2, jamais de chmod après-coup ; hashes en Zeroizing effacés après écriture ; jamais loggés/diagnostiqués ; is_safe_hash anti-injection ; tests comptes synthétiques en tempdir, robustes root). Validation cas-réels (PR #126) : corpus de fixtures /etc réalistes + comparaison sémantique (modèle-vs-modèle, détection de perte) = gate CI reproductible ; harnais advisory /air/etc (examples/etc-fidelity) produisant imported/artifact/reemitted/semantic-diff par backend sur la vraie machine — fidélité sémantique parfaite sur le /etc réel de carbon (138 units, 135 comptes), zéro secret (/etc/shadow jamais lu ; re-scan → 0 hash sous /air/etc, vérifié indépendamment). 5 backends /etc couverts (resolv.conf/hosts, systemd, fstab, passwd/group/shadow). Zéro unsafe/as/dépendance externe/surface C ; 6 cibles fuzz ; 100 % hors exceptions STRUCTURAL ; code généré *_capnp.rs exclu de la couverture (ADR-040). A nécessité le fix couche 0 faccessat2 (re-sceau v1.4, PR #123 — faccessat honorait pas son flag AT_SYMLINK_NOFOLLOW). Reste pour clore la couche 1 : l’ABI C (libair-base.so, doc cadrée ADR-027 addendum).
2026-06-25 — 🎯 JALON : air-config — chaîne verticale complète (tranches 1-3, PR #116/#118/#120). Le modèle de configuration d’Air (ADR-033/040/041) est implémenté de bout en bout, en 3 crates : air-config-compile (parseur du format de source maison — schema-directed, hand-rollé, fuzzé ~1,4 M runs ; binding au schéma + did-you-mean ; conversions typées checked anti-overflow/signe, zéro as ; décodage bytes hex/b64 ; secrets zeroizés/élidés ; encodage Cap’n Proto déterministe → artefact bit-pour-bit reproductible), air-config-schema (4 domaines .capnp : network/services/accounts/mounts + annotations $secret/$domain/$backend ; code Rust généré committé ADR-040 ; build sans tool C++ prouvé ; tool capnp 1.1.0 pinné), air-config (façade : lecteur mmap zéro-copie + vérif magic/checksum/version → InvalidData, jamais de lecture muette ; XDG ; générations + switch atomique + rollback via write_atomic ; trait Backend paramétré par racine + backend resolv.conf/hosts émission swap-atomique + import inverse défensif). Aucune fn unsafe exposée, zéro as, zéro dépendance externe, zéro surface C (capnp runtime pur Rust). 3 cibles fuzz ; 100 % lignes+branches hors exceptions STRUCTURAL ADR-035. Code généré *_capnp.rs exclu de la couverture (--ignore-filename-regex, corollaire ADR-040). Tests backends en tempdir (assert explicite root != /etc). Décisions design : ADR-040 (Cap’n Proto + addendum tool-C++/code-committé), ADR-041 (coexistence /etc), ADR-027 addendum (doc ABI C). Tranche 4 différée (backends systemd → passwd/shadow prudent → fstab). Reste couche 1 : tranche 4 air-config + ABI C (libair-base.so).
2026-06-25 — 🎯 JALON : air-base-lib COMPLET (cœur + services, PR #110). Les services sont livrés : AirLog (journalisation journald via protocole socket natif AF_UNIX, zéro dépendance systemd ; petites entrées CLÉ=valeur / forme binaire ; repli memfd scellé + SCM_RIGHTS sur EMSGSIZE ; non-fatal — log() n’échoue jamais, pertes comptées) + identifiants (AirUuid v7/v4, AirId128 via /etc/machine-id, AirMonotonicId). Aucune fn unsafe exposée, zéro as, zéro dépendance externe, zéro surface C ; 94 unit + property + 11 doctests + 2 cibles fuzz (journal_encode, uuid_parse) ; 100 % hors exceptions STRUCTURAL in-code (bras d’erreur kernel inatteignables : add_seals/write memfd, getrandom partiel ≤256 o). Cœur non régressé (byte-identique). tracing-bridge différé (zéro dépendance). §3 Config NON implémenté ici : superseded par ADR-033/040/041 → crate dédiée air-config (compilateur + artefact Cap’n Proto). Couche 1 — état : 8/8 crates cœur + air-base-lib services ✓. Reste pour clore la couche 1 : air-config (chantier dédié) et l’ABI C (libair-base.so, différée).
2026-06-25 — 🎯🎯 JALON MAJEUR : COUCHE 1 — 8/8 CRATES CŒUR IMPLÉMENTÉ (air-process livré, PR #105). Le cœur de la couche 1 est complet : air-base-lib cœur + air-crypto + air-socket + air-filesystem + air-memory + air-device + air-thread + air-process. air-process : AirCommand/AirProcess (clone3+CLONE_PIDFD, redirections dup3, chdir, execve_prepared, wait synchrone waitid-sur-pidfd, signal/kill), AirPipe (pipe2+CLOEXEC), AirExitStatus typé ; drop_privileges (process courant) + drop_privileges_to (drop dans l’enfant post-fork avant exec) — chemin enfant alloc-free (CStrArray + buffer de groupes construits avant clone3, drop_privileges_with_buffer sans alloc → async-signal-safety), ordre sûr no_new_privs→setgroups→setresgid→setresuid→capset→ambient→seccomp→landlock→verify, codes d’échec distincts spawn/drop. Aucune fn unsafe exposée, zéro as. Tests d’intégration réels + 4 tests root (regain de privilège irréversible, accès root révoqué via drop_privileges_to) + Miri ; 100 % lignes+branches hors exceptions ADR-035 in-code (CHILD-EXIT du chemin enfant, STRUCTURAL wait/clone3). A nécessité une extension couche 0 : execve_prepared (exec sans allocation pour le chemin post-fork) — additive, re-scellée couche-0-v1.3. Décisions config gravées en parallèle : ADR-040 (artefact air-config = Cap’n Proto ; addendum : capnpc exige le tool C++ au build → politique code généré committé) + ADR-041 (coexistence /etc : projection générée, RO sélectif, air-config seul écrivain). Reste pour clore la couche 1 : air-base-lib services (logging journald natif + identifiants ; air-config = chantier dédié Cap’n Proto) et l’ABI C (libair-base.so, différée).
2026-06-25 — 🎯 JALON : air-thread livré (PR #97) — threads + synchronisation de la couche 1. AirThreadBuilder (nom, taille de stack, affinité CPU — implémentée via la couche 0 set_cpu_affinity+gettid, handshake par canaux : l’enfant publie son Tid, l’appelant pose l’affinité avant de débloquer le corps) + AirThreadHandle<T> (join→AirResult). AirMutex/AirRwLock/AirSemaphore au-dessus de std::sync, SANS poisoning (poison absorbé via into_inner, testé par empoisonnement délibéré). AirChannel MPSC ; atomics ré-exportés. Aucune fonction unsafe exposée, zéro as, arithmétique saturante. Property-based + Miri (35 tests ; 5 ignorés = affinité en asm + stack, non supportés par Miri). 100 % lignes+branches hors 1 exception STRUCTURAL (usize::try_from(cpu) Err, impossible sur cibles 64 bits, doc in-code). Backend std::sync v1, migration futex maison différée. NB : l’affinité CPU était marquée « différée » dans la spec ; implémentée car la couche 0 (PR #55) la fournit → spec mise à jour. Piloté par agent headless sur carbon. Couche 1 : 7/8 crates cœur implémenté (+ air-thread).
2026-06-24 — 🎯 JALON : air-device livré (PR #94) — énumération/surveillance/identité natives de la couche 1. Natif sysfs + netlink uevent (groupe KERNEL) + evdev, sans libudev/libc. §1–§4 : AirDevice (attributs/propriétés/parent), AirDeviceEnumerator (/sys/class+/sys/bus, dédup canonique, filtres), AirDeviceMonitor (hotplug uevent), typés AirNetDevice/AirBlockDevice/AirUsbDevice. §5 AirInputDevice (evdev : capacités bitmap, horloge monotone EVIOCSCLOCKID, AirInputEvent::from_raw pur). Monitor durci : next_event couvert par test d’intégration root réel (uevent synthétique echo change > /sys/.../uevent) — plus d’exception PRIVILEGE. Aucune fonction unsafe exposée, zéro as, parsing borné get(). 3 cibles cargo-fuzz (uevent/sysfs/input ; input 5,5 M runs, 0 crash). Production 100 % lignes+branches hors 1 exception STRUCTURAL (write_attribute partiel, écriture sysfs atomique ADR-035) ; résidus = code de test (ADR-035 TEST-HARNESS). Ajout couche 1 : air-base-lib::AirInstant::from_monotonic_micros (horodatage evdev ; air-base-lib reste 100 %). Piloté par agents Claude Code headless sur speedy (gate = barrière x86 speedy+carbon + CI ARM). Couche 1 : 6/8 crates cœur implémenté (air-base-lib cœur + air-crypto + air-socket + air-filesystem + air-memory + air-device).
2026-06-24 — 🎯 JALON : air-memory livré (PR #88, par carbon) — allocateurs spécialisés + comptabilité de la couche 1. 5 sections : AirMemoryTracker/AirMemoryUsage (comptabilité per-composant atomique, opt-in, peak monotone via fetch_max) ; AirArena (bump O(1), alignement align_offset/checked_*, contrat gravé : reset/Drop n’exécutent PAS le Drop des valeurs, OOM par try_reserve sans abort, unique unsafe interne avec // SAFETY: par bloc) ; AirObjectPool/AirPooled (Rc/RefCell, croissance sans plafond, RAII respecte Drop) ; AirSlab/AirSlabKey (slots stables anti-ABA, génération wrapping_add u32, zéro unsafe) ; AirBacking (heap ; mmap différé, API non-cassante). Mono-thread (&mut self), aucune fonction unsafe exposée, arithmétique défensive (zéro as). Zéro dépendance externe, ZÉRO surface C. Tests unitaires + property-based + Miri (incl. test concurrent 8 threads sur le tracker → zéro UB/data-race). Couverture 100 % lignes+branches hors 1 exception STRUCTURAL (bras None du take() dans Drop for AirPooled, inatteignable par construction, gardé car un Drop ne doit pas paniquer ; doc in-code ADR-035). Décisions ratifiées : slab wrapping_add, pool sans plafond, peak monotone. 1ère crate couche 1 entièrement pilotée par agents Claude Code headless sur carbon (orchestration multi-machines ; gate de merge = barrière x86 speedy+carbon + Miri + CI ARM, ADR-037). Couche 1 : 5/8 crates cœur implémenté (air-base-lib cœur + air-crypto + air-socket + air-filesystem + air-memory).
2026-06-16 — 🎯 JALON : air-filesystem livré (PR #87, par carbon) — opérations filesystem de la couche 1. 6 sections sur la couche 0 (famille fs + fs::inotify, types air-sys-types::fs) : chemins canoniques & confinement resolve_within (anti-path-traversal par les flags kernel RESOLVE_BENEATH/RESOLVE_NO_MAGICLINKS d’openat2, sécurité par construction) ; écriture atomique (tmpfile + fsync fichier + parent + rename) ; AirTempDir RAII (Drop récursif non-paniquant, symlink-safe) ; copie copy_file_range + repli read/write transparent, copy_tree borné (anti-boucle de symlink) ; watchers sur inotify (debounce, récursivité, corrélation move par cookie, Q_OVERFLOW → variante dédiée Overflow = zéro-perte ADR-032) ; recherche bornée (read_dir, glob maison sans dépendance, find_regex différé). Zéro dépendance externe ajoutée, ZÉRO surface C (gate check-c-surface vert en défaut et --all-features). Correction de contrat : la note spec « inotify à ajouter en couche 0 » était périmée (inotify y est ; décodage inotify_event déjà fait/fuzzé en couche 0 → couche 1 consomme des événements décodés, ne re-parse pas). Tests unitaires + property-based (glob vs oracle, bornage, idempotence) + intégration (confinement, atomicité, copie, watcher inotify réel) + 2 cibles cargo-fuzz (fuzz_glob_match, fuzz_watch_mapping ; 0 crash). Couvrable VIDE (exclusion couverture retirée → air-filesystem mesuré ; lignes ~97 %, les rares bras non couverts sont des gardes défensives STRUCTUREL/ENV documentées in-code, ADR-035). Décision déférée (à acter) : regex (feature search-regex) refusé par la règle des 80 % → find_regex renvoie Unsupported (glob couvre le courant) ; exception nommée vs report définitif laissé à Thierry. Couche 1 : 4/8 crates cœur implémenté (air-base-lib cœur + air-crypto + air-socket + air-filesystem).
2026-06-16 — 🎯 JALON : air-socket livré (PASSE 2, PR #86) — sockets typés + résolution enfichable + client DNS maison. Sockets synchrones sûrs sur la famille net couche 0 : TCP (AirTcpSocket/Listener, connect_to_host IPv6-first), UDP (AirUdpSocket), Unix stream/listener/datagram + passage de FD SCM_RIGHTS (capability AirCom, ADR-001). OwnedFd RAII, as_fd() seam couche 2, zéro unsafe exposé. Résolution maison enfichable et ordonnée (AirNameResolver, sources [hosts, DNS], IPv6-first/IPv4-fallback) — sans libc/NSS/getaddrinfo. Client DNS maison RFC 1035 (AirDnsSource, A+AAAA, UDP + repli TCP sur bit TC, timeout 5 s/2 essais via poll MSG_DONTWAIT+clock_nanosleep). Parseur DNS = surface de sécurité (Principe 3) : slicing borné get(), pas de suivi de pointeur de compression (anti-boucle par construction), checked_* ; fuzz cargo-fuzz obligatoire (fuzz_dns_parse, smoke 300 000 exécutions, 0 crash). Tests unitaires + property + intégration loopback (TCP/UDP, Unix + passage de FD, serveur DNS en-process + repli TCP). AirNetlinkSocket RETIRÉ (décision 2026-06-15) : la couche 0 scellée n’a pas de socket netlink générique → futur crate air-netlink (couche 2, RFC netlink couche 0 requis). Zéro dépendance externe, zéro surface C (check-c-surface vert). Couche 1 : 3/8 crates cœur implémenté (air-base-lib cœur + air-crypto + air-socket).
2026-06-15 — 🎯 JALON : air-crypto livré (PR #80, merge f7057dd) — cœur crypto moderne de la couche 1. 7 sections (random/keys/hash/hmac/aead/kdf/asymmetric) sur RustCrypto/dalek, purs Rust, ZÉRO surface C (gate check-c-surface vert, ban cc respecté). XChaCha20Poly1305 recommandé par défaut (nonce 192 bits, anti-mésusage) ; BLAKE3 différé (blake3 tire cc → conflit zéro-C ; AirHashAlgorithm #[non_exhaustive]) ; streaming AEAD différé (frontière de volume documentée). KAT de conformité officiels (RFC 8439/8032/9106, NIST CAVP, RFC 4231/5869/7748) traversant le câblage de prod (seams pub(crate)) ; property-based ; zeroize-au-Drop vérifié ; fuzz×4. Couvrable VIDE (exclusion couverture retirée → air-crypto mesuré, planchers tenus). Exception crypto nommée dans EXCEPTIONS.md (ADR-034/ADR-016). Couche 1 : 2/8 crates cœur implémenté (air-base-lib cœur + air-crypto).
2026-06-15 — air-base-lib : module encoding (base64/hex) livré (PR #83, merge 9474c8b). Base64 (RFC 4648) + hexadécimal, std-only, zéro dépendance, zéro surface C ; 100 % lignes/branches (encoding.rs), property-based + cibles cargo-fuzz de décodage. Surface validée par Thierry ; livré complet d’un coup (squelette jamais mergé seul) → air-base-lib reste à 100 % sur main. 1ᵉʳ crate produit par la machine carbon (relais multi-machines).
2026-06-15 — 🎯 JALON : 1ᵉʳ crate couche 1 implémenté (air-base-lib cœur, PR #73, merge cb480ac). Erreurs / chaînes-chemins / temps, adossés à icu4x 2.2 (compiled_data, données baked-in). 100 % couvrable VIDE : lignes/branches 100 % du code atteignable ; les 2 seuls bras non couverts — erreur des horloges kernel (AirInstant::now/AirDateTime::now_utc) — sont STRUCTURELLEMENT inatteignables (ADR-035), documentés in-code, sans seam d’injection (cohérent avec la politique clock_settime couche 0). 4 cibles cargo-fuzz (from_utf8/normalize/path/locale), property-based (proptest), doctests exécutables. Décisions calendrier validées par Thierry : en couche 1 (sans formatage), le japonais partage la numérotation grégorienne CE et l’hébraïque un mois ordinal ; l’ère (Reiwa…) et le MonthCode (Adar I/II) relèvent du formatage (couche 2). deny.toml admet Unicode-3.0 (icu4x 2.x a quitté Unicode-DFS-2016). Dette d’outillage consignée (à NE PAS résoudre ici) : les gates xtask couvrable-vide/audit-exceptions sont câblés couche 0 (filtre air-sys-*) ; leur extension à la couche 1 se heurte à une collision de basename (time.rs existe en couche 0 ET en couche 1) + recompute du récap ⇒ RFC/tâche séparée, les exceptions couche 1 restant documentées in-code en attendant. Couche 1 : 1/8 crates cœur implémenté (air-base-lib cœur ✓ ; services à venir).
2026-06-15 — 🔒 Politique « zéro surface C/C++ » VERROUILLÉE au gate (PR #81, merge 4544709 ; issue de l’audit 082). Deux verrous : deny.toml [bans] (ban nominatif cc/cmake/bindgen/pkg-config/aws-lc-sys/ring) + cargo xtask check-c-surface (garde-fou générique anti-*-sys, parseur testé, scope cargo tree -e normal,build), câblé en CI supply-chain et dans cargo xtask barrier. Produit vérifié zéro-C (48 crates, 0 *-sys). Faux positif neutralisé : deny.toml [graph] exclude = ["loom"] retire le sous-arbre cfg(loom) → generator → cc (harnais de modèle de concurrence test-only, déjà exempté dans machete) — le ban cc reste actif sur tout autre chemin. Réintroduction future (ex. ring pour air-tls) = exception NOMMÉE dans docs/EXCEPTIONS.md (ADR-024/034).
Date de dernière mise à jour : 2026-06-14 — 🔒 COUCHE 0 v1 — SCELLÉE. Audit de barrière complète vert sur tout le workspace, 2 arches (x86_64 speedy, aarch64 raspi-srv-2) : cargo fmt --check (stable et nightly), clippy --workspace --all-targets -D warnings (0 warning), cargo test --workspace (918 tests, 0 échec), cargo llvm-cov en root (lignes 97,73 % / branches 82,73 % — chaque rouge ⊆ une exception documentée de COVERAGE-EXCEPTIONS.md ; DEFERRED-TOOLING VIDE → couvrable VIDE au sens strict), cargo audit + cargo deny check + cargo machete verts, // SAFETY: sur CHAQUE bloc unsafe du workspace (lacune time.rs corrigée — PR #64), mdbook build FR+EN sans lien cassé. CODE COMPLET : 11 familles (process/fs/mem/signal/time/net/ipc/security/system/device/ebpf) + io_uring 12 Temps (1, 2a–2d, 3a–3f, 4) + extensions (MmapRegion, privsep, fs::inotify, affinité CPU). Doc développeur FR + EN. Tag couche-0-v1 (annoté, signé GPG). Caveat de sceau — RÉSORBÉ post-sceau (PR #66 mergée, merge 380d389, prompts 060/061) : les gates qualité sont gravés en cargo xtask (5 gates : barrier, couvrable-vide, check-syscalls, audit-exceptions, repro — dev-tooling zéro-dépendance, exclu de la couverture). La reproductibilité ADR-025 est désormais outillée (cargo xtask repro : double-build en env contrôlé, 4 rlibs bit-pour-bit) ; la dette de re-sync des fichier:ligne est outillée (cargo xtask audit-exceptions : format/taxonomie ADR-035 + recompte du récap, échec sur dérive). Ce gate a corrigé une dérive du décompte : registre à 69 entrées production (STRUCTURAL 38, FEATURE-KERNEL 13, PRIVILEGE 10, CHILD-EXIT 8, DEFERRED-TOOLING 0) — CHILD-EXIT 9/total 70 était un sur-comptage introduit au 7b00e75 (055), pas une rangée perdue (décision Thierry, 2026-06-14). cargo xtask check-syscalls certifie par ailleurs 161 numéros de syscalls conformes aux headers uapi des 2 arches (0 écart). Différés assumés (non bloquants) : types epoll spéculatifs (revue fin couche 3), futex synchrone (v2 own-futex air-thread), fanotify, différés io_uring 3a (submit_files_update/msg_ring_fd). Prochaine phase : implémentation couche 1 (air-base-lib cœur d’abord).
Historique — état précédent (2026-06-14, avant sceau)
Taxonomie d’exceptions actée (ADR-035) + 3 candidates COUVERTES — PR #60 mergée (aa47d74) : fs::try_lock, net::get_so_error, process::pidfd_send_signal(Some) ⇒ DEFERRED-TOOLING VIDE / couvrable VIDE au sens strict ; registre recompté (70 entrées production : STRUCTURAL 38, FEATURE-KERNEL 13, PRIVILEGE 10, CHILD-EXIT 9 ; TEST-HARNESS en annexe — CHILD-EXIT 9/total 70 corrigés en 8/69 au 061, sur-comptage débusqué par xtask audit-exceptions) ; ADR-035 companion d’ADR-031. Puis : intro développeur couche 0 (PR #62, reflet Q4 family-security), traduction anglaise couche 0 (18 docs, PR #63).
- Phase : design pré-ouverture publique. Implémentation couche 0 démarrée dans un workspace Rust privé.
- Règle de pilotage actée le 2026-05-27 : le dépôt reste privé tant que la couche 0 n’est pas réellement opérationnelle ; tout travail d’ouverture publique ou de polish associé reste secondaire tant que cet objectif n’est pas atteint.
- Documents fondateurs (vision, charte, principes d’ingénierie) : finalisés en v1.0.
- ADRs fondateurs + ADR-018 imagerie : 18 ADRs (001 à 018, plus ADR-012-bis) finalisés et figés.
- ADRs phase 0 (couche 0, runtime, infrastructure) : série complète, 7 ADRs finalisés (ADR-019 à ADR-025). La zone « design phase 0 » est close.
- Implémentation couche 0 : deux crates privées déjà présentes dans le workspace :
air-sys-types(types fondamentaux) etair-sys-syscall(wrappers syscall). Les 9 familles de syscalls sont implémentées ET couvertes à 100 % (lignes + branches) hors exceptions documentées, validées sur x86_64 (speedy) ET aarch64 (raspi-srv-2) au 2026-06-01 :process,signal,time,ipc,mem,fs,net,security,system+air-sys-types. Suite : 371 (air-sys-syscall) + 80 (air-sys-types) + 74 doctests + 2 + 1 intégration, 0 échec, sur les 2 arches. Reste couche 0 :io_uring, famillesdevice/ebpfà spécifier. - Toolchain Rust : pin exacte relevée à
1.96.0(bump depuis1.95.0le 2026-05-31 ; le minimum requis suit toujours la dernière stable utilisée), avec composantsrustfmt,clippy,rust-src,llvm-tools-previewet targets Linuxx86_64-unknown-linux-gnu+aarch64-unknown-linux-gnu. Motivation : conserver un dépôt Linux-first sur des versions stables récentes, débloquer l’outillage moderne (cargo-deny,cargo-audit,cargo-machete) et bénéficier du linkerrust-lldpar défaut surx86_64-unknown-linux-gnu. - Specs couche 0 : toutes les familles sont spécifiées (2026-06-11). 11 familles (
process,fs,mem,signal,time,net,ipc,security,system,device,ebpf) + io_uring (maître + 13 Temps, FR+EN) + air-sys-types (partiel). Les deux derniers trous —device(uevent/evdev, parsers zéro-alloc, sysfs→fs) etebpf(bpf() exhaustif 37 cmd + perf_event_open) — sont comblés. Reste à produire : la spec exhaustiveair-sys-types(type par type), en parallèle de l’implémentation. Socle de bindings tranché : syscalls directs viacore::arch::asm!, sans dépendance externe (pas derustix). - Documents de setup (phases A à D) : finalisés.
- Macro-architecture : passe 1 (nettoyage) appliquée le 2026-05-20. Section 2 (couche 0) alignée sur les ADRs phase 0 (019-022) et sur le découpage en deux crates
air-sys-types+air-sys-syscall. Toutes les références prospectives ADR-018 à ADR-026 neutralisées en marqueurs neutres. Passe 2 (complétion sections couches 1-5) à mener plus tard en parallèle de l’implémentation. - Documentation infrastructure :
README.md,README-fr.md,LICENSE,.gitignoreprésents à la racine ; toute la doc (dontdocs/EXCEPTIONS.md,docs/COVERAGE-EXCEPTIONS.md,docs/JOURNAL.md) est consolidée sousdocs/(hygiène documentaire 2026-06-01). Restent à produire :CONTRIBUTING.md,SECURITY.md,CODE_OF_CONDUCT.md,CHANGELOG.mdet les guides/conventions dérivés. - Dépôt GitHub :
air-desktop-project/air, privé, créé le 2026-05-20. Premier commitb3737d6signé GPG (cléA9F56C4D9F59EE03), DCOSigned-off-by, verified par GitHub (reason: valid). Branchemainpoussée surorigin/main. Compte GitHub fondateur :tdelhaise. - Durcissement CI — PR #10 mergée le 2026-06-02 (
3f1034esurmain). Corrige le flaky des 3 testspidfd_*(défaut d’observation de test, pas une course de production : le numéro de fd fermé était réutilisé par un autre thread avant l’observationEBADF). Correctif côté test, API gelée : double défense par construction — dup vers un fd haut (min(soft NOFILE−1, 4096), non réutilisable par les opens concurrents qui prennent le plus petit fd libre) +FD_OBSERVATION_LOCKsérialisant les 3 frères. Plus purge.profraw(cargo llvm-cov clean) avant chaque mesure de couverture et doc du bruit landlock bénin (docs/CI.md). Couverture inchangée (96.61 % lignes / 78.79 % branches — baseline avec exceptions). 3 runs CI verts consécutifs sur les 2 arches. Réserve notée : l’immunité du fd haut tient tant que la suite ne dépasse pas le repère haut — à revisiter si un test ouvre des milliers de fd concurrents. - Incident opérationnel
raspi-srv-2— détecté puis RÉSOLU (2026-06-02). Pendant PR #10, raspi ne pouvait plusgit fetch(remote inaccessible) : machine figée sur un vieux SHA (202e1d9, sansfs.rs),reset --hard origin/mainen échec silencieux, validation initiale sur code périmé (faux « vert », rattrapé via unE0432) ; contournée alors par rsync. Réparé par Thierry : remote SSH + clé de déploiement dédiée —git remote set-url origin git@github.com:air-desktop-project/air.gitpuisgit config core.sshCommand "ssh -i ~/.ssh/air_deploy -o IdentitiesOnly=yes".git fetchfonctionne. Réflexe conservé : après chaque délégation, vérifier le SHA réellement bâti — au moment du fix raspi était encore àc01f3b8(PR #9) et doit re-fetch pour atteindre3f1034e(PR #10). - Renommage ADR-029 — PR #11 mergée le 2026-06-02 (
d1ce8adsurmain). Types façade + paramètres des crates couche 0 dé-abrégés (refactor mécanique, comportement préservé, couverture maintenue, barrière verte 2 arches). Suite : champs publics façade (PR #12), puis io_uring Temps 1. Cette session lande la spec io_uring (maître + Temps 1–4, FR+EN, ADR-028/029) jusque-là non commitée.
Structure physique du dépôt : racine = README.md, README-fr.md, LICENSE, CLAUDE.md, AGENTS.md, .gitignore, Cargo.toml, Cargo.lock, rust-toolchain.toml, docs/, crates/, target/. Toute la documentation vit sous docs/ (y compris EXCEPTIONS.md et COVERAGE-EXCEPTIONS.md, déplacés le 2026-06-01) ; tous les ADRs vivent dans docs/adrs/. Les documents de cadrage et le workspace couche 0 cohabitent dans le même dépôt privé.
Prochain chantier
Implémentation couche 1 — air-base-lib (cœur) d’abord.
🔒 La couche 0 est SCELLÉE (v1, 2026-06-14, tag couche-0-v1). CODE complet
(11 familles + io_uring 12 Temps + extensions), barrière complète verte sur les
2 arches, 100 % de couverture hors exceptions documentées (couvrable VIDE au sens
strict, ADR-035), doc développeur FR + EN. Le socle syscall n’évolue plus que par
RFC (ADR-015). La phase couche 0 est close.
Prochain chantier : démarrer la couche 1, en commençant par le cœur de
air-base-lib (erreurs, chaînes/chemins, temps — cf.
specs/layer-1/air-base-lib-core.md), au-dessus du socle couche 0 gelé.
En parallèle / hors phase : outillage xtask qualité (dont
xtask audit-exceptions pour re-synchroniser les renvois fichier:ligne du
registre de couverture — dette ouverte au sceau), et la spec exhaustive
air-sys-types type par type si besoin pour la couche 1.
Différés assumés de couche 0 (non bloquants, revus en temps voulu) : types
epoll spéculatifs (revue fin couche 3), futex synchrone (v2 own-futex
air-thread), fanotify, différés io_uring 3a (submit_files_update/
msg_ring_fd).
Backlog priorisé
Court terme (avant ouverture publique)
- Famille
ipc. Prochain chantier (cf. ci-dessus). - Famille
memminimale utile. Priorité suivante :mmap_anonymous,mmap_file,munmap,mprotect,memfd_create. - Famille
fsminimale.openat2/openat,read,write,pread,pwrite,statx,lseek,fsync,fdatasync. io_uring— Temps 1. À démarrer une fois les familles synchrones minimales en place, conformément à ADR-022.- Documents d’ouverture publique (
CONTRIBUTING.md,SECURITY.md,CODE_OF_CONDUCT.md,CHANGELOG.md) : explicitement secondaires tant que la couche 0 n’est pas réellement opérationnelle.
Moyen terme (en parallèle de l’implémentation)
- Révision
architecture/macro-architecture-fr.md— passe 2 (complétion). Enrichir sections couches 1 à 5 avec les détails issus des ADRs et specs phase 0+ une fois la couche 1 entamée. La passe 1 (nettoyage) est faite ; la passe 2 reste à mener. - Spec détaillée du module io_uring (9 documents). Voir backlog
INDEX.md. À produire au niveau de détail des autres specs de familles. Réf. ADR-022 +specs/layer-0/io-uring-overview.md. - Spec exhaustive de
air-sys-types. Compléterspecs/layer-0/air-sys-types.mdtype par type (méthodes, invariants, tests). - Documents communauté :
docs/guides/first-contribution.md,docs/guides/getting-started/.
Plus tard
- Documents internes mainteneurs :
docs/maintainers/release-process.md,moderation.md,onboarding-maintainers.md,cve-handling.md. - ADRs services système couche 5 à produire : screencapture, appearance/theming, share, firewalld, power, printd/bluetooth/nfc, prefs, keychain. Numérotation à assigner au moment de l’instruction (≥ ADR-026, sans pré-réservation).
- ADRs ouverts identifiés dans le registre : logging/observabilité au-delà de journald, mise à jour atomique du système, App Store Air, protocole privé AirCom
air-wm↔ apps natives, politique d’accès des agents IA à l’arbre sémantique et aux captures vectorielles.
Dettes connues
- Protections de branche GitHub non actives. Le tier gratuit GitHub Free bloque l’API de protection de branche sur les repos privés d’organisation. Conséquence : actuellement, aucune protection contre force-push ou suppression de branche sur
main; aucune exigence d’enforcement côté GitHub des signatures (la signature reste appliquée localement par discipline et vérifiée à l’affichage). Mitigations actuelles : Thierry seul mainteneur, tous les commits signés GPG + DCO par convention. Résolution prévue : soit passage en GitHub Team payant si jugé utile avant l’ouverture publique, soit attente automatique de l’ouverture publique (le repo public sur Free débloque les protections). À réévaluer quand le projet approchera de l’ouverture publique ou s’il commence à avoir des collaborateurs externes. - Spécifications phase 0 — COMBLÉES (2026-06-11). Les deux dernières familles couche 0 manquantes sont désormais spécifiées au niveau des autres :
- Famille
device:docs/specs/layer-0/family-device.md— socket netlink uevent + itérateurclé=valeurzéro-alloc, evdev (ioctls dédiésEVIOC*+ lecture typéeinput_event), sysfs renvoyé àfs. Consommée parair-wm/air-console(ADR-007). - Famille
ebpf:docs/specs/layer-0/family-ebpf.md— bpf() exhaustif (37 sous-commandes) +perf_event_open/ioctls, RAII, charge des programmes déjà assemblés (logique → couche 1). - Reste : l’implémentation de ces deux familles (specs prêtes à pousser). Traductions EN à produire en passe ultérieure (comme io_uring).
- Famille
- Socle de bindings — TRANCHÉ (2026-05-31, dette résolue). La couche 0 appelle les syscalls directement via
core::arch::asm!, sans dépendance externe (pas derustix).rustixavait été évoqué un temps comme hypothèse de travail dans la macro-architecture early-stage ; la piste est abandonnée. Trace historique conservée dansmacro-architecture-fr.md(section « Socle de bindings »). - Cohérence macro-architecture passe 2. La passe 1 a aligné la section 2 (couche 0). Les sections couches 1 à 5 contiennent encore des détails datant de la rédaction early-stage qui ne sont plus précis par rapport aux ADRs et specs ultérieurs (par ex. nomenclature
AirEventvs runtime async ADR-023, articulationair-aircomcôté couche 2 vsio_uring, etc.). À traiter dans une passe 2 menée en parallèle de l’implémentation couche 1, quand le détail concret existera. madviseio_uring + futex (différés de 2a/2c) — RÉSOLUS (2026-06-12, PR #31).submit_madvise(OP 25) etsubmit_futex_{wait,wake,waitv}(OP 51/52/53) exigeaient un handle de vivacité partagé sur le mapping (slot S1 empêchantmunmaptant qu’une op est en vol). C’est désormaisMmapRegion/MmapRegionLiveness(air-sys-syscall::mem, refcountArc, prouvé Miri+loom) —Mappingreste inchangé. Les deux ops sont branchées ; nommageMapping/MmapRegionet signature futex (&MmapRegion+offset) réconciliés.- Hygiène doc couverture Temps 1 io_uring. Le diagnostic 2a (rapport 017) a relevé ~5 branches/lignes STRUCTURAL préexistantes (non introduites par 2a) du cœur Temps 1 non encore tabulées individuellement dans
COVERAGE-EXCEPTIONS.md: gardesok_or(EINVAL)?debuild(i32::try_from(fd),NonZeroU32::new(cq_entries)) et dering_sizes(checked_add/checked_mulde débordement de tailles d’anneau, fuzzées viaring_sizes_decode), et le brasNonedeleak_forget. Toutes inatteignables par construction (valeurs kernel jamais hors-borne). Tolérées par le plancher CI 96/78 ; à tabuler explicitement dans une passe d’hygiène doc (orthogonale à la livraison 2a). - Additions couche 0 découvertes en spécifiant les couches supérieures (2026-06-11, étendu 2026-06-12) — la méthode « doc-d’abord » paie : spécifier la couche 1 révèle des primitifs couche 0 manquants, désormais spécifiés (specs landées) et à implémenter :
fs::inotify(docs/specs/layer-0/family-fs-inotify.md) —inotify_init1/add_watch/rm_watch+ décodage zéro-alloc desinotify_event. Prérequis deAirFileSystemWatcher(air-filesystem, couche 1).processprivsep (docs/specs/layer-0/family-process-privsep.md) — IMPLÉMENTÉ (2026-06-12, PR #34) :setgroups/setresgid/setresuid+getresgid/getresuid+ typesUid/Gid/ResUid/ResGid. Test de sécurité vert (enfant forké : après réduction versnobody, regain root/gid →EPERM, réajout de groupe →EPERM; saved-set figé). Couverture 100 % hors exceptions, 2 arches. Débloqueair-process::drop_privileges. (NumérosetresgidARM64 corrigé 143→149 ; constat :Uid/Gidetsetuid/setgidn’existaient pas — types créés par cette PR.)MmapRegion(docs/specs/layer-0/family-mem-mmap-region.md) — IMPLÉMENTÉ (2026-06-12, PR #31) : mapping partageable refcounté + garde de vivacité (Miri+loom). A débloqué et branchésubmit_madvise(2a) ETsubmit_futex_*(2c) ; signature futex et nommageMapping/MmapRegionréconciliés. (Reste à implémenter dans cette liste :fs::inotifyetepoll.)- Famille
epoll(découvert au Temps 2c) —EpollOp/EpollEventont été ajoutés àair-sys-types::io_uring(consommés par la façadesubmit_epoll_ctl), mais aucun wrapperepoll_create1n’existe en couche 0 : l’epfd des tests 2c est créé par unepoll_create1brut test-only. Si d’autres consommateurs apparaissent, produire une petite familleepoll(epoll_create1+ ré-hébergerEpollOp/EpollEvent) — à côté defs::inotifyetMmapRegion.
- Réconciliation macro-architecture (passe 2) — points précisés (2026-06-11). En complément de la dette « cohérence macro-architecture passe 2 » ci-dessus :
AirLog/AirId128(couche 1) n’utilisent PASsd-journal/sd-id128(couche 2) : socket natif journald (/run/systemd/journal/socket) et lecture directe de/etc/machine-id. Corriger les mentions « via sd-journal/sd-id128 » demacro-architecture-fr.md(≈ l.311, 313, 624, 625, 1547).- Renommage crates couche 1 (ADR-029, explicite, sans abréviation) :
air-fs→air-filesystem,air-proc→air-process,air-mem→air-memory. Mettre à jour la liste de crates demacro-architecture-fr.md. Les familles couche 0 (fs/mem/net/ipc) restent inchangées (acté).
- Couche 1 — specs COMPLÈTES 8/8 (2026-06-11 → 2026-06-12).
air-base-lib(cœur + services),air-filesystem,air-memory(allocateurs arena/pool/slab + comptabilité per-composant ; mono-thread ; backing heap enfichable),air-process(synchrone, pidfd,drop_privileges= orchestration + politiques fournies),air-thread(threads + synchronisation, backendstd::syncv1),air-socket(sockets + résolution de noms enfichable, client DNS maison),air-crypto(primitives RustCrypto) etair-device(énumération/surveillance/identité natives sysfs+uevent) spécifiés (API Rust ; ABI C différée, cf. ADR-027). Les 8 crates de la couche 1 sont désormais spécifiées. Specs landées sousdocs/specs/layer-1/. Restent hors crates : le compilateur seccomp déclaratif et le helper Landlock→chemins (côtéair-filesystem). Implémentation à venir (après les Temps io_uring restants). - ADR-033 acté (modèle de configuration, 2026-06-12). Source typée canonique → compilateur validant (barre couches 0/1, fuzzé) → artefact binaire reproductible schema-first (version + checksum) consommé par le runtime ; projections texte JSON/YAML/TOML en import/export ; sûreté par générations + switch atomique + rollback ; backends de compilation enfichables = joint d’indépendance (unit files systemd générés et validés pour V1, jamais une dépendance dure). L’intégrité des binaires est explicitement hors périmètre → renvoyée à un futur ADR (dette identifiée).
air-devicenatif — dette ADR-005 à amender (2026-06-12).air-devicedécide énumération/surveillance/identité natives (sysfs + uevent kernel), zérolibudev/udevd; surface en vocabulaire Air (joint), evdev au niveau périphérique (interprétation → compositeur, hors périmètre), 3 sous-systèmes typés (net/block/usb) + générique. Dette : la ligne «libudeven couche 1 » d’ADR-005 est à amender (pré-public : édition directe ou RFC ADR-015) ; la règle du joint (« vocabulaire Air, vérif CI no-systemd-types ») reste à outiller plus tard.air-thread— décisions gravées + 2 nouveaux manques couche 0 (2026-06-12). Le backend de synchronisation (AirMutex/RwLock/Semaphore, sans poisoning) eststd::syncpar expédient v1 ; migration planifiée vers une implémentation futex maison (« on maîtrise ce qui s’y fait ;stda ses propres contraintes, pas les nôtres »). Cela révèle 2 primitifs couche 0 manquants (à côté defs::inotifyetepoll) :sched_setaffinity(prérequis deAirThreadBuilder::cpu_affinity) et un futex synchrone (futex(2)WAIT/WAKEdirect — prérequis de l’implémentation futex maison). (Distinct du futex io_uring déjà livré au 2c : ici un wrapper syscall synchrone classique.)air-socket— client DNS maison à fuzzer (2026-06-12). Décision (override macro-archi) : résolution de noms enfichable et ordonnée (AirNameResolutionSource/AirNameResolver,AirHostsFileSource, client DNS maison sansgetaddrinfo/libc — litresolv.conf, UDP + repli TCP, politique IPv6-first/IPv4-fallback). Le parsing des réponses DNS est de la donnée hostile → fuzzing obligatoire (cargo-fuzz des décodeurs) à l’implémentation.air-crypto— exceptions 80 % +air-tlsséparé (2026-06-12). Zéro crypto maison : RustCrypto (SHA2/3, BLAKE3, Ed25519/X25519, AEAD, HKDF/Argon2),zeroize,subtle(constant-time) sont des exceptions à la règle des 80 % (Principe 6, nommées comme icu4x/ADR-016) → à acter dansDEPENDENCIES.md/docs/EXCEPTIONS.mdà l’implémentation. Cœur moderne uniquement (RSA/legacy différés) ; TLS différé à une specair-tlsséparée. Rigueur : vecteurs de test connus (RFC/NIST) + fuzzing des décodeurs.- io_uring 3a — différés (dette, PR #40, 2026-06-12). Deux opcodes non livrés au Temps 3a, par décision BDFL :
submit_files_update(op 20, mise à jour asynchrone de la table de FD fixes dans le flux) etmsg_ring_fd(MSG_SEND_FD) — plomberie d’op async (slab S1 d’un tableau de FD) tangentielle à la registration. La mise à jour synchrone (FixedFdTable::set/clear, register opcode 14) couvre le besoin immédiat. À reprendre avec le runtime async (ou un addendum 3a-bis). (Écarts kernel 3a — headers bornés à l’opcode 26,register_clock(Realtime)→EINVAL [variante conservée], CLOEXEC refusé sur descripteur direct,register_napi→EINVAL hors NIC/NAPI — documentés dans la spec 3a §13 bis etCOVERAGE-EXCEPTIONS.md.) - io_uring —
io-uring-overview.mddécrit la sémantique soft/hard À L’ENVERS (dette, PR #44, 2026-06-12). L’ancien overview inverse soft (IO_LINK) et hard (IO_HARDLINK). La référence correcte est la spec 3c §2 (vérifiée contreio_uring_enter(2)et confirmée sur kernel réel 6.17) : soft rompt la chaîne sur erreur de complétion (short read inclus), hard ne rompt pas. À corriger dansio-uring-overview.mdlors de sa réconciliation (son rôle d’inventaire est déjà repris par le doc maître ; pas bloquant).
Historique des sessions
Sessions 2026-06-16 → 2026-07-08 — Sceau couche 1, campagnes libc, PAL chantiers A/B (résumé de rattrapage)
Le suivi fin a été tenu dans etat-avancement.md (doc vivant) et la
mémoire agent ; cette entrée consolide l’arc pour l’archive. Détail décisionnel dans les ADRs
062→076.
- Couche 1 SCELLÉE
couche-1-v1.0(2026-07-03, ADR-062) puis re-scellée en additif jusqu’àv1.6: errno_location (ADR-065), signaux/futex (ADR-066),air-account(ADR-067), credentials +drop_to_user(ADR-068), campagne réseau registreair-handle+ moteur socketair-socket(ADR-069). - Toit libc C-ABI (scopée OpenSSH) : M0→M4 (crt/errno, str/mem, malloc+printf fuzzé, FILE*,
fileio, signaux+pthread, temps, comptes passwd/group/shadow, identifiants) + surface socket
complète (30 symboles
libair_c.so: inet, cycle de vie, transfert, sockopt, getaddrinfo, poll/select,sendmsg/recvmsg/SCM_RIGHTS). Descellements couche 0v1.10(ADR-070 sockaddr + ADR-071 sockopt). CI basculée x86 primaire + cross-check/natif aarch64 (ADR-072). - Doctrines : configuration binaire (ADR-073), vision
air-sshd(ADR-074 — la vraie cible ; OpenSSH-compile n’était qu’une fonction de forçage couche 1), fourniture du PAL (ADR-075). - PAL chantier A COMPLET : 6 briques additives couche 1 (Condvar, Once, os_str, env-mutation,
yield/name, destructeurs
thread_local). Bug de fondation aarch64 corrigé (gdb : le TCB 64 o recouvrait la zone TLStpidr_el0+16) — validé 2 arches. - PAL chantier B — option A (ADR-076) :
stdnatif sur la libc d’Air (pas la glibc) ; cibleenv=musl+vendor=air.build-std=stdcompile entièrement pour Air, zéro patch. Cible de production : compléterlibairpour lierstd→hello-stdon-target 2 arches.
Session 2026-06-15 — Extension additive du sceau couche 0 → couche-0-v1.1 (PR #77)
Réouverture délibérée et additive du sceau couche 0 (décision BDFL),
pilotée par les contrats couche 1 — découverte en Passe 2 d’air-base-lib
(073) : sa table From<Errno> mappe ETIMEDOUT/EPIPE/ECONNRESET, que
air_sys_types n’exposait pas (29 constantes ; ETIME 62 ≠ ETIMEDOUT 110). Ni
amendable sans réouverture (couche 0 scellée), ni « inventable » en numéros bruts
en couche 1 (ADR-032) → arrêt-signal (073), puis cette extension. PR #77 mergée
(merge commit 7d03932, code 32a1f13 signé GPG + DCO), barrière complète verte
2 arches (la barrière du nouveau sceau). Re-sceau : tag couche-0-v1.1
(signé GPG) sur le merge commit ; couche-0-v1 conservé comme jalon d’origine.
Constantes ajoutées (valeurs asm-generic, identiques x86_64/aarch64), chacune
justifiée par un contrat landé, rien de spéculatif :
ETIMEDOUT(110),EPIPE(32),ECONNRESET(104) —air-base-lib§1 (From<Errno>→TimedOut/BrokenResource) ;ELOOP(40),EXDEV(18) —air-filesystem(# Errors) ;- alias
EWOULDBLOCK ≡ EAGAIN(valeur 11), pour le mapping par nom.
Display reste basé sur la valeur brute (aucun bras per-const) → zéro branche
ajoutée ; couvrable VIDE maintenu (lignes 97,74 % / branches 82,73 %), aucune
nouvelle exception ADR-035. Le mapping From<Errno> d’air-base-lib est désormais
satisfiable par nom → la Passe 2 (073) est débloquée.
Session 2026-06-14 (suite) — Désactivation du trailer Co-Authored-By mergée (PR #74)
CI-only (prompts 069/070). PR #74 mergée (merge commit 4f48144, code
41c6cb6 signé GPG + DCO), CI verte 2 arches avant merge (couverture sautée :
couche0=false, ADR-036). Le trailer Co-Authored-By: Claude est désactivé au
niveau du repo via .claude/settings.json versionné
({"attribution":{"commit":"","pr":""},"includeCoAuthoredBy":false}) ;
.gitignore tracke uniquement settings.json (.claude/* +
!.claude/settings.json ; settings.local.json/claude_session.id restent
ignorés). Le Signed-off-by (DCO) est conservé — seul Co-Authored-By est
retiré. Décision : pas de réécriture de l’historique ; les commits passés (qui
portaient le trailer jusqu’à #65) sont conservés tels quels. Le comportement
était un défaut client de Claude Code (auto-attribution suite à une mise à
jour), pas une consigne du projet : Claude Code n’est en aucun cas l’auteur ;
c’est un outil d’automatisation, comme l’est un compilateur. Vérification
définitive de la prise d’effet : les commits du prompt 071 (session fraîche
post-merge) ; sinon bug client #18253 → repli ~/.claude/settings.json.
Session 2026-06-14 (suite) — Filtrage par chemin de la re-vérification couche 0 mergé (PR #70, ADR-036)
CI-only (prompts 064/065). PR #70 mergée (merge commit 200b374, code
9321036 signé GPG + DCO), CI verte 2 arches avant merge. ADR-036 landé
(filtrage par chemin de la re-vérification d’une couche scellée ; SUMMARY/INDEX/
registre v3.4). La couche 0 scellée n’est plus re-mesurée (couverture
lignes/branches) sur les PR qui ne touchent pas couche 0 / toolchain / CI /
politique de couverture : job changes (git diff, zéro nouvelle dépendance
d’action) → variable FULL ; étapes de couverture gatées if: env.FULL == 'true'. Re-vérification COMPLÈTE toujours garantie : (a) PR touchant ces
chemins, (b) merge push→main (main jamais non vérifié — dérive de
toolchain bloquée au merge ; confirmé : le run post-merge sur main a re-mesuré
la couverture, vert 2 arches), (c) planifié hebdomadaire (schedule cron
lundi 04:00 UTC). Une PR couche 1 pure garde fmt/clippy/cargo test --workspace (couche 0 bâtie + testée), couverture sautée. Mécanique de
déterminisme de couverture inchangée (purge .profraw, sudo -n, restauration
de propriété de target/) — ne tourne que dans la branche complète. Doc :
docs/CI.md (section « Filtrage par chemin… ADR-036 »).
Session 2026-06-14 (suite) — sccache branché sur les runners mergé (PR #68)
CI-only (prompts 062/063). PR #68 mergée (merge commit 4541cbd, code
82ff55d signé GPG + DCO), CI verte 2 arches avant merge. sccache comme
RUSTC_WRAPPER sur le job test-coverage (store hors target/,
$HOME/.cache/sccache → survit au git clean) : install par binaire pré-bâti
musl à SHA256 épinglé (v0.10.0, fallback cargo install signalé). La
recompilation de la couche 0 scellée par clippy/test est éliminée —
100 % de hit à cache chaud sur le Pi (53/53, 0 miss) ; durée test-coverage
Pi 6 m 23 s (froid) → 5 m 46 s (chaud). Les passes llvm-cov en root sont
volontairement non cachées (choix (b), ADR-031 : sous sudo sccache viserait
le cache de root, et -C instrument-coverage se cache mal) → gros gain restant
= ADR-036 (filtrage par chemin de la re-mesure de couverture). Garde-fou : le
gate cargo xtask repro neutralise RUSTC_WRAPPER (preuve de déterminisme bâtie
sans cache, vérifié). Doc : docs/CI.md (section « Cache de compilation —
sccache »).
Session 2026-06-14 (suite) — Gates qualité gravés en cargo xtask mergés (PR #66) + bascule doc
Post-sceau (prompts 060/061). PR #66 mergée (merge commit 380d389,
code def6c1a signé GPG + DCO), CI verte sur les 2 arches avant merge.
Crate xtask (dev-tooling, membre du workspace, zéro dépendance externe
→ Cargo.lock du produit et repro ADR-025 intacts ; exclue de la couverture via
--exclude xtask, non soumise au 100 %). 5 gates (sortie structurée +
--json, exit ≠ 0 sur violation, pour CC et la CI) :
barrier: un seul point d’entrée (fmt stable+nightly, clippy-D warnings, test, couverture lignes+branches root, audit/deny/machete, pré-filtre// SAFETY:advisory, cohérenceCargo.toml↔DEPENDENCIES.md) — VERTE.couvrable-vide(anti-gaming) :llvm-cov⨯ registre. Gate DUR = plancher (lignes 97,73 % / branches 82,73 %) ; réconciliation par symbole/ section/proximité ADVISORY (la reachability n’est pas mécaniquement décidable, cf.CI.md) ; sépare prod / test-resident.check-syscalls: numéros déclarés (// SAFETY: SYS_x (arch = N)+ consts) vs headers uapi des 2 arches (x86_64unistd_64.h, aarch64asm-generic, indirection__NR3264_*résolue) — 161 conformes, 0 écart (garde la classe d’erreur « privsep ARM64 »).audit-exceptions: format/taxonomie ADR-035 + recompte du récap → a corrigé une dérive :CHILD-EXIT 9→8, total70→69(sur-comptage introduit au7b00e75/055, pas une rangée perdue — décision Thierry). Registre désormais à 69 entrées (STRUCTURAL 38, FEATURE-KERNEL 13, PRIVILEGE 10, CHILD-EXIT 8, DEFERRED-TOOLING 0).repro: double-build bit-pour-bit en env contrôlé (SOURCE_DATE_EPOCH,--remap-path-prefix, toolchain pinné) — 4 rlibs identiques → atteste ADR-025.
Parseurs testés sur entrées synthétiques (34 tests). CI : job
supply-chain exécute check-syscalls + audit-exceptions ; étapes llvm-cov
gagnent --exclude xtask. Doc : docs/outillage-xtask.md (+ SUMMARY, CI.md).
Dettes de sceau RÉSORBÉES : reproductibilité ADR-025 outillée (repro),
re-sync fichier:ligne outillée (audit-exceptions).
Session 2026-06-14 (suite) — Taxonomie d’exceptions ADR-035 + 3 candidates COUVERTES mergées (PR #60)
Suite de la passe de consolidation (le CODE de la couche 0 reste complet).
PR #60 mergée (merge commit aa47d74, code 7b00e75 signé GPG + DCO), CI
verte 2 arches.
- 3 candidates « atteignables-mais-dures » COUVERTES par de vrais tests
déterministes (leurs exceptions supprimées) :
fs::try_lockcontention →Ok(false): harnais multi-processus forké (clone3) — enfant détient le verrou exclusif, parenttry_lock→Ok(false), synchro par pipes (sans sleep), récoltewaitid.net::get_so_error(brasval != 0) :connectnon bloquant vers un port loopback réservé-puis-fermé →ECONNREFUSEDviaSO_ERROR, boucle bornée.process::pidfd_send_signal(Some(&info)): brasSomeexercé via un pidfd de soi-même +SignalInfo::new_queue(déjà public — aucun ajout de surface) ; le kernel rejette parEINVAL(si_signo=0 ≠ sig) avant toute délivrance → sûr même vers soi-même.- ⇒
DEFERRED-TOOLINGVIDE / couvrable VIDE au sens strict.
- Taxonomie appliquée à
COVERAGE-EXCEPTIONS.md(ADR-035) :EFAULT-SAFE+VALUE-UNREACHABLEpermanente → STRUCTURAL ;CHILD-EXITconservée ;TEST-HARNESSretiré → annexe « résidus internes aux tests » (hors décompte). Récap recompté depuis les lignes réelles (figures antérieures dérivées) : 70 entrées production (STRUCTURAL 38, FEATURE-KERNEL 13, PRIVILEGE 10, CHILD-EXIT 9, DEFERRED-TOOLING 0). (⚠️ ce recompte a introduit un sur-comptageCHILD-EXIT 9au lieu de 8 — total réel 69, corrigé au061par le gatextask audit-exceptions, cf. session 2026-06-14 « Gates qualité xtask ».) - ADR-035 (« Taxonomie des exceptions de couverture », companion d’ADR-031,
sans l’amender) créé ; registre ADRs (table + historique v3.3),
SUMMARY.md,INDEX.md.
Dette consignée : la re-vérification durable des
fichier:lignedu registre d’exceptions (numéros sujets à dérive) — passer à des renvois par symbole ou un check automatisé (futurxtask audit-exceptions). Prochain chantier → clôture couche 0 : doc d’introduction développeur (introduction-fr.md, encart « dev C/C++ ») + reflet de Q4 (seccompprimitif) dansfamily-security+ réconciliation INDEX/état → traduction anglaise → sceau barrière complète.
Session 2026-06-14 — Consolidation couche 0 : test flaky uevent durci mergé (PR #58)
Première étape de la passe de consolidation (le CODE de la couche 0 reste
complet). PR #58 mergée (merge commit e74d96e, code 04e6a41 signé GPG + DCO),
CI verte 2 arches.
- Correctif test flaky :
device::tests::uevent_read_nonblock_without_event_is_eagain(socket uevent groupe KERNEL NONBLOCK exigeantEAGAIN) cassait par intermittence quand un vrai uevent kernel (hotplug/udev) était diffusé pendant la fenêtre sur un runner partagé. Remplacé paruevent_read_nonblock_returns_promptly_eagain_or_valid_eventqui teste le vrai contrat NONBLOCK : retour prompt = soitEAGAIN(file vide), soit unUEventMessagebien formé (événement parasite décodable) ; jamais bloquer/ paniquer/trame malformée (EINTRtoléré, ADR-021 c.2). Boucle de robustesse (256 lectures/run) ; prouvé non-flaky (20/20). Changement test-only. - Normalisation du registre
COVERAGE-EXCEPTIONS.md(non controversée, aucun reclassement) : la catégorieTEST-HARNESS(2 entrées) était utilisée sans être ni définie (légende) ni comptée (récap) → ajoutée (Total 60 → 62) ; note d’audit + date. - Audit du registre rendu (
053-consolidation-flaky-exceptions.out) : 3 candidates « atteignables-mais-dures » signalées (net::get_so_error,fs::try_lockcontention,process::pidfd_send_signal(Some)) ; proposition de taxonomie (fusionEFAULT-SAFE/VALUE-UNREACHABLE-permanent → STRUCTURAL, garderCHILD-EXIT, retirerTEST-HARNESS, addendum ADR) — décisions BDFL prises ; application au prompt055.
Prochain chantier →
055: appliquer la taxonomie d’exceptions retenue + couvrir les 3 candidates atteignables + addendum ADR-035. Puis : doc d’intro développeur couche 0, traduction EN, sceau barrière complète.
Session 2026-06-13 (suite) — fs::inotify + affinité CPU mergés (PR #55) — 🎉 CODE couche 0 COMPLET
Passe de complétion couche 0 : deux primitifs indépendants révélés par la
spécification de la couche 1, livrés en un PR (modules disjoints d’io_uring).
PR #55 mergée (merge commit a009f9b, code b609b87 signé GPG + DCO), CI
verte 2 arches.
fs::inotify(air-sys-syscall::fs::inotify+ typesair-sys-types::fs) : RAIIInotify(IN_CLOEXECpar défaut) ;inotify_init/add_watch/remove_watch/read_events; décodeur emprunté zéro-allocation desinotify_eventde taille variable, zéro-perte (ADR-032) — queue tronquée signalée (InotifyEvents::truncated()), jamais avalée. Syscalls vérifiés uapi 6.12 (init1 294/26, add_watch 254/27, rm_watch 255/28). Tests : intégration kernel réelle (CREATE/MODIFY/MOVED_FROM↔MOVED_TO parcookie/DELETE/IGNORED) + décodeur pur + property-based + fuzz 3 M runs (0 crash). Décodeur 100 %/100 %.fanotifyhors périmètre ; récursivité = couche 1.- Affinité CPU (famille
process, specfamily-process-affinity.mdcréée) :set_cpu_affinity/get_cpu_affinity(Option<Tid>, &CpuSet);None= tâche appelante (pid 0typé) ;CpuSetréutilisé depuisair-sys-types::system. Syscalls vérifiés (setaffinity 203/122, getaffinity 204/123). Tests : round-trip set→get,EINVAL/ESRCH, cross-arch. Débloqueair-thread::cpu_affinity(couche 1). - +1 exception STRUCTURAL (épuisement ressources
inotify_init+ casts défensifsi64→i32). Aucune dépendance de production ajoutée. Aucun écart kernel.
🎉 CODE de la couche 0 COMPLET : io_uring (12 Temps) + 11 familles de syscalls
fs::inotify+ affinité CPU — tous implémentés et mergés pour la cible 6.12 (hors interfaces obsolètes →UNSUPPORTED.md). Plus de code à écrire. Prochain chantier → passe de consolidation couche 0 : audit du registre d’exceptions, doc d’introduction développeur, traduction anglaise globale, réconciliation INDEX/état, sceau barrière complète. Différés conservés (epoll types spéculatifs, futex synchrone v2,fanotify, différés io_uring 3a).
Session 2026-06-13 (suite) — io_uring Temps 4 (accès brut) mergé (PR #53) — 🎉 MODULE io_uring COMPLET
Sous-module air-sys-syscall::io_uring::raw : la soupape de sécurité d’ADR-022
(Décision 1) — manipulation directe des SQE/CQE pour les ~5 % de cas que l’API typée
(Temps 1–3f) ne couvre pas. L’ordering d’anneau reste géré par la façade ;
l’unsafe ne porte que sur le contenu du SQE et la validité des buffers.
PR #53 mergée (merge commit c91c693, code 9fe14fb signé GPG + DCO), CI verte
2 arches.
- Types miroirs
#[repr(C)]:RawSubmissionQueueEntry(#[repr(transparent)]surIoUringSqe, 64 o),RawCompletionQueueEntry(16 o),RawOpcode(u8); assertions statiques de taille/offset (uapi 6.12, identiques x86_64/aarch64) → dérive = échec de compilation. Accesseurs typés (pas de calcul d’offset manuel). - Soumission brute :
raw_get_submission_queue_entry(unsafe) ; publication =submit()/submit_and_wait()(sûrs : store release par la façade). Complétion brute :raw_peek_completion_queue_entry(sûr, load acquire, réf. bornée) +raw_advance_completion_queue(sûr, borné). Capacités SQ/CQ. Aucun pointeur mmap nu exposé. - Coexistence brut/niveau 2 (§5) :
RAW_USER_DATA_TAG = 1 << 63. La moisson gérée court-circuite sur le tag (slab non touché, CQE non consommé) ; le slab borne sa génération à 31 bits (GENERATION_MASK) ⇒ le bit 63 duuser_datagéré est toujours nul → brut et niveau 2 coexistent sur le même ring sans collision.debug_assertdu tag manquant (testé via#[should_panic]). - Tests : layout statique ; intégration kernel (NOP brut ↔
raw_peek/raw_advance; op brute vs wrapper niveau 2 ; coexistence slab+brut sans collision ; SQ pleine →None;advanceborné ; moisson gérée qui s’efface devant une brute en tête) ; Miri (chemins purs : layout, slab génération 31 bits — 16 tests verts) ; fuzz du décodageRawCompletionQueueEntry(cibleio_uring_4_raw_cqe, 2 M runs, 0 crash).raw.rs100 % lignes / couvrable vide (aucune exception propre au Temps 4) ;ring.rs100 %/100 %. - Correctif annexe (ADR-032) : 3 littéraux
Completiondefuzz_api(#[cfg(fuzzing)], non bâti en CI) restés sans le champmultishotdepuis le Temps 3d → la fuzz-cible ne compilait plus ; dette silencieuse purgée. Aucun écart kernel.
🎉 Module io_uring COMPLET : les 12 Temps (1, 2a–2d, 3a–3f, 4) sont implémentés et mergés pour la cible 6.12 (hors interfaces obsolètes →
UNSUPPORTED.md). Prochain chantier → clôture couche 0 :fs::inotify+sched_setaffinity, puis passe de consolidation. Dette : traduction anglaise globale des documents du module io_uring (à planifier).
Session 2026-06-13 (suite) — io_uring Temps 3f (confinement) mergé (PR #51)
Sous-module air-sys-syscall::io_uring::sandbox : confine un ring pour qu’il
ne puisse émettre que les opérations explicitement mises en liste blanche,
imposé par le kernel — matérialise la soundness S3. Brique io_uring du
modèle de capabilities d’Air (ADR-001 AirCom, ADR-010 entitlements signés), en
défense en profondeur avec family-security (seccomp/Landlock). PR #51
mergée (merge commit 39aa92f, code eb494c0 signé GPG + DCO), CI verte 2
arches (coverage root).
RestrictionSet(default-deny,#[derive(Default)]) :allow_op/allow_register/allow_sqe_flags/require_sqe_flags+from_entitlements(point d’intégration couche 5, ADR-010 — la politique vit en couche 5).RegisterOp(énum typée des register-ops, ADR-021 conv. 3 — pas deu8magique) ;SqeFlagSet(drapeauxIOSQE_*, partage la repr. deSubmitOptions).- Flux en 3 temps imposé kernel + reflété par l’API :
IoUringBuilder::restrict→ ringR_DISABLED+REGISTER_RESTRICTIONS;IoUring::enable→REGISTER_ENABLE_RINGS; restrictions immuables ensuite. Default-deny : dès unallow_op, le kernel refuse tout le reste (-EACCES). Le stubENOSYSdebuild()(Temps 1) est remplacé par le vraiREGISTER_RESTRICTIONS(test obsolèterestrict_then_build_is_refused_with_enosysretiré). - Preuve de sécurité (§8) :
openat2REFUSÉ (-EACCES) sur un ring « réseau seul » alors que le process a le droit d’ouvrir le fichier → la voie de contournement de seccomp est fermée. Aussi : opcode autorisé qui passe, default-deny,require_sqe_flagsimposé, soumission avantenablerefusée (EBADFD), immuabilité aprèsenable(refus kernel), propagation d’erreurREGISTER_RESTRICTIONS(simulateur), property-based (tout sous-ensemble encode exactement ses opcodes). - Couverture (root) :
sandbox.rs100 % lignes + branches / couvrable vide ; unique résidu = conversion défensiveusize→u32du nombre de restrictions (STRUCTURAL, inatteignable).raw:REGISTER_RESTRICTIONS(11),io_uring_register_restriction_op(0/1/2/3), miroirio_uring_restriction(16 o, assert). TOTAL lignes 96,2 % / branches lib 78,2 %. Aucun écart kernel. Prochain chantier → io_uring Temps 4 (raw), dernier du module.
Session 2026-06-13 (suite) — io_uring Temps 3e (multi-thread) mergé (PR #49)
Sous-module air-sys-syscall::io_uring::shared : les modèles d’usage
multi-thread du ring, par-dessus le cœur Temps 1, ATTACH_WQ (3a) et
msg_ring (2c). Aucun register opcode propre. PR #49 mergée (merge commit
f9be984, code 43c957a signé GPG + DCO), CI verte 2 arches (coverage root).
unsafe impl Send for IoUringréalisé (ADR-022 D6, documenté depuis le Temps 1 mais jamais implémenté : les pointeurs bruts mmappés rendaientIoUringauto-!Send).// SAFETY:: mmaps/FD process-global,Send= transfert d’ownership exclusif,!Syncpréservé (PhantomData<Cell>). Invariant habilitant de tout le Temps. Surface publique inchangée (impl auto).LockedIoUring(Mutex<IoUring>,Send + Sync) : primitivewith_lock(couvre toute op d’IoUringen&self) + raccourcis nommés fréquents — pas de ~50 forwarders (Principe 7) ; verrou unique, sans poisoning.RingPool(thread-per-core) : le 1er ring crée le pool io-wq, les suivantsATTACH_WQ(workers kernel bornés, Pi 4) ; ring fds enregistrés ;into_rings(un ring possédé par worker, déplacé) ;handle(w) -> RingHandlesubmit_message_ring_data_to(routagemsg_ringcross-thread par fd ; le 2c, qui prend&IoUring, reste inchangé).SqpollIoUring:SETUP_SQPOLLSQ_AFF, soumission sansio_uring_enteren régime établi, réveilNEED_WAKEUPgéré (seule magie cachée, documentée).
- Soundness (cœur du Temps) :
IoUring: !Syncprouvé par doctestcompile_fail,LockedIoUring: Send+Syncpar doctest + assertion statique ; loom sur la discipline de verrou (exclusion mutuelle, aucune mise à jour perdue) ; tests d’intégration concurrents réels (threads). - Couverture (root, après complément
046/PR #49) :shared.rsbranches 100 % / couvrable vide — l’exceptionEFAULT-SAFEsur les brasenterSQPOLL est levée (couverts au simulateur : injection-EINTRsur la couturesyscall::enter, errno remonté tel quel ADR-021) ; résidus = fallbackSQPOLL-EPERMdes tests (PRIVILEGE) + timeout du thread pair (TEST-HARNESS). Flakiness réelle corrigée :locked_io_uring_concurrent_nops(course de complétion) rendu atomique sous un seulwith_lock. TOTAL lignes 96,1 % / branches lib 78,1 %. Aucun écart kernel. Prochain chantier → io_uring Temps 3f (confinement).
Session 2026-06-13 — io_uring Temps 3d (opérations multishot) mergé (PR #46)
Sous-module air-sys-syscall::io_uring::multishot : une unique soumission
produit un flux de complétions (CQE_F_MORE), par-dessus le cœur Temps 1, les
buffers fournis du 3b et les descripteurs directs du 3a. Aucun register
opcode ; opcode dédié READ_MULTISHOT (49) + drapeaux d’op. PR #46 mergée
(merge commit ba26ff9, code 00ccee3 signé GPG + DCO), CI verte 2 arches
(coverage root, raspi a bâti 00ccee3).
- Cycle de vie S1 : slot vivant tant que
CQE_F_MORE, libéré uniquement à la terminale (2ᵉ cas après le NOTIF zero-copy du 2b). Slab étendu (reserve_multishot/is_multishot+ champmultishotdu slot) ;Completionporte un marqueur multishot lu avantcomplete;MultishotToken(sémantique fixée) ;Completion::multishot_token(); génération anti-CQE-tardif prouvée Miri (test pur du slab). - Façades :
submit_accept_multishot[_direct](ACCEPT +ACCEPT_MULTISHOT; variante directe = slot auto-allouéFixedFdTable),submit_receive_multishot(RECV +RECV_MULTISHOT) /submit_read_multishot(READ_MULTISHOT49) sur buffers fournis,submit_poll_multishot(POLL_ADD +POLL_ADD_MULTI, edge),submit_timeout_multishot(TIMEOUT +TIMEOUT_MULTISHOT, ticks),cancel_multishot. Pénurie-ENOBUFSTERMINE le multishot, distincte d’une erreur réseau (testé). - Choix signalés (surface inchangée) :
cancel_multishotutilise l’annulation synchrone (IORING_REGISTER_SYNC_CANCELpar jeton) plutôt que l’opASYNC_CANCEL(cohérent avec-> Result<()>, pas de CQE d’annulation à drainer) ;submit_poll_multishotedge-triggered (la surface ne porte pas d’option level-triggered ;IORING_POLL_ADD_LEVEL=1<<3non exposé). - Couverture (root) :
multishot.rs100 % lignes + branches / couvrable vide ; cycle S1 prouvé sous Miri. TOTAL lignes 97,7 % / branches 81,9 %. 9 tests d’intégration + 2 tests slab purs. Aucun écart kernel. Prochain chantier → io_uring Temps 3e (multi-thread).
Session 2026-06-12 (suite) — io_uring Temps 3c (opérations liées) mergé (PR #44)
Sous-module air-sys-syscall::io_uring::linked : chaînes d’opérations liées
(IOSQE_IO_LINK/IO_HARDLINK + LINK_TIMEOUT op 15), par-dessus le cœur Temps 1.
Aucun register opcode. PR #44 mergée (merge commit 383eb99, code 575d3ab
signé GPG + DCO), CI verte 2 arches (coverage root, raspi a bâti 575d3ab).
LinkedChainBuilderviaring.link_chain():first/then(soft,IO_LINKsur le prédécesseur) /then_hard(IO_HARDLINK) /with_link_timeout(LINK_TIMEOUTbornant le maillon précédent ; émission isolée refusée) /submit() -> ChainTokens.- Mode staging : les
submit_*existants réservent slot + écrivent le SQE (drapeau de lien sur le prédécesseur) sans publier la queue ; publication unique ausubmit()(un seul store-release). Aucune duplication desprepare_*. Réservation atomique : un maillon non réservable (EBUSY) déclenche un rollback (release idempotent + rewind de la tail) — jamais de chaîne partielle publiée. - Sémantique soft/hard CORRECTE confirmée sur kernel réel (6.17) : soft rompt
sur erreur de complétion — un short read COMPTE comme erreur (piège testé
explicitement) → suivants
-ECANCELED; hard ne rompt pas.link_timeoutexpirant (maillon annulé) et non-expirant (timeout annulé) testés. skip_cqe_on_successsur les intermédiaires (seule la finale arrive) ;ChainTokenscorrèle chaque complétion à son maillon. Helpersring.rs(local_tail/or_sqe_flags/rewind_to) ;raw.rs(IOSQE_IO_LINK/HARDLINK,OP_LINK_TIMEOUT).- Couverture (root) :
linked.rslignes de production 100 % / couvrable vide (résidu branches = court-circuits d’assertions de test ; garde STRUCTURAL préexistante deprepare). TOTAL lignes 97,7 % / branches 81,7 %. 14 tests +ChainTokenssous Miri. Aucun écart kernel. Prochain chantier → io_uring Temps 3d (multishot).
Session 2026-06-12 (suite) — io_uring Temps 3b (buffers fournis ring-mapped) mergé (PR #42)
Sous-module air-sys-syscall::io_uring::provided : buffers fournis
ring-mapped (IORING_REGISTER_PBUF_RING 22 / UNREGISTER 23 / PBUF_STATUS
26), par-dessus le cœur Temps 1 et la registration Temps 3a. PR #42 mergée
(merge commit 8ca051c, code e56eba0 signé GPG + DCO), CI verte 2 arches
(coverage root, raspi a bâti e56eba0).
ProvidedBufferRing:register(countpuissance de 2,buf_size; modes app etkernel_mmap),unregister,status,group_id,available. Anneau de descripteursio_uring_buf[count](tailu16@ offset 14, avancé par store-release) + mémoire de données possédée (count×buf_size).- Sélection auto :
submit_receive_provided/submit_read_provided(IOSQE_BUFFER_SELECT, aucun buffer au slot S1) +bundle: bool(IORING_RECVSEND_BUNDLE). Pénurie ⇒ complétion-ENOBUFS. -
- Guard RAII
ProvidedBufferviaCompletion::into_provided_buffer(&mut group) id/data/data_mut;Dropréapprovisionne (réécrit le descripteur, store-release du tail). L’emprunt&mutinterdit le désenregistrement tant qu’un buffer vit (doctestcompile_fail).
- Guard RAII
- Incrémental (
IOU_PBUF_RING_INC) :CQE_F_BUF_MORE⇒ même buffer réutilisé, pas de réappro ; réappro à la complétion finale. Accountingavailableidempotent (out_flagsparbid). - Legacy évacués →
docs/UNSUPPORTED.mdcréé :PROVIDE_BUFFERS(31),REMOVE_BUFFERS(32), +OPENAT(18). - Écarts kernel signalés (spec 3b §9 bis,
COVERAGE-EXCEPTIONS.md) :IOU_PBUF_RING_INC/RECVSEND_BUNDLE/CQE_F_BUF_MOREabsents des headers exécuteurs (uapi 6.11/6.12, validés au runtime 6.17) ; bloc-code spec §3 aligné sur la prose (parambundleajouté à la signature, surface effective inchangée). - Couverture (root) :
provided.rslignes 99,6 % / couvrable vide (résidu = bras?desmmap= EFAULT-SAFE ; gardesbid<count= STRUCTURAL). TOTAL lignes 97,7 % / branches 82,0 %. 9 tests (intégration : recv loopback,-ENOBUFS+réappro,kernel_mmap,status,read/bundle; synthétiques déterministesBUF_MORE/réappro/None; property 256 cycles ; simulateur) + 1 doctestcompile_fail. Prochain chantier → io_uring Temps 3c (opérations liées).
Session 2026-06-12 (suite) — io_uring Temps 3a (registration) mergé (PR #40)
Sous-module air-sys-syscall::io_uring::registration : les 21 register
opcodes (io_uring_register(2), n° 427) du Temps 3a, livrés par-dessus le cœur
Temps 1. PR #40 mergée (merge commit 34c164e, code aefec1a signé GPG +
DCO), CI verte 2 arches (x86_64 speedy + aarch64 raspi-srv-2, coverage root).
FixedFdTable(FILES2/FILES_UPDATE2/FILE_ALLOC_RANGE/UNREGISTER_FILES) :register(sparse)/register_with/set/clear/set_alloc_range/unregister(restitue tous lesOwnedFd— zéro discard) /slot() -> FixedSlot<'t>.- Variantes « direct » :
submit_openat2_direct/accept_direct/socket_directFixedSlotTarget::{Index, Alloc}(Alloc= enum typé, jamais~0Uexposé). Découverte kernel :SOCK_CLOEXEC/O_CLOEXECrejeté (EINVAL) sur un descripteur direct ⇒ non posé (leO_CLOEXECest posé àfixed_fd_install). Usage :submit_read_fixed_file/write_fixed_file(viaFixedSlot),submit_fixed_fd_install(FD direct → FD ordinaire).
-
RegisteredBuffers(BUFFERS2/BUFFERS_UPDATE/CLONE_BUFFERS/UNREGISTER_BUFFERS)register(Vec)/register_mmap(MmapRegionpossédées)/update/clone_from/unregister/slice() -> RegisteredBufferSlice<'b>+submit_read_fixed/write_fixed.
- Ring fd enregistré (RING_FDS/UNREGISTER_RING_FDS) : câblé dans le chemin
io_uring_enter(IORING_ENTER_REGISTERED_RING). eventfd (4/5/7), personality (9/10, +IoUring::with_personality), io-wq (IOWQ_AFF 17/18, MAX_WORKERS 19 →WorkQueueWorkerLimits), NAPI (27/28), horloge (29). - Types ajoutés :
FixedFdTable/FixedSlot/FixedSlotTarget/RegisteredBuffers/RegisteredBufferSlice/Personality/WorkQueueWorkerLimits/NapiConfig/ClockSource(sous-moduleregistration) ;CpuSetcréé dansair-sys-types::system(miroircpu_set_t, 1024 bits ; n’existait pas, ADR-029). - Sûreté :
FixedSlot/RegisteredBufferSliceliés par lifetime → ne survivent pas au désenregistrement (prouvé par doctestscompile_fail— pas de dépendancetrybuildajoutée). Téardown S2 inchangé (kernel auto-nettoie au close ; lesOwnedFdpossédés se ferment au drop, aucune fuite). - Écarts kernel signalés (jamais corrigés en silence, ADR-032) : (1) headers
installés bornés à l’opcode 26 (
IORING_REGISTER_LAST == 27) — NAPI/CLOCK/ CLONE_BUFFERS (27-30) repris de l’uapi 6.12 amont, validés à l’exécution ; (2)register_clock(Realtime)rendEINVAL(io_uring n’accepte que MONOTONIC/ BOOTTIME) — type conservé, rejet testé ; (3)register_napirendEINVALsur les exécuteurs (NAPI non exposé) → chemin succès en exception FEATURE-KERNEL. - Couverture (root) :
registration.rsbranches 100 %, lignes 99.2 % (résidu = corps succès NAPI, FEATURE-KERNEL) ;air-sys-types::system(CpuSet) 100 % branches. TOTAL lignes 97.6 % / branches 81.9 % (planchers 96/78). Tests : 23 intégration/simulateur/bornes + 2 doctestscompile_fail. - Différés (signalés) :
submit_files_update(op 20, mise à jour async de la table dans le flux) etmsg_ring_fd(MSG_SEND_FD) — plomberie d’op async tangentielle à la registration, proposés en suite ; la table se met à jour synchroniquement viaset/clear(register opcode 14). Prochain chantier → io_uring Temps 3b (provided, buffers fournis ring-mappedPBUF_RING).
Session 2026-06-12 (suite) — io_uring Temps 2d (URING_CMD, opcode 46) mergé (PR #37)
Le passthrough io_uring (IORING_OP_URING_CMD, 46) est livré : transmettre
une commande opaque au gestionnaire uring_cmd du fichier visé (socket,
NVMe, ublk…). Sous-module air-sys-syscall::io_uring::cmd, surface validée
(io-uring-2d-cmd.md) non modifiée.
- 4 commandes socket typées sûres :
submit_socket_inq/outq(SIOCINQ/SIOCOUTQ→into_result),submit_getsockopt(buffer de sortie ownership S1 →into_buffer_result),submit_setsockopt(valuedéplacée →completed).level: SocketOptionLevel. - Générique :
unsafe trait UringCommand(cmd_op/cmd_len/encode/Output/interpret) +submit_uring_cmd<C>(vérifiecmd_len ≤ 16, ou≤ 80siSQE128,EINVALen amont — pas de troncature) +submit_uring_cmd_raw(unsafe, dernier recours). Structures NVMe/ublk hors couche 0 (mécanisme seul). - Types :
UringCmdFlags(FIXED= bit 0, seul flag 6.12) ;SocketOptionLevel(air-sys-types::net— n’existait pas, créé, ADR-029 ; la spec le disait « partagéfamily-net» maisfamily-netn’utilisait que desi32bruts). - Bug latent corrigé —
SQE128désormais réellement soumissible : la soumission enSETUP_SQE128était buggée depuis le Temps 1 —SubmissionRing::prepareindexait les SQE parsizeof(IoUringSqe)= 64 o, donc les SQE de 128 o étaient mal placés (stride faux) et partiellement non zéro-initialisés ; seul le setup SQE128 marchait, jamais la soumission (aucun test ne soumettait en 128 o). Corrigé :sqe_sizemémorisé, stride en octets, slot entier zéro-init, provenance mmap pour écrire la zonecmd[]étendue ;submit_opfactorisé ensubmit_filled(pointeur brut). Validé sur kernel réel (round-trip 128 o). Dette « SQE128 soumission » RÉSOLUE (PR #37). - Ergonomie signalée :
UringCommand::interpret(&self)impose au consommateur de conserver une instance de la commande (passée par valeur àsubmit) pour interpréter la complétion — fidèle à la surface validée. - PR #37 (merge commit
b7be2ae), commit8b91ae7signé GPG + DCO. Barrière verte 2 arches : fmt (stable et nightly — incident rustfmt résolu), clippy-D, tests (591 lib),llvm-covroot (lignes 97.4 %, branches 81.6 % ;cmd.rs100 % branches, 0 ligne manquante, couvrable vide), Miri (encodeborné), fuzzio_uring_2d_decode(200 k runs), audit/deny/machete. Prochain chantier → io_uring Temps 3a (registration).
Session 2026-06-12 (suite) — extension couche 0 process privsep livrée (PR #34)
Le 4ᵉ manque couche 0 (le plus prioritaire, sécurité) est comblé : la famille
process reçoit les 6 wrappers indispensables à une réduction de privilèges
correcte (drop_privileges d’air-process, Principe 10).
- 6 wrappers :
set_groups(&[Gid])/get_groups(&mut [Gid]) -> &[Gid](setgroups/getgroups,&[]= largage total, zéro alloc),set_resgid/set_resuid(Option<Gid/Uid>×3 ;None= composante inchangée =(id_t)-1typé, ADR-021 conv. 1) qui fixent le saved-set ⇒ réduction irréversible, etget_resgid -> ResGid/get_resuid -> ResUid(vérif défensive).// SAFETY:sur les 6 wrappers + 12 helpers asm. - Types ajoutés (
air-sys-types::process) :Uid/Gid(#[repr(transparent)]suru32; contrairement àPid/Tid,0= root est valide ⇒ pas deNonZeroI32;ROOT/from_raw/as_raw, types distincts) +ResUid/ResGid. Constat : ils n’existaient pas (nisetuid/setgid). - Numéros de syscalls vérifiés sur l’uapi 6.12 par arche — la spec donnait
setresgidARM64 = 143 (FAUX), corrigé en 149 (headerasm-generic). Retenus : setgroups 116/159, getgroups 115/158, setresgid 119/149, setresuid 117/147, getresgid 120/150, getresuid 118/148. Spec corrigée embarquée dans #34. - Test de sécurité (le cœur), vert en root :
privsep_drop_is_irreversible(enfant forké, observé parwaitid) — aprèsset_resgid(nobody³)+set_resuid(nobody³), le regain root →EPERM, regain gid →EPERM, réajout de groupe →EPERM;getres*confirment le saved-set figé. Corps d’enfants sans branche locale (combinés par&) → le flush LLVM impossible après largage n’ajoute aucune branche non couverte. - PR #34 (merge commit
7e07525), 3 commits signés GPG + DCO (9bd2335code,b3b8cbfspec corrigée). Barrière verte 2 arches : fmt, clippy-D, tests (791),llvm-covroot (lignes 97.3 %, branches 81.7 % ; production rouge = 5 bras erreur EPERM/EFAULT ⊆ exceptions PRIVILEGE/STRUCTURAL, couvrable vide), audit/deny/machete. Prochain chantier → io_uring Temps 2d (URING_CMD).
Session 2026-06-12 — MmapRegion livré (PR #31) : madvise (2a) + futex (2c) débloqués/branchés
Le dernier prérequis transverse de la couche 0 io_uring est levé. MmapRegion
(mapping partageable refcounté + garde de vivacité) est implémenté dans
air-sys-syscall::mem, et submit_madvise (Temps 2a) + submit_futex_*
(Temps 2c) sont branchés — les deux reliquats différés sont fermés d’un coup.
MmapRegion=Arc<MmapRegionInner>(ptr/len/prot),munmapau dernier drop (erreur ignorée commeMapping).MmapRegionLiveness(clone de l’Arc) garée dans le slot S1 →munmapimpossible tant qu’une op est en vol : ni use-after-unmap, ni fuite.Mappingreste strictement inchangé (RAII unique, zéro-coût, couverture 100 % intacte). Une allocArcassumée (exception ADR-021 c.4, opt-in).- Soundness prouvée : Miri (ownership : no UAF / no leak / drop-once) +
loom (drops concurrents des gardes →
munmapexactement une fois, jamais en avance). Intégration kernel réel : madvise WILLNEED/DONTNEED, futex wait réveillé par wake,futex_waitvmulti-attente, drop de la région en vol. - Ops branchées :
submit_madvise(&MmapRegion, range, advice)(OP 25) ;submit_futex_wait/wake(&MmapRegion, offset, …)(OP 51/52),submit_futex_waitv(OP 53). Signature futex corrigée (&AtomicU32insoundable →&MmapRegion+offset). Types ajoutés :FutexFlags,FutexWaiter; opcodes +futex_waitvABI. - Trois durcissements d’API appliqués avant merge (PR #31, second commit) :
from_mapping(mapping, prot)(prot explicite, plus de défautREAD|WRITEfaux → fin d’un SIGSEGV latent sur mapping read-only) ;futex_wordexigeWRITE(la réf rendue est mutable) ;FutexWaitersansmask(lestruct futex_waitvkernel n’en porte pas — champ jamais transmis, footgun ADR-032). Test ajouté :from_mappingread-only →bytes()OK,futex_word()EINVALavant accès. - Specs réconciliées (FR + EN) :
family-mem-mmap-region.md(§1from_mapping(prot),futex_word/WRITE ; §3FutexWaitersans mask),io-uring-2a §6.2(madvise) etio-uring-2c §6.1(futex) passent de « différé » à « implémenté ». - PR #31 (merge commit
b61250e), 2 commits signés GPG + DCO (c9e3fbcimpl,b12be19durcissements). Barrière verte 2 arches : fmt, clippy-D, tests (780), Miri, loom,llvm-covroot (TOTAL lignes 97.7 % / branches 81.9 % ; couvrable du nouveau code = vide), audit/deny/machete, mdbook. Prochain chantier → io_uring Temps 2d (URING_CMD).
Session 2026-06-11 (suite) — io_uring Temps 2c (async) mergé (PR #28)
Implémentation, durcissement et merge du cœur async-spécifique du Temps 2c d’io_uring.
- 9 façades + 2 accesseurs typés (PR #28, merge commit
1f2e5cb) :submit_nop_with_result,submit_timeout(+_remove/_update),submit_cancel(async,CancelTargetToken/Fd/Op/Any +CancelFlags::ALL),submit_poll_add(+_remove/_update),submit_epoll_ctl,submit_waitid,submit_message_ring_data(MSG_DATA) ; accesseursinto_poll_result,into_waitid_result.SignalInfo/WaitTarget/WaitOptionsréutilisés defamily-process/signal;KernelTimespec/CancelTargetdu Temps 1. - Couverture 100 % hors exceptions (root) : lignes 97.4 % / branches 81.7 %.
Ensemble « couvrable » VIDE — zéro région ni branche de prod 2c rouge, et
AUCUNE exception 2c (pas de garde
u32ici). Miri (ownership des payloads Timeout/Waitid/Epoll restitués sans UAF), proptest (cancel idempotent), fuzz (io_uring_2c_decode). - Types ajoutés à
air-sys-types:io_uring::{PollEvents, TimeoutFlags, TimeoutSpec, CancelFlags, MessageRingFlags, EpollOp, EpollEvent};signal::SignalInfo::zeroed(). L’epfd des tests vient d’unepoll_create1brut test-only (pas de fausse famille epoll publique). - Spec 2c amendée embarquée dans la PR (réconcilie
ETIME_SUCCESSn’altère pas leres,cancelsansALLrend 0, types epoll, futex→MmapRegion). - Différés (signalés) : futex (51/52/53) → PR coordonnée
family-mem(handle de vivacitéMmapRegion, le même quemadvise— un seul handle débloque les deux) ;link_timeout(15) → Temps 3c ;files_update(20),fixed_fd_install(54),msg_ring_fd(40 MSG_SEND_FD) → Temps 3a. - 2 arches : speedy x86_64 + raspi-srv-2 aarch64 (incl. l’asm
epoll_create1aarch64). Reste couche 0 : io_uring Temps 2d→4, 3a–3f. Prochain : Temps 2d (URING_CMD).
Session 2026-06-11 (suite) — io_uring Temps 2b (réseau) mergé (PR #26) + ADR-032
Implémentation, durcissement et merge du Temps 2b d’io_uring (12 opérations réseau) — stratégique AirCom (ADR-001 : zero-copy + passage de FD).
- 12 façades réseau + accesseurs typés (PR #26, merge commit
6055b11) : accept/_with_peer, connect, socket, bind, listen, send, receive, send_message, receive_message, send_zero_copy, send_message_zero_copy, shutdown. -
- Cycle zero-copy à DEUX complétions (CQE résultat
F_MORE→ CQE NOTIF qui restitue) porté par lehas_moredu slab (Temps 1, sans extension) ; prouvé sûr Miri (le buffer survit à la 1ʳᵉ complétion). Passage de FDSCM_RIGHTS - FD reçus matérialisés en
OwnedFdCLOEXEC,MSG_CTRUNCsignalé, aucune fuite. Invariants Air :MSG_NOSIGNAL/SOCK_CLOEXECpar défaut.
- Cycle zero-copy à DEUX complétions (CQE résultat
- Types possédés
OwnedSendMessage/OwnedReceiveMessageajoutés àair-sys-types::net: lesSendMessageRequest<'a>/ReceiveMessageRequest<'a>defamily-netsont basés sur des emprunts (sync), inadaptés à l’ownership asynchrone S1. Sérialisation sockaddr + cmsg réutilisées denet.rs(pub(crate), DRY).ReceiveMessageMeta,ZeroCopyFlagsajoutés. into_zero_copy_buffers(restitution intégrale des buffers d’unsendmsg_zc, jamais le premier seul) — corrige le point relevé au rapport 019. Spec 2b corrigée en conséquence (types possédés + restitution intégrale).- ADR-032 (« Préservation des données confiées : zéro discard silencieux ») gravé : règle transverse — une API Air ne discarde jamais une donnée confiée (restitution intégrale, ressources matérialisées, troncatures signalées, chemins d’erreur compris). Registre v3.0.
- Couverture 100 % hors exceptions (root) : lignes 97.4 % / branches 81.6 %.
Ensemble « couvrable » VIDE (résidu = 3 gardes
u32STRUCTURAL, §2b). proptest + Miri + cargo-fuzz (io_uring_2b_recvmsg). 2 arches (speedy + raspi-srv-2). Reste couche 0 : io_uring Temps 2c→4, 3a–3f. Prochain : Temps 2c (async).
Session 2026-06-11 (suite) — io_uring Temps 2a (filesystem) implémenté et mergé (PR #24)
Implémentation, durcissement et merge du Temps 2a d’io_uring (opérations filesystem), par-dessus le cœur du Temps 1.
- 23 façades FS + 4 accesseurs typés (PR #24, base
main@26a8bcd, merge commita2eea18).read/write/readv/writev,fsync/sync_file_range/fallocate/ftruncate,openat2/close,statx,fadvise,splice/tee,mkdirat/unlinkat/renameat/linkat/symlinkat, 4× xattr ; accesseursinto_vectored_result/into_statx/into_xattr_result/opened_fd. Sans nouveau type de ring : opcodesIORING_OP_*(uapi 6.12) encodés en SQE, état possédé garé dans le slot S1 (slab généralisé à un payload hétérogèneOwnedOp), restitution typée à la complétion. - Ownership prouvé sound (Miri) :
Vec<u8>/Vec<Vec<u8>>+iovec/Box<Statx>/CStringdéplacés dans le slot ; un buffer en vol ne peut être ni libéré ni réutilisé. Bug corrigé :submit_closeparquait l’OwnedFd→ double close (attrapé par le contrôle IO-safety) ; correctifinto_raw_fd(cession au kernel, aucun payload garé). - Types ajoutés à
air-sys-types::fs(conformesfamily-fs) :FsyncFlags,SyncFileRangeFlags,UnlinkFlags,LinkFlags,XattrFlags,FadviseAdvice(distinct demem::MadviseAdvice— espaces de valeursPOSIX_FADV_*≠MADV_*), et un miroir brutStatx#[repr(C)](256 o, assert de layout). - Couverture 100 % hors exceptions (mesurée en root, ADR-031) : lignes
97.1 % / branches 82.1 % au TOTAL (planchers 96/78). Diagnostic 017 :
0 branche de prod 2a couvrable, seules rouges = 6 gardes
u32(buffers > 4 GiB) documentées STRUCTURAL. Tests : intégration kernel 6.12 (round-trips) + proptest (transfert borné + restitution fidèle, chemins non-UTF-8) + simulateur (EBUSY) + Miri + cargo-fuzz (io_uring_2a_decode). - Barrière verte 2 arches : x86_64 (speedy natif) + aarch64 (CI self-hosted
raspi + run natif
fs_opsau SHA bâti).gh pr checks 24vert sur les 2. - Différé (signalé) :
madvise(PR coordonnéefamily-mem, cf. Dettes),read_fixed/write_fixed+ variantes direct/fixed (Temps 3a). Reste couche 0 : io_uring Temps 2b→4, 3a–3f. Prochain : Temps 2b (réseau).
Session 2026-06-11 (suite) — device + ebpf implémentées et mergées : 11 familles couche 0 livrées
Implémentation, durcissement (proptest + fuzz) et merge des deux dernières familles de la couche 0, puis bascule ✅.
device(PR #20, basemain) : uevent netlink (authenticiténl_pid==0/SCM_CREDENTIALS) + evdev (18 ioctlsEVIOC*, lecture typéeinput_event), sysfs renvoyé àfs.InputEvent.sec/useceni64(fidélitéstruct timevalLP64). 100 % hors exceptions + test d’intégration uinput réel + proptest + fuzzfuzz_uevent_parse.ebpf(PR #21, empilée puis rebasée surmain) :bpf()37 sous-commandes +perf_event_open+ 12 ioctls (dont le pontperf_event_set_bpf_program), RAII. Vrai 100 % hors exceptions STRUCTURAL via un harnais d’intégration privilégié réel (programme compteur assemblé main avecBPF_PSEUDO_MAP_FD+ raw_tracepoint, cgroup v2 attach/link, bpffs pin/get, btf_load + carte BTF-typée, introspection, perf complet). proptest + fuzzfuzz_bpf_obj_info. Bugs trouvés/corrigés par le harnais (bpf_program_queryoffsets,bpf_program_test_runpointeurs NULL).- CI — mesure de couverture en root (PR #22, ADR-031).
ebpfétant quasi-tout-privilégiée, sa couverture plafonnait ~74 % en non-root et faisait tomber le total sous le plancher 96 %. Le jobtest-coverageexécute désormais les étapesllvm-covviasudo -nsur les runners self-hosted (NOPASSWD sur les deux) pour que les tests d’intégration privilégiés tournent sous instrumentation. Modèle de confiance dépôt privé/mono-mainteneur, à revisiter avant l’ouverture publique. - 11 familles couche 0 livrées (100 % hors exceptions, 2 arches : speedy
x86_64 + raspi-srv-2 aarch64) :
process,signal,time,ipc,mem,fs,net,security,system,device,ebpf. #19 (brouillon) fermée, branches mergées supprimées. Reste couche 0 : io_uring Temps 2a→4, 3a–3f. Traductions EN des specs reportées (fin de couche 1).
Session 2026-06-11 — Specs device + ebpf : la couche 0 est entièrement spécifiée
Production des deux dernières specs de famille manquantes, au niveau de détail des autres (docs/specs/layer-0/family-device.md, family-ebpf.md), pour permettre une délégation d’implémentation en une passe.
device— trois sous-systèmes : socket netlinkNETLINK_KOBJECT_UEVENT(UEventSocketRAII +readqui vérifie l’authenticiténl_pid==0/uid==0) avec décodeurUEventMessageemprunté, zéro-alloc (itérateurclé=valeur) ;evdev(lecture typéeInputEvent/slice_from_bytes, ioctls dédiésEVIOC*—evdev_device_id,evdev_name,evdev_supported_codes,evdev_abs_info,evdev_grab/release,evdev_set_clock…, jamais d’ioctl générique, ADR-021 c.3) ;sysfsexplicitement renvoyé àfs(pas de wrapper, anti-duplication). ~22 fonctions. Décision : décoder un format de fil kernel fixe (uevent, input_event) est du miroir légitime en couche 0 (précédentSignalFdInfo) ; le modèle riche de device → couche 1.ebpf— couverture exhaustive debpf(): les 37 sous-commandes deenum bpf_cmd(cible 6.12) chacune en fonction dédiée typée (cartes + éléments + batch, programmes, attache/liens, épinglage, introspection par id, BTF, stats/jetons), RAIIBpfMap/BpfProgram/BpfLink/Btf. Plusperf_event_open(+PerfEventScopesans sentinelle-1) et ses ~12 ioctls dédiés, dontperf_event_set_bpf_program(le pont eBPF↔perf). On charge des programmes déjà assemblés ; assembleur, chargeur libbpf, BTF/CO-RE, décodage du ring-buffer → couche 1 (aligné sur le précédent seccomp Q4). ~51 fonctions.- ABI vérifiée sur en-têtes kernel :
bpf(x86_64 321 / aarch64 280),perf_event_open(298 / 241),NETLINK_KOBJECT_UEVENT=15, ioctlsEVIOC*, layoutsinput_event(24 o LP64),input_id,input_absinfo, énumérationbpf_cmd. - Docs de suivi réconciliées :
INDEX.md(tableau + arbre + stats),etat-avancement.md(lignes device/ebpf 📝→🔜, section 3), JOURNAL (cet historique + État courant + « Prochain chantier » réaligné, qui pointait encore par erreur suripc). - Suite : déléguer l’implémentation de
deviceetebpf(squelette-doc-d’abord), puis les Temps io_uring restants. Traductions EN des deux specs à produire ultérieurement (comme io_uring).
Session 2026-06-01 (suite, 3g) — mem + net finis : 9 familles couche 0 à 100 % hors exceptions, 2 arches
Dernière passe de couverture. mem et net portées à 100 % hors exceptions
(lignes + branches, mesuré artefact-free --workspace --lib --branch), validées
sur speedy (x86_64) et raspi-srv-2 (aarch64). Branche
feat/layer0-familles-restantes, 4d9dbd8 → e46e90d.
mem(3d802dd) : branches 35,7 → 58,9 % (reste = exceptions). Erreurs réellement atteignables testées (mprotect/madvise/msync EINVAL sur adresse non alignée ; mlock/mlock2/munlock ENOMEM sur intervalle non mappé ; mlockall EINVAL ; memfd_create nom trop long ; process_vm_* ESRCH PID inexistant ; mmap_fixed succès + EEXIST).debug_assert→ 2 should_panic. Test flakymmap_fixed_noreplace_anonymousretiré. Divergence d’environnement mlockall (réussit sur speedy, EPERM sur raspi) documentée en PRIVILEGE.net(e46e90d) : ~30 tests. Erreurs sockets via fd invalide → EBADF (listen/getsockname/getpeername/sendmsg/recvmsg/sockopts) ; socket EPROTONOSUPPORT ; socketpair EOPNOTSUPP ; clamps val > i32::MAX (rcvbuf/sndbuf/linger/keepalive/backlog) ; parseursraw_to_socket_addretparse_scm_rights(buffers tronqués/dégénérés) ; passage de FD SCM_RIGHTS bout-en-bout sur socketpair. Reste = exceptions STRUCTURAL (gardes de parseur inatteignables avec données kernel bien formées ;get_so_errorval≠0 = erreur socket asynchrone exigeant poll/epoll non wrappés ;if letsur variante déterministe) + FEATURE-KERNEL (IPv6 absent, skip fcntl).
Couche 0 — 9 familles de syscalls à 100 % couverture (lignes + branches) hors
exceptions, cross-arch. COVERAGE-EXCEPTIONS.md : 52 entrées (PRIVILEGE 9,
FEATURE-KERNEL 12, CHILD-EXIT 8, EFAULT-SAFE 8, STRUCTURAL 11, VALUE-UNREACHABLE 4).
Barrière au HEAD verte sur les 2 arches : fmt / clippy -D warnings /
cargo test --workspace (371 + 80 + 74 + 2 + intégration). Chiffres bruts llvm-cov
(exceptions restant rouges par construction) : lignes ~96,6 %, branches ~78,8 %.
io_uring reste le prochain gros chantier couche 0. PR ouverte (non mergée).
Session 2026-06-01 (suite, 3f) — Couverture 100 % : signal/process/ipc/fs/types + artefact llvm-cov élucidé
Reprise du chantier couverture couche 0 (branche feat/layer0-familles-restantes,
77f4e86 → b6396ce). Objectif : 100 % lignes+branches hors exceptions, mesuré
sur les 2 exécuteurs Linux. API gelée (tests seulement).
Découverte méthodologique majeure — artefact [True:0, False:0] de
llvm-cov --branch. Les régions de branche fantômes collées sur des lignes
pourtant couvertes viennent exclusivement du binaire de test d’intégration
(tests/main_thread_invariant.rs), qui lie la lib sans #[cfg(test)] et n’exécute
que getpid/gettid : son instanciation [0,0] ne fusionne pas avec les compteurs
cfg(test). Preuve : --workspace = 36 shadows sur process ; --workspace --lib =
0 shadow. Décision actée : la barrière branches se mesure
cargo +nightly llvm-cov --workspace --lib --branch (artefact-free) ; les lignes
restent en --workspace. Documenté en tête de COVERAGE-EXCEPTIONS.md.
4 familles syscall + air-sys-types portées à 100 % hors exceptions (2 arches) :
signal(9806f99) : recette flush enfant forké réutilisée (child_exit), EAGAIN/ESRCH ciblés, 2 should_panic, noop_handler, retrait deis_rootmort.process(08103a1) : 2 should_panic ; artefacts [0,0] élucidés ; 40 branches restantes ⊆ exceptions.ipc(a7dfe45) : EventFd::write EINVAL, vmsplice/splice-Some EBADF ; retrait d’un test splice buggé + helper mort.fs(c6945ff) : EBADF sur preadv/pwritev/fallocate/copy_file_range/ sync_file_range, symlinkat EEXIST ; retrait du helper mortsyscall0.air-sys-types(b6396ce) : réconcilié (résidu = défense-en-profondeur).
Plus : correction de 2 violations fmt --check préexistantes (lot à l’aveugle,
d9b126f). COVERAGE-EXCEPTIONS.md : nouvelle catégorie STRUCTURAL, 42 entrées
(PRIVILEGE 8, FEATURE-KERNEL 11, CHILD-EXIT 8, EFAULT-SAFE 7, VALUE-UNREACHABLE 4,
STRUCTURAL 4).
État au HEAD b6396ce, identique speedy/raspi : fmt/clippy -D warnings/
cargo test --workspace (328+80+74+2) verts. Familles 100 % hors exceptions :
time, system, security (préc.) + signal, process, ipc, fs + types complet.
RESTE (2 familles, gaps branches réels mesurés) : mem (branches 35.71 %,
36 rouges) et net (branches 52.59 %, 55 rouges). PR non ouverte (on n’ouvre
qu’au 100 % global). Détail : prompt/couche0-familles-restantes.3f.output.md.
Session 2026-06-01 — Passe 2 des 6 familles + validation cross-arch (x86_64 + aarch64)
Implémentation (passe 2) des 6 familles restantes (ipc, mem, fs, net,
security, system) et première validation réelle sur deux exécuteurs
Linux : speedy (Mac mini Intel, x86_64, Ubuntu 24.04) et raspi-srv-2
(Raspberry Pi 4, aarch64, Ubuntu 26.04). macOS n’est plus un juge (compile
seulement ; rate les tests Linux-gated, les doctests exécutés, et les bugs de
signedness/arch). Toolchain montée à stable 1.96.0 (MSRV = dernière stable).
Bugs aarch64-spécifiques trouvés au premier passage réel sur aarch64 et
corrigés (internes asm!/valeurs, API publique inchangée) :
- clone3 / hang des tests fork : en exécution parallèle, l’enfant d’un
clone3fork héritait des mutex du harnais multi-thread verrouillés par des threads absents ;std::process::exit(atexit/flush) deadlockait (futex_do_wait). Le wrapperclone3lui-même est correct (run sériel OK). Fix : sortie enfant viaexit_groupdirect (helper interne#[cfg(test)]). - openat / EINVAL sur aarch64 :
O_DIRECTORY/O_NOFOLLOW/O_DIRECT/O_LARGEFILE/O_TMPFILEont des valeurs différentes entre x86_64 et aarch64 (ABI asm-generic).OpenFlagsfigeait les valeurs x86_64 →OpenFlags::DIRECTORYdésignaitO_DIRECTsur aarch64. Fix : valeurs conditionnelles àtarget_arch(idemPipeFlags/StatusFlags::DIRECT). - 7 doctests (jamais exécutés car le Mac faisait
cargo doc, pascargo test --doc) : importsAsFdmanquants,GetrandomFlagsnon re-exporté au niveau crate, exempleSockFprog(E0716). Corrigés.
État vérifié sur les deux arches (HEAD 61868ca) : fmt/clippy -D warnings/build verts ; cargo test --workspace = **196 + 78 + 1 (intégration)
- 74 + 2 doctests, 0 échec** ; plus aucun hang
process::sur aarch64.
Outillage couverture : nightly dédié installé sur les deux exécuteurs pour la
mesure de branches (cargo +nightly llvm-cov --branch) ; le build/CI reste sur
stable.
Audit systématique des constantes cross-arch (fait, résultat négatif). Un
programme C de référence compilé sur les deux arches a comparé ~270
constantes kernel (O_, MAP_, MADV_, MFD_, SIG*, SA_, SO_/SOCK_/MSG_,
SECCOMP_, LANDLOCK_, CLONE_, RLIMIT_, CAP_, PR_, F_SEAL_*, errno…) aux
valeurs hardcodées d’air-sys-types. Seule divergence : la famille O_*
(O_DIRECTORY/O_NOFOLLOW/O_DIRECT/O_LARGEFILE/O_TMPFILE), déjà
corrigée. Tout le reste est identique entre x86_64 et aarch64 — aucune autre
correction nécessaire.
Harnais sous-processus seccomp/Landlock (fait, 2/3).
seccomp_set_mode_filter (filtre allow-all) et LandlockRuleset::restrict_self
sont désormais fonctionnellement testés dans un enfant forké (clone3 +
waitid, sortie exit_group), validés sur les deux arches. La mesure de
couverture de leur branche succès reste une exception légitime (l’enfant ne
réécrit pas son .profraw ; cf. COVERAGE-EXCEPTIONS.md). seccomp_set_mode_strict
reste une exception (bloque exit_group + l’I/O du profil).
Reste à faire (chantier couverture, non terminé) : tests fs/mem manquants
(fcntl_*, readlinkat, chemins d’erreur), passage à 100 % lignes + branches
sur les deux arches, promotion éventuelle d’exit_group en API publique
(ADR-021 conv. 5).
Session 2026-05-27 — Famille time complète
Implémentation de la famille time couche 0 dans les deux crates fondatrices :
air-sys-types::time:Clock,Instant,SleepDeadline,SleepError,TimerFdFlags,TimerSetFlags,TimerFdSpec.air-sys-syscall::time:clock_gettime,clock_settime,clock_getres,clock_nanosleep,timerfd_create, wrapper RAIITimerFd::{as_fd, into_fd, arm, disarm, current, read}.Errno::EAGAINajouté pour couvrir les lectures non bloquantes sans événement disponible.- Tests ajoutés : chemins monotonic/resolution/sleep relatifs, validation cross-clock pour
clock_nanosleep, lecture non bloquantetimerfd, armement one-shot, round-tripcurrent/disarm.
Résultat : cargo test -p air-sys-types -p air-sys-syscall vert le 2026-05-27, soit 165 tests passants au total.
Session 2026-05-24 (suite) — Famille signal complète
Branche feat/layer-0-family-signal. Implémente la famille signal couche 0 conformément à docs/specs/layer-0/family-signal.md et ADR-020 (signalfd par défaut + sigaction restreint aux 4 signaux synchrones fatals) :
- Sous-section 1 (signalfd + masques) :
signalfd_create,signalfd_create_blocking,SignalFd::{read, update_mask, as_fd, into_fd, from_owned_fd},block_signals,unblock_signals,set_signal_mask,current_signal_mask,wait_for_signal. - Sous-section 2 (envoi) :
kill,tgkill,rt_sigqueueinfo. - Sous-section 3 (sigaction restreint) : sous-module
synchronous_handleravecinstall_fatal_handleretrestore_handler(unsafe), restreints àFatalSignal::{Segv, Bus, Fpe, Ill}.
Dette d’ambiguïté SignalInfo vs SignalQueueInfo — fermée. La PR pidfd_* avait consigné l’ambiguïté entre les deux types (family-process.md nommait SignalInfo, family-signal.md nommait SignalQueueInfo pour ce qui était fondamentalement le même siginfo_t côté kernel). Résolution dans cette PR : un seul type SignalInfo porte le concept ; SignalQueueInfo disparaît.
SignalInforeste opaque côté représentation (#[repr(C)] struct { raw: [u8; 128] }, layoutsiginfo_tLinux) — sa représentation est inchangée pour ne pas casserpidfd_send_signalqui passe son pointeur au kernel as-is.SignalInfogagne un constructeur publicnew_queue(value: SignalValue)qui fixesi_code = SI_QUEUE(-1), seule valeur que le kernel accepte sans drapeau privilégié pour un envoi cross-process userspace (cf.do_rt_sigqueueinfodanskernel/signal.c: « Not even root can pretend to send signals from the kernel »). Le champsi_signoest laissé à zéro — le kernel l’écrase avecsigà la délivrance.SignalValue { Integer(i32), Pointer(u64) }est promu type public, conforme spec.rt_sigqueueinfo(pid, signal, info: &SignalInfo)consomme la même API.
Déviation mineure de spec assumée. family-signal.md documentait rt_sigqueueinfo(..., info: SignalQueueInfo) par valeur avec champs publics { code: i32, value: SignalValue }. La présente implémentation passe &SignalInfo (référence) construit via new_queue (constructeur discipliné). Raison de sécurité : exposer code brut serait un footgun — le kernel rejette les si_code arbitraires en envoi cross-process avec EPERM. Exposer un constructeur discipliné évite que l’utilisateur ne fabrique un siginfo_t qui se ferait rejeter, ou pire qui « falsifierait » l’origine du signal. La spec sera mise à jour à froid (hors périmètre PR).
pidfd_send_signal non modifié. Périmètre strict respecté : pidfd_send_signal (PR process déjà mergée) n’est PAS touché. Son trou de couverture Some(_) (3 lignes) reste acté. Si un test légitime de cette PR l’avait appelé en Some(...), le trou se serait fermé naturellement — ce n’était pas un objectif et ce n’est pas arrivé (aucun test de cette PR ne fait appel à pidfd_send_signal). Diff vérifié : crates/air-sys-syscall/src/process.rs n’est pas dans la liste des fichiers modifiés.
Constantes Signal ajoutées — 4, vérifiées vs asm-generic/signal.h. SIGILL=4, SIGBUS=7, SIGFPE=8, SIGUSR1=10. Identiques x86_64/aarch64 (ces archs incluent asm-generic/signal.h ; les archs comme MIPS/SPARC/Alpha qui surchargent sont hors ADR-014). Signal total après cette PR : 10/~31 standards POSIX, toujours stub partiel documenté en tête de module.
Borne structurelle Signal::try_from_raw ∈ [1, 64] — correction Principe 4. Pendant la phase de contrôle, défaut identifié : Signal::try_from_raw validait seulement > 0, autorisait donc Signal(99999) qui se serait fait rejeter par EINVAL au runtime côté kernel. Violation du Principe 4 (validation amont) : Signal doit porter sa garantie de validité dans le type, pas attendre le kernel.
Corrections appliquées dans cette PR (périmètre légitime — Signal EST le type central de la famille signal que cette PR complète déjà) :
Signal::try_from_raw(raw)rejette désormais aussiraw > 64(=_NSIGLinux). RenvoieNonepour toutraw ∉ [1, 64].Signal::from_nonzero(qui ne validait pas la borne) retiré : aucune utilisation hors du test roundtrip lui-même, simplification de l’API publique. Les constantes internes (SIGUSR1, etc.) passent parSelf(unwrap_nz(N))qui ne touche pas àfrom_nonzero.unwrap_nzinterne renforcé : panique au compile-time si une constante future est définie avec une valeur hors[1, 64]. Défense en profondeur ; toute régression d’invariant détectée à la compilation.- Constante
NSIG: i32 = 64exposée au module (privée, mais documentée). - 4 nouveaux tests :
try_from_raw_accepts_lower_bound_1,try_from_raw_accepts_upper_bound_nsig,try_from_raw_rejects_above_nsig(65, 99999,i32::MAX),unwrap_nz_runtime_call_panics_above_nsig(65, 100).
Conséquence couverture — 4 lignes acquittées « défense en profondeur ». Avant la borne : « no-op silencieux pour signal > 64 » — comportement faible (silently ignore une demande explicite). Après la borne : défense en profondeur derrière une validation amont — un Signal valide ne peut structurellement pas avoir une valeur > 64 ; les branches None restent comme garde par Principe 5 (sur-sécuriser, ne jamais corrompre) au cas où un futur constructeur oublierait la borne. unreachable!() refusé : transformerait une garde silencieuse en panic potentiel, ce qui est précisément ce que Principe 5 nous demande d’éviter.
Les 4 lignes non couvertes en line-coverage dans air-sys-types/src/signal.rs :
- 3 lignes : branches
None => false/None => self/None => selfdeSignalMask::{contains, with, without}— défensives, inatteignables tant queSignal::try_from_rawborne à[1, 64]. - 1 ligne : bras
None => panic!("unreachable: n > 0 guaranteed by the check above")dansunwrap_nz— inatteignable parce que la garden <= 0 || n > NSIGau-dessus filtre déjà tout argument zéro avant queNonZeroI32::newsoit appelé. Conservé pour satisfaire l’exhaustivité dumatchconst ; même statut « défense en profondeur ».
Toutes catégorie « défense en profondeur, structurellement inatteignable derrière borne validée » — justification cette fois solide (la borne EST garantie par le type).
Vérification non-régression famille process. La borne ≤ 64 ne casse aucun usage existant : tous les signaux utilisés dans family-process sont dans [4, 19] (SIGCHLD=17, SIGKILL=9, SIGSEGV=11, SIGSTOP=19, SIGTRAP=5, SIGABRT=6, SIGBUS=7, SIGFPE=8, SIGILL=4). Les 92 tests air-sys-syscall passent inchangés sur 3 runs stress successifs après l’introduction de la borne.
Stratégie de test pour l’état global signal — mise en œuvre.
MaskGuardRAII (test-only,mod tests) : capture le mask courant en toute première ligne de chaque test, restaure intégralement viaSIG_SETMASK(jamaisSIG_BLOCK/UNBLOCKrelatif) à la destruction. Couvre la contamination per-thread (cargo test recycle les workers).tgkill(self_pid, self_tid, sig)pour les tests d’envoi/réception intra-process : cible exactement le thread courant, évite tout routage à un worker collatéral cargo. Combiné avecsignalfd_create_blocking, donne une livraison déterministe sans flakiness.- Fork-enfant mono-thread pour les opérations process-wide ou unsafe :
kill(cible PID, pas TID — risque de router vers un worker tiers en multi-thread).rt_sigqueueinfo(idem, cible PID).install_fatal_handler/restore_handler(handlers PROCESS-WIDE, contamineraient les autres tests).
- Aucun test ne déclenche réellement un signal fatal (SIGSEGV via
ud2, etc.) — l’install/restore est validé par round-trip, le déclenchement effectif reste hors périmètre (crash reporter end-to-end est un cas family-security / future PR).
Aucune nouvelle dépendance externe. Tous les types kernel (signalfd_siginfo 128 octets, sigset_t u64, struct sigaction 24/32 octets selon arch) sont décrits en #[repr(C)] directement. core::ffi::{c_int, c_void} couvre l’ABI des handlers.
Tests bout-en-bout. 37 tests dans air-sys-syscall::signal (mask round-trip, signalfd round-trip, wait_for_signal bloquant, tgkill+signalfd, kill en fork-enfant, rt_sigqueueinfo en fork-enfant pour les deux variantes SignalValue, ESRCH, install/restore en fork-enfant, parsing signalfd_siginfo unit, errno helper local, wait_for_signal timeout-non-supporté). 10 tests dans air-sys-types::signal (4 nouvelles consts, SignalInfo new_queue avec Integer et Pointer + offsets vérifiés sur as_bytes, SignalMask helpers, SignalFdFlags, FatalSignal mapping).
Stress test : 5 runs successifs de cargo test --workspace, 0 échec. La stratégie tgkill+fork pour les opérations process-level neutralise complètement les races multi-thread observées en début d’itération (signal: 10 sur un worker collatéral).
Métriques finales. 92 tests dans air-sys-syscall (22 nouveaux pour signal) + 58 dans air-sys-types (13 nouveaux pour signal, dont 4 de bornes ajoutés en phase de contrôle finale) + 1 intégration. Total 150 tests passants. Couverture production : air-sys-types::errno/signal/process 100 % fonctions ; air-sys-types::signal 98.3 % lignes (4 lignes catégorie « défense en profondeur » détaillées ci-dessus) ; air-sys-syscall 94-95 % lignes avec gaps catégorisés (mêmes catégories que famille process : Err paths via helper commun déjà testé, branches Ok inatteignables sans privilèges, exits enfants fork-pattern, skips AppArmor/Yama).
Session 2026-05-24 — Fin de la famille process : groupes/sessions, prctl, rlimits, capabilities
Branche feat/layer-0-process-complete. Implémentation des sous-sections 4 à 7 de docs/specs/layer-0/family-process.md, complétant la famille process en couche 0 :
- Sous-section 4 (groupes/sessions) :
setpgid,getpgid,setsid,getsid. Convention 1 ADR-021 appliquée —Option<Pid>partout, jamais la sentinelle kernel0. - Sous-section 5 (prctl par opération) : 12 fonctions individuelles typées (
set/get_parent_death_signal,set/get_no_new_privs,set/get_thread_name,set/get_dumpable,set/get_keep_caps,set_timer_slack,cap_ambient_raise/lower/clear_all/is_set). Convention 3 ADR-021 strictement appliquée : helper privéraw_syscall_prctlnon exposé, chaque opération est sa propre fonction publique typée. - Sous-section 6 (rlimits) :
getrlimit,setrlimit,prlimit— implémentation pivot surprlimit64(syscall moderne, atomique, ciblable).getrlimit/setrlimitdeviennent des alias de compatibilité qui délèguent àprlimit(None, ...). - Sous-section 7 (capabilities) :
capget,capsetvia Linux v3 capabilities (header + 2×__user_cap_data_struct). Conversionu64 ↔ (lo, hi)confinée àair-sys-syscall;CapabilityMaskcôtéair-sys-typesreste un type pur.
Capability — stub partiel à 20 variantes. Création de l’enum Capability avec les 20 variantes listées par la spec (sous-section 7), discriminants explicites tirés de include/uapi/linux/capability.h et vérifiés un-à-un dans un test (capability_discriminants_match_uapi_linux_capability_h). CAP_SYS_PACCT = 20 est délibérément absent (pas listé par la spec) — aucune confusion possible avec CAP_SYS_ADMIN = 21. Les ~21 capabilities restantes (CAP_SETPCAP, CAP_LINUX_IMMUTABLE, CAP_NET_BROADCAST, CAP_IPC_LOCK/OWNER, CAP_SYS_PACCT, CAP_SYS_TTY_CONFIG, CAP_MKNOD, CAP_LEASE, CAP_AUDIT_, CAP_MAC_, CAP_SYSLOG, CAP_WAKE_ALARM, CAP_BLOCK_SUSPEND, CAP_PERFMON, CAP_BPF, CAP_CHECKPOINT_RESTORE, …) seront ajoutées au fil des PRs qui les utiliseront. Le commentaire de tête de l’enum le documente.
CapabilityMask u64 ↔ split (u32, u32) — test du placement absolu. Les Linux v3 capabilities exposent leur bitfield via deux mots u32 côté ABI (datap[0] = bits 0-31, datap[1] = bits 32-63). Une éventuelle inversion lo/hi dans mask_to_words se compenserait dans un simple round-trip via words_to_mask — le bug serait invisible. D’où trois tests d’absolu : mask_to_words_places_bit_0_in_lo, mask_to_words_places_bit_32_in_hi, words_to_mask_recombines_with_correct_placement. Vérifient le placement exact d’un bit dans chaque mot, sur valeur brute (1_u64 << 32) — indépendamment des variants Capability disponibles. Aucune Capability de ce stub n’occupe le mot haut (max = SysTime = 25) ; quand la PR ultérieure ajoutera SetFCap = 31 puis MacOverride = 32, le mot haut sera exercé via une capability réelle.
Aucun ajout Errno ni Signal. Les 6 errnos et 6 signaux du stub partiel actuel suffisent (vérifié via croisement spec↔code). Discipline « ne pas pré-implémenter sans appelant » respectée.
Vérification des numéros de syscall vs spec — aucune divergence. setpgid 109/154, getpgid 121/155, setsid 112/157, getsid 124/156, prctl 157/167, getrlimit 97/163, setrlimit 160/164, prlimit64 302/261, capget 125/90, capset 126/91. Tous concordent ; la spec n’indiquait pas le numéro de prctl (157 x86_64 / 167 aarch64 dans linux/syscalls.h).
Allocation heap justifiée par convention 4 ADR-021. Une seule fonction de cette PR alloue dans son happy-path : get_thread_name() -> CString. Justification : kernel écrit dans un buffer transient de 16 octets ; la spec exige un type owned côté caller (peut sortir du frame). Allocation bornée à 16 octets (un seul Box<[u8]>), exception explicitement admise par la convention 4. capget ne fait pas d’allocation heap : CapabilitySet (3 × u64 = 24 octets) est retourné par valeur sur la pile, le buffer kernel intermédiaire (KernelCapData[2] = 24 octets) est aussi pile.
Mitigation d’une flakiness pré-existante. Le test waitid_dispatches_processgroup_and_anyprocessgroup (PR clone3+waitid) appelait waitid(AnyProcessGroup | EXITED | NOHANG) en parallèle des tests qui forkent. Sans NOWAIT, cet appel pouvait consommer un événement appartenant à un autre test parallèle (canonical SIGKILL test, fork tests de cette PR), provoquant un ECHILD chez le test légitime. Mitigation appliquée dans cette PR : ajout de WaitOptions::NOWAIT (peek-only) au test dispatch. Avant : 1/N failure rate ; après : 5 runs stress successifs sans failure.
Tests skippés en environnement dev — récap consolidé. À l’issue de cette PR, trois conditions runtime Ubuntu 24.04 par défaut masquent silencieusement des chemins applicatifs :
- AppArmor (
apparmor_restrict_unprivileged_userns=1) — bloqueclone3_with_new_user_namespace_runs_or_skips. - Yama (
yama.ptrace_scope ≥ 2) — bloqueraitpidfd_getfd_duplicates_fd_from_child(Ubuntu 24.04 par défaut =1, OK). - Non-root + non-pgrp-leader runner cargo — neutralise le scénario EPERM direct de plusieurs tests (
setsid,capset_raise_non_permitted,setrlimit_raising_hard,cap_ambient_raise_non_permitted,clone3_newpid_without_caps). La majorité ont été mitigés via scénarios fork (l’enfant fork’é se rend pgrp leader, puis exerce le chemin EPERM côté setsid ; les autres restent skip-on-root).
Validation effective de ces chemins relève d’un runner CI permissif (sans AppArmor userns restriction, avec ptrace_scope ≤ 1, et idéalement avec un harness cargo test qui rend le test runner pgrp leader). À provisionner avec le pipeline CI.
Trous de couverture restants — catégorisés. Sur ~1960 lignes production dans air-sys-syscall/process.rs, ~25 lignes restent non couvertes après cette PR. Aucune ne révèle un bug ou un risque :
- 3 lignes : branche
Some(&info)depidfd_send_signal(stubSignalInfoopaque, acted PR pidfd_*). - 10 lignes : corps complet de
set_no_new_privs. Testable en enfant fork’é dès aujourd’hui (clone3+waitid sont mergés ; l’enfant l’appelle, vérifie viaget_no_new_privs, exit code ; parent waitid). Choix de regroupement, pas une impossibilité : le test bout-en-bout sera écrit dans la PRfamily-security(seccomp) qui constitue le contexte naturel d’usage de NNP — les deux fonctions partagent les mêmes fixtures fork-enfant et la même discipline de scénario. - ~12 lignes : branches d’erreur (
if ret < 0 { return Err(errno_from_negative_syscall_ret(ret)) }) de wrappers prctl simples (set/get_parent_death_signal,get_no_new_privs,set/get_thread_name,set/get_dumpable,set/get_keep_caps,set_timer_slack,cap_ambient_lower/clear_all/is_set) et decapget. Ces opérations n’échouent pas en pratique sur un kernel sain ; leur siteErrest inatteignable sans injection de kernel-failure. La logique de décodage Err elle-même est intégralement couverte via le helper communerrno_from_negative_syscall_ret(un point unique appelé identiquement par tous ces sites), testé en isolation parerrno_from_negative_syscall_ret_maps_known_codessur 6 valeurs négatives synthétiques (-1,-3,-4,-9,-10,-22→EPERM,ESRCH,EINTR,EBADF,ECHILD,EINVAL). Seuls les sites d’appel restent non exercés en line-coverage ; la sémantique d’erreur est validée centralement. Aucun risque de logique d’erreur jamais validée. - ~1 ligne :
Ok(())decap_ambient_raise— la fonction ne SUCCÈDE que si l’appelant acapdans permitted∩inheritable. En non-root standard, c’est jamais. Couvrable seulement avec un binaire ayant des file capabilities pré-positionnées. - ~2 lignes : variante
SuidDumpabledu décodageget_dumpable(valeur kernel2, dépréciée — kernels modernes retournentEINVALsur PR_SET_DUMPABLE=2, donc le décodeur n’a jamais à la traiter).
Documentation au code (commentaires explicites) + ce JOURNAL = trace complète. Pas de tricherie sur les chiffres.
Pattern admis transversalement — exits d’échec côté enfant dans les tests fork. À partir de cette PR, le pattern « enfant fork’é encode son résultat de round-trip par code de sortie » (if X.is_err() { std::process::exit(N); } pour N ≠ 0, succès = exit(0)) est acté comme convention de test. Les branches exit(N) d’erreur ne sont prises que si la round-trip échoue côté enfant — c’est-à-dire jamais sur un environnement sain. Ces lignes apparaîtront systématiquement en non-couvertes par llvm-cov, par construction. À ne plus comptabiliser comme dette de couverture à chaque PR : c’est le coût normal d’un pattern de test fork-enfant robuste, supérieur à un test in-process qui contaminerait l’état partagé. Si la round-trip échoue en pratique, la branche s’exécute, le compteur s’incrémente, et la couverture remonte — c’est un canari, pas un trou.
Métriques finales. 70 tests dans air-sys-syscall (38 nouveaux), 45 dans air-sys-types (9 nouveaux), 1 binaire d’intégration inchangé — total 116 tests passants. Coverage production : 100 % air-sys-types, ~93 % air-sys-syscall (130 lignes manquées dont 30 production catégorisées en (A)/(B)/(C)/(D)/(E) ci-dessus + 100 lignes test = skips environnementaux + exits d’échec enfant + branches signal-induced). Régions à 89 % global, conformément à la politique JOURNAL antérieure (régions < 100 % acceptées sur branches d’invariants par construction et skips environnementaux).
Session 2026-05-23 — pidfd_open + pidfd_send_signal + pidfd_getfd
Troisième PR couche 0, sur la branche feat/layer-0-pidfd. Implémentation des trois wrappers pidfd_* de la famille process (spec docs/specs/layer-0/family-process.md sous-section 3) :
pidfd_open(pid, flags) -> Result<PidFd, Errno>— syscall 434 (x86_64 et aarch64), Linux 5.3+.pidfd_send_signal(pidfd, signal, info) -> Result<(), Errno>— syscall 424, Linux 5.1+.pidfd_getfd(pidfd, target_fd, flags) -> Result<OwnedFd, Errno>— syscall 438, Linux 5.6+.
PidFd est réutilisé (existe déjà depuis la PR clone3, qui le retournait dans CloneResult::Parent via CLONE_PIDFD) ; aucune réimplémentation. Drop hérité de OwnedFd — fermeture du fd à la destruction du PidFd, sans impl Drop explicite (qui aurait été redondant et aurait pu interdire les move-out de into_fd). Le test pidfd_drop_closes_underlying_fd prouve cette fermeture en post-mortem via waitid → EBADF sur un RawFd re-borrowé après drop.
Ajouts dans air-sys-types :
PidFdOpenFlags(bitflagsu32) : une seule valeur,NONBLOCK = 0x800, conforme au seul drapeau valide en kernel actuel.SignalInfo(stub opaque) :#[repr(C)] pub struct SignalInfo { _opaque: [u8; 128] }dansair-sys-types::signal. Aucun constructeur public — le type est type-reachable mais value-unreachable depuis l’extérieur de la crate. La signaturepidfd_send_signal(..., info: Option<&SignalInfo>)reste strictement conforme à la spec ; seulNoneest utilisable. Test internesignal_info_size_matches_linux_siginfo_tancre la taille à 128 octets (ABI kernel).Errno::EBADF(9),Errno::ESRCH(3) : deux nouvelles constantes, chacune référencée par un test (drop closure, PID inexistant). Le totalErrnoreste un stub partiel (6 constantes sur ~140), conformément à la discipline établie dans la PR précédente : on ajoute uniquement ce qui a un appelant.
Vérification des valeurs Errno vs asm-generic/errno-base.h. Les six constantes (EPERM=1, ESRCH=3, EINTR=4, EBADF=9, ECHILD=10, EINVAL=22) sont définies dans asm-generic/errno-base.h et utilisées identiquement par x86_64 et aarch64. Aucune surcharge dans asm/errno.h pour ces deux architectures (les surcharges existent sur Alpha/MIPS/SPARC/ParISC, hors ADR-014). Vérifié à la rédaction.
Dette terminologique signalée — SignalInfo vs SignalQueueInfo. La spec family-process.md (sous-section 3, pidfd_send_signal) nomme le paramètre info: Option<&SignalInfo> ; la spec family-signal.md (sous-section 2, rt_sigqueueinfo) introduit SignalQueueInfo avec champs structurés (code: i32 + value: SignalValue). Côté kernel, c’est pourtant le même siginfo_t qui passe dans les deux syscalls. Cette ambiguïté terminologique entre les deux specs doit être harmonisée en PR family-signal (renommer, fusionner, ou clarifier explicitement les deux types — par ex. SignalInfo = vue opaque côté ABI ; SignalQueueInfo = builder Air ergonomique au-dessus). Cette PR-ci ne tranche pas et laisse SignalInfo opaque pour éviter de figer son design.
Trou de couverture acté — branche Some(&info) de pidfd_send_signal. Les lignes 742-744 (extraction du pointeur depuis &SignalInfo) restent non couvertes en ligne, parce que SignalInfo n’a aucun constructeur public dans cette PR — la branche est type-reachable, value-unreachable depuis l’extérieur. Refus délibéré d’introduire un constructeur pub(crate) ou un SignalInfo::zeroed() juste pour la couverture : ce serait commencer à donner du contenu au stub, contre la décision adoptée pour SignalInfo opaque, et un test avec SignalInfo zéroé serait un faux test (validerait « ça ne plante pas », pas que info est transmis correctement — le kernel pourrait même rejeter un si_code incohérent). Le chemin None est testé bout-en-bout par le test canonique SIGKILL — c’est suffisant pour cette PR. // TODO(family-signal): tester Some(&info) avec un vrai constructeur est porté par le commentaire de la fonction. Couverture ré-atteinte 100 % production dès que la PR family-signal apporte le builder public.
Scénario canonique « parent tue enfant » enfin implémenté. La PR clone3 avait posé un waitid_decodes_child_killed_by_signal qui utilisait l’auto-abort() côté enfant (équivalent kernel pour le décodage siginfo, mais non représentatif de l’usage réel). Cette PR ajoute pidfd_send_signal_canonical_parent_kills_child_with_sigkill : parent fork (CLONE_PIDFD), enfant entre en boucle sleep, parent pidfd_send_signal(pidfd, SIGKILL, None), waitid observe Killed { signal: SIGKILL, .. }. C’est l’orchestration réellement employée en production. Le commentaire de tête de waitid_decodes_child_killed_by_signal a été actualisé (sans réécriture du test) pour pointer vers le scénario canonique au lieu d’un TODO(family-signal). La ligne loop { sleep } côté enfant du nouveau test reste non couverte : SIGKILL est uncatchable, donc aucun atexit handler ne s’exécute côté enfant ; même limitation que std::process::abort(), déjà actée précédemment. Trou test-code, pas production.
Skip environnement-dépendant — Yama ptrace_scope. Le test pidfd_getfd_duplicates_fd_from_child requiert que le kernel autorise le ptrace parent → enfant. Sur les systèmes où /proc/sys/kernel/yama/ptrace_scope ≥ 2, le kernel refuse même cette relation (CAP_SYS_PTRACE requis). Le test détecte cette condition au runtime et skip proprement avec un eprintln! explicite, à la même manière que le skip AppArmor pour CLONE_NEWUSER côté clone3. Aucune ligne production n’est dépendante de cette condition — la couverture production reste équivalente.
Tests skippés en environnement dev Ubuntu 24.04 — exécution réelle relève de la CI sur runner permissif. À l’issue de cette PR, deux restrictions runtime Ubuntu 24.04 par défaut peuvent silencieusement masquer du chemin réel :
- AppArmor (
kernel.apparmor_restrict_unprivileged_userns=1) bloque le testclone3_with_new_user_namespace_runs_or_skips(PR clone3+waitid). Sans privilèges et sans levée AppArmor, la création deCLONE_NEWUSERéchoue avant fork : le test ressort en SKIP, son corps n’est pas exécuté. - Yama (
kernel.yama.ptrace_scope ≥ 2) bloquerait le testpidfd_getfd_duplicates_fd_from_child(cette PR). Sur Ubuntu 24.04 la valeur par défaut est1(parent → enfant autorisé), donc le test s’exécute en pratique ; mais sur tout système avec≥ 2il passerait en SKIP.
Conséquence pour le suivi qualité : « cargo test passe 70/70 » sur cette machine ≠ « tous les chemins applicatifs sont validés ». Les chemins NEWUSER et pidfd_getfd-via-ptrace ne sont pas exercés sur un dev Ubuntu 24.04 stock. Leur validation effective relève d’un runner de référence en CI configuré sans ces restrictions (image dédiée, capabilities ajustées, ou simplement userns activés et yama.ptrace_scope=0/1). À provisionner quand le pipeline CI sera mis en place ; en attendant, le BDFL est conscient que ces deux tests sont en lecture seule sur sa machine.
Tests ajoutés et couverture obtenue. Sept tests pour les pidfd_* :
pidfd_open_returns_pidfd_usable_for_waitid_over_child_lifetime— happy path bout-en-bout, prouve aussi que le pidfd survit à la mort de l’enfant.pidfd_open_returns_esrch_for_nonexistent_pid— branche ESRCH, PID0x7FFF_FFFEau-delà dePID_MAX_LIMIT(~2^22).pidfd_drop_closes_underlying_fd— preuve RAII, avec caveat documenté sur la flakiness en multi-thread (mitigée par le fait qu’aucun code n’est exécuté entredropet la vérificationEBADF).pidfd_send_signal_on_closed_pidfd_returns_ebadf— chemin d’erreur depidfd_send_signal.pidfd_send_signal_canonical_parent_kills_child_with_sigkill— scénario canonique SIGKILL parent → enfant.pidfd_getfd_duplicates_fd_from_child— happy path avec skip Yama siptrace_scope ≥ 2.pidfd_getfd_on_closed_pidfd_returns_ebadf— chemin d’erreur depidfd_getfd.
Couverture finale : production 100 % lignes hors les 3 lignes Some(_) de pidfd_send_signal, qui sont le trou délibéré du stub SignalInfo (acté ci-dessus). Régions à 95 % global. Les 35 lignes restantes non couvertes sont toutes en mod tests (skips environnement + branches signal-induced exits déjà actées).
Session 2026-05-22 (suite) — clone3 + waitid, et première exception dépendance
Deuxième PR couche 0 sur la branche feat/layer-0-clone3-waitid. Implémentation des wrappers clone3 (unsafe, fork-style) et waitid (safe) de la famille process, et de la matière de types associée dans air-sys-types : CloneFlags (bitflags), CloneArgs, CloneResult, StackSpec, PidFd, WaitTarget, WaitOptions, WaitStatus, WaitEvent, plus minimum vital pour Errno, Signal, et réexport std::os::fd::{OwnedFd, BorrowedFd, RawFd}.
Périmètre de cette PR sur CloneArgs. Trois champs exposés (flags, exit_signal, stack) — la spec couche 0 (docs/specs/layer-0/family-process.md) en liste neuf. Les six restants (pidfd, child_tid, parent_tid, tls, set_tid, cgroup) seront ajoutés au fil des syscalls qui les motivent (notamment execve pour stack, namespaces de mount pour set_tid). L’évolution est strictement additive ; aucun champ existant ne sera renommé ni retiré. La demande de pidfd se fait via CloneFlags::PIDFD — le PidFd résultant est retourné dans CloneResult::Parent. La création de thread (CLONE_VM/CLONE_THREAD/CLONE_SIGHAND ou stack: Some(_)) est refusée explicitement avec Errno::EINVAL tant que les tests bout-en-bout ne la couvrent pas.
Première exception au Principe 6 — bitflags. L’utilisation de la crate bitflags (~30+ types de drapeaux Linux à venir dans la couche 0) ne respecte pas la règle des 80 % du Principe 6 : la macro bitflags! émet pour chaque type un grand nombre de traits et de méthodes (BitOr/BitAnd/BitXor/Not/Sub/Extend/FromIterator/iter/from_bits/…) dont Air n’utilise qu’une fraction. Le ratio se mesure sur le code émis, pas sur la « surface d’API utile ». Adoption par exception explicite (modèle ADR-016 / icu4x), inscrite dans EXCEPTIONS.md à la racine. Distinction importante consignée pour qu’elle ne se vide pas par mesure subjective : DEPENDENCIES.md reste réservé aux dépendances qui passent le seuil ; les autres vivent dans EXCEPTIONS.md, jamais par redéfinition du dénominateur.
Pin strict appliqué localement sur bitflags. Cette PR pose bitflags = "=2.11.1" (et non "^2.4" / "2.4") dans [workspace.dependencies]. Le pin strict apporte deux garanties complémentaires au Cargo.lock committé : (1) le lock garantit la reproductibilité du build tant qu’il est intact (ADR-025) ; (2) le pin strict ajoute la reproductibilité de la résolution — si le lock est supprimé ou qu’une re-résolution est forcée (ajout d’une autre dép qui contraint le solver, cargo generate-lockfile), une borne lâche ^2.4 peut glisser silencieusement vers la plus récente 2.x compatible, ce qu’=2.11.1 interdit. Le pin reste cohérent entre Cargo.toml, Cargo.lock, et EXCEPTIONS.md. Question délibérément non tranchée ici : politique transversale (=strict pour toute la couche 0/1 ? ^caret au-delà ? autre découpage ?). Cette décision touche aux documents fondateurs (phase-B1, ADRs) et n’a pas sa place dans une PR de wrapper syscall. À instruire dans un ADR ou un futur DEPENDENCY_POLICY.md dédié, où elle sera revue à froid pour ce qu’elle est : une politique transversale, pas un détail technique de clone3.
Errno et Signal adoptés en stubs partiels. Cette PR n’inscrit dans air-sys-types que les constantes effectivement référencées par son code et ses tests : Errno::{EPERM, EINTR, ECHILD, EINVAL} (4 sur les ~140 prévues par ADR-019) et Signal::{SIGCHLD, SIGABRT, SIGKILL, SIGSEGV, SIGSTOP, SIGTRAP} (6 sur les ~31 POSIX standards de family-signal.md). Le périmètre complet — autres constantes, méthodes name()/description() pour Errno, SignalMask/SignalFdInfo pour Signal — viendra dans les PRs dédiées (ADR-019 complet pour Errno, famille signal pour Signal). Discipline assumée : ne pas pré-implémenter des constantes sans appelant ici, sous peine de figer leur design hors de leur PR et de créer du code mort. Le commentaire de tête de chaque module le note explicitement pour qu’un futur contributeur ne tombe pas dans le piège « tant qu’à faire, j’ajoute les autres signaux pendant que j’y suis ».
Stratégie de coverage post-fork. cargo-llvm-cov configure LLVM_PROFILE_FILE avec un placeholder %p ; chaque processus (parent et chaque enfant forké) écrit son propre .profraw, fusionnés à l’agrégation. Conséquence : l’enfant qui termine par std::process::exit(N) (atexit handlers exécutés, profile flushé) contribue à la couverture du wrapper. L’enfant qui termine par std::process::abort() (SIGABRT) ou un signal-induced exit ne flush PAS son profil — limitation acceptée.
Trous de couverture connus et assumés (sur le périmètre de cette PR) :
-
Variants
WaitEvent::Stopped,WaitEvent::Continued,WaitEvent::Trapped: couverts uniquement par le helper privésiginfo_to_wait_eventtesté sur desSigInfosynthétisés en mémoire. Le remplissage effectif desiginfo_tpar le kernel pour ces variants (CLD_STOPPED/CLD_CONTINUED/CLD_TRAPPED) n’est PAS testé bout-en-bout. Trois scénarios manquent : (a)SIGSTOP/SIGTSTPenvoyé à un enfant +waitid(WSTOPPED), (b)SIGCONTenvoyé après reprise +waitid(WCONTINUED), (c)ptrace-trap +waitidcôté tracer. Si un futur bug surgit sur la conversionsi_code → variantepour l’un de ces trois cas, regarder en priorité le helpersiginfo_to_wait_eventet ajouter le scénario bout-en-bout manquant — l’absence de test n’équivaut pas à une absence de risque. -
Test exec dans l’enfant (5ᵉ test clone3 de la spec) : déféré à la PR
execve. Sans wrapper Air d’execve, on ne peut pas tester la séquence fork+exec. Le commentaire au-dessus du module de tests le note. -
Test namespaces
CLONE_NEWUSERskippé : sur Ubuntu 24.04+ (et tout système oùkernel.apparmor_restrict_unprivileged_userns=1), le test passe par uneprintln!("SKIP …")et unreturn. Les ~20 lignes du corps du test ne sont pas exécutées dans cet environnement. Le wrapperclone3lui-même n’a aucun branchement spécifique sur les bitsCLONE_NEW*, donc la couverture production n’est pas réduite — c’est uniquement le test environnement-dépendant qui est en lecture seule sur cet environnement. Re-exécuter ce test sur un système qui autorise les userns non privilégiés (ex. Fedora Workstation, conteneur dédié) fermerait les lignes correspondantes. -
Test capabilities (
NEWPIDsansNEWUSER) : branche SKIP root non couverte. Le test contient un gardeif is_root() { eprintln!(...); return; }. Quand le runner n’est pas root (cas habituel), les lignes du SKIP ne sont pas exécutées. Si jamais le runner tourne en root, le scénario « EPERM attendu » ne s’applique pas et le SKIP entre en jeu ; sinon le corps du test couvre la brancheErr(EPERM). -
Test
waitid_decodes_child_killed_by_signal— orchestration canonique différée. Le test fait s’auto-abort()l’enfant (production de SIGABRT côté enfant), ce qui donnesiginfoidentique à « parent → child viakill/pidfd_send_signal» :si_code = CLD_KILLED/CLD_DUMPED,si_status = SIGABRT. La branche du dispatch waitid exercée est la même — l’objet du test (validation du décodage enWaitEvent::Killed) est rempli. Ce que ce test ne prouve pas : l’orchestration parent→enfant réellement employée en production (où le parent contrôle la mort de l’enfant). Cette orchestration exige un wrapperkilloupidfd_send_signal, qui appartient à la famillesignalet n’est pas dans cette PR. Option refusée délibérément : écrire ~40 lignes d’asm!test-only pour unraw_killétait tentant mais aurait été un syscallkillréimplémenté hors périmètre, sans SAFETY revues, sans conventions ADR-021 — exactement le débordement qu’on a corrigé sur Errno/Signal. Le test garde une branche// TODO(family-signal):qui le pointera vers le scénario canonique quand le wrapper existera. -
Trou de couverture résiduel sur l’auto-
abort()côté enfant.std::process::abort()(etSIGKILLsi on avait choisi le scénario canonique) bypasse les atexit handlers, donc le compteur LLVM côté child n’est pas flushé. La lignestd::process::abort();apparaît non couverte alors qu’elle s’exécute bel et bien (sinon l’enfant ne mourrait pas et le parent ne verrait pasWaitEvent::Killed). Compromis acceptable, hors de portée raisonnable : couvrir cette ligne demanderait un appel explicite à__llvm_profile_write_file()(lien runtime LLVM, non portable hors instrumentation) ou un signal catchable + handler — complications non justifiées par le bénéfice.
Tests : 35 dans air-sys-types (dont les 9 hérités du bootstrap + 26 nouveaux pour Errno/Signal/Clone*/Wait*/PidFd) ; 26 dans air-sys-syscall (dont les 6 hérités pour getpid/gettid + 20 nouveaux pour clone3/waitid/siginfo_to_wait_event/build_kernel_clone_args/errno_from_negative_syscall_ret) ; 1 binaire d’intégration main_thread_invariant inchangé. Total 62 tests passants.
Couverture finale : 100 % lignes production sur les quatre fichiers source ; régions à 96 % global (gap concentré dans les expect() d’invariants par construction et les arms de match jamais pris, conformément à la politique consignée dans la première moitié de la session).
Session 2026-05-22 — Bootstrap couche 0 : workspace + getpid/gettid
Première session d’implémentation. Création de la branche feat/layer-0-bootstrap et mise en place du workspace Cargo (edition 2024, rust-toolchain.toml pinné à 1.85.0) avec les deux crates fondatrices air-sys-types et air-sys-syscall. Deux premières fonctions de la famille process implémentées strictement conformes à la spec (docs/specs/layer-0/family-process.md) : getpid() -> Pid et gettid() -> Tid, syscalls appelés directement via core::arch::asm! sur x86_64 et aarch64, sans passer par la libc. Tests unitaires et test d’intégration harness = false pour l’invariant gettid()==getpid() sur le thread principal (impossible à observer dans un #[test] standard du fait du test runner cargo).
Décision méthodologique consignée — seuil régions llvm-cov. Les régions llvm-cov < 100 % sont acceptées lorsqu’elles correspondent à des branches d’échec d’assertions de test (assert_eq!/assert_ne!) ou de .expect() sur des invariants garantis (par exemple, le retour POSIX strictement positif de getpid et gettid consommé par NonZeroI32::new(...).expect(...)), jamais prises en exécution normale. Le critère du Principe 1 (100 % lignes + branches) reste tenu et reste le seuil normatif ; le seuil « régions » n’est pas un objectif. Motivation : ces branches inatteignables existent par construction (preuve de l’invariant côté kernel/POSIX), les supprimer demanderait soit de dégrader le diagnostic (perdre les messages d’assertion qui aident l’humain en cas de régression sur le wrapper asm!), soit d’enfreindre le Principe 5 en remplaçant un expect() vérifié par un unsafe { new_unchecked } — choix qui transformerait une violation d’invariant bruyante en UB silencieux. À noter pour tout futur contributeur tenté de viser 100 % de régions : c’est un faux objectif.
Décision afférente — préférence stricte pour la construction vérifiée. Sur la couche 0, NonZeroI32::new(...).expect(...) est préféré à unsafe { NonZeroI32::new_unchecked(...) } même quand l’invariant POSIX garantit la non-nullité. Raisons : (1) Principe 5, pas d’optimisation avant mesure sur un chemin non chaud ; (2) si l’asm! des syscalls est jamais mal câblé (numéro de syscall ou registre), new_unchecked(0) est un UB silencieux alors qu’un expect() rend la violation bruyante et diagnostiquable ; (3) la branche d’échec, jamais prise, relève du même statut que les autres branches d’invariants — couverture lignes conservée à 100 %, voir décision ci-dessus.
Session 2026-05-20 (suite 5) — Rédaction du README final (EN + FR)
Refonte du README.md à la racine du dépôt. Le précédent était une version provisoire qui décrivait essentiellement la structure de la documentation. Le nouveau est la vitrine publique du projet prête pour le jour de l’ouverture publique (avec un bandeau de statut à retirer ce jour-là).
Décision sur la langue : README principal en anglais (convention GitHub internationale, anticipation de l’ouverture publique vers une communauté internationale), avec un README-fr.md complémentaire qui fournit la version française. Les deux pointent l’un vers l’autre. Validation BDFL.
Contenu du nouveau README (les deux versions ont une structure identique) :
- Bandeau de statut « phase de design, pas encore ouvert publiquement » à retirer le jour de l’ouverture.
- Présentation d’Air en deux paragraphes : stack en couches, deux profils (
air-baseconsole,air-desktopgraphique), Rust polyglotte via ABI C. - État du projet : phase de design, jalons documentaires atteints, pas de code public, pas de date publique.
- Architecture en bref : les 6 couches en quelques lignes avec liens vers ADR-001 (AirCom), ADR-014 (machines de référence), ADR-025 (builds reproductibles).
- Table de documentation avec chemins et langues, indiquant clairement ce qui est FR / EN / les deux.
- Section « Building Air » : « Not yet applicable » + stratégie documentée (toolchain pinnée, Cargo.lock committé, reproductibilité bit-pour-bit). Pas de procédures fantômes.
- Section « Contributing » : « Not yet open to external contributions », explication du rôle BDFL actuel, énumération de ce qui sera en place à l’ouverture publique (CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, RFC, DCO).
- Communication : domaine
air-desktop.org(réservé, pas de site), org GitHubair-desktop-project, channels à annoncer plus tard. - Trademark : intention de dépôt à la fondation, modèle Linux Foundation pour la politique d’usage.
- Licence : MPL 2.0, renvoi vers LICENSE.
Mises à jour de cohérence : JOURNAL.md mis à jour, nouveau prochain chantier identifié = CONTRIBUTING.md détaillé (le README y fait référence et la matière existe déjà dans docs/setup/phase-B*.md). Le backlog court terme est réordonné en conséquence.
Fichiers committés via Claude Code en local : README.md (modifié), README-fr.md (nouveau), docs/JOURNAL.md (modifié). Commit 048e2cb signé GPG + DCO, vérifié par GitHub (reason: valid), push b3737d6..048e2cb sur origin/main. Diff cumulé : +277 / −54 lignes.
Session 2026-05-20 (suite 4) — Mise en place du dépôt GitHub privé
Sujet soulevé par Thierry : « git c’est aussi un historique… une mémoire ». Création du repo pour versionner immédiatement la documentation, même si on ne produit pas encore de code, afin de capturer l’historique fin des décisions.
Décision stratégique tranchée par BDFL : repo privé d’abord, pas public. Le moment du flip vers public sera l’ouverture publique du projet, à maîtriser plus tard quand README final, CONTRIBUTING, SECURITY, marque protégée et channels communauté seront en place. Cohérent avec la politique d’édition design-phase actuelle.
Nouvelle pratique actée : pour les tâches d’exécution locale (git, gh, build, etc.), Claude produit des prompts autonomes pour Claude Code que Thierry copie-colle dans son terminal. Thierry n’intervient qu’en dernier recours. Mémoire sauvegardée pour les futures sessions.
Travaux effectués :
- Côté Cowork (préparation) :
LICENSEcréé (texte officiel MPL 2.0),.gitignorecréé (macOS/Linux/Windows/éditeurs/backups, avec note queCargo.locksera committé),README.mdenrichi d’un bandeau « phase de design pré-ouverture publique » à retirer le jour J,git init -b mainexécuté dans le workspace avec configuration localeuser.name/user.email. Rédaction du prompt Claude Code détaillé (pré-vols, création repo, commit signé, push, protections de branche, rapport final). - Côté Claude Code (exécution locale) :
- Repo
air-desktop-project/aircréé en privé sur GitHub. - Première authentification
ghrafraîchie pour ajouter les scopesadmin:gpg_keyetadmin:public_key. - Clé GPG
A9F56C4D9F59EE03ajoutée au compte GitHubtdelhaise. - Commit initial
b3737d6signé GPG + DCOSigned-off-by, vérifié par GitHub (verified: true,reason: valid). - Push
origin mainréussi. - Protections de branche non appliquées : le tier gratuit GitHub Free bloque l’API de protection sur les repos privés d’org. Documenté comme dette connue (cf. section ci-dessus).
- Repo
- Côté Cowork (clôture) : JOURNAL mis à jour avec l’état du dépôt, le SHA du commit initial, la dette « protections de branche », et la nouvelle pratique de délégation à Claude Code.
État final : dépôt opérationnel, signé, sourcé, prêt à recevoir tous les travaux ultérieurs. Premier point de bascule franchi.
Session 2026-05-20 (suite 3) — Enrichissement d’ADR-001 (AirCom vs D-Bus)
Sujet soulevé par Thierry : le choix de ne pas reposer sur D-Bus comme transport principal est l’un des plus visibles et contestables d’Air, mais l’ADR-001 dans sa forme initiale faisait 12 lignes — anormalement court par rapport aux autres ADRs phase 0 (145 à 210 lignes). Pour un projet qui prétend à la transparence et à la qualité argumentative, cette dette de documentation pouvait créer de la défiance chez les contributeurs venant de l’écosystème D-Bus traditionnel.
Approche retenue : enrichir substantiellement ADR-001 selon trois principes posés par BDFL — pragmatisme, sources d’informations, cohérence/incohérence avec les exigences de qualité d’Air. Travaux effectués :
- Rédaction d’un ADR-001 substantiel (197 lignes, dans la fourchette des autres ADRs phase 0) avec dix sections : contexte (reconnaissance de la place de D-Bus dans l’écosystème), décision en 4 points fermes, justification technique en 5 sous-sections (broker centralisé, typage/versioning, FD passing, zero-copy/back-pressure, sécurité par policy XML — chaque propriété de D-Bus mise en regard d’une exigence Air nommée : Charte principe 4, Principe d’ingénierie 8, ADR-010, ADR-012, ADR-014, etc.), alternatives évaluées (kdbus 2013-2017, bus1 2016-2018, dbus-broker, évolution incrémentale), synthèse de AirCom, périmètre exact du pont D-Bus dans Air (sd-bus peer-to-peer pour systemd,
air-dbus-bridgeoptionnel,xdg-desktop-portal-aircomme exception assumée), articulation avec ADR-002/005/010/012/013/022, FAQ anticipée de 6 questions courantes, conséquences (bénéfices/coûts/risques), statut futur. - Sources publiques citées : spécification D-Bus officielle, dépôt
dbus-broker(Tom Gundersen / David Herrmann), trois articles LWN sur kdbus et bus1. La pédagogie repose sur des faits vérifiables, pas sur des opinions. Note : les liens LWN précis sont à vérifier avant ouverture publique. - Inspirations AirCom assumées ouvertement : XPC (macOS), Cap’n Proto, FIDL (Fuchsia), Mojo (Chromium), Binder (Android). Cela coupe court à toute accusation de NIH non avouée.
- Ton : factuel, sans posture évangéliste, reconnaissance explicite de la place de D-Bus dans l’écosystème, exception
xdg-desktop-portal-airassumée, FAQ qui désamorce 6 questions classiques (« pourquoi pas étendre D-Bus ? », « est-ce que c’est du NIH ? », etc.). - Mises à jour de cohérence : registre passé en v2.3 (bloc historique enrichi, table inchangée — la décision n’a pas évolué, seule sa documentation l’a fait).
Relu et validé par BDFL sans modification.
Complément vision (priorité 2 du plan initial) : ajout d’un paragraphe pédagogique de 4-5 phrases dans vision-fr.md et vision-en.md, inséré juste après le paragraphe décrivant la couche 2. Le paragraphe (a) reconnaît la place de D-Bus dans l’écosystème Linux (systemd, NetworkManager, BlueZ, polkit, freedesktop), (b) cadre la décision « Air parle D-Bus pour interop, conçoit AirCom pour son cœur natif », (c) explicite les besoins spécifiques (sécurité capability-based, stabilité 10 ans, perf matériel modeste) que D-Bus ne couvre pas par construction, (d) renvoie vers ADR-001 pour le détail. Ton sobre, factuel, cohérent avec le reste de la vision. Le sujet est désormais traité à deux niveaux : visible et accessible dans la vision (porte d’entrée publique), argumenté et sourcé dans ADR-001 (référence technique).
Session 2026-05-20 (suite 2) — Passe 1 de nettoyage de la macro-architecture
Résolution de la dette de cohérence entre architecture/macro-architecture-fr.md (rédigée en early-stage) et les ADRs phase 0 (019-025) finalisés depuis. Travaux effectués :
- Inventaire complet des divergences :
- 35 références prospectives ADR-018 à ADR-026 désignant des services système couche 5 non instruits (protocole privé AirCom
air-wm/apps,air-screencapture,air.system.appearance/theming,air-share,air-firewalld,air-power,air-printd/air-bluetoothd/air-nfcd,air-prefs,air-keychain), en collision avec les ADRs phase 0 réels. - Distinction subtile : ADR-022 prospectif avait deux significations dans le doc (framework networking couche 2 d’une part,
air-firewalldcouche 5 d’autre part) — neutralisations séparées. - Section 2 (couche 0) entièrement obsolète : crate
air-sysunique avec 11 sous-modules incohérents avec le découpage actuel des specs, modèle d’erreurAirSysErrorenum riche contredisant ADR-019 (Errno minimaliste), conventions ADR-021 absentes, choixrustixnon confirmé.
- 35 références prospectives ADR-018 à ADR-026 désignant des services système couche 5 non instruits (protocole privé AirCom
- Révision de la section 2 (couche 0) — réécriture de la sous-section « Crate principale » et des sous-sous-sections détaillées :
- Découpage en deux crates
air-sys-types(types fondamentaux) +air-sys-syscall(wrappers de syscalls). - Liste des 10 familles actuellement spécifiées (process, fs, mem, signal, time, net, ipc, security, system, io_uring) avec pointeurs vers les specs.
- Identification des 3 trous à combler ultérieurement : famille
device, familleebpf, confirmationrustix. - Module
io_uringintroduit avec référence à ADR-022 et ses 4 Temps. - Sous-section « Gestion d’erreur » alignée sur ADR-019 (
Errno#[repr(transparent)]surNonZeroI32, sans contexte ; enrichissement aux couches 1+). - Nouvelle sous-section « Conventions transverses » consignant les 5 conventions d’ADR-021.
- Sous-section
rustixclarifiée en « hypothèse de travail à confirmer en début d’implémentation, dans le cadre du workflow ADR-024 ».
- Découpage en deux crates
- Neutralisation par sed multi-passes des 35 références prospectives. Toutes remplacées par des marqueurs neutres « ADR à produire — service [X] » qui ne pré-réservent aucun numéro. Aucune occurrence prospective résiduelle (vérification par grep exhaustive).
- Mises à jour de cohérence :
INDEX.mdpassé en v1.3 (statut macro-architecture mis à jour, section « Point d’attention » réécrite, backlog réordonné),JOURNAL.mdmis à jour (état courant, prochain chantier = README final, dettes connues purgées des dettes résolues et complétées des dettes restantes).
Vérifications finales : aucune référence ADR cassée, table des matières intacte (10 sections, 1764 lignes — quasi inchangé en volume), références aux ADRs réels uniquement (001-017 fondateurs, 018 imagerie, 019-022/024 phase 0).
Session 2026-05-20 (suite) — ADR-025 « Stratégie de builds reproductibles »
Clôture de la série de design phase 0. Travaux effectués :
- Trois arbitrages structurants validés par BDFL : (1) périmètre = artefacts Rust natifs uniquement pour la phase 0 (bundles
.airapp, images OS = ADRs ultérieurs) ; (2) calendrier = bloquant en CI dès le premier commit phase 0 ; (3) outillage = approche minimaliste Rust natif + discipline, pas de Nix ni de système de build alternatif. - Rédaction d’
ADR-025-builds-reproductibles-fr.md(~210 lignes) couvrant : contexte (rattachement à Charte principes 3 et 5, ADR-013, ADR-015, phase-C), décision en 4 points fermes, mécanismes techniques (toolchain pinnée viarust-toolchain.tomlexact,Cargo.lockcommitté, container Debian par digest SHA-256, env vars whitelistées,SOURCE_DATE_EPOCHdérivée du commit, RUSTFLAGS avec--remap-path-prefix+codegen-units=1+--build-id=none, linkermold, discipline surbuild.rset code généré), vérification CI multi-pipelines (pr-fulldouble-build +diffoscope,pr-hardwarecross-machine,main-nightlytriple-build), articulations avec ADR-010/013/014/015/024 et Principes 1/6/7, conséquences (bénéfices/coûts/risques), statut futur (ADRs ultérieurs pour bundles et image OS). - Mises à jour de cohérence : registre passé en v2.2 (ajout ADR-025, retrait de la mention « à produire »),
INDEX.mdpassé en v1.2 (statut ADR-025 = OK, statistiques recalculées, ordre de priorité court terme revu),JOURNAL.mdmis à jour (état courant, prochain chantier = révision macro-architecture passe 1).
État final : zone de design phase 0 close. Prochain chantier identifié = révision macro-architecture passe 1.
Session 2026-05-20 — Réorganisation documentation et résolution doublon ADR-018
Cohérence et structuration. Travaux effectués :
- Réorganisation physique : déplacement des 22 fichiers traînant à la racine vers
docs/selon l’arborescence cible décrite dansINDEX.md(vision/, charte/, principes-ingenierie/, adrs/, architecture/, specs/layer-0/). Suppression des obsolètes (ADR-009-bis, ancienINDEX.mdracine,.DS_Store). - Résolution du doublon ADR-018 : la série phase 0 instruite (018-erreurs, 019-signaux, 020-conventions, 021-io_uring, 022-runtime, 023-deps, 024-reproducible prévu) a été décalée de +1 pour libérer ADR-018 au profit du « Modèle d’imagerie » fondateur acté en registre v2.0. Nouvelle numérotation : 019=erreurs, 020=signaux, 021=conventions, 022=io_uring, 023=runtime, 024=workflow-deps, 025=reproductibles (à produire).
- Convention de nommage unifiée : tous les ADRs suivent désormais
ADR-NNN-titre-fr.md. Les 6 ADRs phase 0 ont été renommés depuis le style anglais sans préfixe. - Propagation des renumérotations : titres internes H1 des 6 ADRs phase 0 mis à jour ; références croisées corrigées dans
setup/etspecs/layer-0/. - Registre des ADRs : passé en v2.1 (intégration des ADRs phase 0 + bloc historique). Liens vers les fondateurs apparentés corrigés pour utiliser les chemins relatifs
../vision/,../charte/, etc. - Index :
docs/INDEX.mdpassé en v1.1 (arborescence à jour, point d’attention sur la collision macro-architecture, statistiques recalculées). - README.md racine : refondu pour décrire la nouvelle structure et pointer vers
docs/INDEX.md. - Identification de la dette macro-architecture vs ADRs phase 0 (cf. section « Dettes connues »).
État final vérifié : 50 fichiers .md dans docs/, 25 ADRs sous docs/adrs/, aucun lien markdown cassé, aucune référence aux anciens chemins.
Session 2026-05-18 — Documents fondateurs version 1.0
Finalisation et figement des documents fondateurs en v1.0 :
- Vision (FR + EN), Charte (FR + EN), Principes d’ingénierie (FR + EN).
- Registre des ADRs v2.0 (restructuré en fichiers individuels par ADR).
- 17 ADRs fondateurs (001 à 017) finalisés.
- ADR-018 « Modèle d’imagerie » instruit en phase de design pré-ouverture publique.
- ADR-009 révisé pour intégrer l’amendement « API exclusivement déclarative » (anciennement envisagé sous nom ADR-009-bis ; politique d’édition design-phase appliquée — pas de pattern
-bisavant ouverture publique). - Politique d’édition design-phase actée : avant ouverture publique, les amendements aux documents fondateurs s’intègrent directement, pas par fichier
-bis.
Sessions antérieures à 2026-05-18 — Spécifications phase 0
Sessions consacrées à la spécification de la couche 0 et du runtime async. Productions :
- 6 ADRs phase 0 (numérotés à l’époque 018 à 023, renumérotés 019 à 024 le 2026-05-20).
- 9 specs de familles de syscalls dans
specs/layer-0/(process, fs, mem, signal, time, net, ipc, security, system). - Spec partielle
air-sys-types(~25 types). - Spec
io-uring-overview(synthèse, détail Temps 1-4 à produire). - Documents de setup phases A (décisions techniques), B1 (structure workspace), B2 (conventions opérationnelles), C (infrastructure), D (communauté).
- Document de macro-architecture des 6 couches (rédigé en early-stage, dette de cohérence à traiter — cf. section « Dettes connues »).
Licence du document : MPL 2.0 Statut : journal de bord interne, mis à jour à chaque fin de session.