Suivi — état d’avancement & tâches à venir
Document unique de suivi vivant. C’est le point d’entrée « où en est-on / quelle est la prochaine tâche / par où on est passé / qu’a-t-on fait ». Il est tenu à jour au fil des merges (chaque jalon met à jour l’état + coche/ajoute une tâche). Référencé par
CLAUDE.mdetAGENTS.md.Rôle des autres documents (pour ne pas les confondre) :
INDEX.md— carte structurelle : ce qui existe, où le trouver.JOURNAL.md— archive chronologique append-only (le détail session par session, PR par PR). On y écrit l’historique ; on ne le « maintient » pas comme tableau de bord.roadmap-couche1-libc-pal-fr.md— plan détaillé §-numéroté de la clôture couche 1 (les specs/audits y renvoient via « §5.3 P0.x »). Le statut vivant de ce plan est tenu ici.
Dernière mise à jour : 2026-07-13 (🎉 std COMPLET sur le PAL safe + MERGÉ + RE-SCELLÉ — target_os="air", sans libc C, unsafe confiné couche 0. Portage std mergé (#326) + différés soft mergés (#327 : os::fd/pipe, options socket [+ descellement couche-0-v1.13], DNS). Tous sous-systèmes prouvés on-target 2 arches (x86_64/carbon + aarch64/raspi vrai matériel) : alloc/errno/futex+sync/stdio/env/exit/thread/mpsc/fd+fs(+current_exe)/process/net(TCP+UDP + timeouts/multicast)/random/os::fd/pipe/DNS. 1 seul fork externe (bitflags), RustCrypto/icu4x/air-base-lib hors fermeture (ADR-090). RE-SCEAUX POSÉS (signés) : couche-0-v1.13 (options socket à structure) + couche-1-v2.0 (jalon majeur : la couche 1 rend possible std sur Air). COUCHE 2 EN COURS : ✅ ADR-091 (motif réseau sans-IO normatif) ; ✅ métadonnée de couche + gate check-layers (#328) ; ✅ air-async inc 0→3 — runtime async couche 2 complet et sound : découpage air-uring(L1)/air-async(L2) [ADR-092], sûreté d’annulation io_uring (valgrind definitely-lost=0), exécuteur spawn/block_on, éveil inter-thread (handshake SeqCst, loom vert), roue de timers, back-pressure, thread-per-core (ReactorPool/msg_ring), multishot + provided buffers (soundness étendue), I/O fichier/réseau, embarquement hôte — mergés #329-332, zéro unsafe couche 1, zéro descellement couche 0. ✅ IPC AirCom — FONDATION posée (#333) : air-com/air-com-proto (sans-IO sur air-async, ADR-091), capability = fd (SCM_RIGHTS, ADR-010), wire Cap’n Proto (ADR-040) ; 4 lacunes couche 1 comblées — air-shm (memfd+seals, zero-copy), SCM_RIGHTS async (air-uring/air-async), SEQPACKET + pont OwnedFd (air-socket additif → re-sceau couche-1-v2.1) ; preuve : fd-capability portant un shm scellé traverse le socket, pair mmap zero-copy. ✅ Spec AirCom posée (#334) (couche 2, wire format + transport + pub-sub anneau + bootstrap air-registry). ✅ AirCom inc.1 — Codec Cap’n Proto réel : nouveau crate air-com-schema (schéma v1 figé Envelope/CapRef/ShmRef/MessageKind, code généré committé, no_std+alloc), Codec réel remplaçant le placeholder Ping/Pong (Envelope schema-first, décodage borné anti-hostile, zéro unsafe/panic), pilote+exemple Call→Return (exit 0), fuzz fuzz_air_com_codec (871k runs, 0 crash) ; verdict no_std : capnp no_std+alloc OK (prouvé en graphe isolé alloc-only) — chemin nominal, aucune nouvelle exception. Cœur 98 % lignes / pilote 98 %. ✅ AirCom inc.2 — StateMachine multi-états + Multiplexer + Handshaker : StateMachine à deux niveaux (cycle de connexion Opening→Established→Draining→Closed/Faulted + une machine par canal Idle→AwaitingReturn→{Returned|Errored|Cancelled}, transitions illégales rejetées sans panic), Multiplexer (ChannelTable : channelId par canal, parité de rôle client-pairs/serveur-impairs, borné maxChannels, isolation inter-canaux prouvée), Handshaker (négo de version Hello capnp ajouté au schéma v1, intersection déterministe min-version/bornes + extensions communes triées, échec → Faulted sans I/O) ; pilote + exemple ping_pong : handshake + 2 requêtes concurrentes multiplexées sur 1 connexion (exit 0) ; model-based tests de la StateMachine (proptest : modèle de référence, puits terminaux, pas de deadlock) + fuzz fuzz_air_com_handshake (Hello hostile, 506k runs, 0 crash). Cœur 98,9 % lignes (state/channel 100 %) / pilote 99 %. ✅ AirCom inc.3 — transport SEQPACKET bout-en-bout + 2 surfaces + exemple 2-exe : le bootstrap std::os::unix::net d’inc.0 a disparu — le control plane passe sur AirUnixSeqpacket réel (socketpair et adresse abstraite nommée, bridge into_owned_fd→air-async) ; deux surfaces de première classe partageant le même cœur (async sur air-async/io_uring : Connection::connect_async/Listener::{bind_async,accept}/call/serve ; sync sur air-socket bloquant : connect_sync/{bind_sync,accept_blocking}/call_blocking/serve_blocking) ; test croisé client sync ↔ serveur async et l’inverse (preuve du partage du cœur, spec §11) ; livrable développeurs = 2 vrais binaires aircom_server/aircom_client (méthode « greet », 2 requêtes multiplexées sur channelId 0/2, adresse abstraite, README tutoriel) — exécutés en 2 process, exit 0 des deux côtés. Cœur PUR inchangé (aucune fn unsafe exposée), pilote >90 %. ✅ AirCom inc.3b — accept SEQPACKET pleinement io_uring : le STOP-and-report d’inc.3 est clos — l’accept(2) bloquant du listener nommé a disparu. Additif couche 1 (air-socket, autorisé BDFL — pur, zéro changement sémantique des API existantes, miroir de AirUnixSeqpacket) : AirUnixSeqpacketListener::into_owned_fd(self) -> OwnedFd (transfère l’ownership hors du registre air-handle, testé bind→into_owned_fd→ré-adoption→accept réel sans double-close, 100 % lignes+branches sur le neuf). Pilote async air-com : Listener::bind_async bride désormais le fd d’écoute vers air-async (TcpListener::from_owned) et Listener::accept fait un IORING_OP_ACCEPT (plus d’accept(2) bloquant) ; surface sync inchangée (accept_blocking natif). Exemple 2-process relancé (exit 0 des deux côtés) + test pilote named_async_roundtrip (accept io_uring round-trip). Pilote air-com (surface.rs) 100 % lignes. Additif L1 en attente du re-sceau couche-1-v2.2, DIFFÉRÉ au jalon de finalisation d’AirCom (posé par le superviseur — aucun tag posé ici). ✅ AirCom inc.4 — data plane zero-copy intégré + Flow Controller : composant 5/9 Flow Controller réalisé (cœur PUR air-com-proto/flow.rs, remplace le marqueur unité) — crédits par canal, fenêtre bornée (modèle reactive-streams), grant_credit/consume_credit/available_credit intégrés à la table de canaux du Session Context ; jamais de file non bornée, crédit jamais négatif (checked ops), property-tests proptest (invariant somme, borne respectée, épuisement→blocage, recharge→déblocage). Message MessageKind::credit orthogonal à la StateMachine de requête (routé hors du cycle de vie de canal — ni on_send ni on_receive). Data plane intégré au flux de messages (pilote air-com/dataplane.rs) : Connection::call_with_payload/serve_with_payload — une invocation porte une charge volumineuse via ShmRef (schéma capnp v1 étendu par ajout compatible Envelope.shmRefs @5, discipline ADR-012, ShmRef déjà présent activé) ; le memfd scellé voyage par SCM_RIGHTS avec le message de contrôle, le pair mmap zero-copy (la charge ne traverse jamais le socket — seul le fd). Conventions §5 appliquées : sealing obligatoire (segment non scellé refusé côté production ET réception → Faulted ; défense en profondeur : sceaux réels du memfd vérifiés, un sealed=true menti est rejeté), partage read-only (F_SEAL_WRITE + mmap PROT_READ), length logique ≤ taille validé en amont (Principe 4). Le cœur ne voit que la référence (invariant §1.1 — aucun fd ; le pilote apparie slot↔fd). Flow control appliqué au data plane (fenêtre initiale semée à l’ouverture, crédit consommé avant émission, rendu par le consommateur après lecture). Exemple 2-process aircom_dataplane_{server,client} : le client publie 1 Mio scellé, le serveur mmap zero-copy et confirme (len+checksum) — exécutés sur carbon, exit 0 des deux côtés, checksums coïncidents. fuzz fuzz_air_com_codec étendu (champs shmRefs). Cœur 98,9 % lignes (flow/state 100 %, channel 99,6 %) / pilote 97,6 % lignes (dataplane 95,4 %). Cœur PUR inchangé (aucune fn unsafe exposée, no_std). ✅ AirCom inc.5 — pub-sub par anneau de mémoire partagée (§7, diffusion 1→N) : 9ᵉ patron autonome distinct du RPC (canal dédié, hors StateMachine requête/réponse). Cœur PUR air-com-proto/ring.rs — anneau SPMC (1 producteur / N consommateurs) : disposition figée v1 ({capacity, slotPayloadCap, writeSeq, notify} + slots {stamp, len, payload}, arithmétique checked_*/saturante, zéro as nu), RingProducer::ring_publish (overwrite best-effort, ne bloque JAMAIS) / RingConsumer::ring_poll (détection de perte par saut de writeSeq au-delà de capacity, compteur lost persisté), protocole seqlock (estampille writing-impair/committed-pair, barrières Release/Acquire) rejetant toute lecture déchirée (comptée perdue, jamais rendue) — zéro fn unsafe, zéro panic, no_std ; accès mémoire délégués au pilote via traits RingReader/RingWriter (le cœur ne touche aucun octet partagé, invariant §1.1). Pilote air-com/pubsub.rs : Publisher (memfd scellé FUTURE_WRITE|GROW|SHRINK — producteur garde l’écriture, abonnés read-only), Subscriber (mmap RO, poll / next_blocking avec réveil futex réel portée Shared inter-process sur le mot notify), AsyncSubscriber (air-async — sonde temporisée : le réveil futex-via-io_uring/eventfd exige 2 pièces couche 1 absentes, hors additif atomique unique → différé honnêtement, best-effort §7). Additif couche 1 air-shm (accesseurs atomic_u32/atomic_u64 bornés+alignés sur vues mmap — l’unsafe reste couche 1, jamais dans le pilote). Model-based + property tests (roundtrip sans perte, abonné lent détecte l’overwrite, perte en bord de fenêtre reportée, lecture déchirée détectée/comptée/retentée, 2 abonnés reçoivent chacun tout, len mensongère bornée) + fuzz fuzz_air_com_ring (anneau hostile : writeSeq/stamp/len mensongers, charge tronquée — 2 000 000 runs, 0 crash, jamais de charge > slotPayloadCap). Cœur ring.rs 95,7 % lignes (100 % fn ; régions restantes = gardes SizeOverflow/conversions injoignables sur 64 bits, Principe 2) / pilote pubsub.rs 97,4 % lignes. DÉCISION BDFL : ship du mécanisme prouvé in-process ; bootstrap control-plane (§7 « Établissement » : publier le fd d’anneau via ShmRef+MessageKind::event) + exemple 2-exécutables pub-sub DIFFÉRÉS (→ inc.6/additif). Additif L1 air-shm en attente du re-sceau couche-1-v2.2 groupé au jalon de finalisation AirCom (avec l’additif inc.3b). ✅ AirCom inc.6 — contrat air-registry matérialisé (mock couche 2, §9) : la découverte nommée par fd (l’impl réelle est couche 5, hors périmètre). Cœur PUR air-com-proto/registry.rs (no_std+alloc, zéro unsafe/panic, sans fd §1.1) — wire maison length-prefixé borné et anti-hostile (ServiceName newtype validé « parse, don’t validate » : non vide / UTF-8 (from_utf8 Result) / borné MAX_SERVICE_NAME_LEN=255 ; RegistryRequest{Register,Lookup} + RegistryResponse{Ok,NotFound,Denied}, décodage slice::get/checked_*, tag inconnu / longueur mensongère / octets excédentaires rejetés), table de services nom→handle opaque u64 (register/duplicate/lookup, le cœur ne voit jamais de fd — le pilote apparie handle↔fd), et application du verdict d’entitlement (resolve_lookup/resolve_register : reçoivent un booléen, Denied l’emporte sur NotFound, spec §9). Pilote air-com/registry.rs : rendez-vous par socketpair, fd-only — register(name) cède reg_side au registry par SCM_RIGHTS (le service garde svc_side = canal d’acceptation) ; lookup(name) → le registry crée une paire fraîche, pousse service_end au service par le canal stocké et rend client_end au client, chacun enveloppant son fd en Connection (handshake Requester/Responder). Additif de surface couche 2 Connection::from_connected_async(fd,role,id) (aucun additif/descellement couche 1). Preuve end-to-end : service register(«greet») → client lookup(«greet») obtient un fd → call → le service serve répond Hello, Ada — aucune adresse échangée (matchmaking par fd, capability infalsifiable ADR-010) ; cas NotFound (inconnu) et Denied (entitlement refusé masquant un service présent) prouvés. fuzz fuzz_air_com_registry (décodeur de requêtes/réponses hostile + round-trip + table/verdict, 1 000 000 runs, 0 crash). Cœur registry.rs 100 % lignes (99,5 % régions ; 1 garde u16 injoignable par construction, Principe 2) / pilote registry.rs 98 % lignes (95 % fn ; seule fn non couverte = closure d’échec socketpair(2) injoignable). PAS d’exemple 2-process pour cet incrément (décision BDFL : contrat prouvé in-process ; l’exemple viendra avec le bootstrap pub-sub différé). Prochain (AirCom) : AirCom inc.7 — intégration modèle d’objet (ADR-002) : call sur AirObject + 1er service réel air-notifyd → air-sshd. ✅ Chantier air-object (ADR-002) OUVERT — découpage validé BDFL : AO.1 (air-value) → AO.2 (air-object cœur : AirObject/AirClass, refcount AtomicU32, AirHandle) → AO.3 (propriétés observables + send_message + politiques de thread) → AO.4 (proc-macro #[air_class]) → AO.5 (introspection + conformité ABI complète), puis câblage AirCom inc.7 (MessageKind::call sur AirObject + air-notifyd). ✅ AO.1 — air-value RÉALISÉ : nouveau crate crates/air-value/ (couche 2, cdylib+rlib → libair-value.so, calqué sur air-base-capi) livrant l’AirValue — union taguée #[repr(C)] à LAYOUT FIGÉ (ABI 10 ans, ADR-012) : AirValueKind(u32) + AirValuePayload(union 16 o : i:i64/f:f64/b:bool/buffer:AirBuffer{ptr,len}/obj:*mut c_void) ⇒ AirValue = 24 o, align 8 (kind@0, pad 4..8, payload@8), gelé et vérifié par sizeof/offsetof C + tests Rust. AirStatus in-band ({code:AirStatusCode(u32), message}, ADR-045, miroir AirErrorKind + codes frontière NULL_ARGUMENT=100/WRONG_TYPE=102). Surface air_value_* : constructeurs (null/int/float/bool/string/bytes/object), getters typés (as_int…as_object, WRONG_TYPE sur mauvais type), retain/release/clone, set_object_ops. Règle de propriété « +1 retours / +0 arguments » implémentée fidèlement : côté C manuelle, côté Rust automatisée par AirValueOwned (Drop=release, Clone=retain). Buffers AIR_STRING/AIR_BYTES = comptés par référence (en-tête AtomicU32 caché avant ptr — l’ABI ne voit que (ptr,len) ; AIR_STRING valide l’UTF-8). AIR_OBJECT découplé d’air-object (qui n’existe pas encore) via un hook de vtable global (AtomicPtr, air_value_set_object_ops) : retain/release délèguent au hook si installé (AO.2 l’installera), sinon no-op sûr — zéro unsafe non justifié, zéro panic, zéro UB. Header committé include/air_value.h (cbindgen, diffé en CI, xtask CAPI_CRATES étendu). Tests : unit + property (proptest) + conformité ABI C (tests/abi/conformance.c : layout gelé + règle +1/+0 avec ops objet installées par le C) + header_committed + allocateur compteur (tests/alloc_balance.rs : zéro fuite, libération à refcount 0) + multi-thread (retain/release concurrents) + fuzz fuzz_air_value_string (1 000 000 runs, 0 crash, ASAN : ni fuite ni double-free). Barrière verte (fmt/clippy -D warnings/machete/check-layers 2→1) ; couverture 98,5 % lignes / 100 % fonctions (7 lignes restantes = gardes OOM/non_exhaustive injoignables, documentées STRUCTURAL, Principe 2). Dép unique air-base-lib (comme air-base-capi : source #![no_std], l’artefact cdylib hérite de l’allocateur/panic-handler du std du graphe). ✅ AO.2 — air-object cœur RÉALISÉ : nouveau crate crates/air-object/ (couche 2, cdylib+rlib → libair-object.so, calqué sur air-value) livrant le cycle de vie & l’identité d’objet. AirObject — en-tête #[repr(C)] à LAYOUT FIGÉ (ABI 10 ans, ADR-012) : { isa:*const AirClass, refcount:AtomicU32, flags:u32, reserved:u64 } = 24 o, align 8 (isa@0, refcount@8, flags@12, reserved@16 ; reserved = espace figé pour l’extension d’en-tête AO.3+), suivi d’un payload variable zéro-initialisé ; vérifié par sizeof/offsetof C + Rust. Champ isa (idiome ObjC, évite le mot-clé C++ class). Refcount = AtomicU32 côté Rust (interior mutability correcte — le premier prototype u32+from_ptr était UB derrière un &, corrigé), rendu uint32_t côté ABI (post-traitement cbindgen dans xtask). AirClass — méta-classe (une classe est un AirObject) : name/parent(nullable)/instance_size/finalize(nullable) ; layout non gelé (vtable/propriétés → AO.3/AO.5), opaque côté C (forward-déclarée). Bootstrap métaclasse : static ROOT auto-référente (isa → elle-même), immortelle via fanion AIR_OBJECT_FLAG_STATIC (retain/release = no-op, jamais libérée, sûr en .rodata) ; classes déclarables via AirClassCell (const, Sync). API C-ABI : air_object_alloc (refcount 1 + payload zéro), air_object_retain/release (Arc, release saturant ; à 0 → finalize puis dealloc), air_object_class, air_object_is_kind_of (remonte isa→parent), air_object_root_class, air_object_runtime_init. AirHandle<T> (RAII Rust : Drop=release/Clone=retain). Branchement du hook AO.1 prouvé bout-en-bout : air_object_runtime_init installe les ops objet dans air-value (air_value_set_object_ops) ⇒ un AirValue AIR_OBJECT retient/relâche réellement l’AirObject (test C et Rust : refcount varie, finalisé au bon moment). Durcissement conformité ABI (demande superviseur) : env AIR_ABI_CONFORMANCE_REQUIRED=1 (posée en CI, job test-coverage/air-x86_64) ⇒ l’absence de cc n’est plus un skip silencieux mais un ÉCHEC ; si gcc ET clang présents, le consommateur C est compilé+exécuté avec les deux ; rétro-appliqué à air-value. libair-object.so agrège air-value (rlib) et ré-exporte air_value_* (un consommateur du modèle d’objet lie une seule .so ⇒ hook unique). Header committé include/air_object.h (xtask CAPI_CRATES étendu). Tests : unit + property (proptest) + conformité ABI C (layout gelé, métaclasse, cycle de vie, is_kind_of, intégration AO.1) + header_committed + allocateur compteur (alloc_balance.rs) + multi-thread (retain/release concurrents, finalize 1×) + intégration hook (integration_hook.rs). Pas de nouveau fuzz (justifié : aucune frontière d’octets externe — pointeurs+entiers ; la frontière octets vit dans air-value, déjà fuzzée). Barrière verte (fmt/clippy -D warnings/machete/check-layers/gen-capi-header/mdbook) ; couverture 97,7 % lignes / 100 % fonctions (4 lignes restantes = gardes OOM/overflow/saturation injoignables, documentées STRUCTURAL, Principe 2). ✅ AO.3 — propriétés observables + send_message + politiques de thread RÉALISÉ : air-object étendu (spec §5/§2/§6). AirClass étendue (toujours opaque côté C) : politique de thread obligatoire AirThreadPolicy (Immutable/MainThreadOnly/ThreadSafe — AirClassCell::new/with_members l’exigent, pas de défaut silencieux) + table de propriétés (AirPropertyDescriptor = AirPropertyInfo{nom,AirValueKind} + accesseurs typés get(+1)/set(+0), jamais d’offset brut) + table de méthodes (AirMethodDescriptor = sélecteur → imp(args +0 → +1)). Nouvelles structs C figées committées au header : AirPropertyInfo, AirPropertyDescriptor, AirMethodDescriptor, AirThreadPolicy (repr(u32), variantes préfixées AIR_THREAD_POLICY_*) ; AirObservation/AirNotificationToken opaques (forward-déclarées). Propriétés : air_object_get_property(+1, AIR_NULL si inconnu) / air_object_set_property(AirStatus : NOT_FOUND inconnu, UNSUPPORTED Immutable/lecture-seule, WRONG_TYPE via le setter) ; résolution héritée (chaîne parent). Observation (data binding) : air_object_observe/unobserve, set notifie ancienne/nouvelle valeur (+0) sur un instantané par valeur — ré-entrance sûre (un observateur peut set/observe/unobserve), retrait pendant notif sans UB, ordre déterministe. Registre par objet : tête de liste doublement chaînée logée dans les 8 o reserved de l’en-tête figé (usage documenté ; jamais touché par le C), sérialisée par un verrou tournant core pur (no_std, sections courtes) ; purge automatique à la finalisation (zéro fuite, prouvé par l’allocateur compteur). Wrappers Rust : AirObservable<T> (cellule réactive mono-thread) + AirNotificationCenter (pub-sub par nom, closures) — plus un centre C-ABI global air_notification_subscribe/unsubscribe/post ; portée intra-processus clairement documentée (≠ AirCom). Messages : air_object_send_message(+1, dispatch via classe + chaîne parent, AIR_NULL si sélecteur inconnu / args NULL avec n>0). Politiques de thread (§6, sécurité par construction) : MainThreadOnly debug-only (décision ratifiée : debug_assert en debug, zéro coût en release — Main Thread Checker UIKit) via air_object_set_main_thread (TID principal, air-thread::current_tid couche 1, arête 2→1) ; enforcement sur get/set/send_message/release ; prédicat air_object_thread_violation (doc(hidden), testable — le panic=abort du profil de test interdit d’intercepter un vrai debug_assert) ; Immutable refuse set. Layout AirObject (24 o) inchangé/figé. Dép ajoutée : air-thread (couche 1, no_std, zéro dép externe) pour current_tid. Header committé régénéré (air_object.h tire désormais air_value.h). Tests : 54 unit+property (get/set round-trips, inconnu/mauvais type/lecture-seule/écriture-seule, héritage props+méthodes, observation ancienne/nouvelle/ré-entrance/unobserve-pendant-notif/multi-observateurs/purge-à-la-finalisation, send_message dispatch/héritage/inconnu/args, AirObservable, AirNotificationCenter Rust+C-ABI global, Immutable refuse set, noms bornés hostiles, observation concurrente ThreadSafe), + intégration thread_policy (2 arches de la logique MainThreadOnly), conformité ABI C durcie (conformance.c : classe avec props+méthodes définie depuis C, get/set, observateur C notifié, send_message — gcc ET clang), header_committed, alloc_balance (nœuds d’observation zéro fuite). Barrière verte (fmt/clippy -D warnings/machete/check-layers 2→1/gen-capi-header/mdbook) ; couverture 98,2 % lignes / 98,4 % fonctions (restant = gardes OOM/overflow d’alloc AO.2, garde isa NULL, SpinLock::new const-évalué — injoignables, documentés STRUCTURAL, Principe 2). ✅ AO.4 — proc-macro #[air_class] RÉALISÉ : premier crate proc-macro d’Air crates/air-object-macros/ (couche 2, [lib] proc-macro = true, build-time only — compile pour l’hôte, jamais liée dans un artefact cible). Attribut #[air_class(thread = …)] générant (à partir d’une struct) exactement les descripteurs const écrits à la main en AO.3 : un static AirClassCell (via with_members, const), un AirPropertyDescriptor par champ #[observable] (accesseurs get(+1)/set(+0) typés via offset_of! dans le payload #[repr(C)] après l’en-tête figé 24 o ; la notification des observateurs est héritée du chemin générique air_object_set_property d’AO.3 — la macro n’y ajoute rien), la politique de thread issue de thread = …, et un accesseur Self::air_class() -> *const AirClass. « Zéro glue par classe » : aucune fonction C par classe — la classe passe par les air_object_* génériques ; interop AirHandle<Self> gratuite (générique sur le payload). Contrainte forte respectée : thread = … OBLIGATOIRE (absent ⇒ erreur de compilation claire, jamais de défaut silencieux) ; type de champ #[observable] non géré ⇒ erreur de compilation ; jamais de panique de macro non contrôlée (toute mauvaise utilisation = compile_error! à span correct). Périmètre v1 (documenté/justifié) : champs gérés = bool/i64/f64 (scalaires AirValue, Copy, valides zéro-initialisés, sans stockage possédé → aucun finaliseur) ; différés = champs à valeur possédée (AIR_STRING/AIR_BYTES/AIR_OBJECT : exigent stockage refcompté + finaliseur généré + discipline retain/release — incrément à part) et méthodes riches (table de méthodes vide ; le dispatch reste exercé : send_message rend AIR_NULL sur sélecteur inconnu depuis une classe générée). Structure testable : toute la logique vit dans expand.rs (sur proc_macro2::TokenStream, unit-testée — couverte par llvm-cov) ; seul le fin adaptateur #[proc_macro_attribute] (proc_macro↔proc_macro2) est non instrumentable (exécuté par rustc, couvert par trybuild). Macro ré-exportée par air-object (pub use air_object_macros::air_class — dépendance normale, proc-macro compile pour l’hôte donc zéro fork ADR-090 ; header committé air_object.h inchangé : parse_deps=false + une proc-macro n’expose aucun type #[repr(C)]). Exception 80 % proc-macro ACTÉE (syn/quote/proc-macro2, versions pinnées =2.0.117/=1.0.45/=1.0.106, syn default-features=false) dans docs/EXCEPTIONS.md — fixe la politique proc-macro d’Air (vaut pour toute future crate *-macros : build-time only, non livrée, pur Rust zéro-C) — + crates/air-object-macros/DEPENDENCIES.md. Traitement couche/gate : le crate vit sous crates/air-* (contrairement à xtask, hors crates/, donc non scanné) ⇒ il déclare [package.metadata.air] layer = 2 (métadonnée INERTE ; zéro dép air-* → aucune arête à vérifier ; air-object→air-object-macros = arête 2→2 conforme). Tests : trybuild compile-fail (8 cas, .stderr committés : thread manquant/()/inconnu, clé inattendue, args superflus, type de champ non géré, cible non-struct, tuple, générique) + 16 tests unitaires sur expand (tous les bras diagnostiques + génération, validation lexicale de la sortie) + e2e air-object/tests/macro_class.rs (4 tests : une classe #[air_class] ThreadSafe/Immutable traverse alloc/get/set [+ mauvais type→WRONG_TYPE, inconnu→NOT_FOUND]/observation [ancienne/nouvelle]/send_message/is_kind_of/release + AirHandle Clone/Drop — compile ET tourne, plus fort qu’un compile-pass). Pas de fuzz (justifié : la macro consomme du code source de confiance, pas des octets hostiles runtime). Barrière verte (fmt/clippy -D warnings/machete/check-layers 59 couches conformes/gen-capi-header air_object.h identique/deny licences ok/mdbook). Prochain (air-object) : AO.5 — introspection + conformité ABI complète. ✅ AO.5 — introspection (list_all/properties/parent/name/describe) + conformité ABI MERGÉ (#347) — chantier air-object COMPLET : air-object étendu (spec §7). Registre global de classes — enregistrement paresseux automatique à la première air_object_alloc d’une classe (« zéro glue » : le développeur ne déclare rien), liste intrusive (fanion registered: AtomicBool + maillon next_registered: AtomicPtr ajoutés à AirClass — layout non gelé, opaque côté C, donc header inchangé ; les statiques vivent en .data car AirClass contient des atomiques ⇒ mutation définie), zéro allocation heap pour le registre, mutations sérialisées par un verrou tournant REGISTRY_LOCK (core pur). Invariant de durée de vie documenté : une classe allouée-depuis doit être immortelle (#[air_class]/AirClassCell static/air_class_new) ou libérée par air_class_free (qui la désenregistre — pas de pointeur pendouillant) ; air_class_free gère l’unlink tête et multi-pas. Surface C-ABI nouvelle : air_class_list_all(*mut usize) -> *const *const AirClass (instantané possédé pris sous verrou, libéré par air_object_free_class_list(list, n)), air_class_properties(cls, *mut usize) -> *const AirPropertyDescriptor (zéro-copie : table AO.3 de la classe, propriétés propres ; inheritées via remontée parent — décision documentée : renvoie le descripteur, dont .info=AirPropertyInfo{nom,kind}, plutôt que copier un tableau AirPropertyInfo), air_class_parent, air_class_name, air_class_registered_count, et air_object_describe(obj) -> char* — chaîne de debug possédée (nom de classe + propriétés nom=valeur, héritées comprises), bornée et sans octet NUL (scalaires imprimés, agrégats String/Bytes/Object en jetons <…> ⇒ ne re-sérialise aucun octet externe), libérée par air_object_free_string (convention documentée : allocateur d’Air via CString, pas free(3)). AirClass reste opaque côté C (header air_object.h régénéré : 6 fonctions ajoutées, zéro type nouveau). Preuve « zéro glue » (Pattern A générique) — livrable de conformité : conformance.c étendu d’une suite d’introspection qui, sans aucun code par classe, construit une hiérarchie Shape←Square depuis C (air_class_new), énumère toutes les classes (list_all), liste leurs propriétés (properties), lit/écrit par nom découvert dynamiquement (round-trip val+1), décrit un objet (describe → contient nom + propriété propre + héritée), vérifie parenté (parent/is_kind_of) — gcc ET clang, REQUIRED ; + preuve qu’une classe #[air_class] (Widget) traverse la même surface d’introspection générique (macro_class.rs). Tests : 67 unit+property (registre : présence/absence-si-jamais-allouée, énumération, première alloc concurrente 16 threads → inscription unique [barrière], désenregistrement tête/multi-pas/no-op, properties/parent/name + bras NULL, describe scalaires/héritées/write-only/toutes variantes de kind/table NULL/nom NULL) + conformité ABI C durcie (dual-compilateur) + alloc_balance étendu (describe→free_string et list_all→free_class_list zéro fuite, allocateur compteur) + header_committed. Pas de nouveau fuzz (justifié : l’introspection produit, ne parse aucun flux externe ; noms bornés cstr_bytes, déjà couverts). Correctif induit : le test AO.2 air_class_cell_new_at_runtime (cellule sur la pile) fuit désormais intentionnellement sa cellule (Box::leak → 'static) pour respecter le nouvel invariant de registre. Stabilité ABI : la suite de conformité fonctionnelle est livrée ; l’outillage lourd (symboles versionnés GNU/.map, air-symver/air-abi-check/air-deprecation-tracker) reste HORS périmètre AO.5 (chantier transverse séparé, Principe 8) — non construit, noté comme suite. Barrière verte (fmt/clippy -D warnings/machete/check-layers 2→1/gen-capi-header air_object.h identique/deny/mdbook) ; couverture 97,8 % lignes / 98,5 % fonctions (restant = gardes défensives injoignables : saturation/OOM/overflow AO.2, isa NULL, SpinLock::new const-évalué, double-check de course register/unregister, registre vide, CString NUL ; + monomorphisations génériques AirHandle<T>/AirObservable<T> reportées vides par CGU mais exercées — documentées STRUCTURAL, Principe 2). Merge #347 durci en revue (2 findings mineurs, ABI-neutre) : air_class_properties ramène *n_out = 0 sur une classe malformée (table NULL avec compte déclaré > 0 — plus d’itération C props[0..n] sur base NULL ; aligné sur air_object_describe, test dédié verrouillant les deux bras du garde), et le doc-comment de air_class_list_all documente la durée de vie des AirClass* empruntés (une classe heap air_class_new libérée pendant qu’on tient l’instantané ⇒ pointeur pendouillant ; les statiques usuelles restent immortelles). Barrière + test-coverage rejoués verts après correctif. 🎉 Chantier air-object (ADR-002) COMPLET (AO.1→AO.5 : air-value, cœur, propriétés/observation/messages/thread, #[air_class], introspection). ✅ AirCom inc.7 — intégration modèle d’objet (MessageKind::call sur AirObject) + 1er service réel air-notifyd — MERGÉ #349 (main f8ac34b) : schéma corps d’invocation dédié (air-com-schema/invoke.capnp : Call{objectId,selector:Text,args}/Return{value}/Error{code,message} — ajout compatible coexistant ADR-012, aircom.capnp inchangé byte-pour-byte ; methodId = sélecteur textuel cohérent avec air_object_send_message, alternative table-d’ids UInt64 rejetée+documentée) ; cœur PUR air-com-proto/invoke.rs (no_std, zéro unsafe/panic, objectId/selector opaques — sans fd ni objet §1.1) — codec Call/Return/InvokeError borné anti-hostile (unit + property round-trip + fuzz fuzz_air_com_invoke 1,1 M runs, 0 crash ; ~98 % lignes) ; pilote air-com/object.rs — registre d’export objectId → *mut AirObject (retain/release équilibrés, prouvé par finaliseur-compteur), marshalling AirValue ↔ capnp (scalaires v1, AIR_OBJECT différé), dispatch call → send_message → returnMsg/error (objet inconnu / sélecteur inconnu AIR_NULL / violation de politique de thread), Connection::call_object/serve_objects sur les deux surfaces async+sync (+ test croisé sync↔async dans les deux sens) ; nouvelle arête 2→2 air-com → air-object/air-value (check-layers vert) ; air-notifyd (nouveau crate couche 2, 1er service ADR-001) — classe AirNotifyd (méthode notify(title,body)→status + propriété observable count) + 2 exécutables aircom_notifyd_{server,client} exécutés on-target (carbon), exit 0 des deux côtés. NB : #[air_class] diffère encore la génération de méthodes (AO.4) ⇒ la classe notify est câblée à la main via AirClassCell::with_members (forme exacte que la macro émettra). Durci en revue avant merge (superviseur, revue adverse par agent → verdict SAIN ; ABI-neutre) : #![forbid(unsafe_code)] posé sur le cœur air-com-proto (l’invariant §11 « le cœur n’expose aucune fn unsafe » passe de garanti-par-revue à vérifié par le compilateur) + un doc-comment corrigé. 🎉 AirCom : les 7 incréments (inc.1→7) sont MERGÉS — l’IPC natif d’Air a son 1er service réel (air-notifyd). Restent, différés/soft : args AIR_OBJECT + génération de méthodes par #[air_class] ; bootstrap control-plane pub-sub + exemple 2-exe pub-sub (différés inc.5) ; re-sceau couche-1-v2.2 groupé (additifs L1 air-socket::into_owned_fd inc.3b + air-shm inc.5). Prochain : couche 2 — framework réseau → air-sshd (ADR-074, la vraie cible produit). ✅ CHAPITRE RÉSEAU OUVERT (2026-07-13) — stratégie ratifiée BDFL : air-sshd en direct (sur air-async+air-socket+air-crypto en sans-IO ; SSH ≠ TLS → framework générique air-network/air-tls différé, hors chemin critique). ADR-093 rédigé + refondu (directive BDFL v1/v2) (phase 1 = couche transport SSH-2, sous-ADR d’ADR-074, draft pour ratification) : topologie de crates figée — cœur sans-IO air-ssh-proto (invariant, réutilisé verbatim en v2) + air-sshd scindé (pilote d’octets swappable SshByteStream : v1 air-async / v2 stack « ssh » d’air-network enveloppant le même cœur ; couche service invariante) + facilité partagée air-service (trait ServiceHost : v1 systemd socket-activation+sd_notify a minima / v2 air-launchd). air-sshd publie ses événements de session sur AirCom (pub-sub §7 + objet-service interrogeable list_sessions) → air-com dépendance v1. Algos modernes only (curve25519-sha256/ssh-ed25519/chacha20-poly1305@openssh.com/aes256-gcm@openssh.com), aléa injecté, conf binaire air-config (existe ; conf par-utilisateur HomeDirectory en P2), cible wire-compat OpenSSH ; 4 incréments T.1→T.4 (3 crates+seams+schéma events+identification+Framer → KEXINIT+négo+event → KEX+dérivation → chiffré+NEWKEYS+preuve ssh réel). 2 ADR compagnons v2 (air-launchd ; stack « ssh » air-network). Socle entièrement prêt (air-crypto profil SSH implémenté, air-async, air-socket, air-config ~16k l., AirCom, air-process/privsep, PTY, signalfd, air-account). Après ratification : impl en incréments délégués carbon (re-vérif+merge comme AirCom)).
0. Actualité — CIBLE DE PRODUCTION (2026-07-10)
🎯 OBJECTIF PREMIER (pivot 2026-07-10, ADR-088) : une toolchain Rust dont la
stdrepose uniquement sur Air, par un PAL custom safe bindant la couche 1, SANS libc C. Sans cet outil fondamental, on ne peut pas produire sereinement le reste. Le jalon M5 est ATTEINT mais par le chemin de l’option A (ADR-076) :stdliait la libc Clibair_c— or la frontière C réintroduit de l’unsafe, incohérent avec la doctrine («unsafeen couche 0 seulement »). ADR-088 amende ADR-076 : PAL customstd::sys::pal::airbindant les Managers couche 1 Rust safe (premier backendstdsafe de bout en bout), cibletarget_os="air",libair_csurvit mais découplée destd,AirTaskManagerremplaceAirProcessManager, résorberaw_syscall. M5 = marchepied de faisabilité. Étude :etude-std-pal-air-safe-fr.md.Cible produit ensuite :
air-sshd— le serveur SSH d’Air, full Rust, async io_uring, wire-compat OpenSSH (ADR-074) — la toolchain safe est son prérequis. (Il n’y a pas de jalon « compiler OpenSSH réel » : OpenSSH n’était qu’une fonction de forçage, rôle rempli.)
Acquis récents (couche 1 scellée → toits) :
- 🎉 M5 ATTEINT (2026-07-10) —
stdtourne sur Air, ZÉRO glibc, 2 arches. Le lien dehello-stdcontrelibairpasse de 12 → 0 symboles indéfinis (5 PR :__xpg_strerror_r#308,open/fcntl#309, introspection thread couche 1 [ADR-086] #310,getauxval/pthread_self/détection-pile #311, TLS keys#[thread_local]#312,syscallgénérique [ADR-087] #313). Run on-target :hello from std on air+ exit 0 sur x86_64-air (carbon) ET aarch64-air (raspi). Un programme Ruststdstatic-PIE lié intégralement sans glibc. Pari ADR-076 tenu de bout en bout. Re-sceauxcouche-0-v1.12(ADR-087raw_syscall, escape hatch temporaire à retirer avec l’équipe Rust) +couche-1-v1.9(ADR-086 introspection thread). - Campagne réseau ADR-069 close : registre de handles
air-handle, moteur socketair-socket, surface socket libc complète (30 symboles,libair_c.so) +sendmsg/recvmsg/SCM_RIGHTS. Re-sceauxcouche-0-v1.10(ADR-070 sockaddr + ADR-071 sockopt) etcouche-1-v1.6(ADR-069). CI basculée x86 (ADR-072). - Doctrines gravées : config binaire (ADR-073),
vision
air-sshd(ADR-074) — la vraie cible (OpenSSH n’était qu’une fonction de forçage), fourniture du PAL (ADR-075). - Chantier A du PAL COMPLET : 6 briques additives couche 1 (
AirCondvar,AirOnce, os_str, mutation d’env,yield_now/set_thread_name, destructeursthread_local). - Bug de fondation aarch64 CORRIGÉ (gdb) : le TCB (64 o) recouvrait la zone TLS
(
tpidr_el0base commune TCB+TLS à+16) → tout 2ᵉ#[thread_local]cassait. Fix validé 2 arches (selftestrtx86-air = 42 ET aarch64-air = 42). - Chantier B lancé, option A prouvée :
build-std=stdcompile entièrement pour Air (env=musl, zéro patch libc/std) ; reste le lien (-lc= libair,-lunwind= stub).
⚠️ 2026-07-08 — RÈGLE + ARCHITECTURE (ADR-077) : la libc bind EXCLUSIVEMENT la couche 1 (jamais la couche 0), via des Managers de domaine objet (
AirFileManager/AirTaskManager/AirNetworkManager/AirSystemManager/AirSignalManager/AirEnvironmentManager/AirMemoryManager). Remédiation livrée (re-sceaucouche-1-v1.7, ADR-077) : les Managers sont la surface médiatrice objet consommée par les toits (libc et PAL), jamais la couche 0 en direct. Les symbolesair-libc-*bindent les Managers / briques couche 1.
Prochaines tâches (pivot ADR-088 — chantier PAL safe). Le jalon M5 (option A : std sur
la libc C libair_c) est atteint et conservé comme marchepied de faisabilité, mais il
réintroduit de l’unsafe par la frontière C — incohérent avec la doctrine. ADR-088
pivote : std reposera sur un PAL custom safe (std::sys::pal::air + sys/<domaine>/air.rs)
bindant les Managers couche 1 Rust safe, zéro FFI, cible target_os="air" (Air n’est
plus déguisé en unix), libair_c découplée de std, escape hatch raw_syscall résorbé.
Stratégie de sceau : desceller la couche 1 une fois pour tout le chantier, resceller une fois
au jalon (comme couche-1-v1.0).
- ✅ Étapes 1–2 faites : note d’étude (
etude-std-pal-air-safe-fr.md)- ADR-088 acté (tranche §6.a–e). ADR-089 réserve le
SecurityManager(#324).
- ADR-088 acté (tranche §6.a–e). ADR-089 réserve le
- ✅ Lot 1 mergé (#322/#323) — seul changement cassant : renommage
AirProcessManager → AirTaskManager(médieair-process/air-thread/air-runtime+resource_limit) + surfacefutex/gettid; propagation doc (macro-archi, ADR-077/085/086, INDEX, registre, libc-conformance). - ✅ Étape 3 faite (incrément 0+1, branche
feat/pal-safe-air-target, en revue) — reprofilage cible : cible canonique*-linux-airreprofiléetarget_os="air"(retraitenv=musl/target-family), option-A renommée marchepied*-linux-air-musl(M5 préservé, baseline rejouée :hello from std on air/exit 0/zéro glibc). Forkrust-srclocalisé réversible (rt/rust-src-air/: patch +apply-air-pal.sh+ copievendor/gitignorée, override__CARGO_TESTS_ONLY_SRC_ROOT).stdCOMPILE pour*-linux-air, 2 arches (x86_64 + aarch64), lien d’un binairestdminimal sans symbole libc indéfini (patch minimal : 5 fichiersstd::sys, murs A1–A4).os=airinconnue ⇒restricted_std(voie standard des ports custom). - ✅ Incrément 2 — PROTOTYPE
rustc-dep-of-stdsurair-alloc: PROUVÉ + dé-risqué (commit5f12621, branche).stdcompile 2 arches avec le vraiair-alloc(arènemmap/futexcouche 0) comme allocateur global — sondeair-alloc-probe: ELF static-pie, zéro glibc, zéro symbole indéfini, 5 Kio (arène réelle liée, plus le placeholder). Mesure décisive : la couche 0/1 ne change QU’EN build-config (3Cargo.toml, +38 l., featurerustc-dep-of-std+ façadesrustc-std-workspace-*) — ZÉRO.rssource, zéro sémantique, zéro gate/scellé touché. Vérifié superviseur : tests hôte des 3 crates scellées verts (23+807+199, 0 échec). ⇒ Le descellement formel de la couche 0 N’EST PAS nécessaire (crates Air déjàno_std-propres). - ✅ Incrément 3 — AUDIT deps externes (commit
cfe216d, branche) : fardeau brut de binding naïf = ~71 forks (33 icu4x viaair-base-lib+ 37 RustCrypto viaair-crypto+ 1bitflags), intenable — MAIS presque entièrement évitable par DÉCOUPLAGE, pas par fork. Aucune externe n’arustc-dep-of-stdamont (toutesno_std) ; les 11 proc-macros compilent pour l’hôte → zéro fork. Trouvaille : les 2 grappes 🔴 n’entrent dansstdque par couplage accidentel — (1)AirError/AirResult/AirPathsont définis dansair-base-core(icu-free) ;air-base-libne fait que les ré-exporter + ajouter icu ⇒ rediriger les imports Managers fs/net/process/runtime versair-base-coresort icu4x du graphestd(chemin d’import, zéro sémantique) ; (2)AirRandom::filln’appelle queair-sys-syscall::getrandom(couche 0) ⇒ isoler le random sort RustCrypto du graphe. Vérifié source :air-base-coredéfinit bien les types, Vague 0 dépend deair-base-core(pas-lib). - ✅ ADR-090 ratifié (2026-07-11, sur
main) — grave la discipline : binding parrustc-dep-of-std(build-config, pas de descellement) ; importsair-base-corejamaisair-base-lib; exceptions 80 % (icu4x/RustCrypto) hors fermeturestd;AirRandomhors crypto ; frontièreno_std= relation àstd; industrialisation par vagues. - ✅ Vague 0 — binding réel du noyau (commit
a9769c4, branche) :stdcompile ET lie pour x86_64 ET aarch64 sur 5 vrais Managers —io/error→air-base-core,pal/air::futex+sync/*(Mutex/Condvar/RwLock/Once/Parkerréels)→air-thread::raw_futex,stdio→air-stdio,env→air-env(+alloc→air-alloc). Fermeture vérifiée : uniquementbitflags(ni icu4x, ni RustCrypto, niair-base-lib) — fork count = 1. Zéro.rscouche 0/1 touché, hôte scellé vert. Lien : static-pie zéro glibc ; x86_64 =__tls_get_addrseul indéfini (hook TLS runtime), aarch64 = zéro indéfini. Placeholders restants :random,thread_local. - ✅ Vague 1a — découplage
air-base-lib→air-base-core(commitsb9b4edc/89ba1d4/9d80380, branche) : seulsair-filesystem+air-socketdépendaient directement d’air-base-lib(les 3 autres déjà surair-base-core). Redirection ABI-neutre, aucun cas d’i18n réel (le seulfrom_utf8_lossy= wrapper stdlib zéro-icu). Vérifié :air-filesystem/air-socket/air-processont icu4x=0, air-base-lib=0 dans leur fermeture (fork=1).exitréel bindé surair-process::exit_process(prouvé au désassemblage :__NR_exit_group, pasabort()). Hôte scellé vert (aucune régression). - 🎯 Vague 1b — JALON ATTEINT (commits
396944c/636eda7/168dd4b, branche) :hello-stds’exécute sur le PAL safe. Résidus Vague 0 levés : (1)__tls_get_addrsupprimé via"tls-model": "local-exec"dans les specs cibles (TLS statique, accès%fs:OFFSETdirect) ; (2)_startabouté au bootstrap d’air-runtime(crt0 Air : self-reloc/TLS/TCB/canari/argv →__air_start→bootstrap→main→println!→exit_process),_starthors de std (côté binaire, ne casse pas les#![no_main]).air-runtime+air-memoryenrustc-dep-of-std(build-config, zéro source couche 1 touchée), fermeture sans icu4x/crypto, fork=1. VÉRIFIÉ SUPERVISEUR sur 2 arches :hello from std on air+exit 0 sur x86_64/carbon ET aarch64/raspi-srv-2 (sha256 identique, static-pie, 0 glibc, 0 UND). Hôte scellé vert (127 tests feature OFF), zéro FFI hors_startasm/extern "C"(SAFETY documenté), aucun descellement. - ✅ Bug spawn aarch64 : NON REPRODUCTIBLE (2026-07-11, raspi) —
air_runtime::thread::spawn(clone3+CLONE_SETTLS Variant I) prouvé sound (~16 000 spawns : canonique.tbss+ instrumenté.tdata+ isolation ; selftest rt=42, 5/5 re-vérif superviseur). Les 2 bugs historiques sont corrigés (TCB/TLS overlap 2026-07-07 ; asmx19→x21#150). Le TODO « fix bug spawn aarch64 » était stale ⇒sys/thread(PAL) est débloqué côté runtime. - ✅ Vague 1c —
sys/threadréel (commits2e953ef/2ccb15e, branche) :std::thread::spawn+joins’exécutent sur le PAL safe, 2 arches VÉRIFIÉES superviseur (x86_64/carbon + aarch64/raspi : workersum=5050, join, exit 0 ; stress 800 threads OK). BindingThread::new→air_runtime::thread::spawn(trampolineextern "C" fn(usize),Box<ThreadInit>pointeur fin,expose_provenance),join→futexCHILD_CLEARTID,set_name/yield_now→air-thread,sleep→air-base-core, id→current_tid;available_parallelism/garde de pile = stubs documentés. Zéro source couche 0/1 touchée, fermeture toujours 1 fork,air-runtimedéjà bindé (rien à ajouter). - ✅
air-allocalignement fort — MERGÉ surmain(#325,384c80b, 2026-07-11) : implémenté l’alignement fort (align > 16,memaligndlmalloc) dans l’arène, lève ADR-056 D5 (amendé). Découvert viastd::sync::mpsc(align 64 → l’ancien refusalign>16→nullfaisait abort SIGILL). 30 tests (matrice 32/64/128/256/page +realloc/dedicated/alloc_zeroed/OOM) + property + fuzz (44 600 exécutions). CI verte (test-coverage root ✓, supply-chain ✓, cross-aarch64 ✓). ⇒mpsc/ crossbeam/types sur-alignés débloqués (la branche PAL rebasera sur cemainpour en bénéficier). - ✅
std::sync::mpscVALIDÉ sur le PAL, 2 arches (branche, merge927e624+ sonded85405e) : après intégration du fixair-alloc(mergemain, conflit nul), la sondehello-mpsc(8 producteurs × 1000, somme32004000) s’exécute — x86_64/carbon (120 runs 0 échec) ET aarch64/raspi (vrai ARM), exit 0, static-pie 0 glibc. Le cas SIGILL (V1c-6) est clos, fix align-fort prouvé en contexte.air-alloc= dépendancepathdestd(rustc-dep-of-std) ⇒ patch rust-src inchangé. - ✅
sys/fd+ minimalsys/fsréels (commitsd32fb79/f05907c, branche) :FileDesc(OwnedFd) →AirFileManager(read/write/read_at/write_at/seek/fsync/ftruncate/set_nonblocking) +duplicate→air-handle;File::open/create/read/write/seekminimal. Aller-retour fichierstdprouvé 2 arches (sondehello-fs: 16 Ko écrits+relus identiques + seek, exit 0 ; x86_64/carbon 10/10 + aarch64/raspi). Zéro additif couche 1, zérounsafedans le bras, zéro source couche 0/1, fermeture 1 fork. Frontière assumée (Vfd-3) :std::os::fdnon exposé (éviterait un portagenet/pipe/stdio-fd complet) — unificationair-handle(ADR-069) = chantier distinct. - ✅
sys/fsCOMPLÉTÉ (commitsc2879d5/9aa5290, branche) : métadonnées (statx→FileAttr/FileType/FilePermissions, temps),ReadDir/DirEntry(getdents64),unlink/rename/mkdir/rmdir/DirBuilder/remove_dir_all, liens (symlink/link/readlink/canonicalize/copy),set_permissions/set_times,exists;current_exe(readlinkat /proc/self/exe, brassys/paths/air.rs). Zéro additif couche 1, fermeture propre (fork=1, icu/crypto/regex=0). Prouvé 2 arches (sondehello-fs2:read_dirtrié + métadonnées + liens + rename + suppression +current_exe, exit 0 ; x86_64/carbon + aarch64/raspi — 1ʳᵉ panique aarch64 = artefact de renommage du binaire par le superviseur, re-run au bon nom = OK).std::fslargement fonctionnel. - ✅
sys/processréel (commits76a8dc1/f372a9c, branche) :std::process::Commandspawn/exec/wait/status/kill — bâti surAirProcess(pidfd :clone3+CLONE_PIDFD/execve/waitid/pidfd_send_signal, wait+kill en couche 1, passpawn_processnu qui exigerait unwaitidcouche 0).StdioInherit/Null/Fd;Piped/outputdifférés (avecstd::os::fd/pipe). Prouvé 2 arches (hello-proc:/bin/true→0,/bin/false→1, introuvable→127, exit 0 ; x86_64/carbon 20/20 + aarch64/raspi). Zéro additif couche 1, zéro FFI, zérounsafe, fermeture 1 fork (air-account→air-base-core, découplage tenu). - ✅
sys/netréel (commits54a53f6/ce6e8d7, branche) :std::netTCP + UDP — bâti surair_socket::engine::AirSocket(couche 1&self, pas les façades&mut self).TcpStreamconnect/read/write/peek/shutdown/nodelay/keepalive/nonblocking/addrs ;TcpListenerbind/accept (SO_REUSEADDR, backlog 128) ;UdpSocketsend_to/recv_from/peek_from + connect/send/recv ; adresses numériques (DNS différé). Prouvé 2 arches (hello-net: TCP + UDP loopback roundtrip, exit 0 ; x86_64/carbon 10/10 + aarch64/raspi). Zéro additif fonctionnel couche 1, fermeture 1 fork. - ✅
stdFONCTIONNELLEMENT COMPLET pour fichiers / threads / mpsc / process / réseau — le réseau était le dernier gros sous-système. Reste pour « std bouclé » :sys/random(dernier mur dur), + différés soft. - 🎉
sys/randomréel —stdFONCTIONNELLEMENT BOUCLÉ (commits82517f1/12b676e/6e336c0, branche) : isolation par micro-crateair-random(couche 1,no_std, surgetrandomcouche 0) —air-cryptola ré-exporte (surfacefill/generate_keyinchangée, régression hôte 0 : air-crypto 42/42, air-random 3/3). Gate ADR-090 vérifié superviseur : fermetureair-random={air-sys-syscall, air-sys-types, bitflags}, RustCrypto = 0 (build-std 33 crates : RustCrypto/icu4x/air-base-lib = 0, fork = 1).sys/random/air.rs(zérounsafe) →HashMap/RandomStateamorcés sur CSPRNG kernel. Prouvé 2 arches (hello-random: entropie réelle distincte +HashMap1000 entrées, exit 0 ; x86_64/carbon 5/5 + aarch64/raspi). ⇒ tous les murs durs destdfranchis. - ✅ Portage
stdMERGÉ surmain(#326,ce46a0b, 2026-07-11) : squash, CI verte (test-coverage root + supply-chain + build + loom + cross-aarch64), Verified GitHub. Couche 1 évoluée (découplageair-filesystem/air-socket→air-base-core, micro-crateair-random, featuresrustc-dep-of-std) mais NON re-scellée — le re-sceau viendra après les différés soft (décision BDFL : ne desceller qu’une fois). Forkrust-srclocalisé (rt/rust-src-air,vendor/gitignoré). - ⏭️ Séquence en cours (BDFL, branche
feat/std-osfd-pipe, 1 PR à la fin) : différés soft PUIS re-sceau. (a) ✅std::os::fd+sys/pipe(commit3689ebb) —AsRawFd/OwnedFd,io::pipe,Stdio::Piped/Command::outputdébloqués ; zéro additif couche 1 (viasys/pipe+AirStdio::Fd), fermeture 1 fork ; prouvé 2 arches (hello-pipe:io::pipe+ capture"hello-air\n"+os::fd, exit 0). (b) ✅ options socket (commit4105188) — additif couche 1air-socket(méthodes typées&self, +5 tests, zérounsafe) : ttl/linger/nodelay+keepalive getters/broadcast/multicast loop+ttl/only_v6/connect_timeout (ppoll+SO_ERROR)/duplicate ; prouvé 2 arches (hello-sockoptset==get, exit 0), fermeture 1 fork, régression hôte 0. Options à STRUCTURE — additif COUCHE 0 v1.13 FAIT (commits27195dd/1f89c98) :air-sys-types(ip_mreq/ipv6_mreq#[repr(C)],const assert!ABI) +air-sys-syscall(6 wrappers dédiés typés) + couche 1air-socket+ PAL ⇒read_timeout/write_timeout(+ read vide expiré) + multicast join/leave v4/v6, prouvés 2 arches. Plus aucununsupportedsur les options socket. ⚠️ Barrière : plancher lignes rouge (93.29%<94) = artefactllvm-covd’hôte (net.rs 64.53% au baseline = impossible pour couche 0 scellée ; branches +12/0-manque) → arbitre = CItest-coverageroot ; tagcouche-0-v1.13contingent à CI verte. (c) ✅ DNS FAIT (commits0ec7032/e18117c) — câblagelookup_hostsurAirNameResolverexistant (best_effort[hosts, DNS RFC 1035, localhost], IPv6-first) + additifair-socket(AirLocalhostSource, pontscore::net, zérounsafe) ; prouvé 2 arches (hello-dns: localhost + numérique + connect-par-nom, exit 0), fermeture 1 fork (résolveur maison,getaddrinfo0). ⇒ TOUS les différés soft traités.hostname()laisséunsupported(tireraitair-system— chantier distinct). Reste : PR unique + CI (arbitre couverture couche 0) + re-sceaux (couche-0-v1.13 + couche-1). - 📌 DIRECTIVE BDFL (2026-07-11, après consolidation) : rendre normatif le motif réseau sans-IO
(
docs/notes/reseau-architecture-crates-fr.md) via un ADR — toute impl réseau doit s’y conformer (seul motif testant la pile indépendamment). Précurseur :AirDnsSource. À faire avantair-async/ couche 2. Voir mémoire projet. - 🗺️ PHASE SUIVANTE = couche 2 (séquence BDFL) : runloop
air-async(réacteur io_uring↔Waker/ exécuteur/timers) → IPC Air → framework réseau Air →air-sshd(démonstration couche 2). En parallèle, à traiter : hygiène pré-merge branche PAL (Cargo.lockstubs façades →deny/audit/machete+ repro), merge de la branche PAL, re-sceau couche 1 (ADR-088 « resceller une fois »). Différés softstd(non bloquants pour un programme courant) :std::os::fd(AsRawFd/FromRawFd) +sys/pipe(io::pipe, chantier libc-fd, débloqueStdio::Piped/Command::output) ; options socket fines (timeouts/ttl/linger/broadcast/multicast) ; DNS (brancherAirNameResolversurlookup_host) ;available_parallelism/park-unpark/TLS dynamique. RFC amont (etude-rfc-trait-backend-std-fr.md). - 🗺️ Roadmap au-delà du portage
std(séquence BDFL 2026-07-11) : (1) bouclerstd(fs/process/net) ; (2) couche 2 — la runloop (=air-async, réacteur io_uring↔Waker/exécuteur/timers, pièce maîtresse identifiée dansarchitecture-prerequis-air-sshd-fr.md), puis l’IPC Air, puis le framework réseau Air ; (3)air-sshd(ADR-074) — composant réseau primordial et démonstration du fonctionnement de la couche 2. Puis hygiène pré-merge (Cargo.lockstubs façades →deny/audit/machete+ repro ADR-025), merge de la branche, re-sceau couche 1 (ADR-088 « desceller une fois, resceller une fois »). RFC amont Tier-3 + backendstd::syssafe (etude-rfc-trait-backend-std-fr.md) : la preuve downstream est acquise. - ⏭️ Étape 4 (conditionnée à la décision) — implémentation par sous-système (facile→dur, chacun bindant son Manager) :
exit/random/time/os_str/args/env→fd/stdio→futex+sync(les 3 wrappers débloquentMutex/Condvar/RwLock/Once/Parker) →fs→thread+thread_local→process→net. Manques couche 1 (§5 étude :current_exe,DirEntrytypé,park/unpark, options socket) comblés en additifsv1.xau fil de l’eau. - 🎯 Jalon de succès :
hello-stdre-tourne sur le PAL safe, 2 arches, sanslibair_cliée — preuve d’un userland Rust safe de bout en bout (unsafeconfiné couche 0). Puis re-sceau couche 1 unique + RFC amont (Tier-3*-linux-air, backendstd::syssafe).
Dettes/leçons associées : raw_syscall/GlobalAlloc-safe traités dans ce chantier (§5) ;
lane CI nightly hello-std/rt à monter ; leçon gravée jamais différer les runs raspi (un
cross-build aarch64 vert ne prouve rien au runtime). Cible produit après la toolchain safe :
air-sshd (ADR-074).
1. Où en est-on (vue d’ensemble)
| Couche | État | Détail |
|---|---|---|
Couche 0 (air-sys-types + air-sys-syscall) | 🔒 SCELLÉE couche-0-v1.11 (ADR-066 v1.9 signaux async + ADR-070/071 v1.10 sockaddr/sockopt libc, campagne réseau ; ADR-085 v1.11 le 2026-07-09 — additifs cumulés libc/std : chroot, kill_process_group→killpg, sigaltstack+AltStack, sendfile, fchmod/fchown/futimens, munmap_raw ; pause sans additif via ppoll ; ADR-087 v1.12 le 2026-07-10 — raw_syscall, escape hatch syscall générique nécessaire au pal std, EXCEPTION assumée temporaire à retirer avec l’équipe Rust, jalon M5) | Code complet (11 familles + io_uring 12 Temps + termios/tty). N’évolue plus que par descellement additif (RFC, ADR-015/ADR-051). Additif v1.9 (ADR-066) : sous-module async_handler (rt_sigaction non-faute = délivrance async réelle, install/restore) + trampoline rt_sigreturn x86_64 (global_asm) + rt_sigpending ; les 4 fautes restent inertes (ADR-064). Tag posé par le superviseur après barrière x86 + CI aarch64. Couverture 100 % hors exceptions documentées, 2 arches. |
| Couche 1 (briques Rust : libc + PAL) | 🔒 SCELLÉE couche-1-v1.0 → re-scellée jusqu’à couche-1-v1.9 (v1.1 errno_location ADR-065 ; v1.2 signaux/futex ADR-066 ; v1.3 air-account ADR-067 ; v1.4/v1.5 credentials+drop_to_user ADR-068 ; v1.6 campagne réseau air-handle+moteur socket ADR-069 ; v1.7 le 2026-07-09 — Managers de domaine (ADR-077) : AirFileManager/AirProcessManager/AirEnvironmentManager/AirMemoryManager/AirSystemManager/AirNetworkManager + nouveau AirSignalManager (set_alt_stack/pause), surface médiatrice objet consommée par la libc, chantier B ; v1.8 le 2026-07-09 — air-process::spawn_process (fork+file-actions+exec async-signal-safe) + types SpawnFileAction/SpawnAttributes, socle de la face libc posix_spawn, chantier B ; v1.9 le 2026-07-10 — introspection thread cible (ADR-086) : air-runtime::process_context [auxv + bornes de pile] + AirProcessManager::resource_limit, socle de getauxval/pthread_getattr_np de la libc, jalon M5 ; earmark ADR-078 crypto air-tls → couche-1-v1.10) | API Rust publique figée (17 crates, voir ADR-062). Évolue par descellement additif v1.x (RFC ; v1.1 = AirRuntime::errno_location, ADR-065 ; v1.2 = air-signal::install_handler/restore_disposition/pending — brique d’install de handler non-faute dual-face — + air-thread::raw_futex — primitives futex(2) typées sur mémoire externe, ADR-066). v1.3 = crate air-account (codecs comptes /etc/passwd+/etc/group, brique bindée par la libc, ADR-067). Tag superviseur après validation couche 0. Les 4 P0 + remodel env/args pré-sceau. |
| Toit libc C-ABI (scopée OpenSSH) | 🔨 En production (air-libc-capi, roadmap roadmap-libc-openssh-fr.md) | M0/M1-a/M1-b/M1-c/M1-d faits (crt+errno ; str*/mem* ; malloc+moteur format fuzzé ; FILE*+exit/atexit/getenv ; frontière variadique printf). M2 implémenté (à valider superviseur) : I/O fichier fd brut + métadonnées (struct stat ABI Air) + répertoires (DIR opaque) + mkstemp/realpath/getcwd — crate air-libc-fileio (logique mesurée) + shims air-libc-capi::fileio + shim C variadique fileio_shim.c (open/openat/fcntl). Archi libc : logique mesurée / shims C-ABI exclus — 6 crates de logique (air-libc-{fmt,alloc,stdio,stdlib,printf,fileio}) + shims air-libc-capi (cf. docs/libc-conformance.md). M3 Phase A faite (fondations couches 0/1, ADR-066 : install async non-faute + raw_futex). M3 Phase B VALIDÉE x86 (barrière verte : lignes 96.4 %, branches 82.9 %) : familles signaux (sigaction/signal/kill/raise/sigprocmask/sigsuspend/sigset* — crate air-libc-signal, 4 fautes INERTES ADR-064) et pthread (mutex/cond/rwlock/once/key/create/join/… sur air-thread::raw_futex — crate air-libc-thread, cycle de vie/TLS hôte). Types opaques ABI Air (mutex 40/cond 48/rwlock 56, sigset_t 128) + trailer cbindgen hand-written. Preuve d’exécution C (air-libc-c/tests/m3/) : mutex end-to-end + sigaction/raise handler async + déréf NULL → SIGSEGV coredump inerte. pthread_create NON testable via C -lair_c sur l’hôte (récursion d’interposition std::thread→pthread_create ; sans glibc/on-target = OK) — couvert par tests unitaires air-libc-thread. Couverture : air-libc-c (artefact cdylib pur) EXCLU des runs llvm-cov (barrier + CI) — son test --lib force-lierait les shims pthread/TLS surchargeant la glibc (SIGSEGV) ; air-libc-capi modules signal/thread gatés #[cfg(not(test))]. Déviations D-M3.1..D-M3.9 ; additifs remontés (pthread_exit propre, dtor TLS cible, rt_sigsuspend atomique). M4.2 Temps VALIDÉE x86 : clock_gettime (REALTIME+MONOTONIC), gettimeofday, time — crate air-libc-time (logique mesurée, sur air-base-core couche 1, ZÉRO additif : monotone brut reconstruit via AirInstant::duration_since(zéro)). Types timeval/time_t/clockid_t ; preuve d’exécution C (tests/m4/time.c). Additifs remontés M4 : nanosleep (sommeil non-retry couche 1 — air-base-core::sleep retry-EINTR ≠ POSIX), clock_getres, strftime/localtime_r (tz icu4x). M4 Comptes VALIDÉE x86 : getpwnam_r/getpwuid_r (réentrants) — crate air-libc-pwd (parseur /etc/passwd mesuré + empaquetage borné anti-overflow, lecture via air-filesystem::read_to_bytes couche 1) ; struct passwd/uid_t/gid_t ; convention *_r (errno rendu direct, ERANGE/absent) ; preuve C (tests/m4/pwd.c contre le vrai /etc/passwd). Refactor ADR-067 : air-libc-pwd re-câblé en pur binder d’air-account (parseur retiré, ABI inchangée) ; groupes getgrnam_r/getgrgid_r (crate air-libc-grp, packing gr_mem aligné/borné). Non-réentrants + shadow = tranches suivantes. shadow (getspnam_r, codec durci ShadowHash zeroïsé) et identifiants (ADR-068 : air-process::credentials + binders getuid/setuid/setresuid/setgroups/initgroups, sémantique POSIX setuid ratifiée) faits. A.6 air-process::drop_to_user VALIDÉE x86+aarch64 (boucle A close 6/6) : résolveur AirPrivilegeDrop::for_user (résout un nom via air-account → descripteur de largage enrichissable : uid/gid primaires + groupes supplémentaires réels, sémantique initgroups) + convenance drop_to_user (résout puis drop_privileges) ; ADR-068 ; PAS de binder libc (OpenSSH largue via les syscalls individuels déjà exposés) ; re-sceau additif couche-1-v1.5. |
| Couches 2+ | ⏳ Non commencées | Toits (libc C-ABI, PAL std::sys) construits au-dessus de la couche 1 scellée. |
Couche 1 scellée le 2026-07-03 (tag couche-1-v1.0, ADR-062). Voir §4 pour les jalons.
2. Sceau couche-1 : FAIT — et file des tâches à venir
Décision de gouvernance BDFL : combler les P0 des audits, remodeler ce qui l’exigeait, PUIS sceller. Tout est fait :
| # | Tâche | Statut |
|---|---|---|
| P0.1 | AirFile — handle I/O fichier générique (air-filesystem) | ✅ Mergé (#214) |
| P0.2 | air-poll — multiplexeur ppoll générique (crate dédiée) | ✅ Mergé (#215) |
| P0.3 | termios/PTY/job-control re-exposé (air-terminal) | ✅ Mergé (#216) |
| P0.4 | air-signal — face signal générique, 100 % safe (ADR-064) | ✅ Mergé (#217) |
| Remodel | os_str — AirOsStr/AirOsString, env/args figés (sceau-readiness) | ✅ Mergé (#218) |
| §5.4 | Sceau couche-1-v1.0 — ADR-062 + tag git | ✅ SCELLÉ 2026-07-03 |
Prochaine étape (voir §0 Actualité en tête) : air-sshd — le serveur SSH d’Air full Rust (ADR-074). Le chantier B du PAL est clos : std lie et tourne sur la libc d’Air (M5 atteint, hello-std 2 arches). Toits libc C-ABI et PAL prouvés ; la toolchain Rust full-Air est acquise et sert désormais de socle à la production air-sshd.
Additifs v1.x (descellement additif, non bloquants) : spawn ergonomique §2.B.2,
Condvar/Once, set_var/mutation d’env, destructeurs TLS, getsockopt/setsockopt,
mman fichier-adossé, stat/fstat public, mkstemp, barrière de poison + fault_state()
(ADR-064 §7), gap aarch64 crypto (#14), instrumentation IO debug (ADR-063), regex
(§4-I), AirRuntime::errno_location() -> *mut i32 (socle de __errno_location() de la
libc — premier additif révélé par le toit libc M0, roadmap §6 ; RFC de descellement
additif requis), M3 Phase A — signaux+pthread (ADR-066) : install de handler async
réel pour les signaux non-faute (air-signal::install_handler/restore_disposition,
unsafe localisé) + pending(), et primitives futex(2) typées sur mémoire externe
(air-thread::raw_futex) pour bâtir pthread_mutex_t/cond_t de la libc et std::sys du
PAL ; fondation posée en couches 0 (couche-0-v1.9) et 1 (couche-1-v1.2), rien en
couche 2. (La face sigaction-de-faute n’est PAS un additif — les 4 fautes restent
inertes, ADR-064 §2 ; seul l’install de handler non-faute est descellé, ADR-066.)
Cette liste se complète au fil des points identifiés.
3. Méthode de production (rappel pour agents)
- Doctrine de couche : seule la couche 1 consomme la couche 0 ; les tiers couche 1
sont fidèles (un appel = un syscall), EINTR remonté (jamais de retry auto),
zéro
unsafeexposé, erreursAirError/AirResult, arithmétique défensive (jamaisaslossy), nommage ADR-029 (zéro abréviation). - Qualité, avant tout merge :
cargo fmt/clippy -D/test,cargo xtask barrier(WARN = vert ; 5 advisoryunsafepré-existantssecurity.rs/inotify.rsignorés), couverture 100 % lignes (exceptions STRUCTURAL = marqueur in-code// COVERAGE (STRUCTURAL, ADR-035), commeair-process/air-poll), fuzz sur toute surface de parsing d’octets externes (sinon « Note fuzz » justifiée dans la spec). - Git : trunk-based, tout par PR sauf doc pure → commit direct sur
main(pas de CI pour de la doc). DCO-s+ signature GPG (clé BDFL), jamais de trailerCo-Authored-By. Production déléguée àclaude -pheadless (carbon/speedy) ; le superviseur pilote validation + push + PR + merge (squash) + re-vérif main/trailer. - Nouvelle crate ⇒ committer
Cargo.lock(reproductibilité ADR-025).
4. Jalons réalisés (résumé — détail dans JOURNAL.md)
- Couche 0 — scellée
couche-0-v1(2026-06-14) puis re-scellée jusqu’àcouche-0-v1.8(2026-07-02, familleterminal/termios-tty, ADR-060), puis les descellements additifs de la campagne libc/std : v1.9 (signaux async, ADR-066), v1.10 (sockaddr/sockopt libc, ADR-070/071), v1.11 (additifs cumulés libc/std —chroot/killpg/sigaltstack/sendfile/fchmod/fchown/futimens/munmap_raw, ADR-085) et v1.12 le 2026-07-10 (raw_syscall, escape hatch syscall générique, exception temporaire à retirer, ADR-087, jalon M5). Code complet, plus d’évolution hors RFC (descellement additif). - Couche 1 — cœur livré :
air-base-lib(cœur + services),air-crypto,air-socket,air-filesystem,air-memory,air-device,air-thread,air-process,air-config,air-stdio,air-runtime, temps, i18n no_std (icu4x, ADR-059), futex maison, loom (ADR-058). - Couche 1 — SCELLÉE
couche-1-v1.0(2026-07-03, ADR-062) : campagne close. Audits pré-sceau face-libc (#212) / face-PAL (#213) ; les 4 P0 — AirFile (#214), air-poll (#215), termios/PTY (#216), air-signal (#217) ; remodel os_str env/args pré-sceau (#218) ; doctrines gravées ADR-063 (instrumentation IO) + ADR-064 (signaux, dont §7 barrière de poison). Re-scellée additivement jusqu’àcouche-1-v1.9(v1.1 errno_location ADR-065 ; v1.2 signaux/futex ADR-066 ; v1.3 air-account ADR-067 ; v1.4/v1.5 credentials + drop_to_user ADR-068 ; v1.6 air-handle + moteur socket ADR-069 ; v1.7 Managers de domaine ADR-077 ; v1.8spawn_processsocle posix_spawn ; v1.9 introspection thread cible ADR-086, jalon M5). Earmark ADR-078 cryptoair-tls→ v1.10. - 🎉 Jalon M5 —
stdtourne sur Air, zéro glibc (2026-07-10). Un programme Ruststd(hello-std, static-PIE) lié intégralement contrelibair(la libc d’Air, sans glibc) s’exécute on-target sur les 2 arches (hello from std on air+ exit 0 sur x86_64-air/carbon ET aarch64-air/raspi). Le lien passe de 12 → 0 symboles indéfinis (5 PR #308–#314 :__xpg_strerror_r,open/fcntl, introspection thread [ADR-086],getauxval/pthread_self/détection-pile, TLS keys,syscallgénérique [ADR-087]). Le pari ADR-076 (stdsur la libc d’Air) est tenu de bout en bout : toolchain Rust full-Air prouvée. Re-sceauxcouche-0-v1.12+couche-1-v1.9.
5. Dettes connues (à planifier, non bloquantes pour le sceau)
- 🔧 Escape hatch
syscall/raw_syscall(ADR-087) — à RETIRER avec l’équipe Rust. Le palstdde*-linux-airappelle quelques syscalls (gettid,futex…) via la fonction C génériquesyscall, ce qui a imposé unraw_syscallen couche 0 (entorse assumée à ADR-021 conv. 3 + à la règle « libc bind couche 1 »). Dette explicite : travailler avec l’équipe Rust pour que le palstdsur Air se passe dusyscallgénérique (chaque op via son wrapper typé), puis retirerraw_syscallde la couche 0. - 🔧
GlobalAllocsafe (air-alloc) — à RENDRE safe avec l’équipelibsRust. Doctrine fondatrice : leunsafedoit vivre en couche 0 uniquement (barrière kernel) ; l’impl deGlobalAllocparair-alloc(couche 1) resteunsafepar le trait. But = supprimer ceunsafedans le cas d’Air (action item amont teamslibsRust), même registre queraw_syscall. Voir la doctrineunsafe(macro-architecture, Section 1/2/8). - Lane CI nightly
hello-std/rtà monter : le harnais M5 (rt/, hors-arbre) n’est pas encore construit/exécuté en CI. Leçon gravée : jamais différer les runs raspi. - Gates de couverture couche 1 :
cargo xtask couvrable-vide/audit-exceptionsne réconcilient mécaniquement quecrates/air-sys-*(couche 0). Les crates couche 1 documentent leurs exceptions STRUCTURAL in-code. Étendre les gates à la couche 1 bute sur une collision de basename (time.rscouche 0 ET couche 1) → décision de design de gate (RFC/tâche séparée). - mdBook non pinné — à figer pour la reproductibilité (ADR-025).
air-doc-publish.shvit hors dépôt (speedy) — à éventuellement versionner soustools/.- Instrumentation IO (ADR-063) : décidée, à implémenter en additif
v1.x. - Flake préexistant
air-libc-stdio(révélé souscargo test --workspaceen forte charge, taux faible) : des tests inspectanterrnoaprès échec (fputs_null_and_write_on_readonly,close_reports_backend_error) échouent par intermittence — course probable sur l’état global (registreFILE) entre tests parallèles, bien qu’errnosoitthread_local. Hors périmètre M3 (crate non modifiée). À corriger séparément (sérialiser ces tests).
Ce document se met à jour à la main au fil des merges. Pour l’historique fin
(session par session, PR par PR), voir JOURNAL.md.