13 KiB
STEP20C — isolated EGL/FX probe preparation
Status: EXPERIMENTAL / EGL_FX_PROBE_BLOCKED_BEFORE_HELPER / EGL_NOT_RUN / ALLOCATOR_NOT_ATTEMPTED. The single historical launch budget is consumed; no retry is authorized. New admission implementation: EXPERIMENTAL / IMPLEMENTED_OFFLINE / OFFLINE_REVIEW_ACCEPTED; real cgroup qualification and new EGL runtime NOT_RUN. See the task-limit protocol. The source-only fixture prepares one future measurement, without authorizing execution. STEP20C remains NEXT; production multi-output NOT_YET_SUPPORTED. The Gate 2 V5 failure remains PRISTINE_ALLOCATOR_BLOCKED / CORE_NOT_RUN; V4 failed before compositor execution. Historical V4/V5 LABs and verdicts are immutable.
Measurement contract
Use real pinned SceneFX/wlroots objects without sway_server, compositor, Wayland display/socket, client, IPC or output capture. A headless backend needs only its own event loop; it need not be started or create outputs. Inspect software EGL separately from compatibility of FX render targets. No physical host device, seat, desktop socket, bus or host network may be exposed.
| Quantity | Prepared observation / explicit limit |
|---|---|
| EGL vendor, version, display extensions | Query the actual display owned by the FX path; do not infer from installed metadata |
| Selected software EGL device | Query the device associated with that display and require EGL_MESA_device_software; missing identity is refusal |
| DRM FD | Both backend and renderer getters must return exactly -1; otherwise refusal, no hardware fallback |
| Backend and renderer target caps | Read real headless buffer_caps and renderer render_buffer_caps |
| Texture support | Query format sets separately for DMABUF, DATA_PTR and SHM; no invented texture_caps field |
| Target formats | Query the real FX render format set, distinct from texture formats |
| Target allocation | Always NOT_ATTEMPTED; report pinned-source eligibility separately from allocation or rendering |
Only source-backed, existing interfaces may be used. Internal interfaces must
be identified as such and bound to the exact pinned pair; no public ABI change
or invented getter is permitted. The reviewed choice is explicit
wlr_egl_create_with_drm_fd(-1) followed by fx_renderer_create_egl(egl).
These are the real operations of SceneFX's software creation path, accessed
through the exact internal headers, without reading either private struct.
This measures those constructors; it does not exercise the public
fx_renderer_create(backend) entrypoint or its backend-selection policy.
Require EGL_EXT_device_query or EGL_EXT_device_base and query EGL_DEVICE_EXT from the created display before FX creation. Require EGL_MESA_device_software and reject DRM device extensions before FX initialization: its DRM getter can otherwise attempt to open a render node. The selected device identity is borrowed until EGL destruction, as specified by EGL_EXT_device_query. Before FX construction succeeds the helper owns EGL; after success the renderer owns it. Destroy renderer (or untransferred EGL), then backend, then event loop. Format sets are borrowed until renderer destruction. The declared wlr_fx_renderer_get_egl has no export and must not be used.
No allocator is called: eligibility from real caps plus pinned source is reported separately from target allocation, which is NOT_ATTEMPTED. Without UDMABUF and with DMABUF-only FX targets/FD -1, no pinned allocator route is compatible under this confinement. No general impossibility theorem is claimed. FX construction initializes its own internal resources; the helper calls no buffer/framebuffer creation or draw command. No invented texture_caps field is used. JSONL records EGL identity before FX construction, then formats, caps, FDs and completion. Strings are bounded to 16384 bytes, sets to 256 formats, each format to 64 modifiers, and the four sets to 4096 modifiers total. Missing identity or overflow refuses rather than truncates. Exit 0 means only completed measurement; exit 20 means refused/incomplete. Inaccessible required ABI or a mandatory host device blocks preparation instead of weakening isolation.
Source-only preparation and identity boundary
The eight-file fixture contains probe.c, prepare.py, run.py, cgroup_gate.py, test_probe.py, test_cgroup_admission.py, test_probe_mocks.c and README.md. Its README gives exact preparation and inert-test commands. prepare.py creates a new private mode0700 LAB, extracts SceneFX and wlroots from pinned Git objects, checks every extracted blob, compares compiled installed headers and copies only generated wlroots config/version headers. The pins and public source URLs are recorded in the allocator triage. Compilation uses C17 syntax checking and linking, SceneFX before wlroots; neither the target loader, ldd nor the real helper is executed.
The private manifest seals argv, environments, compile commands, cc -M inputs, source/header/script/tool/helper hashes, ELF identities and DT_NEEDED closure. The historical prepared manifest covers 755 files, 80 ELF identities, 17 compared installed headers and 557 source blobs. Installed ELF bytes/Build IDs are pinned, not reproduced from source; header/export agreement proves no binary source identity. Mesa EGL, swrast, GBM dri and gallium providers are explicit closure seeds; conditional runtime dlopen remains unobserved. The inspected ELF closure has no RPATH/RUNPATH; prepare.py does not enforce a general RPATH rejection policy. Licences remain in private source archives; no binary, raw log or private manifest belongs in Git.
Historical pre-attempt confinement and budget
run.command fixes prlimit then Bubblewrap with --unshare-user --unshare-all --disable-userns --new-session --die-with-parent --clearenv, read-only /usr, /bin → usr/bin and /lib,/lib64 → usr/lib, private /proc and /dev, tmpfs /dev/shm, /tmp and /run, and only the new helper bound read-only at /probe. The working directory is /tmp. No host HOME, /etc, /sys, /run/user, /dev/dri, /dev/input, /dev/udmabuf, seat, desktop socket, bus or host network is exposed. The supervisor environment is exactly PATH=/usr/bin and LC_ALL=C; the helper adds PWD=/tmp, WLR_RENDERER_FORCE_SOFTWARE=1, WLR_RENDERER_ALLOW_SOFTWARE=1, LIBGL_ALWAYS_SOFTWARE=1, LP_NUM_THREADS=2 and __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json. This selects the Mesa ICD; software identity still requires the associated-device query. No caller-supplied mount/environment or automatic alternative ICD is allowed.
Budget: one launch, no retry; 8 s payload plus 2 s cleanup reserve within a nominal supervised 10 s total; 1 MiB each raw stdout/stderr; AS 1 GiB, CPU 5 s, NOFILE 64, FSIZE 1 MiB, CORE 0 and NPROC 256. AS/CPU apply per process; NPROC applies to the global UID, not an aggregate cgroup. The supervisor closes inherited FDs, drains pipes concurrently, uses pidfd when available and cleans only its own unreaped leader/process group; bwrap PID namespace exit terminates its descendants. Unproven collection is failure. This provides no hostile same-UID-peer guarantee. Receipts/raw channels stay bounded and private, with atomic writes/fsync. No systemd scope, compositor, client, socket or capture.
Historical pre-attempt preparation and approval boundary
Offline evidence: 23 Python tests and eight inert C-mock scenarios passed; the real helper passed C17 syntax checking and linking without execution. The C harness replaces all EGL/Wayland/wlroots calls and links only libc. Independent validation confirms the sealed target manifest, all 755 file identities, fixture/source correspondence, passive plan, C17 syntax and inert C tests. Python tests were also independently exercised on identical fixtures in a separate preparation; its different path-dependent manifest is not drift of the target LAB. Historical V5's 101 checked files are unchanged. The unique independent code/protocol review is PASS, with no BLOCKING or NON_BLOCKING defect. Final preparation verdict: EGL_FX_PROBE_READY_FOR_APPROVAL / runtime NOT_RUN. No ATTEMPT exists and the single-launch budget remains unconsumed. This verdict qualifies preparation only; software EGL/FX success remains unmeasured.
The default run.py entrypoint only verifies and prints a passive plan: no files created, Bubblewrap or ELF launched. Copy the manifest digest from the reviewed preparation; do not recompute it to accept drift. The historical command template below is retained for provenance only; it is not an admissible retry procedure:
/usr/bin/env -i PATH=/usr/bin LC_ALL=C /usr/bin/python3 -I \
/var/tmp/NEW_PRIVATE_LAB/fixture/run.py \
--lab /var/tmp/NEW_PRIVATE_LAB --manifest-sha256 REVIEWED_DIGEST \
--approve-native-probe
This is not authorization: a separate conversational approval is required. The flag durably consumes ATTEMPT before spawn; any failure, including spawn failure, forbids retry. Existing ATTEMPT also refuses the passive plan. Invoking the real helper, even --version, remains forbidden in this preparation. No device access, compositor, scope, live IPC, installation or production ABI change is authorized. Runtime remains NOT_RUN.
Observed single attempt — 2026-10-17
This dated result supersedes the pre-attempt READY_FOR_APPROVAL and unconsumed
budget statements above, which remain historical evidence. The approved launch
was made from published commit 99f1fbeb4010c7f3ff82c97138431a3c0c5c387c.
The private historical LAB is retained locally, unchanged; neither its private
manifest nor raw logs are published. Its reviewed manifest SHA256 is
205ca2780bb1ca4d21a71beabd46b9aef27d9b6b64fa6345c749f7f3280e75c8.
Read-only inspection in this mission confirms ATTEMPT with budget_consumed=1,
stdout length 0, stderr length 71, and result cause=exit, returncode=1,
collected=true, elapsed=0.003128459007712081 seconds, error=null.
The diagnostic is bwrap: Creating new namespace failed: Resource temporarily unavailable.
The retained launcher is byte-identical to run.py at baseline 99f1fbeb and applies
prlimit --nproc=256 before Bubblewrap creates namespaces.
The namespace creation failed before the C helper, EGL or SceneFX started. No EGL identity, capability, FD, allocation, texture or target was measured. The strict verdict is EGL_FX_PROBE_BLOCKED_BEFORE_HELPER / EGL_NOT_RUN / ALLOCATOR_NOT_ATTEMPTED, not an EGL initialization failure. Collection of the child is recorded; it does not establish renderer success.
The supplied incident diagnosis reports 93 processes / 1126 threads for real UID 1000, with zero procfs read errors, and the normal process limit 60706. These are historical measurements supplied with the ticket, not a new atomic procfs snapshot. Linux RLIMIT_NPROC accounts threads of the real UID. Setting 256 below that reported load is the very probable cause of the immediate EAGAIN-like namespace failure; no syscall trace proves which kernel counter failed. Namespace permission metadata alone cannot eliminate every other cause. See the Linux manual.
The previous 755 sealed dependency identities and linked helper remain passive preparation evidence, with no new runtime success. The reservation is exhausted even though the helper never started. Never remove ATTEMPT, modify the old LAB, rerun its launcher, or reuse its digest to admit revised inputs. No new native process, Bubblewrap, scope, device exposure or compositor was authorized or executed in this incident follow-up. The subsequent task-limit protocol now records the source-only implementation separately from this historical result.
Subsequent cgroup admission implementation — offline only
The schema2 runner implements a manager-owned probe-local scope, a blocked AF_UNIX peer-credential admission handshake before payload exec, inherited NPROC without the explicit 256 override, bounded collection and owned cleanup. The scope budget is TasksMax=256, RuntimeMaxSec=12s and TimeoutStopSec=2s; normal supervision remains bounded to 10 s including cleanup. Mocked/inert validation exercises the admission and refusal paths without a real scope, Bubblewrap or EGL helper. This establishes IMPLEMENTED_OFFLINE, with final review accepted after a bounded private-observer repair; it does not qualify real manager behavior or Mesa capacity.
Fresh benign nominal and parent-death LABs require distinct manifests and budgets. Parent-death qualification requires external read-only observation: a dead supervisor cannot certify CLEANED, and its private gate.sock can remain as an artifact. Pre-admission manager diagnostics are not retained as raw output; the supervisor error remains available. These limits must stay explicit in the qualification handoff. Real qualification is NOT_RUN and requires new conversational approval after the accepted offline review. Only after benign qualification may a separately reviewed fresh EGL LAB be proposed for its own approval. The historical ATTEMPT, manifest, failure and exhausted one-launch budget remain unchanged. STEP20C stays NEXT; production multi-output remains NOT_YET_SUPPORTED.