# STEP20C — fixture source-only EGL/FX **EXPERIMENTAL / runtime NOT_RUN.** Cette fixture prépare une unique mesure future. Elle n'ajoute aucune dépendance de production, ne démarre aucun compositeur et ne démontre ni allocation ni rendu. Le document de référence est [la proposition de sonde](../../../docs/design/STEP20C_HEADLESS_EGL_FX_PROBE.md). Les anciens LAB restent en lecture seule. `probe.c` crée seulement un event loop, un backend headless non démarré et EGL via le constructeur interne pinned `wlr_egl_create_with_drm_fd(-1)`. Il interroge **le device associé à ce display**, exige `EGL_MESA_device_software` et refuse les extensions DRM avant `fx_renderer_create_egl`. Le constructeur FX initialise ses ressources internes ; la sonde n'appelle ni allocateur, ni création de buffer/framebuffer, ni commande de dessin. Les formats de texture DMABUF, DATA_PTR et SHM sont distincts des formats cibles. Les sets et FDs sont empruntés ; EGL est détruit par le renderer seulement après transfert réussi. Les chaînes sont limitées à 16384 octets, chaque set à 256 formats, chaque format à 64 modifiers et leur total à 4096. Les identités absentes ou un dépassement refusent la mesure ; aucune troncature n'est qualifiée de succès. Sorties JSONL, étape EGL flushée avant FX, statut 0 pour une mesure terminée, 20 pour un refus ou une mesure incomplète. `target_allocation=NOT_ATTEMPTED` reste vrai ; l'éligibilité conditionnelle des allocateurs pinned est une interprétation de capacités, pas une allocation mesurée. Préparation passive, sans exécuter le helper, dans un **nouveau** LAB privé : ```sh python3 tooling/probes/step20c-egl-fx/prepare.py \ --lab /var/tmp/NOUVEAU_LAB_PRIVE \ --source-store /var/tmp/swayfx-mosaic-corec-v2-i516p6po/sources ``` Le script lit les objets git pinned SceneFX `37ccd723bef49e6891156ffafce8f549f01446cc` et wlroots `d783533489e1f75d6886c2ab5c5960090ef268f8`, extrait leurs archives dans le nouveau LAB et vérifie chaque blob. Il copie les seuls headers générés de l'installation et compare les headers publics compilés aux sources pinned. Le contrôle syntaxique utilise C17 avant le link, SceneFX avant wlroots. Le manifeste contient les commandes exactes, les dépendances `cc -M`, les SHA256 des sources/headers/scripts/helper/outils, les identités ELF et la fermeture `readelf` DT_NEEDED. Aucun `ldd`, loader ou helper n'est exécuté. Les bibliothèques installées sont scellées par leurs bytes et Build IDs ; elles ne sont **pas reproduites depuis les sources**, et la correspondance des headers/exportations ne prouve pas leur provenance source. Les licences restent dans les archives privées ; aucun binaire, log ou manifeste privé n'entre dans Git. Les tests Python ne lancent que des processus Python inertes pour exercer les pipes, le timeout et la collecte. Aucun test n'invoque bwrap ou le helper : ```sh PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover \ -s tooling/probes/step20c-egl-fx -p test_probe.py -v ``` Le harnais C substitue **tous** les appels EGL/Wayland/wlroots et inclut le corps réel de `probe.c`. Ses huit scénarios discriminent le device sélectionné, les extensions DRM, le transfert EGL, l'ordre de destruction et les formats. Il lie uniquement libc, sans bibliothèque EGL/FX ; son exécution reste un test inerte, distinct de l'exécution interdite du helper réel. Après préparation : ```sh LAB=/var/tmp/NOUVEAU_LAB_PRIVE cc -std=c17 -Wall -Wextra -Werror -DWLR_USE_UNSTABLE \ -I"$LAB/generated" -I"$LAB/sources/scenefx/include" \ -I"$LAB/sources/wlroots/include" \ $(pkg-config --cflags scenefx-0.5 wlroots-0.20 wayland-server egl glesv2 pixman-1 libdrm) \ tooling/probes/step20c-egl-fx/test_probe_mocks.c -o "$LAB/bin/inert-c-tests" readelf -dW "$LAB/bin/inert-c-tests" "$LAB/bin/inert-c-tests" ``` Le préparateur imprime le digest du manifeste et la commande exacte du **plan passif**. Recopier ce digest depuis la préparation revue, sans le recalculer au moment d'exécuter. Le plan ne crée aucun fichier et n'exécute aucun ELF. Un LAB dont `ATTEMPT` existe refuse aussi le plan, car son budget est déjà consommé. Après une **nouvelle autorisation conversationnelle**, la même commande avec `--approve-native-probe` consomme durablement `ATTEMPT` avant spawn ; toute erreur, tout timeout et même un échec de spawn interdisent un retry : ```sh /usr/bin/env -i PATH=/usr/bin LC_ALL=C /usr/bin/python3 -I \ /var/tmp/NOUVEAU_LAB_PRIVE/fixture/run.py \ --lab /var/tmp/NOUVEAU_LAB_PRIVE --manifest-sha256 DIGEST_REVU \ --approve-native-probe ``` Le lanceur refuse tout argument de mount/env libre et tout drift des identités. Le protocole bwrap exact est construit dans `run.command` et scellé. `/usr` est RO ; seul le helper neuf est lié RO à `/probe`. `/proc`, `/dev`, `/dev/shm`, `/tmp` et `/run` sont privés ; pas de HOME, `/etc`, `/sys`, réseau hôte, socket/bus/session, `/dev/dri`, `/dev/input`, `/dev/udmabuf` ou FD hérité. `PWD=/tmp` est explicite. L'ICD est restreint à Mesa par son JSON sous `/usr` ; ce choix ne prouve pas l'identité logicielle, vérifiée séparément par EGL. Mesa EGL, swrast, GBM dri et gallium plus leurs DT_NEEDED sont scellés ; des chargements conditionnels runtime restent non observés jusqu'à l'unique essai. Le budget est exactement un lancement, 8 s de payload et 2 s de réserve de nettoyage dans un délai nominal supervisé de 10 s au total, 1 MiB par canal brut, AS 1 GiB, CPU 5 s, NOFILE 64, FSIZE 1 MiB, CORE 0, NPROC 256. AS/CPU sont par processus et NPROC s'applique à l'UID global ; ce n'est pas une limite agrégée cgroup. Le superviseur crée sa session, ferme les FDs, lit les deux pipes concurremment, utilise pidfd si disponible, tue seulement son groupe non récolté et récolte son enfant. La fin du namespace PID bwrap termine ses descendants. Une collecte non prouvée est un échec explicite ; aucun PID déjà récolté n'est tué. Ce contrat ne prétend pas durcir le système contre un pair hostile du même UID, ni retirer les capacités de diagnostic `/proc` déjà imposées au protocole. Les canaux bruts et reçus bornés restent privés, avec écritures atomiques et fsync ; aucun scope systemd n'est créé.