Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.md et AGENTS.md.

Rôle des autres documents (pour ne pas les confondre) :

  • INDEX.mdcarte structurelle : ce qui existe, où le trouver.
  • JOURNAL.mdarchive 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.mdplan 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éesair-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 CallReturn (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_fdair-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-onlyregister(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 fdcall → le service serve répond Hello, Adaaucune 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-notifydair-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+rliblibair-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_intas_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+rliblibair-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/ThreadSafeAirClassCell::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_messagegcc 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_macroproc_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.mdfixe 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-objectair-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 ShapeSquare 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 (describefree_string et list_allfree_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.rsregistre 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 std repose 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) : std liait la libc C libair_c — or la frontière C réintroduit de l’unsafe, incohérent avec la doctrine (« unsafe en couche 0 seulement »). ADR-088 amende ADR-076 : PAL custom std::sys::pal::air bindant les Managers couche 1 Rust safe (premier backend std safe de bout en bout), cible target_os="air", libair_c survit mais découplée de std, AirTaskManager remplace AirProcessManager, résorbe raw_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) — std tourne sur Air, ZÉRO glibc, 2 arches. Le lien de hello-std contre libair passe 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, syscall gé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 Rust std static-PIE lié intégralement sans glibc. Pari ADR-076 tenu de bout en bout. Re-sceaux couche-0-v1.12 (ADR-087 raw_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 socket air-socket, surface socket libc complète (30 symboles, libair_c.so) + sendmsg/ recvmsg/SCM_RIGHTS. Re-sceaux couche-0-v1.10 (ADR-070 sockaddr + ADR-071 sockopt) et couche-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, destructeurs thread_local).
  • Bug de fondation aarch64 CORRIGÉ (gdb) : le TCB (64 o) recouvrait la zone TLS (tpidr_el0 base commune TCB+TLS à +16) → tout 2ᵉ #[thread_local] cassait. Fix validé 2 arches (selftest rt x86-air = 42 ET aarch64-air = 42).
  • Chantier B lancé, option A prouvée : build-std=std compile 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-sceau couche-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 symboles air-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).
  • Lot 1 mergé (#322/#323)seul changement cassant : renommage AirProcessManager → AirTaskManager (médie air-process/air-thread/air-runtime + resource_limit) + surface futex/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-air reprofilée target_os="air" (retrait env=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). Fork rust-src localisé réversible (rt/rust-src-air/ : patch + apply-air-pal.sh + copie vendor/ gitignorée, override __CARGO_TESTS_ONLY_SRC_ROOT). std COMPILE pour *-linux-air, 2 arches (x86_64 + aarch64), lien d’un binaire std minimal sans symbole libc indéfini (patch minimal : 5 fichiers std::sys, murs A1–A4). os=air inconnue ⇒ restricted_std (voie standard des ports custom).
  • Incrément 2 — PROTOTYPE rustc-dep-of-std sur air-alloc : PROUVÉ + dé-risqué (commit 5f12621, branche). std compile 2 arches avec le vrai air-alloc (arène mmap/futex couche 0) comme allocateur global — sonde air-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 (3 Cargo.toml, +38 l., feature rustc-dep-of-std + façades rustc-std-workspace-*) — ZÉRO .rs source, 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 via air-base-lib + 37 RustCrypto via air-crypto + 1 bitflags), intenable — MAIS presque entièrement évitable par DÉCOUPLAGE, pas par fork. Aucune externe n’a rustc-dep-of-std amont (toutes no_std) ; les 11 proc-macros compilent pour l’hôte → zéro fork. Trouvaille : les 2 grappes 🔴 n’entrent dans std que par couplage accidentel — (1) AirError/AirResult/AirPath sont définis dans air-base-core (icu-free) ; air-base-lib ne fait que les ré-exporter + ajouter icu ⇒ rediriger les imports Managers fs/net/process/runtime vers air-base-core sort icu4x du graphe std (chemin d’import, zéro sémantique) ; (2) AirRandom::fill n’appelle que air-sys-syscall::getrandom (couche 0) ⇒ isoler le random sort RustCrypto du graphe. Vérifié source : air-base-core définit bien les types, Vague 0 dépend de air-base-core (pas -lib).
  • ADR-090 ratifié (2026-07-11, sur main) — grave la discipline : binding par rustc-dep-of-std (build-config, pas de descellement) ; imports air-base-core jamais air-base-lib ; exceptions 80 % (icu4x/RustCrypto) hors fermeture std ; AirRandom hors crypto ; frontière no_std = relation à std ; industrialisation par vagues.
  • Vague 0 — binding réel du noyau (commit a9769c4, branche) : std compile ET lie pour x86_64 ET aarch64 sur 5 vrais Managersio/errorair-base-core, pal/air::futex+sync/* (Mutex/Condvar/RwLock/Once/Parker réels)→air-thread::raw_futex, stdioair-stdio, envair-env (+ allocair-alloc). Fermeture vérifiée : uniquement bitflags (ni icu4x, ni RustCrypto, ni air-base-lib) — fork count = 1. Zéro .rs couche 0/1 touché, hôte scellé vert. Lien : static-pie zéro glibc ; x86_64 = __tls_get_addr seul indéfini (hook TLS runtime), aarch64 = zéro indéfini. Placeholders restants : random, thread_local.
  • Vague 1a — découplage air-base-libair-base-core (commits b9b4edc/89ba1d4/9d80380, branche) : seuls air-filesystem + air-socket dépendaient directement d’air-base-lib (les 3 autres déjà sur air-base-core). Redirection ABI-neutre, aucun cas d’i18n réel (le seul from_utf8_lossy = wrapper stdlib zéro-icu). Vérifié : air-filesystem/air-socket/air-process ont icu4x=0, air-base-lib=0 dans leur fermeture (fork=1). exit réel bindé sur air-process::exit_process (prouvé au désassemblage : __NR_exit_group, pas abort()). Hôte scellé vert (aucune régression).
  • 🎯 Vague 1b — JALON ATTEINT (commits 396944c/636eda7/168dd4b, branche) : hello-std s’exécute sur le PAL safe. Résidus Vague 0 levés : (1) __tls_get_addr supprimé via "tls-model": "local-exec" dans les specs cibles (TLS statique, accès %fs:OFFSET direct) ; (2) _start abouté au bootstrap d’air-runtime (crt0 Air : self-reloc/TLS/TCB/canari/argv → __air_startbootstrapmainprintln!exit_process), _start hors de std (côté binaire, ne casse pas les #![no_main]). air-runtime+air-memory en rustc-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 _start asm/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 ; asm x19→x21 #150). Le TODO « fix bug spawn aarch64 » était stalesys/thread (PAL) est débloqué côté runtime.
  • Vague 1c — sys/thread réel (commits 2e953ef/2ccb15e, branche) : std::thread::spawn+join s’exécutent sur le PAL safe, 2 arches VÉRIFIÉES superviseur (x86_64/carbon + aarch64/raspi : worker sum=5050, join, exit 0 ; stress 800 threads OK). Binding Thread::newair_runtime::thread::spawn (trampoline extern "C" fn(usize), Box<ThreadInit> pointeur fin, expose_provenance), join→futex CHILD_CLEARTID, set_name/yield_nowair-thread, sleepair-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-runtime déjà bindé (rien à ajouter).
  • air-alloc alignement fort — MERGÉ sur main (#325, 384c80b, 2026-07-11) : implémenté l’alignement fort (align > 16, memalign dlmalloc) dans l’arène, lève ADR-056 D5 (amendé). Découvert via std::sync::mpsc (align 64 → l’ancien refus align>16→null faisait 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 ce main pour en bénéficier).
  • std::sync::mpsc VALIDÉ sur le PAL, 2 arches (branche, merge 927e624 + sonde d85405e) : après intégration du fix air-alloc (merge main, conflit nul), la sonde hello-mpsc (8 producteurs × 1000, somme 32004000) 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épendance path de std (rustc-dep-of-std) ⇒ patch rust-src inchangé.
  • sys/fd + minimal sys/fs réels (commits d32fb79/f05907c, branche) : FileDesc(OwnedFd) → AirFileManager (read/write/read_at/write_at/seek/fsync/ftruncate/set_nonblocking) + duplicateair-handle ; File::open/create/read/write/seek minimal. Aller-retour fichier std prouvé 2 arches (sonde hello-fs : 16 Ko écrits+relus identiques + seek, exit 0 ; x86_64/carbon 10/10 + aarch64/raspi). Zéro additif couche 1, zéro unsafe dans le bras, zéro source couche 0/1, fermeture 1 fork. Frontière assumée (Vfd-3) : std::os::fd non exposé (éviterait un portage net/pipe/stdio-fd complet) — unification air-handle (ADR-069) = chantier distinct.
  • sys/fs COMPLÉTÉ (commits c2879d5/9aa5290, branche) : métadonnées (statxFileAttr/ 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, bras sys/paths/air.rs). Zéro additif couche 1, fermeture propre (fork=1, icu/crypto/regex=0). Prouvé 2 arches (sonde hello-fs2 : read_dir trié + 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::fs largement fonctionnel.
  • sys/process réel (commits 76a8dc1/f372a9c, branche) : std::process::Command spawn/exec/wait/status/kill — bâti sur AirProcess (pidfd : clone3+CLONE_PIDFD/execve/ waitid/pidfd_send_signal, wait+kill en couche 1, pas spawn_process nu qui exigerait un waitid couche 0). Stdio Inherit/Null/Fd ; Piped/output différés (avec std::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éro unsafe, fermeture 1 fork (air-accountair-base-core, découplage tenu).
  • sys/net réel (commits 54a53f6/ce6e8d7, branche) : std::net TCP + UDP — bâti sur air_socket::engine::AirSocket (couche 1 &self, pas les façades &mut self). TcpStream connect/read/write/peek/shutdown/nodelay/keepalive/nonblocking/addrs ; TcpListener bind/accept (SO_REUSEADDR, backlog 128) ; UdpSocket send_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.
  • std FONCTIONNELLEMENT 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/random réel — std FONCTIONNELLEMENT BOUCLÉ (commits 82517f1/12b676e/6e336c0, branche) : isolation par micro-crate air-random (couche 1, no_std, sur getrandom couche 0) — air-crypto la ré-exporte (surface fill/generate_key inchangée, régression hôte 0 : air-crypto 42/42, air-random 3/3). Gate ADR-090 vérifié superviseur : fermeture air-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éro unsafe) → HashMap/RandomState amorcés sur CSPRNG kernel. Prouvé 2 arches (hello-random : entropie réelle distincte + HashMap 1000 entrées, exit 0 ; x86_64/carbon 5/5 + aarch64/raspi). ⇒ tous les murs durs de std franchis.
  • Portage std MERGÉ sur main (#326, ce46a0b, 2026-07-11) : squash, CI verte (test-coverage root + supply-chain + build + loom + cross-aarch64), Verified GitHub. Couche 1 évoluée (découplage air-filesystem/air-socketair-base-core, micro-crate air-random, features rustc-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). Fork rust-src localisé (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 (commit 3689ebb)AsRawFd/OwnedFd, io::pipe, Stdio::Piped/ Command::output débloqués ; zéro additif couche 1 (via sys/pipe+AirStdio::Fd), fermeture 1 fork ; prouvé 2 arches (hello-pipe : io::pipe + capture "hello-air\n" + os::fd, exit 0). (b) ✅ options socket (commit 4105188) — additif couche 1 air-socket (méthodes typées &self, +5 tests, zéro unsafe) : ttl/linger/nodelay+keepalive getters/broadcast/multicast loop+ttl/only_v6/connect_timeout (ppoll+SO_ERROR)/duplicate ; prouvé 2 arches (hello-sockopt set==get, exit 0), fermeture 1 fork, régression hôte 0. Options à STRUCTURE — additif COUCHE 0 v1.13 FAIT (commits 27195dd/1f89c98) : air-sys-types (ip_mreq/ipv6_mreq #[repr(C)], const assert! ABI) + air-sys-syscall (6 wrappers dédiés typés) + couche 1 air-socket + PAL ⇒ read_timeout/write_timeout (+ read vide expiré) + multicast join/leave v4/v6, prouvés 2 arches. Plus aucun unsupported sur les options socket. ⚠️ Barrière : plancher lignes rouge (93.29%<94) = artefact llvm-cov d’hôte (net.rs 64.53% au baseline = impossible pour couche 0 scellée ; branches +12/0-manque) → arbitre = CI test-coverage root ; tag couche-0-v1.13 contingent à CI verte. (c) ✅ DNS FAIT (commits 0ec7032/e18117c) — câblage lookup_host sur AirNameResolver existant (best_effort [hosts, DNS RFC 1035, localhost], IPv6-first) + additif air-socket (AirLocalhostSource, ponts core::net, zéro unsafe) ; prouvé 2 arches (hello-dns : localhost + numérique + connect-par-nom, exit 0), fermeture 1 fork (résolveur maison, getaddrinfo 0). ⇒ TOUS les différés soft traités. hostname() laissé unsupported (tirerait air-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 ADRtoute impl réseau doit s’y conformer (seul motif testant la pile indépendamment). Précurseur : AirDnsSource. À faire avant air-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 Airframework réseau Airair-sshd (démonstration couche 2). En parallèle, à traiter : hygiène pré-merge branche PAL (Cargo.lock stubs façades → deny/audit/ machete + repro), merge de la branche PAL, re-sceau couche 1 (ADR-088 « resceller une fois »). Différés soft std (non bloquants pour un programme courant) : std::os::fd (AsRawFd/FromRawFd) + sys/pipe (io::pipe, chantier libc-fd, débloque Stdio::Piped/Command::output) ; options socket fines (timeouts/ttl/linger/broadcast/multicast) ; DNS (brancher AirNameResolver sur lookup_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) boucler std (fs/process/net) ; (2) couche 2 — la runloop (= air-async, réacteur io_uring↔Waker/exécuteur/timers, pièce maîtresse identifiée dans architecture-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.lock stubs 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 + backend std::sys safe (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/envfd/stdiofutex+sync (les 3 wrappers débloquent Mutex/Condvar/RwLock/Once/Parker) → fsthread+thread_localprocessnet. Manques couche 1 (§5 étude : current_exe, DirEntry typé, park/unpark, options socket) comblés en additifs v1.x au fil de l’eau.
  • 🎯 Jalon de succès : hello-std re-tourne sur le PAL safe, 2 arches, sans libair_c liée — preuve d’un userland Rust safe de bout en bout (unsafe confiné couche 0). Puis re-sceau couche 1 unique + RFC amont (Tier-3 *-linux-air, backend std::sys safe).

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ÉtatDé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_groupkillpg, 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-tlscouche-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::threadpthread_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éesToits (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âcheStatut
P0.1AirFile — handle I/O fichier générique (air-filesystem)✅ Mergé (#214)
P0.2air-poll — multiplexeur ppoll générique (crate dédiée)✅ Mergé (#215)
P0.3termios/PTY/job-control re-exposé (air-terminal)✅ Mergé (#216)
P0.4air-signal — face signal générique, 100 % safe (ADR-064)✅ Mergé (#217)
Remodelos_strAirOsStr/AirOsString, env/args figés (sceau-readiness)✅ Mergé (#218)
§5.4Sceau couche-1-v1.0 — ADR-062 + tag gitSCELLÉ 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 unsafe exposé, erreurs AirError/AirResult, arithmétique défensive (jamais as lossy), nommage ADR-029 (zéro abréviation).
  • Qualité, avant tout merge : cargo fmt/clippy -D/test, cargo xtask barrier (WARN = vert ; 5 advisory unsafe pré-existants security.rs/inotify.rs ignorés), couverture 100 % lignes (exceptions STRUCTURAL = marqueur in-code // COVERAGE (STRUCTURAL, ADR-035), comme air-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 trailer Co-Authored-By. Production déléguée à claude -p headless (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, famille terminal/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.8 spawn_process socle posix_spawn ; v1.9 introspection thread cible ADR-086, jalon M5). Earmark ADR-078 crypto air-tls → v1.10.
  • 🎉 Jalon M5 — std tourne sur Air, zéro glibc (2026-07-10). Un programme Rust std (hello-std, static-PIE) lié intégralement contre libair (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, syscall générique [ADR-087]). Le pari ADR-076 (std sur la libc d’Air) est tenu de bout en bout : toolchain Rust full-Air prouvée. Re-sceaux couche-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 pal std de *-linux-air appelle quelques syscalls (gettid, futex…) via la fonction C générique syscall, ce qui a imposé un raw_syscall en 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 pal std sur Air se passe du syscall générique (chaque op via son wrapper typé), puis retirer raw_syscall de la couche 0.
  • 🔧 GlobalAlloc safe (air-alloc) — à RENDRE safe avec l’équipe libs Rust. Doctrine fondatrice : le unsafe doit vivre en couche 0 uniquement (barrière kernel) ; l’impl de GlobalAlloc par air-alloc (couche 1) reste unsafe par le trait. But = supprimer ce unsafe dans le cas d’Air (action item amont teams libs Rust), même registre que raw_syscall. Voir la doctrine unsafe (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-exceptions ne réconcilient mécaniquement que crates/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.rs couche 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.sh vit hors dépôt (speedy) — à éventuellement versionner sous tools/.
  • Instrumentation IO (ADR-063) : décidée, à implémenter en additif v1.x.
  • Flake préexistant air-libc-stdio (révélé sous cargo test --workspace en forte charge, taux faible) : des tests inspectant errno après échec (fputs_null_and_write_on_readonly, close_reports_backend_error) échouent par intermittence — course probable sur l’état global (registre FILE) entre tests parallèles, bien qu’errno soit thread_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.